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 FunctionStateFunctionDelegate
s. 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 event
s, 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
Post a Comment