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
14 changes: 9 additions & 5 deletions readme-nuget.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ The Lite State Machine is designed for vertical scaling. Meaning, it can be used

||
|-|
| Copyright 2021-2025 Xeno Innovations, Inc. (_dba, Suess Labs_) |
| Copyright 2021-2026 Xeno Innovations, Inc. (_DBA:, Suess Labs_) |
| Created by: Damian Suess |
| Date: 2021-06-07 |
| Date: 2021-06-07 (_inception 2016_) |

## Package Releases

Expand Down Expand Up @@ -92,10 +92,9 @@ var uml = machine.ExportUml(includeSubmachines: true);
```cs
using Lite.StateMachine;

var ctxProperties = new PropertyBag() { { "CounterKey", 0 } };

// Note the use of generics '<TStateClass>' to strongly-type the state machine
var machine = new StateMachine<CompositeL1StateId>()
.AddContext(new() { { ParameterType.Counter, 999 } });
.RegisterState<Composite_State1>(CompositeL1StateId.State1, CompositeL1StateId.State2)

.RegisterComposite<Composite_State2>(
Expand Down Expand Up @@ -126,6 +125,9 @@ public class Composite_State1() : BaseState
{
public override Task OnEnter(Context<CompositeL1StateId> context)
{
var cnt = context.ParameterAsInt(ParameterType.Counter);
var blank = context.ParameterAsBool(ParameterType.DummyBool);

context.NextState(Result.Ok);
return Task.CompletedTask;
}
Expand All @@ -152,7 +154,9 @@ public class Composite_State2_Sub1() : BaseState
{
public override Task OnEnter(Context<CompositeL1StateId> context)
{
context.Parameters.Add("ParameterSubStateEntered", SUCCESS);
// Safely add/update key-value in Context
context.Parameters.SafeAdd("StringBasedParamKey", SUCCESS);

context.NextState(Result.Ok);
return Task.CompletedTask;
}
Expand Down
14 changes: 9 additions & 5 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ The Lite State Machine is designed for vertical scaling. Meaning, it can be used

||
|-|
| Copyright 2021-2025 Xeno Innovations, Inc. (_dba, Suess Labs_) |
| Copyright 2021-2026 Xeno Innovations, Inc. (_DBA: Suess Labs_) |
| Created by: Damian Suess |
| Date: 2021-06-07 |
| Date: 2021-06-07 (_inception 2016_) |

## Package Releases

Expand Down Expand Up @@ -94,10 +94,9 @@ var uml = machine.ExportUml(includeSubmachines: true);
```cs
using Lite.StateMachine;

var ctxProperties = new PropertyBag() { { "CounterKey", 0 } };

// Note the use of generics '<TStateClass>' to strongly-type the state machine
var machine = new StateMachine<CompositeL1StateId>()
.AddContext(new() { { ParameterType.Counter, 999 } });
.RegisterState<Composite_State1>(CompositeL1StateId.State1, CompositeL1StateId.State2)

.RegisterComposite<Composite_State2>(
Expand Down Expand Up @@ -128,6 +127,9 @@ public class Composite_State1() : BaseState
{
public override Task OnEnter(Context<CompositeL1StateId> context)
{
var cnt = context.ParameterAsInt(ParameterType.Counter);
var blank = context.ParameterAsBool(ParameterType.DummyBool);

context.NextState(Result.Ok);
return Task.CompletedTask;
}
Expand All @@ -154,7 +156,9 @@ public class Composite_State2_Sub1() : BaseState
{
public override Task OnEnter(Context<CompositeL1StateId> context)
{
context.Parameters.Add("ParameterSubStateEntered", SUCCESS);
// Safely add/update key-value in Context
context.Parameters.SafeAdd("StringBasedParamKey", SUCCESS);

context.NextState(Result.Ok);
return Task.CompletedTask;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,26 +33,26 @@ public void BasicStateGlobalSetup()
public async Task BasicStatesRunsAsync()
{
var maxCounter = CyclesBeforeExit;
PropertyBag parameters = new()
_machine.Context.Parameters = new()
{
{ ParameterType.MaxCounter, maxCounter },
{ ParameterType.Counter, 0 },
};

await _machine.RunAsync(BasicStateId.State1, parameters);
await _machine.RunAsync(BasicStateId.State1);
}

[Benchmark]
public void BasicStatesRunsSync()
{
var maxCounter = CyclesBeforeExit;
PropertyBag parameters = new()
_machine.Context.Parameters = new()
{
{ ParameterType.MaxCounter, maxCounter },
{ ParameterType.Counter, 0 },
};

_machine.RunAsync(BasicStateId.State1, parameters)
_machine.RunAsync(BasicStateId.State1)
.GetAwaiter()
.GetResult();
}
Expand Down
76 changes: 22 additions & 54 deletions source/Lite.StateMachine.Tests/StateTests/BasicStateTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Lite.StateMachine.Tests.TestData;
using Lite.StateMachine.Tests.TestData.States;
Expand All @@ -18,25 +17,26 @@ public class BasicStateTests : TestBase
public void Basic_RegisterState_Executes123_SuccessTest()
{
// Assemble
var ctxProperties = new PropertyBag()
{
{ ParameterType.Counter, 0 },
{ ParameterType.TestExecutionOrder, true },
};

var machine = new StateMachine<BasicStateId>();
machine.RegisterState<BasicState1>(BasicStateId.State1, BasicStateId.State2);
machine.RegisterState<BasicState2>(BasicStateId.State2, BasicStateId.State3);
machine.RegisterState<BasicState3>(BasicStateId.State3);
machine.AddContext(new()
{
{ ParameterType.Counter, 0 },
{ ParameterType.TestExecutionOrder, true },
});

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

// Assert Results
AssertMachineNotNull(machine);

Assert.AreEqual(0, machine.Context.Parameters.Count);
Assert.IsNotEmpty(machine.Context.Parameters);
Assert.AreEqual(9, machine.Context.ParameterAsInt(ParameterType.Counter));
Assert.IsTrue(machine.Context.ParameterAsBool(ParameterType.TestExecutionOrder));

// Ensure all states are registered
var enums = Enum.GetValues<BasicStateId>().Cast<BasicStateId>();
Expand All @@ -53,19 +53,19 @@ public void Basic_RegisterState_Executes123_SuccessTest()
public async Task Basic_RegisterState_Executes123_SuccessTestAsync()
{
// Assemble
var ctxProperties = new PropertyBag()
{
{ ParameterType.Counter, 0 },
{ ParameterType.TestExecutionOrder, true },
};

var machine = new StateMachine<BasicStateId>();
machine.RegisterState<BasicState1>(BasicStateId.State1, BasicStateId.State2);
machine.RegisterState<BasicState2>(BasicStateId.State2, BasicStateId.State3);
machine.RegisterState<BasicState3>(BasicStateId.State3);

machine.Context.Parameters = new()
{
{ ParameterType.Counter, 0 },
{ ParameterType.TestExecutionOrder, true },
};

// Act - Start your engine!
await machine.RunAsync(BasicStateId.State1, ctxProperties);
await machine.RunAsync(BasicStateId.State1);

// Assert Results
AssertMachineNotNull(machine);
Expand All @@ -89,8 +89,8 @@ public async Task Basic_RegisterState_Executes132_SuccessTestAsync()
machine.RegisterState<BasicState2>(BasicStateId.State2);

// Act - Start your engine!
var ctxProperties = new PropertyBag() { { ParameterType.TestExecutionOrder, false } };
await machine.RunAsync(BasicStateId.State1, ctxProperties);
machine.Context.Parameters = new() { { ParameterType.TestExecutionOrder, false } };
await machine.RunAsync(BasicStateId.State1);

// Assert Results
AssertMachineNotNull(machine);
Expand Down Expand Up @@ -121,7 +121,8 @@ public async Task Basic_RegisterState_Fluent_SuccessTestAsync()
.RegisterState<BasicState1>(BasicStateId.State1, BasicStateId.State2)
.RegisterState<BasicState2>(BasicStateId.State2, BasicStateId.State3)
.RegisterState<BasicState3>(BasicStateId.State3)
.RunAsync(BasicStateId.State1, ctxProperties, cancellationToken: TestContext.CancellationToken);
.AddContext(ctxProperties)
.RunAsync(BasicStateId.State1, cancellationToken: TestContext.CancellationToken);

// Assert Results
AssertMachineNotNull(machine);
Expand Down Expand Up @@ -182,9 +183,10 @@ public async Task HungState_Proceeds_DefaultStateTimeout_SuccessTestAsync()
machine.RegisterState<BasicState1>(BasicStateId.State1, BasicStateId.State2);
machine.RegisterState<BasicState2>(BasicStateId.State2, BasicStateId.State3);
machine.RegisterState<BasicState3>(BasicStateId.State3);
machine.AddContext(ctxProperties);

// Act - Start your engine!
await machine.RunAsync(BasicStateId.State1, ctxProperties);
await machine.RunAsync(BasicStateId.State1);

// Assert Results
AssertMachineNotNull(machine);
Expand All @@ -194,38 +196,4 @@ public async Task HungState_Proceeds_DefaultStateTimeout_SuccessTestAsync()
Assert.HasCount(enums.Count(), machine.States);
Assert.IsTrue(enums.All(k => machine.States.Contains(k)));
}

/// <summary>Context is returned at the end.</summary>
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
[TestMethod]
[Ignore("vNext - Currently StateMachine destroys context after run completes.")]
public async Task RegisterState_ReturnsContext_SuccessTestAsync()
{
// Assemble
var ctxProperties = new PropertyBag()
{
{ ParameterType.Counter, 0 },
{ ParameterType.TestExecutionOrder, true },
};

var machine = new StateMachine<BasicStateId>();
machine.RegisterState<BasicState1>(BasicStateId.State1, BasicStateId.State2);
machine.RegisterState<BasicState2>(BasicStateId.State2, BasicStateId.State3);
machine.RegisterState<BasicState3>(BasicStateId.State3);

// Act - Start your engine!
var task = machine.RunAsync(BasicStateId.State1, ctxProperties);
await task; // Non async method: task.GetAwaiter().GetResult();

// Assert Results
AssertMachineNotNull(machine);

var ctxFinalParams = machine.Context.Parameters;
Assert.IsNotNull(ctxFinalParams);
Assert.AreNotEqual(0, ctxFinalParams.Count);

// NOTE: This should be 9 because each state has 3 hooks that increment the counter
// TODO (2025-12-22 DS): Fix last state not calling OnExit.
////Assert.AreEqual(9, ctxFinalParams[ParameterType.Counter]);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ public async Task BasicState_Override_Executes_SuccessAsync()
};

machine
.AddContext(ctxProperties)
.RegisterState<State1>(StateId.State1, StateId.State2)
.RegisterComposite<State2>(StateId.State2, initialChildStateId: StateId.State2_Sub1, onSuccess: StateId.State3)
.RegisterSubState<State2_Sub1>(StateId.State2_Sub1, parentStateId: StateId.State2, onSuccess: StateId.State2_Sub2)
Expand Down Expand Up @@ -81,7 +82,7 @@ public async Task BasicState_Override_Executes_SuccessAsync()
});

// Act - Start your engine!
await machine.RunAsync(StateId.State1, ctxProperties, null, TestContext.CancellationToken);
await machine.RunAsync(StateId.State1, TestContext.CancellationToken);

// Assert Results
AssertMachineNotNull(machine);
Expand Down Expand Up @@ -131,7 +132,7 @@ public async Task CancelsInfiniteStateMachineTestAsync()
events.Publish(new CancelResponse());
});

var result = await machine.RunAsync(StateId.State1, null, null, cts.Token);
var result = await machine.RunAsync(StateId.State1, cts.Token);

// Assert
Assert.IsNotNull(result);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -274,9 +274,10 @@ public async Task Level3_PreviousStateId_SuccessTestAsync()

var ctxProperties = new PropertyBag() { { ParameterType.TestExecutionOrder, true } };
var machine = GenerateStateMachineL3(new StateMachine<CompositeL3>(factory));
machine.AddContext(ctxProperties);

// Act
await machine.RunAsync(CompositeL3.State1, ctxProperties, null, TestContext.CancellationToken);
await machine.RunAsync(CompositeL3.State1, TestContext.CancellationToken);

// Assert
AssertMachineNotNull(machine);
Expand Down
5 changes: 2 additions & 3 deletions source/Lite.StateMachine.Tests/StateTests/ContextTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,14 @@ private enum ParameterType
public void Basic_RegisterState_Executes123_SuccessTest()
{
// Assemble
var ctxProperties = new PropertyBag() { { "KeyString_ValueInt", 99 } };

var machine = new StateMachine<CtxStateId>();
machine.RegisterState<CtxState1>(CtxStateId.State1, CtxStateId.State2);
machine.RegisterState<CtxState2>(CtxStateId.State2, CtxStateId.State3);
machine.RegisterState<CtxState3>(CtxStateId.State3);
machine.AddContext(new() { { "KeyString_ValueInt", 99 } });

// Act - Non async Start your engine!
var task = machine.RunAsync(CtxStateId.State1, ctxProperties);
var task = machine.RunAsync(CtxStateId.State1);
task.GetAwaiter().GetResult();

// Assert Results
Expand Down
20 changes: 10 additions & 10 deletions source/Lite.StateMachine.Tests/StateTests/CustomStateTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,20 +29,19 @@ public async Task BasicState_Override_Executes_SuccessAsync(bool skipState3)
var msgService = services.GetRequiredService<IMessageService>();
Func<Type, object?> factory = t => ActivatorUtilities.CreateInstance(services, t);

var ctxProperties = new PropertyBag()
{
{ ParameterType.Counter, 0 },
{ ParameterType.TestExitEarly, skipState3 },
};

var machine = new StateMachine<CustomStateId>(factory, null, isContextPersistent: true);
machine.RegisterState<State1>(CustomStateId.State1, CustomStateId.State2_Dummy);
machine.RegisterState<State2Dummy>(CustomStateId.State2_Dummy, CustomStateId.State3);
machine.RegisterState<State2Success>(CustomStateId.State2_Success, CustomStateId.State3);
machine.RegisterState<State3>(CustomStateId.State3);
machine.AddContext(new()
{
{ ParameterType.Counter, 0 },
{ ParameterType.TestExitEarly, skipState3 },
});

// Act - Start your engine!
await machine.RunAsync(CustomStateId.State1, ctxProperties, cancellationToken: TestContext.CancellationToken);
await machine.RunAsync(CustomStateId.State1, cancellationToken: TestContext.CancellationToken);

// Assert Results
AssertMachineNotNull(machine);
Expand Down Expand Up @@ -72,14 +71,15 @@ public async Task BasicState_Overrides_ThrowUnregisteredException_Async()
};

var machine = new StateMachine<CustomStateId>(factory, null, isContextPersistent: true);
machine.AddContext(ctxProperties);
machine.RegisterState<State1>(CustomStateId.State1, CustomStateId.State2_Dummy);
machine.RegisterState<State2Dummy>(CustomStateId.State2_Dummy, CustomStateId.State3);
machine.RegisterState<State2Success>(CustomStateId.State2_Success, CustomStateId.State3);
machine.RegisterState<State3>(CustomStateId.State3);

// Act - Start your engine!
await Assert.ThrowsExactlyAsync<UnregisteredStateTransitionException>(()
=> machine.RunAsync(CustomStateId.State1, ctxProperties, null, TestContext.CancellationToken));
=> machine.RunAsync(CustomStateId.State1, TestContext.CancellationToken));

// Assert Results
AssertMachineNotNull(machine);
Expand Down Expand Up @@ -110,7 +110,7 @@ public async Task Composite_Override_Executes_SuccessAsync(bool skipSubState2)
};

var machine = new StateMachine<CustomStateId>(factory, null, isContextPersistent: true);

machine.AddContext(ctxProperties);
machine.RegisterState<State1>(CustomStateId.State1, CustomStateId.State2_Dummy);
machine.RegisterState<State2Dummy>(CustomStateId.State2_Dummy, CustomStateId.State3);
machine.RegisterComposite<State2Success>(CustomStateId.State2_Success, CustomStateId.State2_Sub1, CustomStateId.State3);
Expand All @@ -120,7 +120,7 @@ public async Task Composite_Override_Executes_SuccessAsync(bool skipSubState2)
machine.RegisterState<State3>(CustomStateId.State3);

// Act - Start your engine!
await machine.RunAsync(CustomStateId.State1, ctxProperties, cancellationToken: TestContext.CancellationToken);
await machine.RunAsync(CustomStateId.State1, cancellationToken: TestContext.CancellationToken);

// Assert Results
AssertMachineNotNull(machine);
Expand Down
2 changes: 1 addition & 1 deletion source/Lite.StateMachine.Tests/StateTests/DiMsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,7 @@ public async Task RegisterState_MsDi_EventAggregatorOnly_SuccessTestAsync()
});

// Act - Run the state machine and send messages
await machine.RunAsync(CompositeMsgStateId.Entry, null, null, CancellationToken.None);
await machine.RunAsync(CompositeMsgStateId.Entry, CancellationToken.None);

Console.WriteLine("MS.DI workflow finished.");

Expand Down
Loading