Merge branch 'master' into Improve-HighlightedPopUpColor
This commit is contained in:
@@ -2441,10 +2441,14 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
|
||||
{
|
||||
if (bucket.LoopsLeft == 0)
|
||||
{
|
||||
// End playing animation
|
||||
// End playing animation and reset bucket params
|
||||
value = tryGetValue(node->GetBox(1), Value::Null);
|
||||
bucket.Index = -1;
|
||||
slot.Animation = nullptr;
|
||||
bucket.TimePosition = 0.0f;
|
||||
bucket.BlendInPosition = 0.0f;
|
||||
bucket.BlendOutPosition = 0.0f;
|
||||
bucket.LoopsDone = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -409,27 +409,36 @@ protected:
|
||||
else
|
||||
{
|
||||
// Rebuild entire table completely
|
||||
const int32 elementsCount = _elementsCount;
|
||||
const int32 oldSize = _size;
|
||||
AllocationData oldAllocation;
|
||||
AllocationUtils::MoveToEmpty<BucketType, AllocationType>(oldAllocation, _allocation, _size, _size);
|
||||
AllocationUtils::MoveToEmpty<BucketType, AllocationType>(oldAllocation, _allocation, oldSize, oldSize);
|
||||
_allocation.Allocate(_size);
|
||||
BucketType* data = _allocation.Get();
|
||||
for (int32 i = 0; i < _size; ++i)
|
||||
for (int32 i = 0; i < oldSize; ++i)
|
||||
data[i]._state = HashSetBucketState::Empty;
|
||||
BucketType* oldData = oldAllocation.Get();
|
||||
FindPositionResult pos;
|
||||
for (int32 i = 0; i < _size; ++i)
|
||||
for (int32 i = 0; i < oldSize; ++i)
|
||||
{
|
||||
BucketType& oldBucket = oldData[i];
|
||||
if (oldBucket.IsOccupied())
|
||||
{
|
||||
FindPosition(oldBucket.GetKey(), pos);
|
||||
ASSERT(pos.FreeSlotIndex != -1);
|
||||
if (pos.FreeSlotIndex == -1)
|
||||
{
|
||||
// Grow and retry to handle pathological cases (eg. heavy collisions)
|
||||
EnsureCapacity(_size + 1, true);
|
||||
FindPosition(oldBucket.GetKey(), pos);
|
||||
ASSERT(pos.FreeSlotIndex != -1);
|
||||
}
|
||||
BucketType& bucket = _allocation.Get()[pos.FreeSlotIndex];
|
||||
bucket = MoveTemp(oldBucket);
|
||||
}
|
||||
}
|
||||
for (int32 i = 0; i < _size; ++i)
|
||||
for (int32 i = 0; i < oldSize; ++i)
|
||||
oldData[i].Free();
|
||||
_elementsCount = elementsCount;
|
||||
}
|
||||
_deletedCount = 0;
|
||||
}
|
||||
|
||||
@@ -554,10 +554,11 @@ void AnimatedModel::StopSlotAnimation(const StringView& slotName, Animation* ani
|
||||
{
|
||||
for (auto& slot : GraphInstance.Slots)
|
||||
{
|
||||
if (slot.Animation == anim && slot.Name == slotName)
|
||||
if ((slot.Animation == anim || anim == nullptr) && slot.Name == slotName)
|
||||
{
|
||||
//slot.Animation = nullptr; // TODO: make an immediate version of this method and set the animation to nullptr.
|
||||
slot.Reset = true;
|
||||
if (slot.Animation != nullptr)
|
||||
slot.Reset = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -573,7 +574,7 @@ void AnimatedModel::PauseSlotAnimation(const StringView& slotName, Animation* an
|
||||
{
|
||||
for (auto& slot : GraphInstance.Slots)
|
||||
{
|
||||
if (slot.Animation == anim && slot.Name == slotName)
|
||||
if ((slot.Animation == anim || anim == nullptr) && slot.Name == slotName)
|
||||
{
|
||||
slot.Pause = true;
|
||||
break;
|
||||
@@ -595,7 +596,7 @@ bool AnimatedModel::IsPlayingSlotAnimation(const StringView& slotName, Animation
|
||||
{
|
||||
for (auto& slot : GraphInstance.Slots)
|
||||
{
|
||||
if (slot.Animation == anim && slot.Name == slotName && !slot.Pause)
|
||||
if ((slot.Animation == anim || anim == nullptr) && slot.Name == slotName && !slot.Pause)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -412,8 +412,8 @@ public:
|
||||
/// Stops the animation playback on the slot in Anim Graph.
|
||||
/// </summary>
|
||||
/// <param name="slotName">The name of the slot.</param>
|
||||
/// <param name="anim">The animation to stop.</param>
|
||||
API_FUNCTION() void StopSlotAnimation(const StringView& slotName, Animation* anim);
|
||||
/// <param name="anim">The animation to check. Null to use slot name only.</param>
|
||||
API_FUNCTION() void StopSlotAnimation(const StringView& slotName, Animation* anim = nullptr);
|
||||
|
||||
/// <summary>
|
||||
/// Pauses all the animations playback on the all slots in Anim Graph.
|
||||
@@ -424,8 +424,8 @@ public:
|
||||
/// Pauses the animation playback on the slot in Anim Graph.
|
||||
/// </summary>
|
||||
/// <param name="slotName">The name of the slot.</param>
|
||||
/// <param name="anim">The animation to pause.</param>
|
||||
API_FUNCTION() void PauseSlotAnimation(const StringView& slotName, Animation* anim);
|
||||
/// <param name="anim">The animation to check. Null to use slot name only.</param>
|
||||
API_FUNCTION() void PauseSlotAnimation(const StringView& slotName, Animation* anim = nullptr);
|
||||
|
||||
/// <summary>
|
||||
/// Checks if any animation playback is active on any slot in Anim Graph (not paused).
|
||||
@@ -436,8 +436,8 @@ public:
|
||||
/// Checks if the animation playback is active on the slot in Anim Graph (not paused).
|
||||
/// </summary>
|
||||
/// <param name="slotName">The name of the slot.</param>
|
||||
/// <param name="anim">The animation to check.</param>
|
||||
API_FUNCTION() bool IsPlayingSlotAnimation(const StringView& slotName, Animation* anim);
|
||||
/// <param name="anim">The animation to check. Null to use slot name only.</param>
|
||||
API_FUNCTION() bool IsPlayingSlotAnimation(const StringView& slotName, Animation* anim = nullptr);
|
||||
|
||||
private:
|
||||
void ApplyRootMotion(const Transform& rootMotionDelta);
|
||||
|
||||
@@ -16,7 +16,7 @@ const Char* GetCommandLine(int argc, char* argv[])
|
||||
const Char* cmdLine;
|
||||
if (length != 0)
|
||||
{
|
||||
Char* str = (Char*)malloc(length * sizeof(Char));
|
||||
Char* str = (Char*)malloc((length + 1) * sizeof(Char));
|
||||
cmdLine = str;
|
||||
for (int i = 1; i < argc; i++)
|
||||
{
|
||||
|
||||
@@ -100,6 +100,35 @@ API_STRUCT(NoDefault, Namespace="FlaxEngine.Networking") struct FLAXENGINE_API N
|
||||
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
|
||||
{
|
||||
return Word0 == other.Word0 && Word1 == other.Word1;
|
||||
|
||||
@@ -152,12 +152,12 @@ struct NetworkReplicatedObject
|
||||
|
||||
bool operator==(const NetworkReplicatedObject& other) const
|
||||
{
|
||||
return Object == other.Object;
|
||||
return ObjectId == other.ObjectId;
|
||||
}
|
||||
|
||||
bool operator==(const ScriptingObject* other) const
|
||||
{
|
||||
return Object == other;
|
||||
return other && ObjectId == other->GetID();
|
||||
}
|
||||
|
||||
bool operator==(const Guid& other) const
|
||||
@@ -176,6 +176,11 @@ inline uint32 GetHash(const NetworkReplicatedObject& key)
|
||||
return GetHash(key.ObjectId);
|
||||
}
|
||||
|
||||
inline uint32 GetHash(const ScriptingObject* key)
|
||||
{
|
||||
return key ? GetHash(key->GetID()) : 0;
|
||||
}
|
||||
|
||||
struct Serializer
|
||||
{
|
||||
NetworkReplicator::SerializeFunc Methods[2];
|
||||
@@ -698,14 +703,11 @@ void SendReplication(ScriptingObject* obj, NetworkClientsMask targetClients)
|
||||
return;
|
||||
auto& item = it->Item;
|
||||
const bool isClient = NetworkManager::IsClient();
|
||||
const NetworkClientsMask fullTargetClients = targetClients;
|
||||
|
||||
// Skip serialization of objects that none will receive
|
||||
if (!isClient)
|
||||
{
|
||||
BuildCachedTargets(item, targetClients);
|
||||
if (CachedTargets.Count() == 0)
|
||||
return;
|
||||
}
|
||||
// If server has no recipients, skip early.
|
||||
if (!isClient && !targetClients)
|
||||
return;
|
||||
|
||||
if (item.AsNetworkObject)
|
||||
item.AsNetworkObject->OnNetworkSerialize();
|
||||
@@ -728,18 +730,30 @@ void SendReplication(ScriptingObject* obj, NetworkClientsMask targetClients)
|
||||
|
||||
#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 == targetClients &&
|
||||
Platform::MemoryCompare(item.RepCache.Data.Get(), stream->GetBuffer(), size) == 0)
|
||||
if (item.RepCache.Data.Length() == size && 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);
|
||||
#endif
|
||||
// TODO: use Unreliable for dynamic objects that are replicated every frame? (eg. player state)
|
||||
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
|
||||
NetworkMessageObjectReplicate msgData;
|
||||
msgData.OwnerFrame = NetworkManager::Frame;
|
||||
@@ -1530,7 +1544,21 @@ void NetworkReplicator::DespawnObject(ScriptingObject* obj)
|
||||
// Register for despawning (batched during update)
|
||||
auto& despawn = DespawnQueue.AddOne();
|
||||
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
|
||||
for (int32 i = 0; i < SpawnQueue.Count(); i++)
|
||||
@@ -1823,6 +1851,31 @@ void NetworkInternal::NetworkReplicatorClientConnected(NetworkClient* client)
|
||||
{
|
||||
ScopeLock lock(ObjectsLock);
|
||||
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
|
||||
}
|
||||
|
||||
@@ -2275,7 +2328,9 @@ void NetworkInternal::OnNetworkMessageObjectDespawn(NetworkEvent& event, Network
|
||||
}
|
||||
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