All Projects → prasannavl → Liquidstate

prasannavl / Liquidstate

Licence: other
Efficient asynchronous and synchronous state machines for .NET

Projects that are alternatives of or similar to Liquidstate

Ws Machine
WS-Machine is a websocket finite state machine for client websocket connections (Go)
Stars: ✭ 110 (-44.16%)
Mutual labels:  async, state-machine
Gmqtt
Python MQTT v5.0 async client
Stars: ✭ 195 (-1.02%)
Mutual labels:  async
Awesome Fsm
🤖 A curated list of awesome resources related to finite state machines and statecharts.
Stars: ✭ 189 (-4.06%)
Mutual labels:  state-machine
Diffadapter
A high-performance , easy-to-use Adapter for RecyclerView ,using diffutil
Stars: ✭ 193 (-2.03%)
Mutual labels:  async
Http Kit
http-kit is a minimalist, event-driven, high-performance Clojure HTTP server/client library with WebSocket and asynchronous support
Stars: ✭ 2,234 (+1034.01%)
Mutual labels:  async
Fooproxy
稳健高效的评分制-针对性- IP代理池 + API服务,可以自己插入采集器进行代理IP的爬取,针对你的爬虫的一个或多个目标网站分别生成有效的IP代理数据库,支持MongoDB 4.0 使用 Python3.7(Scored IP proxy pool ,customise proxy data crawler can be added anytime)
Stars: ✭ 195 (-1.02%)
Mutual labels:  async
Iguazu
✨ Iguazu is a simple Redux-powered Async Query engine
Stars: ✭ 186 (-5.58%)
Mutual labels:  async
Aws Lambda Power Tuning
AWS Lambda Power Tuning is an open-source tool that can help you visualize and fine-tune the memory/power configuration of Lambda functions. It runs in your own AWS account - powered by AWS Step Functions - and it supports three optimization strategies: cost, speed, and balanced.
Stars: ✭ 3,040 (+1443.15%)
Mutual labels:  state-machine
Ok ip proxy pool
🍿爬虫代理IP池(proxy pool) python🍟一个还ok的IP代理池
Stars: ✭ 196 (-0.51%)
Mutual labels:  async
Use Async Effect
🏃 Asynchronous side effects, without the nonsense
Stars: ✭ 193 (-2.03%)
Mutual labels:  async
Actix Extras
A collection of additional crates supporting the actix and actix-web frameworks.
Stars: ✭ 190 (-3.55%)
Mutual labels:  async
Thirtyfour
Selenium WebDriver client for Rust, for automated testing of websites
Stars: ✭ 191 (-3.05%)
Mutual labels:  async
Trilogy
TypeScript SQLite layer with support for both native C++ & pure JavaScript drivers.
Stars: ✭ 195 (-1.02%)
Mutual labels:  async
Vkbottle
Homogenic! Customizable asynchronous VK API framework
Stars: ✭ 191 (-3.05%)
Mutual labels:  async
Paper
Hassle-free HTML to PDF conversion abstraction library.
Stars: ✭ 196 (-0.51%)
Mutual labels:  async
Aioquant
Asynchronous event I/O driven quantitative trading framework.
Stars: ✭ 188 (-4.57%)
Mutual labels:  async
Ppipe
pipes values through functions, an alternative to using the proposed pipe operator ( |> ) for ES
Stars: ✭ 192 (-2.54%)
Mutual labels:  async
Stripe
Typed .NET clients for stripe.com REST APIs
Stars: ✭ 193 (-2.03%)
Mutual labels:  async
Gear Lib
Gear-Lib, C library for IOT Embedded Multimedia and Network
Stars: ✭ 2,381 (+1108.63%)
Mutual labels:  async
Slack Client
A better Slack client, with RTM API support.
Stars: ✭ 196 (-0.51%)
Mutual labels:  async

LiquidState

Efficient state machines for .NET with both synchronous and asynchronous support. Heavily inspired by the excellent state machine library Stateless by Nicholas Blumhardt.

NuGet badge

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

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:

var config = StateMachineFactory.CreateConfiguration<State, Trigger>();

or for awaitable, or async machine:

var config = StateMachineFactory.CreateAwaitableConfiguration<State, Trigger>();

Step 2: Setup the machine configurations using the fluent API.

    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<string>(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:

var machine = StateMachineFactory.Create(State.Ringing, config);

Step 4: Use them!

  • Using triggers:
machine.Fire(Trigger.On);

or

await machine.FireAsync(Trigger.On);
  • Using direct states:
machine.MoveToState(State.Ringing);

or its async variant.

  • To use parameterized triggers, have a look at the example below.

Examples

A synchronous machine example:

    var config = StateMachineFactory.CreateConfiguration<State, Trigger>();

    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<string>(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.)

    // Note the "CreateAwaitableConfiguration"
    var config = StateMachineFactory.CreateAwaitableConfiguration<State, Trigger>();

    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<string>(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:

public interface IStateMachineCore<TState, TTrigger>
{
    TState CurrentState { get; }
    bool IsEnabled { get; }
    void Pause();
    void Resume();

    event Action<TriggerStateEventArgs<TState, TTrigger>> UnhandledTrigger;
    event Action<TransitionEventArgs<TState, TTrigger>> InvalidState;
    event Action<TransitionEventArgs<TState, TTrigger>> TransitionStarted;
    event Action<TransitionExecutedEventArgs<TState, TTrigger>>
                                                       TransitionExecuted;
}

Synchronous:

public interface IStateMachine<TState, TTrigger> 
        : IStateMachineCore<TState, TTrigger>
{
    IStateMachineDiagnostics<TState, TTrigger> Diagnostics { get; }

    void Fire<TArgument>(
            ParameterizedTrigger<TTrigger, TArgument> parameterizedTrigger, 
            TArgument argument);
    void Fire(TTrigger trigger);
    void MoveToState(TState state, 
            StateTransitionOption option = StateTransitionOption.Default);
}

Awaitable:

public interface IAwaitableStateMachine<TState, TTrigger> 
        : IStateMachineCore<TState, TTrigger>
{
    IAwaitableStateMachineDiagnostics<TState, TTrigger> Diagnostics { get; }

    Task FireAsync<TArgument>(
            ParameterizedTrigger<TTrigger, TArgument> 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/7

Support

Please use the GitHub issue tracker here 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 ).

Credits

Thanks to JetBrains for the OSS license of Resharper Ultimate.

Proudly developed using:

Resharper logo

License

This project is licensed under either of the following, at your choice:

Code of Conduct

Contribution to the LiquidState project is organized under the terms of the Contributor Covenant, and as such the maintainer @prasannavl promises to intervene to uphold that code of conduct.

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].