使用 Harel UML 状态图进行复杂反应式系统的可视化建模





5.00/5 (7投票s)
本文介绍了一个名为StateWizard的商业级跨平台Harel UML状态图开源应用程序框架,用于以简洁、高效和可扩展的方式进行并发、分布式和实时响应式系统的开发。
引言
响应式系统的特点是,在很大程度上由事件驱动,需要不断对外部和内部刺激做出反应。例如,电话、汽车、通信网络、计算机操作系统和导弹。问题的根源在于,很难以清晰、真实且同时形式化和严谨的方式来描述响应式行为,以至于可以进行详细的计算机模拟[David Harel]。
Harel状态图正变得越来越普及,因为它的一个变体已成为统一建模语言的一部分。这种图类型允许将超状态、并发状态和活动作为状态的一部分进行建模。
传统有限状态机的挑战
经典的Mealy-Moore状态机建模技术要求为定义状态的每个有效参数组合创建不同的节点。这可能导致除最简单的系统外,节点和节点之间的转换数量非常庞大。这种复杂性降低了状态图的可读性。
将计算机系统建模为传统状态机的另一个限制是缺乏对并发构造的支持。传统的状态机建模基于从一个状态到下一个状态的顺序转换。并发系统无法以这种方式建模,因为系统的不同方面可能处于不同的状态。
可以看出,状态机模型固有的局限性包括随着状态数量的增加而产生的固有复杂性,以及对并发系统的建模。本文介绍了一个名为StateWizard的商业级跨平台Harel UML状态图开源应用程序框架,用于以简洁、高效和可扩展的方式进行并发、分布式和实时响应式系统的开发。以下各节将介绍如何通过以下Harel状态图特性来解决复杂的响应式系统问题:
- 分层状态机
- 通过将状态树定义分离到多个C/C++文件中,支持具有数百个状态的大型状态机。
- 状态历史信息和历史转换
- 事件处理时的保护转换
- 条件伪状态
- 连接伪状态
- 正交状态
- 内置状态计时器
如何减小表示的大小?
解决方案是分层状态机。在传统的状态机设计中,所有状态都被认为处于同一级别。设计没有捕捉到状态之间存在的共性。在现实生活中,许多状态以类似的方式处理大多数消息,只在处理少数关键消息时有所不同。即使实际处理方式不同,也仍然存在一些共性。分层状态机设计通过将状态组织成层次结构来捕捉共性。层次结构中较高的状态处理共有的消息处理,而较低的状态则继承较高状态的共性并执行状态特定的功能。
状态机状态层次结构基于父子关系,通过树枝的排列来表示。例如,在下面的图“树状状态层次结构”中,节点Player在状态树中是一个根状态,它有两个子节点:PowerDown
和PowerUp
。同时,节点PowerUp
也是三个子节点的父节点,这些子节点被称为彼此之间的同级状态。
这种分层组织意味着,当从状态PowerDown
到PowerUp
的转换被触发时,状态机将停用状态PowerDown
及其子节点(如果有),并激活PowerUp
及其一个或多个子节点(如果有)。
玩家
PowerDown
(Init)PowerUp
Playing
(Init)Pause
Record
相同的层次结构可以用嵌套图表形式表示。在这种格式中,通过将子状态嵌套在其父状态内来显示状态之间的层次结构。
使用StateWizard应用程序框架API集,Player
状态机定义如下:
/* The definition of the Player composite root state. */
#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)
根状态是最高的状态,承载应用程序名称。添加状态时,状态树会从根状态向下增长。
例如,复合状态Player
是Player
状态机的根状态。其定义如下。PlayerEntry
和PlayerExit
分别是根状态进入和退出时的函数指针。
SME_BEGIN_ROOT_COMP_STATE_DEF(Player, PlayerEntry, PlayerExit)
父状态是分支到一个或多个子状态的状态。一个父节点可以有多个子节点,但一个子节点只有一个父节点。
例如,复合状态PowerUp
的父状态是状态Player
。PowerUp
状态定义如下:
SME_BEGIN_COMP_STATE_DEF(PowerUp, Player, PowerUpEntry, PowerUpExit)
初始子状态标识具有子状态的状态机的初始状态。如果机器有一个或多个状态或并行子节点,则此子节点必须存在。
例如,复合状态PowerUp
的一个初始子状态Playing
定义如下。OnPowerUpInitChild
是指向初始子状态进入动作的函数指针。
SME_BEGIN_COMP_STATE_DEF(PowerUp, Player, PowerUpEntry, PowerUpExit)
SME_ON_INIT_STATE(OnPowerUpInitChild, Playing)
SME_END_STATE_DEF
同级状态是具有共同父节点的所有子状态。
如何扩展状态机?
在复杂的大规模响应式系统中,一个状态机对象可能包含100多个状态,将所有状态定义放在一个源文件中不是一个好方法。
不同复合状态的定义可以分散在源文件中。
例如,复合状态Player
可能定义在Player.c中。有一个名为PowerUp
的子状态,它也是一个复合状态。SME_BEGIN_SUB_STATE_DEF_P(PowerUp)
块在Player
复合状态定义体中声明。PowerUp
复合状态的通用属性和行为在SME_BEGIN_SUB_STATE_DEF_P(PowerUp)
块中定义,例如从PowerUp
到PowerDown
的状态转换,无论当前活动状态是(PowerUp
,Pause
)还是(PowerUp
,Playing
)。
// File: Player.c
#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)
并且,包含其子节点的详细PowerUp
复合状态可以在Player2.c中定义。
// File: 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)
如何激活状态机实例?
状态机应用程序是状态机的实例,或者是区域的实例,区域是复合状态或状态机的正交部分。应用程序可以有两种模式:活动或非活动。活动应用程序是指在给定时间正在状态机上运行的应用程序,而非活动应用程序则不是。换句话说,只有活动的应用程序才能处理事件。状态机引擎负责管理这些应用程序并将事件分派给特定的应用程序。
以下宏定义了一个应用程序实例Player1
,它基于Player
状态机。SmeActivateObj()
激活Player1
实例。第二个参数是要激活的应用程序的父应用程序,其中NULL
表示没有父应用程序。
SME_OBJ_DEF(Player1, Player)
SmeActivateObj(&Player1,NULL);
如何方便地描述状态并发?
解决方案是允许正交状态并发运行。
复合状态有一个或多个子状态区域。区域只是子状态的容器。
复合状态是一个由子状态组成的状态。复合状态可以使用AND关系分解为两个或多个并发区域(子状态容器),或使用OR关系分解为互斥的、不相交的子状态。
正交状态:如果复合状态可以通过AND关系分解为两个或多个并发区域,则称为正交状态。
以下宏将一个名为OrthoState
的正交状态定义为Player
父状态的子节点。状态进入/退出动作分别是OrthoStateEntry
和OrthoStateExit
。正交状态由三个区域组成:
PlayerReg1
是Player
状态机的实例,与其父节点在同一线程中运行,优先级为0。PlayerReg2
是Player
状态机的实例,在一个单独的线程中运行,优先级为0。PlayerReg3
是Player
状态机的实例,在一个单独的线程中运行,优先级为0。
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
使用StateWizard,您可以使用SME_BEGIN_ORTHO_SUB_STATE_DEF()
定义到/从正交状态的转换。但是,您不能显式定义进入区域的某个子状态。所有区域都以应用程序的形式运行,并且并发运行。进入正交状态时,所有区域都会自动激活。退出状态时,所有区域都会自动停用。如果多个区域在其独立的线程上运行,正交状态将向这些区域发布SME_EVENT_EXIT_LOOP
事件,并等待这些线程退出。
如何为实时系统建模状态内置计时器?
计时器需要在实时系统中进行建模。引擎支持两种计时器:状态内置计时器和常规计时器。
状态内置计时器与状态机紧密配合。它们由引擎管理。进入状态时,如果计时器可用,则自动启动内置计时器。退出状态时,则停止计时器。超时时,它们会触发SME_EVENT_STATE_TIMER
事件。引擎将根据使用SME_ON_STATE_TIMEOUT
的状态机定义执行操作。
常规计时器是显式计时器。开发人员必须使用API调用来启动或停止它们。超时时,它们会触发SME_EVENT_TIMER
事件。引擎提供两种处理模式:回调函数调用或事件处理,如下定义:
enum {SME_TIMER_TYPE_CALLBACK, SME_TIMER_TYPE_EVENT};
对于回调函数模式计时器,应定义一个显式回调函数。此模式独立于状态机定义。对于计时器事件模式,应定义SME_ON_EVENT(SME_EVENT_TIMER, handler, new state)
来处理超时事件。
下面的示例定义了一个在Player状态下的3000毫秒计时器和一个在Playing状态下的9000毫秒计时器。超时时,将调用PowerUpTimeOut
操作。进入Playing
状态时,启动计时器。超时时,自动转换到Pause
状态,并执行PlayingTimeOut
操作。
#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) /* Regular timer event */
SME_ON_STATE_TIMEOUT(9000, PlayingTimeOut, Pause)
/* Go to Pause state if 9 sec is timeout. */
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)
如何建模伪状态?
伪状态是状态机图中不同类型节点的抽象,代表从一个状态到另一个状态的转换路径中的瞬时点(例如,分支和合并点)。伪状态用于通过简单转换来构建复杂转换。例如,通过将进入合并伪状态的转换与从合并伪状态退出的转换集结合,我们可以得到一个导致一组目标状态的复杂转换。
条件(合并)伪状态是表示多个退出转换的记法简写,所有这些转换由同一事件触发,但每个转换具有不同的保护条件。
连接伪状态是一个具有多个进入转换和一个退出转换的状态。
使用StateWizard应用程序框架API集,如下定义了一个条件伪状态Cond1
和一个连接伪状态Join1
。Cond1
伪状态有三个分支:COND1
、COND2
和COND_ELSE
。退出目标状态取决于函数Cond1_func
的返回值。进入Join1
伪状态的操作是JoinAct
函数。
#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事件处理工作流
事件处理循环
SmeRun()
函数是状态机引擎的事件处理循环函数。如果队列中没有事件,则该函数会等待外部事件。如果触发了任何事件,它会将事件分派给相应的应用程序,并使状态机执行适当的转换和操作。
如果触发了外部事件,它将通过一个外部事件等待函数(这是OS虚拟层的一个函数)转换为内部事件。
如果挂载了外部事件销毁函数,当外部事件被消耗时,SmeRun()
将调用此函数来销毁外部事件。
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)
{
/* Check the internal event pool firstly. */
pEvent = GetEventFromQueue();
if (pEvent == NULL)
{
/* Wait for an external event. */
if (FALSE == (*g_pfnGetExtEvent)(&ExtEvent))
return; // Exit the thread.
pEvent = &ExtEvent;
pEvent->nOrigin = SME_EVENT_ORIGIN_EXTERNAL;
/* Call hook function on an external event coming. */
if (pThreadContext->fnOnEventComeHook)
(*pThreadContext->fnOnEventComeHook)
(SME_EVENT_ORIGIN_EXTERNAL, pEvent);
}else
{
/* Call hook function on an internal event coming. */
if (pThreadContext->fnOnEventComeHook)
(*pThreadContext->fnOnEventComeHook)
(SME_EVENT_ORIGIN_INTERNAL, pEvent);
}
do {
DispatchEventToApps(pThreadContext, pEvent);
/* Free internal event. Free external event later. */
if (pEvent != &ExtEvent)
SmeDeleteEvent(pEvent);
/* Get an event from event queue if available. */
pEvent = GetEventFromQueue();
if (pEvent != NULL)
{
/* Call hook function on an
internal event coming. */
if (pThreadContext->fnOnEventComeHook)
(*pThreadContext->fnOnEventComeHook)
(SME_EVENT_ORIGIN_INTERNAL, pEvent);
}
else
{
/* The internal event queue is empty. */
break;
}
} while (TRUE); /* Get all events from the internal event pool. */
/* Free external event if necessary. */
if (g_pfnDelExtEvent)
{
(*g_pfnDelExtEvent)(&ExtEvent);
// Engine should delete this event,
// because translation of external event
// will create an internal event.
SmeDeleteEvent(&ExtEvent);
}
} /* Wait for an external event. */
}
SmeSetExtEventOprProc()
函数安装了与平台相关的外部事件操作函数。它们作为StateWizard的OS虚拟层工作。事件处理循环函数SmeRun()
调用fnGetExtEvent
函数获取外部事件,并在通过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;
}
外部事件管理作为OS虚拟层
SmeSetExtEventOprProc()
函数安装了与平台相关的外部事件操作函数。它们作为StateWizard的OS虚拟层工作。事件处理循环函数SmeRun()
调用fnGetExtEvent
函数获取外部事件,并在通过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; //Request Exit
}
#ifdef SME_WIN32
#else
// Built-in call back timer on Linux
else if (SME_EVENT_TIMER == NativeMsg.nMsgID
&& SME_TIMER_TYPE_CALLBACK == NativeMsg.Data.Int.nParam1)
{
// Invoke the call back function.
SME_TIMER_PROC_T pfnCallback =
(SME_TIMER_PROC_T)(NativeMsg.Data.Int.nParam2);
(*pfnCallback)(NativeMsg.pDestObj, NativeMsg.nSequenceNum);
}
#endif
else {
// Translate the native message to SME event.
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));
}
//printf("External message received. \n");
return TRUE;
}; // while (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;
}
应用程序和示例
假设有两个状态机实例Player1
和Player2
,它们运行在线程上下文分别为AppThreadContext1
和AppThreadContext2
的两个线程上。
CPlayer Player1;
CPlayer Player2;
SME_THREAD_CONTEXT_T g_AppThreadContext1;
SME_THREAD_CONTEXT_T g_AppThreadContext2;
有一个控制面板允许用户向这两个状态机实例发送事件。
int main(int argc, char* argv[])
{
XTHREADHANDLE ThreadHandle = 0;
int ret;
printf("Cross-Platform State Timer Sample: \n");
// Install thread local storage data functions.
XTlsAlloc();
SmeSetTlsProc(XSetThreadContext, XGetThreadContext);
////////////////////////////////////////////////////////////////
// Engine initialization.
SmeInitEngine(&g_AppThreadContext1); // Initialize at the thread-1
XInitMsgBuf();
// Install event handler functions.
SmeSetExtEventOprProc(XGetExtEvent, XDelExtEvent,
XPostThreadExtIntEvent, XPostThreadExtPtrEvent, XInitMsgBuf, XFreeMsgBuf);
// Create a thread to trigger external events.
ret = XCreateThread(ConsoleProc, NULL, &ThreadHandle);
// Create the Player2 application thread
ret = XCreateThread(AppThread2Proc, NULL, &ThreadHandle);
SmeActivateObj(&Player1,NULL);
SmeRun();
printf("Exit from SmeRun() at thread-1 \n");
XFreeMsgBuf();
XFreeThreadContext(&g_AppThreadContext1); /* At last, free thread local
storage resource. */
}
第二个应用程序线程函数。Player2
状态机实例在此线程上运行。
#ifdef WIN32
unsigned __stdcall AppThread2Proc(void *Param)
#else
void* AppThread2Proc(void *Param)
#endif
{
SmeInitEngine(&g_AppThreadContext2); // Initialize at the thread-2
// Save the thread context pointer to the TLS.
// XSetThreadContext(&g_AppThreadContext2);
XInitMsgBuf();
// Install event handler functions.
SmeSetExtEventOprProc(XGetExtEvent, XDelExtEvent,
XPostThreadExtIntEvent, XPostThreadExtPtrEvent, XInitMsgBuf, XFreeMsgBuf);
SmeActivateObj(&Player2,NULL);
SmeRun();
printf("Exit from SmeRun() at thread-2 \n");
XFreeMsgBuf();
XFreeThreadContext(&g_AppThreadContext2); /* At last, free thread local
storage resource. */
return 0;
}
用于触发外部事件的控制面板。
#ifdef WIN32
unsigned __stdcall ConsoleProc(void *Param)
#else
void* ConsoleProc(void *Param)
#endif
{
// On Linux platform, call the XInitTimer function at the
// time-out event trigger thread.
// On time-out, the external event trigger thread posts
// SME_EVENT_TIMER to the state machine application thread,
// and then invokes the callback function
// installed by the XSetTimer function.
XInitTimer();
printf("Enter the console procedure thread.\n");
ConsoleProcUsage();
while(TRUE)
{
int nParam1 =0;
if(g_bQuit) break;
//OSRelated::Sleep(ISVW_LOOP_INTERVAL);
nParam1 = fgetc(stdin);
switch(nParam1)
{
case EOF: return 0;//Cancel ConsoleProc in Daemon
case 'x':
case 'X'://Quit
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;
}
更多信息
您可以在此处获取更多信息并下载UML状态图开源框架和IDE工具。
历史
- 2009年5月25日:本文初版
- 2009年6月23日:文章更新
- 2009年9月6日:示例和源代码更新 - 根据用户反馈修复了一些错误