Binstate 1.0.4
See the version list below for details.
dotnet add package Binstate --version 1.0.4
NuGet\Install-Package Binstate -Version 1.0.4
<PackageReference Include="Binstate" Version="1.0.4" />
paket add Binstate --version 1.0.4
#r "nuget: Binstate, 1.0.4"
// Install Binstate as a Cake Addin #addin nuget:?package=Binstate&version=1.0.4 // Install Binstate as a Cake Tool #tool nuget:?package=Binstate&version=1.0.4
Binstate
Simple but yet powerful state machine. Thread safe. Supports async methods. Supports hierarchically nested states.
Features
Thread safe
The state machine is fully thread safe and allows calling any method from any thread.
Control on what thread enter and exit actions are executed
Binstate don't use it's own thread to execute transitions.
Raise(event)
will execute exit action of the current state and enter action of the new state on the current thread.
If enter action is blocking Raise
will block until enter action finishes.
RaiseAsync(event)
uses Task.Run
to execute transition.
It gives an application full control on threading model of the state machine.
Support async methods
Supports using async methods as enter action of the state. Binstate guarantees that async enter action will finis before calling exit action of the current state and enter action of the new state. Async method should return Task
, async void
methods are not supported.
Conditional transitions using C# not DSL
Instead of introducing conditional transition into state machine DSL like
.If(CallDialled, Ringing, () => IsValidNumber)
.If(CallDialled, Beeping, () => !IsValidNumber);
or
.If(CheckOverload).Goto(MovingUp)
.Otherwise().Execute(AnnounceOverload)
Binstate allows using C#
.AddTransition(CallDialed, () => IsValidNumber ? Ringing : Beeping)
.AddTransition(GoUp, () =>
{
if(CheckOverload) return MovingUp;
AnnounceOverload();
return null; // no transition will be executed
});
Safe checking if state machine still in the state
The current state of the state machine is not exposed. No knowledge which state to check - less errors.
not TState CurrentState{ get; }
but bool InMyState {get;}
private static Task PlayMusic(IStateMachine<State> stateMachine)
{
return Task.Run(() =>
{
while (stateMachine.InMyState)
{
// play music
}
});
}
Changing state from enter action
private async Task TrackGame(IStateMachine<State> stateMachine, string opponentName)
{
while (stateMachine.InMyState)
{
// track game
if(IsGameFinished())
stateMachine.RaiseAsync(GameFinished);
}
}
Enter actions with parameters
builder
.DefineState(WaitingForGame)
.OnEnter(WaitForGame)
.AddTransition<string>(GameStarted, TrackingGame)
...
builder
.DefineState(TrackingGame)
.OnEnter<string>(TrackGame)
...
Hierarchically nested states
Supports hierarchically nested states, see "Elevator" example.
Examples
Telephone call
var builder = new Builder<State, Event>();
builder
.DefineState(OffHook)
.AddTransition(CallDialed, Ringing);
builder
.DefineState(Ringing)
.AddTransition(HungUp, OffHook)
.AddTransition(CallConnected, Connected);
builder
.DefineState(Connected)
.AddTransition(LeftMessage, OffHook)
.AddTransition(HungUp, OffHook)
.AddTransition(PlacedOnHold, OnHold);
builder
.DefineState(OnHold)
.OnEnter(PlayMusic)
.AddTransition(TakenOffHold, Connected)
.AddTransition(HungUp, OffHook)
.AddTransition(PhoneHurledAgainstWall, PhoneDestroyed);
builder
.DefineState(PhoneDestroyed);
var stateMachine = builder.Build(OffHook);
// ...
stateMachine.RaiseAsync(CallDialed);
Elevator
public class Elevator
{
private readonly StateMachine<States, Events> _elevator;
public Elevator()
{
var builder = new Builder<States, Events>();
builder
.DefineState(States.Healthy)
.AddTransition(Events.Error, States.Error);
builder
.DefineState(States.Error)
.AddTransition(Events.Reset, States.Healthy)
.AllowReentrancy(Events.Error);
builder
.DefineState(States.OnFloor).AsSubstateOf(States.Healthy)
.OnEnter(AnnounceFloor)
.OnExit(() => Beep(2))
.AddTransition(Events.CloseDoor, States.DoorClosed)
.AddTransition(Events.OpenDoor, States.DoorOpen)
.AddTransition(Events.GoUp, States.MovingUp)
.AddTransition(Events.GoDown, States.MovingDown);
builder
.DefineState(States.Moving).AsSubstateOf(States.Healthy)
.OnEnter(CheckOverload)
.AddTransition(Events.Stop, States.OnFloor);
builder.DefineState(States.MovingUp).AsSubstateOf(States.Moving);
builder.DefineState(States.MovingDown).AsSubstateOf(States.Moving);
builder.DefineState(States.DoorClosed).AsSubstateOf(States.OnFloor);
builder.DefineState(States.DoorOpen).AsSubstateOf(States.OnFloor);
_elevator = builder.Build(States.OnFloor);
// ready to work
}
public void GoToUpperLevel()
{
_elevator.Raise(Events.CloseDoor);
_elevator.Raise(Events.GoUp);
_elevator.Raise(Events.OpenDoor);
}
public void GoToLowerLevel()
{
_elevator.Raise(Events.CloseDoor);
_elevator.Raise(Events.GoDown);
_elevator.Raise(Events.OpenDoor);
}
public void Error()
{
_elevator.Raise(Events.Error);
}
public void Stop()
{
_elevator.Raise(Events.Stop);
}
public void Reset()
{
_elevator.Raise(Events.Reset);
}
private void AnnounceFloor(IStateMachine<Events> stateMachine)
{
/* announce floor number */
}
private void AnnounceOverload()
{
/* announce overload */
}
private void Beep(int times)
{
/* beep */
}
private void CheckOverload(IStateMachine<Events> stateMachine)
{
if (IsOverloaded())
{
AnnounceOverload();
stateMachine.RaiseAsync(Events.Stop);
}
}
private bool IsOverloaded() => false;
private enum States
{
None,
Healthy,
OnFloor,
Moving,
MovingUp,
MovingDown,
DoorOpen,
DoorClosed,
Error
}
private enum Events
{
GoUp,
GoDown,
OpenDoor,
CloseDoor,
Stop,
Error,
Reset
}
}
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. |
.NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
.NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
.NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
MonoAndroid | monoandroid was computed. |
MonoMac | monomac was computed. |
MonoTouch | monotouch was computed. |
Tizen | tizen40 was computed. tizen60 was computed. |
Xamarin.iOS | xamarinios was computed. |
Xamarin.Mac | xamarinmac was computed. |
Xamarin.TVOS | xamarintvos was computed. |
Xamarin.WatchOS | xamarinwatchos was computed. |
This package has no dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
Version | Downloads | Last updated |
---|---|---|
2.0.2 | 1,064 | 12/10/2021 |
1.1.18 | 561 | 4/3/2021 |
1.0.27 | 501 | 10/27/2020 |
1.0.26 | 428 | 10/26/2020 |
1.0.25 | 449 | 10/4/2020 |
1.0.24 | 470 | 9/28/2020 |
1.0.23 | 615 | 9/27/2020 |
1.0.21 | 468 | 9/27/2020 |
1.0.20 | 591 | 9/26/2020 |
1.0.19 | 451 | 9/24/2020 |
1.0.5 | 487 | 4/20/2020 |
1.0.4 | 489 | 4/17/2020 |
1.0.2 | 493 | 4/14/2020 |