destroying an actor on client in response to it being torn off leads to an ensure because the EEndReplicationFlags change

Hi Iris friends!

I notice in 5.6 you’ve added delayed replication-stopping to UReplicationBridge::StopReplicatingNetRefHandle so that InternalDetachInstanceFromNetRefHandle doesn’t happen while reading.

I’m hitting an ensure, though, when my client decides that in a certain actor’s `TornOff()` method, it needs to destroy itself instead of just being torn off. The destroy call ends up trying to stop replicating a second time in the same frame, and we hit this ensure:

{
	EEndReplicationFlags* PreviousFlags = HandlesToStopReplicating.Find(Handle);
	const bool bPreviousFlagsMatch = PreviousFlags ? (*PreviousFlags) == EndReplicationFlags : true;
	ensureMsgf(bPreviousFlagsMatch, TEXT("Received multiple StopReplicating calls for %s with different EndReplicationFlags: Previous: %s | Newest: %s"),
		*NetRefHandleManager->PrintObjectFromNetRefHandle(Handle), *LexToString(*PreviousFlags), *LexToString(EndReplicationFlags));
}

the problem is that the first time the flags were just for the tear off being replicated, and the second time the flags are set to destroy the object (this is because destroying the object triggers EndPlay which calls EndsReplication, which ends up calling UReplicationBridge::StopReplicatingNetRefHandle with Destroy flags as a param)

What’s the fix here? Is there an engine fix that should be made, or is it just illegal to call Destroy on the client on an actor in response to it being TornOff?

Thanks!

Josh

[Attachment Removed]

Hi,

Thank you for bringing this to our attention! This does seem to be a bug, as I believe it should be a supported operation to destroy a torn off actor on the client.

I’ve opened a new issue for this, UE-357753, which should be visible in the public tracker in a day or so. In the meanwhile, the simplest workaround is likely to delay the Destroy call until after the update, when the replication bridge has processed all the delayed calls to StopReplication.

Thanks,

Alex

[Attachment Removed]

thanks, good to know! I made this potentially naughty hack that allows “upgrading” the flags to destroyed, but it might be error prone…

// if the new flag contains destroy, we'll allow it- you just can't go backwards
// const bool bPreviousFlagsMatch = PreviousFlags ? (*PreviousFlags) == EndReplicationFlags : true;
const bool bPreviousFlagsMatch = PreviousFlags ? (*PreviousFlags) == EndReplicationFlags || EnumHasAnyFlags(EndReplicationFlags, EEndReplicationFlags::DestroyNetHandle) : true;

[Attachment Removed]

Hi,

Thank you for the potential workaround! I’m also not sure if this could have undesirable side effects or not, as in this situation, the “Destroy” is coming from a client that believes the actor is no longer replicated. That being said, I’ve made a note of this on the internal tracker for the issue.

Thanks,

Alex

[Attachment Removed]