Maki
Loading...
Searching...
No Matches
Context

As you already know, a state machine is associated to a context type whose instance can be accessed by all the actions and guards. But there's a little bit more than that.

State Machine Context

Let's review the basics. The context of a state machine is an object of any type that can be accessed by all its parts (such as actions and guards). A struct such as this one is an adequate context type:

struct my_context
{
int some_value = 0;
std::unique_ptr<some_component> pcomponent;
};

The type of the context is an option of maki::machine_conf:

constexpr auto machine_conf = maki::machine_conf{}
.context_a<my_context>()
.transition_tables(transition_table)
;
using machine_t = maki::machine<machine_conf>;

You don't instantiate the context yourself; maki::machine does. This is why you must specify how Maki must construct it. Setting the context type with maki::machine_conf::context_a() like we just did indicates to Maki that we want it to construct the context by forwarding the arguments given to the constructor of maki::machine, and only these arguments. Indeed, the _a suffix stands for "arguments".

Then, when we instantiate the state machine like below, we indirectly construct the context by setting all its member variables:

auto machine = machine_t{41, std::make_unique<some_component>()};

After setting all this up, we can finally access the context from our actions:

constexpr auto my_state = maki::state_builder{}
.entry_action_c([](my_context& ctx)
{
++ctx.some_value;
ctx.pcomponent->print_value(ctx.some_value);
})
;

Here is the program containing all the code snippets above in the right order:

#include <maki.hpp>
#include <memory>
#include <iostream>
struct some_component
{
void print_value(const int value)
{
std::cout << "Value is " << value << '\n';
}
};
struct my_context
{
int some_value = 0;
std::unique_ptr<some_component> pcomponent;
};
constexpr auto my_state = maki::state_builder{}
.entry_action_c([](my_context& ctx)
{
++ctx.some_value;
ctx.pcomponent->print_value(ctx.some_value);
})
;
// Just a dummy transition table, because we need one.
constexpr auto transition_table = maki::transition_table{}
//source state, event, target state
(maki::init, my_state)
(my_state, maki::null, maki::null)
;
constexpr auto machine_conf = maki::machine_conf{}
.context_a<my_context>()
.transition_tables(transition_table)
;
using machine_t = maki::machine<machine_conf>;
int main()
{
auto machine = machine_t{41, std::make_unique<some_component>()};
}

The output of this program is the following:

Value is 42

State Context

Any state, however deeply nested in the composite state hierarchy, can have a context of its own as well.

By default, a state is associated with the context of its parent (i.e. the state machine or, in the case of a substate, its direct parent composite state). We can associate it with another one by calling maki::state_builder::context_c(), for example. The _c suffix refers to the machine context; we want Maki to construct the context of the state by passing it the context of its parent.

struct my_machine_context
{
int value = 0;
};
class my_state_context
{
public:
my_state_context(my_machine_context& parent):
parent_(parent)
{
}
void on_entry()
{
++value_;
parent_.value = value_;
}
private:
my_machine_context& parent_;
int value_ = 0;
};
constexpr auto my_state = maki::state_builder{}
.context_c<my_state_context>()
.entry_action_c(&my_state_context::on_entry)
;

In case you're wondering: In the example above, we've been able to set the entry action like we did because in this case, maki::state_builder::entry_action_c() asks Maki to execute the action by calling std::invoke(&my_state_context::on_entry, instance_of_my_state_context), which is equivalent to instance_of_my_state_context.on_entry().

Important
State contexts have the same lifetime as the instance of maki::machine. They're not created at state entry and destructed at state exit!