23 KiB
~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
newand managed via pointers - State machine is the root state —
PIStateMachineinherits fromPIStateBase - 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 executionbool isRunning()— checks if state machine is runningvoid setOnFinish(std::function<void()> f)— sets finish callbackbool 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 enteredvirtual void onExit()— called when state is exitedvoid addState(PIStateBase *s)— adds child statePITransitionBase *addTransition(PIStateBase *target, int event_id)— adds transitionPITransitionTimeout *addTimeoutTransition(PIStateBase *target, PISystemTime timeout)— adds timeout transitionvoid 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 functionPITransitionBase *addAction(std::function<void()> a)— adds actionvoid 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
PIStateMachineis destroyed, all states and transitions are deleted - When
PIStateBaseis 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.
Related Modules
- \ref DateTime module for
PISystemTimeused in timeout transitions
~russian
Машина состояний
Описание машины состояний
Машина состояний — это поведенческий паттерн проектирования, который позволяет объекту изменять свое поведение при изменении внутреннего состояния. Реализация в PIP основана на стандарте SCXML (State Chart XML).
Основные концепции SCXML
- State (Состояние) — узел в дереве состояний, может быть атомарным или составным
- Transition (Переход) — связь между состояниями, срабатывает при событии
- Event (Событие) — триггер, вызывающий переход
- Guard (Сторожевая функция) — условие, которое должно быть истинным для выполнения перехода
- Action (Действие) — операция, выполняемая при переходе
- Parallel (Параллельное состояние) — состояние, в котором все подсостояния активируются одновременно
Особенности реализации PIP
- Нельзя создавать state, event, transition в стеке — все объекты создаются через
newи управляются через указатели - Машина состояний — это корневой state —
PIStateMachineнаследуется от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, используемого в переходах по таймауту