avr-classes
The event system

The main components of the event system are Event, EventReceiver, EventDispatcher and QueuedEventDispatcher. Events are basically numbers (event IDs) and don't carry additional parameters. The pure fact that an event occurred is all information that is distributed. This might seem a bit limiting on the first view, but avoids awkward conditions where associated status information might be outdated when an event finally reaches its observer.

Event IDs are assigned by the user. The classes from core will only use event IDs that have been passed for a specific use. The only event ID known by the core classes is INVALID_EVENT_ID to mark events as invalid (e.g. when for a specific use-case an event should not be sent).

EventReceiver is an interface that can be implemented by components that wish to receive events. It contains only a single method, EventReceiver::onEvent.

EventDispatcher also implements EventReceiver, but is designed to handle events by dispatching them to a list of subscribers. Since it implements the same EventReceiver interface as the "normal" receivers, it is possible to substitute a single receiver by a group of receivers on any level. Yes, that means that EventDispatcher instances can be cascaded and you even can build nasty loops.

The QueuedEventDispatcher is an extension of EventDispatcher that doesn't dispatch the events immediately, but puts them into a queue instead. To a call to QueuedEventDispatcher::dispatch the queued events are dispatched to the subscribers. By doing this the QueuedEventDispatcher can be used to decouple calling contexts, e.g. enqueuing events from the ISR context and dispatch them in the interruptible main context. See notes below.

The basic idea of having the same interface is, that components like the NotifyingBufferedUsart expect a pointer to an EventReceiver instance to report to. The user of the class is then free to use either a single receiver, or an EventDispatcher instance as multiplier. It also allows to manipulate the event distribution by adding filtering components into the dispatching chain. For this the classes EventFilter and EventTranslator exist, which can be subclassed to implement specific behavior.

Warning
Modifications to the subscriber list (subscribe/unsubscribe) are protected by interrupt locks, but dispatching is not protected (because it is a lengthy operation that should be interruptible if run from main context). If the list is modified while dispatching, there is a potential for data corruption, which must be prevented. The following table gives an overview of safe and unsafe scenarios.
Subscribe or unsubscribe Dispatching Result
Main context Main context OK: no interruption can happen
Main context ISR context OK: dispatching can't be interrupted, list modifications are protected against interrupts
ISR context Main context Problem: dispatching can be interrupted by subscribe/unsubscribe
ISR context ISR context OK: no interruption can happen
Note
You should not call subscribe/unsubscribe from a dispatching context of the same EventDispatcher instance. While nothing too bad can happen, adding a subscription may or may not cause a callback during the current dispatch operation, depending on the history of the EventDispatcher instance (effectively if the slot being used for the new subscription is before or after the subscriber which is currently handled).