Maki
Loading...
Searching...
No Matches
Event

How to use events within Maki

Within Maki, an event can be an object of any type. It can carry data that your state machine (more precisely your actions and guards, which we'll see later in this manual) will be able to read.

Maki only requires your events to be copyable (unless you disable run-to-completion, but we'll see that later as well).

Very often, you will define event types in modules that don't even depend on Maki whatsoever.

Example

Let's see how a Maki-based program typically handles events.

We have a device that is made of:

  • a motor that takes a little time to start and stop;
  • a user interface with two buttons to require the motor to start or stop.

We then implement a monitor for these buttons (the user_interface class) as well as a driver for the motor (the motor class). Both these classes send several types of events (asynchronously). To keep our code terse, we wrap these events into std::variants.

Here is user_interface.hpp:

#ifndef USER_INTERFACE_HPP
#define USER_INTERFACE_HPP
#include <variant>
#include <functional>
/*
The user interface monitor
This monitor sends events to notify user actions.
*/
class user_interface
{
public:
//Events
struct start_request{};
struct stop_request{};
using event_type = std::variant<start_request, stop_request>;
using event_callback_type = std::function<void(const event_type&)>;
user_interface(const event_callback_type& cb):
cb_(cb)
{
//[Implementation detail...]
}
private:
//[Implementation detail...]
event_callback_type cb_;
};
#endif

Here is motor.hpp:

#ifndef MOTOR_HPP
#define MOTOR_HPP
#include <variant>
#include <functional>
/*
The motor driver
Since the motor takes time to start and stop, the interface is asynchronous.
It sends events to notify the client when the asynchronous commands complete.
*/
class motor
{
public:
//Events
struct start_event{};
struct stop_event{};
using event_type = std::variant<start_event, stop_event>;
using event_callback_type = std::function<void(const event_type&)>;
motor(const event_callback_type& cb):
cb_(cb)
{
//[Implementation detail...]
}
//Asynchronously start the motor and send a start_event when it's done
void async_start()
{
//[Implementation detail...]
}
//Asynchronously stop the motor and send a stop_event when it's done
void async_stop()
{
//[Implementation detail...]
}
private:
//[Implementation detail...]
event_callback_type cb_;
};
#endif

We need a state machine to make sure:

  • we don't forward multiple start requests while the motor is starting;
  • we don't forward multiple stop requests while the motor is stopping.

Here is the state diagram:

The state machine of a motor

And here is main.cpp, which implements the state machine:

#include "user_interface.hpp"
#include "motor.hpp"
#include <maki.hpp>
//The context, instantiated by machine_t and accessible to all the actions and guards
struct context
{
/*
Note: Machine is always machine_t (defined below).
We need a template to break circular dependency.
*/
template<class Machine>
context(Machine& machine):
user_itf(make_event_callback(machine)),
mtr(make_event_callback(machine))
{
}
/*
Forward events (from event variants) to state machine.
Note: Machine is always machine_t (defined below).
We need a template to break circular dependency.
*/
template<class Machine>
static auto make_event_callback(Machine& machine)
{
return [&machine](const auto& event_variant)
{
std::visit
(
[&machine](const auto& event)
{
machine.process_event(event);
},
event_variant
);
};
}
user_interface user_itf;
motor mtr;
};
//States
constexpr auto idle = maki::state_builder{};
constexpr auto starting = maki::state_builder{};
constexpr auto running = maki::state_builder{};
constexpr auto stopping = maki::state_builder{};
//Actions
constexpr auto start_motor = maki::action_c([](context& ctx)
{
ctx.mtr.async_start();
});
constexpr auto stop_motor = maki::action_c([](context& ctx)
{
ctx.mtr.async_stop();
});
//Transition table
constexpr auto transition_table = maki::transition_table{}
//source, target, event, action
(maki::init, idle)
(idle, starting, maki::event<user_interface::start_request>, start_motor)
(starting, running, maki::event<motor::start_event>)
(running, stopping, maki::event<user_interface::stop_request>, stop_motor)
(stopping, idle, maki::event<motor::stop_event>)
;
//State machine configuration
constexpr auto machine_conf = maki::machine_conf{}
.transition_tables(transition_table)
.context_am<context>()
;
//State machine
using machine_t = maki::machine<machine_conf>;
int main()
{
//Instantiate the state machine
auto machine = machine_t{};
//Run indefinitely...
//[Implementation detail...]
return 0;
}

Don't worry too much about the details for now. Just notice that:

  • all the event types are defined in user_interface and motor, which don't depend on Maki;
  • all the events sent by user_interface and motor are forwarded to our machine_t instance through a call to machine_t::process_event().