Визуальное моделирование сложных реагирующих систем при помощи диаграмм состояния UML Harel

ОГЛАВЛЕНИЕ

Данная статья описывает кросс-платформенную среду разработки приложений на основе Harel UML StateChart рыночного качества с открытым исходным кодом под названием StateWizard для разработки параллельных, распределенных реагирующих систем реального времени с простотой, эффективностью и масштабируемостью.

Введение

Реагирующая система является во многом управляемой событиями, она постоянно должна реагировать на внешние и внутренние сигналы возбуждения (стимулы). Примеры включают телефоны, автомобили, сети передачи данных, компьютерные операционные системы и ракеты. Проблема коренится в трудности описания реагирующего поведения таким образом, чтобы оно было однозначным и реалистичным, и в то же время достаточно формальным и строгим,  чтобы было возможно его моделирование на вычислительной машине.

Использование диаграмм состояния Harel приобретает широкое распространение, так как их разновидность вошла в состав унифицированного языка моделирования. Диаграммы данного типа позволяют моделировать сверхсостояния, одновременные состояния, и действия как часть состояния.

Проблемы с обычными конечными автоматами

Классические методы моделирования конечного автомата Мили-Мура требуют создания отдельных узлов для каждой верной комбинации параметров, определяющих состояние. Это может привести к очень большому числу узлов и переходов между узлами для чуть ли не самых простых систем. Такая сложность снижает удобочитаемость диаграммы состояний.

Еще одно из ограничений моделирования вычислительной системы как обычного конечного автомата – это отсутствие поддержки для параллельных структур. Моделирование традиционных конечных автоматов основано на последовательных переходах из одного состояния к следующему. Параллельные системы нельзя моделировать таким способом, так как разные аспекты системы могут быть в различных состояниях.

Ограничения, характерные для моделей конечных автоматов, включают свойственную им сложность, появляющуюся, когда число состояний увеличивается, и при моделировании параллельных систем. Данная статья описывает кроссплатформенную среду разработки приложений на основе Harel UML StateChart рыночного качества с открытым исходным кодом под названием StateWizard для разработки параллельных, распределенных реагирующих систем реального времени с простотой, эффективностью и масштабируемостью. Следующие разделы описывают способы решения сложных проблем реагирующих систем при помощи следующих возможностей диаграмм состояния Harel:

  • Иерархические конечные автоматы
  • Поддерживает большие конечные автоматы с сотнями состояний посредством разделения определений дерева состояний на несколько файлов C/C++
  • Информация об истории состояний и история переходов
  • Защищенные переходы при обработке событий
  • Условные псевдосостояния
  • Объединенные псевдосостояния
  • Ортогональные состояния
  • Встроенные таймеры состояний

Как уменьшить размер представления?

Решение – иерархические конечные автоматы. При обычном проектировании конечных автоматов все состояния считаются находящимися на одном и том же уровне. Модель не фиксирует общность, существующую среди состояний. В реальности многие состояния обрабатывают большинство сообщений аналогично, и различается только обработка нескольких важных сообщений. Даже когда реальная обработка различается, все же имеется некоторая общность. Проектирование иерархического конечного автомата фиксирует общность путем упорядочивания состояний в виде иерархии. Состояния на более высоком уровне в иерархии выполняют обработку сообщений общего характера, в то время как состояния более низких уровней наследуют общность от более высоких уровней и выполняют конкретные функции состояния.

Иерархия состояний конечного автомата основана на отношении типа «родитель-потомок», которое изображается компоновкой ветвей дерева. Например, на следующем рисунке: иерархия состояний в форме дерева, узел Игрок в дереве состояний – это корневое состояние, имеющее двух потомков: PowerDown (выключение питания) и PowerUp (включение питания). При этом узел PowerUp также является родителем трех потомков, и эти дети именуются родственными состояниями (братьями или сестрами) между собой.

Эта иерархическая организация означает, что когда запускается переход из состояния PowerDown в PowerUp, конечный автомат отключает состояние PowerDown и его потомков (если они есть) и активирует PowerUp и один или больше его потомков (если они есть).

Игрок

  • PowerDown (выключение питания) (инициализация)
  • PowerUp (включение питания)
    • Playing (игра) (инициализация)
    • Pause  (пауза)
    • Record (запись)

Рисунок: Иерархия состояний в форме дерева

Эта же иерархия может быть представлена в форме вложенной диаграммы. В этом формате иерархия среди состояний показывается при помощи вкладывания состояний-потомков внутрь их родительского состояния.

Рисунок: Иерархия состояний в форме диаграммы

При помощи API (интерфейса прикладного программирования) среды разработки приложений StateWizard конечный автомат Player (игрок) определяется так:

/* Определение сложного корневого состояния Игрока. */
#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)

Начальное дочернее состояние обозначает начальное состояние для конечных автоматов, имеющих состояния. Это дочернее состояние должно наступать в том и только в том случае, если автомат имеет одно или больше состояний или параллельных потомков.

Например, начальное дочернее состояние Playing сложного состояния PowerUp определяется, как показано ниже. 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).

// Файл: 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.

// Файл: 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);

Как легко описать параллельность состояний?

Решение – позволить ортогональным состояниям работать одновременно.

Сложные состояния имеют одну или больше областей для субсостояний. Область – это просто контейнер для субсостояний.

Сложное состояние – это состояние, состоящее из субсостояний. Сложное состояние можно разбить при помощи отношений логического умножения на две или больше совпадающих области, являющиеся контейнерами для субсостояний, или при помощи отношений логического сложения разбить на взаимоисключающие непересекающиеся субсостояния.

Ортогональное состояние: Если сложное состояние можно разбить при помощи отношений на две или более совпадающих области, оно называется ортогональным состоянием.

Следующий макрос определяет ортогональное состояние под названием 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(новое состояние) должно быть определено для обработки события простоя.

Следующий пример определяет  таймер на 3000 мс в состоянии Player и таймер на 9000 мс в состоянии Playing. При простое будет вызвано действие 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) /* Событие обычного таймера */
SME_ON_STATE_TIMEOUT(9000, PlayingTimeOut, Pause)
/* Переход в состояние паузы, если простой длится 9 секунд. */

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)

Как моделировать псевдосостояния?

Псевдосостояние – это абстракция различных типов узлов в графе конечного автомата, обозначающих невозвратные точки на ветвях переходов из одного состояния в другое (например, точки разветвлений). Псевдосостояния используются для создания сложных переходов из простых переходов. Например, путем объединения перехода, входящего в разветвляющееся псевдосостояние с набором переходов, выходящих из разветвляющегося псевдосостояния, мы получаем сложный переход, ведущий к набору искомых состояний.

Условное (разветвляющееся) псевдосостояние – это краткое обозначение для  многочисленных выходящих переходов, из которых  все запускаются одним и тем же событием, но каждый из которых имеет различные защиты.

Объединенное псевдосостояние – это состояние с несколькими  входящими переходами и с одним исходящим переходом.

При помощи API среды разработки приложений StateWizard условное псевдосостояние 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() – это функция цикла обработки событий ядра конечного автомата. Если в очереди нет событий, эта функция ждет внешнего события. Если любое событие было запущено, она передаст его соответствующим приложениям и будет обеспечивать выполнение конечными автоматами соответствующих переходов и действий.

Если было запущено внешнее событие, оно будет преобразовано во внутреннее событие при помощи ожидающей внешнее событие функции, которая является функцией виртуального уровня операционной системы.

Если функция разрушения внешнего события привязана, 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)
    {
        /* Сначала проверяется внутренний пул событий. */
        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);
        }

    } /* Ожидание внешнего события. */
}

Функция SmeSetExtEventOprProc() устанавливает зависящие от платформы рабочие функции внешнего события. Они работают в качестве виртуального слоя ОС StateWizard. Функция цикла обработки событий 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;
}

 

Управление внешними событиями

Функция SmeSetExtEventOprProc()устанавливает зависящие от платформы рабочие функции внешнего события. Они работают в качестве виртуального слоя ОС StateWizard. Функция цикла обработки событий 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; //Запрашиваем выход
        }
#ifdef SME_WIN32
#else
        // Встроенный таймер обратного вызова в Linux
        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 {
            // Преобразование собственного сообщения в событие SME.
            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("Получено внешнее сообщение. \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("Пример кроссплатформенного таймера состояния: \n");

    // Установка функций данных локальной памяти потока.
    XTlsAlloc();
    SmeSetTlsProc(XSetThreadContext, XGetThreadContext);

    ////////////////////////////////////////////////////////////////
    // Инициализация ядра.
    SmeInitEngine(&g_AppThreadContext1); // Инициализация потока-1

    XInitMsgBuf();

    // Установка функций обработчиков событий.
    SmeSetExtEventOprProc(XGetExtEvent, XDelExtEvent,
    XPostThreadExtIntEvent, XPostThreadExtPtrEvent, XInitMsgBuf, XFreeMsgBuf);

    // Создание потока для запуска внешних событий.
    ret = XCreateThread(ConsoleProc, NULL, &ThreadHandle);

    // Создание потока приложения Player2
    ret = XCreateThread(AppThread2Proc, NULL, &ThreadHandle);

    SmeActivateObj(&Player1,NULL);
    SmeRun();
    printf("Выход из SmeRun() в потоке -1 \n");
    XFreeMsgBuf();
    XFreeThreadContext(&g_AppThreadContext1); /* Наконец, освобождаются ресурсы локальной памяти потока. */
}

Функция потока второго приложения. Экземпляр конечного автомата Player2 выполняется в данном потоке.

#ifdef WIN32
    unsigned __stdcall AppThread2Proc(void *Param)
#else
    void* AppThread2Proc(void *Param)
#endif
{
    SmeInitEngine(&g_AppThreadContext2); // Инициализация в потоке-2
    // Сохранение указателя на контекст потока в локальной памяти потока.
    // XSetThreadContext(&g_AppThreadContext2);
    XInitMsgBuf();
    // Установка функций обработки событий.
    SmeSetExtEventOprProc(XGetExtEvent, XDelExtEvent,
    XPostThreadExtIntEvent, XPostThreadExtPtrEvent, XInitMsgBuf, XFreeMsgBuf);

    SmeActivateObj(&Player2,NULL);
    SmeRun();
    printf("Выход из SmeRun() в потоке-2 \n");

    XFreeMsgBuf();
    XFreeThreadContext(&g_AppThreadContext2); /* Наконец, освобождаются ресурсы локальной памяти потока. */
    return 0;
}

Панель управления для запуска внешних событий.

#ifdef WIN32
    unsigned __stdcall ConsoleProc(void *Param)
#else
    void* ConsoleProc(void *Param)
#endif
{
    // На платформе Linux вызовите функцию XInitTimer в потоке триггера события простоя.
    // При простое поток триггера внешнего события отправляет
    // SME_EVENT_TIMER потоку приложения конечного автомата
    // и затем вызывает функцию обратного вызова,
    // установленную функцией XSetTimer.
    XInitTimer();

    printf("Вход в поток консольной процедуры.\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;//Отмена ConsoleProc в демоне

            case 'x':
            case 'X'://Выход
                XPostThreadExtIntEvent(&g_AppThreadContext1,
                    SME_EVENT_EXIT_LOOP, 0, 0, NULL,0,
                    SME_EVENT_CAT_OTHER);
                printf("Прекращение работы потока-1 ... Пожалуйста, подождите. \n");
                XPostThreadExtIntEvent(&g_AppThreadContext2,
                    SME_EVENT_EXIT_LOOP, 0, 0, NULL,0,
                    SME_EVENT_CAT_OTHER);
                printf("Прекращение работы потока -2 ... Пожалуйста, подождите. \n");
                return 0;
                break;
. . .
            default:
                break;
        }
    }

    return 0;
}

Дополнительная информация

Вы можете получить дополнительную информацию и загрузить инфраструктуру на основе UML StateChart с открытым исходным кодом и инструментарий IDE (интегрированная среда разработки) здесь.