Click here to Skip to main content
15,878,959 members
Articles / Web Development / HTML

C#/XML Based State Engine

Rate me:
Please Sign up or sign in to vote.
4.00/5 (7 votes)
20 Jul 20043 min read 47.7K   626   30   2
How to create an XML based state engine for controlling a simple real-time system

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.

XML
<?xml version="1.0" encoding="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).

C#
// get our OnState event, then create and add a new delegate to the event
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 to 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.

C#
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() .

C#
// initialise and start up the engine 
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 application's Close event.

C#
// instruct the state engine to stop execution
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).

C#
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.

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 useful in this article and I look forward to reading your comments.

History

  • Version 1.0 - 20th July, 2004 - Original

License

This article has no explicit license attached to it, but may contain usage terms in the article text or the download files themselves. If in doubt, please contact the author via the discussion board below.

A list of licenses authors might use can be found here.


Written By
Web Developer
Australia Australia
Web developer with 15 years of commercial and industrial experience in the software world.
Now working as a contractor through his own company http://codeconsults.com/. See his blog here
Personal homepage http://russquinn.com/

Comments and Discussions

 
GeneralNice job! Pin
Marc Clifton21-Jul-04 9:01
mvaMarc Clifton21-Jul-04 9:01 
GeneralRe: Nice job! Pin
Russq21-Jul-04 21:57
Russq21-Jul-04 21:57 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.