From 30fdde614dcf18dfcb73216ecccd7eb92b7c3fe0 Mon Sep 17 00:00:00 2001 From: Wojciech Figat Date: Mon, 24 Oct 2022 15:02:11 +0200 Subject: [PATCH] Add network objects ownership changing with network sync --- Source/Engine/Networking/NetworkInternal.h | 2 + Source/Engine/Networking/NetworkManager.cpp | 1 + .../Engine/Networking/NetworkReplicator.cpp | 128 +++++++++++++++++- Source/Engine/Networking/NetworkReplicator.h | 8 ++ 4 files changed, 134 insertions(+), 5 deletions(-) diff --git a/Source/Engine/Networking/NetworkInternal.h b/Source/Engine/Networking/NetworkInternal.h index 46030dc86..f65c4a5e9 100644 --- a/Source/Engine/Networking/NetworkInternal.h +++ b/Source/Engine/Networking/NetworkInternal.h @@ -12,6 +12,7 @@ enum class NetworkMessageIDs : uint8 ObjectReplicate, ObjectSpawn, ObjectDespawn, + ObjectRole, MAX, }; @@ -27,4 +28,5 @@ public: static void OnNetworkMessageObjectReplicate(NetworkEvent& event, NetworkClient* client, NetworkPeer* peer); static void OnNetworkMessageObjectSpawn(NetworkEvent& event, NetworkClient* client, NetworkPeer* peer); static void OnNetworkMessageObjectDespawn(NetworkEvent& event, NetworkClient* client, NetworkPeer* peer); + static void OnNetworkMessageObjectRole(NetworkEvent& event, NetworkClient* client, NetworkPeer* peer); }; diff --git a/Source/Engine/Networking/NetworkManager.cpp b/Source/Engine/Networking/NetworkManager.cpp index 3a1f68061..9660e0e08 100644 --- a/Source/Engine/Networking/NetworkManager.cpp +++ b/Source/Engine/Networking/NetworkManager.cpp @@ -131,6 +131,7 @@ namespace NetworkInternal::OnNetworkMessageObjectReplicate, NetworkInternal::OnNetworkMessageObjectSpawn, NetworkInternal::OnNetworkMessageObjectDespawn, + NetworkInternal::OnNetworkMessageObjectRole, }; } diff --git a/Source/Engine/Networking/NetworkReplicator.cpp b/Source/Engine/Networking/NetworkReplicator.cpp index c4e67afa1..f87b53bcf 100644 --- a/Source/Engine/Networking/NetworkReplicator.cpp +++ b/Source/Engine/Networking/NetworkReplicator.cpp @@ -49,6 +49,13 @@ PACK_STRUCT(struct NetworkMessageObjectDespawn Guid ObjectId; }); +PACK_STRUCT(struct NetworkMessageObjectRole + { + NetworkMessageIDs ID = NetworkMessageIDs::ObjectRole; + Guid ObjectId; + uint32 OwnerClientId; + }); + struct NetworkReplicatedObject { ScriptingObjectReference Object; @@ -180,6 +187,30 @@ NetworkReplicatedObject* ResolveObject(Guid objectId, Guid parentId, char object return nullptr; } +void SendObjectRoleMessage(const NetworkReplicatedObject& item, const NetworkClient* excludedClient = nullptr) +{ + auto peer = NetworkManager::Peer; + NetworkMessageObjectRole msgData; + msgData.ObjectId = item.ObjectId; + msgData.OwnerClientId = item.OwnerClientId; + NetworkMessage msg = peer->BeginSendMessage(); + msg.WriteStructure(msgData); + if (NetworkManager::IsClient()) + { + NetworkManager::Peer->EndSendMessage(NetworkChannelType::ReliableOrdered, msg); + } + else + { + CachedTargets.Clear(); + for (const NetworkClient* client : NetworkManager::Clients) + { + if (client->State == NetworkConnectionState::Connected && client != excludedClient) + CachedTargets.Add(client->Connection); + } + peer->EndSendMessage(NetworkChannelType::ReliableOrdered, msg, CachedTargets); + } +} + #if !COMPILE_WITHOUT_CSHARP #include "Engine/Scripting/ManagedCLR/MUtils.h" @@ -321,6 +352,45 @@ NetworkObjectRole NetworkReplicator::GetObjectRole(ScriptingObject* obj) return role; } +void NetworkReplicator::SetObjectOwnership(ScriptingObject* obj, uint32 ownerClientId, NetworkObjectRole localRole) +{ + if (!obj) + return; + ScopeLock lock(ObjectsLock); + const auto it = Objects.Find(obj->GetID()); + if (it == Objects.End()) + return; + auto& item = it->Item; + if (item.Object != obj) + return; + + // Check if this client is object owner + if (item.OwnerClientId == NetworkManager::LocalClientId) + { + // Check if object owner will change + if (item.OwnerClientId != ownerClientId) + { + // Change role locally + CHECK(localRole != NetworkObjectRole::OwnedAuthoritative); + item.OwnerClientId = ownerClientId; + item.LastOwnerFrame = 1; + item.Role = localRole; + SendObjectRoleMessage(item); + } + else + { + // Object is the owner + CHECK(localRole == NetworkObjectRole::OwnedAuthoritative); + } + } + else + { + // Allow to change local role of the object (except ownership) + CHECK(localRole != NetworkObjectRole::OwnedAuthoritative); + item.Role = localRole; + } +} + void NetworkInternal::NetworkReplicatorClientConnected(NetworkClient* client) { ScopeLock lock(ObjectsLock); @@ -559,10 +629,13 @@ void NetworkInternal::OnNetworkMessageObjectReplicate(NetworkEvent& event, Netwo if (!obj) return; - // Reject replication from someone who is not an object owner - if (client && e->OwnerClientId != client->ClientId) + // Reject event from someone who is not an object owner + if (client && item.OwnerClientId != client->ClientId) + return; + + // Skip replication if we own the object (eg. late replication message after ownership change) + if (item.Role == NetworkObjectRole::OwnedAuthoritative) return; - ASSERT(e->Role != NetworkObjectRole::OwnedAuthoritative); // Ensure that we don't replicate object that we own // Drop object replication if it has old data (eg. newer message was already processed due to unordered channel usage) if (item.LastOwnerFrame >= msgData.OwnerFrame) @@ -669,8 +742,8 @@ void NetworkInternal::OnNetworkMessageObjectDespawn(NetworkEvent& event, Network if (!obj || !item.Spawned) return; - // Reject despawn from someone who is not an object owner - if (client && e->OwnerClientId != client->ClientId) + // Reject event from someone who is not an object owner + if (client && item.OwnerClientId != client->ClientId) return; // Remove object @@ -684,3 +757,48 @@ void NetworkInternal::OnNetworkMessageObjectDespawn(NetworkEvent& event, Network #endif } } + +void NetworkInternal::OnNetworkMessageObjectRole(NetworkEvent& event, NetworkClient* client, NetworkPeer* peer) +{ + NetworkMessageObjectRole 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) + return; + + // Reject event from someone who is not an object owner + if (client && item.OwnerClientId != client->ClientId) + return; + + // Update + item.OwnerClientId = msgData.OwnerClientId; + item.LastOwnerFrame = 1; + if (item.OwnerClientId == NetworkManager::LocalClientId) + { + // Upgrade ownership automatically + item.Role = NetworkObjectRole::OwnedAuthoritative; + item.LastOwnerFrame = 0; + } + else if (item.Role == NetworkObjectRole::OwnedAuthoritative) + { + // Downgrade ownership automatically + item.Role = NetworkObjectRole::Replicated; + } + if (!NetworkManager::IsClient()) + { + // Server has to broadcast ownership message to the other clients + SendObjectRoleMessage(item, client); + } + } + else + { +#if NETWORK_REPLICATOR_DEBUG_LOG + LOG(Error, "[NetworkReplicator] Unknown object role update {}", msgData.ObjectId); +#endif + } +} diff --git a/Source/Engine/Networking/NetworkReplicator.h b/Source/Engine/Networking/NetworkReplicator.h index 68665c712..db38682a9 100644 --- a/Source/Engine/Networking/NetworkReplicator.h +++ b/Source/Engine/Networking/NetworkReplicator.h @@ -87,6 +87,14 @@ public: /// The object role. API_FUNCTION() static NetworkObjectRole GetObjectRole(ScriptingObject* obj); + /// + /// Sets the network object ownership - owning client identifier and local role to use. + /// + /// The network object. + /// The new owner. Set to NetworkManager::LocalClientId for local client to be owner (server might reject it). + /// The local role to assign for the object. + API_FUNCTION() static void SetObjectOwnership(ScriptingObject* obj, uint32 ownerClientId, NetworkObjectRole localRole = NetworkObjectRole::Replicated); + private: #if !COMPILE_WITHOUT_CSHARP API_FUNCTION(NoProxy) static void AddSerializer(const ScriptingTypeHandle& type, const Function& serialize, const Function& deserialize);