\~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 state** — `PIStateMachine` 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 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 on_enter, std::function on_exit = nullptr, const PIString &n = {}) \endcode #### PIStateFinal Final state of state machine. \code{.cpp} PIStateFinal(std::function on_finish = nullptr, const PIString &n = {}) \endcode #### PITransitionBase Base class for transitions. **Key methods:** - `PITransitionBase *addGuard(std::function f)` — adds guard function - `PITransitionBase *addAction(std::function 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. ## Related Modules - \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` и управляются через указатели - **Машина состояний — это корневой state** — `PIStateMachine` наследуется от `PIStateBase` - **Все переходы и состояния удаляются автоматически** — при уничтожении родительского объекта ### Основные типы состояний - **Атомарное состояние** — состояние без вложенных подсостояний - **Составное состояние** — состояние, содержащее вложенные подсостояния - **Финальное состояние** — завершающее состояние, указывающее на окончание работы машины - **Параллельное состояние** — состояние, в котором все подсостояния активируются одновременно ### Виды переходов - **Обычный переход** — срабатывает при получении определенного события - **Переход по таймауту** — срабатывает автоматически через заданный промежуток времени - **Переход с guard** — срабатывает только при выполнении определенного условия ## PIP State Machine API ### Основные классы #### PIStateMachine Основной класс машины состояний. **Ключевые методы:** - `bool start()` — запускает выполнение машины состояний - `bool isRunning()` — проверяет, запущена ли машина - `void setOnFinish(std::function 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 on_enter, std::function on_exit = nullptr, const PIString &n = {}) \endcode #### PIStateFinal Финальное состояние машины состояний. \code{.cpp} PIStateFinal(std::function on_finish = nullptr, const PIString &n = {}) \endcode #### PITransitionBase Базовый класс для переходов. **Ключевые методы:** - `PITransitionBase *addGuard(std::function f)` — добавляет сторожевую функцию - `PITransitionBase *addAction(std::function 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`, используемого в переходах по таймауту