2
0
mirror of https://github.com/boostorg/msm.git synced 2026-01-24 06:02:10 +00:00
Files
msm/doc/modules/ROOT/pages/tutorial/functor-front-end.adoc
2025-11-02 13:14:19 -05:00

312 lines
12 KiB
Plaintext

[[functor-front-end]]
= Functor front-end
The functor front-end is the preferred front-end at the moment. It is
more powerful than the standard front-end and has a more readable
transition table. It also makes it easier to reuse parts of state
machines. Like `eUML`, it also comes with a good deal of predefined
actions. Actually, eUML generates a functor front-end through
Boost.Typeof and Boost.Proto so both offer the same functionality.
The rows which MSM offered in the previous front-end come in different
flavors. We saw the a_row, g_row, _row, row, not counting internal rows.
This is already much to know, so why define new rows? These types have
some disadvantages:
* They are more typing and information than we would wish. This means
syntactic noise and more to learn.
* Function pointers are weird in C++.
* The action/guard signature is limited and does not allow for more
variations of parameters (source state, target state, current state
machine, etc.).
* It is not easy to reuse action code from a state machine to another.
[[transition-table]]
== Transition table
We can change the definition of the simple tutorial's transition table
to:
....
struct transition_table : mpl::vector<
// Start Event Target Action Guard
// +---------+------------+-----------+---------------------------+----------------------------+
Row < Stopped , play , Playing , start_playback , none >,
Row < Stopped , open_close , Open , open_drawer , none >,
Row < Stopped , stop , Stopped , none , none >,
// +---------+------------+-----------+---------------------------+----------------------------+
Row < Open , open_close , Empty , close_drawer , none >,
// +---------+------------+-----------+---------------------------+----------------------------+
Row < Empty , open_close , Open , open_drawer , none >,
Row < Empty , cd_detected, Stopped , store_cd_info , good_disk_format >,
g_row< Empty , cd_detected, Playing , &player_::store_cd_info , &player_::auto_start >,
// +---------+------------+-----------+---------------------------+----------------------------+
Row < Playing , stop , Stopped , stop_playback , none >,
Row < Playing , pause , Paused , pause_playback , none >,
Row < Playing , open_close , Open , stop_and_open , none >,
// +---------+------------+-----------+---------------------------+----------------------------+
Row < Paused , end_pause , Playing , resume_playback , none >,
Row < Paused , stop , Stopped , stop_playback , none >,
Row < Paused , open_close , Open , stop_and_open , none >
// +---------+------------+-----------+---------------------------+----------------------------+
> {};
....
Transitions are now of type "Row" with exactly 5 template arguments:
source state, event, target state, action and guard. Wherever there is
nothing (for example actions and guards), write "none". Actions and
guards are no more methods but functors getting as arguments the
detected event, the state machine, source and target state:
....
struct store_cd_info
{
template <class Fsm,class Evt,class SourceState,class TargetState>
void operator()(Evt const&, Fsm& fsm, SourceState&,TargetState& )
{
cout << "player::store_cd_info" << endl;
fsm.process_event(play());
}
};
....
The advantage of functors compared to functions is that functors are
generic and reusable. They also allow passing more parameters than just
events. The guard functors are the same but have an operator() returning
a bool.
It is also possible to mix rows from different front-ends. To show this,
a g_row has been left in the transition table. _Note:_ in case the
action functor is used in the transition table of a state machine
contained inside a top-level state machine, the ``fsm'' parameter refers
to the lowest-level state machine (referencing this action), not the
top-level one.
To illustrate the reusable point, MSM comes with a whole set of
predefined functors. Please refer to eUML for the
link:#Reference-begin[full list]. For example, we are now going to
replace the first action by an action sequence and the guard by a more
complex functor.
We decide we now want to execute two actions in the first transition
(Stopped -> Playing). We only need to change the action start_playback
to
....
ActionSequence_< mpl::vector<some_action, start_playback> >
....
and now will execute some_action and start_playback every time the
transition is taken. ActionSequence_ is a functor calling each action of
the mpl::vector in sequence.
We also want to replace good_disk_format by a condition of the type:
``good_disk_format && (some_condition || some_other_condition)''. We can
achieve this using And_ and Or_ functors:
....
And_<good_disk_format,Or_< some_condition , some_other_condition> >
....
It even starts looking like functional programming. MSM ships with
functors for operators, state machine usage, STL algorithms or container
methods.
[[defining-states-with-entryexit-actions]]
== Defining states with entry/exit actions
You probably noticed that we just showed a different transition table
and that we even mixed rows from different front-ends. This means that
you can do this and leave the definitions for states unchanged. Most
examples are doing this as it is the simplest solution. You still enjoy
the simplicity of the first front-end with the extended power of the new
transition types. This xref:attachment$SimpleWithFunctors.cpp[tutorial],
adapted from the earlier example, does just this.
Of course, it is also possible to define states where entry and exit
actions are also provided as functors as these are generated by eUML and
both front-ends are equivalent. For example, we can define a state as:
....
struct Empty_Entry
{
template <class Event,class Fsm,class State>
void operator()(Event const&,Fsm&,State&)
{
...
}
}; // same for Empty_Exit
struct Empty_tag {};
struct Empty : public msm::front::euml::func_state<Empty_tag,Empty_Entry,Empty_Exit>{};
....
This also means that you can, like in the transition table, write entry
/ exit actions made of more complicated action combinations. The
previous example can therefore xref:attachment$SimpleWithFunctors2.cpp[be
rewritten].
Usually, however, one will probably use the standard state definition as
it provides the same capabilities as this front-end state definition,
unless one needs some of the shipped predefined functors or is a fan of
functional programming.
[[functor-front-end-actions]]
== What do you actually do inside actions / guards (Part 2)?
Using the basic front-end, we saw how to pass data to actions through
the event, that data common to all states could be stored in the state
machine, state relevant data could be stored in the state and access as
template parameter in the entry / exit actions. What was however missing
was the capability to access relevant state data in the transition
action. This is possible with this front-end. A transition's source and
target state are also given as arguments. If the current calculation's
state was to be found in the transition's source state (whatever it is),
we could access it:
....
struct send_rocket
{
template <class Fsm,class Evt,class SourceState,class TargetState>
void operator()(Evt const&, Fsm& fsm, SourceState& src,TargetState& )
{
fire_rocket(evt.direction, src.current_calculation);
}
};
....
It was a little awkward to generate new events inside actions with the
basic front-end. With the functor front-end it is much cleaner:
....
struct send_rocket
{
template <class Fsm,class Evt,class SourceState,class TargetState>
void operator()(Evt const& evt, Fsm& fsm, SourceState& src,TargetState&)
{
fire_rocket(evt.direction, src.current_calculation);
fsm.process_event(rocket_launched());
}
};
....
[[defining-a-simple-state-machine]]
== Defining a simple state machine
Like states, state machines can be defined using the previous front-end,
as the previous example showed, or with the functor front-end, which
allows you to define state machine entry and exit functions as functors,
as in xref:attachment$SimpleWithFunctors2.cpp[this example].
[[anonymous-transitions]]
== Anonymous transitions
Anonymous (completion) transitions are transitions without a named
event. We saw how this front-end uses `none` when no action or guard is
required. We can also use `none` instead of an event to mark an
anonymous transition. For example, the following transition makes an
immediate transition from State1 to State2:
....
Row < State1 , none , State2 >
....
The following transition does the same but calling an action in the
process:
....
Row < State1 , none , State2 , State1ToState2, none >
....
The following diagram shows an example and its
xref:attachment$AnonymousTutorialWithFunctors.cpp[implementation]:
image:Anonymous.jpg[image] ###
[[functor-internal-transitions]]Internal transitions
The xref:attachment$SimpleTutorialInternalFunctors.cpp[following example]
uses internal transitions with the functor front-end. As for the simple
standard front-end, both methods of defining internal transitions are
supported:
* providing a `Row` in the state machine's transition table with `none`
as target state defines an internal transition.
* providing an `internal_transition_table` made of `Internal` rows
inside a state or submachine defines UML-conforming internal transitions
with higher priority.
* transitions defined inside `internal_transition_table` require no
source or target state as the source state is known (`Internal` really
are `Row` without a source or target state).
Like for the `standard front-end internal transitions`, internal
transition tables are added into the main state machine's table, thus
allowing you to distribute the transition table definition and reuse
states.
There is an added bonus offered for submachines, which can have both the
standard transition_table and an internal_transition_table (which has
higher priority). This makes it easier if you decide to make a full
submachine from a state later. It is also slightly faster than the
standard alternative, adding orthogonal regions, because event
dispatching will, if accepted by the internal table, not continue to the
subregions. This gives you an O(1) dispatch instead of O(number of
regions). While the example is with eUML, the same is also possible with
this front-end.
[[any-event]]
== Kleene (any) event
Normally, MSM requires an event to fire a transition. But there are
cases, where any event, no matter which one, would do:
* If you want to reduce the number of transitions: any event would do,
possibly will guards decide what happens.
* Pseudo entry states do not necessarily want to know the event which
caused their activation, or they might want to know only a property of
it.
MSM supports a boost::any as an acceptable event. This event will match
any event, meaning that if a transition with boost::any as event
originates from the current state, this transition would fire (provided
no guards or transition with a higher priority fires first). This event
is named Kleene, as a reference to the Kleene star used in a regex.
For example, this transition on a state machine instance named fsm:
....
Row < State1, boost::any, State2>
....
will fire if State1 is active and an event is processed:
....
fsm.process_event(whatever_event());
....
At this point, you can use this _any_ event in transition actions to get
back to the original event by calling for example _boost::any::type()_.
It is also possible to support your own Kleene events by specializing
boost::msm::is_kleene_event for a given event, for example:
....
namespace boost { namespace msm{
template<>
struct is_kleene_event< my_event >
{
typedef boost::mpl::true_ type;
};
}}
....
The only requirement is that this event must have a copy constructor
from the event originally processed on the state machine.