Introduction

This article discusses how to create hierarchical statemachines based in XML with C# codebehind.

Purpose

Statemachines allow one 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:

<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. Correct XML formatting is provided through a schema but valid references are not checked. The following C# code is generated from the XML.

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, one simply has 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 setting up complex conditions. A transition may have a hierarchical conditional block that will be evaluated before a transition takes place.

<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 on how to combine the condition.

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 files required in the project are:

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

History

  • v0.5 - First release
推荐.NET配套的通用数据层ORM框架:CYQ.Data 通用数据层框架