Search code examples
c#state-machinestateless-state-machine

How to have every state transition to a single state when a trigger occurs using Stateless?


I am using stateless and have the following states:

  • Foo
  • Bar
  • Faulted

When the fault trigger happens, I want the state machine to transition to the Faulted state, regardless of the previous state. Is there a way to do that something like:

machine.RegisterGlobalTrigger(Trigger.Fault, State.Faulted)

Note: I have thought of the following options, but looking for a "cleaner" solution:

  1. Registering that trigger with every state
  2. Creating an "Every" parent state that every class is a subclass of and registering the trigger there

Solution

  • It isn't as pretty as the one-liner, but you could use a List of all possible states as a way of setting global or semi-global configuration, like:

    Enum.GetValues<State>()
            .Where(state => state != State.Faulted) // so we don't put a Faulted .Permit() on the Faulted State
            .ToList()
            .ForEach(state =>
            {
                stateMachine.Configure(state)
                    .Permit(Trigger.Fault, State.Faulted);
            });
    

    Example with testing in this dotnet fiddle demo.

    Full example code:

    using System;
    using System.Linq;
    using Stateless;
    
    public class Program
    {
        public enum State
        {
            Foo,
            Bar,
            Faulted
        }
    
        public enum Trigger
        {
            Fooify,
            Barify,
            Fault
        }
    
        public static void Main()
        {
            // setup
            var stateMachine = new StateMachine<State, Trigger>(State.Faulted);
    
            stateMachine.Configure(State.Foo)
                .Permit(Trigger.Barify, State.Bar);
    
            stateMachine.Configure(State.Bar)
                .Permit(Trigger.Fooify, State.Foo);
    
            stateMachine.Configure(State.Faulted)
                .Permit(Trigger.Fooify, State.Foo)
                .Permit(Trigger.Barify, State.Bar);
    
            Enum.GetValues<State>()
                .Where(state => state != State.Faulted) // so we don't put a Faulted .Permit() on the Faulted State
                .ToList()
                .ForEach(state =>
                {
                    stateMachine.Configure(state)
                        .Permit(Trigger.Fault, State.Faulted);
                });
    
            // using the same pattern to add transition logging to every state
            Enum.GetValues<State>()
                .ToList()
                .ForEach(state =>
                {
                    stateMachine.Configure(state)
                        .OnEntry(t => Console.WriteLine($"Entering {t.Destination} from {t.Source}"));
                });
    
            // testing
            // To Foo, then to Faulted
            stateMachine.Fire(Trigger.Fooify);
            stateMachine.Fire(Trigger.Fault);
    
            // To Bar, then to Faulted
            stateMachine.Fire(Trigger.Barify);
            stateMachine.Fire(Trigger.Fault);
    
            Console.WriteLine("Done.");
        }
    }