# 4. Use ETL as the basis for the event system

Date: 2023-02-13

## Status

Accepted

## Context

The CPF system should be redesigned with an event-driven approach. 

## Decision

The ETL is already used as a dependency in the CPF project, so we will build our event infrastructure using existing ETL components. 

Wherever possible, we will provide alternative types. This will be done to simplify usage of the ETL framework components and make them easier to work with. This will also be used to enforce default values for the CPF system.

## Consequences

- Faster development of components, as the basic infrastructure does not have to be recreated
- Introduces a hard dependency on the ETL
	+ Some components, such as `etl::message` and `etl::message_packet`, are required.
	+ Some components, such as `etl::message_router` and `etl::fsm`, are soft dependencies. They could eventually be replaced by another method that consumes `etl::imessage&` inputs.
- The ETL message_router and fsm constructs rely heavily on templating. This may provide a barrier to use.

## Overview of Event Model Elements

This stuff should be moved into the appropriate headers

### Event Type

- based on [etl::message](https://www.etlcpp.com/messages.html)
- `event_base_types.hpp` supplies our definition for base types
- All events inherit from `EventBase`, which itself inherits from `etl::imessage`
- EventBase provides common functionality required for events in your system
- The framework defines one basic event: `CallableEvent`. This stores a `std::function` object matching the prototype `void(void)`. You can use it to serialize callbacks and other asynchronous operations into the primary event queue
- You can get the message id from imessage type with: `msg.get_message_id()`, though the docs say `.message_id`

#### Event IDs

- Event IDs should be specified as enums of type `event_id_t`. Valid ranges are [0,254] (255 is currently reserved)
- The core event framework reserves some number of event IDs for its own purpose in `ReservedEventIDs`.
	- Currently, this is only the `Callable` event.
	- User events must not stomp on reserved values. There is no check currently.

### Event Storage Element

- event_queue.hpp
- based on [etl::message_packet](https://www.etlcpp.com/message_packet.html)
- We need the ability to store _any kind of event_ in our event queue, not just a specific type. So we need to take the *largest* storage value of all events in our system and use that as the base element size for our event queue
- We have a `MakeEventStorageType` helper that takes the largest event type size and uses that as the basis for storage
- This type is intended for use with the event queue

```cpp
using CPFEventStorage_t = MakeEventStorageType<GPSPositionReadyEvent, GPSTimestampReadyEvent>;
```

Because some event types may be defined in headers other than the core event header, it is recommended to define this type in the application code where you initialize the event processor for your system. This should not be defined in the base event type header.

### Event Queue

- event_queue.hpp
- Needed to store events
- Storage type needs to be created with `MakeEventStorageType` described above
- Need to specify the queue type and the maximum number of elements. If 0 is specified as max (default), a dynamically sized queue will be used.
- std::queue is used for dynamic memory, etl::queue is used for static memory

### Interfacing with the Event System

There are a few different types for handling messages/events in the ETL
- [message router](https://www.etlcpp.com/message_router.html)
	- routes messages to specific handlers based on the types in the template parameter list
- [message bus](https://www.etlcpp.com/message_bus.html)
	- 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.
- [message_broker](https://www.etlcpp.com/message_broker.html)
	- Routers and derived types can subscribe to selected sets of messages
	- similar to message_bus, but with finer-grained control.
	- Broker will distribute a message to all subscribers that have the message in its subscription list.
- [message_router_registry](https://www.etlcpp.com/message_router_registry.html)
	- if you want a central registry of routers
- [fsm](https://www.etlcpp.com/fsm.html)

For our purposes, we will be using the FSM approach, as CPF system states can be modeled as FSMs, and actions as FSM states.

## ETL Notes

The following notes were kept when implementing the event system on top of the ETL. They are preserved here for posterity. Some information is duplicated from above.

### [etl::message](https://www.etlcpp.com/messages.html)

- All messages are ultimately derived from `etl::imessage`. But they are usually are directly derived from the template  `etl::message<const etl::message_id_t ID>`.
- Each message must have an ID that is at least unique to the receiver, though normally IDs would be unique across the application.
- Messages are normally passed around as `const` references to the `etl::imessage` base type
- By default the message id is a `uint_least8_t`. This may be changed by defining `ETL_MESSAGE_ID_TYPE` as the required type.
- You can get the message id from `imessage` type with: `msg.get_message_id()`, though the docs say `.message_id`

```cpp
struct Message1 : public etl::message<1>
{

};

  

struct Message2 : public etl::message<2>
{

};
```

### [etl::message_packet](https://www.etlcpp.com/message_packet.html)

- Used as a `variant`, essentially, to store one of the specified types of messages
- data storage for a packet is the max size/alignment of the largest type in that packet. 
	+ So that means we want to avoid LARGE DATA PAYLOADS, otherwise we're increasing queue requirements significantly.
- - How do we manage the need to have dynamic memory w.r.t. reference validity?
	- Seems like a queue would be useful here, we could make references from the queue, pop when done?
	- Looks like the above suggestion is how it is handled in ETL examples.

### [etl::message_router](https://www.etlcpp.com/message_router.html)

- Messages routers are defined to handle a specified set of messages. Defining a message router will mandate that a specific set of on_receive functions are defined that correspond to the events used in the declaration, along with an 'unknown' handler.
	+ Failure to define the full set of handler functions will result in a compilation error.
- Message routers may either handle the message directly or implement message queues to defer handling. 
- `void set_successor(etl::imessage_router& successor);` could be used from a component level to set the top-level state machine router and enable chain of responsibility?
- You can send messages to a router by invoking their `receive` function. You can also globally send messages to router with `etl::send_message`:
```cpp
void send_message(etl::imessage_router& router,
                  const etl::imessage&  message);
```

### [etl::message_bus](https://www.etlcpp.com/message_bus.html)

- Message buses are a type of message router.
- Message buses distribute messages to other message recipients. They do not process any messages.
- Other message recipients (those classes derived from `etl::imessage_router`) may subscribe to the bus.
- Messages sent to the bus may either be broadcast or directed only to routers with a specified id.

### [etl::fsm](https://www.etlcpp.com/fsm.html)

- FSMs are also a type of message router.
- Messages sent to FSMs are handled in `on_event` handlers in classes derived from `etl::fsm_state`.
- The states are defined to handle a specified set of messages. Failure to define the full set of event functions will result in a compile error. Messages sent to states that have not been define to handle them will call an 'Unknown' handler.
- The FSM class uses the [state pattern](https://en.wikipedia.org/wiki/State_pattern) to change message handlers on state changes.
- By default the state id is a `uint_least8_t`. This may be changed by defining `ETL_STATE_ID_TYPE` as the required type.
