RoadieRichStateMachine

A while back, I found myself needing a state machine for a project. I had some experience with a state machine from a previous employment, but I couldn't find one that looked like that. So I wrote one.

If you clone the repo, you'll get a demo project and unittests, but I also wanted to figure out Nuget, so I uploaded it there.  The Nuget package doesn't include any documentation other than that used for IntelliSense, so I figured I write about it a little.

States

I'll start off by explaining my state classes. FunctionState is the simplest way to define a state.

It has a number of constructors, all of which take one or more of one or another delegate. We'll ignore the WithVars variants for now.

The simplest constructor just takes a single delegate of the type FunctionStateFunctionDelegate. FunctionStateFunctionDelegate looks like this:

public delegate void FunctionStateFunctionDelegate();

While a state is active, that innerFunc will be called in a loop until a condition is met, which we'll get to later.

There's also a veriosn that takes a name parameter as well as an innerFunc, the name is purely for your own reference.

The other constructor takes three FunctionStateFunctionDelegates. The first is called when the state machine enters that state, then there's innerFunc as in the previous constructor, and a delegate called when the state is exited. The enter and exit delegates can be null, if you don't need to use that particular function

The WithVars version adds an argument to the passed in delegate functions, an IDictionary<string, dynamic> that can be used to pass informatiuon between the functions.

There's also an abstract State class that you can inherit to specify your own implementations of the enter, exit and inner functions without having to pass delegates around.

To switch from one state to another, we need to add a transition. There are two methods on State to do this, named AddTransitionTo and AlwaysTransitionTo.

AddTransitionTo takes the destination state and a function returning a bool (optionally with vars as an argument), and tells the state machine to switch to the destination state when that function returns true. You can call this method as many times as you need, to add transitions to any numnber of states.

AlwaysTransitionTo just takes a desitination state. Using that is equivalent to passing a funciton that always returns true to AddTransitionTo.

If a state has multiple transitions defined, they are checked in the order of definition.

There's one special state, a static StateMachine.ExitState. If the state machine transitions to this state, it will immediately stop and yield control back to the calling code. Note that it is an error to try and define transitions out of ExitState, as such a thing makes no sense.

StateMachine

StateMachine defines two properties and a Run method. The properties are InitialState, which should be self explanitory, and Delay which, if set to a value larger than zero, will pause after ever execution of innerFunc. You can achieve a similar effect using Thread.Sleep inside your inner function, if you want more granular control.

StateMachione also exposes two events, StateStarting and StateFinished, which pass a StateEventArgs type that has the current (entering or exiting) state as a property.

Finally, we get to Run. It has one optional parameter, IDictionary<string, dynamic> vars, which allows you to pass in predefined variables to be exposed to all the functions.

Example

The demo project on github contains very similar code, but I'm repeating it here for completness.

//create a StateMachine instance
using StateMachine funcSm = new();

//define states
var funcState = new FunctionState((vars) => Console.Write($"{vars["x"]} "));
var incrementState = new FunctionState((vars) => vars["x"] = vars["x"] + 1);
var evenState = new FunctionState(() => Console.WriteLine("is even"));
var oddState = new FunctionState(() => Console.WriteLine("is odd"));

//define transitions between states
//note the "fluent" interface
incrementState.AddTransitionTo(StateMachine.ExitState, (vars) => vars["x"] > 10)
              .AlwaysTransitionTo(funcState);
                  
funcState.AddTransitionTo(evenFuncState, (vars) => vars["x"] % 2 == 0)
         .AddTransitionTo(oddFuncState, (vars) => vars["x"] % 2 == 1); //this could be AlwaysTransitionTo(...) if you wanted
         
evenState.AlwaysTransitionTo(incrementFuncState);

oddState.AlwaysTransitionTo(incrementFuncState);

//finally, configure the state machine and run it.
funcSm.InitialState = funcState;
funcSm.Run(new() { ["x"] = 1 });

This produces the following output:

1 is odd
2 is even
3 is odd
4 is even
5 is odd
...
9 is odd
10 is even

Comments

Popular posts from this blog

Marshalling Output Arrays

Radio nets for the non-amateur