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::variant
s.
Here is user_interface.hpp
:
#ifndef USER_INTERFACE_HPP
#define USER_INTERFACE_HPP
#include <variant>
#include <functional>
class user_interface
{
public:
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)
{
}
private:
event_callback_type cb_;
};
#endif
Here is motor.hpp
:
#ifndef MOTOR_HPP
#define MOTOR_HPP
#include <variant>
#include <functional>
class motor
{
public:
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)
{
}
void async_start()
{
}
void async_stop()
{
}
private:
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>
struct context
{
template<class Machine>
context(Machine& machine):
user_itf(make_event_callback(machine)),
mtr(make_event_callback(machine))
{
}
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;
};
{
ctx.mtr.async_start();
});
{
ctx.mtr.async_stop();
});
(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>)
;
.context_am<context>()
;
int main()
{
auto machine = machine_t{};
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().