Add objects spawning to networking

This commit is contained in:
Wojciech Figat
2022-10-24 12:06:52 +02:00
parent ef77cdfe3f
commit 771f8ad02b
6 changed files with 333 additions and 47 deletions

View File

@@ -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);
};

View File

@@ -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<NetworkClient*> 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<NetworkClient>(0, NetworkConnection{ 0 });
LocalClientId = 0; // Id gets assigned by server later after connection
NextClientId = 0;
LocalClient = New<NetworkClient>(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<NetworkClient>(NextClientId++, NetworkConnection{ 0 });
LocalClientId = ServerClientId;
NextClientId = ServerClientId + 1;
LocalClient = New<NetworkClient>(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);

View File

@@ -74,6 +74,16 @@ public:
/// </summary>
API_FIELD(ReadOnly) static uint32 Frame;
/// <summary>
/// Server client identifier. Constant value of 0.
/// </summary>
API_FIELD(ReadOnly) static constexpr uint32 ServerClientId = 0;
/// <summary>
/// Local client identifier. Valid even on server that doesn't have LocalClient.
/// </summary>
API_FIELD(ReadOnly) static uint32 LocalClientId;
/// <summary>
/// Local client, valid only when Network Manager is running in client or host mode (server doesn't have a client).
/// </summary>

View File

@@ -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<ScriptingObject> 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<NetworkReplicatedObject> Objects;
Array<ScriptingObjectReference<ScriptingObject>> SpawnQueue;
Dictionary<Guid, Guid> IdsRemappingTable;
NetworkStream* CachedWriteStream = nullptr;
NetworkStream* CachedReadStream = nullptr;
Array<NetworkClient*> NewClients;
Array<NetworkConnection> CachedTargets;
Dictionary<ScriptingTypeHandle, Serializer> 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<NetworkStream>();
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<ScriptingObject>& 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
}
}

View File

@@ -6,6 +6,21 @@
#include "Engine/Scripting/ScriptingObject.h"
#include "Engine/Scripting/ScriptingType.h"
/// <summary>
/// The high-level network object role and authority. Used to define who owns the object and when it can be simulated or just replicated.
/// </summary>
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,
};
/// <summary>
/// High-level networking replication system for game objects.
/// </summary>
@@ -41,8 +56,29 @@ public:
/// </summary>
/// <remarks>Does nothing if network is offline.</remarks>
/// <param name="obj">The object to replicate.</param>
/// <param name="owner">The owner of the object (eg. player that spawned it).</param>
API_FUNCTION() static void AddObject(ScriptingObject* obj, ScriptingObject* owner);
/// <param name="parent">The parent of the object (eg. player that spawned it).</param>
API_FUNCTION() static void AddObject(ScriptingObject* obj, ScriptingObject* parent);
/// <summary>
/// Spawns the object to the other clients. Can be spawned by the owner who locally created it (eg. from prefab).
/// </summary>
/// <remarks>Does nothing if network is offline.</remarks>
/// <param name="obj">The object to spawn on other clients.</param>
API_FUNCTION() static void SpawnObject(ScriptingObject* obj);
/// <summary>
/// Gets the Client Id of the network object owner.
/// </summary>
/// <param name="obj">The network object.</param>
/// <returns>The Client Id.</returns>
API_FUNCTION() static uint32 GetObjectClientId(ScriptingObject* obj);
/// <summary>
/// Gets the role of the network object used locally (eg. to check if can simulate object).
/// </summary>
/// <param name="obj">The network object.</param>
/// <returns>The object role.</returns>
API_FUNCTION() static NetworkObjectRole GetObjectRole(ScriptingObject* obj);
private:
#if !COMPILE_WITHOUT_CSHARP

View File

@@ -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<string, InBuildSerializer> _inBuildSerializers = new Dictionary<string, InBuildSerializer>()
private static readonly Dictionary<string, InBuildSerializer> _inBuildSerializers = new Dictionary<string, InBuildSerializer>()
{
{ "System.Boolean", new InBuildSerializer("WriteBoolean", "ReadBoolean") },
{ "System.Single", new InBuildSerializer("WriteSingle", "ReadSingle") },