123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245 |
- // SPDX-License-Identifier: MIT
- // SPDX-FileCopyrightText: 2022-2024 Ivan Baidakou
- #pragma once
- #include "context.hpp"
- #include "definitions.hpp"
- #include "messages.hpp"
- #include <cassert>
- namespace rotor_light {
- struct SupervisorBase;
- struct QueueBase;
- /** \struct ActorBase
- * \brief base interface and implementation for all actors, including
- * supervisors */
- struct ActorBase {
- /// alias for generic message handler
- using GenericMethod = void (ActorBase::*)(Message &);
- /** \struct handler_traits
- * \brief helps method signature extraction */
- template <typename T> struct handler_traits {};
- template <typename A, typename M> struct handler_traits<void (A::*)(M &)> {
- /// messsage type
- using FinalMessage = M;
- /// final actor type
- using Backend = A;
- };
- struct Handler;
- /// alias for method entry, enough to actually perfrom message delivery
- using MethodEntry = void (*)(Message &, ActorBase &, Handler &);
- /** \struct Handler
- * \brief runtime info about message subscription handler */
- struct Handler {
- /// holds pointer-to-member-function for message processing
- GenericMethod method;
- /// Method entry item
- MethodEntry entry;
- /// holds message_id to do runtime message matching
- MessageTypeId message_id;
- };
- /** the minimum amount of handles, which should
- * a derived actor class pre-allocate */
- static constexpr size_t min_handlers_amount = 1;
- ActorBase(const ActorBase &) = delete;
- ActorBase(ActorBase &&) = delete;
- /**
- * Perform actor intialization, namely subscription to methods.
- * Can ba invoked multiple times, if the actor is restarted several times.
- */
- virtual void initialize();
- /** assign actor id, supervisor and context. Performed only ONCE per
- * program lifetime */
- virtual uint8_t bind(ActorId initial_value, SupervisorBase *supervisor,
- Context &context);
- /** returns actor id */
- inline ActorId get_id() const { return id; }
- /** returns current state of the actor */
- inline State get_state() const { return state; }
- /** returns fail policy of the actor */
- inline FailPolicy get_fail_policy() const { return fail_policy; }
- /** set actor fail policy */
- inline void set_fail_policy(FailPolicy value) { fail_policy = value; }
- /** sends self a shutdown request message. The actor is NOT immediatly
- * stopped. */
- void stop();
- /** adds event, which will trigger in THREAD context after the
- * specified duration by invoking callbacke with the supplied data.
- *
- * The templated ctx is either ctx::thread (masks/unmasks interrupts)
- * or ctx::interrupt (does nothing).
- */
- template <typename Ctx>
- EventId add_event(Duration delta, Callback callback, void *data);
- /** cancels previously created event */
- void cancel_event(EventId event_id);
- /** \brief tries to emplace new message into appropriate queue
- *
- * Creates a new messsage of the specified type "in-place" of
- * the specified queue/priority; all supplied args are forwarded
- * to the message contructor
- *
- * Returns true if the message was succesfully created, and
- * false otherwise, i.e. if the queue is full.
- */
- template <typename Ctx, typename MessageType, typename... Args>
- bool send(size_t queue_index, Args &&...args) {
- return send_impl<Ctx, MessageType, false>(queue_index,
- std::forward<Args>(args)...);
- }
- /** \brief emplaces new message into appropriate queue
- *
- * It behaves the same as `send` method, with the exception
- * that the new message is **always** put into the queue.
- * If the queue is full, then the oldes message is discarded
- * and the new message is emplaced instead of it.
- *
- * Returns true if the queue is full and the oldest message
- * is discarded.
- */
- template <typename Ctx, typename MessageType, typename... Args>
- bool transmit(size_t queue_index, Args &&...args) {
- return send_impl<Ctx, MessageType, true>(queue_index,
- std::forward<Args>(args)...);
- }
- /** performs instant subscription to the specified message handler
- *
- * This method should be invoked from `initialize()`
- */
- template <typename Method> void subscribe(Method method) {
- assert(state == State::off);
- assert(backend_idx < (int)backends_count);
- auto ptr = backends_ptr + ++backend_idx * sizeof(Handler);
- make_handler(method, *reinterpret_cast<Handler *>(ptr));
- }
- protected:
- /** main actor ctor, takes pointer to backend storage and backends count.
- *
- * Sets the fail policy to escalate by default. */
- ActorBase(char *backends, size_t backends_count);
- /** this method is invoked, when actor received "change state" message
- * from it's supervisor, i.e. to advance from "off" state to "initialized"
- * state.
- *
- * The idea to override this method in your actor class to perform some
- * hardware initialization, may be asynchronously, and when it is ready
- * call the advance_init() in the parent class to acknowledge successful
- * initialization to supervisor.
- *
- * If the initialization fails, then instead of invoking the method,
- * the actor should send `message::ChangeStateAck` with the **false** value.
- *
- */
- virtual void advance_init();
- /** this method is invoked, when supervisor is ready to operate, i.e.
- * all other actors acknowledged successful initialialization
- * and it is time to initiate communication.
- *
- * Usually this is "pro-active" method, i.e. to send high-level
- * request to let all messaging machinery work
- */
- virtual void advance_start();
- /** this method is invoked when the actor is acked to shutdown by
- * supervisor.
- *
- * The idea is to override the method in a derived class, shutdown
- * hardware (possibly asynchronously), and then invoke the method
- * of the parent class.
- *
- * Contrary to the `advance_init` method, this method have to
- * be invoked sooner or later.
- */
- virtual void advance_stop();
- /** actor unique id, assigned once upon `bind` method */
- ActorId id;
- /** actor mask, used for messages routing.
- *
- * For simple actors (non-supervisors) it is equal to actor id
- *
- * For supervisors it is includes actor ids of all owned child-actors,
- * including masks of child-supervisors etc.
- */
- ActorId mask;
- /** current actor state */
- State state = State::off;
- /** pointer to the owner/supervisor */
- SupervisorBase *supervisor;
- /** actor's fail policy */
- FailPolicy fail_policy;
- private:
- template <typename Ctx, typename MessageType, bool force, typename... Args>
- bool send_impl(size_t queue_index, Args &&...args);
- void on_state_change(message::ChangeState &);
- template <typename Method>
- void make_handler(Method &method, Handler &handler) {
- using traits = handler_traits<Method>;
- using FinalMessage = typename traits::FinalMessage;
- using Backend = typename traits::Backend;
- handler.message_id = FinalMessage::type_id;
- handler.entry = [](Message &message, ActorBase &actor, Handler &handler) {
- auto &final_message = static_cast<FinalMessage &>(message);
- auto &backend = static_cast<Backend &>(actor);
- auto method = handler.method; // for proper alignment
- (backend.*reinterpret_cast<Method &>(method))(final_message);
- };
- handler.method = reinterpret_cast<GenericMethod &>(method);
- }
- char *backends_ptr;
- size_t backends_count;
- int backend_idx;
- friend struct SupervisorBase;
- };
- /** \struct Actor
- * \brief convenient templated base class for user-defined actors*/
- template <size_t HandlersCount> struct Actor : ActorBase {
- static_assert(HandlersCount >= Actor::min_handlers_amount,
- "no enough handlers");
- Actor() : ActorBase(reinterpret_cast<char *>(&backends), HandlersCount) {}
- /** storage of message handlers */
- ActorBase::Handler backends[HandlersCount];
- };
- } // namespace rotor_light
|