# Intoduction

Modeling your applications with [actors](https://en.wikipedia.org/wiki/Actor_model),
you can get two benefits: **messaging** and **supervising** between *actors*.

Actor is a reactive entity with a state, which can receive and send messages; actor
state is usually changed with new messages. Every **message** has destination (one or
more actors) and user-defined payload. Messaging is send with *fire-and-forget*
approach, i.e. sender does not care about further delivery and processing of the
message, like with UDP protocol.

Like in UDP a message can be "lost", because the destination queue if full.
Unlike UDP, this can be checked, howerver.

The message delivery is *asynchronous*, as the message is put into the queue, and
will be delivered some time later.

With all that properties it is possible to assemble [concurrent](https://en.wikipedia.org/wiki/Concurrency_(computer_science)) programs.

**Supervising** is more about proper actor initialization order, synchronization and
failure handling. *Synchronization* makes it sure that starting sending messages to
actors is performed when actors are ready. *Failure handling* allows to use different
restart strategies of a failing actor, or group of actors, or even escalate locally
unresolvable problem into upper layers... upto the whole board restart.

`rotor-light` is platform-neutral framework, i.e. it can be build and run on any
plaform, which conforms the requirements (C++17, basically).

# rotor and rotor-light

There is a successful C++ actor microframework [rotor](github.com/basiliscos/cpp-rotor/), 
when a few colleagues of mine asked me to have something similar for embedded systems.

The following table highlights the differences

|                              |      rotor light        |  rotor
|:----------------------------:|:-----------------------:|:----------------------:
| max. number of actors        | 64                      | unlimited, runtime
| max. number of messages      | compile-time defined    | unlimited, runtime
| thread-safety                | no                      | yes
| message priorities           | yes                     | no
| request-response pattern     | no                      | yes
| actors discovery & linking   | no                      | yes
| multiple I/O backends        | no (1)                  | yes
| timers                       | yes (1)                 | yes
| non-intrusiveness (2)        | yes                     | yes
| dynamic allocations          | no                      | yes
| C++ standard                 | C++17                   | C++17
| dependencies                 | no (except, stdlib++)   | [boost](https://www.boost.org/), event-loops
| multiple addresses per actor | no                      | yes
| multiple recipients          | yes, via broadcasting   | yes
| uses RTTI                    | no                      | yes


(1) `rotor-light` needs only "now" function from timer, the time point and
time interval are abstract notions, have to be defined by end-user of the library.

(2) Non-intrusiveness means, that a framework does not owns execution thread: when
all messages have been processed, it just exits as there is nothing to do. That way
it is possible to integrate a framework with other event-loops or enter into
power-save mode for MCU.

There are a lot of useful patterns and explanations in
[rotor](github.com/basiliscos/cpp-rotor/) [documentation](https://basiliscos.github.io/cpp-rotor-docs/index.html),
it is worth familiarize self with it to get some insights.

# building

`rotor-light` uses [cmake](https://cmake.org/) build system. It has no dependencies,
except C++17 core libraries  (e.g. `<tuple>`,`<type_traits>` etc.). Basically, the
framework can be build via


```
mkdir build
cd build
cmake ..
make -j4
```

There are few customization options:

 - `ROTOR_LIGHT_FW_QUEUE` - the queue/priority to be used for internal rotor-light
messaging, `0` by default. This might be useful, to have a dedicated queue for
`rotor-light` messages, to avoid messages loss due to queue overfill, or have
lower or higher priorities for `rotor-light` messages.

 - `ROTOR_LIGHT_ACTOR` - defines the type to be used as actor id, the default is
`uint64_t`, which means the limitation of 64 actors in total. If you know a priory,
that there are less than 32/16/8 actors in total, you can save bits here.

 - `ROTOR_LIGHT_TIMEPOINT` defines the type to be used as timepoint. The default
value is `int64_t`.

 - `ROTOR_LIGHT_DURATION` defines the type to be used as time interval. The default
value is `std::int_fast32_t`.

 - `ROTOR_LIGHT_MESSAGE` - defines the type to be used as message type id, the default is
`std::uint_fast16_t`; you can tune that if you have more than 2^16 different message
**types**.

 - `ROTOR_LIGHT_EVENT` defines the type to be used as event id. The default
value is `std::uint16_t`; tune if there is a chance of having more than 2^16
**active events** at one time.

 - `ROTOR_LIGHT_INDEX` defines the type to be used as queue index. The default
value is `std::uint_fast8_t`.

 - `ROTOR_LIGHT_DOC` - generate doxygen docs, false by default.

Usage example:

```
cmake -DROTOR_LIGHT_TIMEPOINT=int32_t ..
```

# error handling

If any **internal** `rotor-light` message is not delivered, this is a critical error.
In the case the following function is invoked in the `rotor_ligth` namespace:

~~~{.cpp}
namespace rotor_light {

__attribute__((weak)) void on_queue_full() {
  for (;;) {
    __asm__("nop");
  }
}
~~~

If there is need to turn on red LED or something like that, then you should define
your own function `void on_queue_full()` (without weak specifier) in the namespace.

Custom message delivery faulure does not causes `on_queue_full()` invokation, it
should be done manually if needed.

A few advices to avoid the critical error:

 - inrease queue size

 - use different queue/prirority for `rotor_light` messages, if it is OK to loose
your custom messeages, as it is fatal to loose `rotor_ligth` messages. See
`ROTOR_LIGHT_FW_QUEUE` build option.
