Refactor AnimGraph to support asynchronous execution

This commit is contained in:
Wojtek Figat
2021-06-12 12:29:47 +02:00
parent b8ad4bdd2a
commit 41ad835d86
6 changed files with 199 additions and 252 deletions

View File

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

View File

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

View File

@@ -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<AnimGraphContext, 64> 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<Node>(), 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<Node>()->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();
}

View File

@@ -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:
/// <summary>
/// Clears this container data.
/// </summary>
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();
/// <summary>
/// Clears this container state data.
/// </summary>
void ClearState()
{
Version = 0;
LastUpdateTime = -1;
CurrentFrame = 0;
RootTransform = Transform::Identity;
RootMotion = RootMotionData::Identity;
State.Resize(0);
NodesPose.Resize(0);
}
void ClearState();
/// <summary>
/// Invalidates the update timer.
/// </summary>
void Invalidate()
{
LastUpdateTime = -1;
CurrentFrame = 0;
}
void Invalidate();
};
/// <summary>
@@ -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<AnimGraphBox>
@@ -575,13 +541,6 @@ public:
/// </summary>
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)
/// <summary>
/// The node transformations (layout matches the linked to graph skinned model skeleton).
/// </summary>
AnimGraphImpulse Nodes;
/// <summary>
/// The custom data (depends on node type). Used to cache data for faster usage at runtime.
/// </summary>
@@ -661,17 +620,11 @@ public:
/// <summary>
/// Gets the root node of the graph (cache don load).
/// </summary>
/// <returns>The root node.</returns>
FORCE_INLINE Node* GetRootNode() const
{
return _rootNode;
}
/// <summary>
/// Clear all cached values in the graph nodes and the sub-graphs data.
/// </summary>
void ClearCache();
/// <summary>
/// Loads the sub-graph.
/// </summary>
@@ -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;
};
/// <summary>
/// The Animation Graph evaluation context.
/// </summary>
struct AnimGraphContext
{
float DeltaTime;
uint64 CurrentFrameIndex;
AnimGraphInstanceData* Data;
AnimGraphImpulse EmptyNodes;
AnimGraphTransitionData TransitionData;
Array<VisjectExecutor::Node*, FixedAllocation<ANIM_GRAPH_MAX_CALL_STACK>> CallStack;
Array<VisjectExecutor::Graph*, FixedAllocation<32>> GraphStack;
Dictionary<VisjectExecutor::Node*, VisjectExecutor::Graph*> Functions;
ChunkedArray<AnimGraphImpulse, 256> PoseCache;
int32 PoseCacheSize;
Dictionary<VisjectExecutor::Box*, Variant> ValueCache;
};
/// <summary>
/// The Animation Graph executor runtime for animation pose evaluation.
/// </summary>
@@ -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<Node*, FixedAllocation<ANIM_GRAPH_MAX_CALL_STACK>> _callStack;
Array<Graph*, FixedAllocation<32>> _graphStack;
Dictionary<Node*, Graph*> _functions;
int32 _skeletonNodesCount = 0;
// Per-thread context to allow async execution
static ThreadLocal<AnimGraphContext, 64> Context;
public:
/// <summary>
/// Initializes the managed runtime calls.
/// </summary>
@@ -854,34 +819,10 @@ public:
/// <summary>
/// Gets the skeleton nodes transformations structure containing identity matrices.
/// </summary>
FORCE_INLINE const AnimGraphImpulse* GetEmptyNodes() const
{
return &_emptyNodes;
}
AnimGraphImpulse* GetEmptyNodes();
/// <summary>
/// Gets the skeleton nodes transformations structure containing identity matrices.
/// </summary>
/// <returns>The data.</returns>
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<AnimGraphImpulse*>(value.AsPointer));
}
/// <summary>
/// Resets the state bucket.
/// </summary>
/// <param name="bucketIndex">The zero-based index of the bucket.</param>
void ResetBucket(int32 bucketIndex);
/// <summary>
/// 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).
/// </summary>
void ResetBuckets(AnimGraphBase* graph);
void ResetBuckets(AnimGraphContext& context, AnimGraphBase* graph);
private:

View File

@@ -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<Animation>();
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<AnimGraphImpulse*>(valueA.AsPointer);
const auto nodesB = static_cast<AnimGraphImpulse*>(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;
}