Это исторический фронтенд, унаследованный от книги MPL. Он предоставляет таблицу переходов, состоящую из рядов различных имен и функций. Действия и предохранители определяются как методы и ссылаются через указатель в переходе. Этот интерфейс обеспечивает простой интерфейс, облегчающий определение машин состояний, но более сложные машины состояний немного сложнее.
A simple example
Давайте посмотрим на диаграмму состояния машины примера основания:
Теперь мы собираемся построить его с основным интерфейсом MSM. Также предусмотрена реализация.
Transition table
Как уже говорилось ранее, МСМ основывается на таблице переходов, поэтому давайте определим одну:
Вы заметите, что это практически наш основополагающий пример. Единственным изменением в таблице переходов являются различные типы переходов (ряды). Основополагающий пример заставляет определить метод действия и не предлагает охранников. У вас есть 4 основных типа строк:
<row>приводит 5 аргументов: состояние начала, событие, состояние цели, действие и охрана.
<a_row>(“a” для действия) позволяет определить только действие и опустить условие охраны.
<g_row>(“g” для охранника) позволяет опустить поведение действия и определить только охранника.
<_row>позволяет исключить действие и защиту.
Подписью для методов действия является void method_name (event const&), например:
void stop_playback(stop const&)
Методы действия ничего не возвращают и рассматривают аргумент как ссылку. Конечно, ничто не запрещает использовать одно и то же действие для нескольких событий:
Охранники имеют в качестве разницы только обратную стоимость, которая является булевой:
bool good_disk_format(cd_detected const& evt)
Переходная таблица на самом деле представляет собой вектор MPL (или список), который ограничивает максимальный размер таблицы по умолчанию 20. Если вам нужно больше переходов, необходимо преодолеть это поведение по умолчанию, поэтому вам нужно добавить перед любым заголовком:
#define BOOST_MPL_CFG_NO_PREPROCESSED_HEADERS
#define BOOST_MPL_LIMIT_VECTOR_SIZE 30 //or whatever you need
#define BOOST_MPL_LIMIT_MAP_SIZE 30 //or whatever you need
Другое ограничение заключается в том, что типы MPL определяются только до 50 записей. На данный момент единственным решением для достижения большего является добавление заголовков в MPL (к счастью, это не очень сложно).
Defining states with entry/exit actions
В то время как состояния были перечислены в книге MPL, они теперь являются классами, которые позволяют им хранить данные, обеспечивать поведение входа, выхода и быть многоразовыми (поскольку они ничего не знают о содержащем состоянии машине). Чтобы определить состояние, наследуйте от желаемого типа состояния. Вы будете использовать простые состояния:
struct Empty: public msm::front::state<>{};
Они могут необязательно обеспечивать поведение входа и выхода:
Обратите внимание, как поведение входа и выхода шаблонизировано на машине событий и состояний. Быть дженериком облегчает повторное использование. Существует больше типов состояний (прекращающихся, прерываемых, псевдосостояний и т.д.), соответствующих стандартным типам состояний UML. Они будут подробно описаны в следующих разделах.
What do you actually do inside actions / guards?
Государственные машины определяют структуру и важные части целостного поведения, но не все. Например, если вам нужно отправить ракету на Альфа Центавра, вы можете перейти в состояние «SendRocketToAlphaCentauri», но нет кода, фактически отправляющего ракету. Именно здесь вам нужны действия. Таким образом, простым действием может быть:
Это было просто. Возможно, мы хотим дать направление. Предположим, что эта информация предоставляется извне, когда это необходимо, имеет смысл использовать событие для этого:
Мы могли бы рассчитать направление, основываясь не только на внешних данных, но и на данных, накопленных в ходе предыдущей работы. В этом случае вы можете захотеть иметь эти данные в самой машине. Поскольку переходные действия являются членами интерфейса, вы можете напрямую получить доступ к данным:
// Event
struct Fire {Direction direction;};
//front-end definition, see down
struct launcher_ : public msm::front::state_machine_def<launcher_>{
Data current_calculation;
template <class Fire> void send_rocket(Fire const& evt)
{
fire_rocket(evt.direction, current_calculation);
}
...
};
Действия входа и выхода представляют собой поведение, общее для состояния, независимо от того, через какой переход он вводится или оставляется. Государства, являющиеся многоразовыми, могут иметь смысл размещать ваши данные там, а не в машине штата, чтобы максимизировать повторное использование и сделать код более читаемым. Действия по входу и выходу имеют доступ к государственным данным (являются членами государства), а также к событию и машине состояния, таким как переходные действия. Это происходит через параметры шаблона Event и Fsm:
Выходные действия также идеально подходят для кланапа, когда государство становится неактивным.
Другим возможным использованием действия ввода является передача данных в подсостояния / подмашины. В этом случае он будет иметь значение<data>.
struct launcher_ : public msm::front::state_machine_def<launcher_>{
Data current_calculation;
// state machines also have entry/exit actions
template <class Event, class Fsm>
void on_entry(Event const& evt, Fsm& fsm)
{
launcher_::Launching& s = fsm.get_state<launcher_::Launching&>();
s.data = fsm.current_calculation;
}
...
};
set_statesback-end метод позволяет заменить полное состояние.
functorfront-end и eUML предлагают больше возможностей.
Однако этот базовый интерфейс также имеет специальные возможности с использованием переходов строки 2 / irow2._row2, a_row2, row2, g_row2, a_irow2, irow2, g_irow2позволяют вызывать действие, расположенное в любом состоянии текущего fsm или в самом интерфейсе, таким образом позволяя размещать полезные данные в любом месте, которое вы считаете нужным.
Иногда желательно генерировать новые события для государственной машины внутри действий. Поскольку метод process_event относится к заднему концу, сначала нужно получить ссылку на него. Задний конец происходит от переднего конца, поэтому один из способов сделать это - использовать слепок:
То же самое может быть реализовано в действиях входа / выхода. Признаюсь, это немного неловко. Более естественный механизм доступен с использованиемфункторафронтенда.
Defining a simple state machine
Объявление машины состояния является простым и выполняется с высоким соотношением сигнал/шум. В нашем примере игрока мы объявляем государственную машину:
struct player_ : public msm::front::state_machine_def<player_>{
/* see below */}
Это декларирует государственную машину, использующую базовый интерфейс. Теперь мы объявляем в структуре государственной машины начальное состояние:
typedef Empty initial_state;
Речь идет обо всем, что абсолютно необходимо. В примере государства декларируются внутри государственной машины для читабельности, но это не требования, государства могут декларироваться где угодно.
Все, что остается сделать, это выбрать бэкэнд (что довольно просто, так как на данный момент есть только один):
typedef msm::back::state_machine<player_> player;
Теперь у вас есть готовая к использованию государственная машина с действиями входа / выхода, охраной, переходными действиями, очередями сообщений, чтобы обработка события могла генерировать другое событие. Государственная машина также адаптировалась к вашим потребностям и убрала почти все функции, которые мы не использовали в этом простом примере. Обратите внимание, что это не по умолчанию самая быстрая государственная машина. Смотрите раздел «Получение большей скорости», чтобы узнать, как получить максимальную скорость. В двух словах, MSM не может знать о вашем использовании некоторых функций, поэтому вам придется четко рассказать об этом.
Государственные объекты строятся автоматически с помощью государственной машины. Они будут существовать до уничтожения государственной машины. MSM использует Boost. Слияние за капотом. К сожалению, это означает, что если вы определите более 10 состояний, вам нужно будет продлить дефолт:
#define FUSION_MAX_VECTOR_SIZE 20 // or whatever you need
При неожиданном событии называется<no_transition(event, state
machine, state id)>способ государственной машины. По умолчанию этот метод просто утверждается при вызове. Можно переписать метод<no_transition>для определения другой обработки:
Примечание: Возможно, вы заметили, что учебник вызывает<start()>на государственной машине сразу после создания. Метод запуска инициирует машину состояния, что означает, что она активирует начальное состояние, что означает, что начальное состояние будет называться входным поведением. Причина, по которой мы нуждаемся в этом, будет объяснена взадней части. После вызова на старт государственная машина готова к обработке событий. Таким же образом, вызов<stop()>вызовет последние действия выхода.
Defining a submachine
Теперь мы хотим расширить нашу последнюю государственную машину, сделав игровое состояние самой государственной машиной.
Это все, что вам нужно сделать. MSM теперь автоматически распознает Игра в качестве подмашины и все события, обрабатываемые Playing (NextSong и PreviousSong), теперь будут автоматически перенаправляться в Playing, когда это состояние активно. Все другие функции машины состояния, описанные ниже, также доступны. Вы даже можете решить использовать государственную машину иногда в качестве подмашины, а иногда в качестве независимой государственной машины.
Однако существует ограничение для подмашин. Если подсостояние подмашины имеет действие ввода, которое требует специального свойства события (например, заданного метода), компилятор потребует, чтобы все события, входящие в эту подмашину, поддерживали это свойство. Поскольку это практически невозможно, мы должны использовать<boost::enable_if>/<boost::disable_if>, чтобы помочь, например, рассмотреть:
// define a property for use with enable_if
BOOST_MPL_HAS_XXX_TRAIT_DEF(some_event_property)
// this event supports some_event_property and a corresponding required method
struct event1
{
// the property
typedef int some_event_property;
// the method required by this property
void some_property(){...}
};
// this event does not supports some_event_property
struct event2
{
};
struct some_state : public msm::front::state<>
{
template <class Event,class Fsm>
// enable this version for events supporting some_event_property
typename boost::enable_if<typename has_some_event_property<Event>::type,void>::type
on_entry(Event const& evt,Fsm& fsm)
{
evt.some_property();
}
// for events not supporting some_event_property
template <class Event,class Fsm>
typename boost::disable_if<typename has_some_event_property<Event>::type,void>::type
on_entry(Event const& ,Fsm& )
{ }
};
Теперь это состояние можно использовать в вашей подмашине.
Это очень распространенная проблема во многих государственных машинах, чтобы справиться с ошибками. Обычно оно включает в себя определение перехода от всех состояний к особому состоянию ошибки. Перевод: не весело. Также непрактично найти, из какого состояния возникла ошибка. Следующая диаграмма показывает пример того, что явно становится не очень читаемым:
Это не очень читаемо и не красиво. И у нас даже нет никаких действий по переходу, чтобы сделать его еще менее читаемым.
К счастью, UML предлагает полезную концепцию ортогональных областей. Рассматривайте их как легкие государственные машины, работающие одновременно внутри общей государственной машины и имеющие возможность влиять друг на друга. Эффект заключается в том, что у вас есть несколько активных состояний в любое время. Таким образом, мы можем сохранить нашу государственную машину от предыдущего примера и просто определить новый регион, состоящий из двух государств, AllOk и ErrorMode. AllOk большую часть времени активен. Но событие error_found делает переход второй области в новое активное состояние ErrorMode. Это событие не интересует основной регион, поэтому его просто проигнорируют.<no_transition>Называться будет только в том случае, если ни один регион вообще не справится с событием. Кроме того, в соответствии с мандатом UML, каждый регион получает шанс справиться с событием в порядке, установленном типом<initial_state>.
Добавить ортогональную область легко, нужно только объявить больше состояний в<initial_state>типдеф. Таким образом, добавление нового региона с AllOk в качестве начального состояния региона:
typedef mpl::vector<Empty,AllOk> initial_state;
Кроме того, когда вы обнаруживаете ошибку, вы обычно не хотите, чтобы события были обработаны. Для этого мы используем другую функцию UML, терминацию состояний. Когда какой-либо регион переходит в конечное состояние, государственная машина “ прекращает” (государственная машина и все ее государства остаются в живых) и все события игнорируются. Это, конечно, не обязательно, можно использовать ортогональные регионы без терминальных состояний. MSM также обеспечивает небольшое расширение для UML. Если вы объявите об ошибке (или увеличении). MPL-последовательность событий, например, boost::mpl::vector< ErrorMode, AnotherEvent>) как состояние прерывания, а не состояние окончания, машина состояния не будет обрабатывать любое событие, кроме того, которое завершает прерывание. Таким образом, это похоже на состояние окончания, с той разницей, что вам разрешено возобновить состояние машины, когда условие (например, обработка исходной ошибки) выполнено.
Последнее, но не менее важное: в этом примере также показана обработка отсрочки события. Допустим, кто-то ставит диск и сразу нажимает на игру. Событие не может быть обработано, но вы хотите, чтобы оно было обработано в более поздний момент и не заставляло пользователя снова нажимать на игру. Решение состоит в том, чтобы определить его как отложенный в пустом и открытом состояниях и получить его обработать в первом состоянии, где событие не должно быть отложено. После этого его можно отклонить или отклонить. В этом примере, когда Stopped становится активным, событие будет обработано, потому что только Empty и Open отсрочат событие.
UML определяет отсрочку события как государственную собственность. Для этого MSM позволяет вам указать это в штатах, предоставив тип<deferred_events>:
struct Empty : public msm::front::state<>
{
// if the play event is fired while in this state, defer it until a state
// handles or rejects it
typedef mpl::vector<play> deferred_events;
...
};
Хотя это требуется UML и просто, это не всегда практично, потому что можно отложить только в определенных условиях. Можно также захотеть сделать это частью переходного действия с дополнительным бонусом охранника для более сложного поведения. Это также соответствовало бы философии МСМ, чтобы получить как можно больше в таблице перехода, где у вас есть вся структура государственной машины. Это также возможно, но не практично с этим фронтендом, поэтому нам нужно будет выбрать другой ряд из фронтэнда функтора. Для полного описания типа<Row>, пожалуйста, посмотрите нафункторный интерфейс.
Во-первых, поскольку нет состояния, в котором МСМ может автоматически узнать об использовании этой функции, нам необходимо явно требовать возможности отложенных событий, добавив тип в определение машины состояния:
struct player_ : public msm::front::state_machine_def<player_>
{
typedef int activate_deferred_events;
...
};
Теперь мы можем отложить событие в любом переходе таблицы переходов, используя в качестве действия предопределенный<msm::front::Defer>функтор, например:
Row < Empty , play , none , Defer , none >
Это внутренний переходный ряд (см.внутренние переходы), но вы можете игнорировать это на данный момент. Это просто означает, что мы не покидаем Пустое государство. Важно то, что мы используем Defer как действие. Это примерно эквивалентно предыдущему синтаксису, но имеет то преимущество, что дает вам всю информацию в таблице перехода с дополнительной силой переходного поведения.
Второе различие заключается в том, что, как мы теперь определили переход, этот переход может играть роль в разрешенииконфликтов перехода. Например, мы можем смоделировать «если (условие 2) перейти к Игре, если (условие 1) отложить игровое событие»:
Row < Empty , play , none , Defer , condition1 >,
g_row < Empty , play , Playing , &player_::condition2 >
UML определяет два типа истории: мелкая история и глубокая история. В предыдущих примерах, если игрок играл вторую песню и пользователь нажимал паузу, оставляя Playing, при следующем нажатии на кнопку воспроизведения состояние Playing активировалось, и первая песня играла снова. Вскоре последовали первые жалобы клиентов. Они, конечно, требовали, чтобы если игрок был остановлен, то он должен был вспомнить, какая песня играла. Но это игрок был остановлен, тогда он должен перезапустить с первой песни. Как это можно сделать? Конечно, вы можете добавить немного логики программирования и генерировать дополнительные события, чтобы заставить вторую песню начаться. Что-то вроде:
if (Event == end_pause)
{
for (int i=0;i< song number;++i) {player.process_event(NextSong()); }
}
Не очень нравится в этом примере, не так ли? Чтобы решить эту проблему, вы определяете то, что называется мелкой или глубокой историей. Неглубокая история активирует последнее активное подсостояние подмашины, когда эта подмашина снова становится активной. Глубокая история делает то же самое рекурсивно, поэтому, если это последнее активное подсостояние подмашины само было подмашиной, его последнее активное подсостояние станет активным, и это будет продолжаться рекурсивно, пока активное состояние не станет нормальным состоянием. Например, давайте посмотрим на следующую диаграмму UML:
Обратите внимание, что основное отличие от предыдущих диаграмм заключается в том, что начальное состояние исчезает и заменяется символом истории (H внутри круга).
Как объясняется внебольшом учебнике UML, История - хорошая концепция с не совсем удовлетворяющей спецификацией. MSM сохранил концепцию, но не спецификацию, и делает это политикой, и вы можете добавить свои собственные типы истории (ссылкаобъясняет, что нужно сделать). Кроме того, история — это бэкэнд-политика. Это позволяет повторно использовать одно и то же определение государственной машины с различными политиками истории в разных контекстах.
Конкретно, ваш интерфейс остается неизменным:
struct Playing_ : public msm::front::state_machine_def<Playing_>
Затем вы добавляете политику в бэкэнд в качестве второго параметра:
Это означает, что неглубокая история должна быть активирована, если машина состояния Playing активируется событием end_pause и только этим событием (или любым другим событием, добавленным в mpl::vector). Если бы государственная машина находилась в остановленном состоянии и была создана игра события, история не активировалась бы и нормальное начальное состояние стало бы активным. По умолчанию история отключена. Для вашего удобства библиотека предоставляет в дополнение к ShallowHistory стандартную политику AlwaysHistory, которая всегда активирует историю, независимо от того, какое событие вызывает активацию подмашины. Глубокая история не доступна в качестве политики (но может быть добавлена). Причина в том, что это будет противоречить политике, которую могут определить подмашины. Конечно, если бы, например, Song1 была самой государственной машиной, она могла бы использовать саму политику ShallowHistory, создавая тем самым для себя Deep History. Приведем такжепример.
Completion (anonymous) transitions
На следующей диаграмме показан пример использования этой функции:
Анонимные переходы — это переходы без названного события. Это означает, что переход автоматически загорается, когда вводится состояние предшественника (точнее, после действия входа). В противном случае это нормальный переход с действиями и охраной. Зачем тебе что-то подобное? Возможный случай будет, если часть вашей машины состояния реализует некоторый алгоритм, где состояния являются этапами реализации алгоритма. Затем, используя несколько анонимных переходов с различными условиями защиты, вы фактически реализуете некоторое утверждение if/else. Другим возможным применением может быть система реального времени, называемая через регулярные промежутки времени и всегда выполняющая одно и то же, что означает реализацию одного и того же алгоритма. Преимущество заключается в том, что как только вы узнаете, сколько времени занимает переход для выполнения в системе, вычисляя самый длинный путь (количество переходов от начала до конца), вы можете в значительной степени узнать, сколько времени займет ваш алгоритм в худшем случае, что, в свою очередь, говорит вам, сколько временных рамок вы должны запросить у планировщика.
Если вы используете Executable UML (хорошая книга, описывающая его как «Executable UML, основа архитектуры, управляемой моделью»), вы заметите, что государственная машина обычно генерирует событие для себя только для того, чтобы заставить покинуть состояние. Анонимные переходы освобождают вас от этого ограничения.
Если вы не используете эту функцию в конкретной государственной машине, MSM отключит ее, и вы не заплатите за нее. Однако, если вы используете его, существует небольшой штраф за производительность, поскольку MSM будет пытаться уволить сложное событие (другое название UML для анонимных переходов) после каждого принятого перехода. Таким образом, это удвоит стоимость обработки событий, что не так плохо, как кажется, поскольку скорость исполнения MSM’ очень высока.
Для определения такого перехода используйте “none” в качестве события в таблице перехода, например:
Внутренние переходы — это переходы, выполняемые в рамках активного состояния, простого состояния или подмашины. Их можно рассматривать как самопереход этого состояния, без действия входа или выхода. Это полезно, когда все, что вам нужно, это выполнить некоторый код для данного события в данном состоянии.
Внутренние переходы определяются как имеющие более высокий приоритет, чем обычные переходы. Хотя это имеет смысл для подмашины с точками выхода, это удивительно для простого состояния. MSM позволяет вам определить приоритет перехода, установив позицию перехода & #8217 внутри таблицы перехода (см.). Разница между «нормальным» и внутренним переходами в том, что внутренние переходы не имеют целевого состояния, поэтому нам нужны новые типы рядов. У нас были a_row, g_row, _row и row, теперь мы добавляем a_irow, g_irow, _irow и irow, которые похожи на обычные переходы, но не определяют целевое состояние. Например, внутренний переход с условием охраны может быть:
Эти новые типы строк могут быть размещены в любом месте таблицы перехода, так что вы все еще можете сгруппировать структуру машины состояния. Единственным отличием поведения от стандарта UML является отсутствующее понятие более высокого приоритета для внутренних переходов. Рассмотримпример.
Также можно сделать это способом UML-соответствия, объявив переходную таблицу под названием<internal transition_table>внутри самого состояния и используя внутренние типы строк. Например:
Объявляет внутреннюю таблицу перехода, называемую внутренней_transition_table, и реагирует на событие cd_detected, вызывая внутреннее_действие на Пустоте. Давайте отметим несколько моментов:
Внутренние таблицы не называются таблицами перехода, а таблицами внутреннего перехода
.
Они используют разные, но похожие типы строк: a_internal, g_internal, _internal и internal.
Эти типы берут в качестве первого шаблонного аргумента запускающее событие, а затем метод действия и защиты. Обратите внимание, что единственным реальным отличием классических строк является дополнительный аргумент перед указателем функции. Это тип, по которому будет называться функция.
Это также позволяет при желании использовать действия и охрану из другого состояния государственной машины или в самой государственной машине.
Подмашины могут иметь внутреннюю таблицу перехода и классическую таблицу перехода.
следующий примериспользует a_internal. Он также использует внутренние переходы на основе функтора, которые будут объяснены в.фронтальный функтор, пожалуйста, игнорируйте их на данный момент. Также обратите внимание, что внутренние переходы, определяемые состоянием, имеющие наивысший приоритет (согласно стандарту UML), проверяются до тех, которые определены в таблице перехода состояния машины.
Какой метод вы должны использовать? Это зависит от того, что вам нужно:
Первая версия (с использованием irow) проще и, вероятно, компилируется быстрее. Это также позволяет вам выбрать приоритет вашего внутреннего перехода.
Вторая версия более логична с точки зрения UML и позволяет сделать состояния более полезными и многоразовыми. Он также позволяет вызывать действия и охрану на любое состояние государственной машины.
Note: There is an added
possibility coming from this feature. The
internal_transition_table transitions being added directly
inside the main state machine's transition table, it is possible, if it is
more to your state, to distribute your state machine definition a bit like
Boost.Statechart, leaving to the state machine itself the only task of
declaring the states it wants to use using the
explicit_creation type definition. While this is not the
author's favorite way, it is still possible. A simplified example using only
two states will show this possibility:
Для подмашин предлагается дополнительный бонус, который может иметь как стандартную таблицу переходов, так и внутреннюю таблицу переходов (которая имеет более высокий приоритет). Это упрощает задачу, если вы решили создать полноценную машину. Это также несколько быстрее, чем стандартная альтернатива, с добавлением ортогональных регионов, поскольку отправка событий, если она будет принята внутренней таблицей, не будет продолжаться в субрегионах. Это дает вам отправку O(1) вместо O(количество регионов). Хотя пример с eUML, то же самое можно сделать с любым интерфейсом.
more row types
Также можно писать переходы с помощью действий и предохранителей не только от государственной машины, но и от содержащихся в ней состояний. При этом необходимо указать не только указатель метода, но и объект, по которому его можно назвать. Этот переходный ряд называется не очень оригинально<row2>. Они приходят, как нормальные переходы в четырех ароматах:<a_row2, g_row2,
_row2 and row2>. Например, переход, вызывающий действие из состояния Пустоты, может быть:
Для внутренних переходов доступны те же возможности, что и у нас:<a_irow2, g_irow2, _irow2 and row2>. Для переходов, определенных как часть<internal_transition_table>, можно использоватьa_internal, g_internal, _internal, internalтипы строк из предыдущих разделов.
Эти типы строк позволяют распределять машинный код состояния между состояниями, делая их многоразовыми и более полезными. Использование таблиц перехода внутри государств также способствует этой возможности. Также приводитсяпримерэтих новых строк.
Explicit entry / entry and exit pseudo-state / fork
MSM (почти) полностью поддерживает эти функции, описанные внебольшом учебнике UML. В настоящее время существуют два ограничения:
можно только явно ввести подсостояние цели, но не подсостояние.
Явно выйти не представляется возможным. Необходимо использовать точки выхода.
Рассмотрим конкретный пример:
Мы находим на этой диаграмме:
A“normal” активация SubFsm2, вызванная событием1. В каждом регионе активируется начальное состояние, то есть SubState1 и SubState1b.
Явная запись в SubFsm2::SubState2 для региона “1” с событием2 в качестве триггера, что означает, что в регионе “2” начальное состояние, SubState1b, активировано.
Вилка в регионы “1” и “2” в явные записи SubState2 и SubState2b, вызванные событием3. Оба государства становятся активными, поэтому ни один регион не активируется по умолчанию (если бы у нас был третий, это было бы).
Соединение двух переходов через входное псевдосостояние, SubFsm2::PseudoEntry1, вызванное событием 4 и запускающее также второй переход на одном и том же событии (оба перехода должны быть вызваны одним и тем же событием). Регион “2” активируется по умолчанию и активируется SubState1b.
Выход из SubFsm2 с использованием псевдосостояния выхода PseudoExit1, вызванного событием 5 и соединяющего два перехода с использованием одного и того же события. Далее событие переносится на второй переход и оба региона выходят, поскольку SubFsm2 становится неактивным. Обратите внимание, что если переход не определен из PseudoExit1, будет обнаружена ошибка (как определено в стандарте UML) и вызов no_transition.
Во-первых, чтобы определить, что состояние является явной записью, вы должны сделать его состоянием и пометить его как явное, давая в качестве параметров шаблона регион id (область id начинается с 0 и соответствует первому начальному состоянию последовательности типа начального состояния).
struct SubFsm2_ : public msm::front::state_machine_def<SubFsm2_>
{
struct SubState2 : public msm::front::state<> ,
public msm::front::explicit_entry<0>
{...};
...
};
Затем вы можете использовать его в качестве цели при переходе с State1 в качестве источника:
_row < State1, Event2, SubFsm2::direct< SubFsm2_::SubState2> > //SubFsm2_::SubState2: complete name of SubState2 (defined within SubFsm2_)
Синтаксис заслуживает объяснения. SubFsm2_ - это передняя часть. SubState2 является вложенным состоянием, поэтому синтаксис SubFsm2_::SubState2. Содержащая машина (содержащая State1 и SubFsm2) относится к backend экземпляру (SubFsm2). SubFsm2::direct указывает, что требуется явная запись.
Благодаря библиотекеmpl_graphвы также можете пропустить индекс региона и позволить MSM узнать для вас. Однако следует отметить два момента:
MSM может узнать индекс региона только в том случае, если явное состояние входа каким-то образом связано с начальным состоянием через переход, независимо от направления.
Для этой функции существует временная стоимость компиляции.
Примечание (также применимо для форков): для того, чтобы сделать время компиляции более приемлемым для более стандартных случаев, и в отличие от начальных состояний, явные состояния ввода, которые также не встречаются в таблице перехода введенной подмашины (редкий случай), НЕ создаются автоматически. Чтобы явно создавать такие состояния, вам нужно добавить в машину состояний, содержащую явные состояния, простую типизированную последовательность состояний, которые должны быть явно созданы, например:
Примечание (также применимо для вилок): На данный момент невозможно использовать подмашину в качестве цели явного ввода. Пожалуйста, используйте псевдосостояния входа для почти идентичного эффекта.
Fork
Нужна вилка вместо явной записи? Поскольку форк является явным входом в состояния разных регионов, мы не меняем определение состояния по сравнению с явным входом и указываем в качестве цели список явных состояний входа:
С SubState2, определенным как ранее, и SubState2b, определенным как находящийся во втором регионе (осторожно: MSM не проверяет, что регион правильный):
struct SubState2b : public msm::front::state<> ,
public msm::front::explicit_entry<1>
Entry pseudo states
Для определения входного псевдосостояния необходимо вывести из соответствующего класса и придать региону id:
struct PseudoEntry1 : public msm::front::entry_pseudo_state<0>
И добавьте соответствующий переход в таблицу перехода машины состояния верхнего уровня:
И еще в определении подмашины SubFsm2_ (помните, что UML определяет точку входа как связь между двумя переходами), например, на этот раз с помощью метода действия:
И, наконец, псевдосостояния выхода должны использоваться почти одинаково, но определяться по-разному: в качестве аргумента шаблона пересылается событие (не требуется идентификатор области):
struct PseudoExit1 : public exit_pseudo_state<event6>
И вам нужно, как для входа псевдосостояний, два перехода, один в подмашине:
Важное примечание 1:UML определяет переход в псевдосостояние входа и не имеющий ни второго перехода, ни переход с ограждением как ошибку, но не определяет обработку ошибок. МСМ будет терпеть такое поведение; псевдосостояние входа будет просто новым активным состоянием.
Важное примечание 2: UML определяет переход в псевдосостояние выхода и отсутствие второго перехода как ошибку, а также не определяет обработку ошибок. Поэтому было решено реализовать псевдосостояние выхода в качестве терминальных состояний, и содержащее композитное вещество, не выходящее должным образом, останется прерванным, как это было технически “выход ”.
Важное примечание 3:UML гласит, что для точки выхода необходимо использовать одно и то же событие в обоих переходах. MSM смягчает это правило и хочет, чтобы событие на внутреннем переходе было конвертируемым в один из внешних переходов. В нашем случае event6 конвертируется из event5. Обратите внимание, что переадресованное событие должно быть указано в определении точки выхода. Например, мы можем определить событие 6 как:
struct event
{
event(){}
template <class Event>
event(Event const&){}
}; //convertible from any event
Note: There is a current
limitation if you need not only convert but also get some data from the
original event. Consider:
struct event1
{
event1(int val_):val(val_) {}
int val;
}; // forwarded from exit point
struct event2
{
template <class Event>
event2(Event const& e):val(e.val){} // compiler will complain about another event not having any val
int val;
}; // what the higher-level fsm wants to get
Решение заключается в предоставлении двух конструкторов:
struct event2
{
template <class Event>
event2(Event const& ):val(0){} // will not be used
event2(event1 const& e)):val(e.val){} // the conversion constructor
int val;
}; // what the higher-level fsm wants to get
Flags
Эторуководствопосвящено концепции, не определенной в UML: флаги. Он был добавлен в МСМ после того, как много раз доказал свою полезность. Пожалуйста, не пугайтесь, поскольку мы не говорим об уродливых ярлыках, сделанных из невероятного сговора булев.
Если вы посмотрите на рост. Документация Statechart вы найдете этот код:
Хотя это верно и встречается во многих книгах UML, это может быть подвержено ошибкам и потенциальной бомбой замедленного действия, когда ваша государственная машина растет, и вы добавляете новые состояния или ортогональные области.
И самое главное, он скрывает реальный вопрос, который будет “ определяет ли текущее состояние моей государственной машины особое свойство”? В этом особом случае “ мои ключи находятся в состоянии блокировки”? Итак, давайте применим Фундаментальную Теорему Программной Инжиниринга и переместим один уровень абстракции выше.
В нашем примере с проигрывателем, скажем, нам нужно знать, есть ли у игрока загруженный компакт-диск. Мы могли бы сделать то же самое:
Это означает, что игра поддерживает оба свойства. Чтобы проверить, есть ли у вашего проигрывателя загруженный компакт-диск, проверьте, активен ли ваш флаг в текущем состоянии:
player p; if (p.is_flag_active<CDLoaded>()) ...
А если у вас есть ортогональные области? Как определить, находится ли государственная машина в помеченном состоянии? По умолчанию вы сохраняете один и тот же код, и текущие состояния будут OR'ed, что означает, что если одно из активных состояний имеет флаг, то _flag_active возвращается истинным. Конечно, в некоторых случаях вы можете захотеть, чтобы все активные государства были отмечены как активные. Вы также можете использовать активные состояния:
if (p.is_flag_active<CDLoaded,player::Flag_AND>()) ...
Записка. Из-за тайных правил C++, при вызове внутри действия правильный вызов:
if (p.template is_flag_active<CDLoaded>()) ...
Следующая диаграмма отображает ситуацию с флагом в учебнике.
Event Hierarchy
Бывают случаи, когда необходим переход по категориям событий. Примером может служить текстовый анализ. Допустим, вы хотите разобрать строку и использовать машину состояния для управления состоянием разбора. Вы хотите разобрать 4 цифры и решить использовать состояние для каждой соответствующей цифры. Ваша государственная машина может выглядеть так:
Но как обнаружить цифровое событие? Мы хотели бы избежать определения 10 переходов на char_0, char_1... между двумя состояниями, поскольку это заставит нас написать 4 x 10 переходов, и время компиляции пострадает. Чтобы решить эту проблему, MSM поддерживает запуск перехода на событие подкласса. Например, если мы определим цифры как:
struct digit {};
struct char_0 : public digit {};
И к тому же для других цифр мы теперь можем запустить события char_0, char_1, и это вызовет переход с «цифрой» в качестве триггера.
Примерс измерением производительности, взятый из документации Boost. Xpressive иллюстрирует этот пример. Вы можете заметить, что производительность на самом деле очень хорошая (в этом случае даже лучше).
Customizing a state machine / Getting more speed
MSM предлагает множество функций UML на высокой скорости, но иногда вам просто нужно больше скорости и вы готовы отказаться от некоторых функций в обмен. Процесс_событие выполняет несколько задач:
проверка состояния прекращения/прекращения
обработка очереди сообщений (для действий входа / выхода / перехода, создающих сами события)
обработка отложенных событий
Исключения (или нет)
обработка переключения состояния и вызовов действий
Из этих задач только последняя является абсолютно необходимой для государственной машины (ее основной работы), другие являются приятными, которые стоят процессорного времени. Во многих случаях это не так важно, но во встроенных системах это может привести к специальным реализациям машин. MSM обнаруживает сам по себе, если конкретная государственная машина использует состояния прекращения / прерывания и отложенные события, и деактивирует их, если они не используются. Для двух других, если они вам не нужны, нужно помочь, указав их в своей реализации. Это делается с двумя простыми типами:
<no_exception_thrown>указывает на то, что поведение никогда не будет бросаться, и МСМ не нужно ничего ловить
.
<no_message_queue>указывает на то, что никакое действие само по себе не сгенерирует новое событие, и MSM может спасти нам очередь сообщений.
Третья возможность конфигурации, описанная здесь, заключается в ручной активации отложенных событий с использованием<activate_deferred_events>. Например, следующая государственная машина устанавливает все три типа конфигурации:
struct player_ : public msm::front::state_machine_def<player_>
{
// no need for exception handling or message queue
typedef int no_exception_thrown;
typedef int no_message_queue;
// also manually enable deferred events
typedef int activate_deferred_events
...// rest of implementation
};
Важное примечание: Поскольку псевдосостояния выхода используют очередь сообщений для пересылки событий из подмашины, опция<no_message_queue>не может использоваться с машинами состояния, содержащими псевдосостояние выхода.
Choosing the initial event
Для этого используется метод<start>. Это приводит к тому, что поведение начального состояния выполняется. Как и любое поведение при входе, оно становится параметром события, вызывающего состояние. Но когда машина запускается, событие не срабатывает. В этом случае MSM отправляет<msm::back::state_machine<...>::InitEvent>, что может быть не по умолчанию. Для этого специального случая МСМ предоставляет механизм конфигурации в виде типдефа. Если переднее определение машины состояния предоставляет набор начального_события для другого события, это событие будет использоваться. Например:
Эта функция по-прежнему поддерживается в MSM для обратной совместимости, но устарела из-за того, что каждое действие / действие / действие входа / действие выхода передает государственную машину в качестве аргумента и может быть удалено позже.
Все состояния, определенные в государственной машине, создаются при государственном машиностроении. Это имеет огромное преимущество в уменьшении синтаксического шума. Стоимость — это небольшая потеря контроля пользователя над созданием государства и доступом к нему. Но иногда нужен был способ, чтобы государство получило доступ к содержащему его государственному аппарату. По сути, государство должно изменить свою декларацию на:
struct Stopped : public msm::front::state<sm_ptr>
И для обеспечения функции set_sm_ptr:<void set_sm_ptr(player*
pl)>
получить указатель на содержащую государственную машину. То же самое относится к end_state / interrupt_state и entry_pseudo_state / exit_pseudo_state.
Статья Basic front-end раздела Meta State Machine (MSM) Chapter 3. Tutorial может быть полезна для разработчиков на c++ и boost.
Материалы статей собраны из открытых источников, владелец сайта не претендует на авторство. Там где авторство установить не удалось, материал подаётся без имени автора. В случае если Вы считаете, что Ваши права нарушены, пожалуйста, свяжитесь с владельцем сайта.