Dormant Actors that are a part of Level Streaming can become desynced if flushed while not loaded

Hi, I believe we’ve found an edge case of dormancy as it pertains to streaming level actors. Seems that if a dormant actor gets flushed after being made invisible on client, there’s a chance that it can become desynced on client when it later comes back into range. The logic for this case is as follows:

  • While in replication and streaming range of the actor, let it go dormant
  • Move away from the actor so that it goes out of replication and streaming range
  • While the actor is out of range, flush its dormancy
  • Wait long enough so that the level the actor is on is fully unloaded, not just hidden, so that the actor is actually destroyed.
  • Move back in range of the actor
  • The replicated actor will be missing initial replication data

What I’ve observed that causes this issue is that while the actor is dormant, if we move away from replication range, the actor stays in the NetDriver’s NetworkObjectList as dormant for the connection. If we then flush dormancy on the actor, the FNetworkObjectList::MarkActive() check in UNetConnection::FlushDormancy() will pass, and we will proceed with creating a replicator for the actor and adding to the DormantReplicatorSet on the connection so that it’s picked up later when we supposedly replicate this actor. However, since it’s out of range, it won’t replicate the actor and it’ll just leave a Replicator there.

If we then come back into range after the actor is fully destroyed on client, the server will pick up this replicator when creating the new actor channel, and begin replicating the actor as if it had already sent all the previous information, when it should instead make a new replicator to start replication anew for this actor.

Is this a known issue? Is there somewhere that we’re missing a MarkActive call, say in UNetConnection::UpdateLevelVisibilityInternal() when the client notifies the server that it has unloaded a level?

EDIT: Forgot to mention, but we’re using the ReplicationGraph plugin in our project.

Best,

João Patrício

Hi,

To get a better idea of the problem, I have a couple of questions.

First, are you able to reproduce this without using the replication graph?

Next, how are these dormant actors routed and handled by the replication graph, and are you using any non-default settings for your replication graph around dormancy (e.g. Net.RepGraph.DormantDynamicActorsDestruction)?

Finally, just to double check my understanding, to reproduce the issue the dormant actor needs to be flushed after the client has gone out of relevancy range for the actor but before the level is fully unloaded on the client?

Thanks,

Alex

Hi Alex, thanks for the help!

We could try to, but the replication graph is very tightly integrated with our project, so we’d have to make a dedicated test in a vanilla UE build.

The actors in question are static, so we’re adding them to a UReplicationGraphNode_GridSpatialization2D node with AddActor_Static. We do have Net.RepGraph.DormantDynamicActorsDestruction turned on but seeing as these actors aren’t dynamic this shouldn’t affect them in any way, is that correct?

Finally on the reproduction steps, whether the level has already fully unloaded when we flush dormancy doesn’t affect the repro, what matters is that the level does fully unload before we go back in replication range of the actor, so that it spawns a new instance of it on the client instead of just adding the one hidden by level streaming back into the world.

Any other info that’s needed let me know!

Thank you,

João Patrício

Hi,

Yes, if these actors aren’t dynamic then Net.RepGraph.DormantDynamicActorsDestruction shouldn’t apply to them.

Unfortunately, I’ve been unable to reproduce the issue. In my own testing, once the client re-loads the streaming level and enters back into the relevancy range of the dormant actor, it does receive the latest values for its replicated properties.

If you are able to provide a basic sample project reproducing the issue, that would be greatly appreciated.

Also just to double check, do you see the issue if these actors are routed to the grid spatialization node with AddActor_Dormancy?

Thanks,

Alex