HUDOverlay on VehicleWeapons sometimes do not display

Issue #395 resolved
Colin Basnett created an issue

Seems to only happen in multiplayer. Try moving between driver and machine-gunner on halftracks, it happens about maybe 5% of the time.

Comments (6)

  1. Matt Hands

    OK, so neither Theel or I can reproduce this, on the test server, trying hundreds of times. But, if Basnett can get it to happen with some regularity (5% is more than good enough) then we can log where the HUD overlay is going wrong. Knowing where will usually suggest how and with how we can usually fix. So ........

    A HUD overlay is spawned or destroyed by the Vehicle.ActivateOverlay(bool bActive) function. If it goes wrong then logic says it must be one of 3 causes:

    1. The overlay is not spawned when we expect it to be.
    2. The overlay is spawned as expected, but is then destroyed.
    3. The overlay is spawned but isn't being drawn on screen.

    An overlay should be spawned clientside in ClientKDriverEnter(), obviously when the player gets into the vehicle weapon, providing the player is not in behind view (and entering should take you out of behind view anyway). This seems reliable and it hard to see why this wouldn't happen.

    An overlay can be destroyed in several places. Usually on exit, but that isn't relevant. But POVChanged() is a suspect and a slightly strange function. And DrawHUD() will destroy an overlay if there is no PlayerController on the 1st pass after entering, or it's still in behind view. And if DrawHUD() does that, I don't think there's anything to re-spawn the overlay, except exit and re-enter. Other destroy points are much more obscure, so I think POVChaned() & DrawHUD() are our 2 main candidates.

    And if we have an overlay, it should always get drawn unless IsSoftwareRendering() is true. That assumes the weapon pawn's DrawHUD() is being called, which it looks like it is if the HUD has a PlayerOwner.

    Overall it's hard to see how the overlay isn't being spawned on entering or that it isn't drawn if it is spawned. So I think cause 2 is more likely, where something unwanted causes the overlay to be destroyed after it has spawned on entering.

    I'll work on the debug.

  2. Matt Hands

    Should be fixed in commit 66f2398. I'll leave this open for a little while so the fix can be confirmed in testing.

    Once the cause is identified, the fix is pretty simple. But the replication problems that caused this probably have wider implications, so please do read carefully. Also very useful, I think, is the method I found to recreate the bug, which allowed me to debug, identify & fix the problem.

    The problem was no.2, from my list above (overlay spawned as expected, but is then destroyed). It's a UT2004 bug.

    The problem was caused on a net client when entering a VehicleWeaponPawn, when the VWP's DrawHUD() function got called before the VWP received its new Controller actor reference through replication. VWP.DrawHUD() is badly written. What it tries to say is "if we're in behind view then let's destroy any HUD overlay", which is fine although unnecessary. But the ways it's written, it also destroys any overlay if there's no PlayerController, not just if a PC is in behind view. So if there's no PC yet and we've just spawned an overlay on entering the vehicle, DrawHUD() destroys it immediately. And once the overlay is destroyed, nothing spawns it again, until we leave the position or switch to & from behind view.

    Without doing anything fancy, we can easily fix this by removing this destroy overlay instruction in DrawHUD(). It isn't necessary, as an overlay simply won't be drawn in behind view and will always be destroyed on exiting the vehicle/VWP. We could also simply return from the function at the beginning if there's no PC.

    But looking at the root cause of this - and other - problem(s), it's the common replication timing bugbear. Under bad network conditions, the replication of the new Controller must have taken a bit longer than normal and some functionality happened before the critical new variable had been received.

    Normally the problem is when we set/update replicated variables on the server, then call a replicated function on the client that critically needs the altered variables. Function replication happens asap, but variable replication depends on net update timing calcs, so the variables often don't reach the client until after the replicated function call. The usual method of combating that problem is to force a quick net update by setting NetUpdateTime = Level.TimeSeconds - 1.

    In this case it's a little different, because it's one of the basic actor references that isn't being received in time. But the same thing really, because the lack of one of those basic actor refs causes client functionality to fail.

    KDriverEnter() is the main entry function on the server, which makes the player's Controller unpossess the old pawn and possess the vehicle/VWP, resulting in PossessedBy() being called on the V/VWP. In PossessedBy(), several critical, replicated variables are set: the new Controller, the Owner (the Controller), & an enhanced Role of AutonomousProxy. ClientKDriverEnter() gets called on the owning client, which triggers clientside functionality like spawning any HUD overlay and any mesh and/or FOV switching.

    PossessedBy() in the Vehicle & Pawn classes does try to force a quick net update, but for whatever reason it isn't working reliably, at least not for some players under some network conditions. I was able to recreate this by artificially delaying a net update in the server's vehicle enter/possession functionality. I re-stated PossessedBy() and instead of upping the NetUpdateFrequency to 100 (variables considered for replication 100 times a second) and setting the next NetUpdateTime to be 1 second n the past, I did the opposite: lowering NetUpdateFrequency to 1 (only once per second) & NetUpdateTime to be 1 second in the future.

    So basically, I enforced a 1 second delay before variable replication. This is enough to reliably recreate this bug, which I had never seen before. The approach can be used to recreate & debug ANY replication timing bug, which is very useful.

    The same basic cause is behind issue 424 - tank interiors do not draw properly. In that case it's the changed vehicle/VWP Role of AutonomousProxy that can take to long to reach the client. When you enter a vehicle the server changes its RemoteRole and that variable replicates. But if ClientKDriverEnter() gets called on the client 1st, it goes to state 'EnteringVehicle' and tries to switch to the interior mesh. But if the net client actor hasn't received ROLE_AutonomousProxy yet, SwitchMesh() does nothing. Delaying the net update in PossessedBy() also reliably recreates this problem too.

    Now, an interesting idea, which I believe will work and will solve the root cause of a number of these vehicle entry replication timing bugs: when ClientKDriverEnter() gets called on the owning client, the new PlayerController is passed with the function. So although the client may not yet have it's new Controller ref, the server has just passed it a PC ref as a function argument. So a client can simply set its Controller ref right here, without waiting for variable replication. And the new Owner will always be the PC, so Owner can be set there too. And the new Role will always be AutonomousProxy, so that can be safely set too. I'll commit that fix separately; it can sit alongside the DrawHUD() changes, but it offers a more fundamental fix.

  3. Matt Hands

    In commit f9de568 I've implemented the idea of setting the key variables on a client in ClientKDriverEnter(), without waiting for the new variable properties to replicate. I think it works well and also avoids the short but noticeable delay & camera stutter when entering vehicles/weapon positions in laggy conditions, i.e. as soon as the client knows you are entering (ClientKDriverEnter being called) the key properties are set, without waiting for variable replication

  4. Log in to comment