using namespace ifsm; StateMachine myMachine;
This is the simple one.
This kind of FSM is not going to be very helpful, but will start from there and expand.
Note: The StateMachine instance is a state itself, so anything that applies to State instances in the rest of this page also applies to the StateMachine instance, ie. transitions, properties, OnEntry, OnExit, ...
Describing an FSM follows a set of rules. When on of these rule is broken, instantFSM throws a meaningfull exception to inform the developper of the problem. It is then good practice, during development, to surround your instantation with a try/catch block and display meaningfull error when an exception is caught:
using namespace ifsm; try{ StateMachine myMachine( ); } catch(const StateMachineException& e){ std::cout<<e.what()<<std::endl; }
A detail of each exception that can be thrown will be given during the tutorial. You can also find it in the cheatsheet.
The first interesting feature is to be able to add subStates to the root state (the StateMachine instance).
This is achieved by just calling the State() function inside the StateMachine constructor. A required parameter to the State function is the name of the State to create :
using namespace ifsm; StateMachine myMachine(State("child"));
You can nest any number of States in any State :
using namespace ifsm; StateMachine myMachine( State("Alpha", initialTag, State("Banana", initialTag), State("Apple") ), State("Bravo", State("Bird", initialTag), State("Fish") ), State("Charlie", State("Jupiter", initialTag), State("Saturn") ) );
When instantiating multiple states alternatively to each other, you have to tell which one will be activated by default when entering the parent State.
This is achieved by passing the initialTag
parameter to the initial State :
using namespace ifsm; StateMachine myMachine( State("Alpha", initialTag), //Alpha will be entered when myMachine is activated State("Bravo"), );
If you forget to set an initial state for each set of alternative nested states, a NoInitialState
exception will be thrown.
Parallel States let you have orthogonal regions in your StateMachine, allowing for concurrent States to be active.
In order to declare a State parallel, pass the parallelTag
to it. All of its nested States will be entered after the parallel State is entered.
using namespace ifsm; StateMachine myMachine( parallelTag, State("Alpha"), State("Bravo"), //Alpha and Bravo will be entered with the StateMachine entry );
Note: The definition of an initial State amongst the nested States of a parallel State is not required.
Most of the time, the execution of your state machine drives properties of other systems. It can be sending some event or messages through a communication system or displaying and hiding some elements of a GUI. These action are executed relatively to the current configuration of the state machine, meaning that one action has to be undergone when entering the state and another, sometimes inserse action, when leaving it.
For instance, for a music player, I can have a state Playing and another state Stopped. In the Playing state, I need to turn the Play button into a Pause button and enable the Stop button, while in Stopped state, I need to turn the Pause button into Play and disable the Stop button.
To achieve this kind of behavior, you can setup entry
and exit
callbacks to each State
you create, by calling respectively the OnEntry
and OnExit
functions inside the State function.
Each of these functions take a callable type as parameter, which will be called upon entry (respectively exit) of the State.
This callable can be of two forms :
The former one is a simple callback with no parameter, while the latter one, receives the StateMachine instance as parameter, allowing the callback to issue an event or query the activity of a state.
This use of a generic callable as parameter allows for various types to be passed, be it pointer to function, std::bind
results, std::function
and especially lambda functions.
Here is a sample of displaying to the standard output when entering and exiting a state using simple functions:
using namespace ifsm; void entering(){ std::cout<<"Entering the root state!"<<std::endl; } void exiting(){ std::cout<<"Exiting the root state!"<<std::endl; } StateMachine myMachine( OnEntry([](){ entering(); }), OnExit([](){ exiting(); }) );
Here is the same sample using c++11's lambdas:
using namespace ifsm; StateMachine myMachine( OnEntry([](){ std::cout<<"Entering the root state!"<<std::endl; }), OnExit([](){ std::cout<<"Exiting the root state!"<<std::endl; }) );
The use of lambdas in the context of state machine definition is very powerfull, since the actions to undertake during specific states'entry and exit are embedded
in the definition. When the statemachine can drive orthogonal services (like a GUI in parallel with a physics simulation), avoiding state-specific function definitions outside of the state
machine provides a clearer view of what are the consequences of entering and exiting a state.
Have a look to the sample page to get a clearer view of the possibilities of such an architecture.
Transitions model the connection between an event and a change of configuration of the state machine. A change of configuration implies leaving some of the active states of the state machine and entering some previously inactive states.
From there, the basic parameters for a transition are :
Extended parameters are :
* Condition(callable_condition) : a callback returning a bool value that prevents the transition from executing if it return false.
* Action(callable) : a callback which is executed after exiting the active states that need to be exited and before entering candidate states for entry.
The following example demonstrate the use of transitions to go back and forth between two state following occuring events.
~~~~~~
:::cpp
using namespace ifsm;
StateMachine myMachine(
State("idle", initialTag,
Transition(
OnEvent("play"), Target("playing"),
Condition(songSelected{return songSelected;}),
Action({
std::cout<<"the player was kicked in"<<std::endl;
})
)
),
State("playing",
Transition(
OnEvent("stop", Target("idle"),
Action({
std::cout<<"the player was stopped"<<std::endl;
})
)
)
);
myMachine.pushEvent("play");
//the state machine enters the "playing" state after displaying the message
myMachine.pushEvent("stop");
//the state machine enters the "idle" state after displaying the stop message
Only transitions from active states are evaluated. This means that there is no need to check that the SM is in "idle" state when considering the event "play" for the first transition. The transition wouldn't be executed if it wasn't the case. ## Targetless transitions Sometimes you want to execute some actions (ie. trigger callbacks) when an event is received, without necessarily transitioning states. This is achieved with targetless transition. As the name suggests, these are defined like ordinary transitions, omitting the `Target()` parameter.
:::cpp
using namespace ifsm;
StateMachine myMachine(
Transition(
OnEvent("play"),
Action({std::cout<<"play event has been received"<<std::endl;})
)
);
Another way of defining a targetless transition is by using the `OnEvent` parameter, which behaves as a shorter synonym for `Transition`, but doesn't carry the falsified semantics. The `OnEvent()` parameter, passed to a `State` or `StateMachine` instantiation, takes an `std::string` giving the event name as first parameter, and a `callable` as second parameter.
:::cpp
using namespace ifsm;
StateMachine myMachine(
OnEvent("play", {std::cout<<"play event has been received"<<std::endl;})
);
~~~~~~
The only drawback to the OnEvent
shortcut for targetless transitions is the inability to set a condition callback like ordinary transitions. When a condition is required, you'd still have to define a proper Transition
.
When instantiated, the StateMachine is not considered active. Posting events to it won't trigger any callback. You can activate the StateMachine by calling StateMachine::enter().
Once active, you can query it to know whether a specific State is active.