// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. #include "NetworkReplicator.h" #include "NetworkClient.h" #include "NetworkManager.h" #include "NetworkInternal.h" #include "NetworkStream.h" #include "NetworkMessage.h" #include "NetworkPeer.h" #include "NetworkChannelType.h" #include "NetworkEvent.h" #include "NetworkRpc.h" #include "INetworkSerializable.h" #include "INetworkObject.h" #include "Engine/Core/Collections/HashSet.h" #include "Engine/Core/Collections/Dictionary.h" #include "Engine/Core/Collections/ChunkedArray.h" #include "Engine/Core/Types/DataContainer.h" #include "Engine/Platform/CriticalSection.h" #include "Engine/Engine/EngineService.h" #include "Engine/Level/Actor.h" #include "Engine/Level/SceneObject.h" #include "Engine/Level/Prefabs/Prefab.h" #include "Engine/Level/Prefabs/PrefabManager.h" #include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Scripting/Script.h" #include "Engine/Scripting/Scripting.h" #include "Engine/Scripting/ScriptingObjectReference.h" #include "Engine/Threading/Threading.h" #include "Engine/Threading/ThreadLocal.h" // Enables verbose logging for Network Replicator actions (dev-only) #define NETWORK_REPLICATOR_DEBUG_LOG 0 #if NETWORK_REPLICATOR_DEBUG_LOG #include "Engine/Core/Log.h" #define NETWORK_REPLICATOR_LOG(messageType, format, ...) LOG(messageType, format, ##__VA_ARGS__) #else #define NETWORK_REPLICATOR_LOG(messageType, format, ...) #endif 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) uint16 DataSize; uint16 PartsCount; }); PACK_STRUCT(struct NetworkMessageObjectReplicatePart { NetworkMessageIDs ID = NetworkMessageIDs::ObjectReplicatePart; uint32 OwnerFrame; uint16 DataSize; 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 { NetworkMessageIDs ID = NetworkMessageIDs::ObjectSpawn; uint32 OwnerClientId; Guid PrefabId; uint16 ItemsCount; }); 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) }); 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; 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; }); struct NetworkReplicatedObject { ScriptingObjectReference Object; Guid ObjectId; Guid ParentId; uint32 OwnerClientId; uint32 LastOwnerFrame = 0; NetworkObjectRole Role; uint8 Spawned = false; DataContainer TargetClientIds; INetworkObject* AsNetworkObject; bool operator==(const NetworkReplicatedObject& other) const { return Object == other.Object; } bool operator==(const ScriptingObject* other) const { return Object == other; } bool operator==(const Guid& other) const { return ObjectId == other; } String ToString() const { return ObjectId.ToString(); } }; inline uint32 GetHash(const NetworkReplicatedObject& key) { return GetHash(key.ObjectId); } struct Serializer { NetworkReplicator::SerializeFunc Methods[2]; void* Tags[2]; }; struct ReplicateItem { ScriptingObjectReference Object; Guid ObjectId; uint16 PartsLeft; uint32 OwnerFrame; Array Data; }; struct SpawnItem { ScriptingObjectReference Object; DataContainer Targets; bool HasOwnership = false; bool HierarchicalOwnership = false; uint32 OwnerClientId; NetworkObjectRole Role; }; struct SpawnGroup { Array> Items; }; struct DespawnItem { Guid Id; DataContainer Targets; }; struct RpcItem { ScriptingObjectReference Object; NetworkRpcName Name; NetworkRpcInfo Info; BytesContainer ArgsData; }; namespace { CriticalSection ObjectsLock; HashSet Objects; Array ReplicationParts; Array SpawnQueue; Array DespawnQueue; Array RpcQueue; Dictionary IdsRemappingTable; NetworkStream* CachedWriteStream = nullptr; NetworkStream* CachedReadStream = nullptr; Array NewClients; Array CachedTargets; Dictionary SerializersTable; #if !COMPILE_WITHOUT_CSHARP Dictionary CSharpCachedNames; #endif Array DespawnedObjects; } class NetworkReplicationService : public EngineService { public: NetworkReplicationService() : EngineService(TEXT("Network Replication"), 1100) { } void Dispose() override; }; void NetworkReplicationService::Dispose() { NetworkInternal::NetworkReplicatorClear(); #if !COMPILE_WITHOUT_CSHARP CSharpCachedNames.ClearDelete(); #endif } NetworkReplicationService NetworkReplicationServiceInstance; void INetworkSerializable_Serialize(void* instance, NetworkStream* stream, void* tag) { const int16 vtableOffset = (int16)(intptr)tag; ((INetworkSerializable*)((byte*)instance + vtableOffset))->Serialize(stream); } void INetworkSerializable_Deserialize(void* instance, NetworkStream* stream, void* tag) { const int16 vtableOffset = (int16)(intptr)tag; ((INetworkSerializable*)((byte*)instance + vtableOffset))->Deserialize(stream); } NetworkReplicatedObject* ResolveObject(Guid objectId) { auto it = Objects.Find(objectId); if (it != Objects.End()) return &it->Item; IdsRemappingTable.TryGet(objectId, objectId); it = Objects.Find(objectId); return it != Objects.End() ? &it->Item : nullptr; } NetworkReplicatedObject* ResolveObject(Guid objectId, Guid parentId, char objectTypeName[128]) { // Lookup object NetworkReplicatedObject* obj = ResolveObject(objectId); if (obj) return obj; // 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)); if (!objectType) return nullptr; for (auto& e : Objects) { auto& item = e.Item; const ScriptingObject* obj = item.Object.Get(); if (item.LastOwnerFrame == 0 && item.ParentId == parentId && obj && obj->GetTypeHandle() == objectType && !IdsRemappingTable.ContainsValue(item.ObjectId)) { if (NetworkManager::IsClient()) { // Boost future lookups by using indirection NETWORK_REPLICATOR_LOG(Info, "[NetworkReplicator] Remap object ID={} into object {}:{}", objectId, item.ToString(), obj->GetType().ToString()); IdsRemappingTable.Add(objectId, item.ObjectId); } return &item; } } return nullptr; } void BuildCachedTargets(const Array& clients) { CachedTargets.Clear(); 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, const uint32 excludedClientId = NetworkManager::ServerClientId) { CachedTargets.Clear(); if (clientIds.IsValid()) { for (const NetworkClient* client : clients) { if (client->State == NetworkConnectionState::Connected && client->ClientId != excludedClientId) { for (int32 i = 0; i < clientIds.Length(); i++) { if (clientIds[i] == client->ClientId) { CachedTargets.Add(client->Connection); break; } } } } } else { for (const NetworkClient* client : clients) { if (client->State == NetworkConnectionState::Connected && client->ClientId != excludedClientId) CachedTargets.Add(client->Connection); } } } FORCE_INLINE void BuildCachedTargets(const NetworkReplicatedObject& item) { // By default send object to all connected clients excluding the owner but with optional TargetClientIds list BuildCachedTargets(NetworkManager::Clients, item.TargetClientIds, item.OwnerClientId); } FORCE_INLINE void GetNetworkName(char buffer[128], const StringAnsiView& name) { Platform::MemoryCopy(buffer, name.Get(), name.Length()); buffer[name.Length()] = 0; } void SendObjectSpawnMessage(const SpawnGroup& group, const Array& clients) { const bool isClient = NetworkManager::IsClient(); auto* peer = NetworkManager::Peer; NetworkMessage msg = peer->BeginSendMessage(); NetworkMessageObjectSpawn msgData; msgData.ItemsCount = group.Items.Count(); { // 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; // Setup clients that should receive this spawn message auto it = Objects.Find(obj->GetID()); auto& item = it->Item; BuildCachedTargets(clients, item.TargetClientIds); } msg.WriteStructure(msgData); for (SpawnItem* e : group.Items) { ScriptingObject* obj = e->Object.Get(); auto it = Objects.Find(obj->GetID()); auto& item = it->Item; // Add object into spawn message NetworkMessageObjectSpawnItem msgDataItem; msgDataItem.ObjectId = item.ObjectId; msgDataItem.ParentId = item.ParentId; if (isClient) { // Remap local client object ids into server ids IdsRemappingTable.KeyOf(msgDataItem.ObjectId, &msgDataItem.ObjectId); IdsRemappingTable.KeyOf(msgDataItem.ParentId, &msgDataItem.ParentId); } msgDataItem.PrefabObjectID = Guid::Empty; auto* objScene = ScriptingObject::Cast(obj); if (objScene && objScene->HasPrefabLink()) msgDataItem.PrefabObjectID = objScene->GetPrefabObjectID(); GetNetworkName(msgDataItem.ObjectTypeName, obj->GetType().Fullname); msg.WriteStructure(msgDataItem); } if (isClient) peer->EndSendMessage(NetworkChannelType::Reliable, msg); else peer->EndSendMessage(NetworkChannelType::Reliable, msg, CachedTargets); } 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); if (NetworkManager::IsClient()) { NetworkManager::Peer->EndSendMessage(NetworkChannelType::ReliableOrdered, msg); } else { BuildCachedTargets(NetworkManager::Clients, excludedClient); peer->EndSendMessage(NetworkChannelType::ReliableOrdered, msg, CachedTargets); } } void DeleteNetworkObject(ScriptingObject* obj) { // Remove from the mapping table const Guid id = obj->GetID(); IdsRemappingTable.Remove(id); IdsRemappingTable.RemoveValue(id); if (obj->Is