Add object replication data cache and send via Reliable channel to reduce data transfer

This commit is contained in:
Wojtek Figat
2024-10-15 19:47:09 +02:00
parent 60ed23105d
commit c94052513e
4 changed files with 74 additions and 14 deletions

View File

@@ -7,6 +7,15 @@
#include "Engine/Core/Collections/Dictionary.h" #include "Engine/Core/Collections/Dictionary.h"
#endif #endif
// Internal version number of networking implementation. Updated once engine changes serialization or connection rules.
#define NETWORK_PROTOCOL_VERSION 4
// Enables encoding object ids and typenames via uint32 keys rather than full data send.
#define USE_NETWORK_KEYS 1
// Cached replication messages if contents didn't change
#define USE_NETWORK_REPLICATOR_CACHE 1
enum class NetworkMessageIDs : uint8 enum class NetworkMessageIDs : uint8
{ {
None = 0, None = 0,

View File

@@ -16,8 +16,6 @@
#include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Scripting/Scripting.h" #include "Engine/Scripting/Scripting.h"
#define NETWORK_PROTOCOL_VERSION 4
float NetworkManager::NetworkFPS = 60.0f; float NetworkManager::NetworkFPS = 60.0f;
NetworkPeer* NetworkManager::Peer = nullptr; NetworkPeer* NetworkManager::Peer = nullptr;
NetworkManagerMode NetworkManager::Mode = NetworkManagerMode::Offline; NetworkManagerMode NetworkManager::Mode = NetworkManagerMode::Offline;
@@ -140,6 +138,7 @@ FORCE_INLINE bool IsNetworkKeyValid(uint32 index)
void NetworkMessage::WriteNetworkId(const Guid& id) void NetworkMessage::WriteNetworkId(const Guid& id)
{ {
#if USE_NETWORK_KEYS
ScopeLock lock(Keys.Lock); ScopeLock lock(Keys.Lock);
uint32 index = MAX_uint32; uint32 index = MAX_uint32;
bool hasIndex = Keys.LookupId.TryGet(id, index); bool hasIndex = Keys.LookupId.TryGet(id, index);
@@ -158,10 +157,14 @@ void NetworkMessage::WriteNetworkId(const Guid& id)
Keys.PendingIds.Add(id, NetworkKey(id)); Keys.PendingIds.Add(id, NetworkKey(id));
} }
} }
#else
WriteBytes((const uint8*)&id, sizeof(Guid));
#endif
} }
void NetworkMessage::ReadNetworkId(Guid& id) void NetworkMessage::ReadNetworkId(Guid& id)
{ {
#if USE_NETWORK_KEYS
ScopeLock lock(Keys.Lock); ScopeLock lock(Keys.Lock);
uint32 index = ReadUInt32(); uint32 index = ReadUInt32();
if (index != MAX_uint32) if (index != MAX_uint32)
@@ -193,10 +196,14 @@ void NetworkMessage::ReadNetworkId(Guid& id)
Keys.PendingIds.Add(id, NetworkKey(id)); Keys.PendingIds.Add(id, NetworkKey(id));
} }
} }
#else
ReadBytes((uint8*)&id, sizeof(Guid));
#endif
} }
void NetworkMessage::WriteNetworkName(const StringAnsiView& name) void NetworkMessage::WriteNetworkName(const StringAnsiView& name)
{ {
#if USE_NETWORK_KEYS
ScopeLock lock(Keys.Lock); ScopeLock lock(Keys.Lock);
uint32 index = MAX_uint32; uint32 index = MAX_uint32;
bool hasIndex = Keys.LookupName.TryGet(name, index); bool hasIndex = Keys.LookupName.TryGet(name, index);
@@ -216,10 +223,14 @@ void NetworkMessage::WriteNetworkName(const StringAnsiView& name)
Keys.PendingNames.Add(newName, NetworkKey(newName)); Keys.PendingNames.Add(newName, NetworkKey(newName));
} }
} }
#else
WriteStringAnsi(name);
#endif
} }
void NetworkMessage::ReadNetworkName(StringAnsiView& name) void NetworkMessage::ReadNetworkName(StringAnsiView& name)
{ {
#if USE_NETWORK_KEYS
ScopeLock lock(Keys.Lock); ScopeLock lock(Keys.Lock);
uint32 index = ReadUInt32(); uint32 index = ReadUInt32();
if (index != MAX_uint32) if (index != MAX_uint32)
@@ -252,6 +263,9 @@ void NetworkMessage::ReadNetworkName(StringAnsiView& name)
Keys.PendingNames.Add(newName, NetworkKey(newName)); Keys.PendingNames.Add(newName, NetworkKey(newName));
} }
} }
#else
name = ReadStringAnsi();
#endif
} }
void OnNetworkMessageHandshake(NetworkEvent& event, NetworkClient* client, NetworkPeer* peer) void OnNetworkMessageHandshake(NetworkEvent& event, NetworkClient* client, NetworkPeer* peer)

View File

@@ -126,12 +126,29 @@ struct NetworkReplicatedObject
DataContainer<uint32> TargetClientIds; DataContainer<uint32> TargetClientIds;
INetworkObject* AsNetworkObject; INetworkObject* AsNetworkObject;
struct
{
NetworkClientsMask Mask;
BytesContainer Data;
void Clear()
{
Mask = NetworkClientsMask();
Data.Release();
}
} RepCache;
NetworkReplicatedObject() NetworkReplicatedObject()
{ {
Spawned = 0; Spawned = 0;
Synced = 0; Synced = 0;
} }
void Dirty()
{
RepCache.Mask = NetworkClientsMask();
}
bool operator==(const NetworkReplicatedObject& other) const bool operator==(const NetworkReplicatedObject& other) const
{ {
return Object == other.Object; return Object == other.Object;
@@ -684,6 +701,7 @@ void FindObjectsForSpawn(SpawnGroup& group, ChunkedArray<SpawnItem, 256>& spawnI
FORCE_INLINE void DirtyObjectImpl(NetworkReplicatedObject& item, ScriptingObject* obj) FORCE_INLINE void DirtyObjectImpl(NetworkReplicatedObject& item, ScriptingObject* obj)
{ {
item.Dirty();
if (Hierarchy) if (Hierarchy)
Hierarchy->DirtyObject(obj); Hierarchy->DirtyObject(obj);
} }
@@ -787,6 +805,7 @@ void InvokeObjectSpawn(const NetworkMessageObjectSpawn& msgData, const Guid& pre
{ {
// Server always knows the best so update ownership of the existing object // Server always knows the best so update ownership of the existing object
item.OwnerClientId = msgData.OwnerClientId; item.OwnerClientId = msgData.OwnerClientId;
item.RepCache.Clear();
if (item.Role == NetworkObjectRole::OwnedAuthoritative) if (item.Role == NetworkObjectRole::OwnedAuthoritative)
{ {
if (Hierarchy) if (Hierarchy)
@@ -1442,6 +1461,7 @@ void NetworkReplicator::SetObjectOwnership(ScriptingObject* obj, uint32 ownerCli
item.OwnerClientId = ownerClientId; item.OwnerClientId = ownerClientId;
item.LastOwnerFrame = 1; item.LastOwnerFrame = 1;
item.Role = localRole; item.Role = localRole;
item.RepCache.Clear();
SendObjectRoleMessage(item); SendObjectRoleMessage(item);
} }
} }
@@ -1449,11 +1469,11 @@ void NetworkReplicator::SetObjectOwnership(ScriptingObject* obj, uint32 ownerCli
{ {
// Allow to change local role of the object (except ownership) // Allow to change local role of the object (except ownership)
#if !BUILD_RELEASE #if !BUILD_RELEASE
if (localRole == NetworkObjectRole::OwnedAuthoritative) if (localRole == NetworkObjectRole::OwnedAuthoritative)
{ {
LOG(Error, "Cannot change ownership of object (Id={}) to the remote client (Id={}) if the local role is set to OwnedAuthoritative.", obj->GetID(), ownerClientId); LOG(Error, "Cannot change ownership of object (Id={}) to the remote client (Id={}) if the local role is set to OwnedAuthoritative.", obj->GetID(), ownerClientId);
return; return;
} }
#endif #endif
if (Hierarchy && it->Item.Role == NetworkObjectRole::OwnedAuthoritative) if (Hierarchy && it->Item.Role == NetworkObjectRole::OwnedAuthoritative)
Hierarchy->RemoveObject(obj); Hierarchy->RemoveObject(obj);
@@ -1864,10 +1884,28 @@ void NetworkInternal::NetworkReplicatorUpdate()
//NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Cannot serialize object {} of type {} (missing serialization logic)", item.ToString(), obj->GetType().ToString()); //NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Cannot serialize object {} of type {} (missing serialization logic)", item.ToString(), obj->GetType().ToString());
continue; continue;
} }
const uint32 size = stream->GetPosition();
if (size > MAX_uint16)
{
LOG(Error, "Too much data for object {} replication ({} bytes provided while limit is {}).", item.ToString(), size, MAX_uint16);
continue;
}
#if USE_NETWORK_REPLICATOR_CACHE
// Process replication cache to skip sending object data if it didn't change
if (item.RepCache.Data.Length() == size &&
item.RepCache.Mask == e.TargetClients &&
Platform::MemoryCompare(item.RepCache.Data.Get(), stream->GetBuffer(), size) == 0)
{
continue;
}
item.RepCache.Mask = e.TargetClients;
item.RepCache.Data.Copy(stream->GetBuffer(), size);
#endif
// TODO: use Unreliable for dynamic objects that are replicated every frame? (eg. player state)
constexpr NetworkChannelType repChannel = NetworkChannelType::Reliable;
// Send object to clients // Send object to clients
const uint32 size = stream->GetPosition();
ASSERT(size <= MAX_uint16);
NetworkMessageObjectReplicate msgData; NetworkMessageObjectReplicate msgData;
msgData.OwnerFrame = NetworkManager::Frame; msgData.OwnerFrame = NetworkManager::Frame;
Guid objectId = item.ObjectId, parentId = item.ParentId; Guid objectId = item.ObjectId, parentId = item.ParentId;
@@ -1908,9 +1946,9 @@ void NetworkInternal::NetworkReplicatorUpdate()
msg.WriteBytes(stream->GetBuffer(), msgDataSize); msg.WriteBytes(stream->GetBuffer(), msgDataSize);
uint32 dataSize = msgDataSize, messageSize = msg.Length; uint32 dataSize = msgDataSize, messageSize = msg.Length;
if (isClient) if (isClient)
peer->EndSendMessage(NetworkChannelType::Unreliable, msg); peer->EndSendMessage(repChannel, msg);
else else
peer->EndSendMessage(NetworkChannelType::Unreliable, msg, CachedTargets); peer->EndSendMessage(repChannel, msg, CachedTargets);
// Send all other parts // Send all other parts
for (uint32 partIndex = 1; partIndex < partsCount; partIndex++) for (uint32 partIndex = 1; partIndex < partsCount; partIndex++)
@@ -1929,9 +1967,9 @@ void NetworkInternal::NetworkReplicatorUpdate()
dataSize += msgDataPart.PartSize; dataSize += msgDataPart.PartSize;
dataStart += msgDataPart.PartSize; dataStart += msgDataPart.PartSize;
if (isClient) if (isClient)
peer->EndSendMessage(NetworkChannelType::Unreliable, msg); peer->EndSendMessage(repChannel, msg);
else else
peer->EndSendMessage(NetworkChannelType::Unreliable, msg, CachedTargets); peer->EndSendMessage(repChannel, msg, CachedTargets);
} }
ASSERT_LOW_LAYER(dataStart == size); ASSERT_LOW_LAYER(dataStart == size);

View File

@@ -37,7 +37,6 @@ TEST_CASE("Networking")
} }
} }
} }
int dataSizeInkB = writeStream->GetPosition() / 1024; // 4291 -> 1869
readStream->Initialize(writeStream->GetBuffer(), writeStream->GetPosition()); readStream->Initialize(writeStream->GetBuffer(), writeStream->GetPosition());
for (int32 x = 0; x <= QuatRes; x++) for (int32 x = 0; x <= QuatRes; x++)
{ {