Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<LangVersion>latest</LangVersion>
<EnforceCodeStyleInBuild>True</EnforceCodeStyleInBuild>

<AssemblyVersion>2.3.1</AssemblyVersion>
<AssemblyVersion>2.4.1</AssemblyVersion>

<BaseOutputPath>../../output/$(MSBuildProjectName)</BaseOutputPath>
<Deterministic>true</Deterministic>
Expand Down
1 change: 1 addition & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
<!--<CentralPackageTransitivePinningEnabled>true</CentralPackageTransitivePinningEnabled>-->
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="DryIoc" Version="5.4.3" />
<PackageVersion Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="5.0.0-1.25277.114" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="10.0.1" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.1" />
Expand Down
9 changes: 7 additions & 2 deletions Lite.StateMachine.slnx
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
<Solution>
<Folder Name="/Samples/">
<Project Path="samples/Sample.Basics/Sample.Basics.csproj" Id="88e1e0a9-34b8-4610-a2f4-bfb36e0d1b43" />
<Project Path="samples/Sample.Mk4/Sample.Mk4.csproj" Id="f7481ec8-d3be-4acd-9eae-a4723c09e245" />
<Project Path="samples/Sample01.BasicStates/Sample01.BasicStates.csproj" Id="07016e87-d49b-4ece-9d24-e062eaa02dc4" />
<Project Path="samples/Sample02.PassingParams/Sample02.PassingParams.csproj" Id="88e1e0a9-34b8-4610-a2f4-bfb36e0d1b43" />
<Project Path="samples/Sample03.DependencyInjection/Sample03.DependencyInjection.csproj" Id="f7481ec8-d3be-4acd-9eae-a4723c09e245" />
<Project Path="samples/Sample04.ResultTransitions/Sample04.ResultTransitions.csproj" Id="c1a4fb9a-6b1b-43e7-aeb1-2c07024b15d5" />
<Project Path="samples/Sample05.CommandStates/Sample05.CommandStates.csproj" Id="cd6b33b8-ac4a-4ec7-a3a8-5cdf14c03847" />
<Project Path="samples/Sample06.PassingEvents/Sample06.PassingEvents.csproj" Id="01a1903a-9da0-4cd2-a811-356822569e03" />
<Project Path="samples/Sample10.UsingEventIPC/Sample10.UsingEventIPC.csproj" Id="f89325bb-17ff-4db9-9647-92bad4e2f974" />
</Folder>
<Folder Name="/Solution Items/">
<File Path=".github/workflows/build-github.yml" />
Expand Down
Binary file modified docs/icon-128x128.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
File renamed without changes.
Binary file added docs/images/icon-128x128_MK2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes.
Binary file added docs/images/nuget-state-machine-icon-badge.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/nuget-state-machine-icon-chip.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/nuget-state-machine-icon-minimal.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/nuget-state-machine-icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
90 changes: 62 additions & 28 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,60 @@ 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 |

_Lite.StateMachine is the fastest and lowest allocation_


## Features

_Thread safe and most customizations can be set on-the-fly!_

* AOT Friendly - Ahead-of-Time compilation, _No Reflection, no Linq, etc._
* Passing parameters between state transitions via `Context`
* Dependency Injection (DI) friendly
* Asynchronous states
* Types of States:
* **Basic Linear State** (`BaseState`)
* **Composite** States (`CompositeState`)
* Hieratical / Nested Sub-states
* Similar to Actor/Director model
* **Command States** with optional Timeout (`CommandState`)
* Uses internal Event Aggregator for sending/receiving messages
* Allows users to hook to external messaging services (TCP/IP, RabbitMQ, DBus, etc.)
* State Transition Triggers:
* Transitions are triggered by setting the context's next state result:
* On Success: `context.NextState(Result.Ok);`
* On Error: `context.NextState(Result.Error);`
* On Failure: : `context.NextState(Result.Failure);`
* State Handlers:
* `OnEntering` - Initial entry of the state
* `OnEnter` - Resting (idle) place for state.
* `OnExit` - (Optional) Thrown during transitioning. Used for housekeeping or exiting activity.
* `OnMessage` (Optional)
* Must ensure that code has exited `OnMessage` before going to the next state.
* `OnTimeout` - (Optional) Thrown when the state is auto-transitioning due to timeout exceeded
* Transition has knowledge of the `PreviousState`, `CurrentStateId`, and `NextState`
* Shared **Context**:
* `Parameters` - _For passing data between states._
* `Errors` - _For passing error information between states._
* `CurrentStateId`
* `NextStates`
* `EventAggregator`
* `LastChildResult`, `LastChildStateId` _(for composite states)_
* Customizable (on-the-fly):
* State Timeout (_per state or default for all states_)
* Overridable Next States! `OnSuccess`, `OnError`, or `OnFailure`

## Usage

Create a _state machine_ by defining the states, transitions, and shared context.
Expand All @@ -28,6 +82,9 @@ You can define the state machine using either the fluent design pattern or stand

### Basic State

![](docs/images/nuget-state-machine-icon-isometric.png)
The basic state exapmle transitions from `State1 -> State2 -> State3`.

```cs
// That's it! Just create the state machine, register states, and run it.
var machine = await new StateMachine<StateId>()
Expand Down Expand Up @@ -57,7 +114,7 @@ public class BasicState1() : BaseState
{
public async Task OnEnter(Context<BasicStateId> context)
{
await Task.Yield(); // Some async work here...
await Task.Yield(); // Your async work here...
context.NextState(Result.Ok);
}
}
Expand All @@ -66,8 +123,9 @@ public class BasicState2() : BaseState
{
public Task OnEnter(Context<StateId> context)
{
// Notice, we did not async/await this method
context.NextState(Result.Ok);
return Task.CompletedTask; // Notice, we did not async/await this method
return Task.CompletedTask;
}
}

Expand All @@ -91,6 +149,8 @@ var uml = machine.ExportUml(includeSubmachines: true);

### Composite States

The following uses the fluent design pattern style, stacking the `.RegisterXXX(...)` methonds ontop of each other with the `RunAsync(...)` method occurring at the end.

```cs
using Lite.StateMachine;

Expand Down Expand Up @@ -183,30 +243,4 @@ public class Composite_State3() : BaseState
}
```

## Features

* AOT Friendly - _No Reflection, no Linq, etc._
* Passing parameters between state transitions via `Context`
* Types of States
* **Basic Linear State** (`BaseState`)
* **Composite** States (`CompositeState`)
* Hieratical / Nested Sub-states
* Similar to Actor/Director model
* **Command States** with optional Timeout (`CommandState`)
* Uses internal Event Aggregator for sending/receiving messages
* Allows users to hook to external messaging services (TCP/IP, RabbitMQ, DBus, etc.)
* State Transition Triggers
* Transitions are triggered by setting the context's next state result:
* On Success: `context.NextState(Result.Ok);`
* On Error: `context.NextState(Result.Error);`
* On Failure: : `context.NextState(Result.Failure);`
* State Handlers
* `OnEntering` - Initial entry of the state
* `OnEnter` - Resting (idle) place for state.
* `OnExit` - (Optional) Thrown during transitioning. Used for housekeeping or exiting activity.
* `OnMessage` (Optional)
* Must ensure that code has exited `OnMessage` before going to the next state.
* `OnTimeout` - (Optional) Thrown when the state is auto-transitioning due to timeout exceeded
* Transition has knowledge of the `PreviousState` and `NextState`

## References
14 changes: 0 additions & 14 deletions samples/Sample.Mk4/Program.cs

This file was deleted.

27 changes: 27 additions & 0 deletions samples/Sample01.BasicStates/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright Xeno Innovations, Inc. 2026
// See the LICENSE file in the project root for more information.

using System;
using System.Threading.Tasks;

namespace Sample01.BasicStates;

internal class Program
{
private static async Task Main(string[] args)
{
Console.WriteLine("Regular State Sync: Starting...");
SampleBasic.BasicStateMachine.Run();
Console.WriteLine("Regular State Sync: DONE!");

Console.WriteLine("\n\nRegular State Async: Starting...");
await SampleBasic.BasicStateMachine.RunAsync();
Console.WriteLine("Regular State Async: DONE!");

Console.WriteLine("\n\n-=-=-=-=-=-\n\n");

Console.WriteLine("Composite Sync: Starting...");
SampleComposite.CompositeStateMachine.Run();
Console.WriteLine("Composite Sync: DONE!");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@
<TargetFramework>net10.0</TargetFramework>
</PropertyGroup>


<ItemGroup>
<ProjectReference Include="..\..\source\Lite.StateMachine\Lite.StateMachine.csproj" />
</ItemGroup>

</Project>
126 changes: 126 additions & 0 deletions samples/Sample01.BasicStates/SampleBasic/BasicStateMachine.cs
Original file line number Diff line number Diff line change
@@ -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.SampleBasic;

/// <summary>State definitions.</summary>
public enum BasicStateId
{
State1,
State2,
State3,
}

public class BasicStateMachine
{
/// <summary>Example synchronous run method.</summary>
public static void Run()
{
var machine = new StateMachine<BasicStateId>();
machine.RegisterState<State1>(BasicStateId.State1, BasicStateId.State2);
machine.RegisterState<State2>(BasicStateId.State2, BasicStateId.State3);
machine.RegisterState<State3>(BasicStateId.State3);

// Non-async Start your engine!
var task = machine.RunAsync(BasicStateId.State1);
task.GetAwaiter().GetResult();
}

/// <summary>Example asynchronous run method.</summary>
/// <returns>Task.</returns>
public static async Task RunAsync()
{
var machine = new StateMachine<BasicStateId>();
machine.RegisterState<State1>(BasicStateId.State1, BasicStateId.State2);
machine.RegisterState<State2>(BasicStateId.State2, BasicStateId.State3);
machine.RegisterState<State3>(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<BasicStateId>
{
public Task OnEntering(Context<BasicStateId> context)
{
Console.WriteLine($"[BasicState1][OnEntering]'");
return Task.CompletedTask;
}

public Task OnEnter(Context<BasicStateId> 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<BasicStateId> 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<BasicStateId>
{
public Task OnEntering(Context<BasicStateId> context)
{
Console.WriteLine($"[BasicState2][OnEntering]'");
return Task.CompletedTask;
}

public Task OnEnter(Context<BasicStateId> 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<BasicStateId> 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<BasicStateId>
{
public Task OnEntering(Context<BasicStateId> context)
{
Console.WriteLine($"[BasicState3][OnEntering]'");
return Task.CompletedTask;
}

public Task OnEnter(Context<BasicStateId> 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<BasicStateId> context)
{
Console.WriteLine($"[BasicState3][OnExit]'");
return Task.CompletedTask;
}
}
Loading
Loading