From 94bd5572484f80b176b0c2ae5b55c85abe28ab57 Mon Sep 17 00:00:00 2001 From: Damian Suess Date: Mon, 1 Jun 2026 13:47:25 -0400 Subject: [PATCH 1/2] state results sample project --- samples/Sample04.ResultTransitions/Program.cs | 89 ++++++++++++++++++- .../Sample04.ResultTransitions.csproj | 4 + 2 files changed, 90 insertions(+), 3 deletions(-) diff --git a/samples/Sample04.ResultTransitions/Program.cs b/samples/Sample04.ResultTransitions/Program.cs index 0cb79dd..716ed1d 100644 --- a/samples/Sample04.ResultTransitions/Program.cs +++ b/samples/Sample04.ResultTransitions/Program.cs @@ -1,12 +1,95 @@ // Copyright Xeno Innovations, Inc. 2026 // See the LICENSE file in the project root for more information. +using Lite.StateMachine; + namespace Sample04.ResultTransitions; -internal class Program +/// State definitions. +public enum StateId +{ + State1, + State2, + State3, +} + +public class Program { - private static void Main(string[] args) + private static async Task Main(string[] args) + { + await ResultsStateMachine.RunAsync(); + } +} + +[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "Ignore")] +public class ResultsStateMachine +{ + /// Example asynchronous run method. + /// Task. + public static async Task RunAsync() + { + var machine = new StateMachine(); + machine.RegisterState(StateId.State1, onSuccess: StateId.State1, onError: StateId.State2, onFailure: null, subscriptionTypes: null); + machine.RegisterState(StateId.State2, onSuccess: StateId.State1, onError: null, onFailure: StateId.State3, subscriptionTypes: null); + machine.RegisterState(StateId.State3, subscriptionTypes: null); + + // Async Example! + await machine.RunAsync(StateId.State1); + } + + public class State1 : IState + { + public Task OnEnter(Context context) + { + // Set Error so we can continue. + context.NextState(Result.Error); + + Console.WriteLine($"[State1][OnEnter].OnSuccess goto: '{context.NextStates.OnSuccess}'"); + Console.WriteLine($"[State1][OnEnter].OnError goto: '{context.NextStates.OnError}'"); + Console.WriteLine($"[State1][OnEnter].OnFailure goto: '{context.NextStates.OnFailure}'"); + return Task.CompletedTask; + } + + public Task OnEntering(Context context) => Task.CompletedTask; + + public Task OnExit(Context context) => Task.CompletedTask; + } + + public class State2 : IState { - Console.WriteLine("Coming Soon!"); + public Task OnEnter(Context context) + { + // Set Success so we can continue. + context.NextState(Result.Error); + + Console.WriteLine($"[State2][OnEnter].OnSuccess goto: '{context.NextStates.OnSuccess}'"); + Console.WriteLine($"[State2][OnEnter].OnError goto: '{context.NextStates.OnError}'"); + Console.WriteLine($"[State2][OnEnter].OnFailure goto: '{context.NextStates.OnFailure}'"); + + return Task.CompletedTask; + } + + public Task OnEntering(Context context) => Task.CompletedTask; + + public Task OnExit(Context context) => Task.CompletedTask; + } + + public class State3 : IState + { + public Task OnEnter(Context context) + { + // Set Failure so we can continue. + context.NextState(Result.Success); + + Console.WriteLine($"[State3][OnEnter].OnSuccess goto: '{context.NextStates.OnSuccess}'"); + Console.WriteLine($"[State3][OnEnter].OnError goto: '{context.NextStates.OnError}'"); + Console.WriteLine($"[State3][OnEnter].OnFailure goto: '{context.NextStates.OnFailure}'"); + + return Task.CompletedTask; + } + + public Task OnEntering(Context context) => Task.CompletedTask; + + public Task OnExit(Context context) => Task.CompletedTask; } } diff --git a/samples/Sample04.ResultTransitions/Sample04.ResultTransitions.csproj b/samples/Sample04.ResultTransitions/Sample04.ResultTransitions.csproj index ed9781c..c3ee00d 100644 --- a/samples/Sample04.ResultTransitions/Sample04.ResultTransitions.csproj +++ b/samples/Sample04.ResultTransitions/Sample04.ResultTransitions.csproj @@ -7,4 +7,8 @@ enable + + + + From 33581c1ae50e71b430b423269b6267f2be34a74c Mon Sep 17 00:00:00 2001 From: Damian Suess Date: Tue, 2 Jun 2026 19:38:16 -0400 Subject: [PATCH 2/2] Added ability for basic states to exit with different results. i.e. `context.NextState(Result.Error);` --- Directory.Build.props | 2 +- samples/Sample04.ResultTransitions/Program.cs | 6 +- .../StateTests/NextStateResultTests.cs | 87 +++++++++++++++++++ .../Lite.StateMachine.csproj | 3 + source/Lite.StateMachine/StateMachine.cs | 2 +- 5 files changed, 95 insertions(+), 5 deletions(-) create mode 100644 source/Lite.StateMachine.Tests/StateTests/NextStateResultTests.cs diff --git a/Directory.Build.props b/Directory.Build.props index 118b552..3a3a01b 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -4,7 +4,7 @@ latest True - 2.4.0 + 2.4.1 ../../output/$(MSBuildProjectName) true diff --git a/samples/Sample04.ResultTransitions/Program.cs b/samples/Sample04.ResultTransitions/Program.cs index 716ed1d..992a8a3 100644 --- a/samples/Sample04.ResultTransitions/Program.cs +++ b/samples/Sample04.ResultTransitions/Program.cs @@ -41,7 +41,7 @@ public class State1 : IState { public Task OnEnter(Context context) { - // Set Error so we can continue. + // Simulate an Error state transition so we can continue. context.NextState(Result.Error); Console.WriteLine($"[State1][OnEnter].OnSuccess goto: '{context.NextStates.OnSuccess}'"); @@ -59,8 +59,8 @@ public class State2 : IState { public Task OnEnter(Context context) { - // Set Success so we can continue. - context.NextState(Result.Error); + // Simulate a Failure state transition so we can continue. + context.NextState(Result.Failure); Console.WriteLine($"[State2][OnEnter].OnSuccess goto: '{context.NextStates.OnSuccess}'"); Console.WriteLine($"[State2][OnEnter].OnError goto: '{context.NextStates.OnError}'"); diff --git a/source/Lite.StateMachine.Tests/StateTests/NextStateResultTests.cs b/source/Lite.StateMachine.Tests/StateTests/NextStateResultTests.cs new file mode 100644 index 0000000..0846074 --- /dev/null +++ b/source/Lite.StateMachine.Tests/StateTests/NextStateResultTests.cs @@ -0,0 +1,87 @@ +// Copyright Xeno Innovations, Inc. 2026 +// See the LICENSE file in the project root for more information. + +using System; +using System.Linq; +using System.Threading.Tasks; + +namespace Lite.StateMachine.Tests.StateTests; + +[TestClass] +public class NextStateResultTests : TestBase +{ + /// State definitions. + public enum StateId + { + State1, + State2, + State3, + } + + [TestMethod] + public async Task NextState_Error_Failure__SuccessTestAsync() + { + var machine = new StateMachine(); + machine.RegisterState(StateId.State1, onSuccess: StateId.State1, onError: StateId.State2, onFailure: null, subscriptionTypes: null); + machine.RegisterState(StateId.State2, onSuccess: StateId.State1, onError: null, onFailure: StateId.State3, subscriptionTypes: null); + machine.RegisterState(StateId.State3, subscriptionTypes: null); + + // Async Example! + await machine.RunAsync(StateId.State1); + + // Assert Results + AssertMachineNotNull(machine); + + // Ensure all states are registered + var enums = Enum.GetValues().Cast(); + Assert.IsNotNull(enums); + Assert.HasCount(enums.Count(), machine.States); + Assert.IsTrue(enums.All(k => machine.States.Contains(k))); + } + + private class State1 : IState + { + public Task OnEnter(Context context) + { + // Simulate an Error state transition so we can continue. + context.NextState(Result.Error); + + Console.WriteLine($"[State1][OnEnter].OnSuccess goto: '{context.NextStates.OnSuccess}'"); + Console.WriteLine($"[State1][OnEnter].OnError goto: '{context.NextStates.OnError}'"); + Console.WriteLine($"[State1][OnEnter].OnFailure goto: '{context.NextStates.OnFailure}'"); + return Task.CompletedTask; + } + + public Task OnEntering(Context context) => Task.CompletedTask; + + public Task OnExit(Context context) => Task.CompletedTask; + } + + private class State2 : IState + { + public Task OnEnter(Context context) + { + // Simulate a Failure state transition so we can continue. + context.NextState(Result.Failure); + return Task.CompletedTask; + } + + public Task OnEntering(Context context) => Task.CompletedTask; + + public Task OnExit(Context context) => Task.CompletedTask; + } + + private class State3 : IState + { + public Task OnEnter(Context context) + { + // Set Failure so we can continue. + context.NextState(Result.Success); + return Task.CompletedTask; + } + + public Task OnEntering(Context context) => Task.CompletedTask; + + public Task OnExit(Context context) => Task.CompletedTask; + } +} diff --git a/source/Lite.StateMachine/Lite.StateMachine.csproj b/source/Lite.StateMachine/Lite.StateMachine.csproj index 32faade..08e3beb 100644 --- a/source/Lite.StateMachine/Lite.StateMachine.csproj +++ b/source/Lite.StateMachine/Lite.StateMachine.csproj @@ -21,6 +21,9 @@ statemachine;lite;fsm;suesslabs;xeno-innovations LICENSE.md + New Features in v2.4: + * Added custom State Results (Success, Error, Failure, etc.) and ability to override the default Result.Success/.Error./Failure. + New Features in v2.2: * New: Renamed, Result.Ok -> Result.Success (Breaking Change). diff --git a/source/Lite.StateMachine/StateMachine.cs b/source/Lite.StateMachine/StateMachine.cs index e0bb1a2..237a6a8 100644 --- a/source/Lite.StateMachine/StateMachine.cs +++ b/source/Lite.StateMachine/StateMachine.cs @@ -133,7 +133,7 @@ public StateMachine RegisterState( IReadOnlyCollection? subscriptionTypes = null) where TStateClass : class, IState { - return RegisterState(stateId, onSuccess, onError: null, onFailure: null, parentStateId: null, isCompositeParent: false, initialChildStateId: null, subscriptionTypes: subscriptionTypes); + return RegisterState(stateId, onSuccess, onError: onError, onFailure: onFailure, parentStateId: null, isCompositeParent: false, initialChildStateId: null, subscriptionTypes: subscriptionTypes); } ///