event loop | support status |
---|---|
boost-asio | supported |
wx-widgets | supported |
ev | supported |
libevent | planned |
libuv | planned |
gtk | planned |
qt | planned |
If you need some other event loop or speedup inclusion of a planned one, please file an issue.
event loop | support status |
---|---|
linux | supported |
windows | supported |
macos | supported |
rotor
is designed to be integrated with event loops, which actually perform some I/O, spawn and
trigger timers and other asynchronous operations, which can be invoked from an actor or a supervisor.
The key accessor here is a supervisor.
First, the final (concrete) supervisor have to be obtained. Then, for safety, the intrusive pointer to the actor should be created(2), and then loop-specific async operation should be initiated(3), where the pointer should be moved(4). Finally, when the result of I/O operation is transformed into rotor message or messages(5), which should be sent, the supervisor should be asked to perform rotor mechanics, i.e. deliver message(s)(6). Let's illustrate it on timer using boost-asio.
struct my_actor_t : r::actor_base_t {
using timer_ptr_t = std::unique_ptr<boost::asio::deadline_timer>;
timer_ptr_t timer;
void on_trigger(message::some_message_t&) noexcept {
using actor_ptr_t = r::intrusive_ptr_t<my_actor_t>;
auto sup = static_cast<r::asio::supervisor_asio_t*>(supervisor); // (1)
auto actor_ptr = actor_ptr_t(this); // (2)
// (3)
auto& strand = sup_ptr->get_strand();
timer = std::make_unique<boost::asio::deadline_timer>(strand);
timer->expires_from_now(timeout);
timer->async_wait([this, actor_ptr = std::move(actor_ptr)](auto& ec){ // (4)
...;
send<message::io_result_t>(...); // (5)
get_supervisor()->do_process(); // (6)
(void)actor_ptr;
});
}
};
Adding new event loop to rotor
is rather simple: the new supervisor
class
should be derived from supervisor_t
, and the following methods should be
defined
void start_timer(const pt::time_duration &send, ) noexcept override;
void cancel_timer(request_id_t timer_id) noexcept override;
void start() noexcept override {}
void shutdown() noexcept override {}
void enqueue(rotor::message_ptr_t) noexcept override {}
The enqueue
method is responsible for puting an message
into supervisor
inbound queue probably in thread-safe manner to allow accept messages
from other supervisors/loops (thread-safety requirement) or just from some
outer context, when supervisor is still not running on the loop (can be
thread-unsafe). The second requirement is to let the supervisor process
all it's inbound messages in a loop context, may be with supervisor-specific
context.
The start
and shutdown
are just convenient methods to start processing
messages in event loop context (for start
) and send a shutdown messages
and process event loop in event loop context .
Here is an skeleton example for enqueue
:
void some_supervisor_t::enqueue(message_ptr_t message) noexcept {
supervisor_ptr_t self{this}; // increase ref-counter
auto& loop = ... // get loop somehow, e.g. from loop-specific context
loop.invoke_later([self = std::move(self), message = std::move(message)]() {
auto &sup = *self;
sup.put(std::move(message)); // put message into inbound queue
sup.do_process(); // process inbound queue
});
}
How to get loop and what method invoke on it, is the implementation-specific information.
For example, loop refrence can be passed on supervisor
constructor. The invoke_later
(alternative names: postpone
, CallAfter
, delay
, dispatch
) is loop-specific
method how to invoke something on in a thread-safe way. Please note, that supervisor
instance is captured via intrusive pointer to make sure it is alive in the loop context
invocations.
The timer-related methods are loop- or application-specific.