|
|||||||||||||||||||||
|
|||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
ContentsIntroductionThis is the second part in a series of articles about my .NET state machine toolkit. In this part, we will take a look at its more advanced features. Our main focus will be on creating hierarchical state machines. We will use a running example to demonstrate how to use the toolkit. A traffic light state machineIn Part I, we built a state machine representing a light switch. In this part, we will build a slightly more complex state machine representing a traffic light. The traffic light state machine will have an On and Off state indicating whether or not it is operational. In addition, when it is in the On state, it will have three substates, Red, Yellow and Green indicating which of the lights is currently lit:
When in the On state, the state machine will periodically change which of the lights is lit (from red to green to yellow and back to red). A real traffic light would be more complex, of course, but this simplified version will suffice for demonstration purposes. Implementing hierarchical state machinesLet's jump into the code. Here is the beginning of our using System;
using Sanford.Threading;
using Sanford.StateMachineToolkit;
namespace TrafficLightDemo
{
public class TrafficLight : ActiveStateMachine
{
public enum EventID
{
Dispose,
TurnOn,
TurnOff,
TimerElapsed
}
public enum StateID
{
On,
Off,
Red,
Yellow,
Green,
Disposed
}
private State on, off, red, yellow, green, disposed;
private DelegateScheduler scheduler = new DelegateScheduler();
public TrafficLight()
{
on = new State((int)StateID.On, new EntryHandler(EntryOn));
off = new State((int)StateID.Off, new EntryHandler(EntryOff));
red = new State((int)StateID.Red, new EntryHandler(EntryRed));
yellow = new State((int)StateID.Yellow,
new EntryHandler(EntryYellow));
green = new State((int)StateID.Green,
new EntryHandler(EntryGreen));
disposed = new State((int)StateID.Disposed,
new EntryHandler(EntryDisposed));
on.Substates.Add(red);
on.Substates.Add(yellow);
on.Substates.Add(green);
on.InitialState = red;
on.HistoryType = HistoryType.Shallow;
Initialize(off);
}
#region Entry/Exit Methods
#endregion
public override void Dispose()
{
#region Guard
if(IsDisposed)
{
return;
}
#endregion
Send((int)EventID.Dispose);
}
private delegate void SendTimerDelegate();
private void SendTimerEvent()
{
Send((int)EventID.TimerElapsed);
}
}
}
This is pretty much boilerplate code and is very similar to the Initial stateSuperstates have an initial state. A superstate's initial state is one of its substates and is the state that is entered when the superstate is the target of a state transition. In practical terms what that means for our traffic light state machine is that since the On state is a superstate to the Red, Yellow, and Green substates, it needs an initial state, and that initial state will be one of its substates. We will make the Red substate the initial state to the On superstate. When the On state is first entered, it will in turn enter the Red state. Implementing this is simply a matter of assigning the substate to the superstate's
The image shows a hierarchical state machine. Substates are nested inside their superstates. A solid circle connected with a line and ending with an arrow points to the initial state. History typeNote in the A A A
Shallow history is indicated by an H inside a circle. An asterisks is added to indicate a TransitionsIf you look at the When the When one of the On's substates is entered, it will use the We want the state machine to remain in the Red and Green substates longer than in the Yellow substate. So when entering the Red or Green substates, the timer event will be scheduled to last longer than it will be when entering the Yellow substate. This is to mimic of how a real traffic light behaves. Here is the constructor with the public TrafficLight()
{
on = new State((int)StateID.On, new EntryHandler(EntryOn));
off = new State((int)StateID.Off, new EntryHandler(EntryOff));
red = new State((int)StateID.Red, new EntryHandler(EntryRed));
yellow = new State((int)StateID.Yellow, new EntryHandler(EntryYellow));
green = new State((int)StateID.Green, new EntryHandler(EntryGreen));
disposed = new State((int)StateID.Disposed,
new EntryHandler(EntryDisposed));
on.Substates.Add(red);
on.Substates.Add(yellow);
on.Substates.Add(green);
on.InitialState = red;
on.HistoryType = HistoryType.Shallow;
Transition trans = new Transition(off);
on.Transitions.Add((int)EventID.TurnOff, trans);
trans = new Transition(on);
off.Transitions.Add((int)EventID.TurnOn, trans);
trans = new Transition(green);
red.Transitions.Add((int)EventID.TimerElapsed, trans);
trans = new Transition(yellow);
green.Transitions.Add((int)EventID.TimerElapsed, trans);
trans = new Transition(red);
yellow.Transitions.Add((int)EventID.TimerElapsed, trans);
trans = new Transition(disposed);
off.Transitions.Add((int)EventID.Dispose, trans);
trans = new Transition(disposed);
on.Transitions.Add((int)EventID.Dispose, trans);
Initialize(off);
}
And here are the entry methods for the states. These are methods that will be invoked when their states are entered: #region Entry/Exit Methods
private void EntryOn()
{
scheduler.Start();
}
private void EntryOff()
{
scheduler.Stop();
scheduler.Clear();
}
private void EntryRed()
{
scheduler.Add(1, 5000, new SendTimerDelegate(SendTimerEvent));
}
private void EntryYellow()
{
scheduler.Add(1, 2000, new SendTimerDelegate(SendTimerEvent));
}
private void EntryGreen()
{
scheduler.Add(1, 5000, new SendTimerDelegate(SendTimerEvent));
}
private void EntryDisposed()
{
scheduler.Dispose();
Dispose(true);
}
#endregion
Notice the entry method for the On state. It starts the (To give credit where credit is due, this use of my Wrapping things upHere is our using System;
using Sanford.Threading;
using Sanford.StateMachineToolkit;
namespace TrafficLightDemo
{
public class TrafficLight : StateMachine
{
public enum EventID
{
Dispose,
TurnOn,
TurnOff,
TimerElapsed
}
public enum StateID
{
On,
Off,
Red,
Yellow,
Green,
Disposed
}
private State on, off, red, yellow, green, disposed;
private DelegateScheduler scheduler = new DelegateScheduler();
public TrafficLight()
{
on = new State((int)StateID.On, new EntryHandler(EntryOn));
off = new State((int)StateID.Off, new EntryHandler(EntryOff));
red = new State((int)StateID.Red, new EntryHandler(EntryRed));
yellow = new State((int)StateID.Yellow,
new EntryHandler(EntryYellow));
green = new State((int)StateID.Green,
new EntryHandler(EntryGreen));
disposed = new State((int)StateID.Disposed,
new EntryHandler(EntryDisposed));
on.Substates.Add(red);
on.Substates.Add(yellow);
on.Substates.Add(green);
on.InitialState = red;
on.HistoryType = HistoryType.Shallow;
Transition trans = new Transition(off);
on.Transitions.Add((int)EventID.TurnOff, trans);
trans = new Transition(on);
off.Transitions.Add((int)EventID.TurnOn, trans);
trans = new Transition(green);
red.Transitions.Add((int)EventID.TimerElapsed, trans);
trans = new Transition(yellow);
green.Transitions.Add((int)EventID.TimerElapsed, trans);
trans = new Transition(red);
yellow.Transitions.Add((int)EventID.TimerElapsed, trans);
trans = new Transition(disposed);
off.Transitions.Add((int)EventID.Dispose, trans);
trans = new Transition(disposed);
on.Transitions.Add((int)EventID.Dispose, trans);
Initialize(off);
}
#region Entry/Exit Methods
private void EntryOn()
{
scheduler.Start();
}
private void EntryOff()
{
scheduler.Stop();
scheduler.Clear();
}
private void EntryRed()
{
scheduler.Add(1, 5000, new SendTimerDelegate(SendTimerEvent));
}
private void EntryYellow()
{
scheduler.Add(1, 2000, new SendTimerDelegate(SendTimerEvent));
}
private void EntryGreen()
{
scheduler.Add(1, 5000, new SendTimerDelegate(SendTimerEvent));
}
private void EntryDisposed()
{
scheduler.Dispose();
Dispose(true);
}
#endregion
public override void Dispose()
{
#region Guard
if(IsDisposed)
{
return;
}
#endregion
Send((int)EventID.Dispose);
}
private delegate void SendTimerDelegate();
private void SendTimerEvent()
{
Send((int)EventID.TimerElapsed);
}
}
}
DependenciesBe sure to read the dependencies section in Part I. ConclusionIn this article, we've taken a closer look at the advanced features of the .NET state machine toolkit. I hope you've found it interesting and helpful. In Part III, we will explore using code generation with the toolkit. Thanks again for your time. Comments and suggestions are welcome as always. History
| ||||||||||||||||||||