diff --git a/Source/Engine/Networking/NetworkInternal.h b/Source/Engine/Networking/NetworkInternal.h index 4840ea796..250eae128 100644 --- a/Source/Engine/Networking/NetworkInternal.h +++ b/Source/Engine/Networking/NetworkInternal.h @@ -11,6 +11,7 @@ enum class NetworkMessageIDs : uint8 HandshakeReply, ReplicatedObject, SpawnObject, + DespawnObject, MAX, }; @@ -25,4 +26,5 @@ public: static void NetworkReplicatorUpdate(); static void OnNetworkMessageReplicatedObject(NetworkEvent& event, NetworkClient* client, NetworkPeer* peer); static void OnNetworkMessageSpawnObject(NetworkEvent& event, NetworkClient* client, NetworkPeer* peer); + static void OnNetworkMessageDespawnObject(NetworkEvent& event, NetworkClient* client, NetworkPeer* peer); }; diff --git a/Source/Engine/Networking/NetworkManager.cpp b/Source/Engine/Networking/NetworkManager.cpp index 74da45e8a..e9390b75d 100644 --- a/Source/Engine/Networking/NetworkManager.cpp +++ b/Source/Engine/Networking/NetworkManager.cpp @@ -130,6 +130,7 @@ namespace OnNetworkMessageHandshakeReply, NetworkInternal::OnNetworkMessageReplicatedObject, NetworkInternal::OnNetworkMessageSpawnObject, + NetworkInternal::OnNetworkMessageDespawnObject, }; } diff --git a/Source/Engine/Networking/NetworkReplicator.cpp b/Source/Engine/Networking/NetworkReplicator.cpp index 5c3df9de0..64f1df4b0 100644 --- a/Source/Engine/Networking/NetworkReplicator.cpp +++ b/Source/Engine/Networking/NetworkReplicator.cpp @@ -43,6 +43,12 @@ PACK_STRUCT(struct NetworkMessageSpawnObject char ObjectTypeName[128]; // TODO: introduce networked-name to synchronize unique names as ushort (less data over network) }); +PACK_STRUCT(struct NetworkMessageDespawnObject + { + NetworkMessageIDs ID = NetworkMessageIDs::SpawnObject; + Guid ObjectId; + }); + struct NetworkReplicatedObject { ScriptingObjectReference Object; @@ -93,6 +99,7 @@ namespace CriticalSection ObjectsLock; HashSet Objects; Array> SpawnQueue; + Array DespawnQueue; Dictionary IdsRemappingTable; NetworkStream* CachedWriteStream = nullptr; NetworkStream* CachedReadStream = nullptr; @@ -262,11 +269,32 @@ void NetworkReplicator::SpawnObject(ScriptingObject* obj) it = Objects.Find(obj->GetID()); } - // Register for spawning (batched spawning during update) + // Register for spawning (batched during update) ASSERT_LOW_LAYER(!SpawnQueue.Contains(obj)); SpawnQueue.Add(obj); } +void NetworkReplicator::DespawnObject(ScriptingObject* obj) +{ + if (!obj || NetworkManager::State == NetworkConnectionState::Offline) + return; + ScopeLock lock(ObjectsLock); + const auto it = Objects.Find(obj->GetID()); + if (it == Objects.End()) + return; + auto& item = it->Item; + if (item.Object != obj || !item.Spawned || item.OwnerClientId != NetworkManager::LocalClientId) + return; + + // Register for despawning (batched during update) + const Guid id = obj->GetID(); + ASSERT_LOW_LAYER(!DespawnQueue.Contains(id)); + DespawnQueue.Add(id); + + // Prevent spawning + SpawnQueue.Remove(obj); +} + uint32 NetworkReplicator::GetObjectClientId(ScriptingObject* obj) { uint32 id = 0; @@ -389,6 +417,30 @@ void NetworkInternal::NetworkReplicatorUpdate() return; } + // Despawn + if (DespawnQueue.Count() != 0) + { + PROFILE_CPU_NAMED("DespawnQueue"); + for (const Guid& e : DespawnQueue) + { + // Send despawn message + NetworkMessageDespawnObject msgData; + msgData.ObjectId = e; + if (isClient) + { + // Remap local client object ids into server ids + IdsRemappingTable.KeyOf(msgData.ObjectId, &msgData.ObjectId); + } + NetworkMessage msg = peer->BeginSendMessage(); + msg.WriteStructure(msgData); + if (isClient) + peer->EndSendMessage(NetworkChannelType::ReliableOrdered, msg); + else + peer->EndSendMessage(NetworkChannelType::ReliableOrdered, msg, CachedTargets); + } + DespawnQueue.Clear(); + } + // Spawn if (SpawnQueue.Count() != 0) { @@ -603,3 +655,32 @@ void NetworkInternal::OnNetworkMessageSpawnObject(NetworkEvent& event, NetworkCl // TODO: if we're server then spawn this object further on other clients } } + +void NetworkInternal::OnNetworkMessageDespawnObject(NetworkEvent& event, NetworkClient* client, NetworkPeer* peer) +{ + NetworkMessageDespawnObject msgData; + event.Message.ReadStructure(msgData); + ScopeLock lock(ObjectsLock); + NetworkReplicatedObject* e = ResolveObject(msgData.ObjectId); + if (e) + { + auto& item = *e; + ScriptingObject* obj = item.Object.Get(); + if (!obj || !item.Spawned) + return; + + // Reject despawn from someone who is not an object owner + if (client && e->OwnerClientId != client->ClientId) + return; + + // Remove object + Objects.Remove(obj); + Delete(obj); + } + else + { +#if NETWORK_REPLICATOR_DEBUG_LOG + LOG(Error, "[NetworkReplicator] Failed to despawn object {}", msgData.ObjectId); +#endif + } +} diff --git a/Source/Engine/Networking/NetworkReplicator.h b/Source/Engine/Networking/NetworkReplicator.h index 560ae3ae0..68665c712 100644 --- a/Source/Engine/Networking/NetworkReplicator.h +++ b/Source/Engine/Networking/NetworkReplicator.h @@ -66,6 +66,13 @@ public: /// The object to spawn on other clients. API_FUNCTION() static void SpawnObject(ScriptingObject* obj); + /// + /// Despawns the object from the other clients. + /// + /// Does nothing if network is offline. + /// The object to despawn on other clients. + API_FUNCTION() static void DespawnObject(ScriptingObject* obj); + /// /// Gets the Client Id of the network object owner. ///