Introduction
A reactive system is characterized by being, to a large extent, event-driven, continuously having to react to external and internal stimuli. Examples include telephones, automobiles, communication networks, computer Operating Systems, and missiles. The problem is rooted in the difficulty of describing reactive behavior in ways that are clear and realistic, and at the same time formal and rigorous, sufficiently so to be amenable to detailed computerized simulation [David Harel].
Harel StateCharts are gaining widespread usage since a variant has become part of the Unified Modeling Language. The diagram type allows the modeling of superstates, concurrent states, and activities as part of a state.
Problems with Conventional FSM
Classic Mealy-Moore state machine modeling techniques require the creation of distinct nodes for every valid combination of parameters that define the state. This can lead to a very large number of nodes and transitions between nodes for all but the simplest of systems. This complexity reduces the readability of the state diagram.
Another of the limitations of modeling a computer system as a conventional state machine is the lack of support for concurrent constructs. Traditional state machine modeling is based on sequential transitions from one state to the next. Concurrent systems cannot be modeled in this manner as various aspects of the system may be in different states.
It is seen that the limitations inherent in state machine models include the inherent complexity which occurs as the number of states increases, and also the modeling of concurrent systems. This article presents a commercial-grade cross-platform Harel UML StateChart Open-Source application framework named StateWizard for concurrent, distributed, and real-time reactive system development with simplicity, efficiency, and scalability. The following sections describe the ways to solve complex, reactive system problems with the following Harel StateChart features:
- Hierarchical state machines
- Supports large scale state machines with hundreds of states through separating state tree definitions to several C/C++ files
- State history information and history transitions
- Guarded transitions on event handling
- Conditional pseudo-states
- Join pseudo-states
- Orthogonal states
- Built-in state timers
How to Reduce the Size of the Representation?
The solution is hierarchical state machines. In conventional state machine design, all states are considered at the same level. The design does not capture the commonality that exists among states. In real life, many states handle most messages in a similar fashion and differ only in the handling of a few key messages. Even when the actual handling differs, there is still some commonality. Hierarchical state machine design captures the commonality by organizing the states as a hierarchy. The states at the higher level in the hierarchy perform the common message handling, while the lower level states inherit the commonality from the higher level ones and perform the state specific functions.
The state machine state hierarchy is based on a parent-child relationship, which is depicted by the arrangement of the tree branches. For example, in the following figure: State Hierarchy in Tree Form, the node Player in the state tree is a root state which has two children: PowerDown
and PowerUp
. Meanwhile, the node PowerUp
is also a parent of three children, and these children are referred as sibling states among them.
This hierarchical organization means that when a transition from state PowerDown
to PowerUp
is triggered, the State Machine de-activates the state PowerDown
and its children (if any) and activates PowerUp
and one or more of its children (if any).
Player
PowerDown
(Init) PowerUp
Playing
(Init) Pause
Record
Figure: State Hierarchy in Tree Form
The same hierarchy may be represented in a nested chart form. In this format, the hierarchy among states are shown by nesting child states within their parent state.
Figure: State Hierarchy in Chart Form
Using the StateWizard application framework API set, the Player
state machine is defined as below:
#define SME_CURR_DEFAULT_PARENT Player
SME_BEGIN_ROOT_COMP_STATE_DEF(Player, PlayerEntry, PlayerExit)
SME_ON_INIT_STATE(SME_NULL_ACTION, PowerDown)
SME_END_STATE_DEF
SME_BEGIN_LEAF_STATE_DEF_P(PowerDown, PowerDownEntry, PowerDownExit)
SME_ON_EVENT(EXT_EVENT_ID_POWER, OnPowerDownEXT_EVENT_ID_POWER, PowerUp)
SME_END_STATE_DEF
SME_BEGIN_SUB_STATE_DEF_P(PowerUp)
SME_ON_EVENT(EXT_EVENT_ID_POWER,OnPowerUpEXT_EVENT_ID_POWER,PowerDown)
SME_END_STATE_DEF
SME_END_COMP_STATE_DEF(Player)
#define SME_CURR_DEFAULT_PARENT PowerUp
SME_BEGIN_COMP_STATE_DEF(PowerUp, Player, PowerUpEntry, PowerUpExit)
SME_ON_INIT_STATE(OnPowerUpInitChild, Playing)
SME_END_STATE_DEF
SME_BEGIN_LEAF_STATE_DEF_P(Playing, PlayingEntry, PlayingExit)
SME_ON_EVENT(EXT_EVENT_ID_PAUSE_RESUME,OnPlayingEXT_EVENT_ID_PAUSE_RESUME,Pause)
SME_END_STATE_DEF
SME_BEGIN_LEAF_STATE_DEF_P(Pause, PauseEntry, PauseExit)
SME_ON_EVENT(EXT_EVENT_ID_START_RECORD,OnPauseEXT_EVENT_ID_START_RECORD,Record)
SME_END_STATE_DEF
SME_BEGIN_LEAF_STATE_DEF_P(Record,RecordEntry,RecordExit)
SME_ON_EVENT(EXT_EVENT_ID_STOP_RECORD,OnRecordEXT_EVENT_ID_STOP_RECORD,Pause)
SME_END_STATE_DEF
SME_END_COMP_STATE_DEF(PowerUp)
Root State is the uppermost state, bearing the application name. As you add states, the state tree grows downwards from the root state.
For example, the Player
composite state is the root state of the Player
state machine. It is defined as below. The PlayerEntry
and PlayerExit
are the function pointers to the actions on the root state entry and exit, respectively.
SME_BEGIN_ROOT_COMP_STATE_DEF(Player, PlayerEntry, PlayerExit)
Parent State is a state that branches into one or more child states. A parent can have several children, but a child has only one parent.
For example, the PowerUp
composite state's parent state is the state Player
. Define the PowerUp
state as below:
SME_BEGIN_COMP_STATE_DEF(PowerUp, Player, PowerUpEntry, PowerUpExit)
Initial Child State identifies the initial state for state machines that have substates. This child must occur if and only if the machine has one or more states or parallel children.
For example, an initial child state, Playing
, of the PowerUp
composite state is defined as below. The OnPowerUpInitChild
is a function pointer to the initial child state entry action.
SME_BEGIN_COMP_STATE_DEF(PowerUp, Player, PowerUpEntry, PowerUpExit)
SME_ON_INIT_STATE(OnPowerUpInitChild, Playing)
SME_END_STATE_DEF
Sibling States are the child states with a common parent.
How to Scale State Machines?
In complex large-scale reactive systems, a state machine object could be composed of more than 100 states, it is not a good way to place all state definitions in a source file.
The definitions of different composite states could be spread in source files.
For example, the Player
composite state could be defined in Player.c. There is a sub-state named PowerUp
which is a composite state too. The SME_BEGIN_SUB_STATE_DEF_P(PowerUp)
block is declared in the Player
composite state definition body. The common attributes and behaviours of the PowerUp
composite state are defined in the SME_BEGIN_SUB_STATE_DEF_P(PowerUp)
block, for example state transition from PowerUp
to PowerDown
, no matter what the current active state is (PowerUp
, Pause
) or (PowerUp
, Playing
).
#define SME_CURR_DEFAULT_PARENT Player
SME_BEGIN_ROOT_COMP_STATE_DEF(Player, PlayerEntry, PlayerExit)
SME_ON_INIT_STATE(SME_NULL_ACTION, PowerDown)
SME_END_STATE_DEF
SME_BEGIN_LEAF_STATE_DEF_P(PowerDown, PowerDownEntry, PowerDownExit)
SME_ON_EVENT_WITH_GUARD(EXT_EVENT_ID_POWER, Guard1_func,
OnPowerDownEXT_EVENT_ID_POWER, Join1)
SME_END_STATE_DEF
SME_BEGIN_SUB_STATE_DEF_P(PowerUp)
SME_ON_EVENT(EXT_EVENT_ID_POWER,OnPowerUpEXT_EVENT_ID_POWER,PowerDown)
SME_END_STATE_DEF
SME_END_COMP_STATE_DEF(Player)
And, the detail PowerUp
composite state with its children could be defined in Player2.c.
#define SME_CURR_DEFAULT_PARENT PowerUp
SME_BEGIN_COMP_STATE_DEF(PowerUp, Player, PowerUpEntry, PowerUpExit)
SME_ON_INIT_STATE(OnPowerUpInitChild, Playing)
SME_ON_STATE_TIMEOUT_INTERNAL_TRAN(3000, PowerUpTimeOut)
SME_END_STATE_DEF
SME_BEGIN_LEAF_STATE_DEF_P(Playing, PlayingEntry, PlayingExit)
SME_ON_EVENT(EXT_EVENT_ID_PAUSE_RESUME,OnPlayingEXT_EVENT_ID_PAUSE_RESUME,Pause)
SME_END_STATE_DEF
SME_BEGIN_LEAF_STATE_DEF_P(Pause, PauseEntry, PauseExit)
SME_ON_EVENT(EXT_EVENT_ID_PAUSE_RESUME,OnPauseEXT_EVENT_ID_PAUSE_RESUME,Playing)
SME_END_STATE_DEF
SME_END_COMP_STATE_DEF(PowerUp)
How to Activate a State Machine Instance?
A state machine application is an instance of a state machine, or an instance of a region which is an orthogonal part of either a composite state or a state machine. Applications can have one of two modes: active or inactive. Active applications are the ones running on the state machine at a given time, whereas inactive applications are not. In other words, only active applications can handle events. A state machine engine is responsible for managing these applications and dispatching events to specific applications.
The following macro defines an application instance, Player1
, based on the Player
state machine. SmeActivateObj()
activates the Player1
instance. The second parameter is the parent application of the application to be activated, with NULL
standing for no parent application.
SME_OBJ_DEF(Player1, Player)
SmeActivateObj(&Player1,NULL);
How to Conveniently Describe State Concurrency?
The solution is to allow Orthogonal states to operate concurrently.
Composite states have one or more regions for substates. A region is simply a container for substates.
A composite state is a state that consists of sub-states. A composite state can be decomposed using AND-relationships into two or more concurrent regions which are containers for sub-states, or using OR-relationships into mutually exclusive disjoint sub-states.
Orthogonal state: If a composite state can be decomposed using AND-relationships into two or more concurrent regions, it is called Orthogonal State.
The following macros defines an Orthogonal state named OrthoState
as a child of the Player
parent state. The state entry/exit actions are OrthoStateEntry
and OrthoStateExit
, respectively. The orthogonal state is composed of three regions:
PlayerReg1
as an instance of the Player
state machine, running at the same thread as its parent with 0 priority. PlayerReg2
as an instance of the Player
state machine, running at a separate thread with 0 priority. PlayerReg3
as an instance of the Player
state machine, running at a separate thread with 0 priority.
SME_BEGIN_ROOT_COMP_STATE_DEF(Player, PlayerEntry, PlayerExit)
SME_ON_INIT_STATE(SME_NULL_ACTION, PowerDown)
SME_END_STATE_DEF
SME_BEGIN_ORTHO_SUB_STATE_DEF_P(OrthoState)
SME_ON_EVENT(EXT_EVENT_ID_POWER, OnPowerDownEXT_EVENT_ID_POWER, Join1)
SME_END_STATE_DEF
SME_BEGIN_ORTHO_COMP_STATE_DEF(OrthoState, Player, OrthoStateEntry, OrthoStateExit)
SME_REGION_DEF(PlayerReg1,Player,SME_RUN_MODE_PARENT_THREAD,0)
SME_REGION_DEF(PlayerReg2,Player,SME_RUN_MODE_SEPARATE_THREAD,0)
SME_REGION_DEF(PlayerReg3,Player,SME_RUN_MODE_SEPARATE_THREAD,0)
SME_END_ORTHO_STATE_DEF
With the StateWizard, you may define a transition from/to an Orthogonal state, using SME_BEGIN_ORTHO_SUB_STATE_DEF()
. However, you cannot define an explicit entry to one of the child states of a region. And, all regions work in the form of applications which run concurrently. On an Orthogonal state entry, all regions will activate automatically. And on state exit, all regions will de-activate automatically. If several regions run at their separate threads, the Orthogonal state will post SME_EVENT_EXIT_LOOP
events to these regions and wait for these threads to exit.
How to Model State Built-in Timer for a Real-time System?
Timers need to be modeled in real-time systems. The engine supports two kinds of timers, state-built-in timers and regular timers.
State-built-in timers work tightly with the state machine. They are managed by the engine. On a state entry, automatically start the built-in timer, if it is available. On a state exit, stop it. On timeout, they trigger SME_EVENT_STATE_TIMER
events. The engine will take actions based on the state machine definition using SME_ON_STATE_TIMEOUT
.
Regular timers are explicit timers. Developers have to start or stop them using API calls. On timeout, they trigger SME_EVENT_TIMER
events. The engine provides two kinds of handling modes, callback function call or event handling, which are defined as below:
enum {SME_TIMER_TYPE_CALLBACK, SME_TIMER_TYPE_EVENT};
An explicit callback function should be defined for a callback function mode timer. This mode is independent from state machine definitions. For a timer event mode, SME_ON_EVENT(SME_EVENT_TIMER, handler, new state)
should be defined to handle a timeout event.
The following sample defines a 3000-ms timer in the Player state and a 9000-ms timer in the Playing state. On timeout, a PowerUpTimeOut
action will be invoked. On the Playing
state entry, start it. On timeout, transit to the Pause
state automatically with the PlayingTimeOut
action execution.
#define SME_CURR_DEFAULT_PARENT PowerUp
SME_BEGIN_COMP_STATE_DEF(PowerUp, Player, PowerUpEntry, PowerUpExit)
SME_ON_INIT_STATE(OnPowerUpInitChild, Playing)
SME_ON_STATE_TIMEOUT_INTERNAL_TRAN(3000, PowerUpTimeOut)
SME_END_STATE_DEF
SME_BEGIN_LEAF_STATE_DEF_P(Playing, PlayingEntry, PlayingExit)
SME_ON_EVENT(EXT_EVENT_ID_PAUSE_RESUME,
OnPlayingEXT_EVENT_ID_PAUSE_RESUME,Pause)
SME_ON_INTERNAL_TRAN_WITH_GUARD(SME_EVENT_TIMER,
GuardTimer2_func,OnTimer2Proc)
SME_ON_STATE_TIMEOUT(9000, PlayingTimeOut, Pause)
SME_END_STATE_DEF
SME_BEGIN_LEAF_STATE_DEF_P(Pause, PauseEntry, PauseExit)
SME_ON_EVENT(EXT_EVENT_ID_PAUSE_RESUME,
OnPauseEXT_EVENT_ID_PAUSE_RESUME,Playing)
SME_END_STATE_DEF
SME_END_COMP_STATE_DEF(PowerUp)
How to Model Pseudo States?
A PseudoState is an abstraction of different types of nodes in the state machine graph which represent transient points in transition paths from one state to another (e.g., branch and fork points). Pseudo states are used to construct complex transitions from simple transitions. For example, by combining a transition entering a fork pseudo state with a set of transitions exiting the fork pseudo state, we get a complex transition that leads to a set of target states.
Conditional (Fork) PseudoStates are a notational shorthand for multiple exiting transitions all triggered by the same event but each having different guards.
Join PseudoState is a state with several incoming transitions and a single outgoing one.
Using the StateWizard application framework API set, a Conditional PseudoState Cond1
and a Join PseudoState Join1
are defined as below. The Cond1
pseudo state has three forks: COND1
, COND2
, and COND_ELSE
. The exit destination state is dependent on the return value of the function Cond1_func
. The action on entry to the Join1
pseudo state is the JoinAct
function.
#define SME_CURR_DEFAULT_PARENT Player
SME_BEGIN_ROOT_COMP_STATE_DEF(Player, PlayerEntry, PlayerExit)
SME_ON_INIT_STATE(SME_NULL_ACTION, PowerDown)
SME_END_STATE_DEF
....
SME_BEGIN_COND_STATE_DEF_P(Cond1, Cond1_func)
SME_ON_EVENT(COND_EV_COND1, CondAct1, Playing)
SME_ON_EVENT(COND_EV_COND2, CondAct2, Pause)
SME_ON_EVENT(SME_EVENT_COND_ELSE, CondActElse, PowerUp)
SME_END_STATE_DEF
SME_BEGIN_JOIN_STATE_DEF_P(Join1)
SME_ON_JOIN_TRAN(JoinAct, Cond1)
SME_END_STATE_DEF
...
StateWizard Event Handling Workflow
Event Handling Loop
The SmeRun()
function is the state machine engine event handling loop function. If there is no event in the queue, this function waits for external event. If any event is triggered, it will dispatch it to the appropriate applications, and keep state machines taking appropriate transitions and actions.
If an external event is triggered, it will be transformed to an internal event via an external event waiting function which is an OS virtual layer function.
If an external event destroy function is hooked, SmeRun()
will call this function to destroy external event when an external event is consumed.
void SmeRun(void)
{
SME_EVENT_T ExtEvent;
SME_EVENT_T *pEvent=NULL;
SME_OBJ_T *pObj;
SME_THREAD_CONTEXT_PT pThreadContext=NULL;
if (g_pfnGetThreadContext)
pThreadContext = (*g_pfnGetThreadContext)();
if (!pThreadContext) return;
if (!g_pfnGetExtEvent) return;
pObj = pThreadContext->pActObjHdr;
while (TRUE)
{
pEvent = GetEventFromQueue();
if (pEvent == NULL)
{
if (FALSE == (*g_pfnGetExtEvent)(&ExtEvent))
return;
pEvent = &ExtEvent;
pEvent->nOrigin = SME_EVENT_ORIGIN_EXTERNAL;
if (pThreadContext->fnOnEventComeHook)
(*pThreadContext->fnOnEventComeHook)
(SME_EVENT_ORIGIN_EXTERNAL, pEvent);
}else
{
if (pThreadContext->fnOnEventComeHook)
(*pThreadContext->fnOnEventComeHook)
(SME_EVENT_ORIGIN_INTERNAL, pEvent);
}
do {
DispatchEventToApps(pThreadContext, pEvent);
if (pEvent != &ExtEvent)
SmeDeleteEvent(pEvent);
pEvent = GetEventFromQueue();
if (pEvent != NULL)
{
if (pThreadContext->fnOnEventComeHook)
(*pThreadContext->fnOnEventComeHook)
(SME_EVENT_ORIGIN_INTERNAL, pEvent);
}
else
{
break;
}
} while (TRUE);
if (g_pfnDelExtEvent)
{
(*g_pfnDelExtEvent)(&ExtEvent);
SmeDeleteEvent(&ExtEvent);
}
}
}
The SmeSetExtEventOprProc()
function installs platform related external event operating functions. They work as the StateWizard OS Virtual Layer. The event handling loop function, SmeRun()
calls the fnGetExtEvent
function to get an external event and free it after handling it via fnDelExtEvent
void SmeSetExtEventOprProc(SME_GET_EXT_EVENT_PROC_T fnGetExtEvent,
SME_DEL_EXT_EVENT_PROC_T fnDelExtEvent,
SME_POST_THREAD_EXT_INT_EVENT_PROC_T fnPostThreadExtIntEvent,
SME_POST_THREAD_EXT_PTR_EVENT_PROC_T fnPostThreadExtPtrEvent,
SME_INIT_THREAD_EXT_MSG_BUF_PROC_T fnInitThreadExtMsgBuf,
SME_INIT_THREAD_EXT_MSG_BUF_PROC_T fnFreeThreadExtMsgBuf)
{
g_pfnGetExtEvent = fnGetExtEvent;
g_pfnDelExtEvent = fnDelExtEvent;
g_pfnPostThreadExtIntEvent = fnPostThreadExtIntEvent;
g_pfnPostThreadExtPtrEvent = fnPostThreadExtPtrEvent;
g_pfnInitThreadExtMsgBuf = fnInitThreadExtMsgBuf;
g_pfnFreeThreadExtMsgBuf = fnFreeThreadExtMsgBuf;
}
External Event Management as OS Virtual Layer
The SmeSetExtEventOprProc()
function installs platform related external event operating functions. They work as the StateWizard OS Virtual Layer. The event handling loop function, SmeRun()
calls the fnGetExtEvent
function to get an external event and free it after handling it via fnDelExtEvent
.
BOOL XGetExtEvent(SME_EVENT_T* pEvent)
{
X_EXT_MSG_T NativeMsg;
int ret=0;
SME_THREAD_CONTEXT_T* p = XGetThreadContext();
X_EXT_MSG_POOL_T *pMsgPool;
if (NULL==pEvent || NULL==p || NULL==p->pExtEventPool)
return FALSE;
pMsgPool = (X_EXT_MSG_POOL_T*)(p->pExtEventPool);
memset(&NativeMsg,0,sizeof(NativeMsg));
while (TRUE)
{
ret = XWaitForEvent(&(pMsgPool->EventToThread),
&(pMsgPool->MutexForPool),
(XIS_CODITION_OK_T)XIsMsgAvailable, NULL,
(XTHREAD_SAFE_ACTION_T)XGetMsgFromBuf,&NativeMsg);
if (NativeMsg.nMsgID == SME_EVENT_EXIT_LOOP)
{
return FALSE; }
#ifdef SME_WIN32
#else
else if (SME_EVENT_TIMER == NativeMsg.nMsgID
&& SME_TIMER_TYPE_CALLBACK == NativeMsg.Data.Int.nParam1)
{
SME_TIMER_PROC_T pfnCallback =
(SME_TIMER_PROC_T)(NativeMsg.Data.Int.nParam2);
(*pfnCallback)(NativeMsg.pDestObj, NativeMsg.nSequenceNum);
}
#endif
else {
memset(pEvent,0,sizeof(SME_EVENT_T));
pEvent->nEventID = NativeMsg.nMsgID;
pEvent->pDestObj = NativeMsg.pDestObj;
pEvent->nSequenceNum = NativeMsg.nSequenceNum;
pEvent->nDataFormat = NativeMsg.nDataFormat;
pEvent->nCategory = NativeMsg.nCategory;
pEvent->bIsConsumed = FALSE;
memcpy(&(pEvent->Data),&(NativeMsg.Data),
sizeof(union SME_EVENT_DATA_T));
}
return TRUE;
}; }
BOOL XDelExtEvent(SME_EVENT_T *pEvent)
{
if (0==pEvent)
return FALSE;
if (pEvent->nDataFormat == SME_EVENT_DATA_FORMAT_PTR)
{
if (pEvent->Data.Ptr.pData)
{
#if SME_CPP
delete pEvent->Data.Ptr.pData;
#else
free(pEvent->Data.Ptr.pData);
#endif
pEvent->Data.Ptr.pData=NULL;
}
}
return TRUE;
}
Application and Sample
Assume there are two state machine instances Player1
and Player2
which run at the two threads whose thread contexts are AppThreadContext1
and AppThreadContext2
.
CPlayer Player1;
CPlayer Player2;
SME_THREAD_CONTEXT_T g_AppThreadContext1;
SME_THREAD_CONTEXT_T g_AppThreadContext2;
There is a control panel which allows the user to post events to these two state machine instances.
int main(int argc, char* argv[])
{
XTHREADHANDLE ThreadHandle = 0;
int ret;
printf("Cross-Platform State Timer Sample: \n");
XTlsAlloc();
SmeSetTlsProc(XSetThreadContext, XGetThreadContext);
SmeInitEngine(&g_AppThreadContext1);
XInitMsgBuf();
SmeSetExtEventOprProc(XGetExtEvent, XDelExtEvent,
XPostThreadExtIntEvent, XPostThreadExtPtrEvent, XInitMsgBuf, XFreeMsgBuf);
ret = XCreateThread(ConsoleProc, NULL, &ThreadHandle);
ret = XCreateThread(AppThread2Proc, NULL, &ThreadHandle);
SmeActivateObj(&Player1,NULL);
SmeRun();
printf("Exit from SmeRun() at thread-1 \n");
XFreeMsgBuf();
XFreeThreadContext(&g_AppThreadContext1);
}
The second application thread function. The Player2
state machine instance runs at this thread.
#ifdef WIN32
unsigned __stdcall AppThread2Proc(void *Param)
#else
void* AppThread2Proc(void *Param)
#endif
{
SmeInitEngine(&g_AppThreadContext2); XInitMsgBuf();
SmeSetExtEventOprProc(XGetExtEvent, XDelExtEvent,
XPostThreadExtIntEvent, XPostThreadExtPtrEvent, XInitMsgBuf, XFreeMsgBuf);
SmeActivateObj(&Player2,NULL);
SmeRun();
printf("Exit from SmeRun() at thread-2 \n");
XFreeMsgBuf();
XFreeThreadContext(&g_AppThreadContext2);
return 0;
}
A control panel to trigger external events.
#ifdef WIN32
unsigned __stdcall ConsoleProc(void *Param)
#else
void* ConsoleProc(void *Param)
#endif
{
XInitTimer();
printf("Enter the console procedure thread.\n");
ConsoleProcUsage();
while(TRUE)
{
int nParam1 =0;
if(g_bQuit) break;
nParam1 = fgetc(stdin);
switch(nParam1)
{
case EOF: return 0;
case 'x':
case 'X': XPostThreadExtIntEvent(&g_AppThreadContext1,
SME_EVENT_EXIT_LOOP, 0, 0, NULL,0,
SME_EVENT_CAT_OTHER);
printf("Exiting thread-1 ... Please wait. \n");
XPostThreadExtIntEvent(&g_AppThreadContext2,
SME_EVENT_EXIT_LOOP, 0, 0, NULL,0,
SME_EVENT_CAT_OTHER);
printf("Exiting thread-2 ... Please wait. \n");
return 0;
break;
. . .
default:
break;
}
}
return 0;
}
Further Information
You may get further information and download the UML StateChart Open Source framework and IDE tool here.
History
- 25th May, 2009: First edition of this article
- 23rd June, 2009: Article updated
- 6th September, 2009: Sample and source code updated - fixed some bugs based on feedbacks from users