Skip to content

Repository files navigation

ESPressio Event

Event-Driven Observer Pattern components of the Flowduino ESPressio Development Platform.

ESPressio Event provides a foundation for designing, structuring, and implementing ESP32 applications using Event-Driven Development (EDD): asynchronous typed Event routing, Event-aware Threads, bounded receiver queues, listener/observer registration, priority dispatch, high-resolution Event timing, optional Observer-to-Event bridges, and optional transport-neutral Serializable Event routing.

Latest Stable Version

The latest stable version is 5.4.0.

Compatibility

ESPressio Event targets the ESP32 family using Arduino-ESP32.

The implementation uses ESP-IDF FreeRTOS facilities, C++ RTTI, std::shared_mutex, and Arduino APIs through its dependency stack.

RTTI must be enabled:

build_unflags =
    -fno-rtti

Compatibility should still be verified by compiling for the intended ESP32 board/core/toolchain combination.

ESPressio Development Platform

The ESPressio Development Platform is a collection of discrete, sometimes interconnected component libraries developed around a common design ethos.

The principal objectives are:

  • Light-weight --- components should strive to minimise memory consumption and operational overhead without sacrificing clarity or correctness.
  • Ease of Use --- ESPressio components frequently provide developer-friendly, strongly typed abstractions over lower-level procedural facilities.
  • Object-Oriented --- a type for everything, and everything in a type.
  • SOLID --- to the maximum extent practical within C++, Arduino, FreeRTOS, and microcontroller constraints:
    • Single Responsibility Principle (SRP) --- keep components small and focused.
    • Open/Closed Principle (OCP) --- prefer extension without modification.
    • Liskov Substitution Principle (LSP) --- derived implementations should remain substitutable for their abstractions.
    • Interface Segregation Principle (ISP) --- prefer focused, client-specific interfaces.
    • Dependency Inversion Principle (DIP) --- depend upon abstractions rather than concrete implementations.

ESPressio Event follows those principles by making the Event itself the shared data contract while keeping Event producers and Event consumers independent of one another.

License

ESPressio and its component libraries are licensed under the Apache License 2.0.

See LICENSE for details.

Namespace

The Event API resides beneath:

ESPressio::Event

ESPressio Library Dependencies

ESPressio is designed as a modular ecosystem of independently useful libraries, with required dependencies kept explicit and optional integrations introduced only when the corresponding functionality is selected.

For a complete overview of required and opt-in relationships, see:

ESPressio Library Dependency Chart

In the dependency chart:

  • Solid relationships represent required ESPressio dependencies.
  • Dashed relationships represent opt-in dependencies introduced only when the corresponding feature, integration, type, or header is used.

Required dependencies

Normal Event applications require only the dependencies declared by the library:

lib_deps =
    flowduino/ESPressio-Event@^5.4.0

The mandatory dependency graph is:

ESPressio Event 5.4
    |
    +-- ESPressio Threads >= 3.1.0
    |
    +-- ESPressio Observable >= 3.0.0
    |
    +-- ESPressio Timing >= 2.2.0

ESPressio Units is available through the Timing dependency stack.

ESPressio Serializable is not a mandatory Event dependency. It is required only when an application explicitly opts into Serializable Events or Event Transport.


What is Event-Driven Observer Pattern?

Event-Driven Observer Pattern is a means of fully decoupling distinct areas of application functionality.

Instead of one module directly invoking another, the producer dispatches an Event containing context-specific payload data. Independent consumers register interest in that Event type and react when it is dispatched.

Conceptually:

Producer
   |
   | dispatches
   v
 Event<T>
   |
   v
EventManager
   |
   +----------+----------+
   |          |          |
   v          v          v
Listener A Listener B Listener C

The producer does not need to know whether zero, one, or many listeners exist.

An Event is therefore best thought of as a data contract between independently implemented pieces of functionality. The producer populates the contract; interested consumers read it.

A central EventManager coordinates delivery to interested Event receivers. Any Event type can consequently be dispatched from anywhere in the application without the dispatching code acquiring direct relationships with its consumers.

This is a natural asynchronous counterpart to the synchronous Observer Pattern provided by ESPressio Observable.

Order of execution

Event-Driven Observer Pattern does not imply a globally defined order of execution between independent listeners.

If an operation requires a strict synchronous ordering relationship, conventional Observer callbacks or direct sequencing may be more appropriate for that particular relationship.

Use the mechanism that correctly expresses the semantics of the operation rather than forcing every interaction through Events.

Events are asynchronous

Event dispatch is fundamentally asynchronous.

When an Event is queued or stacked, the execution chain that dispatched it continues without waiting for every interested receiver to finish processing it.

In practical terms, Events are fire and forget.

This is one of the primary reasons Event-driven design is useful for separating application concerns: the producer neither knows nor waits for the implementation details of its consumers.

Reciprocal Events

Asynchronous operation does not prevent request/result workflows.

A listener processing one Event may dispatch a second, reciprocal Event containing the result of its work:

RequestEvent
     |
     v
 Worker
     |
     v
ResultEvent

The original requester can itself listen for the result Event without introducing a direct call relationship between the two modules.

Event-driven and synchronous Observer patterns are complementary

Not every notification should become an Event.

ESPressio deliberately supports both models:

ESPressio Observable
    -> synchronous observation
    -> useful when the caller/operation and notification are tightly related

ESPressio Event
    -> asynchronous observation
    -> useful when producers and consumers should remain independently scheduled

The opt-in Timing and Threads Event Bridges demonstrate this distinction directly: the originating libraries expose synchronous Observer notifications, while ESPressio Event can optionally convert those notifications into asynchronous Events.


Understanding the components of ESPressio Event

IEvent

IEvent is the non-templated, type-erased Event interface used by the routing infrastructure.

Routing, receivers, listeners, ownership, and Event Manager infrastructure operate using:

IEvent*

Lifecycle timing is available to type-erased infrastructure as raw nanoseconds:

uint64_t GetDispatchTimeNanoseconds() const;
uint64_t GetTimeSinceDispatchNanoseconds() const;

This keeps the Event engine independent of the public Unit representation chosen by a concrete Event.

Event<TTime>

The normal Event base is:

template<
    typename TTime = Timing::DefaultClockTime
>
class Event;

Ordinary Event code therefore uses:

class MyEvent :
    public ESPressio::Event::Event<> {
};

An Event normally contains the immutable contextual payload needed by its consumers.

For example:

class TemperatureChangeEvent final :
    public ESPressio::Event::Event<> {

private:
    const int _previousTemperature;
    const int _newTemperature;

public:
    TemperatureChangeEvent(
        int previousTemperature,
        int newTemperature
    ) :
        _previousTemperature(previousTemperature),
        _newTemperature(newTemperature) {
    }

    int GetPreviousTemperature() const {
        return _previousTemperature;
    }

    int GetNewTemperature() const {
        return _newTemperature;
    }
};

The producer and consumers need to agree only on this Event contract.

Generic time representation

A different Timing-compatible public representation may be selected:

using MyTime = SomeCompatibleTimeType;

class MyEvent :
    public ESPressio::Event::Event<MyTime> {
};

The strongly typed lifecycle API is then:

TTime GetDispatchTime() const;
TTime GetTimeSinceDispatch() const;

Internally, Event lifecycle timing remains stored as raw nanoseconds and is converted through:

Timing::TimeTraits<TTime>

The Event System Clock uses:

Timing::SystemClock<TTime>::GetInstance()

Timing 2.x typed System Clock facades share one underlying global System Clock core.

Dispatching Events

An Event can be dispatched from anywhere.

Queue dispatch provides FIFO semantics:

(new TemperatureChangeEvent(
    previousTemperature,
    temperature
))->Queue();

Stack dispatch provides LIFO semantics:

(new TemperatureChangeEvent(
    previousTemperature,
    temperature
))->Stack();

The first dispatch records the Event's dispatch time. Redispatching the same Event does not replace that original timestamp.

Event priority

Events may be dispatched using the supported Event priority levels. Priority participates in the Event receiver's normal dispatch ordering.

When no explicit priority is supplied, normal priority is used.

Event receiver queues

Event receiver queues are bounded to 64 pending Events by default.

Define:

ESPRESSIO_EVENT_DEFAULT_MAX_PENDING_EVENT_COUNT

before including the library to choose another embedded-safe default, or explicitly configure a receiver maximum of zero when an unbounded queue is genuinely required.

Queue diagnostics include:

GetPendingEventCount()
GetPeakPendingEventCount()
GetRejectedEventCount()
GetDroppedEventCount()
ResetEventQueueStatistics()

Drained Event collections have a separate retained-capacity policy:

EventCollectionCapacityPolicy::Retain
EventCollectionCapacityPolicy::ShrinkWhenUnderutilized
EventCollectionCapacityPolicy::ReleaseAfterDrain

For example:

thread.SetEventCollectionCapacityPolicy(
    Event::EventCollectionCapacityPolicy::ShrinkWhenUnderutilized
);

thread.SetMinimumRetainedEventCapacity(4);
thread.SetEventCapacityExcessFactor(2);

EventThread

EventThread is the principal Event-processing Thread type.

It is built on ESPressio Threads, but differs from a conventional continuously looping Thread.

An EventThread can remain efficiently suspended until an Event matching one of its registered listeners arrives. The relevant Event callback is then executed on the Event Thread's own task. Once pending Events have been processed, the Thread can return to waiting without consuming CPU cycles merely to poll for work.

Remember:

An Event can be created and dispatched from anywhere. Only Event-capable receiver types need to participate in Event processing.

PrecisionEventThread<TTime, Traits>

PrecisionEventThread combines Event processing with the deterministic iteration model provided by ESPressio Threads:

template<
    typename TTime = Timing::DefaultClockTime,
    typename TRepresentationTraits =
        Threads::PrecisionThreadTraits<TTime>
>
class PrecisionEventThread;

Ordinary usage:

class ControlThread :
    public ESPressio::Event::PrecisionEventThread<> {
};

Event processing order can be configured using:

PrecisionEventProcessOrder::EventsBeforeIteration
PrecisionEventProcessOrder::EventsAfterIteration

Arrival behavior can be configured using:

PrecisionEventArrivalPolicy::ProcessOnNextIteration
PrecisionEventArrivalPolicy::TriggerImmediateIteration
PrecisionEventArrivalPolicy::ProcessImmediately

ProcessImmediately still means asynchronously as soon as the owning task is scheduled; Event handlers are not moved onto the dispatching task.

EventListener

Listeners express interest in a specific Event type and invoke application code when a matching Event is delivered.

A listener can be registered on an Event-capable receiver:

Event::EventListenerHandlePtr handle =
    eventThread.RegisterListener<
        TemperatureChangeEvent
    >(
        [](TemperatureChangeEvent* event,
           Event::EventDispatchMethod dispatchMethod,
           Event::EventPriority priority) {

            // React to the Event.
        }
    );

The returned handle owns the registration lifetime. Destroying or unregistering the handle removes the listener.

Listeners can therefore be enabled or disabled dynamically without adding a boolean check to every callback invocation.

Listener interest

Listener interest policies allow a receiver to reject Events that are not relevant to it.

EventListenerInterest::YoungerThan uses a typed EventTime threshold, converted through Timing traits and compared with the Event's raw nanosecond age.

Custom interest logic can also be used where application-specific filtering is required.

Typed Event Observers

Callback-based listeners can alternatively be represented as typed Observers.

Implement:

IEventObserver<EventType>

and register it with the Event receiver:

class TemperatureObserver :
    public Event::IEventObserver<
        TemperatureChangeEvent
    > {

public:
    void OnEvent(
        TemperatureChangeEvent* event,
        Event::EventDispatchMethod dispatchMethod,
        Event::EventPriority priority
    ) override {
        // React to the Event.
    }
};

Registration uses the same asynchronous Event pipeline:

TemperatureObserver observer;

Event::EventListenerHandlePtr observerHandle =
    eventThread.RegisterObserver<
        TemperatureChangeEvent
    >(
        &observer
    );

IEventObserver<EventType> derives from ESPressio Observable's IObserver contract.

Observers are non-owning: the Observer instance must remain alive until its registration handle is unregistered or destroyed.

An Observer may implement multiple IEventObserver<EventType> interfaces and register each independently.

EventManager

EventManager is the central asynchronous dispatch infrastructure.

It coordinates the transit of Events from dispatching code to the interested Event receivers without requiring the producer to know those receivers.

The Event Manager is process-lifetime FreeRTOS infrastructure. Its task, semaphore, dispatcher, and per-Event-type routing structures intentionally remain allocated until device shutdown; this is fixed infrastructure rather than leaked per-dispatch Event ownership.


Type topology

The complete Event type topology is available here:

ESPressio Event complete type topology

The editable vector source is available at:

diagrams/espressio-event-type-topology.svg


Usage example: a decoupled thermometer

The following example illustrates the core purpose of the library.

We want three independent pieces of functionality:

Thermometer
    -> reads a sensor

TemperatureSerialLogger
    -> reports changes to Serial

TemperatureDisplay
    -> updates a physical display

None should need a direct reference to either of the others.

Their only shared contract is:

TemperatureChangeEvent

TemperatureChangeEvent

#pragma once

#include <ESPressio_Event.hpp>

class TemperatureChangeEvent final :
    public ESPressio::Event::Event<> {

private:
    const int _previousTemperature;
    const int _newTemperature;

public:
    TemperatureChangeEvent(
        int previousTemperature,
        int newTemperature
    ) :
        _previousTemperature(previousTemperature),
        _newTemperature(newTemperature) {
    }

    int GetPreviousTemperature() const {
        return _previousTemperature;
    }

    int GetNewTemperature() const {
        return _newTemperature;
    }
};

Both values are supplied through the constructor and no setters are exposed. The Event therefore describes one complete temperature transition.

TemperatureSerialLogger

#pragma once

#include <Arduino.h>
#include <ESPressio_EventThread.hpp>

#include "TemperatureChangeEvent.hpp"

class TemperatureSerialLogger final :
    public ESPressio::Event::EventThread {

private:
    ESPressio::Event::EventListenerHandlePtr
        _temperatureChangeListener =
            RegisterListener<
                TemperatureChangeEvent
            >(
                [](TemperatureChangeEvent* event,
                   ESPressio::Event::EventDispatchMethod,
                   ESPressio::Event::EventPriority) {

                    const int change =
                        event->GetNewTemperature() -
                        event->GetPreviousTemperature();

                    const char* direction =
                        change >= 0 ? "UP" : "DOWN";

                    const int magnitude =
                        change >= 0 ? change : -change;

                    Serial.printf(
                        "Temperature is %s by %d degrees "
                        "(from %d to %d).\n",
                        direction,
                        magnitude,
                        event->GetPreviousTemperature(),
                        event->GetNewTemperature()
                    );
                }
            );

public:
    TemperatureSerialLogger() :
        EventThread(false) {
    }
};

The logger contains no sensor code and no display code. It understands only the Event contract.

TemperatureDisplay

A display implementation can independently register for exactly the same Event:

#pragma once

#include <ESPressio_EventThread.hpp>

#include "TemperatureChangeEvent.hpp"

class TemperatureDisplay final :
    public ESPressio::Event::EventThread {

private:
    ESPressio::Event::EventListenerHandlePtr
        _temperatureChangeListener =
            RegisterListener<
                TemperatureChangeEvent
            >(
                [this](TemperatureChangeEvent* event,
                       ESPressio::Event::EventDispatchMethod,
                       ESPressio::Event::EventPriority) {

                    DisplayTemperature(
                        event->GetNewTemperature()
                    );
                }
            );

    void DisplayTemperature(int temperature) {
        // Update the application's physical display.
    }

public:
    TemperatureDisplay() :
        EventThread(false) {
    }
};

Again, there is no relationship to the logger or sensor implementation.

Thermometer

The sensor-side code only needs to dispatch the Event:

#pragma once

#include "TemperatureChangeEvent.hpp"

class Thermometer {

private:
    int _temperature = 0;

    int ReadTemperatureSensor() {
        // Replace with the appropriate sensor implementation.
        return _temperature;
    }

public:
    void UpdateTemperature() {
        const int temperature =
            ReadTemperatureSensor();

        if (temperature == _temperature) {
            return;
        }

        const int previousTemperature =
            _temperature;

        _temperature = temperature;

        (new TemperatureChangeEvent(
            previousTemperature,
            temperature
        ))->Queue();
    }
};

The significant line is simply:

(new TemperatureChangeEvent(
    previousTemperature,
    temperature
))->Queue();

The Thermometer does not know which modules---if any---will process the Event.

Example topology

Thermometer example decoupled event topology

The editable vector source is available at:

diagrams/thermometer-example-topology.svg

The important result is:

TemperatureSerialLogger
    has no direct relationship with
    TemperatureDisplay or Thermometer

TemperatureDisplay
    has no direct relationship with
    TemperatureSerialLogger or Thermometer

Thermometer
    has no direct relationship with
    TemperatureSerialLogger or TemperatureDisplay

Yet both consumers react independently whenever Thermometer dispatches a TemperatureChangeEvent.

Additional consumers can be introduced later without modifying the existing producer or consumers.

That is the central architectural advantage of ESPressio Event.


Precision Event Thread example

#include <ESPressio_Event.hpp>
#include <ESPressio_PrecisionEventThread.hpp>

using namespace ESPressio;

class SetpointEvent final :
    public Event::Event<> {

private:
    const int _setpoint;

public:
    explicit SetpointEvent(int setpoint) :
        _setpoint(setpoint) {
    }

    int GetSetpoint() const {
        return _setpoint;
    }
};

class ControlThread final :
    public Event::PrecisionEventThread<> {

private:
    int _setpoint = 0;

protected:
    void OnIteration(
        IterationTime delta,
        IterationTime startTime,
        Threads::SkippedIterationCount skippedIterations
    ) override {
        (void)delta;
        (void)startTime;
        (void)skippedIterations;

        // Perform deterministic control work.
    }

public:
    void ApplySetpoint(SetpointEvent* event) {
        _setpoint = event->GetSetpoint();
    }
};

See:

examples/PrecisionEventThread

for the complete repository example.


Optional Serializable Events

Serializable Event support is opt-in:

#include <ESPressio_Event_Serializable.hpp>

or:

#include <ESPressio_SerializableEvent.hpp>

Neither is imported by the normal ESPressio_Event.hpp path.

A consuming application using Serializable Events declares ESPressio Serializable explicitly:

lib_deps =
    flowduino/ESPressio-Event@^5.4.0
    flowduino/ESPressio-Serializable@^0.9.0

SerializableEvent

The optional base is:

template<
    typename TDerived,
    typename TTime =
        Units::SerializableNanoSeconds<uint64_t>
>
class SerializableEvent;

Example:

#include <ESPressio_Event_Serializable.hpp>

class TemperatureEvent final :
    public ESPressio::Event::SerializableEvent<
        TemperatureEvent
    > {

private:
    float _temperature = 0.0f;
    uint32_t _sensorId = 0;

public:
    ESPRESSIO_SERIALIZABLE_TYPE(
        TemperatureEvent
    )

    ESPRESSIO_SERIALIZABLE_SCHEMA_VERSION(1)

    ESPRESSIO_SERIALIZABLE_PROPERTIES(
        ESPRESSIO_PROPERTY(
            "temperature",
            _temperature
        ),
        ESPRESSIO_PROPERTY(
            "sensorId",
            _sensorId
        )
    )
};

The derived payload can use the ESPressio Serializable feature set, including JSON, CBOR, Binary, schema versions, aliases, validation, migrations, streaming, and schema introspection.

What is not serialized

Local Event runtime state is deliberately not Event payload:

reference count
local dispatch status
local System Clock dispatch timestamp
routing/listener state

A deserialized Serializable Event is a new local Event which can subsequently be queued or stacked normally.

Transport metadata belongs either explicitly in the Event payload or in the transport envelope.

Ordinary Events remain serialization-free

This:

#include <ESPressio_Event.hpp>

class ButtonEvent :
    public ESPressio::Event::Event<> {
};

does not require ESPressio Serializable.


Observer-to-Event bridges

ESPressio Event can optionally convert synchronous Observer notifications from other ESPressio libraries into asynchronous Events.

The important dependency direction is:

Timing / Threads
    expose synchronous Observer APIs

Event
    optionally consumes those APIs
    and emits asynchronous Events

Timing and Threads therefore do not need to depend upward on Event.

System Clock Event Bridge

Timing 2.2 System Clock Observer notifications can be bridged using:

#include <ESPressio_SystemClockEventBridge.hpp>

Event::SystemClockEventBridge::
    GetInstance().
    Initialize();

Timing Events are grouped under:

src/timing-events/

with:

#include <ESPressio_TimingEvents.hpp>

Every ISystemClockObserver callback has a corresponding strongly typed Event carrying the relevant callback snapshot, including synchronization before/after values, clock difference, result/status, state changes, configuration changes, and callback lifecycle information.

Serializable counterparts are separately opt-in:

#include <ESPressio_TimingEvents_Serializable.hpp>
#include <ESPressio_SystemClockEventBridge_Serializable.hpp>

Event::SerializableSystemClockEventBridge::
    GetInstance().
    Initialize();

Both bridges remain dormant until explicitly initialized.

Threads infrastructure Event Bridges

The singleton infrastructure Observer APIs in ESPressio Threads can similarly be bridged through:

ThreadManagerEventBridge
ThreadGarbageCollectorEventBridge
ThreadTerminationDispatcherEventBridge

and the opt-in Serializable counterparts:

SerializableThreadManagerEventBridge
SerializableThreadGarbageCollectorEventBridge
SerializableThreadTerminationDispatcherEventBridge

Thread Events are grouped beneath:

src/thread-events/

with:

#include <ESPressio_ThreadEvents.hpp>
#include <ESPressio_ThreadEvents_Serializable.hpp>

Bridge batch headers are:

#include <ESPressio_ThreadEventBridges.hpp>
#include <ESPressio_ThreadEventBridges_Serializable.hpp>

The ordinary bridges do not require ESPressio Serializable.


Event Transport

Version 5.3 introduced a transport-neutral bidirectional routing layer for Serializable Events, extended in 5.4 with per-transport routing policy.

The subsystem is opt-in:

#include <ESPressio_EventTransport.hpp>

Concrete transport implementations live outside ESPressio Event and implement:

IEventTransport

ESPressio Event owns:

Event type registration
Binary serialization/deserialization
stable wire identities
inbound/outbound policy
dispatch metadata
pending-work lifecycle
remote-to-local loop prevention
per-transport routing policy

Stable Event type identity

Every transported Event declares a stable wire identity:

ESPRESSIO_EVENT_TRANSPORT_TYPE(
    MySerializableEvent,
    "com.example.my-event.v1"
)

The stable name is converted to the wire identifier; RTTI implementation names are not used as protocol contracts.

Registering Event directions

Global/default policy:

manager.RegisterInboundEvent<RemoteCommandEvent>();
manager.RegisterOutboundEvent<TelemetryEvent>();
manager.RegisterBidirectionalEvent<SharedStateEvent>();

C++17 bulk forms are available:

manager.RegisterOutboundEvents<
    TelemetryEvent,
    DiagnosticsEvent,
    BatteryStatusEvent
>();

A concrete transport can have its own override:

manager.RegisterOutboundEvent<
    TelemetryEvent
>(
    &udpTransport
);

manager.RegisterBidirectionalEvent<
    SharedStateEvent
>(
    &espNowTransport
);

The model is:

global/default direction
        +
optional per-transport override

A transport-specific override is authoritative for that transport, including EventTransportDirection::None.

Multiple transports

Multiple transports can be registered simultaneously:

manager.RegisterTransport(
    &espNowTransport
);

manager.RegisterTransport(
    &udpTransport
);

The same Event can therefore have different routing policy on different physical/network mechanisms.

Unregistration and pending work

Unregistration mirrors registration and includes bulk forms.

Pending work can be independently configured to complete or be discarded using:

EventTransportUnregistrationOptions

Transport-scoped unregistration affects only matching work for the selected transport.

Remote origin and loop prevention

EventDispatchContext records whether an Event originated locally or remotely, together with transport metadata such as message ID and hop count.

Remotely received Events are dispatched normally to local listeners but are not retransmitted by default, preventing simple transport loops such as:

A -> B -> A -> B ...

Wire envelope

The version-1 transport envelope preserves:

stable Event type ID
Serializable schema version
message ID
dispatch method
priority
hop count
payload length

The payload uses ESPressio Serializable BinaryArchive.

EventTransportManager observation

EventTransportManager exposes an IEventTransportManagerObserver surface for transport registration, type/route lifecycle, and inbound/outbound diagnostics.


Migration from 4.x

Event inheritance

4.x:

class MyEvent :
    public Event::Event {
};

5.x:

class MyEvent :
    public Event::Event<> {
};

PrecisionEventThread inheritance

4.x:

class MyThread :
    public Event::PrecisionEventThread {
};

5.x:

class MyThread :
    public Event::PrecisionEventThread<> {
};

Event time

Timing 1.x's fixed ClockTime is no longer the Event representation contract.

The default representation is:

Timing::DefaultClockTime

Generic code should prefer:

typename MyEvent::TimeType

Type-erased lifecycle timing

IEvent infrastructure uses:

GetDispatchTimeNanoseconds()
GetTimeSinceDispatchNanoseconds()

while concrete typed Events expose:

event->GetDispatchTime()
event->GetTimeSinceDispatch()

Design summary

The core 5.x architecture is:

                         IEvent
                  type-erased Event core
                         |
                         v
                    Event<TTime>
                         |
              +----------+----------+
              |                     |
              v                     v
     DefaultClockTime      SerializableNanoSeconds
                                    |
                                    v
                         SerializableEvent<TDerived>

and:

Threads::PrecisionThread<TTime, Traits>
                    |
                    v
PrecisionEventThread<TTime, Traits>

The wider integration architecture is:

                 ESPressio Event
                       |
        +--------------+--------------+
        |              |              |
        v              v              v
 local routing    Event Bridges   Event Transport
        |              |              |
        |         Timing/Threads      |
        |          Observers           |
        |                             |
        +-----------------------------+
                       |
                Event contracts

The library therefore provides one Event engine and one asynchronous routing system while allowing:

  • the public time representation to vary at compile time;
  • synchronous subsystem notifications to be bridged into Events only when requested;
  • Serializable Events to remain optional;
  • concrete network/radio transports to remain outside Event;
  • global and per-transport routing policy to coexist.

Most importantly, the original Event-driven design principle remains unchanged:

Application modules communicate through Event contracts rather than acquiring direct relationships with one another.

That is the purpose of ESPressio Event.

About

Event-Driven Development Components of the ESPressio Development Platform

Resources

Stars

2 stars

Watchers

1 watching

Forks

Releases

Sponsor this project

Packages

Contributors

Languages