diff --git a/Source/Engine/Networking/NetworkInternal.h b/Source/Engine/Networking/NetworkInternal.h index a2e14140c..4840ea796 100644 --- a/Source/Engine/Networking/NetworkInternal.h +++ b/Source/Engine/Networking/NetworkInternal.h @@ -10,6 +10,7 @@ enum class NetworkMessageIDs : uint8 Handshake, HandshakeReply, ReplicatedObject, + SpawnObject, MAX, }; @@ -17,8 +18,11 @@ enum class NetworkMessageIDs : uint8 class NetworkInternal { public: + static void NetworkReplicatorClientConnected(NetworkClient* client); + static void NetworkReplicatorClientDisconnected(NetworkClient* client); static void NetworkReplicatorClear(); static void NetworkReplicatorPreUpdate(); static void NetworkReplicatorUpdate(); static void OnNetworkMessageReplicatedObject(NetworkEvent& event, NetworkClient* client, NetworkPeer* peer); + static void OnNetworkMessageSpawnObject(NetworkEvent& event, NetworkClient* client, NetworkPeer* peer); }; diff --git a/Source/Engine/Networking/NetworkManager.cpp b/Source/Engine/Networking/NetworkManager.cpp index be8b37063..74da45e8a 100644 --- a/Source/Engine/Networking/NetworkManager.cpp +++ b/Source/Engine/Networking/NetworkManager.cpp @@ -22,6 +22,7 @@ NetworkPeer* NetworkManager::Peer = nullptr; NetworkManagerMode NetworkManager::Mode = NetworkManagerMode::Offline; NetworkConnectionState NetworkManager::State = NetworkConnectionState::Offline; uint32 NetworkManager::Frame = 0; +uint32 NetworkManager::LocalClientId = 0; NetworkClient* NetworkManager::LocalClient = nullptr; Array NetworkManager::Clients; Action NetworkManager::StateChanged; @@ -38,7 +39,7 @@ namespace PACK_STRUCT(struct NetworkMessageHandshake { - NetworkMessageIDs ID; + NetworkMessageIDs ID = NetworkMessageIDs::Handshake; uint32 EngineBuild; uint32 EngineProtocolVersion; uint32 GameProtocolVersion; @@ -49,7 +50,7 @@ PACK_STRUCT(struct NetworkMessageHandshake PACK_STRUCT(struct NetworkMessageHandshakeReply { - NetworkMessageIDs ID; + NetworkMessageIDs ID = NetworkMessageIDs::HandshakeReply; uint32 ClientId; int32 Result; }); @@ -75,7 +76,6 @@ void OnNetworkMessageHandshake(NetworkEvent& event, NetworkClient* client, Netwo // Reply to the handshake message with a result NetworkMessageHandshakeReply replyData; - replyData.ID = NetworkMessageIDs::HandshakeReply; replyData.Result = connectionData.Result; replyData.ClientId = client->ClientId; NetworkMessage msgReply = peer->BeginSendMessage(); @@ -95,6 +95,7 @@ void OnNetworkMessageHandshake(NetworkEvent& event, NetworkClient* client, Netwo client->State = NetworkConnectionState::Connected; LOG(Info, "Client id={0} connected", event.Sender.ConnectionId); NetworkManager::ClientConnected(client); + NetworkInternal::NetworkReplicatorClientConnected(client); } } @@ -112,6 +113,7 @@ void OnNetworkMessageHandshakeReply(NetworkEvent& event, NetworkClient* client, } // Client got connected with server + NetworkManager::LocalClientId = msgData.ClientId; NetworkManager::LocalClient->ClientId = msgData.ClientId; NetworkManager::LocalClient->State = NetworkConnectionState::Connected; NetworkManager::State = NetworkConnectionState::Connected; @@ -127,6 +129,7 @@ namespace OnNetworkMessageHandshake, OnNetworkMessageHandshakeReply, NetworkInternal::OnNetworkMessageReplicatedObject, + NetworkInternal::OnNetworkMessageSpawnObject, }; } @@ -233,12 +236,14 @@ bool NetworkManager::StartServer() PROFILE_CPU(); Stop(); + LOG(Info, "Starting network manager as server"); Mode = NetworkManagerMode::Server; if (StartPeer()) return true; if (!Peer->Listen()) return true; - NextClientId++; + LocalClientId = ServerClientId; + NextClientId = ServerClientId + 1; State = NetworkConnectionState::Connected; StateChanged(); @@ -250,12 +255,15 @@ bool NetworkManager::StartClient() PROFILE_CPU(); Stop(); + LOG(Info, "Starting network manager as client"); Mode = NetworkManagerMode::Client; if (StartPeer()) return true; if (!Peer->Connect()) return true; - LocalClient = New(0, NetworkConnection{ 0 }); + LocalClientId = 0; // Id gets assigned by server later after connection + NextClientId = 0; + LocalClient = New(LocalClientId, NetworkConnection{ 0 }); return false; } @@ -265,12 +273,15 @@ bool NetworkManager::StartHost() PROFILE_CPU(); Stop(); + LOG(Info, "Starting network manager as host"); Mode = NetworkManagerMode::Host; if (StartPeer()) return true; if (!Peer->Listen()) return true; - LocalClient = New(NextClientId++, NetworkConnection{ 0 }); + LocalClientId = ServerClientId; + NextClientId = ServerClientId + 1; + LocalClient = New(LocalClientId, NetworkConnection{ 0 }); // Auto-connect host LocalClient->State = NetworkConnectionState::Connected; @@ -287,6 +298,7 @@ void NetworkManager::Stop() return; PROFILE_CPU(); + LOG(Info, "Stopping network manager"); State = NetworkConnectionState::Disconnecting; if (LocalClient) LocalClient->State = NetworkConnectionState::Disconnecting; @@ -355,7 +367,6 @@ void NetworkManagerService::Update() // Send initial handshake message from client to server NetworkMessageHandshake msgData; - msgData.ID = NetworkMessageIDs::Handshake; msgData.EngineBuild = FLAXENGINE_VERSION_BUILD; msgData.EngineProtocolVersion = NETWORK_PROTOCOL_VERSION; msgData.GameProtocolVersion = GameProtocolVersion; @@ -395,6 +406,7 @@ void NetworkManagerService::Update() client->State = NetworkConnectionState::Disconnecting; LOG(Info, "Client id={0} disconnected", event.Sender.ConnectionId); NetworkManager::Clients.RemoveKeepOrder(client); + NetworkInternal::NetworkReplicatorClientDisconnected(client); NetworkManager::ClientDisconnected(client); client->State = NetworkConnectionState::Disconnected; Delete(client); diff --git a/Source/Engine/Networking/NetworkManager.h b/Source/Engine/Networking/NetworkManager.h index 308c9afa2..3f9af90e9 100644 --- a/Source/Engine/Networking/NetworkManager.h +++ b/Source/Engine/Networking/NetworkManager.h @@ -74,6 +74,16 @@ public: /// API_FIELD(ReadOnly) static uint32 Frame; + /// + /// Server client identifier. Constant value of 0. + /// + API_FIELD(ReadOnly) static constexpr uint32 ServerClientId = 0; + + /// + /// Local client identifier. Valid even on server that doesn't have LocalClient. + /// + API_FIELD(ReadOnly) static uint32 LocalClientId; + /// /// Local client, valid only when Network Manager is running in client or host mode (server doesn't have a client). /// diff --git a/Source/Engine/Networking/NetworkReplicator.cpp b/Source/Engine/Networking/NetworkReplicator.cpp index 2c44f5e0a..5c3df9de0 100644 --- a/Source/Engine/Networking/NetworkReplicator.cpp +++ b/Source/Engine/Networking/NetworkReplicator.cpp @@ -26,22 +26,34 @@ PACK_STRUCT(struct NetworkMessageReplicatedObject { - NetworkMessageIDs ID; + NetworkMessageIDs ID = NetworkMessageIDs::ReplicatedObject; uint32 OwnerFrame; Guid ObjectId; // TODO: introduce networked-ids to synchronize unique ids as ushort (less data over network) - Guid OwnerId; + Guid ParentId; char ObjectTypeName[128]; // TODO: introduce networked-name to synchronize unique names as ushort (less data over network) uint16 DataSize; }); +PACK_STRUCT(struct NetworkMessageSpawnObject + { + NetworkMessageIDs ID = NetworkMessageIDs::SpawnObject; + Guid ObjectId; + Guid ParentId; + uint32 OwnerClientId; + char ObjectTypeName[128]; // TODO: introduce networked-name to synchronize unique names as ushort (less data over network) + }); + struct NetworkReplicatedObject { ScriptingObjectReference Object; Guid ObjectId; - Guid OwnerId; - uint32 LastOwnerFrameSync = 0; + Guid ParentId; + uint32 OwnerClientId; + uint32 LastOwnerFrame = 0; + NetworkObjectRole Role; + uint8 Spawned = false; #if NETWORK_REPLICATOR_DEBUG_LOG - bool InvalidTypeWarn = false; + uint8 InvalidTypeWarn = false; #endif bool operator==(const NetworkReplicatedObject& other) const @@ -80,9 +92,11 @@ namespace { CriticalSection ObjectsLock; HashSet Objects; + Array> SpawnQueue; Dictionary IdsRemappingTable; NetworkStream* CachedWriteStream = nullptr; NetworkStream* CachedReadStream = nullptr; + Array NewClients; Array CachedTargets; Dictionary SerializersTable; } @@ -115,16 +129,25 @@ void INetworkSerializable_Deserialize(void* instance, NetworkStream* stream, voi ((INetworkSerializable*)instance)->Deserialize(stream); } -NetworkReplicatedObject* ResolveObject(Guid objectId, Guid ownerId, char objectTypeName[128]) +NetworkReplicatedObject* ResolveObject(Guid objectId) { - // Lookup object - IdsRemappingTable.TryGet(objectId, objectId); - const auto it = Objects.Find(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(ownerId, ownerId); + IdsRemappingTable.TryGet(parentId, parentId); const ScriptingTypeHandle objectType = Scripting::FindScriptingType(StringAnsiView(objectTypeName)); if (!objectType) return nullptr; @@ -132,8 +155,8 @@ NetworkReplicatedObject* ResolveObject(Guid objectId, Guid ownerId, char objectT { auto& item = e.Item; const ScriptingObject* obj = item.Object.Get(); - if (item.OwnerId == ownerId && - item.LastOwnerFrameSync == 0 && + if (item.LastOwnerFrame == 0 && + item.ParentId == parentId && obj && obj->GetTypeHandle() == objectType) { @@ -204,11 +227,11 @@ bool NetworkReplicator::InvokeSerializer(const ScriptingTypeHandle& typeHandle, return false; } -void NetworkReplicator::AddObject(ScriptingObject* obj, ScriptingObject* owner) +void NetworkReplicator::AddObject(ScriptingObject* obj, ScriptingObject* parent) { if (!obj || NetworkManager::State == NetworkConnectionState::Offline) return; - CHECK(owner && owner != obj); + CHECK(parent && parent != obj); ScopeLock lock(ObjectsLock); if (Objects.Contains(obj)) return; @@ -217,13 +240,71 @@ void NetworkReplicator::AddObject(ScriptingObject* obj, ScriptingObject* owner) NetworkReplicatedObject item; item.Object = obj; item.ObjectId = obj->GetID(); - item.OwnerId = owner->GetID(); + item.ParentId = parent->GetID(); + item.OwnerClientId = NetworkManager::ServerClientId; // Server owns objects by default + item.Role = NetworkManager::IsClient() ? NetworkObjectRole::Replicated : NetworkObjectRole::OwnedAuthoritative; #if NETWORK_REPLICATOR_DEBUG_LOG - LOG(Info, "[NetworkReplicator] Add new object {}:{}, owned by {}:{}", item.ToString(), obj->GetType().ToString(), item.OwnerId.ToString(), owner->GetType().ToString()); + LOG(Info, "[NetworkReplicator] Add new object {}:{}, parent {}:{}", item.ToString(), obj->GetType().ToString(), item.ParentId.ToString(), parent->GetType().ToString()); #endif Objects.Add(MoveTemp(item)); } +void NetworkReplicator::SpawnObject(ScriptingObject* obj) +{ + if (!obj || NetworkManager::State == NetworkConnectionState::Offline) + return; + ScopeLock lock(ObjectsLock); + auto it = Objects.Find(obj->GetID()); + if (it == Objects.End()) + { + // Ensure that object is added to the replication locally + AddObject(obj, nullptr); + it = Objects.Find(obj->GetID()); + } + + // Register for spawning (batched spawning during update) + ASSERT_LOW_LAYER(!SpawnQueue.Contains(obj)); + SpawnQueue.Add(obj); +} + +uint32 NetworkReplicator::GetObjectClientId(ScriptingObject* obj) +{ + uint32 id = 0; + if (obj) + { + ScopeLock lock(ObjectsLock); + const auto it = Objects.Find(obj->GetID()); + if (it != Objects.End()) + id = it->Item.OwnerClientId; + } + return id; +} + +NetworkObjectRole NetworkReplicator::GetObjectRole(ScriptingObject* obj) +{ + NetworkObjectRole role = NetworkObjectRole::None; + if (obj) + { + ScopeLock lock(ObjectsLock); + const auto it = Objects.Find(obj->GetID()); + if (it != Objects.End()) + role = it->Item.Role; + } + return role; +} + +void NetworkInternal::NetworkReplicatorClientConnected(NetworkClient* client) +{ + ScopeLock lock(ObjectsLock); + NewClients.Add(client); +} + +void NetworkInternal::NetworkReplicatorClientDisconnected(NetworkClient* client) +{ + ScopeLock lock(ObjectsLock); + NewClients.Remove(client); +} + void NetworkInternal::NetworkReplicatorClear() { ScopeLock lock(ObjectsLock); @@ -238,6 +319,7 @@ void NetworkInternal::NetworkReplicatorClear() IdsRemappingTable.SetCapacity(0); SAFE_DELETE(CachedWriteStream); SAFE_DELETE(CachedReadStream); + NewClients.Clear(); CachedTargets.Resize(0); } @@ -255,30 +337,105 @@ void NetworkInternal::NetworkReplicatorUpdate() return; if (CachedWriteStream == nullptr) CachedWriteStream = New(); + const bool isClient = NetworkManager::IsClient(); NetworkStream* stream = CachedWriteStream; NetworkPeer* peer = NetworkManager::Peer; // TODO: introduce NetworkReplicationHierarchy to optimize objects replication in large worlds (eg. batched culling networked scene objects that are too far from certain client to be relevant) // TODO: per-object sync interval (in frames) - could be scaled by hierarchy (eg. game could slow down sync rate for objects far from player) // TODO: network authority (eg. object owned by client that can affect server) - if (NetworkManager::IsClient()) + if (!isClient && NewClients.Count() != 0) { - // TODO: client logic to apply replication changes - // TODO: client logic to send owned objects to the server + // 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; + + // Send spawn message + NetworkMessageSpawnObject msgData; + msgData.ObjectId = item.ObjectId; + msgData.ParentId = item.ParentId; + msgData.OwnerClientId = item.OwnerClientId; + // TODO: support spawning whole prefabs + const StringAnsiView& objectTypeName = obj->GetType().Fullname; + Platform::MemoryCopy(msgData.ObjectTypeName, objectTypeName.Get(), objectTypeName.Length()); + msgData.ObjectTypeName[objectTypeName.Length()] = 0; + NetworkMessage msg = peer->BeginSendMessage(); + msg.WriteStructure(msgData); + peer->EndSendMessage(NetworkChannelType::ReliableOrdered, msg, CachedTargets); + } + 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); + } + if (!isClient && CachedTargets.Count() == 0) + { + // Early exit if server has nobody to send data to + Scripting::ObjectsLookupIdMapping.Set(nullptr); + return; + } + + // Spawn + if (SpawnQueue.Count() != 0) + { + PROFILE_CPU_NAMED("SpawnQueue"); + for (ScriptingObjectReference& e : SpawnQueue) + { + ScriptingObject* obj = e.Get(); + const auto it = Objects.Find(obj->GetID()); + if (it == Objects.End()) + continue; // Skip deleted objects + auto& item = it->Item; + if (item.OwnerClientId != NetworkManager::LocalClientId || item.Role != NetworkObjectRole::OwnedAuthoritative) + continue; // Skip spawning objects that we don't own + + // Send spawn message + NetworkMessageSpawnObject msgData; + msgData.ObjectId = item.ObjectId; + msgData.ParentId = item.ParentId; + msgData.OwnerClientId = item.OwnerClientId; + if (isClient) + { + // Remap local client object ids into server ids + IdsRemappingTable.KeyOf(msgData.ObjectId, &msgData.ObjectId); + IdsRemappingTable.KeyOf(msgData.ParentId, &msgData.ParentId); + } + // TODO: support spawning whole prefabs + const StringAnsiView& objectTypeName = obj->GetType().Fullname; + Platform::MemoryCopy(msgData.ObjectTypeName, objectTypeName.Get(), objectTypeName.Length()); + msgData.ObjectTypeName[objectTypeName.Length()] = 0; + NetworkMessage msg = peer->BeginSendMessage(); + msg.WriteStructure(msgData); + if (isClient) + peer->EndSendMessage(NetworkChannelType::ReliableOrdered, msg); + else + peer->EndSendMessage(NetworkChannelType::ReliableOrdered, msg, CachedTargets); + + item.Spawned = true; + } + SpawnQueue.Clear(); + } + + if (isClient) + { + // TODO: client logic to replicate owned objects to the server } else { - // Collect clients for replication - CachedTargets.Clear(); - // TODO: per-object relevancy for connected clients (eg. skip replicating actor to far players) - for (const NetworkClient* client : NetworkManager::Clients) - { - if (client->State == NetworkConnectionState::Connected) - { - CachedTargets.Add(client->Connection); - } - } - // Brute force synchronize all networked objects with clients for (auto it = Objects.Begin(); it.IsNotEnd(); ++it) { @@ -288,7 +445,7 @@ void NetworkInternal::NetworkReplicatorUpdate() { // Object got deleted #if NETWORK_REPLICATOR_DEBUG_LOG - LOG(Info, "[NetworkReplicator] Remove object {}, owned by {}", item.ToString(), item.OwnerId.ToString()); + LOG(Info, "[NetworkReplicator] Remove object {}, owned by {}", item.ToString(), item.ParentId.ToString()); #endif Objects.Remove(it); continue; @@ -314,10 +471,9 @@ void NetworkInternal::NetworkReplicatorUpdate() const uint32 size = stream->GetPosition(); ASSERT(size <= MAX_uint16) NetworkMessageReplicatedObject msgData; - msgData.ID = NetworkMessageIDs::ReplicatedObject; msgData.OwnerFrame = NetworkManager::Frame; msgData.ObjectId = item.ObjectId; - msgData.OwnerId = item.OwnerId; + msgData.ParentId = item.ParentId; const StringAnsiView& objectTypeName = obj->GetType().Fullname; Platform::MemoryCopy(msgData.ObjectTypeName, objectTypeName.Get(), objectTypeName.Length()); msgData.ObjectTypeName[objectTypeName.Length()] = 0; @@ -326,6 +482,7 @@ void NetworkInternal::NetworkReplicatorUpdate() NetworkMessage msg = peer->BeginSendMessage(); msg.WriteStructure(msgData); msg.WriteBytes(stream->GetBuffer(), size); + // TODO: per-object relevancy for connected clients (eg. skip replicating actor to far players) peer->EndSendMessage(NetworkChannelType::Unreliable, msg, CachedTargets); // TODO: stats for bytes send per object type @@ -342,7 +499,7 @@ void NetworkInternal::OnNetworkMessageReplicatedObject(NetworkEvent& event, Netw NetworkMessageReplicatedObject msgData; event.Message.ReadStructure(msgData); ScopeLock lock(ObjectsLock); - NetworkReplicatedObject* e = ResolveObject(msgData.ObjectId, msgData.OwnerId, msgData.ObjectTypeName); + NetworkReplicatedObject* e = ResolveObject(msgData.ObjectId, msgData.ParentId, msgData.ObjectTypeName); if (e) { auto& item = *e; @@ -350,10 +507,15 @@ void NetworkInternal::OnNetworkMessageReplicatedObject(NetworkEvent& event, Netw if (!obj) return; - // Drop object replication if it has old data (eg. newer message was already processed due to unordered channel usage) - if (item.LastOwnerFrameSync >= msgData.OwnerFrame) + // Reject replication from someone who is not an object owner + if (client && e->OwnerClientId != client->ClientId) return; - item.LastOwnerFrameSync = msgData.OwnerFrame; + 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) + return; + item.LastOwnerFrame = msgData.OwnerFrame; // Setup message reading stream if (CachedReadStream == nullptr) @@ -379,3 +541,65 @@ void NetworkInternal::OnNetworkMessageReplicatedObject(NetworkEvent& event, Netw // TODO: put message to the queue to be resolved later (eg. object replication came before spawn packet) - use TTL to prevent memory overgrowing } } + +void NetworkInternal::OnNetworkMessageSpawnObject(NetworkEvent& event, NetworkClient* client, NetworkPeer* peer) +{ + NetworkMessageSpawnObject msgData; + event.Message.ReadStructure(msgData); + ScopeLock lock(ObjectsLock); + NetworkReplicatedObject* e = ResolveObject(msgData.ObjectId, msgData.ParentId, msgData.ObjectTypeName); + if (e) + { + auto& item = *e; + item.Spawned = true; + if (NetworkManager::IsClient()) + { + // Server always knows the best so update ownership of the existing object + item.OwnerClientId = msgData.OwnerClientId; + if (item.Role == NetworkObjectRole::OwnedAuthoritative) + item.Role = NetworkObjectRole::Replicated; + } + else if (item.OwnerClientId != msgData.OwnerClientId) + { + // Other client spawned object with a different owner + // TODO: send reply message to inform about proper object ownership that client + } + } + else + { + // Recreate object locally + // TODO: support spawning whole prefabs + const ScriptingTypeHandle objectType = Scripting::FindScriptingType(StringAnsiView(msgData.ObjectTypeName)); + ScriptingObject* obj = ScriptingObject::NewObject(objectType); + if (!obj) + { +#if NETWORK_REPLICATOR_DEBUG_LOG + LOG(Error, "[NetworkReplicator] Failed to spawn object type {}", String(msgData.ObjectTypeName)); +#endif + return; + } + obj->RegisterObject(); + const NetworkReplicatedObject* parent = ResolveObject(msgData.ParentId); + + // Add object to the list + NetworkReplicatedObject item; + item.Object = obj; + item.ObjectId = obj->GetID(); + item.ParentId = parent ? parent->ObjectId : Guid::Empty; + item.OwnerClientId = client ? client->ClientId : NetworkManager::ServerClientId; + item.Role = NetworkObjectRole::Replicated; + item.Spawned = true; +#if NETWORK_REPLICATOR_DEBUG_LOG + LOG(Info, "[NetworkReplicator] Add new object {}:{}, parent {}:{}", item.ToString(), obj->GetType().ToString(), item.ParentId.ToString(), parent ? parent->Object->GetType().ToString() : String::Empty); +#endif + Objects.Add(MoveTemp(item)); + + // Boost future lookups by using indirection +#if NETWORK_REPLICATOR_DEBUG_LOG + LOG(Info, "[NetworkReplicator] Remap object ID={} into object {}:{}", msgData.ObjectId, item.ToString(), obj->GetType().ToString()); +#endif + IdsRemappingTable.Add(msgData.ObjectId, item.ObjectId); + + // TODO: if we're server then spawn this object further on other clients + } +} diff --git a/Source/Engine/Networking/NetworkReplicator.h b/Source/Engine/Networking/NetworkReplicator.h index 14a40070a..560ae3ae0 100644 --- a/Source/Engine/Networking/NetworkReplicator.h +++ b/Source/Engine/Networking/NetworkReplicator.h @@ -6,6 +6,21 @@ #include "Engine/Scripting/ScriptingObject.h" #include "Engine/Scripting/ScriptingType.h" +/// +/// The high-level network object role and authority. Used to define who owns the object and when it can be simulated or just replicated. +/// +API_ENUM(Namespace="FlaxEngine.Networking") enum class NetworkObjectRole : byte +{ + // Not replicated object. + None = 0, + // Server/client owns the object and replicates it to others. Only owning client can simulate object and provides current state. + OwnedAuthoritative, + // Server/client gets replicated object from other server/client who owns it. Object cannot be simulated locally (any changes will be overriden by replication). + Replicated, + // Client gets replicated object from server but still can locally autonomously simulate it too. For example, client can control local pawn with real human input but will validate with server proper state (eg. to prevent cheats). + ReplicatedAutonomous, +}; + /// /// High-level networking replication system for game objects. /// @@ -41,8 +56,29 @@ public: /// /// Does nothing if network is offline. /// The object to replicate. - /// The owner of the object (eg. player that spawned it). - API_FUNCTION() static void AddObject(ScriptingObject* obj, ScriptingObject* owner); + /// The parent of the object (eg. player that spawned it). + API_FUNCTION() static void AddObject(ScriptingObject* obj, ScriptingObject* parent); + + /// + /// 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. + API_FUNCTION() static void SpawnObject(ScriptingObject* obj); + + /// + /// Gets the Client Id of the network object owner. + /// + /// The network object. + /// The Client Id. + API_FUNCTION() static uint32 GetObjectClientId(ScriptingObject* obj); + + /// + /// Gets the role of the network object used locally (eg. to check if can simulate object). + /// + /// The network object. + /// The object role. + API_FUNCTION() static NetworkObjectRole GetObjectRole(ScriptingObject* obj); private: #if !COMPILE_WITHOUT_CSHARP diff --git a/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs b/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs index 75e216b6b..1cfd99cfd 100644 --- a/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs +++ b/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs @@ -40,7 +40,7 @@ namespace Flax.Build.Plugins internal const string NetworkReplicated = "NetworkReplicated"; private const string Thunk1 = "INetworkSerializable_Serialize"; private const string Thunk2 = "INetworkSerializable_Deserialize"; - private static Dictionary _inBuildSerializers = new Dictionary() + private static readonly Dictionary _inBuildSerializers = new Dictionary() { { "System.Boolean", new InBuildSerializer("WriteBoolean", "ReadBoolean") }, { "System.Single", new InBuildSerializer("WriteSingle", "ReadSingle") },