Files
pip/doc/pages/state_machine.md
2026-03-12 19:13:12 +03:00

23 KiB
Raw Blame History

~english \page state_machine State machine ~russian \page state_machine Машина состояний

~english

State Machine

State Machine Description

State machine is a behavioral design pattern that allows an object to change its behavior when its internal state changes. Implementation in PIP is based on the SCXML (State Chart XML) standard.

SCXML Concepts

  • State — a node in the state tree, can be atomic or compound
  • Transition — a connection between states that triggers on an event
  • Event — a trigger that causes a transition
  • Guard — a condition that must be true for a transition to execute
  • Action — an operation executed during a transition
  • Parallel — a state where all substates are activated simultaneously

PIP State Machine Features

  • Cannot create state, event, transition on stack — all objects are created via new and managed via pointers
  • State machine is the root statePIStateMachine inherits from PIStateBase
  • All transitions and states are automatically deleted — when the parent object is destroyed

State Types

  • Atomic state — a state without nested substates
  • Compound state — a state containing nested substates
  • Final state — a terminating state indicating the end of machine execution
  • Parallel state — a state where all substates are activated simultaneously

Transition Types

  • Normal transition — triggers on receiving a specific event
  • Timeout transition — triggers automatically after a specified time interval
  • Guarded transition — triggers only when a specific condition is met

State Machine API

Main Classes

PIStateMachine

Main state machine class.

Key methods:

  • bool start() — starts state machine execution
  • bool isRunning() — checks if state machine is running
  • void setOnFinish(std::function<void()> f) — sets finish callback
  • bool postEvent(int event_id, Args... args) — posts event to state machine

PIStateBase

Base class for all states.

Key methods:

  • virtual void onEnter() — called when state is entered
  • virtual void onExit() — called when state is exited
  • void addState(PIStateBase *s) — adds child state
  • PITransitionBase *addTransition(PIStateBase *target, int event_id) — adds transition
  • PITransitionTimeout *addTimeoutTransition(PIStateBase *target, PISystemTime timeout) — adds timeout transition
  • void setParallel(bool yes) — sets parallel mode

PIStateLambda

State with lambda callbacks.

\code{.cpp} PIStateLambda(std::function<void()> on_enter, std::function<void()> on_exit = nullptr, const PIString &n = {}) \endcode

PIStateFinal

Final state of state machine.

\code{.cpp} PIStateFinal(std::function<void()> on_finish = nullptr, const PIString &n = {}) \endcode

PITransitionBase

Base class for transitions.

Key methods:

  • PITransitionBase *addGuard(std::function<R(Args...)> f) — adds guard function
  • PITransitionBase *addAction(std::function<void()> a) — adds action
  • void trigger() — triggers transition

PITransitionTimeout

Transition that triggers after a specified timeout.

Usage Examples

Simple State Machine

\code{.cpp} #include "pistatemachine.h" #include "pisystemtime.h"

// Create state machine PIStateMachine *machine = new PIStateMachine("Main");

// Create states PIStateLambda *idle = new PIStateLambda( { piCout << "Entering Idle state\n"; }, { piCout << "Exiting Idle state\n"; }, "Idle" ); PIStateLambda *running = new PIStateLambda( { piCout << "Entering Running state\n"; }, { piCout << "Exiting Running state\n"; }, "Running" ); PIStateFinal *finish = new PIStateFinal( { piCout << "Machine finished\n"; }, "Finish" );

// Add states machine->addState(idle); machine->addState(running); machine->addState(finish);

// Set initial state idle->setInitialState(idle); running->setInitialState(running); machine->setInitialState(idle);

// Add transitions idle->addTransition(running, 1); running->addTransition(finish, 2);

// Set finish callback machine->setOnFinish( { piCout << "Machine execution completed\n"; });

// Start machine machine->start();

// Post events machine->postEvent(1); // Transition to Running machine->postEvent(2); // Transition to Finish \endcode

Guarded Transitions

\code{.cpp} PIStateMachine *machine = new PIStateMachine("With Guards");

PIStateLambda *stateA = new PIStateLambda( { piCout << "State A\n"; }, nullptr, "A"); PIStateLambda *stateB = new PIStateLambda( { piCout << "State B\n"; }, nullptr, "B"); PIStateLambda *stateC = new PIStateLambda( { piCout << "State C\n"; }, nullptr, "C");

machine->addState(stateA); machine->addState(stateB); machine->addState(stateC);

stateA->setInitialState(stateA); machine->setInitialState(stateA);

// Transition with guard function auto *t1 = stateA->addTransition(stateB, 1); t1->addGuard([](int value) -> bool { return value > 10; });

// Transition with another guard function auto *t2 = stateB->addTransition(stateC, 2); t2->addGuard([](const PIString &msg) -> bool { return msg == "allowed"; });

machine->start();

// First call will not execute (value <= 10) machine->postEvent(1, 5);

// Second call will execute (value > 10) machine->postEvent(1, 15);

// First call will not execute (msg != "allowed") machine->postEvent(2, "test");

// Second call will execute (msg == "allowed") machine->postEvent(2, "allowed"); \endcode

Timeout Transitions

\code{.cpp} PIStateMachine *machine = new PIStateMachine("With Timeout");

PIStateLambda *working = new PIStateLambda( { piCout << "Working...\n"; }, { piCout << "Stop working\n"; }, "Working" ); PIStateLambda *timeoutState = new PIStateLambda( { piCout << "Timeout occurred\n"; }, nullptr, "Timeout"); PIStateFinal *finish = new PIStateFinal( { piCout << "Done\n"; }, "Finish");

machine->addState(working); machine->addState(timeoutState); machine->addState(finish);

working->setInitialState(working); machine->setInitialState(working);

// Add timeout transition (after 5 seconds) working->addTimeoutTransition(timeoutState, PISystemTime::fromSeconds(5));

// Add transition from timeoutState to finish timeoutState->addTransition(finish, 1);

machine->start(); \endcode

Compound States

\code{.cpp} PIStateMachine *machine = new PIStateMachine("Compound States");

// Root state (machine) PIStateLambda *root = new PIStateLambda( { piCout << "Root state\n"; }, nullptr, "Root");

// Compound state with substates PIStateLambda *parent = new PIStateLambda( { piCout << "Parent state\n"; }, nullptr, "Parent"); PIStateLambda *child1 = new PIStateLambda( { piCout << "Child 1\n"; }, nullptr, "Child1"); PIStateLambda *child2 = new PIStateLambda( { piCout << "Child 2\n"; }, nullptr, "Child2");

parent->setInitialState(child1); parent->addState(child1); parent->addState(child2); root->addState(parent);

machine->addState(root); machine->setInitialState(root);

// Add transitions root->addTransition(parent, 1); parent->addTransition(root, 2);

machine->start();

// Transition to parent machine->postEvent(1);

// Transition back machine->postEvent(2); \endcode

Parallel States

\code{.cpp} PIStateMachine *machine = new PIStateMachine("Parallel States");

PIStateLambda *root = new PIStateLambda( { piCout << "Root\n"; }, nullptr, "Root"); PIStateLambda *parallel = new PIStateLambda( { piCout << "Parallel\n"; }, nullptr, "Parallel"); PIStateLambda *sub1 = new PIStateLambda( { piCout << "Substate 1\n"; }, nullptr, "Sub1"); PIStateLambda *sub2 = new PIStateLambda( { piCout << "Substate 2\n"; }, nullptr, "Sub2");

// Enable parallel mode parallel->setParallel(true); parallel->addState(sub1); parallel->addState(sub2); parallel->setInitialState(sub1); root->addState(parallel);

machine->addState(root); machine->setInitialState(root);

machine->start();

// When entering parallel, both substates are activated simultaneously \endcode

Transitions with Actions

\code{.cpp} PIStateMachine *machine = new PIStateMachine("With Actions");

PIStateLambda *stateA = new PIStateLambda( { piCout << "State A\n"; }, nullptr, "A"); PIStateLambda *stateB = new PIStateLambda( { piCout << "State B\n"; }, nullptr, "B");

machine->addState(stateA); machine->addState(stateB);

machine->setInitialState(stateA);

// Add transition with action auto *t = stateA->addTransition(stateB, 1); t->addAction( { piCout << "Action during transition\n"; });

machine->start(); machine->postEvent(1); \endcode

Thread Safety

State machine is thread-safe. The postEvent method uses a queue to handle nested calls, allowing safe event posting from different threads.

Usage Rules

Cannot Create on Stack

\code{.cpp} // WRONG PIStateLambda state( {}, nullptr, "State");

// CORRECT PIStateLambda *state = new PIStateLambda( {}, nullptr, "State"); \endcode

Proper Object Hierarchy

\code{.cpp} PIStateMachine *machine = new PIStateMachine("Main");

// States are created via new and added to machine or another state PIStateLambda *state1 = new PIStateLambda( {}, nullptr, "State1"); PIStateLambda *state2 = new PIStateLambda( {}, nullptr, "State2");

machine->addState(state1); machine->addState(state2);

// Transitions are added to states via addTransition state1->addTransition(state2, 1);

// Machine is started machine->start(); \endcode

Memory Cleanup

All objects (states, transitions) are automatically deleted when the parent object is destroyed:

  • When PIStateMachine is destroyed, all states and transitions are deleted
  • When PIStateBase is destroyed, all child states and transitions are deleted

Debugging

For debugging, you can use the print() method to output the state tree:

\code{.cpp} machine->print(); \endcode

Also, you can use activeAtomics() to get a list of active states.

  • \ref DateTime module for PISystemTime used in timeout transitions

~russian

Машина состояний

Описание машины состояний

Машина состояний — это поведенческий паттерн проектирования, который позволяет объекту изменять свое поведение при изменении внутреннего состояния. Реализация в PIP основана на стандарте SCXML (State Chart XML).

Основные концепции SCXML

  • State (Состояние) — узел в дереве состояний, может быть атомарным или составным
  • Transition (Переход) — связь между состояниями, срабатывает при событии
  • Event (Событие) — триггер, вызывающий переход
  • Guard (Сторожевая функция) — условие, которое должно быть истинным для выполнения перехода
  • Action (Действие) — операция, выполняемая при переходе
  • Parallel (Параллельное состояние) — состояние, в котором все подсостояния активируются одновременно

Особенности реализации PIP

  • Нельзя создавать state, event, transition в стеке — все объекты создаются через new и управляются через указатели
  • Машина состояний — это корневой statePIStateMachine наследуется от PIStateBase
  • Все переходы и состояния удаляются автоматически — при уничтожении родительского объекта

Основные типы состояний

  • Атомарное состояние — состояние без вложенных подсостояний
  • Составное состояние — состояние, содержащее вложенные подсостояния
  • Финальное состояние — завершающее состояние, указывающее на окончание работы машины
  • Параллельное состояние — состояние, в котором все подсостояния активируются одновременно

Виды переходов

  • Обычный переход — срабатывает при получении определенного события
  • Переход по таймауту — срабатывает автоматически через заданный промежуток времени
  • Переход с guard — срабатывает только при выполнении определенного условия

PIP State Machine API

Основные классы

PIStateMachine

Основной класс машины состояний.

Ключевые методы:

  • bool start() — запускает выполнение машины состояний
  • bool isRunning() — проверяет, запущена ли машина
  • void setOnFinish(std::function<void()> f) — устанавливает коллбэк завершения
  • bool postEvent(int event_id, Args... args) — отправляет событие в машину состояний

PIStateBase

Базовый класс для всех состояний.

Ключевые методы:

  • virtual void onEnter() — вызывается при входе в состояние
  • virtual void onExit() — вызывается при выходе из состояния
  • void addState(PIStateBase *s) — добавляет дочернее состояние
  • PITransitionBase *addTransition(PIStateBase *target, int event_id) — добавляет переход
  • PITransitionTimeout *addTimeoutTransition(PIStateBase *target, PISystemTime timeout) — добавляет переход по таймауту
  • void setParallel(bool yes) — устанавливает параллельный режим

PIStateLambda

Состояние с lambda-коллбэками.

\code{.cpp} PIStateLambda(std::function<void()> on_enter, std::function<void()> on_exit = nullptr, const PIString &n = {}) \endcode

PIStateFinal

Финальное состояние машины состояний.

\code{.cpp} PIStateFinal(std::function<void()> on_finish = nullptr, const PIString &n = {}) \endcode

PITransitionBase

Базовый класс для переходов.

Ключевые методы:

  • PITransitionBase *addGuard(std::function<R(Args...)> f) — добавляет сторожевую функцию
  • PITransitionBase *addAction(std::function<void()> a) — добавляет действие
  • void trigger() — запускает переход

PITransitionTimeout

Переход, срабатывающий через заданный промежуток времени.

Примеры использования

Простая машина состояний

\code{.cpp} #include "pistatemachine.h" #include "pisystemtime.h"

// Создаем машину состояний PIStateMachine *machine = new PIStateMachine("Main");

// Создаем состояния PIStateLambda *idle = new PIStateLambda( { piCout << "Entering Idle state\n"; }, { piCout << "Exiting Idle state\n"; }, "Idle" ); PIStateLambda *running = new PIStateLambda( { piCout << "Entering Running state\n"; }, { piCout << "Exiting Running state\n"; }, "Running" ); PIStateFinal *finish = new PIStateFinal( { piCout << "Machine finished\n"; }, "Finish" );

// Добавляем состояния machine->addState(idle); machine->addState(running); machine->addState(finish);

// Устанавливаем начальное состояние idle->setInitialState(idle); running->setInitialState(running); machine->setInitialState(idle);

// Добавляем переходы idle->addTransition(running, 1); running->addTransition(finish, 2);

// Устанавливаем коллбэк завершения machine->setOnFinish( { piCout << "Machine execution completed\n"; });

// Запускаем машину machine->start();

// Отправляем события machine->postEvent(1); // Перейти в Running machine->postEvent(2); // Перейти в Finish \endcode

Машина состояний с guard-функциями

\code{.cpp} PIStateMachine *machine = new PIStateMachine("With Guards");

PIStateLambda *stateA = new PIStateLambda( { piCout << "State A\n"; }, nullptr, "A"); PIStateLambda *stateB = new PIStateLambda( { piCout << "State B\n"; }, nullptr, "B"); PIStateLambda *stateC = new PIStateLambda( { piCout << "State C\n"; }, nullptr, "C");

machine->addState(stateA); machine->addState(stateB); machine->addState(stateC);

stateA->setInitialState(stateA); machine->setInitialState(stateA);

// Переход с guard-функцией auto *t1 = stateA->addTransition(stateB, 1); t1->addGuard([](int value) -> bool { return value > 10; });

// Переход с другой guard-функцией auto *t2 = stateB->addTransition(stateC, 2); t2->addGuard([](const PIString &msg) -> bool { return msg == "allowed"; });

machine->start();

// Первый вызов не выполнится (value <= 10) machine->postEvent(1, 5);

// Второй вызов выполнится (value > 10) machine->postEvent(1, 15);

// Первый вызов не выполнится (msg != "allowed") machine->postEvent(2, "test");

// Второй вызов выполнится (msg == "allowed") machine->postEvent(2, "allowed"); \endcode

Машина состояний с таймаутами

\code{.cpp} PIStateMachine *machine = new PIStateMachine("With Timeout");

PIStateLambda *working = new PIStateLambda( { piCout << "Working...\n"; }, { piCout << "Stop working\n"; }, "Working" ); PIStateLambda *timeoutState = new PIStateLambda( { piCout << "Timeout occurred\n"; }, nullptr, "Timeout"); PIStateFinal *finish = new PIStateFinal( { piCout << "Done\n"; }, "Finish");

machine->addState(working); machine->addState(timeoutState); machine->addState(finish);

working->setInitialState(working); machine->setInitialState(working);

// Добавляем переход по таймауту (через 5 секунд) working->addTimeoutTransition(timeoutState, PISystemTime::fromSeconds(5));

// Добавляем переход из timeoutState в finish timeoutState->addTransition(finish, 1);

machine->start(); \endcode

Составные состояния

\code{.cpp} PIStateMachine *machine = new PIStateMachine("Compound States");

// Корневое состояние (машина) PIStateLambda *root = new PIStateLambda( { piCout << "Root state\n"; }, nullptr, "Root");

// Составное состояние с подсостояниями PIStateLambda *parent = new PIStateLambda( { piCout << "Parent state\n"; }, nullptr, "Parent"); PIStateLambda *child1 = new PIStateLambda( { piCout << "Child 1\n"; }, nullptr, "Child1"); PIStateLambda *child2 = new PIStateLambda( { piCout << "Child 2\n"; }, nullptr, "Child2");

parent->setInitialState(child1); parent->addState(child1); parent->addState(child2); root->addState(parent);

machine->addState(root); machine->setInitialState(root);

// Добавляем переходы root->addTransition(parent, 1); parent->addTransition(root, 2);

machine->start();

// Переход в parent machine->postEvent(1);

// Переход обратно machine->postEvent(2); \endcode

Параллельные состояния

\code{.cpp} PIStateMachine *machine = new PIStateMachine("Parallel States");

PIStateLambda *root = new PIStateLambda( { piCout << "Root\n"; }, nullptr, "Root"); PIStateLambda *parallel = new PIStateLambda( { piCout << "Parallel\n"; }, nullptr, "Parallel"); PIStateLambda *sub1 = new PIStateLambda( { piCout << "Substate 1\n"; }, nullptr, "Sub1"); PIStateLambda *sub2 = new PIStateLambda( { piCout << "Substate 2\n"; }, nullptr, "Sub2");

// Включаем параллельный режим parallel->setParallel(true); parallel->addState(sub1); parallel->addState(sub2); parallel->setInitialState(sub1); root->addState(parallel);

machine->addState(root); machine->setInitialState(root);

machine->start();

// При входе в parallel оба подсостояния активируются одновременно \endcode

Машина состояний с действиями

\code{.cpp} PIStateMachine *machine = new PIStateMachine("With Actions");

PIStateLambda *stateA = new PIStateLambda( { piCout << "State A\n"; }, nullptr, "A"); PIStateLambda *stateB = new PIStateLambda( { piCout << "State B\n"; }, nullptr, "B");

machine->addState(stateA); machine->addState(stateB);

machine->setInitialState(stateA);

// Добавляем переход с действием auto *t = stateA->addTransition(stateB, 1); t->addAction( { piCout << "Action during transition\n"; });

machine->start(); machine->postEvent(1); \endcode

Потокобезопасность

Машина состояний PIP потокобезопасна. Метод postEvent использует очередь для обработки вложенных вызовов, что позволяет безопасно отправлять события из разных потоков.

Правила использования

Нельзя создавать в стеке

\code{.cpp} // НЕЛЬЗЯ PIStateLambda state( {}, nullptr, "State");

// ПРАВИЛЬНО PIStateLambda *state = new PIStateLambda( {}, nullptr, "State"); \endcode

Правильная иерархия объектов

\code{.cpp} PIStateMachine *machine = new PIStateMachine("Main");

// Состояния создаются через new и добавляются в машину или другое состояние PIStateLambda *state1 = new PIStateLambda( {}, nullptr, "State1"); PIStateLambda *state2 = new PIStateLambda( {}, nullptr, "State2");

machine->addState(state1); machine->addState(state2);

// Переходы добавляются к состояниям через addTransition state1->addTransition(state2, 1);

// Машина запускается machine->start(); \endcode

Очистка памяти

Все объекты (состояния, переходы) автоматически удаляются при уничтожении родительского объекта:

  • При уничтожении PIStateMachine удаляются все состояния и переходы
  • При уничтожении PIStateBase удаляются все дочерние состояния и переходы

Отладка

Для отладки можно использовать метод print() для вывода дерева состояний:

\code{.cpp} machine->print(); \endcode

Также можно использовать activeAtomics() для получения списка активных состояний.

Связанные модули

  • \ref DateTime модуль для PISystemTime, используемого в переходах по таймауту