From 9ded33b71719892e0a74d2f05193f9de2c5ced1f Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 7 Sep 2026 12:26:59 +0200 Subject: [PATCH] Fix danging replication queue items and parts after client disconnected --- .../Engine/Networking/NetworkReplicator.cpp | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/Source/Engine/Networking/NetworkReplicator.cpp b/Source/Engine/Networking/NetworkReplicator.cpp index 002e3c9ea..a460fbe2c 100644 --- a/Source/Engine/Networking/NetworkReplicator.cpp +++ b/Source/Engine/Networking/NetworkReplicator.cpp @@ -217,6 +217,7 @@ struct SpawnItem struct SpawnItemParts { NetworkMessageObjectSpawn MsgData; + uint32 OwnerClientId; // Duplicate of MsgData.OwnerClientId to reuse template code for other structs Guid PrefabId; Array Items; }; @@ -511,6 +512,38 @@ FORCE_INLINE void BuildCachedTargets(const NetworkReplicatedObject& item, const BuildCachedTargets(NetworkManager::Clients, item.TargetClientIds, item.OwnerClientId, clientsMask); } +template +void RemoveClientFromTargets(Array& items, uint32 clientId) +{ + for (int32 i = items.Count() - 1; i >= 0; i--) + { + auto& rpc = items[i]; + if (SpanContains(rpc.Targets, clientId)) + { + if (rpc.Targets.Length() == 1) + { + items.RemoveAt(i); + } + else + { + Array targets = rpc.Targets; + targets.Remove(clientId); + rpc.Targets = MoveTemp(targets); + } + } + } +} + +template +void RemoveOwnerFromItems(Array& items, uint32 clientId) +{ + for (int32 i = items.Count() - 1; i >= 0; i--) + { + if (items[i].OwnerClientId == clientId) + items.RemoveAt(i); + } +} + void SetupObjectSpawnMessageItem(SpawnItem* e, NetworkMessage& msg) { ScriptingObject* obj = e->Object.Get(); @@ -1885,6 +1918,7 @@ bool NetworkReplicator::EndInvokeRPC(ScriptingObject* obj, const ScriptingTypeHa void NetworkInternal::NetworkReplicatorClientConnected(NetworkClient* client) { + PROFILE_CPU(); ScopeLock lock(ObjectsLock); NewClients.Add(client); @@ -1917,6 +1951,7 @@ void NetworkInternal::NetworkReplicatorClientConnected(NetworkClient* client) void NetworkInternal::NetworkReplicatorClientDisconnected(NetworkClient* client) { + PROFILE_CPU(); ScopeLock lock(ObjectsLock); NewClients.Remove(client); @@ -1943,10 +1978,19 @@ void NetworkInternal::NetworkReplicatorClientDisconnected(NetworkClient* client) Objects.Remove(it); } } + + // Remove any pending RPCs, replication or spawn actions for that client + RemoveClientFromTargets(RpcQueue, clientId); + RemoveClientFromTargets(SpawnQueue, clientId); + RemoveClientFromTargets(DespawnQueue, clientId); + RemoveOwnerFromItems(SpawnQueue, clientId); + RemoveOwnerFromItems(SpawnParts, clientId); + RemoveOwnerFromItems(ReplicationParts, clientId); } void NetworkInternal::NetworkReplicatorClear() { + PROFILE_CPU(); ScopeLock lock(ObjectsLock); // Cleanup @@ -2293,6 +2337,7 @@ void NetworkInternal::OnNetworkMessageObjectSpawn(NetworkEvent& event, NetworkCl // Allocate spawn message parts collecting auto& parts = SpawnParts.AddOne(); parts.MsgData = msgData; + parts.OwnerClientId = msgData.OwnerClientId; parts.PrefabId = prefabId; parts.Items.Resize(msgData.ItemsCount); for (auto& item : parts.Items)