Add network keys table to optimize ids and names sending over network

#2815
This commit is contained in:
Wojtek Figat
2024-10-14 12:11:20 +02:00
parent 23ad24751a
commit 443fe5dbcb
4 changed files with 506 additions and 114 deletions

View File

@@ -12,6 +12,7 @@ enum class NetworkMessageIDs : uint8
None = 0,
Handshake,
HandshakeReply,
Key,
ObjectReplicate,
ObjectReplicatePart,
ObjectSpawn,

View File

@@ -30,11 +30,72 @@ Delegate<NetworkClientConnectionData&> NetworkManager::ClientConnecting;
Delegate<NetworkClient*> NetworkManager::ClientConnected;
Delegate<NetworkClient*> 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<NetworkKey> Table;
Dictionary<Guid, uint32> LookupId;
Dictionary<StringAnsiView, uint32> LookupName;
Dictionary<Guid, NetworkKey> PendingIds;
Dictionary<StringAnsiView, NetworkKey> 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<NetworkConnection> 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();
}

View File

@@ -67,7 +67,7 @@ public:
/// </summary>
/// <param name="bytes">The bytes that will be written.</param>
/// <param name="numBytes">The amount of bytes to write from the bytes pointer.</param>
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<typename T>
FORCE_INLINE void WriteStructure(const T& data)
{
WriteBytes((uint8*)&data, sizeof(data));
WriteBytes((const uint8*)&data, sizeof(data));
}
template<typename T>
@@ -116,7 +116,7 @@ public:
}
#define DECL_READWRITE(type, name) \
FORCE_INLINE void Write##name(type value) { WriteBytes(reinterpret_cast<uint8*>(&value), sizeof(type)); } \
FORCE_INLINE void Write##name(type value) { WriteBytes(reinterpret_cast<const uint8*>(&value), sizeof(type)); } \
FORCE_INLINE type Read##name() { type value = 0; ReadBytes(reinterpret_cast<uint8*>(&value), sizeof(type)); return value; }
DECL_READWRITE(int8, Int8)
DECL_READWRITE(uint8, UInt8)
@@ -207,22 +207,37 @@ public:
/// <summary>
/// Writes data of type String into the message. UTF-16 encoded.
/// </summary>
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));
}
/// <summary>
/// Reads and returns data of type String from the message. UTF-16 encoded.
/// Writes data of type String into the message.
/// </summary>
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());
}
/// <summary>
/// Reads and returns data of type String from the message. UTF-16 encoded. Data valid within message lifetime.
/// </summary>
FORCE_INLINE StringView ReadString()
{
const uint16 length = ReadUInt16();
return StringView(length ? (const Char*)SkipBytes(length * 2) : nullptr, length);
}
/// <summary>
/// Reads and returns data of type String from the message. ANSI encoded. Data valid within message lifetime.
/// </summary>
FORCE_INLINE StringAnsiView ReadStringAnsi()
{
const uint16 length = ReadUInt16();
return StringAnsiView(length ? (const char*)SkipBytes(length) : nullptr, length);
}
/// <summary>
@@ -230,7 +245,7 @@ public:
/// </summary>
FORCE_INLINE void WriteGuid(const Guid& value)
{
WriteBytes((uint8*)&value, sizeof(Guid));
WriteBytes((const uint8*)&value, sizeof(Guid));
}
/// <summary>
@@ -243,6 +258,30 @@ public:
return value;
}
/// <summary>
/// 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.
/// </summary>
/// <param name="id">Network-synced identifier.</param>
void WriteNetworkId(const Guid& id);
/// <summary>
/// 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.
/// </summary>
/// <param name="id">Network-synced identifier.</param>
void ReadNetworkId(Guid& id);
/// <summary>
/// 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.
/// </summary>
/// <param name="name">Network-synced name.</param>
void WriteNetworkName(const StringAnsiView& name);
/// <summary>
/// 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.
/// </summary>
/// <param name="name">Network-synced name.</param>
void ReadNetworkName(StringAnsiView& name);
public:
/// <summary>
/// Returns true if the message is valid for reading or writing.

View File

@@ -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<NetworkMessageObjectSpawnItem> 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<SceneObject>(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<NetworkClient*>
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<SceneObject>(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<NetworkClient*>
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<NetworkClient*>
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<typename MessageType>
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<ScriptingObject*> objects;
if (msgData.PrefabId.IsValid())
if (prefabId.IsValid())
{
const NetworkReplicatedObject* parent = ResolveObject(rootItem.ParentId);
Actor* parentActor = parent && parent->Object && parent->Object->Is<Actor>() ? parent->Object.As<Actor>() : 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<uint8>(NetworkChannelType::Unreliable) && info->Channel != static_cast<uint8>(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));
}
}