Header-only heriarchical FSM framework in C++14, completely static (no dynamic allocations), built with variadic templates.
- Visual Studio 14.u3, 15.9, 16.0 preview
- GCC 4.9, 5.4, 6.3, 7.4, 8.0
- Clang 3.6, 3.7, 3.8, 3.9, 4.0, 5.0, 6.0, 7.0
- Include HFSM2 header:
#include <hfsm2/machine.hpp>
- Define interface class between the state machine and its host (also ok to use the host object itself):
struct Context {
bool powerOn;
};
- For goodness sake, please don't do:
// using namesplace hfsm2;
- (Optional, recommended) Typedef hfsm2::Machine for convenience:
using M = hfsm2::Machine<Context>;
- Declare state machine structure:
Option 1 - with forward declared states:
struct Off;
struct On;
struct Red;
struct Yellow;
struct Green;
struct Done;
using FSM = M::PeerRoot<
// initial state
Off,
// sub-machine region with a head state (On) and and 3 sub-states
M::Composite<On,
// initial state of the region, will be activated with the it
Red,
Yellow,
Green
>,
Done
>;
Option 2 - with a helper macro (don't forget to #undef
it afterwards!):
#define S(s) struct s
using FSM = M::PeerRoot<
// initial state
S(Off),
// sub-machine region with a head state (On) and and 3 sub-states
M::Composite<S(On),
// initial state of the region, will be activated with the it
S(Red),
S(Yellow),
S(Green)
>,
S(Done)
>;
#undef S
- (Optional) Define events to have external code interact with your state machine directly:
struct SomeEvent { /* ... */ };
- Define states and override any or all of the optional state methods:
struct Off
// necessary boilerplate!
: FSM::State
{
// called before state activation, use to re-route transitions
void guard(FullControl& control) {
// access context data
if (control.context().powerOn)
// initiate an immediate transition into 'On' region
control.changeTo<On>();
}
};
// region's head state is active for the entire duration of the region being active
struct On
: FSM::State
{
// called on state activation
void enter(Control& control) {
// access the plan for the region
auto plan = control.plan();
// sequence plan steps, executed when the previous state succeeds
plan.add<Red, Yellow>();
plan.add<Yellow, Green>();
plan.add<Green, Yellow>();
plan.add<Yellow, Red>();
}
// called on state deactivation
void exit(Control& control) { /* ... */ }
// called on the successful completion of all plan steps
void planSucceeded(FullControl& control) {
// we're done here
control.changeTo<Done>();
}
// called if any of the plan steps fails
void planFailed(FullControl& control) { /* ... */ }
};
struct Red
: FSM::State
{
// called on periodic state machine updates
void update(FullControl& control) {
// notify successful completion of the plan step
// plan will advance to the 'Yellow' state
control.succeed();
}
};
struct Yellow
: FSM::State
{
void update(FullControl& control) {
// plan will advance to the 'Green' state on the first entry
// and 'Red' state on the second one
control.succeed();
}
};
struct Green
: FSM::State
{
// called on external events
// it's possible to have multiple 'react()' methods, covering multiple events
template <typename TEvent>
void react(const TEvent&, FullControl& control) {
// can also notify successful completion on external event too
control.succeed();
}
};
struct Done
: FSM::State
{};
- Write the client code to use your new state machine:
int main() {
- Create context and state machine instances:
Context context;
FSM fsm(context);
- Kick off periodic updates:
context.powerOn = true;
// API to check if a state is active
while (!fsm.isActive<Green>())
fsm.update();
- (Optional) Handle your special events:
// gentle push for 'Green' state to yield to 'Yellow'
machine.react(SomeEvent{});
- Keep updating state machine until it's done:
// more updates
while (!fsm.isActive<Off>())
fsm.update();
return 0;
}
- Permissive MIT License
- Written in widely-supported modern(ish) C++ 14
- Header-only
- Fully static, no dynamic allocations
- Uses inline-friendly compile-time pylymorphism, no virtual methods were harmed
- Type-safe transitions:
FSM.changeTo<TargetState>()
- 100% NoUML-compliant
- Hierarchical, with composite (sub-machine) and orthogonal regions
- Gamedev-friendly, supports explicit
State::update()
- Also supports traditional event-based workflow with
State::react()
- New: Supports planning! Schedule state-task execution with scripted transitions.
- New: Can be used to implement your favorite planner algorithms - BT, HTN, GOAP, etc.
- Scaleable, supports state re-use via state injections
- Debug-assisted, includes automatic structure and activity visualization API with
#define HFSM_ENABLE_STRUCTURE_REPORT
- Convenient, minimal boilerplate
- Phil Nash
- Romain Cheminade
- Tristan Brindle
- Kevin Greene
- everybody at C++::London meetup
- programming community at Splash Damage