From 509765e30044b733c8d97f09031119781d2462ec Mon Sep 17 00:00:00 2001 From: Lex Date: Sun, 7 Jun 2026 22:04:39 -0300 Subject: [PATCH 1/2] Fixed stop graph deactivation cascade --- .../Statescript/GraphProcessorTests.cs | 36 +++++++++++++++++++ Forge/Statescript/GraphProcessor.cs | 5 ++- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/Forge.Tests/Statescript/GraphProcessorTests.cs b/Forge.Tests/Statescript/GraphProcessorTests.cs index 5b8487f..1ba8a04 100644 --- a/Forge.Tests/Statescript/GraphProcessorTests.cs +++ b/Forge.Tests/Statescript/GraphProcessorTests.cs @@ -320,6 +320,42 @@ public void Stopping_graph_fires_on_graph_completed_once() value.Should().Be(1); } + [Fact] + [Trait("Graph", "Lifecycle")] + public void Stopping_graph_resolves_property_inputs_during_deactivation_cascade() + { + var graph = new Graph(); + graph.VariableDefinitions.DefineVariable("duration", 100.0); + graph.VariableDefinitions.DefineProperty( + "constant", + new VariantResolver(new Variant128(42), typeof(int))); + + TimerNode timer = CreateTimerNode("duration"); + var readNode = new ReadPropertyNode(); + readNode.BindInput(ReadPropertyNode.ValueInput, "constant"); + + graph.AddNode(timer); + graph.AddNode(readNode); + graph.AddConnection(new Connection( + graph.EntryNode.OutputPorts[EntryNode.OutputPort], + timer.InputPorts[StateNode.InputPort])); + graph.AddConnection(new Connection( + timer.OutputPorts[StateNode.OnDeactivatePort], + readNode.InputPorts[ActionNode.InputPort])); + + var processor = new GraphProcessor(graph); + processor.StartGraph(); + + processor.GraphContext.IsActive.Should().BeTrue(); + + processor.StopGraph(); + + readNode.ExecutionCount.Should().Be(1); + readNode.Found.Should().BeTrue( + "property-backed inputs must still resolve during the StopGraph deactivation cascade"); + readNode.LastReadValue.Should().Be(42); + } + [Fact] [Trait("Graph", "Complex")] public void Complex_graph_with_condition_and_multiple_actions_executes_correctly() diff --git a/Forge/Statescript/GraphProcessor.cs b/Forge/Statescript/GraphProcessor.cs index 04f1dd2..0367e9c 100644 --- a/Forge/Statescript/GraphProcessor.cs +++ b/Forge/Statescript/GraphProcessor.cs @@ -108,9 +108,12 @@ public void StopGraph() return; } - GraphContext.Processor = null; + // Clear HasStarted first so the disable cascade is re-entrancy safe (e.g. an ExitNode triggering StopGraph, or a + // state node reaching FinalizeGraph) without nulling Processor yet. Keeping Processor set throughout the cascade + // lets action nodes on OnDeactivate paths still resolve property-backed inputs. GraphContext.HasStarted = false; Graph.EntryNode.StopGraph(GraphContext); + GraphContext.Processor = null; GraphContext.ActiveStateNodes.Clear(); GraphContext.InternalNodeActivationStatus.Clear(); GraphContext.RemoveAllNodeContext(); From 9e35887ff35b38ad9c6c81eaddb86a72c68c25d7 Mon Sep 17 00:00:00 2001 From: Lex Date: Sun, 7 Jun 2026 22:13:29 -0300 Subject: [PATCH 2/2] Added missing HasStarted guard --- Forge/Statescript/GraphProcessor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Forge/Statescript/GraphProcessor.cs b/Forge/Statescript/GraphProcessor.cs index 0367e9c..dee4108 100644 --- a/Forge/Statescript/GraphProcessor.cs +++ b/Forge/Statescript/GraphProcessor.cs @@ -103,7 +103,7 @@ public void UpdateGraph(double deltaTime) /// public void StopGraph() { - if (GraphContext.Processor != this) + if (GraphContext.Processor != this || !GraphContext.HasStarted) { return; }