From 6daec81db1e19dc08c6c9fce9d72e9ec1c8c6ad4 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 28 May 2026 13:30:40 +0200 Subject: [PATCH] Fix crash when using curves inside Anim Graph https://forum.flaxengine.com/t/bug-report-v1-12-using-curves-in-the-animation-graph-causes-a-crash/2594 --- .../Animations/Graph/AnimGraph.Base.cpp | 4 +- Source/Engine/Content/Assets/VisualScript.cpp | 10 ++-- Source/Engine/Content/Assets/VisualScript.h | 40 +++++++++++++- Source/Engine/Visject/ShaderGraph.h | 9 ++++ Source/Engine/Visject/VisjectGraph.cpp | 2 +- Source/Engine/Visject/VisjectGraph.h | 53 ++++--------------- 6 files changed, 66 insertions(+), 52 deletions(-) diff --git a/Source/Engine/Animations/Graph/AnimGraph.Base.cpp b/Source/Engine/Animations/Graph/AnimGraph.Base.cpp index 1cc9c66a4..8f1d494ec 100644 --- a/Source/Engine/Animations/Graph/AnimGraph.Base.cpp +++ b/Source/Engine/Animations/Graph/AnimGraph.Base.cpp @@ -61,14 +61,14 @@ void AnimGraphBase::Clear() StateTransitions.Resize(0); // Base - GraphType::Clear(); + VisjectGraph::Clear(); } #if USE_EDITOR void AnimGraphBase::GetReferences(Array& output) const { - GraphType::GetReferences(output); + VisjectGraph::GetReferences(output); // Collect references from nested graph (assets used in state machines) for (const auto* subGraph : SubGraphs) diff --git a/Source/Engine/Content/Assets/VisualScript.cpp b/Source/Engine/Content/Assets/VisualScript.cpp index a7e132bdc..62d901b6f 100644 --- a/Source/Engine/Content/Assets/VisualScript.cpp +++ b/Source/Engine/Content/Assets/VisualScript.cpp @@ -163,7 +163,7 @@ VisjectExecutor::Value VisualScriptExecutor::eatBox(Node* caller, Box* box) // Add to the calling stack VisualScripting::StackFrame frame = *stack.Stack; - frame.Node = parentNode; + frame.Node = (VisualScriptGraphNode*)parentNode; frame.Box = box; frame.PreviousFrame = stack.Stack; stack.Stack = &frame; @@ -189,7 +189,7 @@ VisjectExecutor::Value VisualScriptExecutor::eatBox(Node* caller, Box* box) VisjectExecutor::Graph* VisualScriptExecutor::GetCurrentGraph() const { auto& stack = ThreadStacks.Get(); - return stack.Stack && stack.Stack->Script ? &stack.Stack->Script->Graph : nullptr; + return stack.Stack && stack.Stack->Script ? (Graph*)&stack.Stack->Script->Graph : nullptr; } void VisualScriptExecutor::ProcessGroupParameters(Box* box, Node* node, Value& value) @@ -432,7 +432,7 @@ void VisualScriptExecutor::ProcessGroupFunction(Box* boxBase, Node* node, Value& // Call Impulse or Pure Method if (boxBase->ID == 0 || (bool)node->Values[3]) { - auto& cache = node->Data.InvokeMethod; + auto& cache = ((VisualScriptGraphNode*)node)->Data.InvokeMethod; if (!cache.Method) { // Load method signature @@ -667,7 +667,7 @@ void VisualScriptExecutor::ProcessGroupFunction(Box* boxBase, Node* node, Value& // Get Field case 7: { - auto& cache = node->Data.GetSetField; + auto& cache = ((VisualScriptGraphNode*)node)->Data.GetSetField; if (!cache.Field) { const auto typeName = (StringView)node->Values[0]; @@ -753,7 +753,7 @@ void VisualScriptExecutor::ProcessGroupFunction(Box* boxBase, Node* node, Value& // Get Field case 8: { - auto& cache = node->Data.GetSetField; + auto& cache = ((VisualScriptGraphNode*)node)->Data.GetSetField; if (!cache.Field) { const auto typeName = (StringView)node->Values[0]; diff --git a/Source/Engine/Content/Assets/VisualScript.h b/Source/Engine/Content/Assets/VisualScript.h index 9e7af97cd..91b9506fa 100644 --- a/Source/Engine/Content/Assets/VisualScript.h +++ b/Source/Engine/Content/Assets/VisualScript.h @@ -12,11 +12,47 @@ #define VISUAL_SCRIPT_GRAPH_MAX_CALL_STACK 250 #define VISUAL_SCRIPT_DEBUGGING USE_EDITOR -#define VisualScriptGraphNode VisjectGraphNode<> - class VisualScripting; class VisualScriptingBinaryModule; +/// +/// Visual Script graph node. +/// +class VisualScriptGraphNode : public VisjectGraphNode<> +{ +public: + struct InvokeMethodData + { + void* Method; + BinaryModule* Module; + int32 ParamsCount; + uint32 OutParamsMask; + bool IsStatic; + }; + + struct GetSetFieldData + { + void* Field; + BinaryModule* Module; + bool IsStatic; + }; + + /// + /// Custom cached data per node type. Compact to use as small amount of memory as possible. + /// + struct AdditionalData + { + union + { + InvokeMethodData InvokeMethod; + GetSetFieldData GetSetField; + }; + }; + + // The custom per-node data. Used to cache data for faster usage at runtime. + AdditionalData Data; +}; + /// /// The Visual Script graph data. /// diff --git a/Source/Engine/Visject/ShaderGraph.h b/Source/Engine/Visject/ShaderGraph.h index 5b17604d0..d22260a87 100644 --- a/Source/Engine/Visject/ShaderGraph.h +++ b/Source/Engine/Visject/ShaderGraph.h @@ -167,6 +167,15 @@ public: // Base return Base::onNodeLoaded(n); } + void Clear() override + { + FloatCurves.Clear(); + Float2Curves.Clear(); + Float3Curves.Clear(); + Float4Curves.Clear(); + + Base::Clear(); + } }; /// diff --git a/Source/Engine/Visject/VisjectGraph.cpp b/Source/Engine/Visject/VisjectGraph.cpp index ec4f51364..8b8c27010 100644 --- a/Source/Engine/Visject/VisjectGraph.cpp +++ b/Source/Engine/Visject/VisjectGraph.cpp @@ -952,7 +952,7 @@ void VisjectExecutor::ProcessGroupTools(Box* box, Node* node, Value& value) #define SAMPLE_CURVE(id, curves, type, graphType) \ case id: \ { \ - const auto& curve = GetCurrentGraph()->curves[node->Data.Curve.CurveIndex]; \ + const auto& curve = GetCurrentGraph()->curves[node->CurveIndex]; \ const float time = (float)tryGetValue(node->GetBox(0), Value::Zero); \ value.Type = VariantType(VariantType::graphType); \ curve.Evaluate(*(type*)value.AsData, time, false); \ diff --git a/Source/Engine/Visject/VisjectGraph.h b/Source/Engine/Visject/VisjectGraph.h index 1f0de1ce0..0eceaf8cb 100644 --- a/Source/Engine/Visject/VisjectGraph.h +++ b/Source/Engine/Visject/VisjectGraph.h @@ -42,42 +42,6 @@ public: template class VisjectGraphNode : public GraphNode { -public: - struct CurveData - { - /// - /// The curve index. - /// - int32 CurveIndex; - }; - - /// - /// Custom cached data per node type. Compact to use as small amount of memory as possible. - /// - struct AdditionalData - { - union - { - CurveData Curve; - - struct - { - void* Method; - BinaryModule* Module; - int32 ParamsCount; - uint32 OutParamsMask; - bool IsStatic; - } InvokeMethod; - - struct - { - void* Field; - BinaryModule* Module; - bool IsStatic; - } GetSetField; - }; - }; - public: VisjectGraphNode() : GraphNode() @@ -85,10 +49,7 @@ public: } public: - /// - /// The custom data (depends on node type). Used to cache data for faster usage at runtime. - /// - AdditionalData Data; + int32 CurveIndex = MAX_uint16; /// /// The asset references. Linked resources such as Animation assets are referenced in graph data as ID. We need to keep valid refs to them at runtime to keep data in memory. @@ -148,7 +109,7 @@ public: #define SETUP_CURVE(id, curves, access) \ case id: \ { \ - n->Data.Curve.CurveIndex = curves.Count(); \ + n->CurveIndex = curves.Count(); \ auto& curve = curves.AddOne(); \ const int32 keyframesCount = n->Values[0].AsInt; \ auto& keyframes = curve.GetKeyframes(); \ @@ -177,9 +138,17 @@ public: } } - // Base return Base::onNodeLoaded(n); } + void Clear() override + { + FloatCurves.Clear(); + Float2Curves.Clear(); + Float3Curves.Clear(); + Float4Curves.Clear(); + + Base::Clear(); + } }; ///