Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / XML

Hierarchy XML Based C# Statemachine Generator

4.15/5 (6 votes)
23 Feb 2011CPOL3 min read 30.3K   430  
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)