Introduction
Being tasked to look into ways of updating old C++ software, I needed to implement a simple state engine in C#. Wanting to create a generic solution that I can use throughout the code, I looked into having the actual state table written in XML along with a generic engine to process the table using reflection.
Using the code
The state table in XML simply defines a number of states and are identified by number. Each state has a function name and an associated action that is carried out when the function call is complete.
="1.0" ="utf-8"
<StateTable>
<State Number="0">
<Function>CheckInputs</Function>
<Action Type="SLEEP">
<SleepTime>1000</SleepTime>
</Action>
</State>
<State Number="10">
<Function>FirstPressed</Function>
<Action Type="MOVETONEXT">
<SleepTime>100</SleepTime>
<NextState>0</NextState>
</Action>
</State>
<State Number="20">
<Function>SecondPressed</Function>
<Action Type="MOVETONEXT">
<SleepTime>300</SleepTime>
<NextState>0</NextState>
</Action>
</State>
<State Number="30">
<Function>BothPressed</Function>
<Action Type="EXIT" />
</State>
<State Number="40">
<Function>NonePressed</Function>
<Action Type="MOVETONEXT">
<SleepTime>1000</SleepTime>
<NextState>0</NextState>
</Action>
</State>
</StateTable>
The state engine locates the function by using reflection and attaches it to the state's event. This is achieved by first getting state's event type and then attaching a delegate to it by specifying the target class (this.engineMethodsClass) instance and the name of the instance method (functionName).
System.Reflection.EventInfo eventInfo = state.GetType().GetEvent("OnState");
System.Delegate eventDelegate = System.Delegate.CreateDelegate(
eventInfo.EventHandlerType, this.engineMethodsClass, functionName);
eventInfo.AddEventHandler(state, eventDelegate);
An action is made up of the following enumeration values
SLEEP - causes the engine stay at the current state and to sleep for a period (in milliseconds)
MOVETONEXT - moves to the next state (indicated by the <NextState> tag). A sleep time can also be added here
EXIT - causes the engine to exit
To use the state engine first create a variable in the class that handles the state engine
private RQAssemblies.StateEngine.Engine engine = new
RQAssemblies.StateEngine.Engine();
In order to separate the events from the main application, you can specify the class where the events can be found. In this example we will simply point to our main application. We can then load the state table into the engine and instruct it to Run() .
engine.EngineMethodsClass = this;
engine.LoadStateTable(@"..\..\BasicEngine.xml");
engine.Run();
The engine runs on its own thread for execution and therefore must be stopped when the application is terminated. Place the following code in the applications Close event
engine.Stop();
When the engine hits a state it invokes the attached event. In our example state table we need to define 5 events, where the prototype for the event is
public void functionName(Action actionOnExit) public void CheckInputs(RQAssemblies.StateEngine.Action actionOnExit)
{
if((this.input1.Checked == true) && (this.input2.Checked == false))
{
actionOnExit.Type =
RQAssemblies.StateEngine.Action.ActionType.MOVETONEXT;
actionOnExit.StateNumberToMoveTo = 10;
}
else if((this.input1.Checked == false) && (this.input2.Checked == true))
{
actionOnExit.Type =
RQAssemblies.StateEngine.Action.ActionType.MOVETONEXT;
actionOnExit.StateNumberToMoveTo = 20;
}
else if((this.input1.Checked == true) && (this.input2.Checked == true))
{
actionOnExit.Type =
RQAssemblies.StateEngine.Action.ActionType.MOVETONEXT;
actionOnExit.StateNumberToMoveTo = 30;
}
else if((this.input1.Checked == false) && (this.input2.Checked == false))
{
actionOnExit.Type =
RQAssemblies.StateEngine.Action.ActionType.MOVETONEXT;
actionOnExit.StateNumberToMoveTo = 40;
}
}
public void FirstPressed(RQAssemblies.StateEngine.Action actionOnExit)
{
this.result.Text = "First Pressed";
}
public void SecondPressed(RQAssemblies.StateEngine.Action actionOnExit)
{
this.result.Text = "Second Pressed";
}
public void BothPressed(RQAssemblies.StateEngine.Action actionOnExit)
{
this.result.Text = "Both Pressed";
}
public void NonePressed(RQAssemblies.StateEngine.Action actionOnExit)
{
this.result.Text = "None Pressed";
}
Within an event we can modify the exiting action of the state by modifying the actionOnExit parameter.
In our example state table, state 0 simply loops around and checks the states of 2 buttons. Depending on the button states we modify our exit action condition in order to branch to a different part of the state table.
Points of Interest
When I first started learning C#, I viewed reflection as a breath of fresh air from C++, but struggled to find a real-world/work-related application to try it out on. Now I've found one, I can see the doors that it has opened for me to significantly reduce my coding efforts in the future.
History
- Version 1.0 - 20th July 2004 - Original
Conclusion
The implementation of the state engine is very basic and is here to simply show how you can leverage the benefits of XML and reflection in creating a real-time state engine. Clearly, this example isn't interfacing to a real-time system as I can't ZIP my barcode scanner along with this article. In the future I intend to modify this code to be more flexible in terms of its linking between states and providing external input states and possibly 'Yes/No' style state handling.
But in the meantime I hope you find something in this article useful and look forward to reading your comments.