Click here to Skip to main content
15,890,512 members
Articles / Programming Languages / XML

Hierarchy XML Based C# Statemachine Generator

Rate me:
Please Sign up or sign in to vote.
4.15/5 (6 votes)
23 Feb 2011CPOL3 min read 29.6K   426   19   10
Design a Statemachine in XML and generate a C# code representing it.

Introduction

This article discusses how to create hierarchical statemachines based in XML with C# code-behind.

Purpose

Statemachines allow us to represent a problem in a concrete and manageable form that generally has a common solution. The Statemachine code allows one to write a hierarchical based statemachine using XML and generate a corresponding C# code file representing the statemachine with all the semantics needed to execute the statemachine.

Creating a Statemachine

The majority of code to use the statemachine is simply writing it in XML:

XML
<StateMachines name="<NamespaceName>" xmlns="StateMachines">
    <StateMachine name="<ClassName>">
        <States>
            <State name="<StateName>"/>
        </States>
        <Events>
            <Event name="<EventName>"/>
        </Events>
        <Transitions>
            <Transition from="<StateName>"
                to="<StateName>" trigger="<EventName>"/>
        </Transitions>
    </StateMachine>
</StateMachines>

A State element can be another StateMachine with the exact same format. The correct XML formatting is provided through a schema, but valid references are not checked. The following C# code is generated from the XML:

C#
using StateMachines;

namespace StateMachines.NamespaceName
{
    public partial class ClassName :
    StateMachine<ClassName.States, ClassName.Events, ClassName.Transitions>
    {
        public enum States
        {
            StateName
        }

        public enum Events
        {
            EventName
        }

        public enum Transitions
        {
            EventName__StateName_StateName
        }

        public ClassName()
        {
            _States[(int)States.StateName] =
                (this.New(this, States.StateName));

            InitialState = _States[(int)States.StateName];
            _currentState = InitialState;

            _Transitions
            [(int)Transitions.EventName__StateName_StateName] =
            new Transition<States, Events, Transitions>
            (States.StateName, States.StateName, Events.EventName);
            _Transitions[(int)
            Transitions.EventName__StateName_StateName].Conditions =
            new Condition(() => { return true; });
        }

        public new void Fire(ClassName.Events Event, params object[] args)
        {
            switch(Event)
            {
                case Events.EventName:
                if (_Transitions[(int)Transitions.EventName__
                    StateName_StateName].Eval())
                {
                    CurrentState =
                        _States[(int)States.StateName];
                }
                break;
            }
        }

        // User defined functions. Must Implement.
        // Comment out or remove these declarations when used.
    }
}

To use the code, you simply have to instantiate the statemachine, add handlers to its events, then fire them. To generate the statemachine, you must add the file to the StateMachine.tt parser.

States

States are objects representing the states in a statemachine. They cannot be instantiated by anything but the StateMachine and have low weight. A statemachine itself is a state.

Events

Standard OnEnter and OnLeave events exist for all states.

Conditionals

Each transition can have an optional set of conditions that must be passed for the transition to take place. This is to reduce the complexity to set up complex conditions. A transition may have a hierarchical conditional block that will be evaluated before a transition takes place.

XML
<Transitions>
    <Transition from="<StateName>" to="<StateName>" trigger="<EventName>">
        <Conditions op="<OperationType>">
            <Condition name="<ConditionName" op="<OperationType>">
        </Conditions>
    </Transition>
</Transitions>

<Conditions> adds a block of conditions and maybe nested. A <Condition> references a method in the class by "ConditionName", and op is the operation to combine the conditions.

Adding Functionality

In the sample code, there is an example of a statemachine of a mouse interacting with objects. The basic code of this machine is:

C#
void B_MouseEnter(object sender, MouseEventArgs e)
{
    switch (mouseSM.CurrentState._State)
    {
        case MouseSM.States.Waiting :
            mouseSM.Waiting.Fire(Waiting.Events.IntoObject, sender);
            mouseSM.Waiting.ForceCurrentState(Waiting.States.OnObject);
            mouseSM.LeftButton.ForceCurrentState
                    (LeftButton.States.OnObject);
            mouseSM.RightButton.ForceCurrentState
                    (RightButton.States.OnObject);
            break;

        case MouseSM.States.LeftButton :
            mouseSM.LeftButton.Fire
                (LeftButton.Events.IntoObject, sender);
            mouseSM.Waiting.ForceCurrentState(Waiting.States.OnObject);
            mouseSM.LeftButton.ForceCurrentState
                (LeftButton.States.OnObject);
            mouseSM.RightButton.ForceCurrentState
                (RightButton.States.OnObject);
            break;

        case MouseSM.States.RightButton :
            mouseSM.RightButton.Fire
                (RightButton.Events.IntoObject, sender);
            mouseSM.Waiting.ForceCurrentState(Waiting.States.OnObject);
            mouseSM.LeftButton.ForceCurrentState
                (LeftButton.States.OnObject);
            mouseSM.RightButton.ForceCurrentState
                (RightButton.States.OnObject);
            break;

        default: break;
    }

    CurrentState.Text = mouseSM.ActiveState.ToString();
}

The handler here simply fires the appropriate event off when an object (a Button in this case) is interacted with. ForceCurrentState is used in this case to make all the top level states follow each other when they are over a button or not. This is so we enter the correct substate. This could be avoided by using a common class of events and rewriting the statemachine. In this case, we actually have three statemachines working simultaneously. That is being on or off a button, and that of the mouse button being down or not. A third statemachine can be used to manage the possible combinational outcomes of those two. This could also be implemented as one statemachine and having states such as LeftButtonOnObject and LeftButtonOffObject.

We also hook up OnEvents and OnLeaves in the sample code without console output.

Conclusion

The usefulness of this code is to reduce all the boilerplate code required in implementing the statemachine along with the type safety of C#. The major point of posting this code is to provide a foundation for your own statemachine code projects. While T4 is used, it was designed in such a way that it is not required and the code generation can easily be migrated to its own executable. One could add a visual frontend to generate the statemachines.

The files required in the project are:

  • StateMachine.cs
  • StateMachineGen.tt
  • StateMachine.xsd
  • T4Utility.tt

Things To Do

Add the ability to use a common set of events, transitions, states, and conditions. This may make the statemachine overly complex but can reduce complexity in some cases significantly.

History

  • v0.5 - 02/19/2011 - First release.
  • v0.55 - 02/21/2011 - Added local state fields for easy state access. Updated passing data to event handlers.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralMy vote of 4 Pin
Kanasz Robert5-Nov-12 2:48
professionalKanasz Robert5-Nov-12 2:48 
GeneralMy vote of 4 Pin
lluism6526-Feb-11 10:43
lluism6526-Feb-11 10:43 
Generalsuggestion in StateMachine.cs Pin
lluism6526-Feb-11 10:35
lluism6526-Feb-11 10:35 
GeneralRe: suggestion in StateMachine.cs Pin
Jon_Slaughter26-Feb-11 11:55
Jon_Slaughter26-Feb-11 11:55 
No, The problem here is basically chicken and egg since on a transition we have no current state(it is in limbo between the two).

But! We pass the valid state on the Enter and Leave as sender.

The problem with your code is that there is no way for Enter to get the previous state before the transition(previous previous state).

Your code

_States[(int)((dynamic)State)].Enter(_currentState, new EventData(args));

or same as

_currentState.Enter(_currentState, new EventData(args));

is redundant because sender passes _currentState.

Of course StateMachine's _currentState is not consistent with Enter's sender in my code but we just have to remember to use Sender.

For example,

suppose we have

(_previousState, _currentState, _nextState) = (state1, state2, state3)

_currentState.Leave(state3) <- sender tells us where we are going, (state1, state2, state3)
_nextState.Enter(state2) <- sender tells us where we came from (state1, state2, state3)
_previousState = _currentstate; _currentState = _nextState; <- (state2, state3, ??)


or

_currentState.Leave(state3) <- sender tells us where we are going, (state1, state2, state3)
_previousState = _currentstate; _currentState = _nextState; <- (state2, state3, ??)
_nextState.Enter(state3) <- sender tells us where we came from (state2, state3, ??)

Notice the loss of information and last line is basically redundant for sender(we are saying state3.Enter(state3) which would have no more information as state3.Enter(null)).

In my case we can see that the previous state is state1 which is lost in your case. My case allows for a reversion of state if Enter needs to fail but in your case if Enter fails then we lost state1 and cannot get back to it.

Basically you are changing the state variables on the transition and I am changing them after. Yours seems more logical in some regard but also loses information unless we store it/pass like

_nextState.Enter(new Tuple(state1, state2))

or have a history of states... i.e., _previousStates[k] which we will lose the oldest state still(which probably won't be important but)

You just have to remember that sender points to WHERE we are going for Leave and WHERE we came from for Enter and that the StateMachine variables are not updated until after the full transition has taken place so we don't lose information(state 1 in the example).

It seems a little less logical but only way to do it without added more complexity such as history or using tuples or making it more illogical such as _nextState.Enter(_previouspreviousState).

Do you feel this makes sense now or still a problem? Your way will work if you don't mind losing previous previous State. Just want to change your last line as


_currentState.Enter(_previousState, new EventData(args));

So it is not redundant.
QuestionSuggestions? Pin
Jon_Slaughter24-Feb-11 10:46
Jon_Slaughter24-Feb-11 10:46 
GeneralPls provide example of MouseSM.cs Pin
JohnWindy19-Feb-11 11:42
JohnWindy19-Feb-11 11:42 
GeneralRe: Pls provide example of MouseSM.cs [modified] Pin
Jon_Slaughter19-Feb-11 11:56
Jon_Slaughter19-Feb-11 11:56 
GeneralRe: Pls provide example of MouseSM.cs Pin
JohnWindy24-Feb-11 14:02
JohnWindy24-Feb-11 14:02 
GeneralRe: Pls provide example of MouseSM.cs Pin
Jon_Slaughter26-Feb-11 12:04
Jon_Slaughter26-Feb-11 12:04 
GeneralRe: Pls provide example of MouseSM.cs Pin
vishnu5721-Oct-13 5:13
vishnu5721-Oct-13 5:13 

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.