Merge branch 'master' into Improve-HighlightedPopUpColor

This commit is contained in:
Phantom
2025-12-15 00:49:49 +01:00
7 changed files with 131 additions and 33 deletions

View File

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

View File

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

View File

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

View File

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

View File

@@ -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++)
{

View File

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

View File

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