На данный момент есть один бэкэнд. Этот бэкэнд содержит библиотечный движок и определяет компромиссы производительности и функциональности. Доступный в настоящее время бэкэнд реализует большинство функций, определенных стандартом UML 2.0, с очень высокой скоростью выполнения, в обмен на более длительное время компиляции. Скорость выполнения обусловлена постоянными возможностями двойной диспетчеризации и самоадаптации, позволяющими фреймворку адаптироваться к функциям, используемым данной конкретной государственной машиной. Все ненужные функции либо отключаются сами, либо могут быть отключены вручную. См. раздел 5.1 для полного описания алгоритма выполнения-завершения.
MSM, будучи разделенным между передним и задним, сначала нужно определить передний. Затем, чтобы создать машину реального состояния, необходимо объявить бэкэнд:
typedef msm::back::state_machine<my_front_end> my_fsm;
Теперь у нас есть полностью функциональный тип машины. В следующих разделах будет описано, что с ним можно сделать.
Starting and stopping a state
machine
Метод<start()
>запускает машину состояния, что означает, что она активирует начальное состояние, что означает, в свою очередь, что начальное состояние будет называться входным поведением. Нам нужен метод запуска, потому что вы не всегда хотите, чтобы начальное состояние было немедленно вызвано, а только тогда, когда ваша машина состояния готова обрабатывать события. Хорошим примером этого является использование машины состояний для записи алгоритма, и каждый цикл обратно в исходное состояние является вызовом алгоритма. Каждый призыв к началу заставит алгоритм работать один раз. ПримерiPodSearchиспользует эту возможность.
Метод<stop()
>работает одинаково. Это приведет к тому, что будут названы действия выхода активных в настоящее время состояний.
Оба метода на самом деле не являются абсолютной необходимостью. Если вы не позвоните им, это приведет к тому, что ваш первый вход или ваш последний выход не будет вызван.
Основной причиной существования государственной машины является отправка событий. Для МСМ события являются объектами данного типа событий. Сам объект может содержать данные, но тип события — это то, что решает переход. Для MSM, если некоторое_событие является заданным типом (например, простая структура) и e1 и e2 конкретные экземпляры некоторого_события, e1 и e2 эквивалентны с точки зрения перехода. Конечно, e1 и e2 могут иметь разные значения, и вы можете использовать их внутри действий. События отправляются в качестве ссылки, поэтому действия не могут изменить события по очевидным причинам побочных эффектов. Чтобы отправить событие типа some_event, вы можете просто создать его на лету или мгновенно, если перед обработкой:
my_fsm fsm; fsm.process_event(some_event());
some_event e1; fsm.process_event(e1)
Создание события на лету будет оптимизировано компилятором, чтобы производительность не ухудшалась.
Бэкэнд также предлагает способ узнать, какое состояние является активным, хотя обычно это необходимо только для отладки. Если вам просто нужно что-то делать с активным состоянием,внутренние переходыилипосетители— лучшая альтернатива. Если вам нужно знать, какое состояние является активным, const int* current_state() вернет множество идентификаторов состояния. Пожалуйста, обратитесь к разделувнутренних данных, чтобы узнать, как генерируются идентификаторы состояния.
Общей потребностью является возможность сохранить государственную машину и восстановить ее в другое время. MSM поддерживает эту функцию для базового и функторного интерфейсов и более ограниченным образом для eUML. MSM поддерживает надстройку::сериализация из коробки (предлагая функцию<serialize
>). На самом деле, для базовой сериализации не нужно многого делать, МСМ-машина состояния сериализуема почти как любой другой тип. Без какой-либо специальной работы можно заставить государственную машину запомнить свое состояние, например:
MyFsm fsm;
// write to archive
std::ofstream ofs("fsm.txt");
// save fsm to archive
{
boost::archive::text_oarchive oa(ofs);
// write class instance to archive
oa << fsm;
}
Загрузка обратно очень похожа:
MyFsm fsm;
{
// create and open an archive for input
std::ifstream ifs("fsm.txt");
boost::archive::text_iarchive ia(ifs);
// read class state from archive
ia >> fsm;
}
Это десериализирует саму государственную машину, но не данные конкретных государств. Это можно сделать на основе каждого штата, чтобы уменьшить количество необходимого набора текста. Чтобы позволить сериализацию конкретного состояния, предоставьте do_serialize typedef и реализуйте функцию сериализации:
struct Empty : public msm::front::state<>
{
// we want Empty to be serialized. First provide the typedef
typedef int do_serialize;
// then implement serialize
template<class Archive>
void serialize(Archive & ar, const unsigned int /* version */)
{
ar & some_dummy_data;
}
Empty():some_dummy_data(0){}
int some_dummy_data;
};
Также можно сериализовать данные, содержащиеся в классе front-end. Опять же, вам нужно предоставить typedef и реализовать сериализацию:
struct player_ : public msm::front::state_machine_def<player_>
{
//we might want to serialize some data contained by the front-end
int front_end_data;
player_():front_end_data(0){}
// to achieve this, provide the typedef
typedef int do_serialize;
// and implement serialize
template<class Archive>
void serialize(Archive & ar, const unsigned int )
{
ar & front_end_data;
}
...
};
Сохранение данных бэкэнда (текущее состояние (состояния)) действительно для всех фронтендов, поэтому фронтенд, написанный с использованием eUML, может быть сериализован. Однако для сериализации конкретного состояния макросы типа<BOOST_MSM_EUML_STATE
>не могут быть использованы, поэтому государство должно быть реализовано путем прямого наследования от<front::euml::euml_state
>.
Единственное ограничение заключается в том, что очереди событий не могут быть сериализованы, поэтому сериализация должна проводиться в стабильном состоянии, когда ни одно событие не обрабатывается. Сериализация во время обработки событий возможна только при отсутствии очереди (отложенной или очереди событий).
Этотпримерпоказывает машину состояния, которую мы сериализуем после обработки события.<Empty
>также имеет некоторые данные для сериализации.
Иногда требуется настроить состояния, чтобы избежать повторения и обеспечить общую функциональность, например, в виде виртуального метода. Вы также можете сделать свои состояния полиморфными, чтобы вы могли вызвать на них типиду для регистрации или отладки. Это также полезно, если вам нужен посетитель, как будет показано в следующем разделе. Вы заметите, что все интерфейсы предлагают возможность добавления базового типа. Обратите внимание, что все штаты и государственные машины должны иметь одинаковое базовое состояние, чтобы это могло уменьшить повторное использование. Например, используя основной передний конец, вам нужно:
Добавьте базовое состояние без по умолчанию в ваше msm::front::state<>определение в качестве первого шаблонного аргумента (за исключением состояний прерывания, для которых это второй аргумент, первый из которых является событием, заканчивающим прерывание), например, мое базовое состояние является вашим новым базовым состоянием для всех состояний в данной машине состояния:
<struct Empty : public msm::front::state<my_base_state>
>Теперь мое базовое состояние является вашим новым базовым состоянием. Если у вас есть виртуальная функция, ваши состояния становятся полиморфными. MSM также обеспечивает тип полиморфного основания по умолчанию<msm::front::polymorphic_state
>
.Добавить определяемое пользователем базовое состояние в определение интерфейса машины состояния в качестве второго аргумента шаблона, например:
<struct player_ : public msm::front::state_machine<player_,my_base_state>
>
Вы также можете запросить состояние с заданным идентификатором (который вы, возможно, получили от current_state()), используя<const base_state* get_state_by_id(int id)
const
>, где base_state - это то, которое вы только что определили. Теперь можно сделать что-то полиморфное.
В некоторых случаях недостаточно иметь указатель на базу действующих в настоящее время состояний. Вы можете назвать невиртуально метод активных состояний. Нельзя сказать, что МСМ заставит виртуальное ключевое слово застрять у вас в горле!
Для достижения этой цели MSM предоставляет свою собственную вариацию шаблона посетителя с использованием ранее описанной техники определения состояния пользователя. Если вы добавите в свое определяемое пользователем базовое состояние<accept_sig
>typedef, дающий значение возврата (неиспользованное на данный момент) и параметры, и предоставите метод принятия с этой подписью, вызов visit_current_states вызовет согласие на вызов в активных состояниях. Как правило, вы также захотите предоставить пустое согласие на дефолт в вашем базовом состоянии, чтобы не заставлять все ваши государства выполнять согласие. Например, ваше базовое состояние может быть:
struct my_visitable_state
{
// signature of the accept function
typedef args<void> accept_sig;
// we also want polymorphic states
virtual ~my_visitable_state() {}
// default implementation for states who do not need to be visited
void accept() const {}
};
Это делает ваши состояния полиморфными и посещаемыми. В этом случае принятие делается const и не принимает никаких аргументов. Это также может быть:
struct SomeVisitor {…};
struct my_visitable_state
{
// signature of the accept function
typedef args<void,SomeVisitor&> accept_sig;
// we also want polymorphic states
virtual ~my_visitable_state() {}
// default implementation for states who do not need to be visited
void accept(SomeVisitor&) const {}
};
И теперь<accept
>будет принимать один аргумент (он также может быть непостоянным). По умолчанию<accept
>занимает до 2 аргументов. Чтобы получить больше, установите #define BOOST_MSM_VISITOR_ARG_SIZE на другое значение, прежде чем включать state_machine.hpp. Например:
#define BOOST_MSM_VISITOR_ARG_SIZE 3
#include <boost/msm/back/state_machine.hpp>
Обратите внимание, что прием будет осуществляться во всех активных состояниях, а также автоматически в подсостояниях подмашины.
Важное предупреждение: Способ visit_current_states принимает свой параметр по значению, поэтому, если подпись приемной функции состоит в том, чтобы содержать параметр, переданный посредством ссылки, пропустите этот параметр с надбавкой:ref/cref, чтобы избежать нежелательных копий или нарезки. Так, например, в вышеприведенном случае вызовите:
SomeVisitor vis; sm.visit_current_states(boost::ref(vis));
Этотпримериспользует функцию посещения с 2 аргументами.
Flags - это концепция, поддерживаемая всеми интерфейсами, которые основываются на функциях:
template <class Flag> bool is_flag_active()
template <class Flag,class BinaryOp> bool is_flag_active()
Эти функции возвращаются, если в настоящее время активные государства поддерживают свойство флага. Первый вариант ИЛИ дает результат при наличии нескольких ортогональных областей, второй ожидает ИЛИ или И, например:
my_fsm.is_flag_active<MyFlag>()
my_fsm.is_flag_active<MyFlag,my_fsm_type::Flag_OR>()
Пожалуйста, обратитесь к разделам интерфейса для примеров использования.
Иногда необходимо, чтобы клиентский код получал доступ к данным государств. В конце концов, государства создаются раз и навсегда, пока государственная машина делает это, почему бы не использовать ее? Просто иногда нужно получить информацию о каком-либо состоянии, даже неактивном. Например, если вы хотите написать инструмент охвата и знать, сколько раз посещалось государство. Чтобы получить состояние, используйте метод get_state, например:
player::Stopped* tempstate = p.get_state<player::Stopped*>();
или
player::Stopped& tempstate2 = p.get_state<player::Stopped&>();
В зависимости от вашего личного вкуса.
State machine constructor with arguments
Возможно, вы захотите определить государственную машину с конструктором без по умолчанию. Например, вы можете захотеть написать:
struct player_ : public msm::front::state_machine_def<player_>
{
player_(int some_value){…}
};
Это возможно, используя back-end в качестве пересылающего объекта:
typedef msm::back::state_machine<player_ > player; player p(3);
Бэкэнд вызовет соответствующего фронтэнд-конструктора при создании.
Вы можете передать аргументы до значения BOOST_MSM_CONSTRUCTOR_ARG_SIZE Макро (в настоящее время 5) аргументов. Измените это значение, прежде чем включать любой заголовок, если вам нужно перезаписать по умолчанию.
Вы также можете передавать аргументы по ссылке (или по ссылке) с помощью boost::ref (или boost::cref):
struct player_ : public msm::front::state_machine_def<player_>
{
player_(SomeType& t, int some_value){…}
};
typedef msm::back::state_machine<player_ > player;
SomeType data;
player p(boost::ref(data),3);
Обычно МСМ по умолчанию конструирует все свои состояния или подмашины. Однако есть случаи, когда вы можете этого не хотеть. Примером может служить использование машины состояния в качестве субмашины, и эта субмашина использует вышеописанные конструкторы. В качестве первого аргумента конструктора государственных машин можно добавить выражение, в котором передаются и копируются существующие состояния:
player p( back::states_ << state_1 << ... << state_n , boost::ref(data),3);
где state_1.n — это примеры некоторых или всех состояний государственной машины. Подмашины, являющиеся государственными машинами, могут повторяться, например, если Playing представляет собой подмашину, содержащую состояние Song1, имеющее сам конструктор, где передаются некоторые данные:
player p( back::states_ << Playing(back::states_ << Song1(some_Song1_data)) ,
boost::ref(data),3);
Также возможно заменить данное состояние новым экземпляром в любое время, используя<set_states()
>и тот же синтаксис, например:
p.set_states( back::states_ << state_1 << ... << state_n );
Приводитсяпримеринтенсивного использования этой способности.
Trading run-time speed for
better compile-time / multi-TU compilation
MSM оптимизирован для скорости выполнения за счет более длительного времени компиляции. Это может стать проблемой с более старыми компиляторами и большими государственными машинами, особенно если вы не очень заботитесь о скорости выполнения и будете удовлетворены производительностью, примерно такой же, как большинство государственных машинных библиотек. MSM предлагает внутреннюю политику, чтобы помочь там. Но прежде чем попробовать, если вы используете VC-компилятор, отключите опцию компилятора /Gm (по умолчанию для сборок отладки). Этот вариант может привести к тому, что сборки будут в 3 раза длиннее. Если время компиляции все еще является проблемой, читайте дальше. МСМ предлагает политику, которая ускорит составление в двух основных случаях:
У бэк-энда<msm::back::state_machine
>есть политический аргумент (сначала фронт-энд, затем политика истории), который не соответствует<favor_runtime_speed
>. Для перехода на<favor_compile_time
>, который заявлен в<<msm/back/favor_compile_time.hpp>
>, необходимо:
переключить политику на<favor_compile_time
>для основной государственной машины (и, возможно, подмашин)
переместить объявления о подмашинах в их собственный заголовок, который включает<<msm/back/favor_compile_time.hpp>
>
добавьте для каждой подмашины файл cpp, включая заголовок и вызов макроса, который генерирует код помощника, например:
<#include "mysubmachine.hpp"
BOOST_MSM_BACK_GENERATE_PROCESS_EVENT(mysubmachine)
>настройте компилятор для многоядерной компиляции
Теперь вы будете компилировать свою машину состояния на столько ядер, сколько у вас есть подмашин, что значительно ускорит компиляцию, если вы разложите свою машину состояния на более мелкие подмашины.
Самостоятельно разрешение конфликтов переходного периода также будет намного быстрее.
Эта политика использует импульс. любой за капотом, что означает, что мы потеряем функцию, которую MSM предлагает с политикой по умолчанию,иерархия событий. Следующий пример берет наш пример iPod и ускоряет время компиляции, используя эту технику. У нас есть:
Compile-time state machine analysis
Если МСМ-машина является метапрограммой, то вполне логично, что проверка достоверности конкретной машины состояния происходит в компиляционное время. С этой целью, используя библиотеку графов времени компиляцииmpl_graph(доставленную на данный момент с MSM) от Гордона Вудхалла, MSM предоставляет несколько проверок времени компиляции:
Проверьте, что ортогональные области действительно ортогональные.
Убедитесь, что все состояния доступны из начальных состояний или являются явными состояниями входа / псевдовхода.
Чтобы использовать эту функцию, back-end предоставляет политику (по умолчанию нет анализа)<msm::back::mpl_graph_fsm_check
>. Например:
typedef msm::back::state_machine< player_,msm::back::mpl_graph_fsm_check> player;
MSM теперь использует Boost. Параметр декларирования политики, выбор политики может быть сделан на любой позиции после типа front-end (в данном случае<player_
>).
В случае обнаружения ошибки провоцируется утверждение времени компиляции.
Эта функция не включена по умолчанию, потому что она имеет непренебрежимую стоимость компиляции. Алгоритм является линейным, если в машине состояний не обнаружено явных или псевдосостояний входа, к сожалению, все еще O (число состояний * число состояний входа) иначе. Это будет сделано в будущих версиях MSM.
Этот же алгоритм также используется в случае, если вы хотите пропустить индекс области в.явная запись/псевдозаявлениедекларация.
Совет автора состоит в том, чтобы включить проверку после любого изменения структуры машины и отключить ее снова после успешного анализа.
следующий примервызывает утверждение, если используется одна из первых двух строк таблицы перехода.
Enqueueing events for later
processing
Звонок<process_event(Event const&)
>немедленно обработает событие семантикой выполнения-завершения. Вы также можете отслеживать события и задерживать их обработку, позвонив<enqueue_event(Event
const&)
>. После этого вызов<execute_queued_events()
>обработает все события в очереди (в порядке FIFO). Позвонив<execute_single_queued_event()
>, выполните самое старое из перечисленных событий.
Вы можете запросить размер очереди, позвонив<get_message_queue_size()
>.
Customizing the message queues
MSM по умолчанию использует std::deque для своих очередей (одна очередь сообщений для событий, генерируемых во время выполнения или с<enqueue_event
>, одна для отложенных событий). К сожалению, на некоторых реализациях STL это очень дорогой контейнер по размеру и времени копирования. Если это проблема, МСМ предлагает альтернативу, основанную на импульсе::circular_buffer. Полис msm::back::queue_container_circular. Чтобы использовать его, вам нужно предоставить его в заднее определение:
typedef msm::back::state_machine< player_,msm::back::queue_container_circular> player;
Вы можете получить доступ к очередям с помощью get_message_queue и get_deferred_queue, возвращая ссылку или const ссылку на сами очереди. Boost::circular_buffer выходит за рамки данной документации. Однако вам нужно будет определить емкость очереди (первоначально 0) к тому, что, по вашему мнению, ваша очередь будет расти, например (размер 1 обычно):
fsm.get_message_queue().set_capacity(1);
Policy definition with Boost.Parameter
MSM использует Boost. Параметр, позволяющий легче определить обратную сторону::state_machine<>политические аргументы (все, кроме front-end). Это позволяет определять политические аргументы (история, время компиляции/время выполнения, анализ состояния машины, контейнер для очередей) в любой позиции, в любом количестве. Например:
typedef msm::back::state_machine< player_,msm::back::mpl_graph_fsm_check> player;
typedef msm::back::state_machine< player_,msm::back::AlwaysHistory> player;
typedef msm::back::state_machine< player_,msm::back::mpl_graph_fsm_check,msm::back::AlwaysHistory> player;
typedef msm::back::state_machine< player_,msm::back::AlwaysHistory,msm::back::mpl_graph_fsm_check> player;
Choosing when to switch active
states
UML Стандарт умалчивает об очень важном вопросе: когда горит переход, в какой именно точке находится целевое состояние новой активной машины состояния? В конце переходного периода? После того, как государство-источник было оставлено? Что делать, если сделано исключение? Стандарт считает, что переход к завершению означает, что переход завершается почти в кратчайшие сроки. Но даже это может быть в некоторых условиях очень долго. Рассмотрим следующий пример. У нас есть государственная машина, представляющая собой сетевое соединение. Мы можем быть<Connected
>и<Disconnected
>. Когда мы переходим из одного состояния в другое, мы посылаем сигнал другому существу. По умолчанию MSM делает целевое состояние новым после завершения перехода. Мы хотим послать сигнал, основанный на подключенном флаге, что верно, когда в соединенном состоянии.
Мы находимся в состоянии<Disconnected
>и получаем событие<connect
>. Переходное действие спросит государственную машину<is_flag_active<is_connected>
>и станет... ложным, потому что мы все еще находимся в<Disconnected
>. Хмм, что делать? Мы могли бы поставить в очередь действие и выполнить его позже, но это означает дополнительную очередь, больше работы и более высокое время выполнения.
МСМ предоставляет возможность (в форме политики) для интерфейса решить, когда целевое государство становится активным. Это может быть:
перед переходом огонь, если охранник допустит переход к огню:<active_state_switch_before_transition
>
после вызова выхода состояния источника:<active_state_switch_after_exit
>
после выполнения переходного действия:<active_state_switch_after_transition_action
>
после выполнения входного действия целевого состояния (по умолчанию):<active_state_switch_after_entry
>
Проблема и решение показаны дляфунктор-фронт-эндиeUML. Удаление<active_state_switch_before_transition
>покажет состояние по умолчанию.