PIP 5.6.1
Platform-Independent Primitives
State machine

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.

PIStateLambda(std::function<void()> on_enter, std::function<void()> on_exit = nullptr, const PIString &n = {})
State implementation that forwards enter and exit virtual methods to callbacks.
Definition: pistatemachine_state.h:195
String class.
Definition: pistring.h:42

PIStateFinal

Final state of state machine.

PIStateFinal(std::function<void()> on_finish = nullptr, const PIString &n = {})
Final state that finishes its parent state when entered.
Definition: pistatemachine_state.h:227

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

#include "pistatemachine.h"
#include "pisystemtime.h"
// Create state machine
PIStateMachine *machine = new PIStateMachine("Main");
// Create states
[]() { piCout << "Entering Idle state\n"; },
[]() { piCout << "Exiting Idle state\n"; },
"Idle"
);
[]() { 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
void setInitialState(PIStateBase *s)
Sets the initial child state for a non-parallel compound state.
Definition: pistatemachine_state.cpp:63
PITransitionBase * addTransition(PIStateBase *target, int event_id)
Adds an event-driven transition from this state to target.
Definition: pistatemachine_state.cpp:71
void addState(PIStateBase *s)
Adds a child state owned by this state.
Definition: pistatemachine_state.cpp:48
Root object that owns and runs a hierarchical state machine.
Definition: pistatemachine.h:43
void setOnFinish(std::function< void()> f)
Sets a callback invoked when the machine finishes.
Definition: pistatemachine.h:60
bool start()
Starts the machine from its initial active configuration.
Definition: pistatemachine.cpp:28
bool postEvent(int event_id, Args... args)
Posts an event to active states and triggers the first matching transition.
Definition: pistatemachine.h:66
#define piCout
Definition: picout.h:36
State machine. Main state machine class that manages states and transitions. Thread-safe implementati...
System time structs and methods.

Guarded Transitions

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");
PITransitionBase * addGuard(std::function< R(Args...)> f)
Sets a guard function that must return bool.
Definition: pistatemachine_transition.h:74

Timeout Transitions

PIStateMachine *machine = new PIStateMachine("With Timeout");
[]() { 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)
// Add transition from timeoutState to finish
timeoutState->addTransition(finish, 1);
machine->start();
PITransitionTimeout * addTimeoutTransition(PIStateBase *target, PISystemTime timeout)
Adds a timeout transition that fires while this state stays active.
Definition: pistatemachine_state.cpp:79
static PISystemTime fromSeconds(double v)
Contructs time from seconds "v".
Definition: pisystemtime.h:374

Compound States

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);

Parallel States

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
void setParallel(bool yes)
Enables or disables parallel activation of child states.
Definition: pistatemachine_state.h:119

Transitions with Actions

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);
PITransitionBase * addAction(std::function< void()> a)
Sets an action executed when the transition fires.
Definition: pistatemachine_transition.cpp:37

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

// ❌ WRONG
PIStateLambda state([]() {}, nullptr, "State");
// ✅ CORRECT
PIStateLambda *state = new PIStateLambda([]() {}, nullptr, "State");

Proper Object Hierarchy

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();

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:

machine->print();
void print(PIString prefix={})
Prints the state tree and active branches to piCout.
Definition: pistatemachine_state.cpp:87

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

Related Modules

  • DateTime module for PISystemTime used in timeout transitions