Click here to Skip to main content
Click here to Skip to main content

A Lightweight Implementation of UML Statecharts in C++

By , 23 Aug 2007
 

Introduction

This statechart "engine" (implemented as a C++ template class) implements the most commonly used aspects of UML statecharts. All you need to do is define an array of states and implement event checking and handling methods. Then call the engine when an event occurs. The engine calls your event checking and handling methods in the correct order to figure out what event happened, and tracks the current state.

This is a lightweight implementation that compiles under Visual Studio 2005 and, with some slight modifications, under VC++ 6.0. This implementation requires less statechart-related housekeeping code than other C++ implementations. (See Miro Samek's, for example.)

Background

Statecharts were developed by David Harel to add nesting and other features to flat state machines. (See David Harel, "On Visual Formalisms", Communications of the ACM, Vol. 31, No. 5, pp 514-530, 1988.) Statecharts were later added to the Unified Modeling Language (UML) and standardized. They are an excellent tool for modeling classes, sub-systems and interfaces that have many distinct states and complex transitions among them.

My personal need for them arose while implementing a software interface between two real-time, embedded systems that controlled separate machines requiring physical synchronization. I have also used them for modeling and implementing user interfaces that featured many modes.

The following is a brief summary of the notation and behavior of statecharts. For a full presentation of the UML statechart notation, see the UML 2.0 specification available here.

Graphically, UML shows states as boxes with rounded corners, and transitions as arrows between boxes. (See Figure 1.) The transitions are labeled with the event that causes the transition, optionally followed by a forward slash, and the action(s) that will be taken upon transition. A condition, called a "guard," can be indicated in square brackets. If the event happens, the guard condition is evaluated and the transition is taken only if the condition is true.

Figure 1

States can be nested (See Figure 2.), which allows high-level events to invoke transitions that leave any of several states with just one arrow. The use of so-called "composite" states keeps statechart diagrams much simpler than what flat state machines would require. Even though states can be nested, the system must always transition to some simple (i.e. non-composite) state.

Figure 2

A solid circle at the beginning of an arrow indicates a default start state. A statechart must have at least one default start state designated, indicating the initial state of the system. If any transition -- including a default one -- ends on a compound state, then there must be a default state designation inside that compound state, and so on, eventually leading to a simple state. One exception to this is described below.

States can have internal transitions that do not take the system to another state, yet do have associated actions. These are shown inside the state where they are handled. Compound states may also have internal transitions. Two special internal transitions are "entry" and "exit." These are executed upon entry to, and exit from, the corresponding state. This allows common actions such as initialization and destruction to be expressed one time, rather than as actions on every event leading to/from a state. Custom internal events can also be specified.

If a transition takes the system across several state boundaries, the various actions are executed in the following order:

  1. The exit action(s) of all states that must be exited
  2. The action specified on the transition arrow
  3. The entry action(s) for all states that are entered

Statecharts allow a transition to return to a previous state within a composite state, without requiring that you specify what state you were in previously. This is represented by a transition arrow that ends in an encircled "H" for "history." (See Figure 3.) For example, say an event can be handled from any of two simple states within a compound state. If you show a transition from the compound state, back inside it to the encircled "H," this means "handle the event and return to the state you most recently left." That is much simpler than showing two (or more) transitions with identical event/action labels.

Figure 3

There are two kinds of history returns in UML statecharts: shallow and deep. Shallow transitions (indicated by an "H") return to the most recently exited state at the level where the "H" is shown. If that does not lead to a simple state, then it is an error. Deep history (indicated by an "H*") means that the system will return to the most recent simple state within the compound state that it most recently exited. So, if the system in Figure 3 was in state A when event x happened, the system would return to state A.

Using the Code

This implementation supports state nesting, entry, exit and custom internal events, default states and deep history transitions. If a history return cannot find a recently exited state at a given level, it will try to use default state designations to get the system to a simple state. Only failing that will it be considered an error.

This implementation does not currently support orthogonal states, factored transition paths, forks, joins, synch states or message broadcasting. Many of those unsupported features depend heavily on the system you are integrating this code into and can be simulated in your event handlers.

Once you have designed your statechart, do the following. First, define an enumeration of states. The following comes from the included sample application, which illustrates all of the above features. This application performs a path-cover test of the statechart engine.

enum eStates
{
    eStateA,
    eStartState = eStateA,
    eStateB,
    eStateC,
    eStateD,
    eStateE,
    eNumberOfStates
};

Here I name the start state and I also designate the number of states by the last enum value, so it will always be correct. Your specific state names will likely be more meaningful than these. Next, I allocate an array of the following:

typedef struct
{
    int32         m_i32StateName;
    std::string      m_sStateName;
    int32         m_i32ParentStateName;
    int32         m_i32DefaultChildToEnter;
    int32         (T::*m_pfi32EventChecker)(void);
    void          (T::*m_pfDefaultStateEntry)(void);
    void          (T::*m_pfEnteringState)(void);
    void          (T::*m_pfLeavingState)(void);
} xStateType;

For example,

TStatechart<CStateClass>::xStateType xaStates[eNumberOfStates] = {
/* name                      */    {eStateA,
/* string name               */    "A",
/* parent                    */    -1,
/* default_substate          */    eStateB,
/* event-checking func       */    &CStateClass::evStateA,
/* default state entry func  */    &CStateClass::defEntryStateA,
/* entering state func       */    &CStateClass::entryStateA,
/* exiting state func        */    &CStateClass::exitStateA},

/* name                      */    {eStateB,
/* string name               */    "B",
/* parent                    */    eStateA,
/* default_substate          */    eStateC,
/* event-checking func       */    &CStateClass::evStateB,
/* default state entry func  */    &CStateClass::defEntryStateB,
/* entering state func       */    &CStateClass::entryStateB,
/* exiting state func        */    &CStateClass::exitStateB},

/* name                      */    {eStateC,
/* string name               */    "C",
/* parent                    */    eStateB,
/* default_substate          */    -1,
/* event-checking func       */    &CStateClass::evStateC,
/* default state entry func  */    &CStateClass::defEntryStateC,
/* entering state func       */    &CStateClass::entryStateC,
/* exiting state func        */    &CStateClass::exitStateC},

/* name                      */    {eStateD,
/* string name               */    "D",
/* parent                    */    eStateA,
/* default_substate          */    -1,
/* event-checking func       */    &CStateClass::evStateD,
/* default state entry func  */    &CStateClass::defEntryStateD,
/* entering state func       */    &CStateClass::entryStateD,
/* exiting state func        */    &CStateClass::exitStateD},

/* name                      */    {eStateE,
/* string name               */    "E",
/* parent                    */    eStateB,
/* default_substate          */    -1,
/* event-checking func       */    &CStateClass::evStateE,
/* default state entry func  */    &CStateClass::defEntryStateE,
/* entering state func       */    &CStateClass::entryStateE,
/* exiting state func        */    &CStateClass::exitStateE}
};

The structs must be initialized in the same order as the states in the enumeration above. Only the top-most state, here eStateA, will have -1 for its parent designation. States without a default sub-state (which will include all simple states) must specify -1 for the default sub-state. Every state must have an event checking/handling method, but need not have the last three fields filled in. Specify 0 for those if they are not defined.

The string name field is used in printing out trace information. In the file TStatechart.hpp, set TRACING_STATUS to 1 to activate this. If compiled with MFC, the information will be written via the TRACE() macro. Otherwise, the text is sent to cout. The engine is referenced internally via a void pointer, so the class using the statechart must have a void pointer for its use:

class CStateClass
{
    .
    .
    .
    void    *engine;
};

The engine must be created and destroyed in your class. These macros and the ones below hide some of the necessary details. The engine name appears in all of them so that you may have more than one declared in the same client class.

CStateClass::CStateClass(void)
{
    CREATE_ENGINE(CStateClass, engine, 
        xaStates, eNumberOfStates, eStartState);
}

CStateClass::~CStateClass(void)
{
    DESTROY_ENGINE(CStateClass, engine);
}

At the point where events happen, place the following call:

PROCESS_EVENT(CStateClass, engine)

Since an event may be far more complex than examining a mere scalar value, the engine does not pass the event around to your event checking/handling methods. Rather, you must store the event in member variable(s) in your class before calling PROCESS_EVENT so that the event checking/handling methods can test for it. Thus, it does not appear in the above call.

For each state in your statechart diagram, an event checking/handling method must be defined that checks for all events that can be handled by that state. Simply test for each event that can happen while you are in the given state, as in the following:

uint32 CStateClass::evStateA(void)
{
    // Checking for event in state A.
    if ('g' == m_cCharRead)        // include any guard conditions here
    {
        BEGIN_EVENT_HANDLER(CStateClass, engine, eStateA);
        // Put the transition action code here.
        END_EVENT_HANDLER(CStateClass, engine);
        return (iHandlingDone);
    }
    return (iNoMatch);
}

This arrangement allows any guard conditions to be tested at the same point in the code as the event itself, simplifying the code. Return iNoMatch from a handler that does not find an event it is supposed to handle.

The BEGIN_EVENT_HANDLER macro lets the engine know that you have found a match for an event. It records this fact and executes the exit handlers for every state that must be exited to get to the destination state. It also records the fact that eStateA will be the state the system goes to after executing the handler code. If the given state is a composite state, then of course you will end up in some simple state inside that composite state.

Control then returns to this method, where any transition actions are carried out. The END_EVENT_HANDLER macro executes any state entry handlers for states that you must enter to end up in the correct simple state. If you wish to transition to a composite state with history, "OR" the history flag onto the state name in the BEGIN_EVENT_HANDLER macro:

BEGIN_EVENT_HANDLER(CStateClass, engine, eStateA | iWithHistory);

For an internal transition, use the "no state change" flag:

BEGIN_EVENT_HANDLER(CStateClass, engine, iNoStateChange);

That's it!

Internals

Internally, the state definition array is parsed upon initialization and more sophisticated data structures are created from it. Those data structures, plus the state definition array, are referenced when an event is being processed. Don't even think about changing the state definition array at run-time!

Because your code must call the statechart engine, it calls your event handlers back in order to find out who will be handling the event and where to go next. Hence, when executing an event handler, you are on the same thread that initially called the engine.

The code contains numerous assert() statements, which check for a variety of simple mistakes that can be made when defining the array of states. Examples are failure to have an initial default state, and a state not having a valid parent state. Such errors are caught during initialization, rather than at run-time.

History

  • 23 August, 2007 -- Article and downloads updated
    • The code has been updated to compile/run with Visual Studio 2005.
    • A tracing feature has been added to assist in debugging.
    • The article was updated to match.
  • 23 August, 2005 -- Original version posted

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

GDSchultz
Software Developer (Senior)
United States United States
Member
No Biography provided

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralDon't understand the assertEvent() methods in StateClass.hppmemberMember 10937063 Feb '11 - 7:48 
What are these and how are these defined? I'm having a real time picturing the UML state machine for this.
GeneralConcurrent statesmembernonamir11 Feb '09 - 10:28 
Does your Statecharts engine support AND-states (concurrent states)?
GeneralRe: Concurrent states [modified]memberNick Alexeev11 Feb '09 - 20:54 
Article author wrote:
This implementation does not currently support orthogonal states, ... , forks, joins, synch states...

 
May be, it's possible to run several of these state machines engines in parallel, and that will mimic AND-states.
 
P.S. I'm exploring issues associated with concurrent state machines too. I wrote a humble article about joins http://www.codeproject.com/KB/architecture/join_pseudostate.aspx[^]. Check it out.
 
modified on Thursday, February 26, 2009 4:10 PM

GeneralRe: Concurrent statesmembernonamir13 Feb '09 - 21:47 
I thought about that. Actually it's not the same. It *is* possible to express AND-states by the product automaton of all substates, but that enlarges the state machine quite alot.
GeneralSDL and Z.100 ITU RecommendationmemberOleksiy Sokolovskiy15 May '06 - 13:51 
A well balanced article, reflecting a more comprehensive, compared to flow charts, design approach. My 5.
 
However, it's really interesting to recall specification description language (SDL) (1980), which provided perfect possibilities to describe a system as a set of communicating processes.
 
Take a look at http://www.sdl-forum.org/SDL/index.htm
You may also notice SDL stencil in MS Visio.
 
In my opinion, whatever UML statecharts suggest to meet software design needs, is just a simplified version of SDL constructs (inventing a wheel one more time).
 
Big Grin | :-D
Oleksiy
GeneralVery interesting...memberboutblock27 Jan '06 - 2:14 
Hi !
 
I also developed a statechart engine myself a few years ago (in 2002 !) in C++ that I ported into C# as an training for this new language (at the time being Smile | :)
 
My implementation has got those specifications :
 
* fully supported OMG-UML 1.4 features (See chapters 2.12 and 3.74-3.83) :
- static named events with optional action parameters
- dynamic inherited events with internal data (and optional action parameters)
- guards (conditions on events)
- nested composite states (tree set)
- initial stubstate
- final stubstate / completion transition / completion event (= unnamed event)
- compound multi-level transitions
- internal transitions
- default transition (no event + [else] guard)
- immediate transitions (unnamed event)
- entry/exit actions (nested)
- junction and choice points (static and dynamic branches)
- deep and shallow history pseudostates
- TimeEvent / [after] guard (periodic timers allowed for inner transitions)
- deferred events (be careful to args lifetime)
- compliant to "step to completion" rule (propagated events sent within actions are deferred)
- including of submachines
 
* extended features :
- self-transitions are allowed for top state
- entry/exit actions are allowed for top state
- able to send asynchronous events to a state machine using the system threads pool and locks
- logging traces and errors to streams
 
* not yet supported features :
- do activity
 
* unscheduled features :
- concurrent transitions (region)
- join and fork vertices
- SynchState, [is_in()] guard
- ChangeEvent (boolean)
- [when] guard
 

GeneralRe: Very interesting...memberboutblock27 Jan '06 - 2:16 
the interface looked like this :
 
//
// STATE MACHINE ENGINE BASE CLASS
//
 
#ifndef MOD_CSTATEMACHINE_H
#define MOD_CSTATEMACHINE_H
 
#include "delegate.h"
 
//------------- CLASS INTERFACE ---------------
 
namespace StateMachines
{
// a guard must be true to validate a transition triggered by an event
// (remember that if you declare the same event with different guards
// for a finite state, only one transition path must be true at a time !)
typedef delegate fguard;
 
// an action is simply a boolean function call with an untyped parameter
typedef delegate faction;
 
// a function which must return a timeout value in milliseconds
typedef delegate ftimer;
 
// inherite from this base class to build your own state machines
class CStateMachine
{
public:
 
// private inner class hidding implementation
class CHandle;
 
// a event must be unique in a state machine context
// we have two solutions to achieve this goal :
// - declare static events with a name
// - inherate each event you need from a base class
class CEvent
{
public:
operator const char *();
operator bool();
 
operator == (CEvent &evt);
operator != (CEvent &evt);
 
// needed internally for STL
operator == (CEvent *evt);
operator != (CEvent *evt);
 
virtual ~CEvent() {};
 
protected: friend CHandle;
CEvent(IN const char *name, IN bool need_args);
 
const char *m_name;
bool m_need_args;
void *m_args;
};
 
class CDynamicEvent : public CEvent
{
public:
CDynamicEvent (IN const char *name, IN bool need_args = false);
};
 
class CStaticEvent : public CEvent
{
public:
// to ease user life, reference is made fully automatic
operator CEvent * () { return static_cast(this); }
 
CStaticEvent (IN const char *name, IN bool need_args = false);
};
 
// predefined events
static CStaticEvent EVT_NULL; // do nothing but helps generalizing msg pump loops
static CStaticEvent EVT_TIMER; // on WM_TIMER, send EVT_TIMER with event_args equals to msg.wParam (ie. the timer id)
 
// need to be declared here because of cross references between transition and state classes
class CTransition;
class CTransitionUnnamed;
 
// a state is a set of transitions to states
// (a state machine is a tree set of states)
virtual class CState
{
public:
operator const char *();
~CState (void);
 
protected: friend class CHandle;
 
// Note: VC++ bugs when using ellipses AND default arguments at the same time
bool Set (const char * name,
faction & on_entry,
faction & on_exit,
class CState & upper_state,
class CState & default_state,
class CTransition ** transition);
 
virtual bool Behave (IN CHandle *Handle, void *event_args) = 0;
 
const char * m_name;
faction m_on_entry;
faction m_on_exit;
class CState * m_upper_state;
class CState * m_default_state;

// dynamic
class CState * m_last_substate;
class CStateMachine *m_self;
 
vector m_transition;
};
 
// abstract class used to make a difference between persistent states and transcient pseudostates
virtual class CSubState : public CState
{
};
 
// abstract class used to make a difference between persistent states and transcient pseudostates
virtual class CPseudoState : public CState
{
};
 
class CContainerState : public CSubState
{
public:
 
bool Set (const char * name,
faction & on_entry,
faction & on_exit,
CContainerState & upper_state,
CSubState & initial_substate,
CTransition * transition,
...);
 
bool Set (const char * name,
CContainerState & upper_state,
CSubState & initial_substate,
CTransition * transition,
...);
private:
bool Behave (IN CHandle *Handle, void *event_args);
};
 
class CTopState : public CContainerState
{
public:

bool Set (const char * name,
faction & on_entry,
faction & on_exit,
CSubState & initial_substate,
CTransition * transition,
...);
 
bool Set (const char * name,
CSubState & initial_substate,
CTransition * transition,
...);
 
private: friend class CHandle;
bool Behave (IN CHandle *Handle, void *event_args);
 
// dynamic
class CContainerState * m_including_state;
};
 
class CIncludeState : public CContainerState
{
public:
 
bool Set (const char * name,
faction & on_entry,
faction & on_exit,
CContainerState & upper_state,
CStateMachine & substatemachine,
CTransition * transition,
...);
 
bool Set (const char * name,
CContainerState & upper_state,
CStateMachine & substatemachine,
CTransition * transition,
...);
 
private: friend class CHandle;
bool Behave (IN CHandle *Handle, void *event_args);
 
// dynamic
class CStateMachine * m_substatemachine;
};
 
class CSimpleState : public CSubState
{
public:
 
bool Set (const char * name,
faction & on_entry,
faction & on_exit,
CContainerState & upper_state,
CTransition * transition,
...);
 
bool Set (const char * name,
CContainerState & upper_state,
CTransition * transition,
...);
 
private:
bool Behave (IN CHandle *Handle, void *event_args);
};
 
class CFinalState : public CPseudoState
{
public:

bool Set (const char * name,
faction & on_entry,
CContainerState & upper_state);
 
bool Set (const char * name,
CContainerState & upper_state);
 
private:
bool Behave (IN CHandle *Handle, void *event_args);
};
 
class CDeepHistoryState : public CPseudoState
{
public:

bool Set (const char * name,
faction & on_entry,
CContainerState & upper_state,
CSubState & default_state);
 
bool Set (const char * name,
CContainerState & upper_state,
CSubState & default_state);
 
private:
bool Behave (IN CHandle *Handle, void *event_args);
};
 
class CShallowHistoryState : public CPseudoState
{
public:

bool Set (const char * name,
faction & on_entry,
CContainerState & upper_state,
CSubState & default_state);
 
bool Set (const char * name,
CContainerState & upper_state,
CSubState & default_state);
 
private:
bool Behave (IN CHandle *Handle, void *event_args);
};
 
class CChoiceState : public CPseudoState
{
public:
 
bool Set (const char * name,
faction & on_entry,
CContainerState & upper_state,
CTransitionUnnamed * transition,
...);
 
bool Set (const char * name,
CContainerState & upper_state,
CTransitionUnnamed * transition,
...);
 
private:
bool Behave (IN CHandle *Handle, void *event_args);
};
 
// a transition is triggered by an event associated to { a guard, a next state, an action }
class CTransition
{
public:
 
// used to initialize NO_MORE_TRANSITIONS
CTransition (void);
~CTransition (void);
 
// evt : the event triggering the transition
// guard : must return true to validate the event and so the transition
// next_state : the next state to the be set as the current active state when transition is validate by the event and guard
// action : the action to be done between the exit and entry actions of the destination state
CTransition (class CEvent & event,
fguard & guard,
class CState & next_state,
faction & action);
 
// evt : the event triggering the transition
// guard : must return true to validate the event and so the transition
// next_state : the next state to the be set as the current active state when transition is validate by the event and guard
CTransition (class CEvent & event,
fguard & guard,
class CState & next_state);

// evt : the event triggering the transition
// next_state : the next state to the be set as the current active state when transition is validate by the event and guard
// action : the action to be done between the exit and entry actions of the destination state
CTransition (class CEvent & event,
class CState & next_state,
faction & action);
 
// evt : the event triggering the transition
// next_state : the next state to the be set as the current active state when transition is validate by the event and guard
CTransition (class CEvent & event,
class CState & next_state);
 
// a transition internal to a state (no state change, no exit/entry action done)
// evt : the event triggering the internal transition
// guard : must return true to validate the event and so the transition
// action : the action to be done inside the current state context
CTransition (class CEvent & event,
fguard & guard,
faction & action);
 
// a transition internal to a state (no state change, no exit/entry action done)
// evt : the event triggering the transition
// action : the action to be done inside the current state context
CTransition (class CEvent & event,
faction & action);
 
// default transition if no others matches the current event
// next_state : the next state to the be set as the current active state when transition is validate by the event and guard
// action : the action to be done inside the current state context
CTransition (class CState & next_state,
faction & action);
 
// default transition if no others matches the current event
// next_state : the next state to the be set as the current active state when transition is validate by the event and guard
CTransition (class CState & next_state);
 
// default internal transition if no others matches the current event (no state change, no exit/entry action done)
// action : the action to be done inside the current state context
CTransition (faction & action);
 
// assignment operator needed by vector template
virtual CTransition& operator=( const CTransition &t );
 
// comparison operators needed by delegate template
operator == (CTransition &transition);
operator != (CTransition &transition);
 
protected: friend class CHandle;
class CEvent m_event;
fguard m_guard;
class CState * m_next_state;
faction m_action;
 
// dynamic
UINT_PTR m_timer;
DWORD m_timer_duration_ms; // milliseconds
ftimer m_ftimer;
bool guard_timer (void * event_args);
};
 
// a transition not triggered by an event but only by its guard
class CTransitionUnnamed : public CTransition
{
public:
// guard : must return true to validate the transition
// next_state : the next state to the be set as the current active state when transition is validate by the guard
// action : the action to be done between the exit and entry actions of the destination state
CTransitionUnnamed (
fguard & guard,
class CState & next_state,
faction & action);
 
// guard : must return true to validate the transition
// next_state : the next state to the be set as the current active state when transition is validate by the guard
CTransitionUnnamed (
fguard & guard,
class CState & next_state);
 
// guard : must return true to validate the internal transition
// action : the action to be done inside the current state context
CTransitionUnnamed (
fguard & guard,
faction & action);
};
 
// a transition internal to a state (no state change, no exit/entry action done) that defers the event to the next state change
class CTransitionDeferEvent : public CTransition
{
public:
// evt : the event to be deferred
// guard : (delegate) must return true for the event to be deferred
CTransitionDeferEvent (class CEvent & event,
fguard & guard);
 
// evt : the event to be deferred
CTransitionDeferEvent (class CEvent & event);
 
// a transition internal to a state (no state change, no exit/entry action done)
// that defers the event to the next state change by default
CTransitionDeferEvent (void);
};
 
// a transition performing an action and a state change when the timeout occured
class CTransitionTimer : public CTransition
{
public:
// duration : value of the timeout in milliseconds
// next_state : the next state to the be set as the current active state when transition timed out
// action : the action to be done between the exit and entry actions of the destination state
CTransitionTimer (
DWORD duration_ms,
class CState & next_state,
faction & action);
 
// duration : value of the timeout in milliseconds
// next_state : the next state to the be set as the current active state when transition timed out
CTransitionTimer (
DWORD duration_ms,
class CState & next_state);
 
// ftimer : function which returns the timeout value in milliseconds
// next_state : the next state to the be set as the current active state when transition timed out
// action : the action to be done between the exit and entry actions of the destination state
CTransitionTimer (
ftimer & fduration,
class CState & next_state,
faction & action);
 
// ftimer : function which returns the timeout value in milliseconds
// next_state : the next state to the be set as the current active state when transition timed out
CTransitionTimer (
ftimer & fduration,
class CState & next_state);
 
// duration : value of the timeout in milliseconds
// action : (delegate) the action to be done between the exit and entry actions of the destination state
CTransitionTimer (
DWORD duration_ms,
faction & action);
 
// ftimer : function which returns the timeout value in milliseconds
// action : (delegate) the action to be done between the exit and entry actions of the destination state
CTransitionTimer (
ftimer & fduration,
faction & action);
};
 
static CTransition NO_MORE_TRANSITIONS;
 
// fire an event with or without args to a state machine (in the current thread context)
bool Fire (IN CEvent &event, void *event_args = NULL);
 
// retrieve the variable parameters as a (va_list *) in action function
// (be careful, events can't be deferred because args are not persistent)
bool Fire (IN CEvent *event, ...);
 
// send an event with or without args to a state machine internal thread
// event pointer must be allocated with "new" and args must be persistent
// Note that whether a Send() is called within an action,
// the event will be deferred to comply with the "step to completion" rule
bool Send (IN CEvent *event, void *event_args = NULL, bool high_priority = false);
 
// used usually in an action transition for forwarding the event to sub-machines
// (may also be the last sent if not called within an action function context)
CEvent * GetCurrentEvent (void);
 
// used in guards to synchronize concurrent machines
bool Is_In (IN CState &state);
 
// return the state machine name
operator const char *();
 
// useful to add traces in actions
void Error (IN char *printf_format, ...);
void Trace (IN char *printf_format, ...);
 
CStateMachine::CStateMachine (IN CTopState &top_state,
IN char *name = "",
IN FILE *traceout = NULL,
IN FILE *errorout = NULL);

virtual CStateMachine::~CStateMachine ();
 
// initialize the machine at its initial active state
// and within or not its own thread
bool Run(bool thread = false);
 
private: friend CHandle; friend CIncludeState;
CHandle *Handle;
};
}
 
#endif // MOD_CSTATEMACHINE_H

GeneralRe: Very interesting...memberboutblock27 Jan '06 - 2:40 
Declaration of a StateChart would look like this :
 
class CSMTest::CHandle : public CStateMachine
{
public: friend CSMTest;
 
// finite list of states
CTopState TOP_STATE;
CContainerState STATE_1;
CDeepHistoryState STATE_1_H;
CSimpleState STATE_1_1;
CSimpleState STATE_1_2;
CSimpleState STATE_2;
 
// prototypes of the transition functions
bool Process_Interrupt (MSG *args);
bool Ack_Interrupt (MSG *args);
bool Set_State_High (MSG *args);
bool Set_State_Low (MSG *args);
bool Count_Signal (va_list *args);
bool Timeout_Occured (MSG *args);
bool Entry_Action (MSG *args);
bool Exit_Action (MSG *args);
 
DWORD Timer_Duration (void *event_args);
 
// a template to help casting devired class transition functions
// to its CStateMachine parent class generic prototype
template
faction CSM_ACTION(PARAM_TYPE f) { return faction(*this, reinterpret_cast(f)); }
 
// a define to help casting devired class guard functions
// to its CStateMachine parent class generic prototype
template
fguard CSM_GUARD(PARAM_TYPE f) { return fguard(*this, reinterpret_cast(f)); }
 
// a define to help casting devired class timer functions
// to its CStateMachine parent class generic prototype
template
ftimer CSM_TIMER(PARAM_TYPE f) { return ftimer(*this, reinterpret_cast(f)); }
 
// ctor/ dtor
CHandle::CHandle (char *name, CSMTest *self);
};
 
// ---- declaration of static data members ----
 
CStateMachine::CStaticEvent CSMTest::EVT_INTERRUPT ("EVT_INTERRUPT");
 
// ---- declaration of the transition functions ----
 
bool CSMTest::CHandle::Process_Interrupt (MSG *args)
{
return true;
}
 
bool CSMTest::CHandle::Ack_Interrupt (MSG *args)
{
return true;
}
 
bool CSMTest::CHandle::Set_State_High (MSG *args)
{
EVT_PULSE *event = (EVT_PULSE *) GetCurrentEvent();
 
Trace("w = %lu, l = %lu", event->m_msg.wParam, event->m_msg.lParam);
 
return true;
}
 
bool CSMTest::CHandle::Set_State_Low (MSG *args)
{
EVT_PULSE *event = (EVT_PULSE *) GetCurrentEvent();
 
Trace("w = %lu, l = %lu", event->m_msg.wParam, event->m_msg.lParam);

return true;
}
 
bool CSMTest::CHandle::Count_Signal (va_list *args)
{
MSG msg = {0};

msg.wParam = va_arg (*args, WPARAM);
msg.lParam = va_arg (*args, LPARAM);
 
Trace("w = %lu, l = %lu", msg.wParam, msg.lParam);
 
return true;
}
 
// generic entry action
bool CSMTest::CHandle::Entry_Action (MSG *args)
{
return true;
}
 
// generic exit action
bool CSMTest::CHandle::Exit_Action (MSG *args)
{
return true;
}
 
DWORD CSMTest::CHandle::Timer_Duration (void *event_args)
{
return 5000;
}
 
bool CSMTest::CHandle::Timeout_Occured (MSG *args)
{
Trace ("Timer occured !");
 
return true;
}
 
/***********************/
/* FONCTIONS PUBLIQUES */
/***********************/
 
CSMTest::CSMTest (char *name)
{
m_Handle = new CHandle (name, this);

if (m_Handle->Run(true) == false)
throw;
}
 
CSMTest::CHandle::CHandle (char *name, CSMTest *self)
: CStateMachine (TOP_STATE, name, stdout)
{
TOP_STATE.Set (
"TOP_STATE", CSM_ACTION (Entry_Action), CSM_ACTION (Exit_Action), STATE_1,
&NO_MORE_TRANSITIONS
);
 
STATE_1.Set (
"STATE_1",CSM_ACTION (Entry_Action), CSM_ACTION (Exit_Action), TOP_STATE, STATE_1_1,
new CTransition ( EVT_INTERRUPT, STATE_2, CSM_ACTION (Process_Interrupt) ),
&NO_MORE_TRANSITIONS
);
 
STATE_1_1.Set (
"STATE_1_1", CSM_ACTION (Entry_Action), CSM_ACTION (Exit_Action), STATE_1,
new CTransition ( EVT_PULSE(), STATE_1_2, CSM_ACTION (Set_State_High) ),
new CTransition ( EVT_SIGNAL(), CSM_ACTION (Count_Signal) ),
&NO_MORE_TRANSITIONS
);
 
STATE_1_2.Set (
"STATE_1_2", CSM_ACTION (Entry_Action), CSM_ACTION (Exit_Action), STATE_1,
new CTransitionDeferEvent ( EVT_INTERRUPT ),
new CTransition ( EVT_PULSE(), STATE_1_1, CSM_ACTION (Set_State_Low) ),
new CTransitionDeferEvent ( EVT_SIGNAL() ),
new CTransitionTimer (CSM_TIMER (Timer_Duration), STATE_1_1, CSM_ACTION (Timeout_Occured) ),
&NO_MORE_TRANSITIONS
);
 
STATE_1_H.Set (
"STATE_1_H", CSM_ACTION (Entry_Action), STATE_1, STATE_1_1
);
 
STATE_2.Set (
"STATE_2", CSM_ACTION (Entry_Action), CSM_ACTION (Exit_Action), TOP_STATE,
new CTransition ( EVT_END_OF_INTERRUPT(), STATE_1_H, CSM_ACTION (Ack_Interrupt) ),
new CTransition ( EVT_SIGNAL(), STATE_1 ),
&NO_MORE_TRANSITIONS
);
}
 
CSMTest::~CSMTest (void)
{
// this will automatically delete all allocated states and kill thread
delete m_Handle;
}
 
bool CSMTest::Send (IN CStateMachine::CEvent *event, IN void *args)
{
return m_Handle->Send (event, args);
}

GeneralRe: Very interesting...memberboutblock27 Jan '06 - 2:43 
And usage :
 
void main (void)
{
CSMTest Test("TEST");
UINT choice;
MSG msg;
msg.wParam = 1;
msg.lParam = 2;
 
do
{
cout << "WM_QUIT = 0" << endl
<< "WM_INTERRUPT = 1" << endl
<< "WM_END_OF_INTERRUPT = 2" << endl
<< "WM_PULSE = 3" << endl
<< "WM_SIGNAL = 4" << endl
<< "Send : ";
cin >> choice;
 
switch (choice)
{
 
case 1:
// sending a static event
Test.Send (CSMTest::EVT_INTERRUPT);
break;
 
case 2:
Test.Send (new CSMTest::EVT_END_OF_INTERRUPT());
break;
 
case 3:
Test.Send (new CSMTest::EVT_PULSE(&msg));
break;
 
case 4:
Test.Send (new CSMTest::EVT_SIGNAL(1, 2));
break;
}
}
while (choice != 0);
}
GeneralRe: Very interesting...membergschultz30 Jan '06 - 5:23 
Sounds very full-featured. Perhaps that would make a good submission for CodeProject.
 
gschultz
GeneralAn excellent start for an under-publicized methodology.memberWREY30 Aug '05 - 6:16 
StateChart has recently (like about 3 to 4 years) come upon the scene as a very useful and valuable alternative to FlowCharting. It describes the various stages every application go through when being used (meaning, when the application is running). Those stages are what StateCharts present as 'states'.
 
Describing 'states', forces everyone involved in the developmental stage of the project to focus (in a sort of hierarchical manner) on what the application is suppose to accomplish every step of the way, and the order in which those steps are to be followed.
 
Whether you are a user, analyst, programmer, or the person who writes up the schedule when certain programs get run, StateChart has a place for everyone to participate in the entry of their specific set of information.
 
On the surface the entire concept might appear rigid and not very flexible, but that is not true. There is enormous flexibility in the methodology that is very much in sync with OOD and OOP. Therefore, whether you know it or not, if you are programming in Windows, or using Visual C++, or any Object Oriented language, you are already writing your programs according to 'states', and StateChart is the method through which you can record and show how your applications function to whatever level of detail you wish to describe it.
 
Personally, I have just started using StateCharts recently (like a few months ago), and there's no turning back for me when it comes to describing and explaining how one program, a group of programs, or an entire system functions.
 
It is a very productive methodology to become acquainted with and use, especially for those times when things in your program (or system) are not working well and you need something solid on which to fall back on. Best of all, the learning curve is not very difficult since almost everything about it is repetitive.
 
Picture a mosaic with each piece of the mosaic a 'state'. You describe each particular piece, where it's located, how to get there, and where next from there you go. That's StateChart!!
 
Big Grin | :-D
 
William
 
Fortes in fide et opere!
 
-- modified at 12:26 Tuesday 30th August, 2005
GeneralRe: An excellent start for an under-publicized methodology.memberJim Crafton2 Sep '05 - 13:25 
How is this different from sequence diagrams? I've always found those really, really useful, and I'm not really familiar at all with state charts.
 
¡El diablo está en mis pantalones! ¡Mire, mire!
 
Real Mentats use only 100% pure, unfooled around with Sapho Juice(tm)!
 
SELECT * FROM User WHERE Clue > 0
0 rows returned

Save an Orange - Use the VCF!
GeneralRe: An excellent start for an under-publicized methodology.memberWREY6 Sep '05 - 12:07 
Sequence diagrams are like flowcharts, or Finite State Machines.
 
With flowcharts, situations can quickly become overcrowded and confusing since the person drawing the flowchart might (from time to time) succumb to the temptation of (at one time) become really detailed about the logic flow he/she is presenting, and then at other times become less detailed. IOW, there could be a mixture of more detailed, less detailed, kind of detailed, not as detailed, and a whole variety of ways the behavior of what you're presenting can become. IOW, it is not uniform, plus there could be a lot of repetition of paths you're describing (which is what would cause it to become overcrowded and confusing).
 
With State Machines (DFSM, or NFSM) you don't have the same problem as flowcharting because you are confined to one level of detail: States. The drawback one has when dealing with FSM, is the lack of tools (or primitives, like descriptive notations) that would allow you to describe what you're trying to do to any level of depth that you might want to analyze what it is you're doing. IOW, FSM sort of force you to remain at a high level of description for what it is you're trying to do.
 
Enter StateCharts: It forces you to retain the 'state' level of description for what it is you're trying to describe (which is very good), but it also offers you the necessary notations that would allow you to get down as deeply as you wish to go in doing your analysis. So you have rigidness at the 'state' level, but enormous flexibility to describe however deeply you wish to go in your analysis (because at each level of depth, you must apply the behavior of 'state').
 
With StateCharts you have the best of both worlds. You have the discipline of sticking to 'state' description, and you also have the flexibility of going however detailed you wish to go.
 
Big Grin | :-D
 
William
 
Fortes in fide et opere!
 
-- modified at 18:22 Tuesday 6th September, 2005
AnswerRe: An excellent start for an under-publicized methodology.membergschultz7 Sep '05 - 3:16 
Sequence diagrams are most similar to flowcharts, in that they show the sequence of invocations of methods/routines. Unlike flowcharts, which can sprawl out in two dimensions, sequence diagrams designate that the vertical dimension (usually) shows time/order of invocation, while the horizontal dimension (usually) shows different objects. This makes sequence diagrams easier to organize and read than flowcharts. (I haven't drawn a flowchart in almost 20 years!)
 
Statecharts (and finite state machines) show "state", i.e., the stable modes or situations that a piece of code or a system can be in, along with the transitions between those states. So a statechart for a traffic signal would have states {green, yellow, red}. An invoice might have the states {created, mailed, paid, written-off}. Statecharts would then show what could cause transitions among those states.

 
gschultz
GeneralStateChart vs. FlowChartmemberKochise23 Aug '07 - 21:57 
Hey WREY, take also into account that FlowChart are perfectly representative of the way Imperative languages works, whereas StateChart are perfect towards Functional languages. I think if Functional languages were more widely known and used, StateChart would also have more success because more easily usable and mirror-copy of the code...
 
Kochise
 
In Code we trust !

GeneralStateChart vs. FlowChartmemberNick Alexeev9 Feb '09 - 17:01 
WREY wrote:
StateChart has recently (like about 3 to 4 years) come upon the scene as a very useful and valuable alternative to FlowCharting...

 
StateCharts are an addition to FlowCharts, rather than alternative. StateCharts display the big picture better than flowcharts, and they are better suited for describing interactive systems. FlowCharts are more useful for describing the fine details, e.g. how a particular state responds to a particular message.

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web03 | 2.6.130523.1 | Last Updated 23 Aug 2007
Article Copyright 2005 by GDSchultz
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid