12 Commits 94e8511f09 ... 93af118363

Author SHA1 Message Date
  Andrey Nikolaev 93af118363 Merge branch 'master' of https://notabug.org/basiliscos/cpp-rotor-light 2 years ago
  Andrey Nikolaev 5a74b54f15 build example for smt32 2 years ago
  Ivan Baidakou 64e6b1c86b allow to build on termux 2 years ago
  Ivan Baidakou c0739e36c8 moar docs 2 years ago
  Ivan Baidakou 80089afe68 format sources 2 years ago
  Ivan Baidakou b6dee61160 moar docs 2 years ago
  Ivan Baidakou da362d6aad Initial docs 2 years ago
  Ivan Baidakou b1da6c0971 refactor 2 years ago
  Ivan Baidakou 7772d2172b moar performance tests 2 years ago
  Ivan Baidakou f89b0645c2 initial docs 2 years ago
  Ivan Baidakou afcdf12241 add blinking LED example 2 years ago
  Ivan Baidakou c9c271f14d add blinking LED with uart interaction example 2 years ago
10 changed files with 2873 additions and 1 deletions
  1. 16 1
      CMakeLists.txt
  2. 44 0
      README.md
  3. 145 0
      docs/Conception.md
  4. 2467 0
      docs/Doxyfile.in
  5. 61 0
      docs/Introduction.md
  6. 81 0
      docs/Tutorial.md
  7. 1 0
      docs/actor-lifetime.drawio
  8. BIN
      docs/actor-lifetime.png
  9. 58 0
      docs/header.html
  10. 0 0
      examples/CMakeLists.txt

+ 16 - 1
CMakeLists.txt

@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.8)
+cmake_minimum_required(VERSION 3.13)
 if(${CMAKE_VERSION} VERSION_GREATER_EQUAL 3.12)
     cmake_policy(SET CMP0074 NEW)
 endif()
@@ -46,4 +46,19 @@ if (CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME)
             add_subdirectory("tests")
         endif()
     endif()
+
+    find_package(Doxygen)
+    if (DOXYGEN_FOUND)
+        set(DOXYGEN_IN ${CMAKE_CURRENT_SOURCE_DIR}/docs/Doxyfile.in)
+        set(DOXYGEN_OUT ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile)
+        configure_file(${DOXYGEN_IN} ${DOXYGEN_OUT} @ONLY)
+        add_custom_target( doc_doxygen ALL
+            COMMAND ${DOXYGEN_EXECUTABLE} ${DOXYGEN_OUT}
+            WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
+            COMMENT "Generating API documentation with Doxygen"
+            VERBATIM)
+        file(GLOB DOC_IMAGES CONFIGURE_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/docs/*.png)
+        file(COPY ${DOC_IMAGES} DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}/doxygen)
+        message("Doxygen need to be installed to generate the doxygen documentation")
+    endif()
 endif()

+ 44 - 0
README.md

@@ -0,0 +1,44 @@
+`rotor-light` is real-time platform-neutral, C++ actor micro-framework for
+embedded systems with supervising capabilities.
+
+`rotor-light` is built with the concurrency in the mind, i.e. independent
+entities (actors) interact each other via messages.
+
+`rotor-light` is adoption of [rotor](https://github.com/basiliscos/cpp-rotor)
+framework for embedded systems. The framework has no platform-dependent
+code, so, it can, however, be used as is on host system, if you cautiously
+care about thread-safety.
+
+## features
+
+ - erlang-like hierarchical supervisors
+
+ - message priorities
+
+ - non-intrusiveness
+
+ - asynchornous message passing interface
+
+ - plaform-agnostic code (including timers)
+
+ - C++17
+
+ - no thread-safety
+
+
+## ping-pong messaging performance & binary size
+
+
+|      host (1)                 |  Arduino Uno R3
+|:-----------------------------:|:---------------------------:
+|  ~48.7M messages/second, 26kb |  ~5.2k messages/second, 9kb
+
+
+All examples can be measured with `examples/ping-pong-throughput.cpp`,
+compiled with `CMAKE_BUILD_TYPE=MinSizeRel` and the stripped
+
+(1) Setup: Intel Core i7-8550U, Void Linux 5.15.
+
+## license
+
+MIT

+ 145 - 0
docs/Conception.md

@@ -0,0 +1,145 @@
+# Conception
+
+## actor
+
+`actor` is an entity with its own lifetime, whose job is to react to incoming messages. 
+Here "react" means to produce side-effects like logging, blinking LEDs etc, or to send
+new messages.
+
+Here is a diagram of normal actor life cycle, where round ellipse is actor state and
+box is actor method.
+
+![actor lifcycle](actor-lifetime.png)
+
+Usually, actor reacts on user-defined messages, when it is in `operational` state. 
+An actor have to subscribe on particual message type(s) to react on the message.
+This subscription is performed via `initialize()` method overriding. As there
+are framework messages, an user-defined actor **must** invoke `ActorBase::initialize()`
+method to allow the framework do that.
+
+Actor does not react on the messages, when it is in the `off` state. 
+The `ActorBase::advance_stop()` method invocation does that. Actor can override
+the `advance_stop()` method and postpone the `ActorBase::advance_stop()` invokation
+when it will be ready, i.e. until the underlying hardware will be off. 
+
+Likewise the `advance_init()` method can be overrided and the `ActorBase::advance_init()`
+can be postponed, until underlying hardware will be ready. 
+
+The `advance_start()` method plays a bit different role: it is singalled from its 
+**supervisor** (see below), that everything is ready to start, i.e. all other actor's
+siblings, belonging to the same supervisor, and it is safe to send message to other
+actors as they are `operational`. In other words, it is the *cross-actor synchronization
+point*. When overriding, the `ActorBase::advance_start()` must be invoked to change
+the state.
+
+`actor` is designed to be recycleable, i.e. when it is shut down (`off` state), it can
+be started again. If an actor has defaults values, which are changed during its lifetime, 
+the overriden `initialize()` method is the proper place to reset the defaults.
+
+There might be the case, then an actor cannot be initialized, i.e. due to hardware
+failure. In that case, the `ActorBase::initialize()` **must not be invoked**, and
+the failure should be reported to its supervisor via the code like:
+
+```
+send<message::ChangeStateAck>(0, supervisor->get_id(), id, State::initialized, false);
+```
+
+What to do with the failure, is the job of the *supervisor*.
+
+### actor summary
+
+Subscribe and initialize the default values in `initialize()` method. Start messaging
+with other actors in `advance_start()`. Initialize the hardware via the overriding
+`advance_init()` method and when you done, invoke the `ActorBase::advance_init()`.
+Similarly, shutdown the hardware via the overriding `advance_stop()` method and when 
+you done, invoke the `ActorBase::advance_stop()`.
+
+All cross-actor communication when everything is ready is performed when actors are 
+in the `operational` state.
+
+## supervisor
+
+Supervisor is a special kind of actor, which manages (or  "orchestrates", if you like)
+other actors (including supervisors), which it owns. Supervisor is responsible for
+**initializing**, **shutting down**, **synchronization** of actors and also handles
+**actors failures**.
+
+Supervisor initialization (shut down) is simple: it becomes `initialized` (`off`)
+when all its child actors are `initialized` (`off`). It is imporant, that supervisor
+**waits**, until all its children become `initialized` (`off`).
+
+When it is `initialized` it advances self into `operational` state and dispatches
+start message to all its children. This way it **synchronizes** start of actors:
+an actor has guarantee, when it starts sending messages to other actors, they 
+are `operational`.
+
+### fail policies
+
+Dealing with actor failure, is a bit more complex task, as it depends on actor state
+and the fail policy, defined in the actor.
+
+The full list of fail policies is:
+
+```
+enum class FailPolicy {
+    restart        = 0b00000001,
+    force_restart  = 0b00000011,
+    escalate       = 0b00000100,    // default
+    force_escalate = 0b00001100,
+    ignore         = 0b00010000,
+};
+```
+
+If actor stops in `operational` state this is the normal case, and supervisor does
+nothing, unless the actor policy is `force_restart` or `force_escalate`, which 
+start restart and escalate procedure correspondingly.
+
+The actor **restart procedure** is simple: send it stop message (if the actor wasn't `off`),
+and then `initialize()` it and wait initialization confirmation (it's done via 
+`ActorBase::advance_init()`). 
+
+The actor **escalate procedure** is simple: supervisor stops all actors, and then shuts
+self down too. If it was the root supervisor, it exits from `::process()` messages
+method too, as there are no more messages(). Then, usually, that leads to exiting
+from `main()` methon of the firmware which causes hardware restart..
+
+If actor fails in `initializing` state, and it's policy is `restart`, then supervisor
+performs the restart actor procedure. 
+
+If actor fails in `initializing` state, and it's policy is `escalate`, then supervisor
+performs the escalate failure procedure. 
+
+If actor has `ignore` policy and it stops, the supervisor continue to perform it's 
+normal activity (i.e. wait of initialization/shutdown of other actors).
+
+NB: fail policy can be changed by actor itself during it's lifetime, i.e. escalate
+falure after 3 failed initializations.
+
+The **default policy** of an actor is `escalate`.
+
+The "failed to shutdown" is meaningless for the framework, i.e. supervisor always
+waits until it receives child actor shutdown confirmation.
+
+### fail policies summary
+
+`force_*` policies, "ignore" actor state and instruct supervisor to perform appropriate
+procedure. `restart`, `escalate` and `ignore` has sense only for `operational` actor
+state. 
+
+## composition
+
+The underlying idea is: if an actor fails to initialize (due to hardware failure), 
+give it another chance by restarting it afresh. May be a few times. May be recurrently.
+
+
+Supervisor was designed to be composeable, i.e. it can contain other supervisors as
+its childern forming *hierarchy of actors* . 
+
+Why would you need that, instead of having the "flat" actors list and just have messaging? 
+It is needed to handle failure escalation: supervisor groups related actors, and if one 
+of them fails, restart the whole group, maybe a few times, util escalating the failure
+upstream, i.e. to root supervisor.
+
+If the root supervisor fails too... ok, it seems, that we have tried the best we can,
+and the final radical remedy left is just to *restart the whole board* (i.e. exit from
+`main()` entry point).

File diff suppressed because it is too large
+ 2467 - 0
docs/Doxyfile.in


+ 61 - 0
docs/Introduction.md

@@ -0,0 +1,61 @@
+# Intoduction
+
+Modelling 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 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. Message 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.
+
+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 unresolveable
+problem into upper layers... upto restarting 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 colleques of mine aske me to have something similar for embedded systems.
+
+The following table highlight the differences
+
+|                              |      rotor light        |  rotor
+|:----------------------------:|:-----------------------:|:----------------------:
+| max. number of actors        | 64                      | unlimited
+| max. number of messages      | compile-time defined    | unlimited
+| 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.

+ 81 - 0
docs/Tutorial.md

@@ -0,0 +1,81 @@
+# Tutorial
+
+## blink with LED
+
+This is platform-neural tutorial, although hardware implementation refers to
+arduino. The full code can be found at `examples/atmega328p/blink-led.cpp`
+
+Let's start from the inclusion of headers:
+
+```
+#include <rotor-light.hpp>
+```
+
+All further interactions with the framework is done via `rl` namespace
+
+```
+namespace rl = rotor_light;
+```
+
+In this example, there is only single message - `BlinkCommand`. It is good
+practice to define messages in it's own namespace, as it is much more
+convenient to deail with messages later (handle and send).
+
+```
+namespace message {
+
+struct BlinkCommand : rl::Message {
+  static constexpr auto type_id = __LINE__;
+  using rl::Message::Message;
+  rl::MessageTypeId get_type_id() const override { return type_id; }
+};
+
+} // namespace message
+```
+
+The `BlinkCommand` message have to be derived from `rotor_ligth` `Message`,
+and as the `BlinkCommand` has no payload, it just reuses its parent
+consturctor (`using rl::Message::Message`).
+
+As the `rotor_ligth` does not uses RTTI it should somehow identify
+message types at runtime. That's why overriden `get_type_id()` method
+should be available on every message type. `rotor_light` does not care
+how you, as the user, define your message type, the only requirement is the
+uniqueness of them. The easiest way to achieve that is just have line
+
+```
+  static constexpr auto type_id = __LINE__;
+```
+
+in every message type definition, and group all used messages in a single
+header file.
+
+Let's move to the blinker actor code; as it is rather small, it is shown
+entirely:
+
+```
+struct Blinker : rl::Actor<2> {
+using Parent = Actor<2>;
+
+void initialize() override {
+  subscribe(&Blinker::on_blink_command);
+  Parent::initialize();
+}
+
+void advance_start() override {
+  Parent::advance_start();
+  add_event(
+      delay, [](void *data) { static_cast<Blinker *>(data)->blink(); }, this);
+}
+
+void blink() {
+  toggle_led();
+  add_event(
+      delay, [](void *data) { static_cast<Blinker *>(data)->blink(); }, this);
+}
+
+void on_blink_command(message::BlinkCommand &msg) { blink(); }
+
+rl::Duration delay;
+};
+```

File diff suppressed because it is too large
+ 1 - 0
docs/actor-lifetime.drawio


BIN
docs/actor-lifetime.png


+ 58 - 0
docs/header.html

@@ -0,0 +1,58 @@
+<!-- HTML header for doxygen 1.8.16-->
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/>
+<meta http-equiv="X-UA-Compatible" content="IE=9"/>
+<meta name="generator" content="Doxygen $doxygenversion"/>
+<meta name="viewport" content="width=device-width, initial-scale=1"/>
+<!--BEGIN PROJECT_NAME--><title>$projectname: $title</title><!--END PROJECT_NAME-->
+<!--BEGIN !PROJECT_NAME--><title>$title</title><!--END !PROJECT_NAME-->
+<link href="$relpath^tabs.css" rel="stylesheet" type="text/css"/>
+<script type="text/javascript" src="$relpath^jquery.js"></script>
+<script type="text/javascript" src="$relpath^dynsections.js"></script>
+$treeview
+$search
+$mathjax
+<link href="$relpath^$stylesheet" rel="stylesheet" type="text/css" />
+$extrastylesheet
+</head>
+<body>
+<a href="http://www.reactivemanifesto.org/"> <img style="border: 0; position: fixed; right: 0; top:0; z-index: 9000" src="//d379ifj7s9wntv.cloudfront.net/reactivemanifesto/images/ribbons/we-are-reactive-white-right.png"> </a>
+
+<div id="top"><!-- do not remove this div, it is closed by doxygen! -->
+
+<!--BEGIN TITLEAREA-->
+<div id="titlearea">
+<table cellspacing="0" cellpadding="0">
+ <tbody>
+ <tr style="height: 56px;">
+  <!--BEGIN PROJECT_LOGO-->
+  <td id="projectlogo"><img alt="Logo" src="$relpath^$projectlogo"/></td>
+  <!--END PROJECT_LOGO-->
+  <!--BEGIN PROJECT_NAME-->
+  <td id="projectalign" style="padding-left: 0.5em;">
+   <div id="projectname">$projectname
+   <!--BEGIN PROJECT_NUMBER-->&#160;<span id="projectnumber">$projectnumber</span><!--END PROJECT_NUMBER-->
+   </div>
+   <!--BEGIN PROJECT_BRIEF--><div id="projectbrief">$projectbrief</div><!--END PROJECT_BRIEF-->
+  </td>
+  <!--END PROJECT_NAME-->
+  <!--BEGIN !PROJECT_NAME-->
+   <!--BEGIN PROJECT_BRIEF-->
+    <td style="padding-left: 0.5em;">
+    <div id="projectbrief">$projectbrief</div>
+    </td>
+   <!--END PROJECT_BRIEF-->
+  <!--END !PROJECT_NAME-->
+  <!--BEGIN DISABLE_INDEX-->
+   <!--BEGIN SEARCHENGINE-->
+   <td>$searchbox</td>
+   <!--END SEARCHENGINE-->
+  <!--END DISABLE_INDEX-->
+ </tr>
+ </tbody>
+</table>
+</div>
+<!--END TITLEAREA-->
+<!-- end header part -->

+ 0 - 0
examples/CMakeLists.txt


Some files were not shown because too many files changed in this diff