From 41ad835d865fe148c97140021a0376bf8fed1369 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 12 Jun 2021 12:29:47 +0200 Subject: [PATCH] Refactor AnimGraph to support asynchronous execution --- .../Animations/Graph/AnimGraph.Base.cpp | 19 --- .../Animations/Graph/AnimGraph.Custom.cpp | 33 ++-- Source/Engine/Animations/Graph/AnimGraph.cpp | 124 ++++++++++----- Source/Engine/Animations/Graph/AnimGraph.h | 127 ++++------------ .../Animations/Graph/AnimGroup.Animation.cpp | 141 +++++++++--------- Source/Engine/Visject/VisjectGraph.h | 7 - 6 files changed, 199 insertions(+), 252 deletions(-) diff --git a/Source/Engine/Animations/Graph/AnimGraph.Base.cpp b/Source/Engine/Animations/Graph/AnimGraph.Base.cpp index 22b74e6f8..8748e99bb 100644 --- a/Source/Engine/Animations/Graph/AnimGraph.Base.cpp +++ b/Source/Engine/Animations/Graph/AnimGraph.Base.cpp @@ -9,25 +9,6 @@ #include "Engine/Utilities/Delaunay2D.h" #include "Engine/Serialization/MemoryReadStream.h" -void AnimGraphBase::ClearCache() -{ - // Clear sub-graphs - for (int32 i = 0; i < SubGraphs.Count(); i++) - { - SubGraphs[i]->ClearCache(); - } - - // Clear cache - for (int32 i = 0; i < Nodes.Count(); i++) - { - auto& node = Nodes[i]; - for (int32 j = 0; j < node.Boxes.Count(); j++) - { - node.Boxes[j].InvalidateCache(); - } - } -} - AnimSubGraph* AnimGraphBase::LoadSubGraph(const void* data, int32 dataLength, const Char* name) { if (data == nullptr || dataLength == 0) diff --git a/Source/Engine/Animations/Graph/AnimGraph.Custom.cpp b/Source/Engine/Animations/Graph/AnimGraph.Custom.cpp index 60ae46f16..5c082a8b5 100644 --- a/Source/Engine/Animations/Graph/AnimGraph.Custom.cpp +++ b/Source/Engine/Animations/Graph/AnimGraph.Custom.cpp @@ -89,13 +89,10 @@ void AnimGraphExecutor::initRuntime() void AnimGraphExecutor::ProcessGroupCustom(Box* boxBase, Node* nodeBase, Value& value) { - auto box = (AnimGraphBox*)boxBase; - if (box->IsCacheValid()) - { - // Return cache - value = box->Cache; + auto& context = Context.Get(); + if (context.ValueCache.TryGet(boxBase, value)) return; - } + auto box = (AnimGraphBox*)boxBase; auto node = (AnimGraphNode*)nodeBase; auto& data = node->Data.Custom; value = Value::Null; @@ -105,16 +102,16 @@ void AnimGraphExecutor::ProcessGroupCustom(Box* boxBase, Node* nodeBase, Value& return; // Prepare node context - InternalContext context; - context.Graph = &_graph; - context.GraphExecutor = this; - context.Node = node; - context.NodeId = node->ID; - context.BoxId = box->ID; - context.DeltaTime = _deltaTime; - context.CurrentFrameIndex = _currentFrameIndex;; - context.BaseModel = _graph.BaseModel->GetOrCreateManagedInstance(); - context.Instance = _data->Object ? _data->Object->GetOrCreateManagedInstance() : nullptr; + InternalContext internalContext; + internalContext.Graph = &_graph; + internalContext.GraphExecutor = this; + internalContext.Node = node; + internalContext.NodeId = node->ID; + internalContext.BoxId = box->ID; + internalContext.DeltaTime = context.DeltaTime; + internalContext.CurrentFrameIndex = context.CurrentFrameIndex; + internalContext.BaseModel = _graph.BaseModel->GetOrCreateManagedInstance(); + internalContext.Instance = context.Data->Object ? context.Data->Object->GetOrCreateManagedInstance() : nullptr; // Peek managed object const auto obj = mono_gchandle_get_target(data.Handle); @@ -126,7 +123,7 @@ void AnimGraphExecutor::ProcessGroupCustom(Box* boxBase, Node* nodeBase, Value& // Evaluate node void* params[1]; - params[0] = &context; + params[0] = &internalContext; MonoObject* exception = nullptr; MonoObject* result = data.Evaluate->Invoke(obj, params, &exception); if (exception) @@ -138,7 +135,7 @@ void AnimGraphExecutor::ProcessGroupCustom(Box* boxBase, Node* nodeBase, Value& // Extract result value = MUtils::UnboxVariant(result); - box->Cache = value; + context.ValueCache.Add(boxBase, value); } bool AnimGraph::IsReady() const diff --git a/Source/Engine/Animations/Graph/AnimGraph.cpp b/Source/Engine/Animations/Graph/AnimGraph.cpp index 321c5a944..6d95cbdfc 100644 --- a/Source/Engine/Animations/Graph/AnimGraph.cpp +++ b/Source/Engine/Animations/Graph/AnimGraph.cpp @@ -1,11 +1,14 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. #include "AnimGraph.h" +#include "Engine/Animations/Animations.h" #include "Engine/Content/Assets/SkinnedModel.h" #include "Engine/Graphics/Models/SkeletonData.h" #include "Engine/Scripting/Scripting.h" #include "Engine/Engine/Time.h" +ThreadLocal AnimGraphExecutor::Context; + RootMotionData RootMotionData::Identity = { Vector3(0.0f), Quaternion(0.0f, 0.0f, 0.0f, 1.0f) }; RootMotionData& RootMotionData::operator+=(const RootMotionData& b) @@ -78,16 +81,44 @@ void AnimGraphImpulse::SetNodeModelTransformation(SkeletonData& skeleton, int32 parentTransform.WorldToLocal(value, Nodes[nodeIndex]); } +void AnimGraphInstanceData::Clear() +{ + Version = 0; + LastUpdateTime = -1; + CurrentFrame = 0; + RootTransform = Transform::Identity; + RootMotion = RootMotionData::Identity; + Parameters.Resize(0); + State.Resize(0); + NodesPose.Resize(0); +} + +void AnimGraphInstanceData::ClearState() +{ + Version = 0; + LastUpdateTime = -1; + CurrentFrame = 0; + RootTransform = Transform::Identity; + RootMotion = RootMotionData::Identity; + State.Resize(0); + NodesPose.Resize(0); +} + +void AnimGraphInstanceData::Invalidate() +{ + LastUpdateTime = -1; + CurrentFrame = 0; +} + AnimGraphImpulse* AnimGraphNode::GetNodes(AnimGraphExecutor* executor) { - // Ensure to have memory + auto& context = AnimGraphExecutor::Context.Get(); const int32 count = executor->_skeletonNodesCount; - if (Nodes.Nodes.Count() != count) - { - Nodes.Nodes.Resize(count, false); - } - - return &Nodes; + if (context.PoseCacheSize == context.PoseCache.Count()) + context.PoseCache.AddOne(); + auto& nodes = context.PoseCache[context.PoseCacheSize++]; + nodes.Nodes.Resize(count, false); + return &nodes; } bool AnimGraph::Load(ReadStream* stream, bool loadMeta) @@ -181,20 +212,24 @@ void AnimGraphExecutor::Update(AnimGraphInstanceData& data, float dt) // Initialize auto& skeleton = _graph.BaseModel->Skeleton; + auto& context = Context.Get(); { ANIM_GRAPH_PROFILE_EVENT("Init"); - // Prepare graph data for the evaluation + // Init data from base model _skeletonNodesCount = skeleton.Nodes.Count(); - _graphStack.Clear(); - _graphStack.Push((Graph*)&_graph); - _data = &data; - _deltaTime = dt; _rootMotionMode = (RootMotionMode)(int32)_graph._rootNode->Values[0]; - _currentFrameIndex = ++data.CurrentFrame; - _callStack.Clear(); - _functions.Clear(); - _graph.ClearCache(); + + // Prepare context data for the evaluation + context.GraphStack.Clear(); + context.GraphStack.Push((Graph*)&_graph); + context.Data = &data; + context.DeltaTime = dt; + context.CurrentFrameIndex = ++data.CurrentFrame; + context.CallStack.Clear(); + context.Functions.Clear(); + context.PoseCacheSize = 0; + context.ValueCache.Clear(); // Prepare instance data if (data.Version != _graph.Version) @@ -208,18 +243,18 @@ void AnimGraphExecutor::Update(AnimGraphInstanceData& data, float dt) data.State.Resize(_graph.BucketsCountTotal, false); // Initialize buckets - ResetBuckets(&_graph); + ResetBuckets(context, &_graph); } // Init empty nodes data - _emptyNodes.RootMotion = RootMotionData::Identity; - _emptyNodes.Position = 0.0f; - _emptyNodes.Length = 0.0f; - _emptyNodes.Nodes.Resize(_skeletonNodesCount, false); + context.EmptyNodes.RootMotion = RootMotionData::Identity; + context.EmptyNodes.Position = 0.0f; + context.EmptyNodes.Length = 0.0f; + context.EmptyNodes.Nodes.Resize(_skeletonNodesCount, false); for (int32 i = 0; i < _skeletonNodesCount; i++) { auto& node = skeleton.Nodes[i]; - _emptyNodes.Nodes[i] = node.LocalTransform; + context.EmptyNodes.Nodes[i] = node.LocalTransform; } } @@ -244,7 +279,7 @@ void AnimGraphExecutor::Update(AnimGraphInstanceData& data, float dt) { ANIM_GRAPH_PROFILE_EVENT("Global Pose"); - _data->NodesPose.Resize(_skeletonNodesCount, false); + data.NodesPose.Resize(_skeletonNodesCount, false); // Note: this assumes that nodes are sorted (parents first) for (int32 nodeIndex = 0; nodeIndex < _skeletonNodesCount; nodeIndex++) @@ -254,18 +289,16 @@ void AnimGraphExecutor::Update(AnimGraphInstanceData& data, float dt) { nodesTransformations[nodeIndex] = nodesTransformations[parentIndex].LocalToWorld(nodesTransformations[nodeIndex]); } - nodesTransformations[nodeIndex].GetWorld(_data->NodesPose[nodeIndex]); + nodesTransformations[nodeIndex].GetWorld(data.NodesPose[nodeIndex]); } - } - // Process the root node transformation and the motion - { - _data->RootTransform = nodesTransformations[0]; - _data->RootMotion = animResult->RootMotion; + // Process the root node transformation and the motion + data.RootTransform = nodesTransformations[0]; + data.RootMotion = animResult->RootMotion; } // Cleanup - _data = nullptr; + context.Data = nullptr; } void AnimGraphExecutor::GetInputValue(Box* box, Value& result) @@ -273,29 +306,39 @@ void AnimGraphExecutor::GetInputValue(Box* box, Value& result) result = eatBox(box->GetParent(), box->FirstConnection()); } -void AnimGraphExecutor::ResetBucket(int32 bucketIndex) +AnimGraphImpulse* AnimGraphExecutor::GetEmptyNodes() { - auto& stateBucket = _data->State[bucketIndex]; - _graph._bucketInitializerList[bucketIndex](stateBucket); + return &Context.Get().EmptyNodes; } -void AnimGraphExecutor::ResetBuckets(AnimGraphBase* graph) +void AnimGraphExecutor::InitNodes(AnimGraphImpulse* nodes) const +{ + const auto& emptyNodes = Context.Get().EmptyNodes; + Platform::MemoryCopy(nodes->Nodes.Get(), emptyNodes.Nodes.Get(), sizeof(Transform) * _skeletonNodesCount); + nodes->RootMotion = emptyNodes.RootMotion; + nodes->Position = emptyNodes.Position; + nodes->Length = emptyNodes.Length; +} + +void AnimGraphExecutor::ResetBuckets(AnimGraphContext& context, AnimGraphBase* graph) { if (graph == nullptr) return; - ASSERT(_data); + auto& state = context.Data->State; for (int32 i = 0; i < graph->BucketsCountTotal; i++) { const int32 bucketIndex = graph->BucketsStart + i; - _graph._bucketInitializerList[bucketIndex](_data->State[bucketIndex]); + _graph._bucketInitializerList[bucketIndex](state[bucketIndex]); } } VisjectExecutor::Value AnimGraphExecutor::eatBox(Node* caller, Box* box) { + auto& context = Context.Get(); + // Check if graph is looped or is too deep - if (_callStack.Count() >= ANIM_GRAPH_MAX_CALL_STACK) + if (context.CallStack.Count() >= ANIM_GRAPH_MAX_CALL_STACK) { OnError(caller, box, TEXT("Graph is looped or too deep!")); return Value::Zero; @@ -309,7 +352,7 @@ VisjectExecutor::Value AnimGraphExecutor::eatBox(Node* caller, Box* box) #endif // Add to the calling stack - _callStack.Add(caller); + context.CallStack.Add(caller); #if USE_EDITOR Animations::DebugFlow(_graph._owner, context.Data->Object, box->GetParent()->ID, box->ID); @@ -322,12 +365,13 @@ VisjectExecutor::Value AnimGraphExecutor::eatBox(Node* caller, Box* box) (this->*func)(box, parentNode, value); // Remove from the calling stack - _callStack.RemoveLast(); + context.CallStack.RemoveLast(); return value; } VisjectExecutor::Graph* AnimGraphExecutor::GetCurrentGraph() const { - return _graphStack.Peek(); + auto& context = Context.Get(); + return context.GraphStack.Peek(); } diff --git a/Source/Engine/Animations/Graph/AnimGraph.h b/Source/Engine/Animations/Graph/AnimGraph.h index a8f055033..1e011aed3 100644 --- a/Source/Engine/Animations/Graph/AnimGraph.h +++ b/Source/Engine/Animations/Graph/AnimGraph.h @@ -4,6 +4,7 @@ #include "Engine/Visject/VisjectGraph.h" #include "Engine/Content/Assets/Animation.h" +#include "Engine/Core/Collections/ChunkedArray.h" #include "Engine/Animations/AlphaBlend.h" #include "Engine/Core/Math/Matrix.h" #include "../Config.h" @@ -362,40 +363,17 @@ public: /// /// Clears this container data. /// - void Clear() - { - Version = 0; - LastUpdateTime = -1; - CurrentFrame = 0; - RootTransform = Transform::Identity; - RootMotion = RootMotionData::Identity; - Parameters.Resize(0); - State.Resize(0); - NodesPose.Resize(0); - } + void Clear(); /// /// Clears this container state data. /// - void ClearState() - { - Version = 0; - LastUpdateTime = -1; - CurrentFrame = 0; - RootTransform = Transform::Identity; - RootMotion = RootMotionData::Identity; - State.Resize(0); - NodesPose.Resize(0); - } + void ClearState(); /// /// Invalidates the update timer. /// - void Invalidate() - { - LastUpdateTime = -1; - CurrentFrame = 0; - } + void Invalidate(); }; /// @@ -424,18 +402,6 @@ public: : VisjectGraphBox(parent, id, type) { } - -public: - - bool IsCacheValid() const - { - return Cache.Type.Type != VariantType::Pointer || Cache.AsPointer != nullptr; - } - - void InvalidateCache() - { - Cache = Variant::Null; - } }; class AnimGraphNode : public VisjectGraphNode @@ -575,13 +541,6 @@ public: /// int32 BucketIndex = -1; - // TODO: use shared allocator per AnimGraph to reduce dynamic memory allocation (also bones data would be closer in memory -> less cache misses) - - /// - /// The node transformations (layout matches the linked to graph skinned model skeleton). - /// - AnimGraphImpulse Nodes; - /// /// The custom data (depends on node type). Used to cache data for faster usage at runtime. /// @@ -661,17 +620,11 @@ public: /// /// Gets the root node of the graph (cache don load). /// - /// The root node. FORCE_INLINE Node* GetRootNode() const { return _rootNode; } - /// - /// Clear all cached values in the graph nodes and the sub-graphs data. - /// - void ClearCache(); - /// /// Loads the sub-graph. /// @@ -751,9 +704,9 @@ public: AnimGraph(Asset* owner, bool isFunction = false) : AnimGraphBase(this) , _isFunction(isFunction) + , _isRegisteredForScriptingEvents(false) , _bucketInitializerList(64) , _owner(owner) - , _isRegisteredForScriptingEvents(false) { } @@ -806,6 +759,24 @@ public: bool onParamCreated(Parameter* p) override; }; +/// +/// The Animation Graph evaluation context. +/// +struct AnimGraphContext +{ + float DeltaTime; + uint64 CurrentFrameIndex; + AnimGraphInstanceData* Data; + AnimGraphImpulse EmptyNodes; + AnimGraphTransitionData TransitionData; + Array> CallStack; + Array> GraphStack; + Dictionary Functions; + ChunkedArray PoseCache; + int32 PoseCacheSize; + Dictionary ValueCache; +}; + /// /// The Animation Graph executor runtime for animation pose evaluation. /// @@ -815,20 +786,14 @@ class AnimGraphExecutor : public VisjectExecutor private: AnimGraph& _graph; - float _deltaTime = 0.0f; - uint64 _currentFrameIndex = 0; - int32 _skeletonNodesCount = 0; RootMotionMode _rootMotionMode = RootMotionMode::NoExtraction; - AnimGraphInstanceData* _data = nullptr; - AnimGraphImpulse _emptyNodes; - AnimGraphTransitionData _transitionData; - Array> _callStack; - Array> _graphStack; - Dictionary _functions; + int32 _skeletonNodesCount = 0; + + // Per-thread context to allow async execution + static ThreadLocal Context; public: - /// /// Initializes the managed runtime calls. /// @@ -854,34 +819,10 @@ public: /// /// Gets the skeleton nodes transformations structure containing identity matrices. /// - FORCE_INLINE const AnimGraphImpulse* GetEmptyNodes() const - { - return &_emptyNodes; - } + AnimGraphImpulse* GetEmptyNodes(); - /// - /// Gets the skeleton nodes transformations structure containing identity matrices. - /// - /// The data. - FORCE_INLINE AnimGraphImpulse* GetEmptyNodes() - { - return &_emptyNodes; - } - - FORCE_INLINE void InitNodes(AnimGraphImpulse* nodes) const - { - // Initialize with cached node transformations - Platform::MemoryCopy(nodes->Nodes.Get(), _emptyNodes.Nodes.Get(), sizeof(Transform) * _skeletonNodesCount); - nodes->RootMotion = _emptyNodes.RootMotion; - nodes->Position = _emptyNodes.Position; - nodes->Length = _emptyNodes.Length; - } - - FORCE_INLINE void InitNode(AnimGraphImpulse* nodes, int32 index) const - { - // Initialize with cached node transformation - nodes->Nodes[index] = GetEmptyNodes()->Nodes[index]; - } + // Initialize impulse with cached node transformations + void InitNodes(AnimGraphImpulse* nodes) const; FORCE_INLINE void CopyNodes(AnimGraphImpulse* dstNodes, AnimGraphImpulse* srcNodes) const { @@ -899,16 +840,10 @@ public: CopyNodes(dstNodes, static_cast(value.AsPointer)); } - /// - /// Resets the state bucket. - /// - /// The zero-based index of the bucket. - void ResetBucket(int32 bucketIndex); - /// /// Resets all the state bucket used by the given graph including sub-graphs (total). Can eb used to reset the animation state of the nested graph (including children). /// - void ResetBuckets(AnimGraphBase* graph); + void ResetBuckets(AnimGraphContext& context, AnimGraphBase* graph); private: diff --git a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp index d7baadfc0..732390112 100644 --- a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp +++ b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp @@ -14,7 +14,7 @@ int32 AnimGraphExecutor::GetRootNodeIndex(Animation* anim) if (anim->Data.RootNodeName.HasChars()) { auto& skeleton = _graph.BaseModel->Skeleton; - for (int32 i = 0; i < _skeletonNodesCount; i++) + for (int32 i = 0; i < skeleton.Nodes.Count(); i++) { if (skeleton.Nodes[i].Name == anim->Data.RootNodeName) { @@ -119,7 +119,7 @@ float GetAnimSamplePos(float length, Animation* anim, float pos, float speed) // Also, scale the animation to fit the total animation node length without cut in a middle const auto animLength = anim->GetLength(); const int32 cyclesCount = Math::FloorToInt(length / animLength); - const float cycleLength = animLength * cyclesCount; + const float cycleLength = animLength * (float)cyclesCount; const float adjustRateScale = length / cycleLength; auto animPos = pos * speed * adjustRateScale; while (animPos > animLength) @@ -152,10 +152,11 @@ Variant AnimGraphExecutor::SampleAnimation(AnimGraphNode* node, bool loop, float nodes->Position = pos; nodes->Length = length; const auto mapping = anim->GetMapping(_graph.BaseModel); - for (int32 i = 0; i < _skeletonNodesCount; i++) + const auto emptyNodes = GetEmptyNodes(); + for (int32 i = 0; i < nodes->Nodes.Count(); i++) { const int32 nodeToChannel = mapping->At(i); - InitNode(nodes, i); + nodes->Nodes[i] = emptyNodes->Nodes[i]; if (nodeToChannel != -1) { // Calculate the animated node transformation @@ -197,7 +198,7 @@ Variant AnimGraphExecutor::SampleAnimationsWithBlend(AnimGraphNode* node, bool l nodes->Length = length; const auto mappingA = animA->GetMapping(_graph.BaseModel); const auto mappingB = animB->GetMapping(_graph.BaseModel); - for (int32 i = 0; i < _skeletonNodesCount; i++) + for (int32 i = 0; i < nodes->Nodes.Count(); i++) { const int32 nodeToChannelA = mappingA->At(i); const int32 nodeToChannelB = mappingB->At(i); @@ -286,12 +287,13 @@ Variant AnimGraphExecutor::SampleAnimationsWithBlend(AnimGraphNode* node, bool l const auto mappingB = animB->GetMapping(_graph.BaseModel); const auto mappingC = animC->GetMapping(_graph.BaseModel); Transform tmp, t; - for (int32 i = 0; i < _skeletonNodesCount; i++) + const auto emptyNodes = GetEmptyNodes(); + for (int32 i = 0; i < nodes->Nodes.Count(); i++) { const int32 nodeToChannelA = mappingA->At(i); const int32 nodeToChannelB = mappingB->At(i); const int32 nodeToChannelC = mappingC->At(i); - tmp = t = GetEmptyNodes()->Nodes[i]; + tmp = t = emptyNodes->Nodes[i]; // Calculate the animated node transformations if (nodeToChannelA != -1) @@ -384,7 +386,7 @@ Variant AnimGraphExecutor::Blend(AnimGraphNode* node, const Value& poseA, const if (!ANIM_GRAPH_IS_VALID_PTR(poseB)) nodesB = GetEmptyNodes(); - for (int32 i = 0; i < _skeletonNodesCount; i++) + for (int32 i = 0; i < nodes->Nodes.Count(); i++) { Transform::Lerp(nodesA->Nodes[i], nodesB->Nodes[i], alpha, nodes->Nodes[i]); } @@ -443,6 +445,7 @@ void ComputeMultiBlendLength(float& length, AnimGraphNode* node) void AnimGraphExecutor::ProcessGroupParameters(Box* box, Node* node, Value& value) { + auto& context = Context.Get(); switch (node->TypeID) { // Get @@ -453,7 +456,7 @@ void AnimGraphExecutor::ProcessGroupParameters(Box* box, Node* node, Value& valu const auto param = _graph.GetParameter((Guid)node->Values[0], paramIndex); if (param) { - value = _data->Parameters[paramIndex].Value; + value = context.Data->Parameters[paramIndex].Value; switch (param->Type.Type) { case VariantType::Vector2: @@ -523,19 +526,20 @@ void AnimGraphExecutor::ProcessGroupParameters(Box* box, Node* node, Value& valu void AnimGraphExecutor::ProcessGroupTools(Box* box, Node* nodeBase, Value& value) { + auto& context = Context.Get(); auto node = (AnimGraphNode*)nodeBase; switch (node->TypeID) { // Time case 5: { - auto& bucket = _data->State[node->BucketIndex].Animation; - if (bucket.LastUpdateFrame != _currentFrameIndex) + auto& bucket = context.Data->State[node->BucketIndex].Animation; + if (bucket.LastUpdateFrame != context.CurrentFrameIndex) { - bucket.TimePosition += _deltaTime; - bucket.LastUpdateFrame = _currentFrameIndex; + bucket.TimePosition += context.DeltaTime; + bucket.LastUpdateFrame = context.CurrentFrameIndex; } - value = box->ID == 0 ? bucket.TimePosition : _deltaTime; + value = box->ID == 0 ? bucket.TimePosition : context.DeltaTime; break; } default: @@ -546,13 +550,10 @@ void AnimGraphExecutor::ProcessGroupTools(Box* box, Node* nodeBase, Value& value void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Value& value) { - auto box = (AnimGraphBox*)boxBase; - if (box->IsCacheValid()) - { - // Return cache - value = box->Cache; + auto& context = Context.Get(); + if (context.ValueCache.TryGet(boxBase, value)) return; - } + auto box = (AnimGraphBox*)boxBase; auto node = (AnimGraphNode*)nodeBase; switch (node->TypeID) { @@ -569,7 +570,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu case 2: { const auto anim = node->Assets[0].As(); - auto& bucket = _data->State[node->BucketIndex].Animation; + auto& bucket = context.Data->State[node->BucketIndex].Animation; const float speed = (float)tryGetValue(node->GetBox(5), node->Values[1]); const bool loop = (bool)tryGetValue(node->GetBox(6), node->Values[2]); const float startTimePos = (float)tryGetValue(node->GetBox(7), node->Values[3]); @@ -584,17 +585,17 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu const float length = anim ? anim->GetLength() : 0.0f; // Calculate new time position - if (speed < 0.0f && bucket.LastUpdateFrame < _currentFrameIndex - 1) + if (speed < 0.0f && bucket.LastUpdateFrame < context.CurrentFrameIndex - 1) { // If speed is negative and it's the first node update then start playing from end bucket.TimePosition = length; } - float newTimePos = bucket.TimePosition + _deltaTime * speed; + float newTimePos = bucket.TimePosition + context.DeltaTime * speed; value = SampleAnimation(node, loop, length, startTimePos, bucket.TimePosition, newTimePos, anim, 1.0f); bucket.TimePosition = newTimePos; - bucket.LastUpdateFrame = _currentFrameIndex; + bucket.LastUpdateFrame = context.CurrentFrameIndex; break; } @@ -615,7 +616,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu // Is Playing case 4: // If anim was updated during this or a previous frame - value = bucket.LastUpdateFrame >= _currentFrameIndex - 1; + value = bucket.LastUpdateFrame >= context.CurrentFrameIndex - 1; break; } break; @@ -643,7 +644,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu value = Value::Null; if (inputBox->HasConnection()) value = eatBox(nodeBase, inputBox->FirstConnection()); - box->Cache = value; + context.ValueCache.Add(boxBase, value); return; } const auto nodeIndex = _graph.BaseModel->Skeleton.Bones[boneIndex].NodeIndex; @@ -690,7 +691,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu // Transform every node const auto& skeleton = BaseModel->Skeleton; - for (int32 i = 0; i < _skeletonNodesCount; i++) + for (int32 i = 0; i < nodes->Nodes.Count(); i++) { const int32 parentIndex = skeleton.Nodes[i].ParentIndex; if (parentIndex != -1) @@ -729,7 +730,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu // Inv transform every node const auto& skeleton = BaseModel->Skeleton; - for (int32 i = _skeletonNodesCount - 1; i >= 0; i--) + for (int32 i = nodes->Nodes.Count() - 1; i >= 0; i--) { const int32 parentIndex = skeleton.Nodes[i].ParentIndex; if (parentIndex != -1) @@ -775,7 +776,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu { // Pass through the input value = input; - box->Cache = value; + context.ValueCache.Add(boxBase, value); return; } @@ -836,7 +837,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu if (!ANIM_GRAPH_IS_VALID_PTR(valueB)) nodesB = GetEmptyNodes(); - for (int32 i = 0; i < _skeletonNodesCount; i++) + for (int32 i = 0; i < nodes->Nodes.Count(); i++) { Transform::Lerp(nodesA->Nodes[i], nodesB->Nodes[i], alpha, nodes->Nodes[i]); } @@ -876,7 +877,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu const auto nodesA = static_cast(valueA.AsPointer); const auto nodesB = static_cast(valueB.AsPointer); Transform t, tA, tB; - for (int32 i = 0; i < _skeletonNodesCount; i++) + for (int32 i = 0; i < nodes->Nodes.Count(); i++) { tA = nodesA->Nodes[i]; tB = nodesB->Nodes[i]; @@ -921,7 +922,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu // Blend all nodes masked by the user Transform tA, tB; auto& nodesMask = mask->GetNodesMask(); - for (int32 nodeIndex = 0; nodeIndex < _skeletonNodesCount; nodeIndex++) + for (int32 nodeIndex = 0; nodeIndex < nodes->Nodes.Count(); nodeIndex++) { tA = nodesA->Nodes[nodeIndex]; if (nodesMask[nodeIndex]) @@ -956,7 +957,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu // [1]: Guid Animation // Prepare - auto& bucket = _data->State[node->BucketIndex].MultiBlend; + auto& bucket = context.Data->State[node->BucketIndex].MultiBlend; const auto range = node->Values[0].AsVector4(); const auto speed = (float)tryGetValue(node->GetBox(1), node->Values[1]); const auto loop = (bool)tryGetValue(node->GetBox(2), node->Values[2]); @@ -988,12 +989,12 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu } // Calculate new time position - if (speed < 0.0f && bucket.LastUpdateFrame < _currentFrameIndex - 1) + if (speed < 0.0f && bucket.LastUpdateFrame < context.CurrentFrameIndex - 1) { // If speed is negative and it's the first node update then start playing from end bucket.TimePosition = data.Length; } - float newTimePos = bucket.TimePosition + _deltaTime * speed; + float newTimePos = bucket.TimePosition + context.DeltaTime * speed; ANIM_GRAPH_PROFILE_EVENT("Multi Blend 1D"); @@ -1035,7 +1036,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu } bucket.TimePosition = newTimePos; - bucket.LastUpdateFrame = _currentFrameIndex; + bucket.LastUpdateFrame = context.CurrentFrameIndex; break; } @@ -1054,7 +1055,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu // [1]: Guid Animation // Prepare - auto& bucket = _data->State[node->BucketIndex].MultiBlend; + auto& bucket = context.Data->State[node->BucketIndex].MultiBlend; const auto range = node->Values[0].AsVector4(); const auto speed = (float)tryGetValue(node->GetBox(1), node->Values[1]); const auto loop = (bool)tryGetValue(node->GetBox(2), node->Values[2]); @@ -1090,12 +1091,12 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu } // Calculate new time position - if (speed < 0.0f && bucket.LastUpdateFrame < _currentFrameIndex - 1) + if (speed < 0.0f && bucket.LastUpdateFrame < context.CurrentFrameIndex - 1) { // If speed is negative and it's the first node update then start playing from end bucket.TimePosition = data.Length; } - float newTimePos = bucket.TimePosition + _deltaTime * speed; + float newTimePos = bucket.TimePosition + context.DeltaTime * speed; ANIM_GRAPH_PROFILE_EVENT("Multi Blend 2D"); @@ -1227,7 +1228,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu } bucket.TimePosition = newTimePos; - bucket.LastUpdateFrame = _currentFrameIndex; + bucket.LastUpdateFrame = context.CurrentFrameIndex; break; } @@ -1246,7 +1247,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu // [3]: AlphaBlendMode Mode // Prepare - auto& bucket = _data->State[node->BucketIndex].BlendPose; + auto& bucket = context.Data->State[node->BucketIndex].BlendPose; const int32 poseIndex = (int32)tryGetValue(node->GetBox(1), node->Values[0]); const float blendDuration = (float)tryGetValue(node->GetBox(2), node->Values[1]); const int32 poseCount = Math::Clamp(node->Values[2].AsInt, 0, MaxBlendPoses); @@ -1259,7 +1260,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu } // Check if transition is not active (first update, pose not changing or transition ended) - bucket.TransitionPosition += _deltaTime; + bucket.TransitionPosition += context.DeltaTime; if (bucket.PreviousBlendPoseIndex == -1 || bucket.PreviousBlendPoseIndex == poseIndex || bucket.TransitionPosition >= blendDuration || blendDuration <= ANIM_GRAPH_BLEND_THRESHOLD) { bucket.TransitionPosition = 0.0f; @@ -1356,11 +1357,11 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu ANIM_GRAPH_PROFILE_EVENT("State Machine"); // Prepare - auto& bucket = _data->State[node->BucketIndex].StateMachine; + auto& bucket = context.Data->State[node->BucketIndex].StateMachine; auto& data = node->Data.StateMachine; int32 transitionsLeft = maxTransitionsPerUpdate == 0 ? MAX_uint16 : maxTransitionsPerUpdate; bool isFirstUpdate = bucket.LastUpdateFrame == 0 || bucket.CurrentState == nullptr; - if (bucket.LastUpdateFrame != _currentFrameIndex - 1 && reinitializeOnBecomingRelevant) + if (bucket.LastUpdateFrame != context.CurrentFrameIndex - 1 && reinitializeOnBecomingRelevant) { // Reset on becoming relevant isFirstUpdate = true; @@ -1384,19 +1385,19 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu bucket.TransitionPosition = 0.0f; // Reset all state buckets pof the graphs and nodes included inside the state machine - ResetBuckets(data.Graph); + ResetBuckets(context, data.Graph); } // Update the active transition if (bucket.ActiveTransition) { - bucket.TransitionPosition += _deltaTime; + bucket.TransitionPosition += context.DeltaTime; - // Check ofr transition end + // Check for transition end if (bucket.TransitionPosition >= bucket.ActiveTransition->BlendDuration) { // End transition - ResetBuckets(bucket.CurrentState->Data.State.Graph); + ResetBuckets(context, bucket.CurrentState->Data.State.Graph); bucket.CurrentState = bucket.ActiveTransition->Destination; bucket.ActiveTransition = nullptr; bucket.TransitionPosition = 0.0f; @@ -1422,7 +1423,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu // Evaluate source state transition data (position, length, etc.) const Value sourceStatePtr = SampleState(bucket.CurrentState); - auto& transitionData = _transitionData; // Note: this could support nested transitions but who uses state machine inside transition rule? + auto& transitionData = context.TransitionData; // Note: this could support nested transitions but who uses state machine inside transition rule? if (ANIM_GRAPH_IS_VALID_PTR(sourceStatePtr)) { // Use source state as data provider @@ -1475,7 +1476,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu if (bucket.ActiveTransition && bucket.ActiveTransition->BlendDuration <= ZeroTolerance) { // End transition - ResetBuckets(bucket.CurrentState->Data.State.Graph); + ResetBuckets(context, bucket.CurrentState->Data.State.Graph); bucket.CurrentState = bucket.ActiveTransition->Destination; bucket.ActiveTransition = nullptr; bucket.TransitionPosition = 0.0f; @@ -1498,7 +1499,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu } // Update bucket - bucket.LastUpdateFrame = _currentFrameIndex; + bucket.LastUpdateFrame = context.CurrentFrameIndex; break; } @@ -1537,7 +1538,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu // Transition Source State Anim case 23: { - const AnimGraphTransitionData& transitionsData = _transitionData; + const AnimGraphTransitionData& transitionsData = context.TransitionData; switch (box->ID) { // Length @@ -1587,7 +1588,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu if (callFunc == function) { value = Value::Zero; - box->Cache = value; + context.ValueCache.Add(boxBase, value); return; } } @@ -1606,12 +1607,12 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu Box* functionOutputBox = functionOutputNode->TryGetBox(0); // Cache relation between current node in the call stack to the actual function graph - _functions[nodeBase] = (Graph*)data.Graph; + context.Functions[nodeBase] = (Graph*)data.Graph; // Evaluate the function output - _graphStack.Push((Graph*)data.Graph); + context.GraphStack.Push((Graph*)data.Graph); value = functionOutputBox && functionOutputBox->HasConnection() ? eatBox(nodeBase, functionOutputBox->FirstConnection()) : Value::Zero; - _graphStack.Pop(); + context.GraphStack.Pop(); break; } // Transform Bone (local/model space) @@ -1635,7 +1636,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu value = Value::Null; if (inputBox->HasConnection()) value = eatBox(nodeBase, inputBox->FirstConnection()); - box->Cache = value; + context.ValueCache.Add(boxBase, value); return; } const auto nodes = node->GetNodes(this); @@ -1704,7 +1705,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu { // Pass through the input value = input; - box->Cache = value; + context.ValueCache.Add(boxBase, value); return; } @@ -1859,18 +1860,14 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu default: break; } - box->Cache = value; + context.ValueCache.Add(boxBase, value); } void AnimGraphExecutor::ProcessGroupFunction(Box* boxBase, Node* node, Value& value) { - auto box = (AnimGraphBox*)boxBase; - if (box->IsCacheValid()) - { - // Return cache - value = box->Cache; + auto& context = Context.Get(); + if (context.ValueCache.TryGet(boxBase, value)) return; - } switch (node->TypeID) { // Function Input @@ -1878,13 +1875,13 @@ void AnimGraphExecutor::ProcessGroupFunction(Box* boxBase, Node* node, Value& va { // Find the function call AnimGraphNode* functionCallNode = nullptr; - ASSERT(_graphStack.Count() >= 2); + ASSERT(context.GraphStack.Count() >= 2); Graph* graph; - for (int32 i = _callStack.Count() - 1; i >= 0; i--) + for (int32 i = context.CallStack.Count() - 1; i >= 0; i--) { - if (_callStack[i]->Type == GRAPH_NODE_MAKE_TYPE(9, 24) && _functions.TryGet(_callStack[i], graph) && _graphStack[_graphStack.Count() - 1] == (Graph*)graph) + if (context.CallStack[i]->Type == GRAPH_NODE_MAKE_TYPE(9, 24) && context.Functions.TryGet(context.CallStack[i], graph) && context.GraphStack.Last() == (Graph*)graph) { - functionCallNode = (AnimGraphNode*)_callStack[i]; + functionCallNode = (AnimGraphNode*)context.CallStack[i]; break; } } @@ -1926,19 +1923,19 @@ void AnimGraphExecutor::ProcessGroupFunction(Box* boxBase, Node* node, Value& va if (functionCallBox && functionCallBox->HasConnection()) { // Use provided input value from the function call - _graphStack.Pop(); + context.GraphStack.Pop(); value = eatBox(node, functionCallBox->FirstConnection()); - _graphStack.Push(graph); + context.GraphStack.Push(graph); } else { // Use the default value from the function graph value = tryGetValue(node->TryGetBox(1), Value::Zero); } + context.ValueCache.Add(boxBase, value); break; } default: break; } - box->Cache = value; } diff --git a/Source/Engine/Visject/VisjectGraph.h b/Source/Engine/Visject/VisjectGraph.h index 7be06566e..d38234c6d 100644 --- a/Source/Engine/Visject/VisjectGraph.h +++ b/Source/Engine/Visject/VisjectGraph.h @@ -18,13 +18,6 @@ class VisjectGraphNode; class VisjectGraphBox : public GraphBox { -public: - - /// - /// The cached value. - /// - Variant Cache; - public: VisjectGraphBox()