From cad2d82e3b000d9cb54243287fc44dd5d2031b33 Mon Sep 17 00:00:00 2001 From: Damian Suess Date: Sun, 31 May 2026 11:24:53 -0400 Subject: [PATCH 1/7] Backed up old POCs --- {samples/Sample.Mk4 => docs/old-concepts}/Sample4AMachine.cs | 0 {samples/Sample.Mk4 => docs/old-concepts}/Sample4BMachine.cs | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename {samples/Sample.Mk4 => docs/old-concepts}/Sample4AMachine.cs (100%) rename {samples/Sample.Mk4 => docs/old-concepts}/Sample4BMachine.cs (100%) diff --git a/samples/Sample.Mk4/Sample4AMachine.cs b/docs/old-concepts/Sample4AMachine.cs similarity index 100% rename from samples/Sample.Mk4/Sample4AMachine.cs rename to docs/old-concepts/Sample4AMachine.cs diff --git a/samples/Sample.Mk4/Sample4BMachine.cs b/docs/old-concepts/Sample4BMachine.cs similarity index 100% rename from samples/Sample.Mk4/Sample4BMachine.cs rename to docs/old-concepts/Sample4BMachine.cs From 045658bde978e59893fcbf1dc595d410cb1767fc Mon Sep 17 00:00:00 2001 From: Damian Suess Date: Sun, 31 May 2026 11:52:56 -0400 Subject: [PATCH 2/7] stats don't lie --- readme.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/readme.md b/readme.md index 804b2a3..69e0a6a 100644 --- a/readme.md +++ b/readme.md @@ -20,6 +20,17 @@ The Lite State Machine is designed for vertical scaling. Meaning, it can be used |-|-|-| | Lite.StateMachine | [![Lite.StateMachine NuGet Badge](https://img.shields.io/nuget/v/Lite.StateMachine)](https://www.nuget.org/packages/Lite.StateMachine/) | [![Lite.StateMachine NuGet Badge](https://img.shields.io/nuget/vpre/Lite.StateMachine)](https://www.nuget.org/packages/Lite.StateMachine/) +## Benchmarks + +Not only is [Lite.StateMachine](https://github.com/SuessLabs/Lite.StateMachine) smaller, easier to read, manage, and maintain, _**it faster too**_! + +The following table is an output of local [benchmark results](https://github.com/DamianSuess/Lite.StateMachine.Benchmarks) using state-transition operations across multiple states: + +| Method | Version | Mean | Allocated | +|-|-|-|-| +| **Lite.StateMachine** | v2.3.0 | **10.17 us** | **8.02 KB** | +| Stateless | v5.20.1 | 10.72 us | 10.62 KB | + ## Usage Create a _state machine_ by defining the states, transitions, and shared context. From cd0c7edb943b3074a61200e49166b8ce76743180 Mon Sep 17 00:00:00 2001 From: Damian Suess Date: Sun, 31 May 2026 12:10:59 -0400 Subject: [PATCH 3/7] Added basic Dependency Injection sample. Same as 'basics', just isolated naming --- Lite.StateMachine.slnx | 2 +- readme.md | 2 + .../{MessageService.cs => CounterService.cs} | 4 +- samples/Sample.Mk4/BasicDiStates.cs | 29 ++++++++ samples/Sample.Mk4/CounterService.cs | 54 +++++++++++++++ samples/Sample.Mk4/Program.cs | 52 ++++++++++++-- ...oj => Sample03.DependencyInjection.csproj} | 2 + samples/Sample.Mk4/StateDiBase.cs | 67 +++++++++++++++++++ 8 files changed, 204 insertions(+), 8 deletions(-) rename samples/Sample.Basics/Services/{MessageService.cs => CounterService.cs} (94%) create mode 100644 samples/Sample.Mk4/BasicDiStates.cs create mode 100644 samples/Sample.Mk4/CounterService.cs rename samples/Sample.Mk4/{Sample.Mk4.csproj => Sample03.DependencyInjection.csproj} (72%) create mode 100644 samples/Sample.Mk4/StateDiBase.cs diff --git a/Lite.StateMachine.slnx b/Lite.StateMachine.slnx index 3e7a3ea..8bdc710 100644 --- a/Lite.StateMachine.slnx +++ b/Lite.StateMachine.slnx @@ -1,7 +1,7 @@ - + diff --git a/readme.md b/readme.md index 69e0a6a..6ca4cf6 100644 --- a/readme.md +++ b/readme.md @@ -31,6 +31,8 @@ The following table is an output of local [benchmark results](https://github.com | **Lite.StateMachine** | v2.3.0 | **10.17 us** | **8.02 KB** | | Stateless | v5.20.1 | 10.72 us | 10.62 KB | +_Lite.StateMachine is the fastest and lowest allocation_ + ## Usage Create a _state machine_ by defining the states, transitions, and shared context. diff --git a/samples/Sample.Basics/Services/MessageService.cs b/samples/Sample.Basics/Services/CounterService.cs similarity index 94% rename from samples/Sample.Basics/Services/MessageService.cs rename to samples/Sample.Basics/Services/CounterService.cs index 59ff57e..0fdc9ce 100644 --- a/samples/Sample.Basics/Services/MessageService.cs +++ b/samples/Sample.Basics/Services/CounterService.cs @@ -6,7 +6,7 @@ namespace Sample.Basics.Services; [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1649:File name should match first type name", Justification = "Allowed for testing.")] -public interface IMessageService +public interface ICounterService { /// public int Counter1 { get; set; } diff --git a/samples/Sample.Mk4/BasicDiStates.cs b/samples/Sample.Mk4/BasicDiStates.cs new file mode 100644 index 0000000..83ce830 --- /dev/null +++ b/samples/Sample.Mk4/BasicDiStates.cs @@ -0,0 +1,29 @@ +// Copyright Xeno Innovations, Inc. 2025 +// See the LICENSE file in the project root for more information. + +using Microsoft.Extensions.Logging; + +namespace Sample03.DependencyInjection; + +#pragma warning disable SA1649 // File name should match first type name +#pragma warning disable SA1402 // File may only contain a single type + +/// State definitions. +public enum BasicStateId +{ + State1, + State2, + State3, +} + +public class BasicDiState1(ICounterService msg, ILogger log) + : StateDiBase(msg, log); + +public class BasicDiState2(ICounterService msg, ILogger log) + : StateDiBase(msg, log); + +public class BasicDiState3(ICounterService msg, ILogger log) + : StateDiBase(msg, log); + +#pragma warning restore SA1649 // File name should match first type name +#pragma warning restore SA1402 // File may only contain a single type diff --git a/samples/Sample.Mk4/CounterService.cs b/samples/Sample.Mk4/CounterService.cs new file mode 100644 index 0000000..7517485 --- /dev/null +++ b/samples/Sample.Mk4/CounterService.cs @@ -0,0 +1,54 @@ +// Copyright Xeno Innovations, Inc. 2025 +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; + +namespace Sample03.DependencyInjection; + +[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1649:File name should match first type name", Justification = "Allowed for testing.")] +public interface ICounterService +{ + /// uses it as an automatic state transition counter. + /// + int Counter1 { get; set; } + + /// Gets or sets the user's custom counter. + int Counter2 { get; set; } + + /// Gets or sets the user's custom counter. + int Counter3 { get; set; } + + /// Gets or sets the user's custom counter. + int Counter4 { get; set; } + + /// Gets a list of user's custom messages. + List Messages { get; } + + /// Add message to read-only list. + /// Message. + void AddMessage(string message); +} + +public class CounterService : ICounterService +{ + /// + public int Counter1 { get; set; } + + /// + public int Counter2 { get; set; } + + /// + public int Counter3 { get; set; } + + /// + public int Counter4 { get; set; } + + /// + public List Messages { get; } = []; + + /// + public void AddMessage(string message) => + Messages.Add(message); +} diff --git a/samples/Sample.Mk4/Program.cs b/samples/Sample.Mk4/Program.cs index 57986ff..88c0e8d 100644 --- a/samples/Sample.Mk4/Program.cs +++ b/samples/Sample.Mk4/Program.cs @@ -1,14 +1,56 @@ // Copyright Xeno Innovations, Inc. 2025 // See the LICENSE file in the project root for more information. -namespace Sample.Mk4a; +using System; +using System.Linq; +using System.Threading.Tasks; +using Lite.StateMachine; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace Sample03.DependencyInjection; internal class Program { - private static void Main() + private static async Task Main() + { + await TestFlatStatesAsync(); + } + + private static async Task TestFlatStatesAsync() { - ////Mk4.SampleA.TestApp.Run(); - //// - ////Mk4.SampleB.TestApp.Run(); + // Assemble with Dependency Injection + var services = new ServiceCollection() + //// Register Services + .AddLogging(b => b.AddSimpleConsole()) + .AddSingleton() + //// Register States + .AddTransient() + .AddTransient() + .AddTransient() + .BuildServiceProvider(); + + Func factory = t => ActivatorUtilities.CreateInstance(services, t); + + var machine = new StateMachine(factory) + .RegisterState(BasicStateId.State1, BasicStateId.State2) + .RegisterState(BasicStateId.State2, BasicStateId.State3) + .RegisterState(BasicStateId.State3); + + var result = await machine.RunAsync(BasicStateId.State1); + + ////Assert.IsNotNull(result); + ////AssertMachineNotNull(machine); + + var msgService = services.GetRequiredService(); + ////Assert.AreEqual(9, msgService.Counter1, "Message service should have 9 from the 3 states."); + + // Ensure all states are registered + var enums = Enum.GetValues().Cast(); + ////Assert.AreEqual(enums.Count(), machine.States.Count()); + ////Assert.IsTrue(enums.All(k => machine.States.Contains(k))); + + // Ensure they're registered in order + ////Assert.IsTrue(enums.SequenceEqual(machine.States), "States should be registered for execution in the same order as the defined enums, StateId 1 => 2 => 3."); } } diff --git a/samples/Sample.Mk4/Sample.Mk4.csproj b/samples/Sample.Mk4/Sample03.DependencyInjection.csproj similarity index 72% rename from samples/Sample.Mk4/Sample.Mk4.csproj rename to samples/Sample.Mk4/Sample03.DependencyInjection.csproj index 0afa75f..bc3c792 100644 --- a/samples/Sample.Mk4/Sample.Mk4.csproj +++ b/samples/Sample.Mk4/Sample03.DependencyInjection.csproj @@ -3,9 +3,11 @@ Exe net10.0 + latest + diff --git a/samples/Sample.Mk4/StateDiBase.cs b/samples/Sample.Mk4/StateDiBase.cs new file mode 100644 index 0000000..6cf9413 --- /dev/null +++ b/samples/Sample.Mk4/StateDiBase.cs @@ -0,0 +1,67 @@ +// Copyright Xeno Innovations, Inc. 2025 +// See the LICENSE file in the project root for more information. + +using System; +using System.Diagnostics; +using System.Threading.Tasks; +using Lite.StateMachine; +using Microsoft.Extensions.Logging; + +namespace Sample03.DependencyInjection; + +#pragma warning disable SA1124 // Do not use regions + +public class StateDiBase(ICounterService msg, ILogger logger) : IState + where TStateId : struct, Enum +{ + private readonly ILogger _logger = logger; + private readonly ICounterService _msgService = msg; + + /// Gets or sets a value indicating whether output transitions for debugging tests. + public bool HasExtraLogging { get; set; } = false; + + public ILogger Log => _logger; + + public ICounterService MessageService => _msgService; + + #region Suppress CodeMaid Method Sorting + + public virtual Task OnEntering(Context context) + { + _msgService.Counter1++; + _logger.LogInformation("[OnEntering]"); + + if (HasExtraLogging) + Debug.WriteLine($"[{GetType().Name}] [OnEntering]"); + + return Task.CompletedTask; + } + + #endregion Suppress CodeMaid Method Sorting + + public virtual Task OnEnter(Context context) + { + _msgService.Counter1++; + _logger.LogInformation("[OnEnter] => OK"); + + if (HasExtraLogging) + Debug.WriteLine($"[{GetType().Name}] [OnEnter] => OK"); + + context.NextState(Result.Success); + return Task.CompletedTask; + } + + public virtual Task OnExit(Context context) + { + _msgService.Counter1++; + _logger.LogInformation("[OnExit]"); + + if (HasExtraLogging) + Debug.WriteLine($"[{GetType().Name}] [OnExit]"); + + context.NextState(Result.Success); + return Task.CompletedTask; + } +} + +#pragma warning restore SA1124 // Do not use regions From 793f3fd6bdd7fe1addef742cf510901eeb8a8e16 Mon Sep 17 00:00:00 2001 From: Damian Suess Date: Sun, 31 May 2026 15:08:03 -0400 Subject: [PATCH 4/7] Refactored sample project names and moved unused examples --- Lite.StateMachine.slnx | 4 ++-- .../old-concepts}/DiStates/DemoDiMachine.cs | 0 .../old-concepts}/DiStates/DemoDiStates.cs | 0 .../old-concepts}/DiStates/DiStateBase.cs | 0 .../Models/ParameterType.cs | 2 +- .../Program.cs | 7 ++++--- .../Sample02.PassingParams.csproj} | 0 .../Services/CounterService.cs | 2 +- .../States/DemoMachine.cs | 4 ++-- .../States/DemoStates.cs | 4 ++-- .../States/StateBase.cs | 2 +- samples/Sample02.PassingParams/readme.md | 13 +++++++++++++ .../BasicDiStates.cs | 0 .../CounterService.cs | 0 .../Program.cs | 17 +++++++++-------- .../Sample03.DependencyInjection.csproj | 0 .../StateDiBase.cs | 0 samples/Sample03.DependencyInjection/readme.md | 6 ++++++ 18 files changed, 41 insertions(+), 20 deletions(-) rename {samples/Sample.Basics => docs/old-concepts}/DiStates/DemoDiMachine.cs (100%) rename {samples/Sample.Basics => docs/old-concepts}/DiStates/DemoDiStates.cs (100%) rename {samples/Sample.Basics => docs/old-concepts}/DiStates/DiStateBase.cs (100%) rename samples/{Sample.Basics => Sample02.PassingParams}/Models/ParameterType.cs (82%) rename samples/{Sample.Basics => Sample02.PassingParams}/Program.cs (79%) rename samples/{Sample.Basics/Sample.Basics.csproj => Sample02.PassingParams/Sample02.PassingParams.csproj} (100%) rename samples/{Sample.Basics => Sample02.PassingParams}/Services/CounterService.cs (96%) rename samples/{Sample.Basics => Sample02.PassingParams}/States/DemoMachine.cs (91%) rename samples/{Sample.Basics => Sample02.PassingParams}/States/DemoStates.cs (98%) rename samples/{Sample.Basics => Sample02.PassingParams}/States/StateBase.cs (93%) create mode 100644 samples/Sample02.PassingParams/readme.md rename samples/{Sample.Mk4 => Sample03.DependencyInjection}/BasicDiStates.cs (100%) rename samples/{Sample.Mk4 => Sample03.DependencyInjection}/CounterService.cs (100%) rename samples/{Sample.Mk4 => Sample03.DependencyInjection}/Program.cs (65%) rename samples/{Sample.Mk4 => Sample03.DependencyInjection}/Sample03.DependencyInjection.csproj (100%) rename samples/{Sample.Mk4 => Sample03.DependencyInjection}/StateDiBase.cs (100%) create mode 100644 samples/Sample03.DependencyInjection/readme.md diff --git a/Lite.StateMachine.slnx b/Lite.StateMachine.slnx index 8bdc710..77454cf 100644 --- a/Lite.StateMachine.slnx +++ b/Lite.StateMachine.slnx @@ -1,7 +1,7 @@ - - + + diff --git a/samples/Sample.Basics/DiStates/DemoDiMachine.cs b/docs/old-concepts/DiStates/DemoDiMachine.cs similarity index 100% rename from samples/Sample.Basics/DiStates/DemoDiMachine.cs rename to docs/old-concepts/DiStates/DemoDiMachine.cs diff --git a/samples/Sample.Basics/DiStates/DemoDiStates.cs b/docs/old-concepts/DiStates/DemoDiStates.cs similarity index 100% rename from samples/Sample.Basics/DiStates/DemoDiStates.cs rename to docs/old-concepts/DiStates/DemoDiStates.cs diff --git a/samples/Sample.Basics/DiStates/DiStateBase.cs b/docs/old-concepts/DiStates/DiStateBase.cs similarity index 100% rename from samples/Sample.Basics/DiStates/DiStateBase.cs rename to docs/old-concepts/DiStates/DiStateBase.cs diff --git a/samples/Sample.Basics/Models/ParameterType.cs b/samples/Sample02.PassingParams/Models/ParameterType.cs similarity index 82% rename from samples/Sample.Basics/Models/ParameterType.cs rename to samples/Sample02.PassingParams/Models/ParameterType.cs index 9447f9c..a4bd8fe 100644 --- a/samples/Sample.Basics/Models/ParameterType.cs +++ b/samples/Sample02.PassingParams/Models/ParameterType.cs @@ -1,7 +1,7 @@ // Copyright Xeno Innovations, Inc. 2025 // See the LICENSE file in the project root for more information. -namespace Sample.Basics.Models; +namespace Sample02.PassingParams.Models; public enum ParameterType { diff --git a/samples/Sample.Basics/Program.cs b/samples/Sample02.PassingParams/Program.cs similarity index 79% rename from samples/Sample.Basics/Program.cs rename to samples/Sample02.PassingParams/Program.cs index da53eae..8a149f7 100644 --- a/samples/Sample.Basics/Program.cs +++ b/samples/Sample02.PassingParams/Program.cs @@ -4,15 +4,16 @@ using System; using System.Diagnostics; using System.Threading.Tasks; +using Sample02.PassingParams.States; -namespace Sample.Basics; +namespace Sample02.PassingParams; internal class Program { private static async Task Main(string[] args) { Console.WriteLine("Sample state machine with LiteState!"); - await States.DemoMachine.RunAsync(); + await DemoMachine.RunAsync(); // Poor man's timestamp Console.WriteLine("\nRunning again, showing simple benchmarks..."); @@ -20,7 +21,7 @@ private static async Task Main(string[] args) { var sw = Stopwatch.StartNew(); - await States.DemoMachine.RunAsync(logOutput: false); + await DemoMachine.RunAsync(logOutput: false); sw.Stop(); Console.WriteLine($"Took {sw.ElapsedMilliseconds} ms ({sw.ElapsedTicks} ticks)"); diff --git a/samples/Sample.Basics/Sample.Basics.csproj b/samples/Sample02.PassingParams/Sample02.PassingParams.csproj similarity index 100% rename from samples/Sample.Basics/Sample.Basics.csproj rename to samples/Sample02.PassingParams/Sample02.PassingParams.csproj diff --git a/samples/Sample.Basics/Services/CounterService.cs b/samples/Sample02.PassingParams/Services/CounterService.cs similarity index 96% rename from samples/Sample.Basics/Services/CounterService.cs rename to samples/Sample02.PassingParams/Services/CounterService.cs index 0fdc9ce..d382759 100644 --- a/samples/Sample.Basics/Services/CounterService.cs +++ b/samples/Sample02.PassingParams/Services/CounterService.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; -namespace Sample.Basics.Services; +namespace Sample02.PassingParams.Services; [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1649:File name should match first type name", Justification = "Allowed for testing.")] public interface ICounterService diff --git a/samples/Sample.Basics/States/DemoMachine.cs b/samples/Sample02.PassingParams/States/DemoMachine.cs similarity index 91% rename from samples/Sample.Basics/States/DemoMachine.cs rename to samples/Sample02.PassingParams/States/DemoMachine.cs index 925bfe0..baf147a 100644 --- a/samples/Sample.Basics/States/DemoMachine.cs +++ b/samples/Sample02.PassingParams/States/DemoMachine.cs @@ -3,9 +3,9 @@ using System.Threading.Tasks; using Lite.StateMachine; -using Sample.Basics.Models; +using Sample02.PassingParams.Models; -namespace Sample.Basics.States; +namespace Sample02.PassingParams.States; public enum BasicStateId { diff --git a/samples/Sample.Basics/States/DemoStates.cs b/samples/Sample02.PassingParams/States/DemoStates.cs similarity index 98% rename from samples/Sample.Basics/States/DemoStates.cs rename to samples/Sample02.PassingParams/States/DemoStates.cs index 1a34232..e6b2e2d 100644 --- a/samples/Sample.Basics/States/DemoStates.cs +++ b/samples/Sample02.PassingParams/States/DemoStates.cs @@ -4,9 +4,9 @@ using System; using System.Threading.Tasks; using Lite.StateMachine; -using Sample.Basics.Models; +using Sample02.PassingParams.Models; -namespace Sample.Basics.States; +namespace Sample02.PassingParams.States; #pragma warning disable SA1649 // File name should match first type name #pragma warning disable SA1402 // File may only contain a single type diff --git a/samples/Sample.Basics/States/StateBase.cs b/samples/Sample02.PassingParams/States/StateBase.cs similarity index 93% rename from samples/Sample.Basics/States/StateBase.cs rename to samples/Sample02.PassingParams/States/StateBase.cs index 9716bdb..3670389 100644 --- a/samples/Sample.Basics/States/StateBase.cs +++ b/samples/Sample02.PassingParams/States/StateBase.cs @@ -5,7 +5,7 @@ using System.Threading.Tasks; using Lite.StateMachine; -namespace Sample.Basics.States; +namespace Sample02.PassingParams.States; public class StateBase : IState where TStateId : struct, Enum diff --git a/samples/Sample02.PassingParams/readme.md b/samples/Sample02.PassingParams/readme.md new file mode 100644 index 0000000..89ab781 --- /dev/null +++ b/samples/Sample02.PassingParams/readme.md @@ -0,0 +1,13 @@ +# Sample 2 - Passing Parameters + +This sample demonstrates how to pass the Context Parameters +(`context.Parameters`) using the `PropertyBag` class. + +```cs + var counter = 0; + var ctxProperties = new PropertyBag() + { + { ParameterType.Counter, counter }, // (int) + { ParameterType.LogOutput, logOutput }, // (bool) + }; +``` diff --git a/samples/Sample.Mk4/BasicDiStates.cs b/samples/Sample03.DependencyInjection/BasicDiStates.cs similarity index 100% rename from samples/Sample.Mk4/BasicDiStates.cs rename to samples/Sample03.DependencyInjection/BasicDiStates.cs diff --git a/samples/Sample.Mk4/CounterService.cs b/samples/Sample03.DependencyInjection/CounterService.cs similarity index 100% rename from samples/Sample.Mk4/CounterService.cs rename to samples/Sample03.DependencyInjection/CounterService.cs diff --git a/samples/Sample.Mk4/Program.cs b/samples/Sample03.DependencyInjection/Program.cs similarity index 65% rename from samples/Sample.Mk4/Program.cs rename to samples/Sample03.DependencyInjection/Program.cs index 88c0e8d..d4e8753 100644 --- a/samples/Sample.Mk4/Program.cs +++ b/samples/Sample03.DependencyInjection/Program.cs @@ -14,10 +14,10 @@ internal class Program { private static async Task Main() { - await TestFlatStatesAsync(); + await TestMicrosoftDependencyInjectionAsync(); } - private static async Task TestFlatStatesAsync() + private static async Task TestMicrosoftDependencyInjectionAsync() { // Assemble with Dependency Injection var services = new ServiceCollection() @@ -39,18 +39,19 @@ private static async Task TestFlatStatesAsync() var result = await machine.RunAsync(BasicStateId.State1); - ////Assert.IsNotNull(result); - ////AssertMachineNotNull(machine); + Console.WriteLine("\n\nPost Execution Validations:"); + Console.WriteLine("---------------------------"); var msgService = services.GetRequiredService(); - ////Assert.AreEqual(9, msgService.Counter1, "Message service should have 9 from the 3 states."); + Console.WriteLine($"* Message service Counter1: {msgService.Counter1} (expected 9)"); // Ensure all states are registered var enums = Enum.GetValues().Cast(); - ////Assert.AreEqual(enums.Count(), machine.States.Count()); - ////Assert.IsTrue(enums.All(k => machine.States.Contains(k))); + Console.WriteLine($"* State Machine Counts: {machine.States.Count()}. State Enum Count: {enums.Count()}"); + Console.WriteLine($"* All states registered: {enums.All(k => machine.States.Contains(k))}"); // Ensure they're registered in order - ////Assert.IsTrue(enums.SequenceEqual(machine.States), "States should be registered for execution in the same order as the defined enums, StateId 1 => 2 => 3."); + // Validates that States are registered for execution in the same order as the defined enums. StateId 1 => 2 => 3. + Console.WriteLine($"* State registered in order: {enums.SequenceEqual(machine.States)}"); } } diff --git a/samples/Sample.Mk4/Sample03.DependencyInjection.csproj b/samples/Sample03.DependencyInjection/Sample03.DependencyInjection.csproj similarity index 100% rename from samples/Sample.Mk4/Sample03.DependencyInjection.csproj rename to samples/Sample03.DependencyInjection/Sample03.DependencyInjection.csproj diff --git a/samples/Sample.Mk4/StateDiBase.cs b/samples/Sample03.DependencyInjection/StateDiBase.cs similarity index 100% rename from samples/Sample.Mk4/StateDiBase.cs rename to samples/Sample03.DependencyInjection/StateDiBase.cs diff --git a/samples/Sample03.DependencyInjection/readme.md b/samples/Sample03.DependencyInjection/readme.md new file mode 100644 index 0000000..325370d --- /dev/null +++ b/samples/Sample03.DependencyInjection/readme.md @@ -0,0 +1,6 @@ +# Sample 3 - State Machine with Dependency Injection + +This project demonstrates using Microsoft.Extensions.DependencyInjection to manage dependencies throughout state machine transitions. + +The sample includes a simple state machine that uses dependency injection to manage the `CounterService` required by the state machine. It increments a counter for each state's transition and elements of the state (`OnEntering`, `OnEnter`, `OnExit` and outputs the resuts to command line. + From 47110ab149fa06223ed8f86c72d3fdf074ae80d0 Mon Sep 17 00:00:00 2001 From: Damian Suess Date: Sun, 31 May 2026 15:10:04 -0400 Subject: [PATCH 5/7] unused --- .../Services/CounterService.cs | 48 ------------------- 1 file changed, 48 deletions(-) delete mode 100644 samples/Sample02.PassingParams/Services/CounterService.cs diff --git a/samples/Sample02.PassingParams/Services/CounterService.cs b/samples/Sample02.PassingParams/Services/CounterService.cs deleted file mode 100644 index d382759..0000000 --- a/samples/Sample02.PassingParams/Services/CounterService.cs +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright Xeno Innovations, Inc. 2025 -// See the LICENSE file in the project root for more information. - -using System.Collections.Generic; - -namespace Sample02.PassingParams.Services; - -[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1649:File name should match first type name", Justification = "Allowed for testing.")] -public interface ICounterService -{ - /// uses it as an automatic state transition counter. - /// - int Counter1 { get; set; } - - /// Gets or sets the user's custom counter. - int Counter2 { get; set; } - - /// Gets or sets the user's custom counter. - int Counter3 { get; set; } - - /// Gets a list of user's custom messages. - List Messages { get; } - - /// Add message to read-only list. - /// Message. - void AddMessage(string message); -} - -public class CounterService : ICounterService -{ - /// - public int Counter1 { get; set; } - - /// - public int Counter2 { get; set; } - - /// - public int Counter3 { get; set; } - - /// - public List Messages { get; } = []; - - /// - public void AddMessage(string message) => - Messages.Add(message); -} From bde29c51c60e41472ee7af00e1f199f505294310 Mon Sep 17 00:00:00 2001 From: Damian Suess Date: Sun, 31 May 2026 16:04:22 -0400 Subject: [PATCH 6/7] Basic state transition example application --- .editorconfig | 2 +- Lite.StateMachine.slnx | 1 + samples/Sample01.BasicStates/Program.cs | 21 +++ .../Sample01.BasicStates.csproj | 13 ++ .../Samples/BasicStateMachine.cs | 126 ++++++++++++++++++ .../Samples/CompositeStateMachine.cs | 12 ++ .../Sample01.BasicStates/Samples/StateBase.cs | 28 ++++ samples/Sample01.BasicStates/readme.md | 26 ++++ 8 files changed, 228 insertions(+), 1 deletion(-) create mode 100644 samples/Sample01.BasicStates/Program.cs create mode 100644 samples/Sample01.BasicStates/Sample01.BasicStates.csproj create mode 100644 samples/Sample01.BasicStates/Samples/BasicStateMachine.cs create mode 100644 samples/Sample01.BasicStates/Samples/CompositeStateMachine.cs create mode 100644 samples/Sample01.BasicStates/Samples/StateBase.cs create mode 100644 samples/Sample01.BasicStates/readme.md diff --git a/.editorconfig b/.editorconfig index 26350ca..fe2d82f 100644 --- a/.editorconfig +++ b/.editorconfig @@ -197,7 +197,7 @@ csharp_preferred_modifier_order = public,private,protected,internal,static,exter dotnet_separate_import_directive_groups = false dotnet_sort_system_directives_first = true # file_header_template = unset -file_header_template = Copyright Xeno Innovations, Inc. 2025\nSee the LICENSE file in the project root for more information. +file_header_template = Copyright Xeno Innovations, Inc. 2026\nSee the LICENSE file in the project root for more information. # this. and Me. preferences dotnet_style_qualification_for_event = false diff --git a/Lite.StateMachine.slnx b/Lite.StateMachine.slnx index 77454cf..f736c75 100644 --- a/Lite.StateMachine.slnx +++ b/Lite.StateMachine.slnx @@ -1,5 +1,6 @@ + diff --git a/samples/Sample01.BasicStates/Program.cs b/samples/Sample01.BasicStates/Program.cs new file mode 100644 index 0000000..c622e8d --- /dev/null +++ b/samples/Sample01.BasicStates/Program.cs @@ -0,0 +1,21 @@ +// Copyright Xeno Innovations, Inc. 2026 +// See the LICENSE file in the project root for more information. + +using System; +using System.Diagnostics; +using System.Threading.Tasks; + +namespace Sample01.BasicStates; + +internal class Program +{ + private static async Task Main(string[] args) + { + Console.WriteLine("Running Basic State Machine: Synchronous sample..."); + + Samples.BasicStateMachine.Run(); + + Console.WriteLine("\n\nRunning Basic State Machine: Asynchronous sample..."); + await Samples.BasicStateMachine.RunAsync(); + } +} diff --git a/samples/Sample01.BasicStates/Sample01.BasicStates.csproj b/samples/Sample01.BasicStates/Sample01.BasicStates.csproj new file mode 100644 index 0000000..68b478d --- /dev/null +++ b/samples/Sample01.BasicStates/Sample01.BasicStates.csproj @@ -0,0 +1,13 @@ + + + + Exe + net10.0 + + + + + + + + diff --git a/samples/Sample01.BasicStates/Samples/BasicStateMachine.cs b/samples/Sample01.BasicStates/Samples/BasicStateMachine.cs new file mode 100644 index 0000000..ab625d6 --- /dev/null +++ b/samples/Sample01.BasicStates/Samples/BasicStateMachine.cs @@ -0,0 +1,126 @@ +// Copyright Xeno Innovations, Inc. 2026 +// See the LICENSE file in the project root for more information. + +using System; +using System.Threading.Tasks; +using Lite.StateMachine; + +namespace Sample01.BasicStates.Samples; + +/// State definitions. +public enum BasicStateId +{ + State1, + State2, + State3, +} + +public class BasicStateMachine +{ + /// Example synchronous run method. + public static void Run() + { + var machine = new StateMachine(); + machine.RegisterState(BasicStateId.State1, BasicStateId.State2); + machine.RegisterState(BasicStateId.State2, BasicStateId.State3); + machine.RegisterState(BasicStateId.State3); + + // Non-async Start your engine! + var task = machine.RunAsync(BasicStateId.State1); + task.GetAwaiter().GetResult(); + } + + /// Example asynchronous run method. + /// Task. + public static async Task RunAsync() + { + var machine = new StateMachine(); + machine.RegisterState(BasicStateId.State1, BasicStateId.State2); + machine.RegisterState(BasicStateId.State2, BasicStateId.State3); + machine.RegisterState(BasicStateId.State3); + + // Async Example! + await machine.RunAsync(BasicStateId.State1); + } +} + +[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "ignore")] +public class State1 : IState +{ + public Task OnEntering(Context context) + { + Console.WriteLine($"[BasicState1][OnEntering]'"); + return Task.CompletedTask; + } + + public Task OnEnter(Context context) + { + // Set success from OnEnter to transition to the next state, State2. + context.NextState(Result.Success); + Console.WriteLine($"[BasicState1][OnEnter].OnSuccess goto: '{context.NextStates.OnSuccess}'"); + Console.WriteLine($"[BasicState1][OnEnter].OnError goto: '{context.NextStates.OnError}'"); + Console.WriteLine($"[BasicState1][OnEnter].OnFailure goto: '{context.NextStates.OnFailure}'"); + + return Task.CompletedTask; + } + + public Task OnExit(Context context) + { + Console.WriteLine($"[BasicState1][OnExit]'"); + return Task.CompletedTask; + } +} + +[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "ignore")] +public class State2 : IState +{ + public Task OnEntering(Context context) + { + Console.WriteLine($"[BasicState2][OnEntering]'"); + return Task.CompletedTask; + } + + public Task OnEnter(Context context) + { + // Set success from OnEnter to transition to the next state, State2. + context.NextState(Result.Success); + Console.WriteLine($"[BasicState2][OnEnter].OnSuccess goto: '{context.NextStates.OnSuccess}'"); + Console.WriteLine($"[BasicState2][OnEnter].OnError goto: '{context.NextStates.OnError}'"); + Console.WriteLine($"[BasicState2][OnEnter].OnFailure goto: '{context.NextStates.OnFailure}'"); + + return Task.CompletedTask; + } + + public Task OnExit(Context context) + { + Console.WriteLine($"[BasicState2][OnExit]'"); + return Task.CompletedTask; + } +} + +[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "ignore")] +public class State3 : IState +{ + public Task OnEntering(Context context) + { + Console.WriteLine($"[BasicState3][OnEntering]'"); + return Task.CompletedTask; + } + + public Task OnEnter(Context context) + { + // Set success from OnEnter to transition to the next state, State2. + context.NextState(Result.Success); + Console.WriteLine($"[BasicState3][OnEnter].OnSuccess goto: '{context.NextStates.OnSuccess}'"); + Console.WriteLine($"[BasicState3][OnEnter].OnError goto: '{context.NextStates.OnError}'"); + Console.WriteLine($"[BasicState3][OnEnter].OnFailure goto: '{context.NextStates.OnFailure}'"); + + return Task.CompletedTask; + } + + public Task OnExit(Context context) + { + Console.WriteLine($"[BasicState3][OnExit]'"); + return Task.CompletedTask; + } +} diff --git a/samples/Sample01.BasicStates/Samples/CompositeStateMachine.cs b/samples/Sample01.BasicStates/Samples/CompositeStateMachine.cs new file mode 100644 index 0000000..43fa880 --- /dev/null +++ b/samples/Sample01.BasicStates/Samples/CompositeStateMachine.cs @@ -0,0 +1,12 @@ +// Copyright Xeno Innovations, Inc. 2026 +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Sample01.BasicStates.Samples; + +internal class CompositeStateMachine +{ +} diff --git a/samples/Sample01.BasicStates/Samples/StateBase.cs b/samples/Sample01.BasicStates/Samples/StateBase.cs new file mode 100644 index 0000000..f683d48 --- /dev/null +++ b/samples/Sample01.BasicStates/Samples/StateBase.cs @@ -0,0 +1,28 @@ +// Copyright Xeno Innovations, Inc. 2025 +// See the LICENSE file in the project root for more information. + +using System; +using System.Threading.Tasks; +using Lite.StateMachine; + +namespace Sample01.BasicStates.Samples; + +public class StateBase : IState + where TStateId : struct, Enum +{ + public virtual Task OnEnter(Context context) + { + context.NextState(Result.Success); + return Task.CompletedTask; + } + + public virtual Task OnEntering(Context context) + { + return Task.CompletedTask; + } + + public virtual Task OnExit(Context context) + { + return Task.CompletedTask; + } +} diff --git a/samples/Sample01.BasicStates/readme.md b/samples/Sample01.BasicStates/readme.md new file mode 100644 index 0000000..e392622 --- /dev/null +++ b/samples/Sample01.BasicStates/readme.md @@ -0,0 +1,26 @@ +# Sample 01 - Basic States + +This project demonstrates the basics of Lite.StateMachine using a +.NET application, including how to manage and transition between different +states such as initialization, running, and shutdown. + +It serves as a foundational example for understanding the lifecycle of a +state-based application and how to handle state management effectively. + +There are 2 applications in this sample: + +* **Basic State Machine** - Flat state machine with no nested states, demonstrating simple state transitions. +* **Composite State Machine** - More complex state machine with nested states, showcasing hierarchical state management. + +## Basic State Machine + +The file `BasicStateMachine.cs` contains the implementation of a simple state +machine with three states: `State1`, `State2`, and `State3` for verbosity. + +Each of the states will be fully implemented with `OnEntering`, `OnEnter`, and `OnExit` transitions. + +## Composite State Machine + +The file `CompositeStateMachine.cs` contains a more complex state machine that includes nested states. +This example demonstrates how to manage hierarchical states, where a parent state can contain multiple +child states, allowing for more complex behavior and transitions. From 606d2cc0d9507e02f1c54edbf9c601653cffdd4e Mon Sep 17 00:00:00 2001 From: Damian Suess Date: Sun, 31 May 2026 17:05:34 -0400 Subject: [PATCH 7/7] Composite State Machine sample --- samples/Sample01.BasicStates/Program.cs | 16 +- .../BasicStateMachine.cs | 2 +- .../SampleComposite/CompositeStateMachine.cs | 220 ++++++++++++++++++ .../SampleComposite/StateBase.cs | 50 ++++ .../Samples/CompositeStateMachine.cs | 12 - .../Sample01.BasicStates/Samples/StateBase.cs | 28 --- samples/Sample01.BasicStates/readme.md | 12 + .../TestData/States/CompositeL3DiStates.cs | 4 +- 8 files changed, 297 insertions(+), 47 deletions(-) rename samples/Sample01.BasicStates/{Samples => SampleBasic}/BasicStateMachine.cs (98%) create mode 100644 samples/Sample01.BasicStates/SampleComposite/CompositeStateMachine.cs create mode 100644 samples/Sample01.BasicStates/SampleComposite/StateBase.cs delete mode 100644 samples/Sample01.BasicStates/Samples/CompositeStateMachine.cs delete mode 100644 samples/Sample01.BasicStates/Samples/StateBase.cs diff --git a/samples/Sample01.BasicStates/Program.cs b/samples/Sample01.BasicStates/Program.cs index c622e8d..c6445e5 100644 --- a/samples/Sample01.BasicStates/Program.cs +++ b/samples/Sample01.BasicStates/Program.cs @@ -2,7 +2,6 @@ // See the LICENSE file in the project root for more information. using System; -using System.Diagnostics; using System.Threading.Tasks; namespace Sample01.BasicStates; @@ -11,11 +10,18 @@ internal class Program { private static async Task Main(string[] args) { - Console.WriteLine("Running Basic State Machine: Synchronous sample..."); + Console.WriteLine("Regular State Sync: Starting..."); + SampleBasic.BasicStateMachine.Run(); + Console.WriteLine("Regular State Sync: DONE!"); - Samples.BasicStateMachine.Run(); + Console.WriteLine("\n\nRegular State Async: Starting..."); + await SampleBasic.BasicStateMachine.RunAsync(); + Console.WriteLine("Regular State Async: DONE!"); - Console.WriteLine("\n\nRunning Basic State Machine: Asynchronous sample..."); - await Samples.BasicStateMachine.RunAsync(); + Console.WriteLine("\n\n-=-=-=-=-=-\n\n"); + + Console.WriteLine("Composite Sync: Starting..."); + SampleComposite.CompositeStateMachine.Run(); + Console.WriteLine("Composite Sync: DONE!"); } } diff --git a/samples/Sample01.BasicStates/Samples/BasicStateMachine.cs b/samples/Sample01.BasicStates/SampleBasic/BasicStateMachine.cs similarity index 98% rename from samples/Sample01.BasicStates/Samples/BasicStateMachine.cs rename to samples/Sample01.BasicStates/SampleBasic/BasicStateMachine.cs index ab625d6..ecdfa8a 100644 --- a/samples/Sample01.BasicStates/Samples/BasicStateMachine.cs +++ b/samples/Sample01.BasicStates/SampleBasic/BasicStateMachine.cs @@ -5,7 +5,7 @@ using System.Threading.Tasks; using Lite.StateMachine; -namespace Sample01.BasicStates.Samples; +namespace Sample01.BasicStates.SampleBasic; /// State definitions. public enum BasicStateId diff --git a/samples/Sample01.BasicStates/SampleComposite/CompositeStateMachine.cs b/samples/Sample01.BasicStates/SampleComposite/CompositeStateMachine.cs new file mode 100644 index 0000000..0ea8ca2 --- /dev/null +++ b/samples/Sample01.BasicStates/SampleComposite/CompositeStateMachine.cs @@ -0,0 +1,220 @@ +// Copyright Xeno Innovations, Inc. 2026 +// See the LICENSE file in the project root for more information. + +using System; +using System.Threading.Tasks; +using Lite.StateMachine; + +namespace Sample01.BasicStates.SampleComposite; + +public enum CompositeL3 +{ + State1, + State2, + State2_Sub1, + State2_Sub2, + State2_Sub2_Sub1, + State2_Sub2_Sub2, + State2_Sub2_Sub3, + State2_Sub3, + State3, +} + +public class CompositeStateMachine +{ + /// Example synchronous run method. + public static void Run() + { + // Non-async Start your engine! + var machine = GenerateStateMachineL3(new StateMachine()); + var task = machine.RunAsync(CompositeL3.State1); + task.GetAwaiter().GetResult(); + } + + /// Example asynchronous run method. + /// Task. + public static async Task RunAsync() + { + // Async Example! + var machine = GenerateStateMachineL3(new StateMachine()); + await machine.RunAsync(CompositeL3.State1); + } + + private static StateMachine GenerateStateMachineL3(StateMachine machine) + { + machine + .RegisterState(CompositeL3.State1, CompositeL3.State2) + .RegisterComposite(CompositeL3.State2, initialChildStateId: CompositeL3.State2_Sub1, onSuccess: CompositeL3.State3) + .RegisterSubState(CompositeL3.State2_Sub1, parentStateId: CompositeL3.State2, onSuccess: CompositeL3.State2_Sub2) + .RegisterSubComposite(CompositeL3.State2_Sub2, parentStateId: CompositeL3.State2, initialChildStateId: CompositeL3.State2_Sub2_Sub1, onSuccess: CompositeL3.State2_Sub3) + .RegisterSubState(CompositeL3.State2_Sub2_Sub1, parentStateId: CompositeL3.State2_Sub2, onSuccess: CompositeL3.State2_Sub2_Sub2) + .RegisterSubState(CompositeL3.State2_Sub2_Sub2, parentStateId: CompositeL3.State2_Sub2, onSuccess: CompositeL3.State2_Sub2_Sub3) + .RegisterSubState(CompositeL3.State2_Sub2_Sub3, parentStateId: CompositeL3.State2_Sub2, onSuccess: null) + .RegisterSubState(CompositeL3.State2_Sub3, parentStateId: CompositeL3.State2, onSuccess: null) + .RegisterState(CompositeL3.State3, onSuccess: null); + + return machine; + } +} + +#pragma warning disable SA1124 // Do not use regions +#pragma warning disable SA1649 // File name should match first type name +#pragma warning disable SA1402 // File may only contain a single type +#pragma warning disable IDE0130 // Namespace does not match folder structure + +public class State1() + : StateBase() +{ + public override Task OnEnter(Context context) + { + Console.WriteLine($"[State1][OnEnter]"); + return base.OnEnter(context); + } +} + +/// Level-1: Composite. +public class State2() + : StateBase() +{ + #region CodeMaid - DoNotReorder + + public override Task OnEntering(Context context) + { + Console.WriteLine($"[State2][OnEntering]"); + return base.OnEntering(context); + } + + #endregion CodeMaid - DoNotReorder + + public override Task OnEnter(Context context) + { + // NOTE: + // We're a parent composite state. The 'context.NextState' is + // not used here as we will call it in the OnExit after all child + // states have completed. + Console.WriteLine($"[State2][OnEnter]**"); + return Task.CompletedTask; + } + + public override Task OnExit(Context context) + { + Console.WriteLine($"[State2][OnExit]**"); + + // NOTE: + // As this is a parent Composite state, we MUST call NextState to trigger + // the parent state to move to the next state to signify that the child states have completed. + context.NextState(Result.Success); + return base.OnExit(context); + } +} + +/// Sublevel-2: State. +public class State2_Sub1() + : StateBase() +{ + public override Task OnEnter(Context context) + { + Console.WriteLine($"[State2_Sub1][OnEnter]"); + return base.OnEnter(context); + } +} + +/// Sublevel-2: Composite. +public class State2_Sub2() + : StateBase() +{ + #region CodeMaid - DoNotReorder + + public override Task OnEntering(Context context) + { + Console.WriteLine($"[State2_Sub2][OnEntering]"); + return base.OnEntering(context); + } + + #endregion CodeMaid - DoNotReorder + + public override Task OnEnter(Context context) + { + Console.WriteLine($"[State2_Sub2][OnEnter]**"); + Console.WriteLine($"[State2_Sub2][OnEnter] CurrentStateId: {context.CurrentStateId} ({CompositeL3.State2_Sub2})"); + Console.WriteLine($"[State2_Sub2][OnEnter] PreviousStateId: {context.PreviousStateId} ({CompositeL3.State2_Sub1})"); + Console.WriteLine($"[State2_Sub2][OnEnter] LastChildStateId: {context.LastChildStateId} ({CompositeL3.State2_Sub2_Sub3})"); + + // NOTE: + // We're a parent composite state. The 'context.NextState' is + // not used here as we will call it in the OnExit after all child + // states have completed. + ////return base.OnEnter(context); + return Task.CompletedTask; + } + + public override Task OnExit(Context context) + { + Console.WriteLine($"[State2_Sub2][OnExit]"); + Console.WriteLine($"[State2_Sub2][OnExit] CurrentStateId: {context.CurrentStateId} ({CompositeL3.State2_Sub2})"); + Console.WriteLine($"[State2_Sub2][OnExit] PreviousStateId: {context.PreviousStateId} ({CompositeL3.State2_Sub1})"); + Console.WriteLine($"[State2_Sub2][OnExit] LastChildStateId: {context.LastChildStateId} ({CompositeL3.State2_Sub2_Sub3})"); + + // NOTE: + // As this is a parent Composite state, we MUST call NextState to trigger + // the parent state to move to the next state to signify that the child states have completed. + context.NextState(Result.Success); + return base.OnExit(context); + } +} + +/// Sublevel-3: State. +public class State2_Sub2_Sub1() + : StateBase() +{ + public override Task OnEnter(Context context) + { + Console.WriteLine($"[State2_Sub2_Sub1][OnEnter] (success)"); + return base.OnEnter(context); + } +} + +/// Sublevel-3: State. +/// NOTE: We are auto-succeeding and not populating OnEnter. +public class State2_Sub2_Sub2() + : StateBase() +{ +} + +/// Sublevel-3: Last State. +public class State2_Sub2_Sub3() + : StateBase() +{ + public override Task OnEnter(Context context) + { + Console.WriteLine($"[State2_Sub2_Sub3][OnEnter] (Success)"); + return base.OnEnter(context); + } +} + +/// Sublevel-2: Last State. +public class State2_Sub3() + : StateBase() +{ + public override Task OnEnter(Context context) + { + Console.WriteLine($"[State2_Sub3][OnEnter]"); + return base.OnEnter(context); + } +} + +/// Make sure not child-created context is there. +public class State3() + : StateBase() +{ + public override Task OnEnter(Context context) + { + Console.WriteLine($"[State3][OnEnter]"); + return base.OnEnter(context); + } +} + +#pragma warning restore IDE0130 // Namespace does not match folder structure +#pragma warning restore SA1649 // File name should match first type name +#pragma warning restore SA1402 // File may only contain a single type +#pragma warning restore SA1124 // Do not use regions diff --git a/samples/Sample01.BasicStates/SampleComposite/StateBase.cs b/samples/Sample01.BasicStates/SampleComposite/StateBase.cs new file mode 100644 index 0000000..b1e9bee --- /dev/null +++ b/samples/Sample01.BasicStates/SampleComposite/StateBase.cs @@ -0,0 +1,50 @@ +// Copyright Xeno Innovations, Inc. 2025 +// See the LICENSE file in the project root for more information. + +using System; +using System.Threading.Tasks; +using Lite.StateMachine; + +namespace Sample01.BasicStates.SampleComposite; + +/// +/// This is a base state class that can be used for all states in the sample. +/// It provides default implementations of the IState methods, which can be +/// overridden by derived classes as needed. This allows for code reuse and +/// consistency across all states in the state machine. +/// +/// Note, that if 'OnEnter' is not provided, it will AUTO-SUCCEED and transition +/// to the next state (if any). This is a convenient default behavior for states +/// that do not have any specific logic to execute upon entering, but it can be +/// overridden if you need to perform some actions before transitioning to the +/// next state. +/// +/// The type of the state class. +/// The type of the state identifier. +public class StateBase : IState + where TStateId : struct, Enum +{ + #region Suppress CodeMaid Method Sorting + + [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1124:Do not use regions", Justification = "ignore")] + public virtual Task OnEntering(Context context) + { + ////Console.WriteLine("[StateBase][OnEntering]"); + return Task.CompletedTask; + } + + #endregion + + public virtual Task OnEnter(Context context) + { + ////Console.WriteLine("[StateBase][OnEnter]"); + context.NextState(Result.Success); + return Task.CompletedTask; + } + + public virtual Task OnExit(Context context) + { + ////Console.WriteLine("[StateBase][OnExit]"); + return Task.CompletedTask; + } +} diff --git a/samples/Sample01.BasicStates/Samples/CompositeStateMachine.cs b/samples/Sample01.BasicStates/Samples/CompositeStateMachine.cs deleted file mode 100644 index 43fa880..0000000 --- a/samples/Sample01.BasicStates/Samples/CompositeStateMachine.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright Xeno Innovations, Inc. 2026 -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections.Generic; -using System.Text; - -namespace Sample01.BasicStates.Samples; - -internal class CompositeStateMachine -{ -} diff --git a/samples/Sample01.BasicStates/Samples/StateBase.cs b/samples/Sample01.BasicStates/Samples/StateBase.cs deleted file mode 100644 index f683d48..0000000 --- a/samples/Sample01.BasicStates/Samples/StateBase.cs +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright Xeno Innovations, Inc. 2025 -// See the LICENSE file in the project root for more information. - -using System; -using System.Threading.Tasks; -using Lite.StateMachine; - -namespace Sample01.BasicStates.Samples; - -public class StateBase : IState - where TStateId : struct, Enum -{ - public virtual Task OnEnter(Context context) - { - context.NextState(Result.Success); - return Task.CompletedTask; - } - - public virtual Task OnEntering(Context context) - { - return Task.CompletedTask; - } - - public virtual Task OnExit(Context context) - { - return Task.CompletedTask; - } -} diff --git a/samples/Sample01.BasicStates/readme.md b/samples/Sample01.BasicStates/readme.md index e392622..24ea458 100644 --- a/samples/Sample01.BasicStates/readme.md +++ b/samples/Sample01.BasicStates/readme.md @@ -10,7 +10,9 @@ state-based application and how to handle state management effectively. There are 2 applications in this sample: * **Basic State Machine** - Flat state machine with no nested states, demonstrating simple state transitions. + * NOTE: _**You MUST**_ handle the `context.NextState` property during the `OnEnter` transition to signify that the state has completed successfully. * **Composite State Machine** - More complex state machine with nested states, showcasing hierarchical state management. + * NOTE: _**You MUST**_ handle the `context.NextState` property during the `OnExit` transition to signify that the child states have completed successfully. ## Basic State Machine @@ -24,3 +26,13 @@ Each of the states will be fully implemented with `OnEntering`, `OnEnter`, and ` The file `CompositeStateMachine.cs` contains a more complex state machine that includes nested states. This example demonstrates how to manage hierarchical states, where a parent state can contain multiple child states, allowing for more complex behavior and transitions. + +### Handling 'context.NextState' + +NOTE: + +Composite states can have multiple child states, MUST handle the `context.NextState` property +during the `OnExit` transition to signify that the child states have completed successfully. + +This is a crucial step for _Composite States_ and differs from a regular "State" who performs +the `context.NextState` assignment during the `OnEnter` transition. diff --git a/source/Lite.StateMachine.Tests/TestData/States/CompositeL3DiStates.cs b/source/Lite.StateMachine.Tests/TestData/States/CompositeL3DiStates.cs index c6321f3..9abd767 100644 --- a/source/Lite.StateMachine.Tests/TestData/States/CompositeL3DiStates.cs +++ b/source/Lite.StateMachine.Tests/TestData/States/CompositeL3DiStates.cs @@ -11,9 +11,11 @@ #pragma warning disable SA1402 // File may only contain a single type #pragma warning disable IDE0130 // Namespace does not match folder structure -/// namespace Lite.StateMachine.Tests.TestData.States.CompositeL3DiStates; +/// +/// Added "CompositeL3DiStates" to namespace to reduce class naming collisions. +/// public class CommonDiStateBase(IMessageService msg, ILogger logger) : StateDiBase(msg, logger) where TStateId : struct, Enum