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

715 lines
23 KiB
Markdown

\~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<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.
## 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<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`, используемого в переходах по таймауту