From f8dc8ab903a9f246240a08d79b8683bfd05ae153 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Sun, 23 Nov 2025 14:19:11 -0600 Subject: [PATCH 01/11] Fix not being able to replay same animation in animation slot. --- Source/Engine/Animations/Graph/AnimGroup.Animation.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp index fd62ec537..fe0ad52e1 100644 --- a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp +++ b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp @@ -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; } From 2d56411e5f8f4a292b328eeac51b6c3d59fe04f5 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Sun, 23 Nov 2025 14:19:37 -0600 Subject: [PATCH 02/11] Add slot stop methods without anim param. --- Source/Engine/Level/Actors/AnimatedModel.cpp | 24 ++++++++++++++++++++ Source/Engine/Level/Actors/AnimatedModel.h | 12 ++++++++++ 2 files changed, 36 insertions(+) diff --git a/Source/Engine/Level/Actors/AnimatedModel.cpp b/Source/Engine/Level/Actors/AnimatedModel.cpp index ee66c6c77..a8c091eb9 100644 --- a/Source/Engine/Level/Actors/AnimatedModel.cpp +++ b/Source/Engine/Level/Actors/AnimatedModel.cpp @@ -563,6 +563,20 @@ void AnimatedModel::StopSlotAnimation(const StringView& slotName, Animation* ani } } +void AnimatedModel::StopSlotAnimation(const StringView& slotName) +{ + for (auto& slot : GraphInstance.Slots) + { + if (slot.Name == slotName) + { + //slot.Animation = nullptr; // TODO: make an immediate version of this method and set the animation to nullptr. + if (slot.Animation != nullptr) + slot.Reset = true; + break; + } + } +} + void AnimatedModel::PauseSlotAnimation() { for (auto& slot : GraphInstance.Slots) @@ -601,6 +615,16 @@ bool AnimatedModel::IsPlayingSlotAnimation(const StringView& slotName, Animation return false; } +bool AnimatedModel::IsPlayingSlotAnimation(const StringView& slotName) +{ + for (auto& slot : GraphInstance.Slots) + { + if (slot.Name == slotName && !slot.Pause) + return true; + } + return false; +} + void AnimatedModel::ApplyRootMotion(const Transform& rootMotionDelta) { // Skip if no motion diff --git a/Source/Engine/Level/Actors/AnimatedModel.h b/Source/Engine/Level/Actors/AnimatedModel.h index a011be0d0..dc837c658 100644 --- a/Source/Engine/Level/Actors/AnimatedModel.h +++ b/Source/Engine/Level/Actors/AnimatedModel.h @@ -414,6 +414,12 @@ public: /// The name of the slot. /// The animation to stop. API_FUNCTION() void StopSlotAnimation(const StringView& slotName, Animation* anim); + + /// + /// Stops the animation playback on the slot in Anim Graph. + /// + /// The name of the slot. + API_FUNCTION() void StopSlotAnimation(const StringView& slotName); /// /// Pauses all the animations playback on the all slots in Anim Graph. @@ -439,6 +445,12 @@ public: /// The animation to check. API_FUNCTION() bool IsPlayingSlotAnimation(const StringView& slotName, Animation* anim); + /// + /// Checks if the animation playback is active on the slot in Anim Graph (not paused). + /// + /// The name of the slot. + API_FUNCTION() bool IsPlayingSlotAnimation(const StringView& slotName); + private: void ApplyRootMotion(const Transform& rootMotionDelta); void SyncParameters(); From d9a18b1d31ab76070d5a2ba7a54f7c89a37db4e8 Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Tue, 25 Nov 2025 21:20:20 +0100 Subject: [PATCH 03/11] Fixed HashSet compact rehash under heavy collisions - Compact now iterates over the old bucket array using the saved oldSize, and frees with that size, avoiding out-of-bounds when _size changes. - If reinsertion finds no free slot during compaction (pathological collisions), the table grows once and retries, preventing AVs. - This fix addresses problems with weak hash keys (like #3824). --- Source/Engine/Core/Collections/HashSetBase.h | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/Source/Engine/Core/Collections/HashSetBase.h b/Source/Engine/Core/Collections/HashSetBase.h index 36fcf275d..96adabf79 100644 --- a/Source/Engine/Core/Collections/HashSetBase.h +++ b/Source/Engine/Core/Collections/HashSetBase.h @@ -409,26 +409,33 @@ protected: else { // Rebuild entire table completely + const int32 oldSize = _size; AllocationData oldAllocation; - AllocationUtils::MoveToEmpty(oldAllocation, _allocation, _size, _size); + AllocationUtils::MoveToEmpty(oldAllocation, _allocation, oldSize, oldSize); _allocation.Allocate(_size); BucketType* data = _allocation.Get(); for (int32 i = 0; i < _size; ++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); + 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(); } _deletedCount = 0; From 0007185b5fc9515b5a8222dc2c7efaeec876862f Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Wed, 26 Nov 2025 17:46:41 +0100 Subject: [PATCH 04/11] Fixed late-join network replication - Adjusted replication to resend unchanged state only to missing clients. - Skip server serialization when no recipients, and downgrade unknown-despawn noise. --- .../Engine/Networking/NetworkReplicator.cpp | 80 +++++++++++++++---- 1 file changed, 66 insertions(+), 14 deletions(-) diff --git a/Source/Engine/Networking/NetworkReplicator.cpp b/Source/Engine/Networking/NetworkReplicator.cpp index c584d3526..5e2979db0 100644 --- a/Source/Engine/Networking/NetworkReplicator.cpp +++ b/Source/Engine/Networking/NetworkReplicator.cpp @@ -698,14 +698,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(); @@ -727,19 +724,33 @@ void SendReplication(ScriptingObject* obj, NetworkClientsMask targetClients) } #if USE_NETWORK_REPLICATOR_CACHE + // Check if only newly joined clients are missing this data to avoid resending it to everyone + NetworkClientsMask missingClients; + missingClients.Word0 = targetClients.Word0 & ~item.RepCache.Mask.Word0; + missingClients.Word1 = targetClients.Word1 & ~item.RepCache.Mask.Word1; + // 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; + // 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 +1541,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> 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 +1848,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 +2325,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); } } From 56beca0db4d7e8b5d14323e988ab86275fcdf071 Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Thu, 27 Nov 2025 22:54:43 +0100 Subject: [PATCH 05/11] Fixed network replicated-object deduplication by hashing/equality on ObjectId Aligned NetworkReplicatedObject equality with its hash (compare ObjectId, not pointer). --- Source/Engine/Networking/NetworkReplicator.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Networking/NetworkReplicator.cpp b/Source/Engine/Networking/NetworkReplicator.cpp index c584d3526..bf874b3c4 100644 --- a/Source/Engine/Networking/NetworkReplicator.cpp +++ b/Source/Engine/Networking/NetworkReplicator.cpp @@ -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]; From 00f9a28729e3c4c0c18efecb99d19dd647592bc4 Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Fri, 28 Nov 2025 15:51:19 +0100 Subject: [PATCH 06/11] Fixed HashSet compaction count after mid-compact growth Ensure HashSetBase::Compact() preserves _elementsCount even when EnsureCapacity() triggers during compaction. The growth path resets the counter; we now cache the original count and restore it after moving all buckets so Count() stays correct in heavy-collision scenarios. --- Source/Engine/Core/Collections/HashSetBase.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Source/Engine/Core/Collections/HashSetBase.h b/Source/Engine/Core/Collections/HashSetBase.h index 96adabf79..58f1702c2 100644 --- a/Source/Engine/Core/Collections/HashSetBase.h +++ b/Source/Engine/Core/Collections/HashSetBase.h @@ -408,6 +408,7 @@ protected: } else { + const int32 elementsCount = _elementsCount; // Rebuild entire table completely const int32 oldSize = _size; AllocationData oldAllocation; @@ -437,6 +438,7 @@ protected: } for (int32 i = 0; i < oldSize; ++i) oldData[i].Free(); + _elementsCount = elementsCount; } _deletedCount = 0; } From 82bd91527440dafe28fc841f06852778974f1843 Mon Sep 17 00:00:00 2001 From: Ari Vuollet Date: Fri, 12 Dec 2025 14:44:24 +0200 Subject: [PATCH 07/11] Fix out-of-bounds write while parsing command-line arguments --- Source/Engine/Main/MainUtil.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Engine/Main/MainUtil.h b/Source/Engine/Main/MainUtil.h index 668c69c3e..9e1a1503e 100644 --- a/Source/Engine/Main/MainUtil.h +++ b/Source/Engine/Main/MainUtil.h @@ -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++) { From 0e627577fc1d039942b1fa10bd2bb4292c2ec377 Mon Sep 17 00:00:00 2001 From: Chandler Cox Date: Sun, 14 Dec 2025 15:00:44 -0600 Subject: [PATCH 08/11] Simplify code. --- Source/Engine/Level/Actors/AnimatedModel.cpp | 27 ++------------------ Source/Engine/Level/Actors/AnimatedModel.h | 16 ++---------- 2 files changed, 4 insertions(+), 39 deletions(-) diff --git a/Source/Engine/Level/Actors/AnimatedModel.cpp b/Source/Engine/Level/Actors/AnimatedModel.cpp index a8c091eb9..991ac3e05 100644 --- a/Source/Engine/Level/Actors/AnimatedModel.cpp +++ b/Source/Engine/Level/Actors/AnimatedModel.cpp @@ -554,20 +554,7 @@ void AnimatedModel::StopSlotAnimation(const StringView& slotName, Animation* ani { for (auto& slot : GraphInstance.Slots) { - if (slot.Animation == anim && slot.Name == slotName) - { - //slot.Animation = nullptr; // TODO: make an immediate version of this method and set the animation to nullptr. - slot.Reset = true; - break; - } - } -} - -void AnimatedModel::StopSlotAnimation(const StringView& slotName) -{ - for (auto& slot : GraphInstance.Slots) - { - if (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. if (slot.Animation != nullptr) @@ -609,17 +596,7 @@ bool AnimatedModel::IsPlayingSlotAnimation(const StringView& slotName, Animation { for (auto& slot : GraphInstance.Slots) { - if (slot.Animation == anim && slot.Name == slotName && !slot.Pause) - return true; - } - return false; -} - -bool AnimatedModel::IsPlayingSlotAnimation(const StringView& slotName) -{ - for (auto& slot : GraphInstance.Slots) - { - if (slot.Name == slotName && !slot.Pause) + if ((slot.Animation == anim || anim == nullptr) && slot.Name == slotName && !slot.Pause) return true; } return false; diff --git a/Source/Engine/Level/Actors/AnimatedModel.h b/Source/Engine/Level/Actors/AnimatedModel.h index dc837c658..344f633cc 100644 --- a/Source/Engine/Level/Actors/AnimatedModel.h +++ b/Source/Engine/Level/Actors/AnimatedModel.h @@ -413,13 +413,7 @@ public: /// /// The name of the slot. /// The animation to stop. - API_FUNCTION() void StopSlotAnimation(const StringView& slotName, Animation* anim); - - /// - /// Stops the animation playback on the slot in Anim Graph. - /// - /// The name of the slot. - API_FUNCTION() void StopSlotAnimation(const StringView& slotName); + API_FUNCTION() void StopSlotAnimation(const StringView& slotName, Animation* anim = nullptr); /// /// Pauses all the animations playback on the all slots in Anim Graph. @@ -443,13 +437,7 @@ public: /// /// The name of the slot. /// The animation to check. - API_FUNCTION() bool IsPlayingSlotAnimation(const StringView& slotName, Animation* anim); - - /// - /// Checks if the animation playback is active on the slot in Anim Graph (not paused). - /// - /// The name of the slot. - API_FUNCTION() bool IsPlayingSlotAnimation(const StringView& slotName); + API_FUNCTION() bool IsPlayingSlotAnimation(const StringView& slotName, Animation* anim = nullptr); private: void ApplyRootMotion(const Transform& rootMotionDelta); From 5fdbed2b56f9d1f738f4ed499efafd316c1eb2f2 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 14 Dec 2025 22:41:00 +0100 Subject: [PATCH 09/11] Minor codestyle adjustments --- Source/Engine/Core/Collections/HashSetBase.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Source/Engine/Core/Collections/HashSetBase.h b/Source/Engine/Core/Collections/HashSetBase.h index 58f1702c2..94bbd149d 100644 --- a/Source/Engine/Core/Collections/HashSetBase.h +++ b/Source/Engine/Core/Collections/HashSetBase.h @@ -408,14 +408,14 @@ protected: } else { - const int32 elementsCount = _elementsCount; // Rebuild entire table completely + const int32 elementsCount = _elementsCount; const int32 oldSize = _size; AllocationData oldAllocation; AllocationUtils::MoveToEmpty(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; @@ -430,8 +430,8 @@ protected: // Grow and retry to handle pathological cases (eg. heavy collisions) EnsureCapacity(_size + 1, true); FindPosition(oldBucket.GetKey(), pos); + ASSERT(pos.FreeSlotIndex != -1); } - ASSERT(pos.FreeSlotIndex != -1); BucketType& bucket = _allocation.Get()[pos.FreeSlotIndex]; bucket = MoveTemp(oldBucket); } From 2b6339c05cd8a66e7964b29076c3b3053f19c891 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 14 Dec 2025 22:58:53 +0100 Subject: [PATCH 10/11] Minor code cleanup --- .../Networking/NetworkReplicationHierarchy.h | 29 +++++++++++++++++++ .../Engine/Networking/NetworkReplicator.cpp | 10 +++---- 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/Source/Engine/Networking/NetworkReplicationHierarchy.h b/Source/Engine/Networking/NetworkReplicationHierarchy.h index 9db851996..29cf0b30f 100644 --- a/Source/Engine/Networking/NetworkReplicationHierarchy.h +++ b/Source/Engine/Networking/NetworkReplicationHierarchy.h @@ -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; diff --git a/Source/Engine/Networking/NetworkReplicator.cpp b/Source/Engine/Networking/NetworkReplicator.cpp index 02333aa64..e5a7d232e 100644 --- a/Source/Engine/Networking/NetworkReplicator.cpp +++ b/Source/Engine/Networking/NetworkReplicator.cpp @@ -729,14 +729,12 @@ void SendReplication(ScriptingObject* obj, NetworkClientsMask targetClients) } #if USE_NETWORK_REPLICATOR_CACHE - // Check if only newly joined clients are missing this data to avoid resending it to everyone - NetworkClientsMask missingClients; - missingClients.Word0 = targetClients.Word0 & ~item.RepCache.Mask.Word0; - missingClients.Word1 = targetClients.Word1 & ~item.RepCache.Mask.Word1; - // Process replication cache to skip sending object data if it didn't change if (item.RepCache.Data.Length() == size && Platform::MemoryCompare(item.RepCache.Data.Get(), stream->GetBuffer(), size) == 0) { + // 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; @@ -2330,7 +2328,7 @@ void NetworkInternal::OnNetworkMessageObjectDespawn(NetworkEvent& event, Network } else { - // If this client never had the object (eg. it was targeted to other clients only), drop the message quietly. + // 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); } From 056de752ed02d9d145ef7eee61e08267f589d63f Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 14 Dec 2025 23:03:34 +0100 Subject: [PATCH 11/11] Add docs --- Source/Engine/Level/Actors/AnimatedModel.cpp | 2 +- Source/Engine/Level/Actors/AnimatedModel.h | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Source/Engine/Level/Actors/AnimatedModel.cpp b/Source/Engine/Level/Actors/AnimatedModel.cpp index 991ac3e05..343710183 100644 --- a/Source/Engine/Level/Actors/AnimatedModel.cpp +++ b/Source/Engine/Level/Actors/AnimatedModel.cpp @@ -574,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; diff --git a/Source/Engine/Level/Actors/AnimatedModel.h b/Source/Engine/Level/Actors/AnimatedModel.h index 344f633cc..a520d6723 100644 --- a/Source/Engine/Level/Actors/AnimatedModel.h +++ b/Source/Engine/Level/Actors/AnimatedModel.h @@ -412,7 +412,7 @@ public: /// Stops the animation playback on the slot in Anim Graph. /// /// The name of the slot. - /// The animation to stop. + /// The animation to check. Null to use slot name only. API_FUNCTION() void StopSlotAnimation(const StringView& slotName, Animation* anim = nullptr); /// @@ -424,8 +424,8 @@ public: /// Pauses the animation playback on the slot in Anim Graph. /// /// The name of the slot. - /// The animation to pause. - API_FUNCTION() void PauseSlotAnimation(const StringView& slotName, Animation* anim); + /// The animation to check. Null to use slot name only. + API_FUNCTION() void PauseSlotAnimation(const StringView& slotName, Animation* anim = nullptr); /// /// Checks if any animation playback is active on any slot in Anim Graph (not paused). @@ -436,7 +436,7 @@ public: /// Checks if the animation playback is active on the slot in Anim Graph (not paused). /// /// The name of the slot. - /// The animation to check. + /// The animation to check. Null to use slot name only. API_FUNCTION() bool IsPlayingSlotAnimation(const StringView& slotName, Animation* anim = nullptr); private: