From 70cfbada04247d1540f342addb1b52a2beb353fd Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Wed, 2 Nov 2022 12:13:44 +0100 Subject: [PATCH] Add support for spawning network object on custom set of clients --- .../Engine/Networking/NetworkReplicator.cpp | 124 ++++++++++++++---- Source/Engine/Networking/NetworkReplicator.h | 11 ++ 2 files changed, 113 insertions(+), 22 deletions(-) diff --git a/Source/Engine/Networking/NetworkReplicator.cpp b/Source/Engine/Networking/NetworkReplicator.cpp index af8a5de17..c0959489d 100644 --- a/Source/Engine/Networking/NetworkReplicator.cpp +++ b/Source/Engine/Networking/NetworkReplicator.cpp @@ -13,6 +13,8 @@ #include "Engine/Core/Log.h" #include "Engine/Core/Collections/HashSet.h" #include "Engine/Core/Collections/Dictionary.h" +#include "Engine/Core/Types/DataContainer.h" +#include "Engine/Core/Types/Pair.h" #include "Engine/Platform/CriticalSection.h" #include "Engine/Engine/EngineService.h" #include "Engine/Level/Actor.h" @@ -81,6 +83,7 @@ struct NetworkReplicatedObject #if NETWORK_REPLICATOR_DEBUG_LOG uint8 InvalidTypeWarn = false; #endif + DataContainer TargetClientIds; bool operator==(const NetworkReplicatedObject& other) const { @@ -114,11 +117,17 @@ struct Serializer void* Tags[2]; }; +struct SpawnItem +{ + ScriptingObjectReference Object; + DataContainer Targets; +}; + namespace { CriticalSection ObjectsLock; HashSet Objects; - Array> SpawnQueue; + Array SpawnQueue; Array DespawnQueue; Dictionary IdsRemappingTable; NetworkStream* CachedWriteStream = nullptr; @@ -200,6 +209,63 @@ NetworkReplicatedObject* ResolveObject(Guid objectId, Guid parentId, char object return nullptr; } +void BuildCachedTargets(const Array& clients) +{ + CachedTargets.Clear(); + // TODO: skip building cached targets if previous frame send it to all clients + for (const NetworkClient* client : clients) + { + if (client->State == NetworkConnectionState::Connected) + CachedTargets.Add(client->Connection); + } +} + +void BuildCachedTargets(const Array& clients, const NetworkClient* excludedClient) +{ + CachedTargets.Clear(); + for (const NetworkClient* client : clients) + { + if (client->State == NetworkConnectionState::Connected && client != excludedClient) + CachedTargets.Add(client->Connection); + } +} + +void BuildCachedTargets(const Array& clients, const DataContainer& clientIds) +{ + CachedTargets.Clear(); + if (clientIds.IsValid()) + { + for (const NetworkClient* client : clients) + { + if (client->State == NetworkConnectionState::Connected) + { + for (int32 i = 0; i < clientIds.Length(); i++) + { + if (clientIds[i] == client->ClientId) + { + CachedTargets.Add(client->Connection); + break; + } + } + } + } + } + else + { + // TODO: skip building cached targets if previous frame send it to all clients + for (const NetworkClient* client : clients) + { + if (client->State == NetworkConnectionState::Connected) + CachedTargets.Add(client->Connection); + } + } +} + +FORCE_INLINE void BuildCachedTargets(const NetworkReplicatedObject& item) +{ + BuildCachedTargets(NetworkManager::Clients, item.TargetClientIds); +} + void SendObjectSpawnMessage(const NetworkReplicatedObject& item, ScriptingObject* obj) { NetworkMessageObjectSpawn msgData; @@ -247,12 +313,7 @@ void SendObjectRoleMessage(const NetworkReplicatedObject& item, const NetworkCli } else { - CachedTargets.Clear(); - for (const NetworkClient* client : NetworkManager::Clients) - { - if (client->State == NetworkConnectionState::Connected && client != excludedClient) - CachedTargets.Add(client->Connection); - } + BuildCachedTargets(NetworkManager::Clients, excludedClient); peer->EndSendMessage(NetworkChannelType::ReliableOrdered, msg, CachedTargets); } } @@ -382,6 +443,12 @@ void NetworkReplicator::RemoveObject(ScriptingObject* obj) } void NetworkReplicator::SpawnObject(ScriptingObject* obj) +{ + DataContainer clientIds; + SpawnObject(obj, MoveTemp(clientIds)); +} + +void NetworkReplicator::SpawnObject(ScriptingObject* obj, const DataContainer& clientIds) { if (!obj || NetworkManager::State == NetworkConnectionState::Offline) return; @@ -391,7 +458,9 @@ void NetworkReplicator::SpawnObject(ScriptingObject* obj) return; // Skip if object was already spawned // Register for spawning (batched during update) - SpawnQueue.AddUnique(obj); + auto& item = SpawnQueue.AddOne(); + item.Object = obj; + item.Targets.Copy(clientIds); } void NetworkReplicator::DespawnObject(ScriptingObject* obj) @@ -412,7 +481,14 @@ void NetworkReplicator::DespawnObject(ScriptingObject* obj) DespawnQueue.Add(id); // Prevent spawning - SpawnQueue.Remove(obj); + for (int32 i = 0; i < SpawnQueue.Count(); i++) + { + if (SpawnQueue[i].Object == obj) + { + SpawnQueue.RemoveAt(i); + break; + } + } // Delete object locally DeleteNetworkObject(obj); @@ -530,7 +606,7 @@ void NetworkInternal::NetworkReplicatorClear() SAFE_DELETE(CachedWriteStream); SAFE_DELETE(CachedReadStream); NewClients.Clear(); - CachedTargets.Resize(0); + CachedTargets.Clear(); } void NetworkInternal::NetworkReplicatorPreUpdate() @@ -558,27 +634,20 @@ void NetworkInternal::NetworkReplicatorUpdate() // Sync any previously spawned objects with late-joining clients PROFILE_CPU_NAMED("NewClients"); // TODO: try iterative loop over several frames to reduce both server and client perf-spikes in case of large amount of spawned objects - CachedTargets.Clear(); - for (NetworkClient* client : NewClients) - CachedTargets.Add(client->Connection); for (auto it = Objects.Begin(); it.IsNotEnd(); ++it) { auto& item = it->Item; ScriptingObject* obj = item.Object.Get(); if (!obj || !item.Spawned) continue; + BuildCachedTargets(NewClients, item.TargetClientIds); SendObjectSpawnMessage(item, obj); } NewClients.Clear(); } // Collect clients for replication (from server) - CachedTargets.Clear(); - for (const NetworkClient* client : NetworkManager::Clients) - { - if (client->State == NetworkConnectionState::Connected) - CachedTargets.Add(client->Connection); - } + BuildCachedTargets(NetworkManager::Clients); if (!isClient && CachedTargets.Count() == 0) { // Early exit if server has nobody to send data to @@ -603,6 +672,7 @@ void NetworkInternal::NetworkReplicatorUpdate() } NetworkMessage msg = peer->BeginSendMessage(); msg.WriteStructure(msgData); + // TODO: use TargetClientIds for object despawning (send despawn message only to relevant clients) if (isClient) peer->EndSendMessage(NetworkChannelType::ReliableOrdered, msg); else @@ -615,9 +685,9 @@ void NetworkInternal::NetworkReplicatorUpdate() if (SpawnQueue.Count() != 0) { PROFILE_CPU_NAMED("SpawnQueue"); - for (ScriptingObjectReference& e : SpawnQueue) + for (auto& e : SpawnQueue) { - ScriptingObject* obj = e.Get(); + ScriptingObject* obj = e.Object.Get(); auto it = Objects.Find(obj->GetID()); if (it == Objects.End()) { @@ -631,7 +701,16 @@ void NetworkInternal::NetworkReplicatorUpdate() if (item.OwnerClientId != NetworkManager::LocalClientId || item.Role != NetworkObjectRole::OwnedAuthoritative) continue; // Skip spawning objects that we don't own + if (e.Targets.IsValid()) + { + // TODO: if we spawn object with custom set of targets clientsIds on client, then send it over to the server + if (NetworkManager::IsClient()) + MISSING_CODE("Sending TargetClientIds over to server for partial object replication."); + item.TargetClientIds = MoveTemp(e.Targets); + } + NETWORK_REPLICATOR_LOG(Info, "[NetworkReplicator] Spawn object ID={}", item.ToString()); + BuildCachedTargets(item); SendObjectSpawnMessage(item, obj); item.Spawned = true; } @@ -689,6 +768,7 @@ void NetworkInternal::NetworkReplicatorUpdate() msg.WriteStructure(msgData); msg.WriteBytes(stream->GetBuffer(), size); // TODO: per-object relevancy for connected clients (eg. skip replicating actor to far players) + BuildCachedTargets(item); peer->EndSendMessage(NetworkChannelType::Unreliable, msg, CachedTargets); // TODO: stats for bytes send per object type @@ -879,7 +959,7 @@ void NetworkInternal::OnNetworkMessageObjectSpawn(NetworkEvent& event, NetworkCl sceneObject->SetParent(parentActor); } - // TODO: if we're server then spawn this object further on other clients + // TODO: if we're server then spawn this object further on other clients (use TargetClientIds for that object - eg. object spawned by client on client for certain set of other clients only) } } diff --git a/Source/Engine/Networking/NetworkReplicator.h b/Source/Engine/Networking/NetworkReplicator.h index b2c272198..0ff8b00f0 100644 --- a/Source/Engine/Networking/NetworkReplicator.h +++ b/Source/Engine/Networking/NetworkReplicator.h @@ -6,6 +6,9 @@ #include "Engine/Scripting/ScriptingObject.h" #include "Engine/Scripting/ScriptingType.h" +template +class DataContainer; + /// /// The high-level network object role and authority. Used to define who owns the object and when it can be simulated or just replicated. /// @@ -73,6 +76,14 @@ public: /// The object to spawn on other clients. API_FUNCTION() static void SpawnObject(ScriptingObject* obj); + /// + /// Spawns the object to the other clients. Can be spawned by the owner who locally created it (eg. from prefab). + /// + /// Does nothing if network is offline. + /// The object to spawn on other clients. + /// List with network client IDs that should receive network spawn event. Empty to spawn on all clients. + API_FUNCTION() static void SpawnObject(ScriptingObject* obj, const DataContainer& clientIds); + /// /// Despawns the object from the other clients. Deletes object from remove clients. ///