Core - Keep the last good outputs of a node that fails to evaluate (#917)

A node that failed to evaluate reset its outputs to their default values, so a node breaking for a few frames made
everything after it jump to defaults. It now keeps the outputs of its last successful run, like before #911, and still
marks them as evaluated so it isn't evaluated again for every pin reading from it.
This commit is contained in:
RobertBeekman authored and GitHub committed 2026-10-27 21:07:32 +01:00
1 parent 5ddd50b4a8
commit 8d2927a054
4 files changed
+8 -25

No files matched your search

@@ -218,9 +218,9 @@ public class NodeTests : ScriptTestBase
}
[Fact]
public void FailingNode_InAScript_SetsItsOutputsToTheirDefaultValues()
public void FailingNode_InAScript_KeepsTheOutputsOfItsLastSuccessfulRun()
{
// Rather than letting the value from the last run that worked flow on
// So a node that fails for a few frames doesn't make the script jump to default values
NodeScript<Numeric> script = CreateScript<Numeric>();
TestThrowingNode node = ScriptNodes.AddNode<TestThrowingNode>(script);
script.GetExitNode().Input.ConnectTo(node.Output);
@@ -231,7 +231,7 @@ public class NodeTests : ScriptTestBase
node.FailsOn = TestThrowingNode.FailureMode.Evaluate;
script.Run();
script.Result.ShouldBe(new Numeric(0));
script.Result.ShouldBe(new Numeric(5));
node.BrokenState.ShouldBe("Failed to evaluate");
script.GetExitNode().BrokenState.ShouldBeNull();
}
@@ -239,7 +239,7 @@ public class NodeTests : ScriptTestBase
[Fact]
public void FailingNode_InAScript_IsEvaluatedOncePerRun()
{
// A failing node sets its outputs to their default values, so the other pins reading from it don't try again
// A failing node still marks its outputs as evaluated, so the other pins reading from it don't try again
NodeScript<Numeric> script = CreateScript<Numeric>();
TestNode failing = new() {OnEvaluate = _ => throw new InvalidOperationException()};
OutputPin<Numeric> output = failing.CreateOutputPin<Numeric>();
@@ -388,10 +388,10 @@ public abstract class Node : BreakableModel, INode
if (TryOrBreak(Evaluate, "Failed to evaluate"))
return;
// Don't let the values of the last successful run flow on, and don't try again for every pin that reads from this
// node. Setting a value marks the pin as evaluated until the next run
foreach (Pin pin in Pins.Concat(PinCollections.SelectMany(c => c)).OfType<Pin>().Where(p => p.Direction == PinDirection.Output))
pin.SetDefaultValue();
// Keep the values of the last successful run so a brief failure goes unnoticed, and don't try again for every pin
// that reads from this node
foreach (IPin pin in Pins.Concat(PinCollections.SelectMany(c => c)).Where(p => p.Direction == PinDirection.Output))
pin.IsEvaluated = true;
}
/// <summary>
@@ -29,11 +29,6 @@ public sealed class OutputPin<T> : Pin
/// <inheritdoc />
public override PinDirection Direction => PinDirection.Output;
internal override void SetDefaultValue()
{
Value = default;
}
private T? _value;
/// <summary>
@@ -105,11 +100,6 @@ public sealed class OutputPin : Pin
/// <inheritdoc />
public override PinDirection Direction => PinDirection.Output;
internal override void SetDefaultValue()
{
Value = Type.GetDefault();
}
private object? _value;
private Type _type;
@@ -85,13 +85,6 @@ public abstract class Pin : CorePropertyChanged, IPin
IsEvaluated = false;
}
/// <summary>
/// Sets the value of an output pin to the default value of its type, used when its node fails to evaluate
/// </summary>
internal virtual void SetDefaultValue()
{
}
/// <inheritdoc />
public void ConnectTo(IPin pin)
{