Lightweight State Machine
$ dotnet add package Lombda.StateMachineA powerful and flexible state machine library for .NET 8.0 that supports concurrent execution, complex state transitions, and type-safe operations. This library provides both generic and non-generic state machine implementations with extensive event handling and logging capabilities.
Add the project reference to your solution or include the source files directly in your project.
States essentially transforms the Input into the Output
Where FooState : BaseState<InputType, OutputType>
Invoke(InputType input) Must Return the Output Type (Strongly Typed)
You can only Transition to a state where the Output of the current state is the Input to the next state
class ConvertStringToIntState : BaseState<string, int>
{
public override async Task<int> Invoke(string input)
{
return int.Parse(this.Input)
}
}
You can build pipelines of states and let the agent transition between them based on the results.
//Where string is Input and int is Output
StateMachine<string, int> stateMachine = new();
//Set start state with string Input
stateMachine.SetEntryState(inputState);
//Set state where output is
stateMachine.SetOutputState(resultState);
//Return list of Output objects from State
//List because machine might generate more than 1 output depending on flow
List<int> stateResults = await stateMachine.Run("3");---
//AllowsParallelTransitions = true Allows state Outputs to transition to all states that meet the criteria
ConvertStringToIntState inputState = new() { AllowsParallelTransitions = true };
IntPlus3State state3 = new();
IntPlus4State state4 = new();
//CombineInput = true only does 1 execution reguardless of # of Inputs
//Handle all of the Inputs
SummingState summingState = new() { CombineInput = true };
ConvertIntToStringState resultState = new();
//should happen in parallel and get result
inputState.AddTransition(state3);
inputState.AddTransition(state4);
//summing State will get both results next tick
state3.AddTransition(summingState);
state4.AddTransition(summingState);
//Will sum all inputs
summingState.AddTransition(resultState);
//Convert result and End the State Machine
resultState.AddTransition(new ExitState());
//Create Input & Output State Machine
StateMachine<string, string> stateMachine = new();
//Define Entry and Output States
stateMachine.SetEntryState(inputState);
stateMachine.SetOutputState(resultState);
//Run the StateMachine
List<string?> stateResults = await stateMachine.Run("3");
ConvertStringToIntState inputState = new();
ConvertStringToIntState resultState = new();
//Input State will convert string to int
inputState.AddTransition((result) => result.ToString(), resultState);
resultState.AddTransition(new ExitState());
StateMachine<string, int?> stateMachine = new();
stateMachine.SetEntryState(inputState);
stateMachine.SetOutputState(resultState);
var stateResults = await stateMachine.Run(["3","2","4"]);
Console.WriteLine($"State Results: {string.Join(", ", stateResults.Select(r => string.Join(", ", r)))}");
Assert.IsTrue(stateResults[0].Contains(3));
public class StateMachine
{
// Properties
public int MaxThreads { get; set; }
public bool RecordSteps { get; set; }
public bool IsFinished { get; set; }
public ConcurrentDictionary<string, object> RuntimeProperties { get; set; }
public List<StateProcess> ActiveProcesses { get; }
public CancellationToken CancelToken { get; }
// Events
public event Action OnBegin;
public event Action OnTick;
public event Action<StateProcess>? OnStateEntered;
public event Action<BaseState>? OnStateExited;
public event Action<StateProcess>? OnStateInvoked;
public event Action? FinishedTriggered;
public event Action? CancellationTriggered;
public event Action<string>? VerboseLog;
// Methods
public async Task Run(BaseState runStartState, object? input = null);
public void Stop();
public void Finish();
public void ResetRun();
}
public class StateMachine<TInput, TOutput> : StateMachine
{
// Properties
public List<TOutput>? Results { get; }
public BaseState StartState { get; set; }
public BaseState ResultState { get; set; }
// Methods
public async Task<List<TOutput?>> Run(TInput input);
public async Task<List<List<TOutput?>>> Run(TInput[] inputs);
public void SetEntryState(BaseState startState);
public void SetOutputState(BaseState resultState);
}
When contributing to this project:
This project is available under the terms specified in the project license.