Click or drag to resize
StaMaStateMachineTemplate and StateMachine

Describes how a state machine is started and how the state machine is triggered to do its work.

When the StateMachineTemplate is successfully prepared, it allows to create StateMachine instances through the StateMachineTemplate.CreateStateMachine method.

A StateMachine instance holds all the mutable data of a state machine. Actually this is not very much:

  • A reference to the State instance that is currently active. In case the state machine has composite states or orthogonal sub-regions there may also be multiple of them, depending on the nesting depth and orthogonality rank.

  • In case the history mechanism is used within the state machine, references to the last active State instances have to be stored per Region that uses the history mechanism.

  • A queue of event instances that are sent to the state machine.

  • A reference to an arbitrary client specific context object which is usually a reference to the wrapper class that hosts the state machine.

  • A backward reference to the StateMachineTemplate instance from which the StateMachine instance was created.

  • A few internal references to tracing related delegate functions.

The following code sample shows how a StateMachine instance is created from a StateMachineTemplate through the StateMachineTemplate.CreateStateMachine method. The StateMachine.Startup method enters the initial state and the StateMachine.SendTriggerEvent method sends the "Event1" signal to the state machine which triggers the transition "Transit1".

StateMachine creation, startup and sending an event
class SimpleStateMachine
{
    private StateMachine m_stateMachine;

    public SimpleStateMachine()
    {
        StateMachineTemplate t = new StateMachineTemplate();
        t.Region("State1", false);
            t.State("State1", null, ExitState1);
                t.Transition("Transit1", "State2", "Event1", null, null);
            t.EndState();
            t.State("State2", EnterState2, null);
                t.Transition("Transit2", "State1", null, null, null);
            t.EndState();
        t.EndRegion();

        m_stateMachine = t.CreateInstance();
        m_stateMachine.Startup();
        m_stateMachine.SendTriggerEvent("Event1");
    }

    private void ExitState1(StateMachine stateMachine, object triggerEvent, EventArgs eventArgs)
    {
    }

    private void EnterState2(StateMachine stateMachine, object triggerEvent, EventArgs eventArgs)
    {
    }
}

The following sections will discuss:

 

Enable Tracing for State Changes, for Event Processing and for Transition Checking

The StateMachine instance has properties to set callback methods that are called in the following situations:

StateMachine.TraceStateChange

The state machine executes a transition and switches to a state.

StateMachine.TraceDispatchTriggerEvent

The state machine fetches an event instance from the queue as part of the run-to-completion processing and dispatches it.

StateMachine.TraceTestTransition

The state machine dispatched an event and reached a transition that will be tested if it can be executed.

See also Tracing State Changes, Transition Testing and Event Dispatching.

 

Reuse the StateMachineTemplate for multiple StateMachine instances

The above sample code chunk creates a StateMachineTemplate instance for every instance of the SimpleStateMachine class and StateMachine instance. This is usually a reasonable programming style as it allows to directly use member methods of the wrapper class in the StateMachineTemplate tree definition.

However there may be cases, where many StateMachine instances are needed and instance creation performance and footprint require further optimization. In such cases it is possible to create multiple StateMachine instances from the same StateMachineTemplate instance. This approach takes advantage of fact that the StateMachineTemplate is immutable after the tree definition is completed. The somewhat complex StateMachineTemplate tree definition with syntax and consistency checks are needed only once.

For this coding approach the StateMachineTemplate is created as a static member of the wrapper class and the action callbacks are mede static methods of the wrapper class. In order to invoke instance methods of the wrapper class, the static methods fetch the StateMachine.Context property value, cast it to the wrapper class type and invoke the corresponding instance method of the wrapper class. The StateMachine.Context property is initialized through the context parameter of the StateMachineTemplate.CreateStateMachine(Object) factory method.

The following code sample shows the approach:

SampleSimpleStateMachineNETMFMultiInstance.cs
class SampleSimpleStateMachineNETMFMultiInstance
{
    private static StateMachineTemplate m_stateMachineTemplate;
    private StateMachine m_stateMachine;
    private DispatcherTimer m_timeoutTimer;


    private static StateMachineTemplate GetStateMachineTemplate()
    {
        if (m_stateMachineTemplate == null)
        {
            StateMachineTemplate t = new StateMachineTemplate();

            t.Region("State1", false);
                t.State("State1", EnterState1, ExitState1);
                    t.Transition("Transition1to2", "State2", "Event1", null, null);
                t.EndState();
                t.State("State2", EnterState2, ExitState2);
                    t.Transition("Transition2to1", "State1", "TimeoutState2", null, null);
                t.EndState();
            t.EndRegion();

            m_stateMachineTemplate = t;
        }

        return m_stateMachineTemplate;
    }


    public SampleSimpleStateMachineNETMFMultiInstance()
    {
        StateMachineTemplate stateMachineTemplate = GetStateMachineTemplate();
        m_stateMachine = stateMachineTemplate.CreateStateMachine(this);
        m_stateMachine.TraceStateChange = this.TraceStateChange;

        m_timeoutTimer = new DispatcherTimer();
        m_timeoutTimer.Tick += TimeoutTimer_Tick;

        m_stateMachine.Startup();
    }


    private static void EnterState1(StateMachine stateMachine, object triggerEvent, EventArgs eventArgs)
    {
        ((SampleSimpleStateMachineNETMFMultiInstance)stateMachine.Context).EnterState1Instance(stateMachine, triggerEvent, eventArgs);
    }


    private static void ExitState1(StateMachine stateMachine, object triggerEvent, EventArgs eventArgs)
    {
        ((SampleSimpleStateMachineNETMFMultiInstance)stateMachine.Context).ExitState1Instance(stateMachine, triggerEvent, eventArgs);
    }


    private static void EnterState2(StateMachine stateMachine, object triggerEvent, EventArgs eventArgs)
    {
        ((SampleSimpleStateMachineNETMFMultiInstance)stateMachine.Context).EnterState2Instance(stateMachine, triggerEvent, eventArgs);
    }


    private static void ExitState2(StateMachine stateMachine, object triggerEvent, EventArgs eventArgs)
    {
        ((SampleSimpleStateMachineNETMFMultiInstance)stateMachine.Context).ExitState2Instance(stateMachine, triggerEvent, eventArgs);
    }


    private void EnterState1Instance(StateMachine stateMachine, object triggerEvent, EventArgs eventArgs)
    {
        Display.WriteLine("EnterState1Instance");
    }


    private void ExitState1Instance(StateMachine stateMachine, object triggerEvent, EventArgs eventArgs)
    {
        Display.WriteLine("ExitState1Instance");
    }


    private void EnterState2Instance(StateMachine stateMachine, object triggerEvent, EventArgs eventArgs)
    {
        Display.WriteLine("EnterState2Instance");
        m_timeoutTimer.Tag = "TimeoutState2";
        m_timeoutTimer.Interval = new TimeSpan(0, 0, 2);
        m_timeoutTimer.Start();
    }


    private void ExitState2Instance(StateMachine stateMachine, object triggerEvent, EventArgs eventArgs)
    {
        m_timeoutTimer.Stop();
        Display.WriteLine("ExitState2Instance");
    }


    void TimeoutTimer_Tick(object sender, EventArgs e)
    {
        m_timeoutTimer.Stop();
        m_stateMachine.SendTriggerEvent(m_timeoutTimer.Tag);
    }


    private void TraceStateChange(StateMachine stateMachine, StateConfiguration stateConfigurationFrom, StateConfiguration stateConfigurationTo, Transition transition)
    {
        string info = DateTime.Now.ToString("HH:mm:ss.fff") + " ActiveState=\"" + stateConfigurationTo.ToString() + "\"" + " Transition=" + ((transition != null) ? "\"" + transition.Name + "\"" : "Startup/Finish");
        Debug.Print(info);
        Display.WriteLine(info);
    }


    public void ButtonUp()
    {
        m_stateMachine.SendTriggerEvent("Event1");
    }
}

The repeated coding of static to instance routing methods can be automated through a code generator shape in StaMa Visio Shapes.