Click here to Skip to main content
15,896,557 members
Articles / Programming Languages / C++

Create Cross-platform Thread-Independent Event Loops

Rate me:
Please Sign up or sign in to vote.
4.89/5 (5 votes)
12 May 20076 min read 45.8K   687   28  
This article discloses what is behind the GetMessage() and PostThreadMessage() Windows API, and implements them on Linux and Windows platforms using basic operation system functions.
/* ==============================================================================================================================
 * This notice must be untouched at all times.
 *
 * Copyright  IntelliWizard Inc. 
 * All rights reserved.
 * LICENSE: LGPL. 
 * Redistributions of source code modifications must send back to the Intelliwizard Project and republish them. 
 * Web: http://www.intelliwizard.com
 * eMail: info@intelliwizard.com
 * We provide technical supports for UML StateWizard users. The StateWizard users do NOT have to pay for technical supports 
 * from the Intelliwizard team. We accept donation, but it is not mandatory.
 * ==============================================================================================================================*/

#include "sme.h"
#include "sme_debug.h"
#if SME_DEBUG
	#include "sme_cross_platform.h"
#endif

#if !SME_CPP && defined(WIN32)
	/* C4055: A data pointer is cast (possibly incorrectly) to a function pointer. This is a level 1 warning under /Za and a level 4 warning under /Ze. */
	#pragma warning( disable : 4055)
#endif
/*******************************************************************************************
* State Machine Engine static variables.
*******************************************************************************************/
static SME_MEM_ALLOC_PROC_T g_fnMAllocProc = NULL;
static SME_MEM_FREE_PROC_T g_fnMFreeProc = NULL;

static SME_SET_THREAD_CONTEXT_PROC g_pfnSetThreadContext=NULL;
SME_GET_THREAD_CONTEXT_PROC g_pfnGetThreadContext=NULL;

BOOL DispatchInternalEvents(SME_THREAD_CONTEXT_PT pThreadContext);
BOOL DispatchEventToApps(SME_THREAD_CONTEXT_PT pThreadContext,SME_EVENT_T *pEvent);

/*******************************************************************************************
* DESCRIPTION:  Initialize state machine engine given the thread context.
* INPUT:  None.
* OUTPUT: None.
* NOTE: 
*******************************************************************************************/
void SmeInitEngine(SME_THREAD_CONTEXT_PT pThreadContext)
{
	int i;
	if (!pThreadContext) return;

	// For the platform independent engine..
	// Save the thread context pointer to the TLS.
	if (g_pfnSetThreadContext) (*g_pfnSetThreadContext)(pThreadContext);

	/* Service agent will automatically set the following Virtual RTOS functions, when
	application thread launches.

	pThreadContext->fnGetExtEvent = NULL;
	pThreadContext->fnDelExtEvent = NULL;
	g_fnMAllocProc = NULL;
	g_fnMFreeProc = NULL;
	*/

	memset(pThreadContext,0,sizeof(SME_THREAD_CONTEXT_T));

	pThreadContext->pActAppHdr = NULL;
	pThreadContext->pFocusedApp = NULL;

	pThreadContext->pEventQueueFront=NULL; 
	pThreadContext->pEventQueueRear=NULL;


	pThreadContext->fnOnEventComeHook = NULL; 
	pThreadContext->fnOnEventHandleHook = NULL;

	//SME_DBG_INIT(); 

	/* Set all event pool are empty. */
	for (i=0; i<SME_EVENT_POOL_SIZE; i++)
		pThreadContext->EventPool[i].nEventID = SME_INVALID_EVENT_ID;
	pThreadContext->nEventPoolIdx =0;
}

/*******************************************************************************************
* DESCRIPTION:  Get an event data buffer from event pool.
* INPUT:  None
* OUTPUT: New event pointer. If pool is used up, return NULL. 
* NOTE: 
*   
*******************************************************************************************/
static SME_EVENT_T *GetAEvent()
{
	SME_EVENT_T *e=NULL;
	int nOldEventPoolIdx;
	SME_THREAD_CONTEXT_PT pThreadContext=NULL;

	if (g_pfnGetThreadContext)
		pThreadContext = (*g_pfnGetThreadContext)();
	if (!pThreadContext) return NULL;

	nOldEventPoolIdx = pThreadContext->nEventPoolIdx;
	do {
		if (pThreadContext->EventPool[pThreadContext->nEventPoolIdx].nEventID == SME_INVALID_EVENT_ID)
		{
			/*This event data buffer is available.*/
			e = &(pThreadContext->EventPool[pThreadContext->nEventPoolIdx]);
			pThreadContext->nEventPoolIdx = (pThreadContext->nEventPoolIdx+1)%SME_EVENT_POOL_SIZE;
			return e;
		} else
		{
			pThreadContext->nEventPoolIdx = (pThreadContext->nEventPoolIdx+1)%SME_EVENT_POOL_SIZE;
			if (pThreadContext->nEventPoolIdx == nOldEventPoolIdx)
				return NULL;
		}
	} while(TRUE);

}
/*******************************************************************************************
* DESCRIPTION:  Create a state machine event.
* INPUT:  event id, parameter1, parameter2, event category, destination application pointer.
* OUTPUT: New event pointer.
* NOTE: 
*   
*******************************************************************************************/
SME_EVENT_T *SmeCreateIntEvent(SME_EVENT_ID_T nEventId,
								   unsigned long nParam1,
								   unsigned long nParam2,
								   SME_EVENT_CAT_T nCategory,
								   SME_APP_T *pDestApp)
{
	SME_EVENT_T *e=NULL;

	if (nEventId==SME_INVALID_EVENT_ID)
		return NULL;

	e=GetAEvent();
	if(e)
	{
		e->nEventID=nEventId;
		e->nSequenceNum =0;
		e->pNext = NULL;
		e->Data.Int.nParam1 = nParam1;
		e->Data.Int.nParam2 = nParam2;
		e->pDestApp=pDestApp;
		e->pPortInfo = NULL;
 		e->nOrigin = SME_EVENT_ORIGIN_INTERNAL;
		e->nCategory=nCategory;
		e->nDataFormat = SME_EVENT_DATA_FORMAT_INT;
		e->bIsConsumed = FALSE;
	}
	return e;
}

SME_EVENT_T *SmeCreatePtrEvent(SME_EVENT_ID_T nEventId,
								   void* pData,
								   unsigned long nSize,
								   SME_EVENT_CAT_T nCategory,
								   SME_APP_T *pDestApp)
{
	SME_EVENT_T *e=NULL;

	e=GetAEvent();
	if(e)
	{
		e->nEventID=nEventId;
		e->nSequenceNum =0;
		e->pNext = NULL;
		e->Data.Ptr.pData = pData;
		e->Data.Ptr.nSize = nSize;
		e->pDestApp=pDestApp;
		e->pPortInfo = NULL;
		e->nOrigin = SME_EVENT_ORIGIN_INTERNAL; //by default
		e->nCategory=nCategory;
		e->nDataFormat = SME_EVENT_DATA_FORMAT_PTR;
		e->bIsConsumed = FALSE;
	}
	return e;
}

/*******************************************************************************************
* DESCRIPTION:  Delete a event by marking with SME_INVALID_EVENT_ID.
* INPUT:  
* OUTPUT: None.
* NOTE: 
*   
*******************************************************************************************/
BOOL SmeDeleteEvent(SME_EVENT_T *pEvent)
{
	if(!pEvent) return FALSE;
	pEvent->nEventID = SME_INVALID_EVENT_ID;
	return TRUE;
}

/*******************************************************************************************
Utilities for the state 
*******************************************************************************************/
static SME_STATE_T* GetCompState(SME_STATE_T *pSubState)
{
	if (SME_NULL_STATE==pSubState || SME_STYPE_SUB!=pSubState->nStateType)
		return SME_NULL_STATE;

#if SME_CPP	
	return (SME_STATE_T *)(pSubState->pCompState);
#else
	return (SME_STATE_T *)pSubState->pfnFunc;
#endif
}

static SME_EVENT_HANDLER_T GetStateEntryAction(SME_STATE_T *pState)
{
	if (SME_NULL_STATE == pState) 
		return NULL;

	if (SME_IS_PSEUDO_STATE(pState))
		return NULL;

	switch (pState->nStateType)
	{
	case SME_STYPE_LEAF:
	case SME_STYPE_COMP:
#if SME_CPP
		return (SME_EVENT_HANDLER_T)(pState->pfnFunc);
#else
		return (SME_EVENT_HANDLER_T)(pState->pfnFunc);
#endif
		break;
	case SME_STYPE_SUB:
		{
			SME_STATE_T *pCompState = GetCompState(pState);
			if (NULL!=pCompState) 
			{
#if SME_CPP
				return (SME_EVENT_HANDLER_T)(pCompState->pfnFunc);
#else
				return (SME_EVENT_HANDLER_T)(pCompState->pfnFunc);
#endif
			}
		}
		break;
	default:
		return NULL;
	};

	return NULL;
}

static SME_EVENT_HANDLER_T GetStateExitAction(SME_STATE_T *pState)
{
	if (SME_NULL_STATE == pState) 
		return NULL;

	if (SME_IS_PSEUDO_STATE(pState))
		return NULL;

	switch (pState->nStateType)
	{
	case SME_STYPE_LEAF:
	case SME_STYPE_COMP:
		return (SME_EVENT_HANDLER_T)(pState->pfnExitFunc);
		break;
	case SME_STYPE_SUB:
		{
			SME_STATE_T *pCompState = GetCompState(pState);
			if (NULL!=pCompState) 
			{
				return (SME_EVENT_HANDLER_T)(pCompState->pfnExitFunc);
			}
		}
		break;
	default:
		return NULL;
	};

	return NULL;
}

/* Get initial child state, initial action, and state built-in timeout information */
static void GetStateInfo(SME_STATE_T *pState, SME_STATE_T **ppInitChildState, SME_EVENT_HANDLER_T *ppfnInitAction, 
						 int *pTimeout, SME_EVENT_HANDLER_T *ppfnTimeoutAction, SME_STATE_T **ppTimeoutDestState)
{
	SME_EVENT_TABLE_T *pEvtHldTbl;

	if (NULL==pState || NULL==ppInitChildState || NULL==ppfnInitAction)
		return;

	*ppInitChildState = SME_NULL_STATE; /* By default. */

	if (SME_STYPE_SUB == pState->nStateType)
		pState = GetCompState(pState); /* Get Composite State */
	else if (SME_STYPE_COMP != pState->nStateType)
		*ppInitChildState = SME_NULL_STATE;

	/* Composite State */
	pEvtHldTbl = pState->EventTable;

	if (NULL==pEvtHldTbl)
	{
		/* ERROR */
		return;
	}

	/* Traverse the event handler table. */
	while (SME_INVALID_EVENT_ID != pEvtHldTbl->nEventID)
	{
		if (SME_INIT_CHILD_STATE_ID == pEvtHldTbl->nEventID)
		{
			*ppInitChildState = pEvtHldTbl->pNewState;
			*ppfnInitAction = pEvtHldTbl->pHandler;
		} else if (SME_IS_STATE_TIMEOUT_EVENT_ID(pEvtHldTbl->nEventID))
		{
			if (NULL!=pTimeout || NULL!=ppfnTimeoutAction || NULL!=ppTimeoutDestState)
			{
				*pTimeout = SME_GET_STATE_TIMEOUT_VAL(pEvtHldTbl->nEventID);
				*ppfnTimeoutAction = pEvtHldTbl->pHandler;
				*ppTimeoutDestState = pEvtHldTbl->pNewState;
			}
		}
		pEvtHldTbl++;
	}

	if (SME_STYPE_COMP == pState->nStateType && *ppInitChildState == SME_NULL_STATE) 
	{
		SME_TRACE_ASC_FMT(0,SMESTR_ERR_NO_INIT_STATE_IN_COMP);
	}
	return; 
}

/* Call an event handler, or a conditional function.
If the handler function is NULL, return SME_EVENT_COND_ELSE.
*/
static int CallHandler(SME_EVENT_HANDLER_T pEvtHdl, SME_APP_T* pApp, SME_EVENT_T *pEvent)
{
	if(NULL==pEvtHdl)
		return SME_EVENT_COND_ELSE;
#if SME_CPP
	return (pApp->*pEvtHdl)(pApp,pEvent);
#else
	return pEvtHdl(pApp,pEvent);
#endif
}
/*******************************************************************************************
* DESCRIPTION: Activate the given application. 
* INPUT:  
*  1) pNewApp: The new activated application.
*  2) pParentApp: who starts this new activated application.
* OUTPUT: None.
*  TRUE: Start it successfully.
*  FALSE: Fail to start it.
* NOTE: 
*  The active applications are linked as a stack.
*  NULL <--- Node.pNext  <--- Node.pNext  <--- pThreadContext->pActAppHdr
*******************************************************************************************/
BOOL SmeActivateApp(SME_APP_T *pNewApp, SME_APP_T *pParentApp)
{
	SME_STATE_T *pState;
	SME_THREAD_CONTEXT_PT pThreadContext=NULL;
	SME_EVENT_HANDLER_T pEvtHdl=NULL;

	if (g_pfnGetThreadContext)
		pThreadContext = (*g_pfnGetThreadContext)();
	if (!pThreadContext) return FALSE;

	if(!pNewApp || SME_IS_ACTIVATED(pNewApp)) return FALSE;

	pNewApp->pParent = pParentApp;
    /* Push the new application to active application stack. */
	pNewApp->pNext=pThreadContext->pActAppHdr;
	pThreadContext->pActAppHdr=pNewApp;
	
	/* Call default entry functions. */
	pState = pNewApp->pRoot; /* The new application state. */
	pNewApp->pAppState = pNewApp->pRoot; /* Keep track of the route of state transitions including non-leaf node.*/
	pNewApp->pHistoryState = pNewApp->pRoot;
	do 
	{
		SME_STATE_T *pChildState=NULL;
		SME_EVENT_HANDLER_T pfnDefSubStateAction=NULL;
		int nStateTimeOut=-1;
		SME_EVENT_HANDLER_T pfnStateTimeoutAction=NULL;
		SME_STATE_T *pTimeOutDestState=NULL;

		/* Call state's entry function.*/
		pEvtHdl = GetStateEntryAction(pState);
		CallHandler(pEvtHdl, pNewApp, NULL);
		GetStateInfo(pState, &pChildState, &pfnDefSubStateAction, &nStateTimeOut, &pfnStateTimeoutAction, &pTimeOutDestState);
		if (NULL == pChildState)
		{
			/* Reach the leaf */
			/* New application state: pNewApp->StateLine[pNewApp->nStateLineLen-1] is the destination state's leaf. */
			pNewApp->pAppState = pState; 
			break;
		} else
		{
			/* Call the action for the default sub-state. */
			CallHandler(pfnDefSubStateAction,pNewApp,NULL);

			pState = pChildState;
			/* Keep track of the route of state transitions including non-leaf node. */
			pNewApp->pAppState = pChildState; 
		}
	} while(TRUE); /* It is not a leaf.*/

	/* Dispatch all internal events which may be triggered by entry functions.*/
	DispatchInternalEvents(pThreadContext);
	
	/* Focus on the new application. */
	SME_SET_FOCUS(pNewApp);

	SME_STATE_TRACK(NULL, pNewApp, SME_NULL_STATE, SME_REASON_ACTIVATED,0);

	return TRUE;
}

/*******************************************************************************************
* DESCRIPTION:  De-activate the given application.
* INPUT:  
*  pApp: the application to be de-activated.
* OUTPUT: None.
*  TRUE: Deactivate it successfully.
*  FALSE: Fail to de-activate it.
* NOTE: 
*******************************************************************************************/
BOOL SmeDeactivateApp(SME_APP_T *pApp)
{
	SME_APP_T *p,*pPre;
	SME_STATE_T *pState;
	SME_THREAD_CONTEXT_PT pThreadContext=NULL;
	SME_EVENT_HANDLER_T pEvtHdl=NULL;

	if (g_pfnGetThreadContext)
		pThreadContext = (*g_pfnGetThreadContext)();
	if (!pThreadContext) return FALSE;

	if (!pApp || !SME_IS_ACTIVATED(pApp)) return FALSE;

	/* Locate the application in the active application stack.*/
	p=pThreadContext->pActAppHdr;
	pPre=NULL;
	while (p!=NULL)
	{
		/* The application should be located in the leaf of the active application tree.*/
		if (p->pParent == pApp)
			return FALSE;
		/* 1) Get the previous node of pApp.*/
		if (p->pNext == pApp)
			pPre=p;
		/* 2) Move to next application and traverse all applications. Assert pApp is a leaf. */
		p=p->pNext;
	}

	/* Adjust active application stack. */
	if (pApp==pThreadContext->pActAppHdr)
		/* the application is the active application header.*/
		pThreadContext->pActAppHdr=pApp->pNext;
	else
		pPre->pNext=pApp->pNext;

	/* If de-activated application is a focused application,the de-activated application's
	parent will be new focused application*/
	if	(pApp==pThreadContext->pFocusedApp)
		if (pApp->pParent != NULL)
			SME_SET_FOCUS(pApp->pParent); /* Focus on the parent application.*/
		else 
			pThreadContext->pFocusedApp = NULL; /* No any application focused.*/

	/* Call exit functions from leaf to the root.*/
	pState = pApp->pAppState; /* It should be leaf. */
	do {
		pEvtHdl = GetStateExitAction(pState);
		CallHandler(pEvtHdl,pApp,NULL);
		pState = pState->pParent;
	} while (pState!=SME_NULL_STATE); 


	/* Reset the application data.*/
	pApp->pAppState = SME_NULL_STATE;
	pApp->pHistoryState = SME_NULL_STATE;
	pApp->pNext =NULL;
	pApp->pParent =NULL;

	SME_STATE_TRACK(NULL, pApp, pApp->pAppState, SME_REASON_DEACTIVATED,0);

	/* Dispatch all internal events which may be triggered by exit functions.*/
	DispatchInternalEvents(pThreadContext);

	return TRUE;
}

/*******************************************************************************************
* DESCRIPTION: Set an application focused. 
* INPUT:  
* OUTPUT: None.
* NOTE: 
*   Send SME_EVENT_KILL_FOCUS event to old focused application. And send SME_EVENT_SET_FOCUS
*   to the new focused application.
*******************************************************************************************/
BOOL SmeSetFocus(SME_APP_T *pApp)
{
	SME_EVENT_T *pEvent1,*pEvent2;
	SME_THREAD_CONTEXT_PT pThreadContext=NULL;

	if (g_pfnGetThreadContext)
		pThreadContext = (*g_pfnGetThreadContext)();
	if (!pThreadContext) return FALSE;

	if (!pApp) return FALSE;

	if (pThreadContext->pFocusedApp)
	{
		pEvent1=SmeCreateIntEvent(SME_EVENT_KILL_FOCUS,
			0, /*(unsigned long)pApp,*/
			0,
			SME_EVENT_CAT_UI,
			pThreadContext->pFocusedApp);
		if (pEvent1!=NULL)
		{
			SmeDispatchEvent(pEvent1,pThreadContext->pFocusedApp);
			SmeDeleteEvent(pEvent1);
		} else return FALSE;
	}

    pEvent2=SmeCreateIntEvent(SME_EVENT_SET_FOCUS,
		0, /*(unsigned long)pThreadContext->pFocusedApp,*/
		0,
		SME_EVENT_CAT_UI,
		pApp);
	if (pEvent2!=NULL)
	{
		SmeDispatchEvent(pEvent2,pApp);
		SmeDeleteEvent(pEvent2);
	}

    pThreadContext->pFocusedApp=pApp;
	return TRUE;
}

/*******************************************************************************************
* DESCRIPTION:  Post an event to queue.
* INPUT:  pEvent: An event.
* OUTPUT: None.
* NOTE: 
*   
*******************************************************************************************/
BOOL SmePostEvent(SME_EVENT_T *pEvent)
{
	SME_THREAD_CONTEXT_PT pThreadContext=NULL;

	if (g_pfnGetThreadContext)
		pThreadContext = (*g_pfnGetThreadContext)();
	if (!pThreadContext) return FALSE;

	if (pEvent == NULL) return FALSE;

	if (pThreadContext->pEventQueueRear==NULL) 
	{ 
		/* The first event in queue. */
		pThreadContext->pEventQueueFront=pThreadContext->pEventQueueRear=pEvent;
	}
	else 
	{
		/* Append the event to queue.*/
		pThreadContext->pEventQueueRear->pNext=pEvent;
		pEvent->pNext=NULL;
		pThreadContext->pEventQueueRear=pEvent;
	}
	return TRUE;
}

/*******************************************************************************************
* DESCRIPTION:  Get an event from queue.
* INPUT:  pEvent: An event.
* RETURN: Event in the head of queue. return NULL if not available.
* NOTE: 
*   
*******************************************************************************************/
SME_EVENT_T * GetEventFromQueue()
{
	/* Get an event from event queue if available. */
	SME_EVENT_T *pEvent = NULL;
	SME_THREAD_CONTEXT_PT pThreadContext=NULL;

	if (g_pfnGetThreadContext)
		pThreadContext = (*g_pfnGetThreadContext)();
	if (!pThreadContext) return NULL;

	if (pThreadContext->pEventQueueFront != NULL)
	{
		pEvent = pThreadContext->pEventQueueFront;
		pThreadContext->pEventQueueFront = pThreadContext->pEventQueueFront->pNext;
		/* Set the end of queue to NULL if queue is empty.*/
		if (pThreadContext->pEventQueueFront == NULL)
			pThreadContext->pEventQueueRear = NULL;
	}
	return pEvent;
}

/*******************************************************************************************
 Check the state's event handler table from leaf to root.
 If find it and pass the guard if available, stop searching, no matter there is another handler in parent state's event table.
********************************************************************************************/
BOOL IsHandlerAvailable(/* IN */SME_STATE_T *pState, SME_APP_T *pApp, SME_EVENT_T *pEvent,
						/* OUT */ SME_STATE_T **ppNewState, SME_EVENT_HANDLER_T *ppHandler)
{
	/* Trace back from leaf to root, so as to retrieve all event handler tables.
	 Find what nNewState is */
	SME_STATE_T *pSuperState=SME_NULL_STATE;
	SME_EVENT_TABLE_T *pStateEventTable;
	SME_STATE_T *pCurrState = pState;
	int i=0;
	BOOL bFoundHandler=FALSE;
	BOOL bSubStateChecked = FALSE;
	if (SME_NULL_STATE==pState || NULL==pEvent || NULL==ppNewState || NULL==ppHandler)
		return FALSE;

	if (pState->nStateType!=SME_STYPE_LEAF &&
		pState->nStateType!=SME_STYPE_SUB &&
		pState->nStateType!=SME_STYPE_COMP)
		return FALSE;
	

	/* Check the event handler table in Composite state, and then the table in sub state.*/
	if (SME_STYPE_SUB == pState->nStateType)
	{
		pSuperState = GetCompState(pState);
	}
	
	if (pSuperState)
	{
		pStateEventTable = pSuperState->EventTable;
		bSubStateChecked = FALSE;
	}
	else
	{
		pStateEventTable = pState->EventTable;
		bSubStateChecked = TRUE;
	}

	while (TRUE)
	{
		/* Check the current state's event handler table.*/
		i=0;
		while (pStateEventTable && pStateEventTable[i].nEventID != SME_INVALID_EVENT_ID)
		{
			if (pStateEventTable[i].nEventID == SME_EXPLICIT_EXIT_EVENT_ID(pEvent->nEventID)) 
			{
				/* The Explicit Exit makes nEventID an explicit event going out of pNewState (the source state) instead of 
				a transition from all children of the current composite state.  */
				SME_STATE_T *p = pCurrState;
				BOOL bIsSrcState = FALSE; /* Is the current state is the source state of the explicit exit.*/
				
				/* Check whether the current state is the source state of the explicit exit or its children state. */
				while(SME_NULL_STATE!=p)
				{
					if (p==pStateEventTable[i].pNewState)
					{
						/* It is an explicit exit, proceed with looking for SME_ON_EVENT() for this explicit exit.*/
						bIsSrcState = TRUE;
						break;
					}
					p=p->pParent;
				}

				if (!bIsSrcState)
				{
					/* Not match an explicit exit. */
					SME_STATE_TRACK(pEvent, pApp, pApp->pAppState, SME_REASON_NOT_MATCH,0);
					return FALSE;
				}
			} else if (pStateEventTable[i].nEventID == pEvent->nEventID)
			{
#if SME_CPP
				if ((0 == pStateEventTable[i].pGuardFunc) /* NULL pointer */
				|| (pApp->pSME_NULL_GUARD == pStateEventTable[i].pGuardFunc) /* pointer to a NULL function. */
				|| (pApp->*pStateEventTable[i].pGuardFunc)(pApp,pEvent) /* Call guard function. */
				)
#else
				if (SME_NULL == pStateEventTable[i].pGuardFunc 
					|| (*pStateEventTable[i].pGuardFunc)(pApp,pEvent)
				)
#endif
				{
					/* Match, and the guard returns TRUE. */
					/* Hit */
					*ppNewState=pStateEventTable[i].pNewState;
					*ppHandler = pStateEventTable[i].pHandler;
					bFoundHandler = TRUE;
					return TRUE;
				} else
				{
					/* Match, however the guard returns FALSE. */
					SME_STATE_TRACK(pEvent, pApp, pApp->pAppState, SME_REASON_GUARD,0);
					return FALSE;
				}
			}
			i++;
		} /* Search the event handler table. */
		
		if (!bSubStateChecked)
		{
			/* About to check sub-state */
			pStateEventTable = pState->EventTable;
			bSubStateChecked = TRUE;
		} else
		{
			/* Get the parent state's event handler table. */
			if (SME_NULL_STATE==pState->pParent) break;
			pState = pState->pParent;
			/* SME_STYPE_SUB == pState->nStateType */
			pSuperState = GetCompState(pState);
			pStateEventTable = pSuperState->EventTable;
			bSubStateChecked = FALSE;
		}
	}

	SME_STATE_TRACK(pEvent, pApp, pApp->pAppState, SME_REASON_NOT_MATCH,0);
	return FALSE;
}

/*******************************************************************************************
 Check whether an explicit entry on the given event is available in a given composite state event handler table, 
 If find it, return the entry child state .
********************************************************************************************/
static BOOL IsExplicitEntryAvailable(/* IN */SME_STATE_T *pState, SME_EVENT_T *pEvent,
						/* OUT */ SME_STATE_T **ppNewState)
{
	SME_EVENT_TABLE_T *pStateEventTable=NULL;
	SME_STATE_T *pCompState=SME_NULL_STATE;

	if (SME_NULL_STATE ==pState || NULL == pEvent || NULL==ppNewState)
		return FALSE;

	if (SME_STYPE_COMP != pState->nStateType && SME_STYPE_SUB != pState->nStateType)
		return FALSE;

	if (SME_STYPE_SUB == pState->nStateType)
	{
		pCompState = GetCompState(pState);
	}else
		pCompState = pState;
	
	pStateEventTable = pCompState->EventTable;

	if (pStateEventTable)
	{
		int i=0;
		while (SME_INVALID_EVENT_ID != pStateEventTable[i].nEventID)
		{
			if (SME_EXPLICIT_ENTRY_EVENT_ID(pEvent->nEventID) == pStateEventTable[i].nEventID)
			{
				*ppNewState = pStateEventTable[i].pNewState;
				return TRUE;
			}

			i++;
		}
	}
	return FALSE;
}

/*******************************************************************************************
* DESCRIPTION: Dispatch the incoming event to an application if it is specified, otherwise
*  dispatch to all active applications until it is consumed.  
* INPUT:  
*  1) pEvent: Incoming event 
*  2) pApp: The destination application that event will be dispatched.
* OUTPUT: None.
* NOTE: 
*	1) Call exit functions in old state   
*	2) Call event handler functions   
*	3) Call entry functions in new state  
*  4) Transit from one state region to another state region. All states exit functions that jump out 
*  the old region will be called. And all states exit functions that jump in the new region will be called.
*  5) Although there is a property pEvent->pDestApp in SME_EVENT_T, this function will ignore this one,
*		because if pEvent->pDestApp is NULL, this event have to dispatch to all active applications.
*******************************************************************************************/
BOOL SmeDispatchEvent(SME_EVENT_T *pEvent, SME_APP_T *pApp)
{
	SME_STATE_T *pOldState=SME_NULL_STATE; /* Old state should be a leaf.*/
	SME_STATE_T *pState=SME_NULL_STATE;
    SME_STATE_T *pNewState=SME_NULL_STATE;
	int i;
	SME_EVENT_HANDLER_T pHandler = NULL;

	SME_STATE_T *OldStateStack[SME_MAX_STATE_TREE_DEPTH];
	SME_STATE_T *NewStateStack[SME_MAX_STATE_TREE_DEPTH];
	int nOldStateStackTop =0;
	int nNewStateStackTop =0;
	int nRepeatTime=0;

	SME_THREAD_CONTEXT_PT pThreadContext=NULL;
	
	#if SME_DEBUG
	int nBeginTick=XGetTick();
	#endif

	if (g_pfnGetThreadContext)
		pThreadContext = (*g_pfnGetThreadContext)();
	if (!pThreadContext) return FALSE;

	if (pEvent==NULL || pApp==NULL) return FALSE;

	pOldState = pApp->pAppState; /* Old state should be a leaf. */

	if (!IsHandlerAvailable(pOldState, pApp, pEvent, &pNewState, &pHandler)) 
		return FALSE;

	do { /* Loop until the destination state is not a pseudo state .*/

	/* Call the exit functions if it is not an internal transition and the old state is a regular state. */
	if (SME_INTERNAL_TRAN != pNewState)
	{
		if (!SME_IS_PSEUDO_STATE(pNewState))
			pApp->pHistoryState = pOldState; /* The original state before transition.*/

		/* It is a state transition.
		 Push all old state's ancestors.
		*/
		nOldStateStackTop =0;
		nNewStateStackTop =0;

		pState = pOldState;
		while (pState!=SME_NULL_STATE)
		{
			OldStateStack[nOldStateStackTop++] = pState;
			pState=pState->pParent;
			if (nOldStateStackTop >= SME_MAX_STATE_TREE_DEPTH)
				return FALSE;
		}

		/* Push all new state's ancestors. */
		pState = pNewState;
		while (pState!=SME_NULL_STATE)
		{
			NewStateStack[nNewStateStackTop++] = pState;
			pState=pState->pParent;
			if (nNewStateStackTop >= SME_MAX_STATE_TREE_DEPTH)
				return FALSE;
		}

		/* Pop all equal states except the last one.
		 Special case 1: self transition state1->state1, leave one item in each stack.
		 Special case 2: a parent state transits to its child state, leave one item in the parent state stack.
		*/
		while ((nOldStateStackTop>1) && (nNewStateStackTop>1)
			&& (OldStateStack[nOldStateStackTop-1] == NewStateStack[nNewStateStackTop-1]))
		{
			nOldStateStackTop--;
			nNewStateStackTop--;
		}

		/* Get the leaf of the old state.
		 Note: Old state should be a leaf state.
		 Call exit functions from the leaf nState to the top state in the old state stack.
		 */
		for (i=0; i<nOldStateStackTop; i++)
		{
			SME_EVENT_HANDLER_T pEvtHdl;
			pState = OldStateStack[i];
			pApp->pAppState = pState; /* Keep track of the route of state transitions including non-leaf node.*/
			pEvtHdl=GetStateExitAction(pState); /* If it is pseudo state, return NULL. */
			CallHandler(pEvtHdl,pApp,pEvent);
		};
	}; /* end of non internal transition.*/

	/*******************************************************************************************
	 Call event handler function if given event handler is available and handler is not empty.
	 Maybe their is a transition, however handler is empty.
	*/
	CallHandler(pHandler,pApp,pEvent);
	pHandler= NULL;

	/*******************************************************************************************
	 Call entry functions from new state stack's top to leaf state. 
	*/
	if (pNewState != SME_INTERNAL_TRAN)
	{
		/******************************************************************************************
			New state is a Pseudo State
		*/
		if (SME_STYPE_COND == pNewState->nStateType)
		{
			/* Conditional Pseudo State => a regular state */
			SME_EVENT_ID_T nCondRet = (SME_EVENT_ID_T)CallHandler((SME_EVENT_HANDLER_T)pNewState->pfnFunc,pApp,pEvent);
			SME_EVENT_TABLE_T *pStateEventTable = pNewState->EventTable;
			int i=0;
			while (pStateEventTable && pStateEventTable[i].nEventID != SME_INVALID_EVENT_ID)
			{
				if (nCondRet == pStateEventTable[i].nEventID
					|| SME_EVENT_COND_ELSE == pStateEventTable[i].nEventID)
				{
					/* Get the conditional action function. */
					pHandler = pStateEventTable[i].pHandler;
					/* Set the old state */
					pOldState = pNewState;
					/* Get the destination state */
					pNewState = pStateEventTable[i].pNewState;
					/* The SME_EVENT_COND_ELSE probably is not located at the last item in the condition list. */
					if (nCondRet == pStateEventTable[i].nEventID)
						break;
				}
				i++;
			}
		} else if (SME_STYPE_JOIN == pNewState->nStateType)
		{
			/* Join Pseudo State => a regular state */
			SME_EVENT_TABLE_T *pStateEventTable = pNewState->EventTable;
			if (pStateEventTable && SME_JOIN_STATE_ID == pStateEventTable[0].nEventID)
			{
				/* Get the conditional action function. */
				pHandler = pStateEventTable[0].pHandler;
				/* Set the old state */
				pOldState = pNewState;
				/* Get the destination state */
				pNewState = pStateEventTable[0].pNewState;
				if (SME_NULL_STATE == pNewState)
				{
					/* ERROR */
					SME_ASCII_TRACE(SME_MODULE_ERR_OUTPUT,SMESTR_ERR_NO_JOIN_TRAN_STATE);
				}
			} else
			{
				/* ERROR */
				SME_ASCII_TRACE(SME_MODULE_ERR_OUTPUT,SMESTR_ERR_NO_JOIN_TRAN_STATE);
			}
		} else if (SME_STYPE_LEAF == pNewState->nStateType || SME_STYPE_SUB == pNewState->nStateType)
		{
			/******************************************************************************************
				New state is a Regular State
			*/
			SME_STATE_T *pChildState=NULL;
			SME_EVENT_HANDLER_T pfnDefSubStateAction=NULL;
			int nStateTimeOut=-1;
			SME_EVENT_HANDLER_T pfnStateTimeoutAction=NULL;
			SME_STATE_T *pTimeOutDestState=NULL;

			/* It is a state transition.
			 Call entry functions from ancestor to new state.
			*/
			for (i=nNewStateStackTop-1; i>=0; i--)
			{
				/* Keep track of the route of state transitions including non-leaf node.*/
				pApp->pAppState = NewStateStack[i]; 
				CallHandler(GetStateEntryAction(NewStateStack[i]), pApp,pEvent);
			};

			/* Call entry functions from new state's child to leaf. */
			pState = NewStateStack[0];
			do{
				GetStateInfo(pState, &pChildState, &pfnDefSubStateAction, &nStateTimeOut, &pfnStateTimeoutAction, &pTimeOutDestState);
				if (NULL == pChildState)
				{
					/* Reach the leaf */
					/* New application state: pNewApp->StateLine[pNewApp->nStateLineLen-1] is the destination state's leaf. */
					break;
				} else
				{
					if (IsExplicitEntryAvailable(pState,pEvent,&pNewState))
					{
						/* Explicit entry to a specific child state. */
						pChildState = pNewState;
					} else
					{
						/* Call the action for the initial sub-state. */
						CallHandler(pfnDefSubStateAction,pApp,pEvent);
					}

					/* Keep track of the route of state transitions including non-leaf node. */
					pState = pChildState;
					pApp->pAppState = pChildState;
					CallHandler((SME_EVENT_HANDLER_T)pState->pfnFunc,pApp,pEvent);
				}
			} while (TRUE);

			pApp->pHistoryState = pOldState; /* The destination state after transition.*/
			pApp->pAppState = pState; /* New application state is the destination state's leaf. */
			break; /* Break the pseudo state transition loop. */
		}
	};
	   nRepeatTime++;
	} while (nRepeatTime<=SME_MAX_PSEUDO_TRAN_NUM && pNewState != SME_INTERNAL_TRAN 
		&& (SME_IS_PSEUDO_STATE(pNewState) || SME_IS_PSEUDO_STATE(pOldState))); 
	/* Loop until the destination state is not a pseudo state. */

	if (nRepeatTime>=SME_MAX_PSEUDO_TRAN_NUM)
	{
		SME_ASCII_TRACE(SME_MODULE_ERR_OUTPUT,SMESTR_ERR_A_LOOP_PSEUDO_STATE_TRAN);
	}

	/*******************************************************************************************
	 Call event handle hook function if given event handler is available and no matter whether handler is empty or not.
	 */
	if (pThreadContext->fnOnEventHandleHook)
		(*pThreadContext->fnOnEventHandleHook)((SME_EVENT_ORIGIN_T)(pEvent->nOrigin), 
			pEvent, 
			pApp, 
			SME_GET_APP_STATE(pApp));

	if (pNewState != SME_INTERNAL_TRAN)
		SME_STATE_TRACK(pEvent, pApp, pOldState, SME_REASON_HIT, XGetTick() - nBeginTick);
	else
		SME_STATE_TRACK(pEvent, pApp, pOldState, SME_REASON_INTERNAL_TRAN, XGetTick() - nBeginTick);
	return TRUE;
}
/*******************************************************************************************
* DESCRIPTION:  This API function installs getting and deleting external event functions. 
*  It will never exit. 
* INPUT:  
* OUTPUT: None.
* NOTE: 
*   
*******************************************************************************************/
void SmeSetExtEventOprProc(SME_GET_EXT_EVENT_PROC_T fnGetExtEvent, 
						   SME_DEL_EXT_EVENT_PROC_T fnDelExtEvent)
{
	SME_THREAD_CONTEXT_PT pThreadContext=NULL;

	if (g_pfnGetThreadContext)
		pThreadContext = (*g_pfnGetThreadContext)();
	if (!pThreadContext) return;

	pThreadContext->fnGetExtEvent = fnGetExtEvent;
	pThreadContext->fnDelExtEvent = fnDelExtEvent;

}

/*******************************************************************************************
* DESCRIPTION:  This API function is the state machine engine event handling loop function. 
*  It will never exit. 
* INPUT:  
* OUTPUT: None.
* NOTE: 

Engine has to check internal event first, because SmeActivateApp() may trigger some internal events.

Case:
	SmeActivateApp(&SME_GET_APP_VAR(Player1),NULL);
	SmeRun();
  
*******************************************************************************************/
void SmeRun()
{
	SME_EVENT_T ExtEvent;
	SME_EVENT_T *pEvent=NULL;
	SME_APP_T *pApp;
	
	SME_THREAD_CONTEXT_PT pThreadContext=NULL;
	if (g_pfnGetThreadContext)
		pThreadContext = (*g_pfnGetThreadContext)();
	if (!pThreadContext) return;

	if (!pThreadContext->fnGetExtEvent) return;

	pApp = pThreadContext->pActAppHdr;

	while (TRUE)
	{
		/* Check the internal event pool firstly. */
		pEvent = GetEventFromQueue();
		if (pEvent == NULL)
		{
			/* Wait for an external event. */
			if (FALSE == (*pThreadContext->fnGetExtEvent)(&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 (pThreadContext->fnDelExtEvent)
		{
			(*pThreadContext->fnDelExtEvent)(&ExtEvent);
			// Engine should delete this event, because translation of external event will create an internal event. 
			SmeDeleteEvent(&ExtEvent); 
		}

	} /* Wait for an external event. */
}

/*******************************************************************************************
* DESCRIPTION:   
* INPUT:  
* OUTPUT: None.
* NOTE: SmeRun() and SmeActivateApp()/SmeDeactivateApp() calls this function.  
********************************************************************************************/
BOOL DispatchInternalEvents(SME_THREAD_CONTEXT_PT pThreadContext)
{
	SME_EVENT_T *pEvent=NULL;
	SME_APP_T *pApp;
	
	if (!pThreadContext) return FALSE;

	pApp = pThreadContext->pActAppHdr;

	pEvent = GetEventFromQueue();
	while (pEvent != NULL)
	{
		pEvent->nOrigin = SME_EVENT_ORIGIN_INTERNAL;
		/* Call hook function on an internal event coming. */
		if (pThreadContext->fnOnEventComeHook)
			(*pThreadContext->fnOnEventComeHook)(SME_EVENT_ORIGIN_INTERNAL, pEvent);
		
		DispatchEventToApps(pThreadContext, pEvent);

		/* Free internal event*/
		SmeDeleteEvent(pEvent);
		/* Next internal event? */
		pEvent = GetEventFromQueue();
	}
	return TRUE;
}

/*******************************************************************************************
* DESCRIPTION:   
* INPUT:  
* OUTPUT: None.
* NOTE: SmeRun() and SmeActivateApp()/SmeDeactivateApp() calls this function.  
********************************************************************************************/
BOOL DispatchEventToApps(SME_THREAD_CONTEXT_PT pThreadContext,SME_EVENT_T *pEvent)
{
	SME_APP_T *pApp;
	if (pThreadContext==NULL || pEvent==NULL) return FALSE;

	/*Dispatch it to active applications*/
	if (pEvent->pDestApp)
	{
		/* This event has destination application. Dispatch it if the application is active.*/
		if (SME_IS_ACTIVATED(pEvent->pDestApp))
			/* Dispatch it to an destination application. */
			SmeDispatchEvent(pEvent, pEvent->pDestApp);
	}
	else if (pEvent->nCategory == SME_EVENT_CAT_UI)
		/* Dispatch UI event to the focused application. */
		SmeDispatchEvent(pEvent, pThreadContext->pFocusedApp);
	else 
	{
		/* Traverse all active applications. */
		pApp = pThreadContext->pActAppHdr;
		while (pApp != NULL) 
		{
			SmeDispatchEvent(pEvent, pApp);
			if (pEvent->bIsConsumed) 
				break;
			else pApp = pApp->pNext; 
		}
	}
	return TRUE;
}
/*******************************************************************************************
* DESCRIPTION:  This API function install a hook function. It will be called when event comes. 
* INPUT:  
* OUTPUT: None.
* NOTE: 
*   
*******************************************************************************************/
SME_ON_EVENT_COME_HOOK_T SmeSetOnEventComeHook(SME_ON_EVENT_COME_HOOK_T fnHook)
{
	SME_ON_EVENT_COME_HOOK_T fnOldHook;
	SME_THREAD_CONTEXT_PT pThreadContext=NULL;
	if (g_pfnGetThreadContext)
		pThreadContext = (*g_pfnGetThreadContext)();
	if (!pThreadContext) return NULL;

	fnOldHook = pThreadContext->fnOnEventComeHook;
	pThreadContext->fnOnEventComeHook = fnHook;
	return fnOldHook;
}

/*******************************************************************************************
* DESCRIPTION:  This API function install a hook function. It will be called when event is handled. 
* INPUT:  
* OUTPUT: None.
* NOTE: 
*   
*******************************************************************************************/
SME_ON_EVENT_HANDLE_HOOK_T SmeSetOnEventHandleHook(SME_ON_EVENT_HANDLE_HOOK_T fnHook)
{
	SME_ON_EVENT_HANDLE_HOOK_T fnOldHook;
	SME_THREAD_CONTEXT_PT pThreadContext=NULL;
	if (g_pfnGetThreadContext)
		pThreadContext = (*g_pfnGetThreadContext)();
	if (!pThreadContext) return NULL;

	fnOldHook = pThreadContext->fnOnEventHandleHook;
	pThreadContext->fnOnEventHandleHook = fnHook;
	return fnOldHook;
}
/*******************************************************************************************
* DESCRIPTION:  This API function set an event consumed. It will not dispatched to another application. 
* INPUT:  
* OUTPUT: None.
* NOTE: 
*   
*******************************************************************************************/
void SmeConsumeEvent(SME_EVENT_T *pEvent)
{
	if (!pEvent) return;
	pEvent->bIsConsumed = TRUE;
}

/*******************************************************************************************
* DESCRIPTION:  This API function sets memory allocation and free function pointers. 
* INPUT: fnMAllocProc: Memory allocation function pointer;
*	      fnMFreeProc: 	Memory free function pointer.	 
* OUTPUT: None.
* NOTE: 
*   
*******************************************************************************************/
void SmeSetMemOprProc(SME_MEM_ALLOC_PROC_T fnMAllocProc, SME_MEM_FREE_PROC_T fnMFreeProc)
{
	g_fnMAllocProc= fnMAllocProc;
	g_fnMFreeProc = fnMFreeProc;
}

/*******************************************************************************************
* DESCRIPTION:  State machine engine memory allocation function in release version. 
* INPUT: nSize: Memory size;
* OUTPUT: Allocated memory pointer.
* NOTE: It will just call user defined memory allocation function.
*   
*******************************************************************************************/
void* SmeMAllocRelease(unsigned int nSize)
{
	if (g_fnMAllocProc)
		return (*g_fnMAllocProc)(nSize);
	else return NULL;
}

/*******************************************************************************************
* DESCRIPTION:  State machine engine memory free function in release version. 
* INPUT: pUserData: Memory block pointer;
* OUTPUT: The result depends on user defined memory free function.
* NOTE: It will just call user defined memory free function.
*   
*******************************************************************************************/
BOOL SmeMFreeRelease(void * pUserData)
{
	if (g_fnMFreeProc)
		return (*g_fnMFreeProc)(pUserData);
	else return FALSE;
}

/*******************************************************************************************
* DESCRIPTION:  This API function sets thread local storage function pointers. 
* INPUT: pSetThreadContextProc: Thread context setting function pointer;
*	      pGetThreadContext: 	Thread context getting function pointer.	 
* OUTPUT: None.
* NOTE: 
*   
*******************************************************************************************/
void SmeSetTlsProc(SME_SET_THREAD_CONTEXT_PROC pfnSetThreadContext, SME_GET_THREAD_CONTEXT_PROC pfnGetThreadContext)
{
	g_pfnSetThreadContext = pfnSetThreadContext;
	g_pfnGetThreadContext = pfnGetThreadContext;
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

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


Written By
Software Developer (Senior)
United States United States
Alex "Question is more important than the answer."

Comments and Discussions