diff --git a/docs/Messaging/message-router.md b/docs/Messaging/message-router.md index 7b2c14a2..9253f69e 100644 --- a/docs/Messaging/message-router.md +++ b/docs/Messaging/message-router.md @@ -3,7 +3,7 @@ title: message_router weight: 3 --- -A class that will automatically route incoming messages to specific handlers based on the message types declared in the template parameter list. Messages are passed to the receive member function which will static cast it to its real type and call the matching on_receive function in the derived class. A compilation error will occur if the matching on_receive does not exist. +A class that will automatically route incoming messages to specific handlers based on the message types declared in the template parameter list. Messages are passed to the receive member function which will static cast it to its real type and call the matching `on_receive` function in the derived class. A compilation error will occur if the matching `on_receive` does not exist. The `on_receive` functions are not virtual. The template base class uses `CRTP` to directly call the derived class's functions. @@ -26,25 +26,25 @@ Note: A message router 'group' is deemed to be a set of routers with identical I The default router id is `etl::imessage_router::MESSAGE_ROUTER`. ### Scenario 1 -You never send a message to a router using it's ID. -You never use the ID to uniquely identify a router. -You do not require the router ID to act as a priority level when subscribing to a message bus. -You do not require that messages can be sent to a group. +- You never send a message to a router using it's ID. +- You never use the ID to uniquely identify a router. +- You do not require the router ID to act as a priority level when subscribing to a message bus. +- You do not require that messages can be sent to a group. All message router IDs can be identical. ### Scenario 2 -You never use the ID to uniquely identify a router. -You may use the router ID to send to a particular router group. -You require the router ID to act as a priority level when subscribing to a message bus. +- You never use the ID to uniquely identify a router. +- You may use the router ID to send to a particular router group. +- You require the router ID to act as a priority level when subscribing to a message bus. Router IDs will be assigned in groups. i.e. Some routers may share IDs. ### Scenario 3 -You use the ID to uniquely identify a router. -You use the router ID to send to a particular router. -You require the router ID to act as a priority level when subscribing to a message bus. -You require all priority levels to be unique. +- You use the ID to uniquely identify a router. +- You use the router ID to send to a particular router. +- You require the router ID to act as a priority level when subscribing to a message bus. +- You require all priority levels to be unique. All router IDs are unique @@ -117,7 +117,7 @@ Returns the router id. ```cpp virtual bool is_null_router() const = 0; ``` -Returns `true` if the router is a null message router, otherwise `false`. +Returns `true` if the router is a null message router, otherwise `false`. **Deprecated. ** --- @@ -154,7 +154,7 @@ MAX_MESSAGE_ROUTER ## message_router User defined message routers are derived from this class. -Derived from `## imessage_router`. +Derived from `imessage_router`. --- @@ -168,11 +168,12 @@ template ` provides typed messages with static IDs. Type traits (`is_message`, `is_message_type`, etc.) and ID comparison utilities support compile-time validation. +- `message_router.h` + Defines `etl::imessage_router`, the central routing interface with receive, accepts, and router identity. Provides message_router that statically dispatches by message ID (contiguous IDs optimized). Includes null_message_router and message_producer helpers, plus send_message utilities. +- `message_bus.h` +Implements `etl::imessage_bus`, a router that manages a sorted list of subscribed routers and forwards messages based on destination ID (broadcast or addressed). Supports subscription limits and forwards to successors. +- `message_broker.h` + Implements etl::message_broker, a router with explicit subscription objects mapping routers to message ID lists (span). It routes only to subscribers that match both message ID and destination ID, then forwards to any successor. +- `message_packet.h` + A type-erased, in-place container for a fixed set of message types. Validates message ID acceptance, supports copy/move, and exposes `get()` as `etl::imessage&`. Uses aligned storage sized to the largest message type. +- `shared_message.h` + Reference-counted wrapper around pooled messages (`ireference_counted_message_pool`). Supports copy/move semantics with automatic release when the last reference drops. + +## Basic architecture + +![message-framework](images/message-framework.png) diff --git a/docs/messaging/images/message-framework.png b/docs/messaging/images/message-framework.png new file mode 100644 index 00000000..a4b8a424 Binary files /dev/null and b/docs/messaging/images/message-framework.png differ diff --git a/docs/messaging/message-broker.md b/docs/messaging/message-broker.md new file mode 100644 index 00000000..ffc5df6d --- /dev/null +++ b/docs/messaging/message-broker.md @@ -0,0 +1,217 @@ +--- +title: "message_broker" +weight: 6 +--- + +{{< callout type="info">}} + Header: `message_broker.h` +{{< /callout >}} + +Message Broker + +A variant of the observer pattern in that message routers and derived types are be able to subscribe to selected sets of messages. The message_broker is similar to the message_bus, but it provides more control over the routing of messages. While the message_bus simply broadcasts every message to all subscribers, the message_broker allows you to specify which subscribers should receive each message. + +Derived from `imessage_router`. + +## Types + +```cpp +message_id_span_t etl::span +``` + +## subscription +A nested class of `etl::message_broker`. +The base for broker subscription information. +Derive from this to define your subscription class. +See Example. + +--- + +```cpp +subscription(etl::imessage_router& router) +``` +Constructor. + +--- + +```cpp +virtual message_id_span_t message_id_list() const = 0; +``` +Override this to return a span of message ids. + +## message_broker + +```cpp +message_broker() +``` +The broker is constructed with an id of `etl::imessage_router::MESSAGE_BROKER`. + +--- + +```cpp +message_broker(etl::imessage_router& successor) +``` +The broker is constructed with an id of `etl::imessage_router::MESSAGE_BROKER`. +Sets the successor. + +--- + +```cpp +message_broker(etl::message_router_id_t id) +``` +The broker is constructed with the specified id. + +--- + +```cpp +message_broker(etl::message_router_id_t id, etl::imessage_router& successor) +``` +The broker is constructed with the specified id. +Sets the successor. + +--- + +```cpp +void subscribe(etl::imessage_router& router) +``` +Subscribes an `etl::imessage_router` derived class to the broker. +A subscription object must have a lifetime of at least the same as the broker. +A subscription cannot be shared with another broker. + +--- + +```cpp +void unsubscribe(etl::imessage_router& router) +``` +Unsubscribes the specified `etl::imessage_router` derived class from the bus. +Does not unsubscribe from nested buses. + +--- + +```cpp +void receive(const etl::imessage& message) +void receive(etl::shared_message message) +``` +Receives a message and distributes it to all subscribers that have registered to receive the message type. +Forwards the message to any successor. + +Override this in a derived class if you wish to capture messages sent to the broker. +Call the base receive function from here to allow normal operation to continue. + +--- + +```cpp +bool accepts(etl::message_id_t id) const +``` +Always returns `true`. + +--- + +```cpp +void clear() +``` +Clears the broker of all subscribers. + +--- + +```cpp +ETL_DEPRECATED bool is_null_router() const ETL_OVERRIDE +``` +Always returns `false`. + +--- + +```cpp +bool is_producer() const ETL_OVERRIDE +``` +Always returns `true`. + +--- + +```cpp +bool is_consumer() const ETL_OVERRIDE +``` +Always returns `true`. + +--- + +```cpp +bool empty() const +``` +Returns `true` is the are are no subscribers. + +## Example +```cpp +// Some router ids. +enum +{ + ROUTER_ID_1, + ROUTER_ID_2, +}; + +// Custom subscription type. +class Subscription : public etl::message_broker::subscription +{ +public: + + Subscription(etl::imessage_router& router, std::initializer_list id_list_) + : etl::message_broker::subscription(router) + , id_list(id_list_) + { + } + + etl::message_broker::message_id_span_t message_id_list() const override + { + return etl::message_broker::message_id_span_t(id_list.begin(), id_list.end()); + } + + std::vector id_list; +}; + +// Instances of messages. +Message1 message1; +Message2 message2; +Message3 message3; +Message4 message4; + +// Custom broker. +class Broker : public etl::message_broker +{ +public: + + using etl::message_broker::receive; + + // Hook incoming messages and translate Message4 to Message3. + void receive(const etl::imessage& msg) override + { + if (msg.get_message_id() == Message4::ID) + { + etl::message_broker::receive(Message3()); + } + else + { + etl::message_broker::receive(msg); + } + } +}; + +// Instances of message routers. +Router1 router1; +Router2 router2; + +// The subscriptions. +Subscription subscription1{ router1, { Message1::ID, Message2::ID } }; +Subscription subscription2{ router2, { Message2::ID, Message3::ID } }; + +// Instance of message broker. +etl::message_broker broker; + +// Subscribe router1 and router1 to the broker. +broker.subscribe(subscription1); +broker.subscribe(subscription2); + +broker.receive(message1); // Received by router1 +broker.receive(message2); // Received by router1 and router2 +broker.receive(message3); // Received by router2 +broker.receive(message4); // Received by router2 as a Message3 +``` diff --git a/docs/messaging/message-bus.md b/docs/messaging/message-bus.md new file mode 100644 index 00000000..921e5b86 --- /dev/null +++ b/docs/messaging/message-bus.md @@ -0,0 +1,207 @@ +--- +title: "message_bus" +weight: 5 +--- + +{{< callout type="info">}} + Header: `message_broker.h` + From: `20.33.0` +{{< /callout >}} + +Message Bus + +This page documents version `20.0.0` and above. + +A variant of the observer pattern in that message routers and derived types are be able to subscribe to messages on a bus. The messages can be either broadcast, to be automatically picked up by any router that has a handler, or addressed to a particular router or router id. Message buses may be nested by setting a successor. + +## imessage_bus +Derived from imessage_router. + +The base for all message buses. +Inherits publicly from `etl::imessage_router`. +Message buses are therefore also a type of router. +Objects of type `etl::imessage_bus` cannot be directly constructed. + +## Member functions + +```cpp +bool subscribe(etl::imessage_router& router) +``` +Subscribes an `etl::imessage_router` derived class to the bus. +Returns `true` on success. + +--- + +```cpp +void unsubscribe(etl::imessage_router& router) +``` +Unsubscribes the specified `etl::imessage_router` derived class from the bus. +Does not unsubscribe from nested buses. + +--- + +```cpp +void unsubscribe(etl::message_router_id_t id) +``` +Unsubscribes routers with the specified id from the bus. +Does not unsubscribe from nested buses. + +`etl::imessage::MESSAGE_BUS` will unsubscribe all message buses. +`etl::imessage::ALL_MESSAGE_ROUTERS` will unsubscribe all routers and buses. Equivalent to calling `clear()`. + +--- + +```cpp +void receive(const etl::imessage& message) +void receive(etl::shared_message message) +``` +Receives a message and distributes it to all subscribers. +Forwards the message to any successor. + +The routers are called first, in order of ascending router id. +Routers with the duplicate ids will be called in subscribe order. +Any nested message buses are called in subscribe order. + +--- + +```cpp +void receive(etl::message_router_id_t destination_router_id, + const etl::imessage& message); +void receive(etl::message_router_id_t destination_router_id, + etl::shared_message message) +``` +Receives a message and distributes it to all subscribers that have the specified router id. +Forwards the message to any successor. + +Routers with the duplicate ids will be called in subscribe order. +Any nested message buses are called in subscribe order. + +Override this in a derived class if you wish to capture messages sent to the bus. +Call the base receive function from here to allow normal operation to continue. + +--- + +```cpp +bool accepts(etl::message_id_t id) const +``` +Always returns `true`. + +--- + +```cpp +size_t size() const +``` +Returns the number of subscribers. + +--- + +```cpp +void clear() +``` +Clears the bus of all subscribers. + +Message buses inherit all of the public functions of `etl::imessage_router`. + +## Errors + +```cpp +message_bus_exception +``` +Base error class for `etl::message_bus`. Inherits from `etl::exception`. + +--- + +```cpp +message_bus_too_many_subscribers +``` +Emitted when the number of subscribers exceeds the capacity. Inherits from `etl::message_bus_exception`. + +## message_bus +Derived from `imessage_bus`. + +```cpp +template +class message_bus +``` +`MAX_ROUTERS` The maximum number of routers that can be subscribed. + +## Member functions + +```cpp +message_bus() +``` +Constructs a message bus. +Message buses always have a router id of `etl::imessage::MESSAGE_BUS`. + +```cpp +message_bus(etl::imessage_router& successor) +``` +Constructs a message bus and sets the successor. +Message buses always have a router id of `etl::imessage::MESSAGE_BUS`. + +## Example +```cpp +// Some router ids. +enum +{ + ROUTER_ID_1, + ROUTER_ID_2, + ROUTER_ID_3 +}; + +// Instances of messages. +MessageA messageA; + +// Instances of message routers. +RouterA routerA(ROUTER_ID_1); +RouterB routerB(ROUTER_ID_1); +RouterC routerC(ROUTER_ID_2); +RouterD routerD(ROUTER_ID_3); +RouterE routerE(ROUTER_ID_1); + +// Instances of message buses. +etl::message_bus<4> bus1; +etl::message_bus<2> bus2; +etl::message_bus<1> bus3; + +// Subscribe bus2 & bus3 to bus1. +bus1.subscribe(bus3); +bus1.subscribe(bus2); + +// Subscribe routerB & routerA to bus1. +bus1.subscribe(routerB); +bus1.subscribe(routerA); + +// Subscribe routerD & routerC to bus2. +bus2.subscribe(routerD); +bus2.subscribe(routerC); + +// Subscribe routerE to bus3. +bus3.subscibe(routerE); + +// Assume all routers accept the same messages. + +// Broadcast messageA to everyone. +bus1.receive(messageA); + +// The call order will be... +// routerB +// routerA +// routerE +// routerC +// routerD + +// Address messageA to routers with id ROUTER_ID_1. +bus1.receive(ROUTER_ID_1, messageA); + +// The call order will be... +// routerB +// routerA +// routerE + +// Address messageA to routers with id ROUTER_ID_3. +bus1.receive(ROUTER_ID_3, messageA); + +// The call order will be... +// routerD +``` diff --git a/docs/messaging/message-packet.md b/docs/messaging/message-packet.md new file mode 100644 index 00000000..d027aaed --- /dev/null +++ b/docs/messaging/message-packet.md @@ -0,0 +1,155 @@ +--- +title: "message_packet" +--- + +{{< callout type="info">}} + Header: `message_packet.h` +{{< /callout >}} + +A container for more than one type of message. +The messages must have been derived from `etl::imessage`. + +The messages types that the packet may contain are listed as template parameters. +e.g. `etl::message_packet` + +From `20.40.0` message types are not required to have a virtual destructor. + +--- + +```cpp +message_packet() +``` +Default constructs a message packet. +`is_valid()` returns `false`. + +--- + +```cpp +explicit message_packet(const etl::imessage&) +``` +Constructs a message packet from an `etl::imessage` reference. +Asserts an `etl::unhandled_message_exception` error if the parameter is not one listed in the template parameter list. + +--- + +```cpp +explicit message_packet(etl::imessage&&) C++11 +``` +Move constructs a message packet from an `etl::imessage` rvalue reference. +Emits an `etl::unhandled_message_exception` error if the parameter is not one listed in the template parameter list. + +--- + +```cpp +template +explicit message_packet(const TMessage&) +``` +Constructs a message packet from a `TMessage` reference. +Emits a compile time static assert if the parameter is not one listed in the template parameter list. +From: `20.22.0` + +--- + +```cpp +template +explicit message_packet(TMessage&&) +``` +Move constructs a message packet from a `TMessage` rvalue reference. +Emits a compile time static assert if the parameter is not one listed in the template parameter list. +From: `20.22.0` + +--- + +```cpp +message_packet(const message_packet&) +``` +Constructs a message packet from an `message_packet` reference. +Emits an `etl::unhandled_message_exception` error if the parameter is not one listed in the template parameter list. + +--- + +```cpp +message_packet(message_packet&&) +``` +Move constructs a message packet from an `message_packet` rvalue reference. +Emits an `etl::unhandled_message_exception` if the parameter is not one listed in the template parameter list. + +--- + +```cpp +message_packet& operator =(const message_packet&) +``` +Assigns a message packet from an `message_packet` reference. +Emits an `etl::unhandled_message_exception` error if the parameter is not one listed in the template parameter list. + +--- + +```cpp +message_packet& operator =(message_packet&&) +``` +Move assigns a message packet from an `message_packet` rvalue reference. +Emits an `etl::unhandled_message_exception` error if the parameter is not one listed in the template parameter list. + +--- + +```cpp +etl::imessage& get() +const etl::imessage& get() const +``` +Returns a reference to an `etl::imessage`. + +--- + +```cpp +bool is_valid() const +``` +Returns `true` if the packet contains a valid message, otherwise `false`. + +--- + +## Constants + +`SIZE` The size of the largest message. +`ALIGNMENT` The largest message alignment. + +--- + +## Example +```cpp +enum +{ + MESSAGE1, + MESSAGE2, + MESSAGE3 +}; + +struct Message1 : public etl::message +{ +}; + +struct Message2 : public etl::message +{ +}; + +struct Message3 : public etl::message +{ +}; + +using Packet = etl::message_packet + +Message1 message1; +Message2 message2; +Message3 message3; + +Packet packet1(message1); +Packet packet2(message2); +Packet packet3(message3); // Runtime time error! Packet does not support Message3 type. + +etl::imessage& m1 = message1; +Packet packet4(m1); // Construct from an etl::imessage reference. + +etl::imessage& m3 = message3; +Packet packet4(m3); // Asserts etl::unhandled_message_exception! Packet does not support Message3 type. + +etl::imessage& m = packet2.get(); // Get a reference to an etl::imessage from the packet. +``` diff --git a/docs/messaging/message-router-registry.md b/docs/messaging/message-router-registry.md new file mode 100644 index 00000000..cfd21d1d --- /dev/null +++ b/docs/messaging/message-router-registry.md @@ -0,0 +1,180 @@ +--- +title: "message_router_registry" +weight: 4 +--- + +{{< callout type="info">}} + Header: `message_router_registry` + From: `20.6.0` +{{< /callout >}} + +A class that will act as a registry for all message router types. +When iterating through the registry, routers with identical IDs are ordered by insertion. + +**Defines the following classes** +```cpp +etl::imessage_router_registry +etl::message_router_registry +``` + +## imessage_router_registry + +The base class for all router registries. + +### Iterators +```cpp +iterator +const_iterator +``` + +### Member functions + +```cpp +iterator begin() +const_iterator begin() const +const_iterator cbegin() const +``` +Get the beginning of the registry. + +--- + +```cpp +iterator end() +const_iterator end() const +const_iterator cend() const +``` +Get the end of the registry. + +--- + +```cpp +iterator lower_bound(etl::message_router_id_t id) +const_iterator lower_bound(etl::message_router_id_t id) const +``` +Get the lower bound in the registry of the router with the specified ID. + +--- + +```cpp +iterator upper_bound(etl::message_router_id_t id) +const_iterator upper_bound(etl::message_router_id_t id) const +``` +Get the upper bound in the registry of the router with the specified ID. + +--- + +```cpp +etl::imessage_router* find(etl::message_router_id_t id) +const etl::imessage_router* find(etl::message_router_id_t id) const +``` +Returns a pointer to the first router with the specified ID, or `ETL_NULLPTR` if it cannot be found. + +--- + +```cpp +void add(etl::imessage_router& router) +void add(etl::imessage_router* p_router) +``` +Registers a router. +If the registry is full then an ETL assert is emitted (`etl::message_router_registry_full`). +Duplicate routers will be ignored. + +--- + +```cpp +template +void add(TIterator first, const TIterator& last) +``` +Registers a collection of routers. +If the registry becomes full then an ETL assert is emitted (`etl::message_router_registry_full`). +Duplicate routers will be ignored. + +--- + +```cpp +void remove(etl::message_router_id_t id) +``` +Unregisters a router. + +--- + +```cpp +bool contains(const etl::message_router_id_t id) const +bool contains(const etl::imessage_router* const p_router) const +bool contains(const etl::imessage_router& router) const +``` +Returns `true` if the registry contains a router that has the specified ID or object. + +--- + +```cpp +bool empty() const +``` +Returns `true` if the registry is empty, otherwise `false`. + +--- + +```cpp +bool full() const +``` +Returns `true` if the registry is full, otherwise `false`. + +--- + +```cpp +size_t size() const +``` +Returns the size of the registry. + +--- + +```cpp +size_t available() const +``` +Returns the available size of the registry. + +--- + +```cpp +size_t max_size() const +``` +Returns the maximum size of the registry. + +--- + +## message_router_registry + +```cpp +message_router_registry() +``` +Default constructor. + +--- + +```cpp +template +message_router_registry(TIterator first, const TIterator& last) +``` +Constructs from an iterator range. + +--- + +```cpp +message_router_registry(std::initializer_list init) +``` +Initializer_list constructor. +Enabled for C++11 or above. + +--- + +```cpp +message_router_registry(const message_router_registry& rhs) +``` +Copy constructor. + +--- + +```cpp +message_router_registry& operator =(const message_router_registry& rhs) +``` +Assignment operator. diff --git a/docs/messaging/message-router.md b/docs/messaging/message-router.md new file mode 100644 index 00000000..9253f69e --- /dev/null +++ b/docs/messaging/message-router.md @@ -0,0 +1,504 @@ +--- +title: message_router +weight: 3 +--- + +A class that will automatically route incoming messages to specific handlers based on the message types declared in the template parameter list. Messages are passed to the receive member function which will static cast it to its real type and call the matching `on_receive` function in the derived class. A compilation error will occur if the matching `on_receive` does not exist. + +The `on_receive` functions are not virtual. The template base class uses `CRTP` to directly call the derived class's functions. + +**Defines the following classes** +```cpp +etl::imessage_router +etl::message_router +etl::null_message_router +``` + +Note: This C++03 portion of this header is a generated from `message_router_generator.h`. To handle more than the standard 16 message types then a new one must be generated. +See [Generators](./generators-tutorial) + +## Message router ID +Allowable router IDs run from `0` to `MAX_MESSAGE_ROUTER` (`249`) inclusive. + +Each message router is given an ID. Whether this ID is unique or not depends on how you are using and identifying message routers. + +Note: A message router 'group' is deemed to be a set of routers with identical IDs. +The default router id is `etl::imessage_router::MESSAGE_ROUTER`. + +### Scenario 1 +- You never send a message to a router using it's ID. +- You never use the ID to uniquely identify a router. +- You do not require the router ID to act as a priority level when subscribing to a message bus. +- You do not require that messages can be sent to a group. + +All message router IDs can be identical. + +### Scenario 2 +- You never use the ID to uniquely identify a router. +- You may use the router ID to send to a particular router group. +- You require the router ID to act as a priority level when subscribing to a message bus. + +Router IDs will be assigned in groups. i.e. Some routers may share IDs. + +### Scenario 3 +- You use the ID to uniquely identify a router. +- You use the router ID to send to a particular router. +- You require the router ID to act as a priority level when subscribing to a message bus. +- You require all priority levels to be unique. + +All router IDs are unique + +## imessage_router +The base class for all routers. + +#### Member functions +```cpp +virtual ~imessage_router() {} +``` + +--- + +```cpp +virtual void receive(const etl::imessage& message) = 0; +``` +Overridden by the derived class. + +--- + +```cpp +virtual void receive(etl::message_router_id_t destination_router_id, + const etl::imessage& message) +``` +Receive a message for a specific destination id. +If the destination id is the router's id, then the message is forwarded to `receive(message)`. +Can be overridden by the derived class. + +--- + +```cpp +virtual void receive(etl::shared_message shared_msg) +``` +Receive a shared message. +By default, forwards to `receive(shared_msg.get_message())`. + +--- + +```cpp +virtual void receive(etl::message_router_id_t destination_router_id, + etl::shared_message shared_msg) +``` +Receive a message for a specific destination id. +By default, forwards to `receive(destination_router_id, shared_msg.get_message())`. + +--- + +```cpp +virtual bool accepts(etl::message_id_t id) const = 0; +``` +Returns `true` if the router accepts the message id. +Overridden by the derived class. + +--- + +```cpp +bool accepts(const etl::imessage& msg) const; +``` +Returns `true` if the router accepts the message. + +--- + +```cpp +etl::message_router_id_t get_message_router_id() const; +``` +Returns the router id. + +--- + +```cpp +virtual bool is_null_router() const = 0; +``` +Returns `true` if the router is a null message router, otherwise `false`. +**Deprecated. ** + +--- + +```cpp +virtual bool is_producer() const = 0; +``` +Returns `true` if the router is a producer of messages, otherwise `false`. + +--- + +```cpp +virtual bool is_consumer() const = 0; +``` +Returns `true` if the router is a consumer of messages, otherwise `false`. + +--- + +```cpp +void set_successor(etl::imessage_router& successor); +``` +Sets the successor router. Any unhandled message will be sent here. +Allows the router to implement the Chain Of Responsibility design pattern. + +## Enumerations +```cpp +NULL_MESSAGE_ROUTER +MESSAGE_BUS +ALL_MESSAGE_ROUTERS +MAX_MESSAGE_ROUTER +``` + +--- + +## message_router +User defined message routers are derived from this class. +Derived from `imessage_router`. + +--- + +### For C++03 +```cpp +template +class message_router +``` +**Template parameters** +`TDerived` The derived class. +`T1` The first message type. +`T2...` The additional message types. + +The maximum number of types can be set by running the generator for this file. +The default is 16.This may be changed by running a modified version of generate_message_router.bat. +See [Generators](../tutorials/generators-tutorial) + +--- + +### For C++11 and above +```cpp +template +class message_router +``` +**Template parameters** +`TDerived` The derived class. +`TMessageTypes` The list of message types. + +**Before: 20.47.0** +This definition will automatically be selected if the compiler supports C++17 or above. It uses fold expressions to resolve `on_receive()` calls. +To use the older C++03 compatible definition, define `ETL_MESSAGE_ROUTER_FORCE_CPP03` as a project setting or in the optional `etl_profile.h`. + +**Since: 20.47.0** +This definition will automatically be selected if the compiler supports C++11 or above. It uses either an O(1) or O(logN) mechanism to resolve `on_receive()` calls. +To use the older C++03 compatible definition, define `ETL_MESSAGE_ROUTER_FORCE_CPP03` as a project setting or in the optional `etl_profile.h`. + +### The derived class must define the following member functions. + +```cpp +void on_receive(const Type& msg); +``` +Replace Type with each concrete message type. +Define for all of the template parameter types. + +--- + +```cpp +void on_receive_unknown(const etl::imessage& msg) +``` +Called when a message type is received that is not in the template list. + +## message_packet + +This is a nested member class. + +See [etl::message_packet](./message-packet) + +--- + +### Member functions +```cpp +message_router() +``` +Constructs the router. +The router id is set to `etl::imassage_router::MESSAGE_ROUTER`. + +--- + +```cpp +message_router(etl::imessage_router& successor) +``` +Constructs the router. +The router id must be between `0` and `MAX_MESSAGE_ROUTER` (`249`). Other IDs are reserved for ETL use. +Emits an error if the id is outside the legal range. +Routers may have duplicate ids. +Sets the successor router to this one. Any unhandled message will be sent here. + +--- + +```cpp +message_router(etl::message_router_id_t id) +``` +Constructs the router. +The router id must be between `0` and `249`. Ids `250` to `255` are reserved for ETL use. +Asserts an error if the id is outside the legal range. +Routers may have duplicate ids. + +--- + +```cpp +message_router(etl::message_router_id_t id, etl::imessage_router& successor) +``` +Constructs the router. +The router id must be between 0 and MAX_MESSAGE_ROUTER (249) . Other IDs are reserved for ETL use. +Sets the successor router to this one. Any unhandled message will be sent here. + +--- + +```cpp +void receive(const etl::imessage& msg) +``` +Receives a message and routes to the `on_receive()` handler. +If not handled, passes the message on to a successor, if it exists. + +--- + +```cpp +template +void receive(const TMessage& msg) +``` +Receives a message and routes directly to the `on_receive()` handler. +This template function will be chosen for concrete message types. +If not handled, passes the message on to a successor, if it exists. + +--- + +```cpp +bool accepts(etl::message_id_t id) const +``` +Returns `true` if the router accepts the message id. + +--- + +### Errors +```cpp +message_router_exception +``` +Base exception for router errors. +Derived from `etl::exception`. + +--- + +```cpp +message_router_illegal_id +``` +The router id is out of the legal range. +Derived from `etl::message_router_exception`. + +--- + +### null_message_router +This router can be used as a sink for messages or a 'null source' router. +Derived from `etl::imessage_router`. + +--- + +```cpp +null_message_router() +``` +Constructs a null message router. +The router id will be `etl::imessage_router::NULL_MESSAGE_ROUTER`. + +--- + +```cpp +void receive(const etl::imessage& message) +``` +Receives a message. +Does nothing, except pass on to a successor, if it exists. + +--- + +```cpp +bool accepts(etl::message_id_t id) const +``` +Returns `false` if no successor is set. +Returns `true` if a successor accepts the message id. + +--- + +```cpp +static null_message_router& instance() +``` +Returns an instance of `etl::null_message_router`. + +## message_producer +This router can be used as a producer-only of messages, such an interrupt routine. +Derived from `etl::imessage_router`. + +--- + +```cpp +message_producer(etl::message_router_id_t id); +``` +Constructs the router. +The router id must be between `0` and `249`. Ids `250` to `255` are reserved for ETL use. +Emits an error if the id is outside the legal range. +Routers may have duplicate ids. + +--- + +```cpp +void receive(const etl::imessage& message) +``` +Receives a message. +Does nothing, except pass on to a successor, if it exists. + +--- + +```cpp +bool accepts(etl::message_id_t id) const +``` +Returns `false` if no successor is set. +Returns `true` if a successor accepts the message id. + +--- + +```cpp +static null_message_router& instance() +``` +Returns an instance of `etl::null_message_router`. + +--- + +## Global functions + +```cpp +void send_message(etl::imessage_router& router, + const etl::imessage& message) +``` +Send the message to the router. + +--- + +```cpp +void send_message(etl::imessage_router& router, + etl::message_router_id_t id, + const etl::imessage& message) +``` +Send the message to the router if it has the specified id. + +--- + +```cpp +void send_message(etl::imessage_router& router, + etl::shared_message& message) +``` +Send the shared message to the router. + +--- + +```cpp +void send_message(etl::imessage_router& router, + etl::message_router_id_t id, + etl::shared_message& message) +``` +Send the shared message to the router if it has the specified id. + +## Example + +```cpp +// Message ids. +enum +{ + START, + STOP, + SET_SPEED +}; + +// The start message. +struct Start : public etl::message +{ +}; + +// The stop message. +struct Stop : public etl::message +{ +}; + +// The set speed message. +struct SetSpeed : public etl::message +{ + SetSpeed(uint32_t speed_) + : speed(speed_) + { + } + + uint32_t speed; +}; + +// The router. +class Router : public etl::message_router +{ +public: + + // Construct the router with an id of 0. + Router() + : message_router(0) + { + } + + // Received a start message. + void on_receive(etl::imessage_router& sender, const Start& msg) + { + std::cout << "Start message received\n"; + } + + // Received a stop message. + void on_receive(const Stop& msg) + { + std::cout << "Start message received\n"; + } + + // Received a set speed message. + void on_receive(const SetSpeed& msg) + { + std::cout << "SetSpeed(" << msg.speed << ") message received\n"; + } + + // Received an unknown message. + void on_receive_unknown(const etl::imessage& msg) + { + std::cout << "Unknown message " << msg.message_id << " received\n"; + } +} + +// Router and message instances. +Router router; +Start start; +Stop stop; +SetSpeed halfSpeed(50); +SetSpeed maxSpeed(100); + +// A queue for Router messages. +etl::queue queue; + +// Add to the queue. +queue.emplace(start); +queue.emplace(stop); +queue.emplace(maxSpeed); +queue.emplace(halfSpeed); + +// Send the queued messages to 'router'. +while (!queue.empty()) +{ + etl::send_message(router, queue.front().get()); + queue.pop(); +} +``` + +--- + +An example of a queued message router can be found in the repository in `examples/QueuedMessageRouter`. diff --git a/docs/messaging/message.md b/docs/messaging/message.md new file mode 100644 index 00000000..5f6efa95 --- /dev/null +++ b/docs/messaging/message.md @@ -0,0 +1,112 @@ +--- +title: "message" +weight: 1 +--- + +{{< callout type="info">}} + Header: `message.h` +{{< /callout >}} + +Message types used for many of the framework classes. + +--- + +```cpp +etl::message_id_t +``` +The type used for message ids. +By default can hold a value between 0 and 255. +If `ETL_MESSAGE_ID_TYPE` is defined then this type will be used instead. + +--- + +```cpp +etl::message_router_id_t +``` +The type used for message router ids. +Can hold a value between 0 and 255. + +--- + +The message classes are the common communication method across all of the message capable frameworks. +They are identified by a unique id number that specialises the base class. + +## imessage + +The base class for messages. +It is this class that is passed around, usually by const reference. +The class is abstract. + +--- + +```cpp +etl::message_id_t get_message_id() const ETL_NOEXCEPT = 0; +``` +Returns the id of the message. + +--- + +## message + +```cpp +message +``` +Requires an integral id as the template parameter. +Inherits from `TParent`, which defaults to `etl::imessage`. +`TParent` allows additional base interfaces or functionality to be included. +static asserts if `TParent` is not a base of `etl::imessage`. + +--- + +```cpp +ID +``` +The id of the message as an enum. +Can be accessed by `etl::message` instances. + +--- + +```cpp +TParent +``` +The class that it inherits from. It must ultimately derive from `etl::imessage`. +The default is `etl::imessage`. + +--- + +## Example + +```cpp +enum +{ + START, + STOP, + SET_SPEED +}; + +struct MyInterface : public etl::imessage +{ + virtual void DoStuff() = 0; +}; + +// Start implements MyIterface +struct Start : public etl::message +{ + void DoStuff() override + { + // Do stuff here. + } +}; + +struct Stop : public etl::message +{ + bool isEmergencyStop; +}; + +struct SetSpeed : public etl::message +{ + uint32_t speed; +}; + +void Receive(const etl::imessage& msg); +``` diff --git a/docs/messaging/reference-counted-message-pool.md b/docs/messaging/reference-counted-message-pool.md new file mode 100644 index 00000000..b2701cd0 --- /dev/null +++ b/docs/messaging/reference-counted-message-pool.md @@ -0,0 +1,139 @@ +--- +title: "reference_counted_message_pool" +--- + +{{< callout type="info">}} + Header: `reference_counted_message_pool.h` + Header: `ireference_counted_message_pool.h` +{{< /callout >}} + +Allocates `etl::ireference_counted_message` types that are used by `etl::shared_message`. +Uses a supplied `memory_block allocator` derived from `etl::imemory_block_allocator`. + +## ireference_counted_message_pool.h +Defines the following class. +`etl::ireference_counted_message_pool` + +## reference_counted_message_pool.h +Defines the following classes. +`etl::reference_counted_message_pool_exception` +`etl::reference_counted_message_pool_allocation_failure` +`etl::reference_counted_message_pool_release_failure` + + +## ireference_counted_message_pool + +```cpp +etl::ireference_counted_message_pool +``` + +The interface for reference counted message pools. +```cpp +virtual ~ireference_counted_message_pool() {} +``` +```cpp +virtual void release(const etl::ipool_message& msg) = 0; +``` + +Virtual `lock()` and `unlock()` functions are defined. The default action is to do nothing. +A derived class may override these functions to provide a thread or interrupt safe pool. +`virtual void lock()` +`virtual void unlock()` + +## reference_counted_message_pool + +```cpp +etl::reference_counted_message_pool +``` +The concrete reference counted message pool. + +```cpp +reference_counted_message_pool(imemory_block_allocator& memory_block_allocator) +``` +Constructs the pool and assigns the memory block allocator to it. + +--- + +```cpp +template +etl::reference_counted_message* allocate() +``` +Returns a pointer to a pool message that holds a `TMessage`. +The message is default constructed. +ETL_ASSERT if one cannot be allocated and returns `ETL_NULLPTR`. + +--- + +```cpp +template +etl::reference_counted_message* allocate(const TMessage& message) +``` +Returns a pointer to a pool message that holds a `TMessage`. +ETL_ASSERT if one cannot be allocated and returns `ETL_NULLPTR`. + +--- + +```cpp +void release(const etl::ireference_counted_message& rcmessage) +``` +Returns the reference counted to a pool message that holds a `TMessage`. +`ETL_ASSERT` if it cannot be released. Reasons can include the message not belonging to the pool. + +## pool_message_parameters + +**C++03** +The C++03 version defines the largest size and alignment of up to 8 types at a time. + +```cpp +template +struct pool_message_parameters +``` + +--- + +```cpp +static const size_t max_size; +``` +The maximum size. + +--- + +```cpp +static const size_t max_alignment; +``` +The maximum alignment. + +**C++11 or above** +The C++11 version defines the largest size and alignment of a set of message types. + +```cpp +template +struct pool_message_parameters +``` + +--- + +```cpp +static constexpr size_t max_size +``` +The maximum size. + +--- + +```cpp +static constexpr size_t max_alignment; +``` +The maximum alignment. + +--- + +**For C++11, with atomic support.** + +```cpp +template +using atomic_counted_message_pool = etl::reference_counted_message_pool; +``` +Defines an alias to a reference counted message pool that uses an atomic. diff --git a/docs/messaging/reference-counted-messages.md b/docs/messaging/reference-counted-messages.md new file mode 100644 index 00000000..8e8bd728 --- /dev/null +++ b/docs/messaging/reference-counted-messages.md @@ -0,0 +1,197 @@ +--- +title: "reference_counted_message" +--- + +{{< callout type="info">}} + Header: `reference_counted_message.h` +{{< /callout >}} + +Reference counted message types that are used by `etl::shared_message`. + +**Defines the following classes** + +```cpp +etl::ireference_counted_message +``` +The interface of all reference counted message types. + +--- + +```cpp +etl::reference_counted_message +``` +Derived from `etl::ireference_counted_message`. + +```cpp +etl::persistent_message +``` +Derived from `etl::ireference_counted_message`. + +## ireference_counted_message + +```cpp +etl::ireference_counted_message +``` +The interface of all reference counted message types. + +--- + +```cpp +virtual ~ireference_counted_message() +``` + +--- + +```cpp +ETL_NODISCARD virtual etl::imessage& get_message() = 0; +``` +Get a reference to the message. + +--- + +```cpp +ETL_NODISCARD virtual const etl::imessage& get_message() const = 0; +``` +Get a const reference to the message. + +--- + +```cpp +ETL_NODISCARD virtual etl::ireference_counter& get_reference_counter() = 0; +``` +Get a reference to the reference counter. + +--- + +```cpp +ETL_NODISCARD virtual const etl::ireference_counter& get_reference_counter() const = 0; +``` +Get a const reference to the reference counter. + +--- + +```cpp +virtual void release() = 0; +``` +Release back to the owner. + +## reference_counted_message + +```cpp +etl::reference_counted_message +``` +The implementation of reference counted messages owned by a pool. +Will static assert if TMessage is no derived from etl::imessage. + +--- + +```cpp +reference_counted_message(const TMessage& message, etl::ireference_counted_message_pool& owner) +``` +Constructs from a message and the pool from which the reference counted message is allocated. +The message is copied. + +--- + +```cpp +reference_counted_message(etl::ireference_counted_message_pool& owner) +``` +Constructs from a message and the pool from which the reference counted message is allocated. +The message is default constructed. + +--- + +```cpp +ETL_NODISCARD TMessage& get_message() ETL_OVERRIDE +``` +Get a reference to the message. + +--- + +```cpp +ETL_NODISCARD const TMessage& get_message() const ETL_OVERRIDE +``` +Get a const reference to the message. + +--- + +```cpp +ETL_NODISCARD etl::ireference_counter& get_reference_counter() ETL_OVERRIDE +``` +Get a reference to the reference counter. + +--- + +```cpp +ETL_NODISCARD const etl::ireference_counter& get_reference_counter() const ETL_OVERRIDE +``` +Get a const reference to the reference counter. + +--- + +```cpp +void release() ETL_OVERRIDE +``` +Release back to the owner pool. + +## persistent_message + +```cpp +etl::persistent_message +``` + +The implementation of reference counted messages not owned by a pool. +It's counter type is `void`. +Will static assert if `TMessage` is not derived from `etl::imessage`. + +--- + +```cpp +persistent_message(const TMessage& message) +``` +Constructs from a message. +The message is copied. + +--- + +```cpp +ETL_NODISCARD TMessage& get_message() ETL_OVERRIDE +``` +Get a reference to the message. + +--- + +```cpp +ETL_NODISCARD const TMessage& get_message() const ETL_OVERRIDE +``` +Get a const reference to the message. + +--- + +```cpp +ETL_NODISCARD etl::ireference_counter& get_reference_counter() ETL_OVERRIDE +``` +Get a reference to the reference counter. + +--- + +```cpp +ETL_NODISCARD const etl::ireference_counter& get_reference_counter() const ETL_OVERRIDE +``` +Get a const reference to the reference counter. + +--- + +```cpp +void release() ETL_OVERRIDE +``` +Does nothing for a persistent message. + +## For C++11, with atomic support. + +```cpp +template +using atomic_counted_message = etl::reference_counted_message; +``` +Defines an alias to a reference counted message that uses an atomic. + diff --git a/docs/messaging/shared-message.md b/docs/messaging/shared-message.md new file mode 100644 index 00000000..79ea8a74 --- /dev/null +++ b/docs/messaging/shared-message.md @@ -0,0 +1,79 @@ +--- +title: "shared_message" +weight: 2 +--- + +{{< callout type="info">}} + Header: `shared_message.h` +{{< /callout >}} + +Shared Messages + +The type used to encapsulate reference counted messages. +Shared messages are usually passed by value. + +See the Shared Message Tutorial + +--- + +```cpp +template +shared_message(TPool& owner, const TMessage& message) +``` +Static asserts if `TPool` is not derived from `etl::ireference_counted_message_pool` +Static asserts if `TMessage` not derived from `etl::imessage`. + +Requires that `TPool` implements the following member function to allocate from the pool. + +```cpp +template +etl::ireference_counted_message* allocate(const TMessage& message) +``` +--- + +```cpp +explicit shared_message(etl::ireference_coutnted_message& message) +``` +Construct from a reference counted message. + +--- + +```cpp +shared_message(const etl::shared_message& other) +``` +Copy constructor. + +--- + +```cpp +shared_message& operator =(const etl::shared_message& other) +``` +Assignment operator. + +--- + +```cpp +~shared_message() +``` +Destructor. + +--- + +```cpp +ETL_NODISCARD etl::imessage& get_message() +``` +Gets a reference to the contained message. + +--- + +```cpp +ETL_NODISCARD const etl::imessage& get_message() const +``` +Gets a const reference to the contained message. + +--- + +```cpp +ETL_NODISCARD uint32_t get_reference_count() const +``` +Gets the current reference count for this shared message. diff --git a/docs/state machines/finite-state-machine.md b/docs/state machines/finite-state-machine.md index ca238ed4..0977433e 100644 --- a/docs/state machines/finite-state-machine.md +++ b/docs/state machines/finite-state-machine.md @@ -10,7 +10,7 @@ This page documents version `20.0.0` and above. A finite state machine driven by the reception of events (messages) . The incoming messages will be automatically routed to specific handlers based on the message list defined in the template parameters. Optional on_entry and on_exit handlers are available. -This FSM is slightly more involved to set up than the traditional simple table driven method, but provides great flexibility in implementation. It may also be faster due to the fact that all messages are routed with either O(1) or O(N) rather than scanning a lookup table and calling indirectly through function pointers. +This FSM is slightly more involved to set up than the traditional simple table driven method, but provides great flexibility in implementation. It may also be faster due to the fact that all messages are routed with either O(1) or O(logN) rather than scanning a lookup table and calling indirectly through function pointers. The `on_event` functions are not virtual. The template class uses [CRTP](https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern) to directly call the derived class's functions.