steipete /
Aspects
Delightful, simple library for aspect oriented programming in Objective-C and Swift.
Loading repository data…
dotnet-state-machine / repository
A simple library for creating state machines in C# code
Create state machines and lightweight state machine-based workflows directly in .NET code:
var phoneCall = new StateMachine<State, Trigger>(State.OffHook);
phoneCall.Configure(State.OffHook)
.Permit(Trigger.CallDialled, State.Ringing);
phoneCall.Configure(State.Connected)
.OnEntry(t => StartCallTimer())
.OnExit(t => StopCallTimer())
.InternalTransition(Trigger.MuteMicrophone, t => OnMute())
.InternalTransition(Trigger.UnmuteMicrophone, t => OnUnmute())
.InternalTransition<int>(_setVolumeTrigger, (volume, t) => OnSetVolume(volume))
.Permit(Trigger.LeftMessage, State.OffHook)
.Permit(Trigger.PlacedOnHold, State.OnHold);
// ...
phoneCall.Fire(Trigger.CallDialled);
Assert.AreEqual(State.Ringing, phoneCall.State);
This project, as well as the example above, was inspired by Simple State Machine (Archived).
Most standard state machine constructs are supported:
Some useful extensions are also provided:
In the example below, the OnHold state is a substate of the Connected state. This means that an OnHold call is still connected.
phoneCall.Configure(State.OnHold)
.SubstateOf(State.Connected)
.Permit(Trigger.TakenOffHold, State.Connected)
.Permit(Trigger.PhoneHurledAgainstWall, State.PhoneDestroyed);
In addition to the StateMachine.State property, which will report the precise current state, an IsInState(State) method is provided. IsInState(State) will take substates into account, so that if the example above was in the OnHold state, IsInState(State.Connected) would also evaluate to true.
In the example, the StartCallTimer() method will be executed when a call is connected. The StopCallTimer() will be executed when call completes (by either hanging up or hurling the phone against the wall.)
The call can move between the Connected and OnHold states without the StartCallTimer() and StopCallTimer() methods being called repeatedly because the OnHold state is a substate of the Connected state.
Entry/Exit action handlers can be supplied with a parameter of type Transition that describes the trigger, source and destination states.
Sometimes a trigger needs to be handled, but the state shouldn't change. This is an internal transition. Use InternalTransition for this.
A substate can be marked as initial state. When the state machine enters the super state it will also automatically enter the substate. This can be configured like this:
sm.Configure(State.B)
.InitialTransition(State.C);
sm.Configure(State.C)
.SubstateOf(State.B);
Due to Stateless' internal structure, it does not know when it is "started". This makes it impossible to handle an initial transition in the traditional way. It is possible to work around this limitation by adding a dummy initial state, and then use Activate() to "start" the state machine.
sm.Configure(InitialState)
.OnActivate(() => sm.Fire(LetsGo))
.Permit(LetsGo, StateA)
Stateless is designed to be embedded in various application models. For example, some ORMs place requirements upon where mapped data may be stored, and UI frameworks often require state to be stored in special "bindable" properties. To this end, the StateMachine constructor can accept function arguments that will be used to read and write the state values:
var stateMachine = new StateMachine<State, Trigger>(
() => myState.Value,
s => myState.Value = s);
In this example the state machine will use the myState object for state storage.
Another example can be found in the JsonExample solution, located in the example folder.
It might be necessary to perform some code before storing the object state, and likewise when restoring the object state. Use Deactivate and Activate for this. Activation should only be called once before normal operation starts, and once before state storage.
The state machine can provide a list of the triggers that can be successfully fired within the current state via the StateMachine.PermittedTriggers property. Use StateMachine.GetInfo() to retrieve information about the state configuration.
The state machine will choose between multiple transitions based on guard clauses, e.g.:
phoneCall.Configure(State.OffHook)
.PermitIf(Trigger.CallDialled, State.Ringing, () => IsValidNumber)
.PermitIf(Trigger.CallDialled, State.Beeping, () => !IsValidNumber);
phoneCall.Configure(State.OffHook)
.PermitIfAsync(Trigger.CallDialled, State.Ringing, async () => await IsValidNumber())
.PermitIfAsync(Trigger.CallDialled, State.Beeping, async () => !await IsValidNumber());
Guard clauses within a state must be mutually exclusive (multiple guard clauses cannot be valid at the same time.) Substates can override transitions by respecifying them, however substates cannot disallow transitions that are allowed by the superstate.
The guard clauses will be evaluated whenever a trigger is fired. Guards should therefore be made side effect free.
Strongly-typed parameters can be assigned to triggers:
var assignTrigger = stateMachine.SetTriggerParameters<string>(Trigger.Assign);
stateMachine.Configure(State.Assigned)
.OnEntryFrom(assignTrigger, email => OnAssigned(email));
stateMachine.Fire(assignTrigger, "joe@example.com");
Trigger parameters can be used to dynamically select the destination state using the PermitDynamic() configuration method.
In Stateless, firing a trigger that does not have an allowed transition associated with it will cause an exception to be thrown. This ensures that all transitions are explicitly defined, preventing unintended state changes.
To ignore triggers within certain states, use the Ignore(TTrigger) directive:
phoneCall.Configure(State.Connected)
.Ignore(Trigger.CallDialled);
Alternatively, a state can be marked reentrant. A reentrant state is one that can transition back into itself. In such cases, the state's exit and entry actions will be executed, providing a way to handle events that require the state to reset or reinitialize.
stateMachine.Configure(State.Assigned)
.PermitReentry(Trigger.Assigned)
.OnEntry(() => SendEmailToAssignee());
A state can also have a conditional reentrant, both synchronous and asynchronous.
stateMachine.Configure(State.Assigned)
.PermitReentryIf(Trigger.Assigned, () => ShouldSendEmailAgain())
.OnEntry(() => SendEmailToAssignee());
stateMachine.Configure(State.Assigned)
.PermitReentryIfAsync(Trigger.Assigned, async () => await ShouldSendEmailAgain())
.OnEntry(() => SendEmailToAssignee());
By default, triggers must be ignored explicitly. To override Stateless's default behaviour of throwing an exception when an unhandled trigger is fired, configure the state machine using the OnUnhandledTrigger method:
stateMachine.OnUnhandledTrigger((state, trigger) => { });
Dynamic state transitions allow the destination state to be determined at runtime based on trigger parameters or other logic.
stateMachine.Configure(State.Start)
.PermitDynamic(Trigger.CheckScore, () => score < 10 ? State.LowScore : State.HighScore);
When a dynamic transition results in the same state as the current state, it effectively becomes a reentrant transition, causing the state's exit and entry actions to execute. This can be useful for scenarios where the state needs to refresh or reset based on certain triggers.
stateMachine.Configure(State.Waiting)
.OnEntry(() => Console.WriteLine($"Elapsed time: {elapsed} seconds..."))
.PermitDynamic(Trigger.CheckStatus, () => ready ? State.Done : State.Waiting);
Stateless supports 2 types of state machine events:
// Synchronously
stateMachine.OnTransitioned((transition) => { });
// Asynchronously
stateMachine.OnTransitionedAsync((transition) => { return Task.FromResult(0); });
This event will be invoked every time the state machine changes state.
// Synchronously
stateMachine.OnTransitionCompleted((transition) => { });
// Asynchronously
stateMachine.OnTransitionCompletedAsync((transition) => { return Task.FromResult(0); });
This event will be invoked at the very end of the trigger handling, after the last entry action has been executed.
In addition to this, Stateless also provides you with the ability to unregister from state machine events in 3 ways.
// Keep a reference to the synchronous callback action we want to unregister later.
Action transitionCallbackAction = (transition) => { };
stateMachine.OnTransitionedUnregister(transitionCallbackAction);
This method will unregister the specified action callback from the transition event.
// Keep a reference to the asynchronous callback function we want to unregister later.
Func<Transition, Task> transitionAsyncCallback => (transition) => { return Task.FromResult(0); };
stateMachine.OnTransitionedAsyncUnregister(transitionAsyncCallback);
This method will unregister the specified async function callback from the transition event.
// Keep a reference to the synchronous callback action we want to unregister later.
Action transitionCompletedCallbackAction = (transition) => { });
stateMachine.OnTransitionCompletedUnregister(transitionCompletedCallbackAction);
This method will unregister the specified action callback from the transition completed event.
// Keep a reference to to the asynchronous callback function we want to unregister later.
Func<Transition, Task> transitionCompletedAsyncCallback => (transition) => { return Task.FromResult(0); });
stateMachine.OnTransitionCompletedAsyncUnregister(transitionCompletedAsyncCallback);
This method
Selected from shared topics, language and repository description—not editorial ratings.
steipete /
Delightful, simple library for aspect oriented programming in Objective-C and Swift.
bblanchon /
📟 JSON library for Arduino and embedded C++. Simple and efficient.
eidheim /
A very simple, fast, multithreaded, platform independent HTTP and HTTPS server and client library implemented using C++11 and Boost.Asio. Created to be an easy way to make REST resources available from C++ applications.
felixguendling /
Cista is a simple, high-performance, zero-copy C++ serialization & reflection library.
alibaba /
A collection of modern C++ libraries, include coro_http, coro_rpc, compile-time reflection, struct_pack, struct_json, struct_xml, struct_pb, easylog, async_simple etc.
fungos /
cr.h: A Simple C Hot Reload Header-only Library