diff --git a/Source/Engine/Networking/NetworkInternal.h b/Source/Engine/Networking/NetworkInternal.h index 050ca67da..23738cfc8 100644 --- a/Source/Engine/Networking/NetworkInternal.h +++ b/Source/Engine/Networking/NetworkInternal.h @@ -12,6 +12,7 @@ enum class NetworkMessageIDs : uint8 None = 0, Handshake, HandshakeReply, + Key, ObjectReplicate, ObjectReplicatePart, ObjectSpawn, diff --git a/Source/Engine/Networking/NetworkManager.cpp b/Source/Engine/Networking/NetworkManager.cpp index b9bb0125a..f2de18fef 100644 --- a/Source/Engine/Networking/NetworkManager.cpp +++ b/Source/Engine/Networking/NetworkManager.cpp @@ -30,11 +30,72 @@ Delegate NetworkManager::ClientConnecting; Delegate NetworkManager::ClientConnected; Delegate NetworkManager::ClientDisconnected; +PACK_STRUCT(struct NetworkMessageKey + { + NetworkMessageIDs ID = NetworkMessageIDs::Key; + byte Type; + uint32 Index; + }); + +struct NetworkKey +{ + enum Types + { + TypeNone = 0, + TypeId = 1, + TypeName = 2, + } Type; + + union + { + Guid Id; + StringAnsiView Name; + }; + + POD_COPYABLE(NetworkKey); + + NetworkKey() + { + Type = TypeNone; + } + + NetworkKey(const Guid& id) + { + Type = TypeId; + Id = id; + } + + NetworkKey(const StringAnsiView& name) + { + Type = TypeName; + Name = name; + } +}; + +struct NetworkKeys +{ + CriticalSection Lock; + Array Table; + Dictionary LookupId; + Dictionary LookupName; + Dictionary PendingIds; + Dictionary PendingNames; + + void SendPending(); + void SendAll(const NetworkConnection* target = nullptr); + void Clear(); + +private: + static void Send(const NetworkKey& key, uint32 index, const NetworkConnection* target = nullptr); +}; + namespace { uint32 GameProtocolVersion = 0; uint32 NextClientId = 0; double LastUpdateTime = 0; + Array ActiveConnections; + NetworkKeys Keys; } PACK_STRUCT(struct NetworkMessageHandshake @@ -55,6 +116,143 @@ PACK_STRUCT(struct NetworkMessageHandshakeReply int32 Result; }); +FORCE_INLINE StringAnsiView CloneAllocName(const StringAnsiView& name) +{ + StringAnsiView result; + if (name.Get()) + { + const int32 length = name.Length(); + char* str = (char*)Allocator::Allocate(length + 1); + Platform::MemoryCopy(str, name.Get(), length); + str[length] = 0; + result = StringAnsiView(str, length); + } + return result; +} + +FORCE_INLINE bool IsNetworkKeyValid(uint32 index) +{ + // TODO: use NetworkClientsMask to skip using network keys for clients that might not know it yet + // TODO: if key has been added within a last couple of frames then don't use it yet as it needs to be propagated across the peers + return true; +} + +void NetworkMessage::WriteNetworkId(const Guid& id) +{ + ScopeLock lock(Keys.Lock); + uint32 index = MAX_uint32; + bool hasIndex = Keys.LookupId.TryGet(id, index); + if (hasIndex) + hasIndex &= IsNetworkKeyValid(index); + WriteUInt32(index); + if (!hasIndex) + { + // No key cached locally so send the full data + WriteBytes((const uint8*)&id, sizeof(Guid)); + + // Add to the pending list (ignore on clients as server will automatically create a key once it gets full data) + if (NetworkManager::Mode != NetworkManagerMode::Client && + !Keys.PendingIds.ContainsKey(id)) + { + Keys.PendingIds.Add(id, NetworkKey(id)); + } + } +} + +void NetworkMessage::ReadNetworkId(Guid& id) +{ + ScopeLock lock(Keys.Lock); + uint32 index = ReadUInt32(); + if (index != MAX_uint32) + { + if (index < (uint32)Keys.Table.Count()) + { + // Use cached key data + const NetworkKey& k = Keys.Table.Get()[index]; + ASSERT(k.Type == NetworkKey::TypeId); + id = k.Id; + } + else + { + // Incorrect data + // TODO: should we check if message comes before new key arrival? should sender assume that key needs confirmation of receive? + id = Guid::Empty; + } + } + else + { + // Read full data + ReadBytes((uint8*)&id, sizeof(Guid)); + + // When server receives unknown data then turn this into key so connected client will receive it + if (NetworkManager::Mode != NetworkManagerMode::Client && + !Keys.PendingIds.ContainsKey(id) && + !Keys.LookupId.ContainsKey(id)) + { + Keys.PendingIds.Add(id, NetworkKey(id)); + } + } +} + +void NetworkMessage::WriteNetworkName(const StringAnsiView& name) +{ + ScopeLock lock(Keys.Lock); + uint32 index = MAX_uint32; + bool hasIndex = Keys.LookupName.TryGet(name, index); + if (hasIndex) + hasIndex &= IsNetworkKeyValid(index); + WriteUInt32(index); + if (!hasIndex) + { + // No key cached locally so send the full data + WriteStringAnsi(name); + + // Add to the pending list (ignore on clients as server will automatically create a key once it gets full data) + if (NetworkManager::Mode != NetworkManagerMode::Client && + !Keys.PendingNames.ContainsKey(name)) + { + StringAnsiView newName = CloneAllocName(name); + Keys.PendingNames.Add(newName, NetworkKey(newName)); + } + } +} + +void NetworkMessage::ReadNetworkName(StringAnsiView& name) +{ + ScopeLock lock(Keys.Lock); + uint32 index = ReadUInt32(); + if (index != MAX_uint32) + { + if (index < (uint32)Keys.Table.Count()) + { + // Use cached key data + const NetworkKey& k = Keys.Table.Get()[index]; + ASSERT(k.Type == NetworkKey::TypeName); + name = k.Name; + } + else + { + // Incorrect data + // TODO: should we check if message comes before new key arrival? should sender assume that key needs confirmation of receive? + name = StringAnsiView::Empty; + } + } + else + { + // Read full data + name = ReadStringAnsi(); + + // When server receives unknown data then turn this into key so connected client will receive it + if (NetworkManager::Mode != NetworkManagerMode::Client && + !Keys.PendingNames.ContainsKey(name) && + !Keys.LookupName.ContainsKey(name)) + { + StringAnsiView newName = CloneAllocName(name); + Keys.PendingNames.Add(newName, NetworkKey(newName)); + } + } +} + void OnNetworkMessageHandshake(NetworkEvent& event, NetworkClient* client, NetworkPeer* peer) { // Read client connection data @@ -94,6 +292,8 @@ void OnNetworkMessageHandshake(NetworkEvent& event, NetworkClient* client, Netwo { client->State = NetworkConnectionState::Connected; LOG(Info, "Client id={0} connected", event.Sender.ConnectionId); + ActiveConnections.Add(event.Sender); + Keys.SendAll(&event.Sender); NetworkManager::ClientConnected(client); NetworkInternal::NetworkReplicatorClientConnected(client); } @@ -120,6 +320,44 @@ void OnNetworkMessageHandshakeReply(NetworkEvent& event, NetworkClient* client, NetworkManager::StateChanged(); } +void OnNetworkMessageKey(NetworkEvent& event, NetworkClient* client, NetworkPeer* peer) +{ + // Read key data + NetworkMessageKey msgData; + event.Message.ReadStructure(msgData); + Guid id; + StringAnsiView name; + if (msgData.Type == NetworkKey::TypeId) + event.Message.ReadBytes((uint8*)&id, sizeof(Guid)); + else + name = event.Message.ReadStringAnsi(); + + ScopeLock lock(Keys.Lock); + if (NetworkManager::IsClient()) + { + // Add new key + if (msgData.Index >= (uint32)Keys.Table.Count()) + Keys.Table.Resize(msgData.Index + 1); + NetworkKey& key = Keys.Table[msgData.Index]; + ASSERT_LOW_LAYER(key.Type == NetworkKey::TypeNone); + key.Type = (NetworkKey::Types)msgData.Type; + if (key.Type == NetworkKey::TypeId) + { + key.Id = id; + Keys.LookupId.Add(id, msgData.Index); + } + else + { + key.Name = CloneAllocName(name); + Keys.LookupName.Add(key.Name, msgData.Index); + } + } + else + { + // TODO: make new pending key if client explicitly sends it + } +} + namespace { // Network message handlers table @@ -128,6 +366,7 @@ namespace nullptr, OnNetworkMessageHandshake, OnNetworkMessageHandshakeReply, + OnNetworkMessageKey, NetworkInternal::OnNetworkMessageObjectReplicate, NetworkInternal::OnNetworkMessageObjectReplicatePart, NetworkInternal::OnNetworkMessageObjectSpawn, @@ -362,11 +601,98 @@ void NetworkManager::Stop() LocalClient = nullptr; } + // Clear local state + NextClientId = 0; + LastUpdateTime = 0; + ActiveConnections.Clear(); + Keys.Clear(); + State = NetworkConnectionState::Disconnected; Mode = NetworkManagerMode::Offline; StateChanged(); } +void NetworkKeys::SendPending() +{ + PROFILE_CPU(); + ScopeLock lock(Lock); + + // Add new keys + int32 initialCount = Table.Count(); + int32 sendIndex = initialCount; + for (auto& e : PendingIds) + { + const int32 key = sendIndex++; + LookupId.Add(e.Key, key); + Table.Add(e.Value); + } + for (auto& e : PendingNames) + { + const int32 key = sendIndex++; + LookupName.Add(e.Key, key); + Table.Add(e.Value); + } + + // Send new entries + sendIndex = initialCount; + for (auto& e : PendingIds) + Send(e.Value, sendIndex++); + for (auto& e : PendingNames) + Send(e.Value, sendIndex++); + + // Clear lists + PendingIds.Clear(); + PendingNames.Clear(); +} + +void NetworkKeys::SendAll(const NetworkConnection* target) +{ + PROFILE_CPU(); + ScopeLock lock(Lock); + int32 sendIndex = 0; + for (auto& e : Table) + Send(e, sendIndex++, target); +} + +void NetworkKeys::Clear() +{ + ScopeLock lock(Lock); + LookupId.Clear(); + LookupName.Clear(); + PendingNames.GetValues(Table); + PendingNames.Clear(); + for (auto& e : Table) + { + if (e.Type == NetworkKey::TypeName) + { + // Free allocated string + Allocator::Free((void*)e.Name.Get()); + } + } + Table.Clear(); +} + +void NetworkKeys::Send(const NetworkKey& key, uint32 index, const NetworkConnection* target) +{ + // TODO: optimize with batching multiple keys into a single message + auto peer = NetworkManager::Peer; + NetworkMessage msg = peer->BeginSendMessage(); + NetworkMessageKey msgData; + msgData.Type = key.Type; + msgData.Index = index; + msg.WriteStructure(msgData); + if (key.Type == NetworkKey::TypeId) + msg.WriteGuid(key.Id); + else + msg.WriteStringAnsi(key.Name); + if (NetworkManager::IsClient()) + peer->EndSendMessage(NetworkChannelType::Reliable, msg); + else if (target) + peer->EndSendMessage(NetworkChannelType::Reliable, msg, *target); + else + peer->EndSendMessage(NetworkChannelType::Reliable, msg, ActiveConnections); +} + void NetworkManagerService::Update() { const double currentTime = Time::Update.UnscaledTime.GetTotalSeconds(); @@ -450,27 +776,28 @@ void NetworkManagerService::Update() NetworkManager::ClientDisconnected(client); client->State = NetworkConnectionState::Disconnected; Delete(client); + ActiveConnections.Remove(event.Sender); } break; case NetworkEventType::Message: + { + // Process network message + NetworkClient* client = NetworkManager::GetClient(event.Sender); + if (!client && NetworkManager::Mode != NetworkManagerMode::Client) { - // Process network message - NetworkClient* client = NetworkManager::GetClient(event.Sender); - if (!client && NetworkManager::Mode != NetworkManagerMode::Client) - { - LOG(Error, "Unknown client"); - break; - } - uint8 id = *event.Message.Buffer; - if (id < (uint8)NetworkMessageIDs::MAX) - { - MessageHandlers[id](event, client, peer); - } - else - { - LOG(Warning, "Unknown message id={0} from connection {1}", id, event.Sender.ConnectionId); - } + LOG(Error, "Unknown client"); + break; } + uint8 id = *event.Message.Buffer; + if (id < (uint8)NetworkMessageIDs::MAX) + { + MessageHandlers[id](event, client, peer); + } + else + { + LOG(Warning, "Unknown message id={0} from connection {1}", id, event.Sender.ConnectionId); + } + } peer->RecycleMessage(event.Message); break; default: @@ -481,4 +808,7 @@ void NetworkManagerService::Update() // Update replication NetworkInternal::NetworkReplicatorUpdate(); + + // Flush pending network key updates + Keys.SendPending(); } diff --git a/Source/Engine/Networking/NetworkMessage.h b/Source/Engine/Networking/NetworkMessage.h index 83bbc251c..139b78e32 100644 --- a/Source/Engine/Networking/NetworkMessage.h +++ b/Source/Engine/Networking/NetworkMessage.h @@ -67,7 +67,7 @@ public: /// /// The bytes that will be written. /// The amount of bytes to write from the bytes pointer. - FORCE_INLINE void WriteBytes(uint8* bytes, const int numBytes) + FORCE_INLINE void WriteBytes(const uint8* bytes, const int32 numBytes) { ASSERT(Position + numBytes <= BufferSize); Platform::MemoryCopy(Buffer + Position, bytes, numBytes); @@ -106,7 +106,7 @@ public: template FORCE_INLINE void WriteStructure(const T& data) { - WriteBytes((uint8*)&data, sizeof(data)); + WriteBytes((const uint8*)&data, sizeof(data)); } template @@ -116,7 +116,7 @@ public: } #define DECL_READWRITE(type, name) \ - FORCE_INLINE void Write##name(type value) { WriteBytes(reinterpret_cast(&value), sizeof(type)); } \ + FORCE_INLINE void Write##name(type value) { WriteBytes(reinterpret_cast(&value), sizeof(type)); } \ FORCE_INLINE type Read##name() { type value = 0; ReadBytes(reinterpret_cast(&value), sizeof(type)); return value; } DECL_READWRITE(int8, Int8) DECL_READWRITE(uint8, UInt8) @@ -207,22 +207,37 @@ public: /// /// Writes data of type String into the message. UTF-16 encoded. /// - FORCE_INLINE void WriteString(const String& value) + FORCE_INLINE void WriteString(const StringView& value) { WriteUInt16(value.Length()); // TODO: Use 1-byte length when possible - WriteBytes((uint8*)value.Get(), value.Length() * sizeof(Char)); + WriteBytes((const uint8*)value.Get(), value.Length() * sizeof(Char)); } /// - /// Reads and returns data of type String from the message. UTF-16 encoded. + /// Writes data of type String into the message. /// - FORCE_INLINE String ReadString() + FORCE_INLINE void WriteStringAnsi(const StringAnsiView& value) { - uint16 length = ReadUInt16(); - String value; - value.Resize(length); - ReadBytes((uint8*)value.Get(), length * sizeof(Char)); - return value; + WriteUInt16(value.Length()); // TODO: Use 1-byte length when possible + WriteBytes((const uint8*)value.Get(), value.Length()); + } + + /// + /// Reads and returns data of type String from the message. UTF-16 encoded. Data valid within message lifetime. + /// + FORCE_INLINE StringView ReadString() + { + const uint16 length = ReadUInt16(); + return StringView(length ? (const Char*)SkipBytes(length * 2) : nullptr, length); + } + + /// + /// Reads and returns data of type String from the message. ANSI encoded. Data valid within message lifetime. + /// + FORCE_INLINE StringAnsiView ReadStringAnsi() + { + const uint16 length = ReadUInt16(); + return StringAnsiView(length ? (const char*)SkipBytes(length) : nullptr, length); } /// @@ -230,7 +245,7 @@ public: /// FORCE_INLINE void WriteGuid(const Guid& value) { - WriteBytes((uint8*)&value, sizeof(Guid)); + WriteBytes((const uint8*)&value, sizeof(Guid)); } /// @@ -243,6 +258,30 @@ public: return value; } + /// + /// Writes identifier into the stream that is networked-synced (by a server). If both peers acknowledge a specific id then the data transfer is optimized to 32 bits. + /// + /// Network-synced identifier. + void WriteNetworkId(const Guid& id); + + /// + /// Reads identifier from the stream that is networked-synced (by a server). If both peers acknowledge a specific id then the data transfer is optimized to 32 bits. + /// + /// Network-synced identifier. + void ReadNetworkId(Guid& id); + + /// + /// Writes name into the stream that is networked-synced (by a server). If both peers acknowledge a specific name then the data transfer is optimized to 32 bits. + /// + /// Network-synced name. + void WriteNetworkName(const StringAnsiView& name); + + /// + /// Reads name from the stream that is networked-synced (by a server). If both peers acknowledge a specific name then the data transfer is optimized to 32 bits. + /// + /// Network-synced name. + void ReadNetworkName(StringAnsiView& name); + public: /// /// Returns true if the message is valid for reading or writing. diff --git a/Source/Engine/Networking/NetworkReplicator.cpp b/Source/Engine/Networking/NetworkReplicator.cpp index 232980f9f..a0fa11514 100644 --- a/Source/Engine/Networking/NetworkReplicator.cpp +++ b/Source/Engine/Networking/NetworkReplicator.cpp @@ -52,11 +52,13 @@ PACK_STRUCT(struct NetworkMessageObjectReplicate { NetworkMessageIDs ID = NetworkMessageIDs::ObjectReplicate; uint32 OwnerFrame; - Guid ObjectId; // TODO: introduce networked-ids to synchronize unique ids as ushort (less data over network) - Guid ParentId; - char ObjectTypeName[128]; // TODO: introduce networked-name to synchronize unique names as ushort (less data over network) + }); + +PACK_STRUCT(struct NetworkMessageObjectReplicatePayload + { uint16 DataSize; uint16 PartsCount; + uint16 PartSize; }); PACK_STRUCT(struct NetworkMessageObjectReplicatePart @@ -67,7 +69,6 @@ PACK_STRUCT(struct NetworkMessageObjectReplicatePart uint16 PartsCount; uint16 PartStart; uint16 PartSize; - Guid ObjectId; // TODO: introduce networked-ids to synchronize unique ids as ushort (less data over network) }); PACK_STRUCT(struct NetworkMessageObjectSpawn @@ -75,7 +76,6 @@ PACK_STRUCT(struct NetworkMessageObjectSpawn NetworkMessageIDs ID = NetworkMessageIDs::ObjectSpawn; uint32 OwnerClientId; uint32 OwnerSpawnId; // Unique for peer who spawned it and matches OwnerSpawnId inside following part messages - Guid PrefabId; uint16 ItemsCount; // Total items count uint8 UseParts : 1; // True if spawn message is header-only and all items come in the separate parts }); @@ -87,35 +87,29 @@ PACK_STRUCT(struct NetworkMessageObjectSpawnPart uint32 OwnerSpawnId; }); +// TODO: optimize spawn item to use Network Keys rather than fixed-size data PACK_STRUCT(struct NetworkMessageObjectSpawnItem { Guid ObjectId; Guid ParentId; Guid PrefabObjectID; - char ObjectTypeName[128]; // TODO: introduce networked-name to synchronize unique names as ushort (less data over network) + char ObjectTypeName[128]; }); PACK_STRUCT(struct NetworkMessageObjectDespawn { NetworkMessageIDs ID = NetworkMessageIDs::ObjectDespawn; - Guid ObjectId; }); PACK_STRUCT(struct NetworkMessageObjectRole { NetworkMessageIDs ID = NetworkMessageIDs::ObjectRole; - Guid ObjectId; uint32 OwnerClientId; }); PACK_STRUCT(struct NetworkMessageObjectRpc { NetworkMessageIDs ID = NetworkMessageIDs::ObjectRpc; - Guid ObjectId; - Guid ParentId; - char ObjectTypeName[128]; // TODO: introduce networked-name to synchronize unique names as ushort (less data over network) - char RpcTypeName[128]; // TODO: introduce networked-name to synchronize unique names as ushort (less data over network) - char RpcName[128]; // TODO: introduce networked-name to synchronize unique names as ushort (less data over network) uint16 ArgsSize; }); @@ -193,6 +187,7 @@ struct SpawnItem struct SpawnItemParts { NetworkMessageObjectSpawn MsgData; + Guid PrefabId; Array Items; }; @@ -333,7 +328,7 @@ NetworkReplicatedObject* ResolveObject(Guid objectId) return it != Objects.End() ? &it->Item : nullptr; } -NetworkReplicatedObject* ResolveObject(Guid objectId, Guid parentId, const char objectTypeName[128]) +NetworkReplicatedObject* ResolveObject(Guid objectId, Guid parentId, const StringAnsiView& objectTypeName) { // Lookup object NetworkReplicatedObject* obj = ResolveObject(objectId); @@ -342,7 +337,7 @@ NetworkReplicatedObject* ResolveObject(Guid objectId, Guid parentId, const char // Try to find the object within the same parent (eg. spawned locally on both client and server) IdsRemappingTable.TryGet(parentId, parentId); - const ScriptingTypeHandle objectType = Scripting::FindScriptingType(StringAnsiView(objectTypeName)); + const ScriptingTypeHandle objectType = Scripting::FindScriptingType(objectTypeName); if (!objectType) return nullptr; for (auto& e : Objects) @@ -467,12 +462,6 @@ FORCE_INLINE void BuildCachedTargets(const NetworkReplicatedObject& item, const BuildCachedTargets(NetworkManager::Clients, item.TargetClientIds, item.OwnerClientId, clientsMask); } -FORCE_INLINE void GetNetworkName(char buffer[128], const StringAnsiView& name) -{ - Platform::MemoryCopy(buffer, name.Get(), name.Length()); - buffer[name.Length()] = 0; -} - void SetupObjectSpawnMessageItem(SpawnItem* e, NetworkMessage& msg) { ScriptingObject* obj = e->Object.Get(); @@ -493,7 +482,9 @@ void SetupObjectSpawnMessageItem(SpawnItem* e, NetworkMessage& msg) auto* objScene = ScriptingObject::Cast(obj); if (objScene && objScene->HasPrefabLink()) msgDataItem.PrefabObjectID = objScene->GetPrefabObjectID(); - GetNetworkName(msgDataItem.ObjectTypeName, obj->GetType().Fullname); + const StringAnsiView objectTypeName = obj->GetType().Fullname; + Platform::MemoryCopy(msgDataItem.ObjectTypeName, objectTypeName.Get(), objectTypeName.Length()); + msgDataItem.ObjectTypeName[objectTypeName.Length()] = 0; msg.WriteStructure(msgDataItem); } @@ -505,13 +496,14 @@ void SendObjectSpawnMessage(const SpawnGroup& group, const Array NetworkMessage msg = peer->BeginSendMessage(); NetworkMessageObjectSpawn msgData; msgData.ItemsCount = group.Items.Count(); + Guid prefabId; { // The first object is a root of the group (eg. prefab instance root actor) SpawnItem* e = group.Items[0]; ScriptingObject* obj = e->Object.Get(); msgData.OwnerClientId = e->OwnerClientId; auto* objScene = ScriptingObject::Cast(obj); - msgData.PrefabId = objScene && objScene->HasPrefabLink() ? objScene->GetPrefabID() : Guid::Empty; + prefabId = objScene && objScene->HasPrefabLink() ? objScene->GetPrefabID() : Guid::Empty; // Setup clients that should receive this spawn message auto it = Objects.Find(obj->GetID()); @@ -523,6 +515,7 @@ void SendObjectSpawnMessage(const SpawnGroup& group, const Array msgData.OwnerSpawnId = ++SpawnId; msgData.UseParts = msg.BufferSize - msg.Position < group.Items.Count() * sizeof(NetworkMessageObjectSpawnItem); msg.WriteStructure(msgData); + msg.WriteNetworkId(prefabId); if (msgData.UseParts) { if (isClient) @@ -570,11 +563,11 @@ void SendObjectSpawnMessage(const SpawnGroup& group, const Array void SendObjectRoleMessage(const NetworkReplicatedObject& item, const NetworkClient* excludedClient = nullptr) { NetworkMessageObjectRole msgData; - msgData.ObjectId = item.ObjectId; msgData.OwnerClientId = item.OwnerClientId; auto peer = NetworkManager::Peer; NetworkMessage msg = peer->BeginSendMessage(); msg.WriteStructure(msgData); + msg.WriteNetworkId(item.ObjectId); if (NetworkManager::IsClient()) { NetworkManager::Peer->EndSendMessage(NetworkChannelType::ReliableOrdered, msg); @@ -695,14 +688,13 @@ FORCE_INLINE void DirtyObjectImpl(NetworkReplicatedObject& item, ScriptingObject Hierarchy->DirtyObject(obj); } -template -ReplicateItem* AddObjectReplicateItem(NetworkEvent& event, const MessageType& msgData, uint16 partStart, uint16 partSize, uint32 senderClientId) +ReplicateItem* AddObjectReplicateItem(NetworkEvent& event, uint32 ownerFrame, uint16 partsCount, uint16 dataSize, const Guid& objectId, uint16 partStart, uint16 partSize, uint32 senderClientId) { // Reuse or add part item ReplicateItem* replicateItem = nullptr; for (auto& e : ReplicationParts) { - if (e.OwnerFrame == msgData.OwnerFrame && e.Data.Count() == msgData.DataSize && e.ObjectId == msgData.ObjectId) + if (e.OwnerFrame == ownerFrame && e.Data.Count() == dataSize && e.ObjectId == objectId) { // Reuse replicateItem = &e; @@ -713,11 +705,11 @@ ReplicateItem* AddObjectReplicateItem(NetworkEvent& event, const MessageType& ms { // Add replicateItem = &ReplicationParts.AddOne(); - replicateItem->ObjectId = msgData.ObjectId; - replicateItem->PartsLeft = msgData.PartsCount; - replicateItem->OwnerFrame = msgData.OwnerFrame; + replicateItem->ObjectId = objectId; + replicateItem->PartsLeft = partsCount; + replicateItem->OwnerFrame = ownerFrame; replicateItem->OwnerClientId = senderClientId; - replicateItem->Data.Resize(msgData.DataSize); + replicateItem->Data.Resize(dataSize); } // Copy part data @@ -775,7 +767,7 @@ void InvokeObjectReplication(NetworkReplicatedObject& item, uint32 ownerFrame, b DirtyObjectImpl(item, obj); } -void InvokeObjectSpawn(const NetworkMessageObjectSpawn& msgData, const NetworkMessageObjectSpawnItem* msgDataItems) +void InvokeObjectSpawn(const NetworkMessageObjectSpawn& msgData, const Guid& prefabId, const NetworkMessageObjectSpawnItem* msgDataItems) { ScopeLock lock(ObjectsLock); @@ -814,11 +806,11 @@ void InvokeObjectSpawn(const NetworkMessageObjectSpawn& msgData, const NetworkMe // Recreate object locally (spawn only root) Actor* prefabInstance = nullptr; Array objects; - if (msgData.PrefabId.IsValid()) + if (prefabId.IsValid()) { const NetworkReplicatedObject* parent = ResolveObject(rootItem.ParentId); Actor* parentActor = parent && parent->Object && parent->Object->Is() ? parent->Object.As() : nullptr; - if (parentActor && parentActor->GetPrefabID() == msgData.PrefabId) + if (parentActor && parentActor->GetPrefabID() == prefabId) { // Reuse parent object as prefab instance prefabInstance = parentActor; @@ -828,7 +820,7 @@ void InvokeObjectSpawn(const NetworkMessageObjectSpawn& msgData, const NetworkMe // Try to find that spawned prefab (eg. prefab with networked script was spawned before so now we need to link it) for (Actor* child : parentActor->Children) { - if (child->GetPrefabID() == msgData.PrefabId) + if (child->GetPrefabID() == prefabId) { if (Objects.Contains(child->GetID())) { @@ -851,16 +843,16 @@ void InvokeObjectSpawn(const NetworkMessageObjectSpawn& msgData, const NetworkMe if (!prefabInstance) { // Spawn prefab - auto prefab = (Prefab*)LoadAsset(msgData.PrefabId, Prefab::TypeInitializer); + auto prefab = (Prefab*)LoadAsset(prefabId, Prefab::TypeInitializer); if (!prefab) { - NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Failed to find prefab {}", msgData.PrefabId.ToString()); + NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Failed to find prefab {}", prefabId.ToString()); return; } prefabInstance = PrefabManager::SpawnPrefab(prefab, nullptr, nullptr); if (!prefabInstance) { - NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Failed to spawn object type {}", msgData.PrefabId.ToString()); + NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Failed to spawn object type {}", prefabId.ToString()); return; } } @@ -873,7 +865,7 @@ void InvokeObjectSpawn(const NetworkMessageObjectSpawn& msgData, const NetworkMe ScriptingObject* obj = FindPrefabObject(prefabInstance, msgDataItem.PrefabObjectID); if (!obj) { - NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Failed to find object {} in prefab {}", msgDataItem.PrefabObjectID.ToString(), msgData.PrefabId.ToString()); + NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Failed to find object {} in prefab {}", msgDataItem.PrefabObjectID.ToString(), prefabId.ToString()); Delete(prefabInstance); return; } @@ -1667,14 +1659,15 @@ void NetworkInternal::NetworkReplicatorUpdate() // Send despawn message NETWORK_REPLICATOR_LOG(Info, "[NetworkReplicator] Despawn object ID={}", e.Id.ToString()); NetworkMessageObjectDespawn msgData; - msgData.ObjectId = e.Id; + Guid objectId = e.Id; if (isClient) { // Remap local client object ids into server ids - IdsRemappingTable.KeyOf(msgData.ObjectId, &msgData.ObjectId); + IdsRemappingTable.KeyOf(objectId, &objectId); } NetworkMessage msg = peer->BeginSendMessage(); msg.WriteStructure(msgData); + msg.WriteNetworkId(objectId); BuildCachedTargets(NetworkManager::Clients, e.Targets); if (isClient) peer->EndSendMessage(NetworkChannelType::ReliableOrdered, msg); @@ -1877,18 +1870,23 @@ void NetworkInternal::NetworkReplicatorUpdate() ASSERT(size <= MAX_uint16); NetworkMessageObjectReplicate msgData; msgData.OwnerFrame = NetworkManager::Frame; - msgData.ObjectId = item.ObjectId; - msgData.ParentId = item.ParentId; + Guid objectId = item.ObjectId, parentId = item.ParentId; if (isClient) { // Remap local client object ids into server ids - IdsRemappingTable.KeyOf(msgData.ObjectId, &msgData.ObjectId); - IdsRemappingTable.KeyOf(msgData.ParentId, &msgData.ParentId); + IdsRemappingTable.KeyOf(objectId, &objectId); + IdsRemappingTable.KeyOf(parentId, &parentId); } - GetNetworkName(msgData.ObjectTypeName, obj->GetType().Fullname); - msgData.DataSize = size; - const uint32 msgMaxData = peer->Config.MessageSize - sizeof(NetworkMessageObjectReplicate); - const uint32 partMaxData = peer->Config.MessageSize - sizeof(NetworkMessageObjectReplicatePart); + NetworkMessage msg = peer->BeginSendMessage(); + msg.WriteStructure(msgData); + msg.WriteNetworkId(objectId); + msg.WriteNetworkId(parentId); + msg.WriteNetworkName(obj->GetType().Fullname); + NetworkMessageObjectReplicatePayload msgDataPayload; + msgDataPayload.DataSize = size; + const uint32 networkKeyIdWorstCaseSize = sizeof(uint32) + sizeof(Guid); + const uint32 msgMaxData = peer->Config.MessageSize - msg.Position - sizeof(NetworkMessageObjectReplicatePayload); + const uint32 partMaxData = peer->Config.MessageSize - sizeof(NetworkMessageObjectReplicatePart) - networkKeyIdWorstCaseSize; uint32 partsCount = 1; uint32 dataStart = 0; uint32 msgDataSize = size; @@ -1904,9 +1902,9 @@ void NetworkInternal::NetworkReplicatorUpdate() else dataStart += size; ASSERT(partsCount <= MAX_uint8); - msgData.PartsCount = partsCount; - NetworkMessage msg = peer->BeginSendMessage(); - msg.WriteStructure(msgData); + msgDataPayload.PartsCount = partsCount; + msgDataPayload.PartSize = msgDataSize; + msg.WriteStructure(msgDataPayload); msg.WriteBytes(stream->GetBuffer(), msgDataSize); uint32 dataSize = msgDataSize, messageSize = msg.Length; if (isClient) @@ -1919,13 +1917,13 @@ void NetworkInternal::NetworkReplicatorUpdate() { NetworkMessageObjectReplicatePart msgDataPart; msgDataPart.OwnerFrame = msgData.OwnerFrame; - msgDataPart.ObjectId = msgData.ObjectId; - msgDataPart.DataSize = msgData.DataSize; - msgDataPart.PartsCount = msgData.PartsCount; + msgDataPart.DataSize = msgDataPayload.DataSize; + msgDataPart.PartsCount = msgDataPayload.PartsCount; msgDataPart.PartStart = dataStart; msgDataPart.PartSize = Math::Min(size - dataStart, partMaxData); msg = peer->BeginSendMessage(); msg.WriteStructure(msgDataPart); + msg.WriteNetworkId(objectId); msg.WriteBytes(stream->GetBuffer() + msgDataPart.PartStart, msgDataPart.PartSize); messageSize += msg.Length; dataSize += msgDataPart.PartSize; @@ -1974,20 +1972,22 @@ void NetworkInternal::NetworkReplicatorUpdate() // Send RPC message //NETWORK_REPLICATOR_LOG(Info, "[NetworkReplicator] Rpc {}::{} object ID={}", e.Name.First.ToString(), String(e.Name.Second), item.ToString()); NetworkMessageObjectRpc msgData; - msgData.ObjectId = item.ObjectId; - msgData.ParentId = item.ParentId; + Guid msgObjectId = item.ObjectId; + Guid msgParentId = item.ParentId; if (isClient) { // Remap local client object ids into server ids - IdsRemappingTable.KeyOf(msgData.ObjectId, &msgData.ObjectId); - IdsRemappingTable.KeyOf(msgData.ParentId, &msgData.ParentId); + IdsRemappingTable.KeyOf(msgObjectId, &msgObjectId); + IdsRemappingTable.KeyOf(msgParentId, &msgParentId); } - GetNetworkName(msgData.ObjectTypeName, obj->GetType().Fullname); - GetNetworkName(msgData.RpcTypeName, e.Name.First.GetType().Fullname); - GetNetworkName(msgData.RpcName, e.Name.Second); msgData.ArgsSize = (uint16)e.ArgsData.Length(); NetworkMessage msg = peer->BeginSendMessage(); msg.WriteStructure(msgData); + msg.WriteNetworkId(msgObjectId); + msg.WriteNetworkId(msgParentId); + msg.WriteNetworkName(obj->GetType().Fullname); + msg.WriteNetworkName(e.Name.First.GetType().Fullname); + msg.WriteNetworkName(e.Name.Second); msg.WriteBytes(e.ArgsData.Get(), e.ArgsData.Length()); uint32 dataSize = e.ArgsData.Length(), messageSize = msg.Length, receivers = 0; NetworkChannelType channel = (NetworkChannelType)e.Info.Channel; @@ -2032,11 +2032,18 @@ void NetworkInternal::OnNetworkMessageObjectReplicate(NetworkEvent& event, Netwo { PROFILE_CPU(); NetworkMessageObjectReplicate msgData; + NetworkMessageObjectReplicatePayload msgDataPayload; + Guid objectId, parentId; + StringAnsiView objectTypeName; event.Message.ReadStructure(msgData); + event.Message.ReadNetworkId(objectId); + event.Message.ReadNetworkId(parentId); + event.Message.ReadNetworkName(objectTypeName); + event.Message.ReadStructure(msgDataPayload); ScopeLock lock(ObjectsLock); - if (DespawnedObjects.Contains(msgData.ObjectId)) + if (DespawnedObjects.Contains(objectId)) return; // Skip replicating not-existing objects - NetworkReplicatedObject* e = ResolveObject(msgData.ObjectId, msgData.ParentId, msgData.ObjectTypeName); + NetworkReplicatedObject* e = ResolveObject(objectId, parentId, objectTypeName); if (!e) return; auto& item = *e; @@ -2046,16 +2053,15 @@ void NetworkInternal::OnNetworkMessageObjectReplicate(NetworkEvent& event, Netwo return; const uint32 senderClientId = client ? client->ClientId : NetworkManager::ServerClientId; - if (msgData.PartsCount == 1) + if (msgDataPayload.PartsCount == 1) { // Replicate - InvokeObjectReplication(item, msgData.OwnerFrame, event.Message.Buffer + event.Message.Position, msgData.DataSize, senderClientId); + InvokeObjectReplication(item, msgData.OwnerFrame, event.Message.Buffer + event.Message.Position, msgDataPayload.DataSize, senderClientId); } else { // Add to replication from multiple parts - const uint16 msgMaxData = peer->Config.MessageSize - sizeof(NetworkMessageObjectReplicate); - ReplicateItem* replicateItem = AddObjectReplicateItem(event, msgData, 0, msgMaxData, senderClientId); + ReplicateItem* replicateItem = AddObjectReplicateItem(event, msgData.OwnerFrame, msgDataPayload.PartsCount, msgDataPayload.DataSize, objectId, 0, msgDataPayload.PartSize, senderClientId); replicateItem->Object = e->Object; } } @@ -2064,20 +2070,24 @@ void NetworkInternal::OnNetworkMessageObjectReplicatePart(NetworkEvent& event, N { PROFILE_CPU(); NetworkMessageObjectReplicatePart msgData; + Guid objectId; event.Message.ReadStructure(msgData); + event.Message.ReadNetworkId(objectId); ScopeLock lock(ObjectsLock); - if (DespawnedObjects.Contains(msgData.ObjectId)) + if (DespawnedObjects.Contains(objectId)) return; // Skip replicating not-existing objects const uint32 senderClientId = client ? client->ClientId : NetworkManager::ServerClientId; - AddObjectReplicateItem(event, msgData, msgData.PartStart, msgData.PartSize, senderClientId); + AddObjectReplicateItem(event, msgData.OwnerFrame, msgData.PartsCount, msgData.DataSize, objectId, msgData.PartStart, msgData.PartSize, senderClientId); } void NetworkInternal::OnNetworkMessageObjectSpawn(NetworkEvent& event, NetworkClient* client, NetworkPeer* peer) { PROFILE_CPU(); NetworkMessageObjectSpawn msgData; + Guid prefabId; event.Message.ReadStructure(msgData); + event.Message.ReadNetworkId(prefabId); if (msgData.ItemsCount == 0) return; if (msgData.UseParts) @@ -2085,6 +2095,7 @@ void NetworkInternal::OnNetworkMessageObjectSpawn(NetworkEvent& event, NetworkCl // Allocate spawn message parts collecting auto& parts = SpawnParts.AddOne(); parts.MsgData = msgData; + parts.PrefabId = prefabId; parts.Items.Resize(msgData.ItemsCount); for (auto& item : parts.Items) item.ObjectId = Guid::Empty; // Mark as not yet received @@ -2092,7 +2103,7 @@ void NetworkInternal::OnNetworkMessageObjectSpawn(NetworkEvent& event, NetworkCl else { const auto* msgDataItems = (NetworkMessageObjectSpawnItem*)event.Message.SkipBytes(msgData.ItemsCount * sizeof(NetworkMessageObjectSpawnItem)); - InvokeObjectSpawn(msgData, msgDataItems); + InvokeObjectSpawn(msgData, prefabId, msgDataItems); } } @@ -2130,7 +2141,7 @@ void NetworkInternal::OnNetworkMessageObjectSpawnPart(NetworkEvent& event, Netwo if (!e.ObjectId.IsValid()) return; } - InvokeObjectSpawn(spawnParts.MsgData, spawnParts.Items.Get()); + InvokeObjectSpawn(spawnParts.MsgData, spawnParts.PrefabId, spawnParts.Items.Get()); SpawnParts.RemoveAt(spawnPartsIndex); } @@ -2138,9 +2149,11 @@ void NetworkInternal::OnNetworkMessageObjectDespawn(NetworkEvent& event, Network { PROFILE_CPU(); NetworkMessageObjectDespawn msgData; + Guid objectId; event.Message.ReadStructure(msgData); + event.Message.ReadNetworkId(objectId); ScopeLock lock(ObjectsLock); - NetworkReplicatedObject* e = ResolveObject(msgData.ObjectId); + NetworkReplicatedObject* e = ResolveObject(objectId); if (e) { auto& item = *e; @@ -2153,10 +2166,10 @@ void NetworkInternal::OnNetworkMessageObjectDespawn(NetworkEvent& event, Network return; // Remove object - NETWORK_REPLICATOR_LOG(Info, "[NetworkReplicator] Despawn object {}", msgData.ObjectId); + NETWORK_REPLICATOR_LOG(Info, "[NetworkReplicator] Despawn object {}", objectId); if (Hierarchy && item.Role == NetworkObjectRole::OwnedAuthoritative) Hierarchy->RemoveObject(obj); - DespawnedObjects.Add(msgData.ObjectId); + DespawnedObjects.Add(objectId); if (item.AsNetworkObject) item.AsNetworkObject->OnNetworkDespawn(); Objects.Remove(obj); @@ -2164,7 +2177,7 @@ void NetworkInternal::OnNetworkMessageObjectDespawn(NetworkEvent& event, Network } else { - NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Failed to despawn object {}", msgData.ObjectId); + NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Failed to despawn object {}", objectId); } } @@ -2172,9 +2185,11 @@ void NetworkInternal::OnNetworkMessageObjectRole(NetworkEvent& event, NetworkCli { PROFILE_CPU(); NetworkMessageObjectRole msgData; + Guid objectId; event.Message.ReadStructure(msgData); + event.Message.ReadNetworkId(objectId); ScopeLock lock(ObjectsLock); - NetworkReplicatedObject* e = ResolveObject(msgData.ObjectId); + NetworkReplicatedObject* e = ResolveObject(objectId); if (e) { auto& item = *e; @@ -2212,7 +2227,7 @@ void NetworkInternal::OnNetworkMessageObjectRole(NetworkEvent& event, NetworkCli } else { - NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Unknown object role update {}", msgData.ObjectId); + NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Unknown object role update {}", objectId); } } @@ -2220,21 +2235,28 @@ void NetworkInternal::OnNetworkMessageObjectRpc(NetworkEvent& event, NetworkClie { PROFILE_CPU(); NetworkMessageObjectRpc msgData; + Guid msgObjectId, msgParentId; + StringAnsiView objectTypeName, rpcTypeName, rpcName; event.Message.ReadStructure(msgData); + event.Message.ReadNetworkId(msgObjectId); + event.Message.ReadNetworkId(msgParentId); + event.Message.ReadNetworkName(objectTypeName); + event.Message.ReadNetworkName(rpcTypeName); + event.Message.ReadNetworkName(rpcName); ScopeLock lock(ObjectsLock); // Find RPC info NetworkRpcName name; - name.First = Scripting::FindScriptingType(msgData.RpcTypeName); - name.Second = msgData.RpcName; + name.First = Scripting::FindScriptingType(rpcTypeName); + name.Second = rpcName; const NetworkRpcInfo* info = NetworkRpcInfo::RPCsTable.TryGet(name); if (!info) { - NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Unknown RPC {}::{} for object {}", String(msgData.RpcTypeName), String(msgData.RpcName), msgData.ObjectId); + NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Unknown RPC {}::{} for object {}", String(rpcTypeName), String(rpcName), msgObjectId); return; } - NetworkReplicatedObject* e = ResolveObject(msgData.ObjectId, msgData.ParentId, msgData.ObjectTypeName); + NetworkReplicatedObject* e = ResolveObject(msgObjectId, msgParentId, objectTypeName); if (e) { auto& item = *e; @@ -2245,12 +2267,12 @@ void NetworkInternal::OnNetworkMessageObjectRpc(NetworkEvent& event, NetworkClie // Validate RPC if (info->Server && NetworkManager::IsClient()) { - NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Cannot invoke server RPC {}::{} on client", String(msgData.RpcTypeName), String(msgData.RpcName)); + NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Cannot invoke server RPC {}::{} on client", String(rpcTypeName), String(rpcName)); return; } if (info->Client && NetworkManager::IsServer()) { - NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Cannot invoke client RPC {}::{} on server", String(msgData.RpcTypeName), String(msgData.RpcName)); + NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Cannot invoke client RPC {}::{} on server", String(rpcTypeName), String(rpcName)); return; } @@ -2266,6 +2288,6 @@ void NetworkInternal::OnNetworkMessageObjectRpc(NetworkEvent& event, NetworkClie } else if (info->Channel != static_cast(NetworkChannelType::Unreliable) && info->Channel != static_cast(NetworkChannelType::UnreliableOrdered)) { - NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Unknown object {} RPC {}::{}", msgData.ObjectId, String(msgData.RpcTypeName), String(msgData.RpcName)); + NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Unknown object {} RPC {}::{}", msgObjectId, String(rpcTypeName), String(rpcName)); } }