Maki
Loading...
Searching...
No Matches
Action

Definition

There are several kinds of actions:

  • transition actions;
  • entry actions;
  • exit actions.

Transition action

A transition action, as its name suggests, is associated to a transition. Such an action is executed whenever the associated transition occurs.

In the following diagram, action is executed whenever the state machine transitions from state0 to state1 (because event occurs):

A state transition action

Entry action

An entry action is associated to a state. Such an action is executed whenever the state machine enters the associated state.

In the following diagram, action is executed whenever the state machine enters state2, whatever the source state (here state0 or state1):

An entry action

Exit action

An exit action is associated to a state. Such an action is executed whenever the state machine exits the associated state.

In the following diagram, action is executed whenever the state machine exits state0, whatever the target state (here state1 or state2):

An exit action

When to use which kind of action

Entry/exit actions, being associated to a specific state, are well suited for:

  • the initialization/deinitialization of the state (e.g. allocation/deallocation of resources, start/stop of timer to implement a timeout, etc.);
  • calling functions that are semantically associated with the state name (e.g. start_motor()/stop_motor() for a state named running_motor).

Transition actions are well suited for executing functions that have more to do with the specific transition it's associated to than with the source or target state.

How to define actions within Maki

There are two ways to define and associate an action:

  • within the transition table, possible for transition actions;
  • within the associated state, possible for internal actions (see Local Transition), entry actions and exit actions.

Within the transition table

The action is the fourth (optional) parameter of maki::transition_table::operator()(). In this context, Maki expects an instance of maki::action or maki::null.

What determines the kind of the action (between a state transition action and an internal action) is the target state (the third parameter) given to maki::transition_table::operator()():

  • if the target state is maki::null, the action is an internal action;
  • if the target state is a valid state, the action is a state transition action.

Note: A transition without target state is called an internal transition. No state exit or entry happens in this case.

Here is an example of two actions, with their definition and their association with a transition:

//Actions
constexpr auto some_action = maki::action_v([]
{
std::cout << "Executing some action...\n";
});
constexpr auto some_other_action = maki::action_e([](const some_other_event& event)
{
std::cout << "Executing some other action (some_other_event{" << event.value << "})...\n";
});
//Transition table
constexpr auto transition_table = maki::transition_table{}
//source, target, event, action
(maki::init, state0)
(state0, state1, maki::event<some_event>, some_action /*state transition action*/)
(state0, maki::null, maki::event<some_other_event>, some_other_action /*internal transition action*/)
(state0, state2, maki::event<yet_another_event>)
(state1, state2, maki::event<yet_another_event>)
(state2, state0, maki::event<yet_another_event>)
;

Within the associated state

To associate an action to a state, you have to add a callable to the state builder through a call to either:

Here is an example of a state that defines all three kinds of actions (entry, internal and exit):

constexpr auto state2 = maki::state_builder{}
/*
Entry action.
Called on state entry for state transitions caused by some_other_event.
*/
.entry_action_e<some_other_event>
(
[](const some_other_event& event)
{
std::cout << "Executing state2 entry action (some_other_event{" << event.value << "})...\n";
}
)
/*
Entry action.
Called on state entry for state transitions caused by any other type of
event.
*/
.entry_action_v([]
{
std::cout << "Executing state2 entry action...\n";
})
/*
Internal action.
*/
.internal_action_v<some_event>([]
{
std::cout << "Executing state2 some_event action\n";
})
/*
Internal action.
*/
.internal_action_e<some_other_event>([](const some_other_event& event)
{
std::cout << "Executing state2 some_other_event action (some_other_event{" << event.value << "})...\n";
})
/*
Exit action.
Called on state exit, whatever the event that caused the state
transitions.
*/
.exit_action_v([]
{
std::cout << "Executing state2 exit action...\n";
})
;

As you can see, for each action you have to specify:

  • the signature of the action, specified by the suffix of the function name, such as:
    • _e for an action that only takes a reference to the event that triggers the action;
    • _ce for an action that takes a reference to the context, as well as a reference to the event;
    • _v for an action that doesn't take any argument;
    • and so on (see maki::state_builder for all suffixes)...;
  • the event type for which the action is invoked, specified by the given template argument.

Order matters! Whenever the state machine must execute an action, it iterates over the provided action list until it finds a match (i.e. an action of the adequate kind, specifying the adequate event type).

Example

Here is an example program for all the actions we've defined in this chapter:

#include <maki.hpp>
#include <iostream>
struct context{};
//Events
struct some_event{};
struct some_other_event
{
int value = 0;
};
struct yet_another_event{};
//States
constexpr auto state0 = maki::state_builder{};
constexpr auto state1 = maki::state_builder{};
constexpr auto state2 = maki::state_builder{}
/*
Entry action.
Called on state entry for state transitions caused by some_other_event.
*/
.entry_action_e<some_other_event>
(
[](const some_other_event& event)
{
std::cout << "Executing state2 entry action (some_other_event{" << event.value << "})...\n";
}
)
/*
Entry action.
Called on state entry for state transitions caused by any other type of
event.
*/
.entry_action_v([]
{
std::cout << "Executing state2 entry action...\n";
})
/*
Internal action.
*/
.internal_action_v<some_event>([]
{
std::cout << "Executing state2 some_event action\n";
})
/*
Internal action.
*/
.internal_action_e<some_other_event>([](const some_other_event& event)
{
std::cout << "Executing state2 some_other_event action (some_other_event{" << event.value << "})...\n";
})
/*
Exit action.
Called on state exit, whatever the event that caused the state
transitions.
*/
.exit_action_v([]
{
std::cout << "Executing state2 exit action...\n";
})
;
//Actions
constexpr auto some_action = maki::action_v([]
{
std::cout << "Executing some action...\n";
});
constexpr auto some_other_action = maki::action_e([](const some_other_event& event)
{
std::cout << "Executing some other action (some_other_event{" << event.value << "})...\n";
});
//Transition table
constexpr auto transition_table = maki::transition_table{}
//source, target, event, action
(maki::init, state0)
(state0, state1, maki::event<some_event>, some_action /*state transition action*/)
(state0, maki::null, maki::event<some_other_event>, some_other_action /*internal transition action*/)
(state0, state2, maki::event<yet_another_event>)
(state1, state2, maki::event<yet_another_event>)
(state2, state0, maki::event<yet_another_event>)
;
//State machine configuration
constexpr auto machine_conf = maki::machine_conf{}
.transition_tables(transition_table)
.context_a<context>()
;
//State machine
using machine_t = maki::machine<machine_conf>;
int main()
{
auto machine = machine_t{};
//Calls some_other_action()
machine.process_event(some_other_event{42});
//Exits state0, calls some_action() and enters state1
machine.process_event(some_event{});
//No effect, because no action is associated to state1 and some_event
machine.process_event(some_event{});
//Exits state1 and enters state2 (which calls state2::on_entry())
machine.process_event(yet_another_event{});
//Calls state2::on_event(some_other_event{123})
machine.process_event(some_other_event{1337});
//Exits state2 (which calls state2::on_exit()) and enter state0
machine.process_event(yet_another_event{});
return 0;
}

The output of this program is the following:

Executing some other action (some_other_event{42})...
Executing some action...
Executing state2 entry action...
Executing state2 some_other_event action (some_other_event{1337})...
Executing state2 exit action...