Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/prasannavl/LiquidState
Efficient asynchronous and synchronous state machines for .NET
https://github.com/prasannavl/LiquidState
async c-sharp state-machine
Last synced: 16 days ago
JSON representation
Efficient asynchronous and synchronous state machines for .NET
- Host: GitHub
- URL: https://github.com/prasannavl/LiquidState
- Owner: prasannavl
- License: apache-2.0
- Created: 2014-11-26T23:34:40.000Z (almost 10 years ago)
- Default Branch: master
- Last Pushed: 2020-10-03T05:13:56.000Z (about 4 years ago)
- Last Synced: 2024-10-26T20:59:43.486Z (17 days ago)
- Topics: async, c-sharp, state-machine
- Language: C#
- Homepage:
- Size: 512 KB
- Stars: 241
- Watchers: 21
- Forks: 29
- Open Issues: 6
-
Metadata Files:
- Readme: README.md
- License: LICENSE-APACHE
- Code of conduct: CODE_OF_CONDUCT.md
Awesome Lists containing this project
README
LiquidState
====Efficient state machines for .NET with both synchronous and asynchronous support.
Heavily inspired by the excellent state machine library [**Stateless**](https://github.com/nblumhardt/stateless) by
**Nicholas Blumhardt.**[![NuGet badge](https://buildstats.info/nuget/LiquidState)](https://www.nuget.org/packages/LiquidState)
Installation
----**NuGet:**
> Install-Package LiquidState
**Supported Platforms:**
> .NETPlatform 1.0 (Formerly PCL259 profile - Supports .NETCore, .NETDesktop, Xamarin and Mono)Highlights
----- Zero heap allocations during the machine execution - GC friendly and high-performance. (Awaitable machines still incur the async/await
costs).
- Fully supports async/await methods everywhere => `OnEntry`, `OnExit`, during trigger, and even trigger conditions.
- Builds a linked object graph internally during configuration making it a much faster and more efficient implementation than regular dictionary based implementations.
- Both synchronous, and asynchronous machines with full support for `async-await`.
- `MoveToState`, to move freely between states, without triggers.
- `PermitDynamic` to support selection of states dynamically on-the-fly.
- `Diagnostics` in-built to check for validity of triggers, and currently available triggers.[**Release Notes**](https://github.com/prasannavl/LiquidState/blob/master/ReleaseNotes.md)
How To Use
---You only ever create machines with the `StateMachineFactory` static class. This is the factory for both configurations and the machines. The different types of machines given above are automatically chosen based on the parameters specified from the factory.
**Step 1:** Create a configuration:
```c#
var config = StateMachineFactory.CreateConfiguration();
```or for awaitable, or async machine:
```c#
var config = StateMachineFactory.CreateAwaitableConfiguration();
```**Step 2:** Setup the machine configurations using the fluent API.
```c#
config.ForState(State.Off)
.OnEntry(() => Console.WriteLine("OnEntry of Off"))
.OnExit(() => Console.WriteLine("OnExit of Off"))
.PermitReentry(Trigger.TurnOn)
.Permit(Trigger.Ring, State.Ringing,
() => { Console.WriteLine("Attempting to ring"); })
.Permit(Trigger.Connect, State.Connected,
() => { Console.WriteLine("Connecting"); });var connectTriggerWithParameter =
config.SetTriggerParameter(Trigger.Connect);config.ForState(State.Ringing)
.OnEntry(() => Console.WriteLine("OnEntry of Ringing"))
.OnExit(() => Console.WriteLine("OnExit of Ringing"))
.Permit(connectTriggerWithParameter, State.Connected,
name => {
Console.WriteLine("Attempting to connect to {0}", name);
})
.Permit(Trigger.Talk, State.Talking,
() => { Console.WriteLine("Attempting to talk"); });
```**Step 3:** Create the machine with the configuration:
```c#
var machine = StateMachineFactory.Create(State.Ringing, config);
```**Step 4:** Use them!
* Using triggers:
>
```c#
machine.Fire(Trigger.On);
```
or
```
await machine.FireAsync(Trigger.On);
```* Using direct states:
>
```c#
machine.MoveToState(State.Ringing);
```
or its async variant.* To use parameterized triggers, have a look at the example below.
Examples
---A synchronous machine example:
```c#
var config = StateMachineFactory.CreateConfiguration();config.ForState(State.Off)
.OnEntry(() => Console.WriteLine("OnEntry of Off"))
.OnExit(() => Console.WriteLine("OnExit of Off"))
.PermitReentry(Trigger.TurnOn)
.Permit(Trigger.Ring, State.Ringing,
() => { Console.WriteLine("Attempting to ring"); })
.Permit(Trigger.Connect, State.Connected,
() => { Console.WriteLine("Connecting"); });var connectTriggerWithParameter =
config.SetTriggerParameter(Trigger.Connect);config.ForState(State.Ringing)
.OnEntry(() => Console.WriteLine("OnEntry of Ringing"))
.OnExit(() => Console.WriteLine("OnExit of Ringing"))
.Permit(connectTriggerWithParameter, State.Connected,
name => { Console.WriteLine("Attempting to connect to {0}", name); })
.Permit(Trigger.Talk, State.Talking,
() => { Console.WriteLine("Attempting to talk"); });config.ForState(State.Connected)
.OnEntry(() => Console.WriteLine("AOnEntry of Connected"))
.OnExit(() => Console.WriteLine("AOnExit of Connected"))
.PermitReentry(Trigger.Connect)
.Permit(Trigger.Talk, State.Talking,
() => { Console.WriteLine("Attempting to talk"); })
.Permit(Trigger.TurnOn, State.Off,
() => { Console.WriteLine("Turning off"); });config.ForState(State.Talking)
.OnEntry(() => Console.WriteLine("OnEntry of Talking"))
.OnExit(() => Console.WriteLine("OnExit of Talking"))
.Permit(Trigger.TurnOn, State.Off,
() => { Console.WriteLine("Turning off"); })
.Permit(Trigger.Ring, State.Ringing,
() => { Console.WriteLine("Attempting to ring"); });var machine = StateMachineFactory.Create(State.Ringing, config);
machine.Fire(Trigger.Talk);
machine.Fire(Trigger.Ring);
machine.Fire(connectTriggerWithParameter, "John Doe");
```Now, let's take the same dumb, and terrible example, but now do it **asynchronously**!
(Mix and match synchronous code when you don't need asynchrony to avoid the costs.)```c#
// Note the "CreateAwaitableConfiguration"
var config = StateMachineFactory.CreateAwaitableConfiguration();config.ForState(State.Off)
.OnEntry(async () => Console.WriteLine("OnEntry of Off"))
.OnExit(async () => Console.WriteLine("OnExit of Off"))
.PermitReentry(Trigger.TurnOn)
.Permit(Trigger.Ring, State.Ringing,
async () => { Console.WriteLine("Attempting to ring"); })
.Permit(Trigger.Connect, State.Connected,
async () => { Console.WriteLine("Connecting"); });var connectTriggerWithParameter =
config.SetTriggerParameter(Trigger.Connect);config.ForState(State.Ringing)
.OnEntry(() => Console.WriteLine("OnEntry of Ringing"))
.OnExit(() => Console.WriteLine("OnExit of Ringing"))
.Permit(connectTriggerWithParameter, State.Connected,
name => { Console.WriteLine("Attempting to connect to {0}", name); })
.Permit(Trigger.Talk, State.Talking,
() => { Console.WriteLine("Attempting to talk"); });config.ForState(State.Connected)
.OnEntry(async () => Console.WriteLine("AOnEntry of Connected"))
.OnExit(async () => Console.WriteLine("AOnExit of Connected"))
.PermitReentry(Trigger.Connect)
.Permit(Trigger.Talk, State.Talking,
async () => { Console.WriteLine("Attempting to talk"); })
.Permit(Trigger.TurnOn, State.Off,
async () => { Console.WriteLine("Turning off"); });config.ForState(State.Talking)
.OnEntry(() => Console.WriteLine("OnEntry of Talking"))
.OnExit(() => Console.WriteLine("OnExit of Talking"))
.Permit(Trigger.TurnOn, State.Off,
() => { Console.WriteLine("Turning off"); })
.Permit(Trigger.Ring, State.Ringing,
() => { Console.WriteLine("Attempting to ring"); });var machine = StateMachineFactory.Create(State.Ringing, config);
await machine.FireAsync(Trigger.Talk);
await machine.FireAsync(Trigger.Ring);
await machine.FireAsync(connectTriggerWithParameter, "John Doe");
```Core APIs
---**IStateMachineCore:**
```c#
public interface IStateMachineCore
{
TState CurrentState { get; }
bool IsEnabled { get; }
void Pause();
void Resume();event Action> UnhandledTrigger;
event Action> InvalidState;
event Action> TransitionStarted;
event Action>
TransitionExecuted;
}
```**Synchronous:**
```c#
public interface IStateMachine
: IStateMachineCore
{
IStateMachineDiagnostics Diagnostics { get; }void Fire(
ParameterizedTrigger parameterizedTrigger,
TArgument argument);
void Fire(TTrigger trigger);
void MoveToState(TState state,
StateTransitionOption option = StateTransitionOption.Default);
}
```**Awaitable:**
```c#
public interface IAwaitableStateMachine
: IStateMachineCore
{
IAwaitableStateMachineDiagnostics Diagnostics { get; }Task FireAsync(
ParameterizedTrigger parameterizedTrigger,
TArgument argument);
Task FireAsync(TTrigger trigger);
Task MoveToStateAsync(TState state,
StateTransitionOption option = StateTransitionOption.Default);
}
```In-built Machines
---* **Common Roots:**
- **AbstractStateMachineCore** - All machines derive from this. This mostly just provide the common boiler plates.* **Synchronous:**
- **RawStateMachine** - Direct, fully functional raw state machine. No protective abstractions or overheads. Typically only used as a base for other machines.
- **GuardedStateMachine** - Lock-free protection over raw state machine. Minimal statemachine for independent usage.
- **BlockingStateMachine** - Synchronized using Monitor and blocks until all of the requests are completed one by one. Order is not guaranteed, when parallel triggers are fired due to the nature of locks.* **Awaitable:**
- **RawAwaitableStateMachine** - Direct, fully functional raw state machine. No protective abstractions or overheads. Typically only used as a base for other machines.
- **GuardedAwaitableStateMachine** - Lock-free protection over raw state machine. Minimal statemachine for independent usage.
- **ScheduledAwaitableStateMachine** - Schedules the machine implementation to an external TaskScheduler. Thread-safety, order, and synchronization are the responsibility of the scheduler.
- **QueuedAwaitableStateMachine** - A lock-free implementation of a fully asynchronous queued statemachine. Order is guaranteed.Notes:
- Awaitable state machines are a superset of asynchronous machines. All async machines are awaitable, but the opposite `may or may not` be true.
- Most of the above machines come with both their own sealed classes as well as `Base` classes, for extending them.Dynamic Triggers
---A simple implementation of the dynamic trigger is a part of the sample.
For more information or if you want to understand in detail the choices leading up to the design, please have a look at:
https://github.com/prasannavl/LiquidState/pull/20, and https://github.com/prasannavl/LiquidState/pull/7Support
----Please use the GitHub issue tracker [here](https://github.com/prasannavl/LiquidState/issues) if you'd like to report problems or discuss features. As always, do a preliminary search in the issue tracker before opening new ones - (*Tip:* include pull requests, closed, and open issues: *[Exhaustive search](https://github.com/prasannavl/LiquidState/issues?q=)* ).
Credits
---
Thanks to [JetBrains](https://www.jetbrains.com) for the OSS license of Resharper Ultimate.Proudly developed using:
License
---This project is licensed under either of the following, at your choice:
* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or [https://www.apache.org/licenses/LICENSE-2.0](https://www.apache.org/licenses/LICENSE-2.0))
* GPL 3.0 license ([LICENSE-GPL](LICENSE-GPL) or [https://opensource.org/licenses/GPL-3.0](https://opensource.org/licenses/GPL-3.0))Code of Conduct
---Contribution to the LiquidState project is organized under the terms of the Contributor Covenant, and as such the maintainer [@prasannavl](https://github.com/prasannavl) promises to intervene to uphold that code of conduct.