GithubHelp home page GithubHelp logo

algoke / binstate Goto Github PK

View Code? Open in Web Editor NEW

This project forked from ed-pavlov/binstate

0.0 0.0 0.0 594 KB

Binstate is a simple but yet powerful state machine for .NET. Thread safe. Supports async methods. Supports hierarchically nested states.

License: Apache License 2.0

C# 100.00%

binstate's Introduction

If Binstate has done you any good, consider buying me a cup of coffee


Nuget Build & Test


Binstate

Binstate (pronounced as "Be in state") is a simple but yet powerful state machine for .NET. Thread safe. Supports async methods. Supports hierarchically nested states.

Features

Thread safety

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) executes an 'exit' action of the current state and an 'enter' action of the new state on the current thread. If an 'enter' action is blocking Raise will block until enter action finishes. RaiseAsync(event) uses Task.Run to execute a transition.

It gives an application full control over the threading model of the state machine.

Support async methods

Supports async methods as an 'enter' action of the state. Binstate guarantees that an async 'enter' action will finish before calling an 'exit' action of the current state and an 'enter' action of the new state. An 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's 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 publicly. 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 a state from an 'enter' action

  private async Task TrackGame(IStateMachine<State> stateMachine, string opponentName)
  {
    while (stateMachine.InMyState)
    {
      // track game
      if(IsGameFinished())
        stateMachine.RaiseAsync(GameFinished);
    }
  }

Enter/Exit/OnTransition actions with arguments

     builder
       .DefineState(WaitingForGame)
       .OnExit<string>(WaitForGame)
       .AddTransition<string>(GameStarted, TrackingGame, OnTransitionToTrackingGame)
       ...

     builder
       .DefineState(TrackingGame)
       .OnEnter<string>(TrackGame)
       ...

Hierarchically nested states

Supports hierarchically nested states, see "Elevator" example.

Relaying arguments

Relaying arguments attached to a state through states upon activation

     builder
       .DefineState(SomeState)
       .OnEnter<string>(...) // argument passed to the 'enter' action is 'attached' to the state
       .AddTransition(SomeEvent, AnotherState)

     builder
       .DefineState(AnotherState)
       .OnEnter<string>(...) // this state also requires an argument
       ...

     stateMachine.Raise(SomeEvent); // argument will be passed from SomeState to AnotherState

Mixing relaying and passing arguments

      builder
       .DefineState(SomeState)
       .OnEnter<string>(...) // argument passed to the 'enter' action is 'attached' to the state
       .AddTransition(SomeEvent, AnotherState)

     builder
       .DefineState(AnotherState)
       .OnEnter<ITuple<object, string>>(...) // this state requires two arguments; OnEnter<object, string>(...) overload can be used to simplify code
       ...

     // one argument will be relayed from the SomeState and the second one passed using Raise method
     stateMachine.Raise(SomeEvent, new object());

Relay argument to one of activated state and pass to another

      builder
        .DefineState(SomeState)
        .OnEnter<string>(...) // argument passed to the 'enter' action is 'attached' to the state
        .AddTransition(SomeEvent, Child)

     builder
       .DefineState(Parent)
       .OnEnter<object>(...)
       ...

     builder
       .DefineState(Child).AsSubstateOf(Parent)
       .OnEnter<string>(...)
       ...

     // object passed to Raise will be passed to the Parent state and string argument from the SomeState will be relayed to the Child state
     stateMachine.Raise(SomeEvent, new object()) // will be passed to Child
  • Argument for relaying can be gotten from one of the parent of the active state if the active state itself has no argument.
  • Argument will be relayed to all parent states of the newly activated state if they require an argument.
  • If a state already has 'tuple' argument, it can be split by two when relaying to the newly activated state (and its parents) depending on their 'enter' actions argument

Examples

Telephone call

  var builder = new Builder<State, Event>(OnException);

  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>(OnException);

        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
    }
  }

binstate's People

Contributors

ed-pavlov avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.