We have a project that uses Mass and replicated entities. We are trying to upgrade it to UE5.8 but we are facing an issue when destroying replicated entities.
On the method UMassReplicationProcessor::Execute, the execution is being halt by the checkf present at the end of that method that checks AgentData.Handle is not valid (with message “This replicated agent should have been removed from this client and was not”). After debugging the code, we found that when an entity has b PendingDestruction == true its not added to the entity set processed to replicate. Therefore, method ProcessClientReplication is not being called and the entity is not invalidated by CalculateClientReplication. As a side note, it does not seem adequate to have a check there, as it halts the execution. Maybe an ensureMsg should be enough?
Also, for this setup, we had to set value to 0 of CVar mass.UseProcessingQueue because the UMassReplicationProcessor copies it’s EntityQuery to the FMassReplicationSharedFragment and then Replicator adds it’s custom requierements. Therefore when executing the query and checking it’s correctly registered on FQueuedProcessor::SetCurrentQuery it cannot find the query in the list of registered queries since it is comparing by pointer.
We are not sure if the problem is in the engine or on our end. Some help would be appreciated.
What Type of Bug are you experiencing?
Gameplay
Steps to Reproduce
Use UMassReplicationProcessor::Execute to replicate entities on UE5.7
Update engine to UE 5.8
Check the status of replicated entities
Expected Result
Mass Replicated Entities are destroyed after flagging them as pending_destruction=true when we upgrade to UE5.8
Observed Result
Mass Replicated Entities are not getting destroyed when we tried to upgrade to UE5.8
Hello @vselvEPS ,Welcome to the forums!
I’m seeing the same behavior on UE 5.8.2, and after debugging it a bit further I think there are actually two separate issues involved here.
First, regarding the bPendingDestruction path: I would check what your replicator does inside its OnEntityRemovedCallback / equivalent removal callback. In my case, the replicated agent data was being marked for destruction, but the actual replicated handle was only invalidated when the replicator’s removal path ran.
The problem is that UMassReplicationProcessor::Execute() only calls CachedReplicator->ProcessClientReplication() from inside RepSharedFragment.EntityQuery.ForEachEntityChunk(...). If the destroyed entity is the last entity matching that shared fragment/query, there are no chunks left to iterate, so ProcessClientReplication() is never called at all. That means the removal callback never runs, the replicated handle remains valid, and execution eventually reaches:
checkf(!AgentData.Handle.IsValid(),
TEXT("This replicated agent should have been removed from this client and was not"));
So there is an additional edge case here: destroying the last replicated entity for that replication setup can reliably hit this assert simply because the replicator gets no chance to process pending removals.
I worked around this by processing pending removals at a higher level, before the EntitySets / entity query iteration, using the existing FMassReplicationSharedFragment and its cached replicator. That way pending removals are processed even when there are zero live entities/chunks left.
I would not just replace the checkf with an ensure, because the assert is exposing a real state inconsistency: bPendingDestruction is true while the replicated handle is still valid. The real issue is that the code path responsible for invalidating/removing that handle is skipped.
Separately, I can confirm the mass.UseProcessingQueue issue as well. With the processing queue enabled, UMassReplicationProcessor copies its EntityQuery into FMassReplicationSharedFragment, then the replicator adds custom requirements to that copied query. FQueuedProcessor::SetCurrentQuery() later tries to match the executing query against the registered query list by pointer identity, so the copied query is not found and triggers the “Processor attempting to run a query it doesn’t own” assert. Setting:
mass.UseProcessingQueue 0
avoids that crash and Mass replication works again through the legacy execution path.
So at least from what I’m seeing, both issues look engine-side rather than project-specific:
copied MassReplication queries are incompatible with the new ProcessingQueue query ownership tracking;
pending removals depend on ProcessClientReplication() running from an entity query, so removing the last matching entity prevents the replicator from processing its own removal state.
It would be worth checking whether your OnEntityRemovedCallback is the place that actually invalidates the agent handle. If so, I suspect you are hitting the exact same last-entity case.