Merge branch 'VitaminCpp-late_join_fix'
This commit is contained in:
@@ -100,6 +100,35 @@ API_STRUCT(NoDefault, Namespace="FlaxEngine.Networking") struct FLAXENGINE_API N
|
|||||||
return Word0 + Word1 != 0;
|
return Word0 + Word1 != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NetworkClientsMask operator&(const NetworkClientsMask& other) const
|
||||||
|
{
|
||||||
|
return { Word0 & other.Word0, Word1 & other.Word1 };
|
||||||
|
}
|
||||||
|
|
||||||
|
NetworkClientsMask operator|(const NetworkClientsMask& other) const
|
||||||
|
{
|
||||||
|
return { Word0 | other.Word0, Word1 | other.Word1 };
|
||||||
|
}
|
||||||
|
|
||||||
|
NetworkClientsMask operator~() const
|
||||||
|
{
|
||||||
|
return { ~Word0, ~Word1 };
|
||||||
|
}
|
||||||
|
|
||||||
|
NetworkClientsMask& operator|=(const NetworkClientsMask& other)
|
||||||
|
{
|
||||||
|
Word0 |= other.Word0;
|
||||||
|
Word1 |= other.Word1;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
NetworkClientsMask& operator&=(const NetworkClientsMask& other)
|
||||||
|
{
|
||||||
|
Word0 &= other.Word0;
|
||||||
|
Word1 &= other.Word1;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
bool operator==(const NetworkClientsMask& other) const
|
bool operator==(const NetworkClientsMask& other) const
|
||||||
{
|
{
|
||||||
return Word0 == other.Word0 && Word1 == other.Word1;
|
return Word0 == other.Word0 && Word1 == other.Word1;
|
||||||
|
|||||||
@@ -703,14 +703,11 @@ void SendReplication(ScriptingObject* obj, NetworkClientsMask targetClients)
|
|||||||
return;
|
return;
|
||||||
auto& item = it->Item;
|
auto& item = it->Item;
|
||||||
const bool isClient = NetworkManager::IsClient();
|
const bool isClient = NetworkManager::IsClient();
|
||||||
|
const NetworkClientsMask fullTargetClients = targetClients;
|
||||||
|
|
||||||
// Skip serialization of objects that none will receive
|
// If server has no recipients, skip early.
|
||||||
if (!isClient)
|
if (!isClient && !targetClients)
|
||||||
{
|
return;
|
||||||
BuildCachedTargets(item, targetClients);
|
|
||||||
if (CachedTargets.Count() == 0)
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item.AsNetworkObject)
|
if (item.AsNetworkObject)
|
||||||
item.AsNetworkObject->OnNetworkSerialize();
|
item.AsNetworkObject->OnNetworkSerialize();
|
||||||
@@ -733,18 +730,30 @@ void SendReplication(ScriptingObject* obj, NetworkClientsMask targetClients)
|
|||||||
|
|
||||||
#if USE_NETWORK_REPLICATOR_CACHE
|
#if USE_NETWORK_REPLICATOR_CACHE
|
||||||
// Process replication cache to skip sending object data if it didn't change
|
// Process replication cache to skip sending object data if it didn't change
|
||||||
if (item.RepCache.Data.Length() == size &&
|
if (item.RepCache.Data.Length() == size && Platform::MemoryCompare(item.RepCache.Data.Get(), stream->GetBuffer(), size) == 0)
|
||||||
item.RepCache.Mask == targetClients &&
|
|
||||||
Platform::MemoryCompare(item.RepCache.Data.Get(), stream->GetBuffer(), size) == 0)
|
|
||||||
{
|
{
|
||||||
return;
|
// Check if only newly joined clients are missing this data to avoid resending it to everyone
|
||||||
|
NetworkClientsMask missingClients = targetClients & ~item.RepCache.Mask;
|
||||||
|
|
||||||
|
// If data is the same and only the client set changed, replicate to missing clients only
|
||||||
|
if (!missingClients)
|
||||||
|
return;
|
||||||
|
targetClients = missingClients;
|
||||||
}
|
}
|
||||||
item.RepCache.Mask = targetClients;
|
item.RepCache.Mask = fullTargetClients;
|
||||||
item.RepCache.Data.Copy(stream->GetBuffer(), size);
|
item.RepCache.Data.Copy(stream->GetBuffer(), size);
|
||||||
#endif
|
#endif
|
||||||
// TODO: use Unreliable for dynamic objects that are replicated every frame? (eg. player state)
|
// TODO: use Unreliable for dynamic objects that are replicated every frame? (eg. player state)
|
||||||
constexpr NetworkChannelType repChannel = NetworkChannelType::Reliable;
|
constexpr NetworkChannelType repChannel = NetworkChannelType::Reliable;
|
||||||
|
|
||||||
|
// Skip serialization of objects that none will receive
|
||||||
|
if (!isClient)
|
||||||
|
{
|
||||||
|
BuildCachedTargets(item, targetClients);
|
||||||
|
if (CachedTargets.Count() == 0)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Send object to clients
|
// Send object to clients
|
||||||
NetworkMessageObjectReplicate msgData;
|
NetworkMessageObjectReplicate msgData;
|
||||||
msgData.OwnerFrame = NetworkManager::Frame;
|
msgData.OwnerFrame = NetworkManager::Frame;
|
||||||
@@ -1535,7 +1544,21 @@ void NetworkReplicator::DespawnObject(ScriptingObject* obj)
|
|||||||
// Register for despawning (batched during update)
|
// Register for despawning (batched during update)
|
||||||
auto& despawn = DespawnQueue.AddOne();
|
auto& despawn = DespawnQueue.AddOne();
|
||||||
despawn.Id = obj->GetID();
|
despawn.Id = obj->GetID();
|
||||||
despawn.Targets = item.TargetClientIds;
|
if (item.TargetClientIds.IsValid())
|
||||||
|
{
|
||||||
|
despawn.Targets = item.TargetClientIds;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Snapshot current recipients to avoid sending despawn to clients that connect later (and never got the spawn)
|
||||||
|
Array<uint32, InlinedAllocation<8>> clientIds;
|
||||||
|
for (const NetworkClient* client : NetworkManager::Clients)
|
||||||
|
{
|
||||||
|
if (client->State == NetworkConnectionState::Connected && client->ClientId != item.OwnerClientId)
|
||||||
|
clientIds.Add(client->ClientId);
|
||||||
|
}
|
||||||
|
despawn.Targets.Copy(clientIds);
|
||||||
|
}
|
||||||
|
|
||||||
// Prevent spawning
|
// Prevent spawning
|
||||||
for (int32 i = 0; i < SpawnQueue.Count(); i++)
|
for (int32 i = 0; i < SpawnQueue.Count(); i++)
|
||||||
@@ -1828,6 +1851,31 @@ void NetworkInternal::NetworkReplicatorClientConnected(NetworkClient* client)
|
|||||||
{
|
{
|
||||||
ScopeLock lock(ObjectsLock);
|
ScopeLock lock(ObjectsLock);
|
||||||
NewClients.Add(client);
|
NewClients.Add(client);
|
||||||
|
|
||||||
|
// Ensure cached replication acknowledges the new client without resending to others.
|
||||||
|
// Clear the new client's bit in RepCache and schedule a near-term replication.
|
||||||
|
const int32 clientIndex = NetworkManager::Clients.Find(client);
|
||||||
|
if (clientIndex != -1)
|
||||||
|
{
|
||||||
|
const uint64 bitMask = 1ull << (uint64)(clientIndex % 64);
|
||||||
|
const int32 wordIndex = clientIndex / 64;
|
||||||
|
for (auto it = Objects.Begin(); it.IsNotEnd(); ++it)
|
||||||
|
{
|
||||||
|
auto& item = it->Item;
|
||||||
|
ScriptingObject* obj = item.Object.Get();
|
||||||
|
if (!obj || !item.Spawned || item.Role != NetworkObjectRole::OwnedAuthoritative)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Mark this client as missing cached data
|
||||||
|
uint64* word = wordIndex == 0 ? &item.RepCache.Mask.Word0 : &item.RepCache.Mask.Word1;
|
||||||
|
*word &= ~bitMask;
|
||||||
|
|
||||||
|
// Force next replication tick for this object so the new client gets data promptly
|
||||||
|
if (Hierarchy)
|
||||||
|
Hierarchy->DirtyObject(obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ASSERT(sizeof(NetworkClientsMask) * 8 >= (uint32)NetworkManager::Clients.Count()); // Ensure that clients mask can hold all of clients
|
ASSERT(sizeof(NetworkClientsMask) * 8 >= (uint32)NetworkManager::Clients.Count()); // Ensure that clients mask can hold all of clients
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2280,7 +2328,9 @@ void NetworkInternal::OnNetworkMessageObjectDespawn(NetworkEvent& event, Network
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Failed to despawn object {}", objectId);
|
// If this client never had the object (eg. it was targeted to other clients only), drop the message quietly
|
||||||
|
DespawnedObjects.Add(objectId);
|
||||||
|
NETWORK_REPLICATOR_LOG(Warning, "[NetworkReplicator] Failed to despawn object {}", objectId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user