|
JH-Toolkit v1.4.1
An engineering-oriented C++20 toolkit with duck-typed concepts, static design, async coroutines, and semantic containers — header-only, RTTI-free, and concurrency-friendly.
|
Coroutine-based asynchronous slot/listener/event_signal system with One-Slot-Per-Hub semantics, multi-listener fan-in, and user-controlled fan-out logic inside the slot coroutine.
More...
#include <coroutine>#include <optional>#include <mutex>#include <chrono>#include <functional>#include "jh/typed"Go to the source code of this file.
Classes | |
| class | jh::async::slot |
| Coroutine representing the user-defined asynchronous state machine. More... | |
| struct | jh::async::slot::promise_type |
| Slot coroutine promise type. More... | |
| class | jh::async::slot_hub |
| Synchronization domain managing timed mutex acquisition and binding exactly one slot. More... | |
| class | jh::async::listener< T > |
| A one-shot inbox that serves as the fan-in aggregation point. More... | |
| class | jh::async::event_signal< T > |
| Lightweight push-only event emitter. More... | |
Namespaces | |
| namespace | jh::async |
| Aggregated entry point for coroutine-based asynchronous facilities. | |
Coroutine-based asynchronous slot/listener/event_signal system with One-Slot-Per-Hub semantics, multi-listener fan-in, and user-controlled fan-out logic inside the slot coroutine.
This module defines a minimal coroutine-driven event dispatch mechanism. A slot is a coroutine that represents arbitrary user-defined behavior. A listener<T> is an awaitable endpoint that provides values of type T into the slot when an event is emitted.
A slot_hub manages synchronization, timeout behavior, and the One-to-One binding between the hub and a single slot . All listeners created from a slot_hub forward events only to the slot bound to that slot_hub.
slot_hub ↔ slot is strictly One-to-One.slot_hub can bind exactly one slot. All listeners made from that slot_hub always deliver values to that same slot. slot → many listeners.listeners are being monitored at different stages or under different conditions. Listening to multiple listeners within the same stage constitutes a synchronization barrier (semantically incorrect). listener → many event_signals (fan-in).listeners may be connected to multiple event_signal objects. All signals write into the same inbox and attempt to resume the slot. The user can distinguish signal sources by encoding IDs/tags in the payload. slot.event_signal performs only "push to listener". Routing, filtering, branching, switching, multi-stage flow control, and fan-out behavior are entirely user-defined inside the slot coroutine. listener succeeds in acquiring the hub's mutex within the timeout window. Once written, the slot is resumed immediately, consumes the value during await_resume(), and the inbox is cleared. write → resume → consume → clearor not delivered at all.
spawn() binds the slot to the calling thread.spawn(), all event-triggered resumes occur on that same thread. slot, slot_hub, and all listeners are expected to share the same lifetime. Moving a slot after binding/spawn may break this constraint and must be avoided. event_signal must not outlive its mapped listener. The usage pattern is conceptually divided into two independent parts. Each part has its own internal ordering constraints, while the two parts themselves do not impose ordering constraints on each other.
These steps must occur in the following order:
slot_hub. listeners from the hub. listeners. slot to the hub via bind_slot(). spawn() to start the coroutine and bind it to the calling thread. listener must already exist before connecting event_signals to it. event_signal::connect(listener*) must be called before the first emit() targeting that listener. emit() should be called after slot.spawn() was called. Advanced patterns (multi-signal, switching, state machines, routing, phase transitions, conditional awaits) are implemented entirely in the slot coroutine. The library provides only the suspension/resume primitives.
slot is created and suspended at initial_suspend(). slot_hub binds exactly one slot; listeners use this binding. spawn() resumes the coroutine for the first time. co_await listener suspends the coroutine and registers its handle. event_signal writes into the listener inbox and resumes the slot after acquiring the hub's timed mutex. co_yield {} yields a monostate and resumes execution immediately — useful for deterministic scheduling points. final_suspend(). Traditional Buffer Queues are presenting a synchronous mechanism, not an asynchronous one, which is why we reject them.
std::timed_mutex is essentially a timed queuing aid with timeout (overpressure) circuit breaking to ensure that emit is invoked according to the order of calls.
| Aspect | Lock Queues | Buffer Queues |
|---|---|---|
| Data Buffering | No — values are written only if the lock is acquired; at most one in-flight value | Yes — values are enqueued and buffered until consumed |
| Queuing Mechanism | FIFO lock waiters — emitters are queued implicitly via the mutex | Explicit data queue — typically implemented via ring buffer or linked list |
| Overflow Control | Timeout — emit is rejected if lock cannot be acquired in time | Unbounded or bounded buffer — may require manual pressure control |
| Resume Semantics | Inline — slot is resumed immediately after value is written | Out-of-band — consumer must poll or wait |
| Synchronization Role | Integral — part of the event delivery protocol | Separate — often requires condition variables or manual signaling |
| Fan-in Behavior | Yes — multiple signals may target a single listener via mutex arbitration | Possible — but usually requires a multiplexer |
| Fan-out Support | No — deliberate omission; routing is done inside slot | Optional — may push to multiple consumers |
slot_hub may bind only one slot. event_signal must not outlive the listener it is connected to. slot, slot_hub, and listeners must share the same overall lifetime. slot alone is responsible for any fan-out or complex routing logic. 1.4.x
2025