Click here to Skip to main content
Click here to Skip to main content
Go to top

C#/XML based state engine

, 20 Jul 2004
Rate this:
Please Sign up or sign in to vote.
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 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).

// 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 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() .

// 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 applications Close event

// 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)
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.

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

Share

About the Author

Russ Quinn
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! PinprotectorMarc Clifton21-Jul-04 9:01 
GeneralRe: Nice job! PinmemberRussq21-Jul-04 21:57 

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

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

| Advertise | Privacy | Mobile
Web02 | 2.8.140905.1 | Last Updated 21 Jul 2004
Article Copyright 2004 by Russ Quinn
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid