ddn.hap

Module ddn.hap

Provides DDN Haps: synchronous, in-process, application-level notification primitives.

Haps are small multicast observer containers. A subject owns a hap, observers attach handlers to it, and the subject emits the hap when something meaningful "hap"-pens. (You should understand now why I named it "hap"?)

Types 20

private structRegistrationRecord(Handler)

One attached handler record inside HapState, identified by a monotonically increasing id so Registration handles stay stable across splices.

Fields
size_t idUnique id assigned by the owning hap.
Handler handlerThe attached handler; `.init` marks a removed slot.
private classHapState(Handler)

Heap-backed state shared between a hap and its Registration handles.

Fields
RegistrationRecord!Handler[] recordsAttached handlers; may be replaced (not mutated) while dispatching.
size_t nextIdNext id handed out by the owning hap.
size_t dispatchDepthNumber of in-flight dispatches; copy-on-write is active while non-zero.
bool alive`false` once the owning hap has been destroyed.
Methods
size_t activeCount() const @safe nothrow @nogcCount the attached records. Only live handlers are ever stored, so this is simply the record count.
void addRecord(size_t id, Handler handler) @safeAppend a handler record, copying first when a dispatch is in flight.
void removeRecordById(size_t id) @safeRemove the record with the given id, copying first when dispatching.
void removeFirstByHandler(Handler handler) @safeRemove the first record whose handler matches, copying first when dispatching.
void removeAll() @safe nothrowDrop every record. In-flight snapshots keep their own reference.
void markDead() @safe nothrowRecord that the owning hap has been destroyed.
private void removeAt(size_t index) @safeSplice one record out by index; the caller handles copy-on-write.
structRegistration(Handler)

A registration handle returned by Hap, Hap.register or NothrowHap, NothrowHap.register.

Registration represents exactly one attached handler and provides explicit, idempotent detach. It is non-copyable and move-only.

A plain Registration does not auto-detach in its destructor; automatic cleanup belongs to scoped registration helpers and RegistrationBag.

Parameters

HandlerThe delegate handler type of the source hap.
Fields
private HapState!Handler _state
private size_t _id
private bool _detached
Methods
bool attached() const @safe nothrow @nogcDetermine whether this registration's source hap is alive and this registration has not been detached.
bool detach() @safeDetach this registration from its source hap.
Constructors
this(HapState!Handler state, size_t id)
structScopedRegistration(Handler)

An RAII wrapper that detaches a Registration, registration automatically at scope exit.

ScopedRegistration is non-copyable and safely movable. When a scoped registration is destroyed, it calls detach() on the underlying Registration. Call release() to transfer ownership or disable automatic cleanup; release() returns the underlying Registration by move and leaves the scoped wrapper inert.

Parameters

HandlerThe delegate handler type of the source hap.
Fields
private Registration!Handler _reg
Methods
bool attached() const @safe nothrow @nogcDetermine whether this scoped registration's underlying registration is still attached.
bool detach() @safeDetach the underlying registration manually.
Registration!Handler release() @safe nothrowRelease ownership of the underlying registration without detaching.
Constructors
this(Registration!Handler reg)
Destructors
~thisDestroy this scoped registration, detaching the underlying registration if it is still attached.
private structDetachRecord

One stored detach callback inside RegistrationBag.

Fields
bool delegate() @safe detachDetach callback; `null` marks an already-cleared entry.
private classOneShotState(Handler)

Shared state behind a pending registerOneShot registration.

Fields
Handler userHandlerThe wrapped user handler, invoked at most once.
Registration!Handler regThe underlying regular registration.
bool fired`true` once the user handler has fired.
Constructors
this(Handler h)Wrap a user handler.
structOneShotRegistration(Handler)

A handle returned by registerOneShot that can cancel a pending one-shot registration.

OneShotRegistration is non-copyable and move-only. Call cancel() to prevent the one-shot handler from ever firing, or check attached() to see if it is still pending.

Parameters

HandlerThe delegate handler type of the source hap.
Fields
private OneShotState!Handler _state
Methods
bool attached() const @safe nothrow @nogcDetermine whether the one-shot handler has not yet fired and has not been cancelled.
bool cancel() @safeCancel the one-shot registration.
Constructors
this(OneShotState!Handler state)
private classHeapRegistration(Handler)

Heap-anchored registration so an Aggregate-style container can hold a Registration by pointer and keep its detach callback addressable.

Fields
Registration!Handler regThe anchored registration.
Constructors
this(ref Registration!Handler r)Take ownership of a registration.

An owner-held container that detaches multiple heterogeneous registrations in bulk.

RegistrationBag stores registrations from any number of Hap or NothrowHap instances, regardless of their argument types, using internal type erasure. Call clear() or let the bag be destroyed to detach all live registrations.

Multiple private bags are expected in real programs: each component, library, plugin, controller, or test fixture that creates registrations should own its own bag. A global or shared bag is not recommended.

RegistrationBag is non-copyable and move-only.

Fields
private DetachRecord[] _records
Methods
void add(Handler)(ref Registration!Handler reg) @safeAdd a Registration to this bag.
void add(Handler)(ref ScopedRegistration!Handler scoped) @safe nothrowAdd a ScopedRegistration to this bag.
void register(Args...)(ref Hap!Args hap, void delegate(Args) @safe handler) @safeRegister a handler on a hap and add the resulting registration to this bag.
void register(Args...)(ref Hap!Args hap, void function(Args) @safe handler) @safeditto
void register(Args...)(ref NothrowHap!Args hap, void delegate(Args) @safe nothrow handler) @safe nothrowditto
void register(Args...)(ref NothrowHap!Args hap, void function(Args) @safe nothrow handler) @safe nothrowditto
size_t length() const @safe nothrow @nogcDetermine the number of detach records in this bag.
void clear() @safeDetach all registrations held by this bag.
Destructors
~thisDestroy this bag, detaching all live registrations.
structHap(Args...)

A multicast hap that invokes all attached handlers synchronously.

Hap is the general-purpose, GC-backed variant for ordinary application code. It accepts @safe handlers, supports throwing handlers, and uses snapshot dispatch semantics: mutations made during emit affect later emits, not the handler slice currently being iterated.

Hap is intentionally not thread-safe. Use it as thread-confined owned state, or provide external synchronization when deliberately sharing an owning object.

Parameters

ArgsArgument types passed to attached handlers.
Fields
private HapState!Handler _state
Methods
bool hasHaps() const @safe nothrow @nogcDetermine whether at least one handler is attached.
size_t hapCount() const @safe nothrow @nogcGet the number of currently attached handlers.
void opOpAssign(string op : "~")(Handler handler) @safeAttach a delegate handler with `~=` syntax.
void opOpAssign(string op : "~")(FunctionHandler handler) @safeAttach a free-function handler with `~=` syntax.
void attach(Handler handler) @safeAttach a delegate handler.
void attach(FunctionHandler handler) @safeAttach a free-function handler.
void detach(Handler handler) @safeDetach the first matching delegate handler by delegate identity.
void detach(FunctionHandler handler) @safeDetach the first matching free-function handler.
void detachAll() @safe nothrowDetach all handlers.
Registration!Handler register(Handler handler) @safeRegister a delegate handler and return a detachable Registration.
Registration!Handler register(FunctionHandler handler) @safeRegister a free-function handler and return a detachable registration.
void opCall(Args args) @safeEmit this hap by invoking all handlers captured at the start of dispatch.
void emit(Args args) @safeNamed alternative to `opCall`.
void emitSafe(Args args, scope void delegate(Exception) @safe onError = null) @safeEmit this hap while reporting handler exceptions and continuing dispatch.
private void ensureState() @safe nothrow
Destructors
~thisMark the shared state dead on destruction so registrations that outlive this hap report `attached() == false` and refuse to detach.
structNothrowHap(Args...)

A multicast hap restricted to handlers that cannot throw exceptions.

NothrowHap has the same ownership and snapshot semantics as Hap, but its handlers are nothrow, so emit is also nothrow.

Parameters

ArgsArgument types passed to attached handlers.
Fields
private HapState!Handler _state
Methods
bool hasHaps() const @safe nothrow @nogcDetermine whether at least one handler is attached.
size_t hapCount() const @safe nothrow @nogcGet the number of currently attached handlers.
void opOpAssign(string op : "~")(Handler handler) @safeAttach a nothrow delegate handler with `~=` syntax.
void opOpAssign(string op : "~")(FunctionHandler handler) @safeAttach a nothrow free-function handler with `~=` syntax.
void attach(Handler handler) @safeAttach a nothrow delegate handler.
void attach(FunctionHandler handler) @safeAttach a nothrow free-function handler.
void detach(Handler handler) @safeDetach the first matching delegate handler by delegate identity.
void detach(FunctionHandler handler) @safeDetach the first matching free-function handler.
void detachAll() @safe nothrowDetach all handlers.
Registration!Handler register(Handler handler) @safe nothrowRegister a nothrow delegate handler and return a detachable registration.
Registration!Handler register(FunctionHandler handler) @safe nothrowRegister a nothrow free-function handler and return a detachable registration.
void opCall(Args args) @safe nothrowEmit this hap by invoking all nothrow handlers captured at dispatch start.
void emit(Args args) @safe nothrowNamed alternative to `opCall`.
private void ensureState() @safe nothrow
Destructors
~thisMark the shared state dead on destruction so registrations that outlive this hap report `attached() == false` and refuse to detach.
structStaticHap(size_t capacity, Args...)

A fixed-capacity multicast hap for @nogc nothrow dispatch paths.

StaticHap stores handlers inline, never allocates, and reports attachment failure when the configured capacity is reached.

StaticHap does not provide register because the registration lifetime model requires heap-backed state that would compromise @nogc dispatch. Use attach and detach for StaticHap handler management.

Parameters

capacityMaximum number of handlers stored by this hap.
ArgsArgument types passed to attached handlers.
Fields
private Handler[capacity] _handlers
private size_t _count
Methods
bool hasHaps() const @safe nothrow @nogcDetermine whether at least one handler is attached.
size_t hapCount() const @safe nothrow @nogcGet the number of currently attached handlers.
bool isFull() const @safe nothrow @nogcDetermine whether the fixed handler storage is full.
bool attach(Handler handler) @safe nothrow @nogcAttach a `@nogc nothrow` delegate handler.
bool attach(FunctionHandler handler) @safe nothrow @nogcAttach a `@nogc nothrow` free-function handler.
void opOpAssign(string op : "~")(Handler handler) @safe nothrow @nogcAttach a `@nogc nothrow` delegate handler with `~=` syntax.
void opOpAssign(string op : "~")(FunctionHandler handler) @safe nothrow @nogcAttach a `@nogc nothrow` free-function handler with `~=` syntax.
bool detach(Handler handler) @safe nothrow @nogcDetach the first matching delegate handler by delegate identity.
bool detach(FunctionHandler handler) @safe nothrow @nogcDetach the first matching free-function handler.
void detachAll() @safe nothrow @nogcDetach all handlers.
void opCall(Args args) @safe nothrow @nogcEmit this hap by invoking a stack-local snapshot of fixed handler storage.
void emit(Args args) @safe nothrow @nogcNamed alternative to `opCall`.
structProperty(T)

An observable value that emits when assignment changes the stored value.

Property!T is a small subject in observer-pattern terms: it owns one value of type T and emits onChanged with the old and new values whenever a successful assignment occurs. Unchanged assignments (where the new value compares equal to the old value) do not emit.

Property is non-copyable because the embedded onChanged hap is an owned notification channel. Move semantics are available via core.lifetime.move.

Parameters

TThe type of the stored value.
Fields
Hap!(T, T) onChangedEmitted with `(oldValue, newValue)` after an accepted assignment.
private T _value
Methods
const(T) value() @property ref const return @safeGet the current value.
bool set(T newValue) @safeSet the current value.
void opAssign(T newValue) @safeAssign a new value, emitting `onChanged` when the value changes.
structSharedHap(Args...)

A thread-safe multicast hap that invokes all attached handlers synchronously.

SharedHap provides the same attach/detach/emit semantics as Hap but with internal synchronization for safe use across threads. Handlers are invoked outside the internal lock to prevent deadlocks during re-entrant calls. The internal state is published with an atomic compare-and-swap on first use and every access reads it with acquire semantics, so concurrent first use from multiple threads is safe: no handler is ever lost to an initialization race.

Snapshot semantics: emit captures a snapshot of current handlers under the lock, then releases the lock before invoking handlers. Mutations during emit affect future snapshots, not the current one. A handler already captured in an in-flight snapshot may still run after detach or detachAll returns.

SharedHap is non-copyable and move-only. It uses reference-backed shared state so in-flight snapshots remain valid even if the public SharedHap value is moved or destroyed after snapshot capture.

Note

SharedHap intentionally provides no register (detachable handle) or

emitSafe (error-reporting dispatch) counterparts. When those are needed, use Hap or NothrowHap, which are single-threaded by design.

Parameters

ArgsArgument types passed to attached handlers.
Fields
private SharedState _state
Methods
bool hasHaps() @safeDetermine whether at least one handler is attached.
size_t hapCount() @safeGet the number of currently attached handlers.
void opOpAssign(string op : "~")(Handler handler) @safeAttach a delegate handler with `~=` syntax.
void opOpAssign(string op : "~")(FunctionHandler handler) @safeAttach a free-function handler with `~=` syntax.
void attach(Handler handler) @safeAttach a delegate handler.
void attach(FunctionHandler handler) @safeAttach a free-function handler.
void detach(Handler handler) @safeDetach the first matching delegate handler by delegate identity.
void detach(FunctionHandler handler) @safeDetach the first matching free-function handler.
void detachAll() @safeDetach all handlers.
void opCall(Args args) @safeEmit this hap by invoking all handlers captured at dispatch start.
void emit(Args args) @safeNamed alternative to `opCall`.
private SharedState loadState() @safe nothrowRead the shared state with acquire semantics so a thread can never observe a stale `null` after another thread has published the state.
private void ensureState() @safe nothrowCreate the shared state on first use, publishing it atomically.
private void removeAt(size_t index) @safe
Nested Templates
SharedRecordOne attached handler in `SharedState`.
SharedStateLock-protected handler list shared by all `SharedHap` handles and threads.
structPriorityHap(Args...)

A multicast hap that dispatches handlers in priority order.

PriorityHap associates each handler with an integer priority. Handlers with lower priority values run first. Handlers with equal priority run in stable attachment order. This is a separate type from Hap to keep normal attachment-order dispatch simple and fast.

PriorityHap supports attach, detach, detachAll, emit, and hasHaps/hapCount. It is non-copyable and move-only.

Parameters

ArgsArgument types passed to attached handlers.
Fields
private PriorityRecord[] _records
private size_t _nextOrder
private size_t _dispatchDepth
Methods
bool hasHaps() const @safe nothrow @nogcDetermine whether at least one handler is attached.
size_t hapCount() const @safe nothrow @nogcGet the number of currently attached handlers.
void attach(Handler handler, int priority = 0) @safeAttach a delegate handler with the given priority.
void attach(FunctionHandler handler, int priority = 0) @safeAttach a free-function handler with the given priority.
void opOpAssign(string op : "~")(Handler handler) @safeAttach a delegate handler with `~=` syntax at default priority 0.
void opOpAssign(string op : "~")(FunctionHandler handler) @safeAttach a free-function handler with `~=` syntax at default priority 0.
void detach(Handler handler) @safeDetach the first matching delegate handler by delegate identity.
void detach(FunctionHandler handler) @safeDetach the first matching free-function handler.
void detachAll() @safe nothrowDetach all handlers.
void opCall(Args args) @safeEmit this hap by invoking all handlers in priority order.
void emit(Args args) @safeNamed alternative to `opCall`.
private void sortRecords() @safe
private void removeAt(size_t index) @safe
Nested Templates
PriorityRecordOne attached handler with its priority and insertion order.

Cancellation state passed to handlers during a CancellableHap emit.

A handler calls cancel() to stop later handlers in the same emit from running. Cancellation is one-way: once cancelled, it cannot be undone.

Fields
private bool _cancelled
Methods
void cancel() @safe nothrowCancel propagation. Later handlers in this emit will not run.
bool cancelled() const @safe nothrow @nogcCheck whether propagation has been cancelled.
structCancellableHap(Args...)

A multicast hap that supports propagation-stopping workflows.

CancellableHap passes a CancelToken as the first argument to each handler. When a handler calls token.cancel(), subsequent handlers in that emit are skipped. Handlers before cancellation run in attachment order.

This is a separate type from Hap to keep normal dispatch simple and to make cancellation explicit at the handler signature level.

Parameters

ArgsArgument types passed to attached handlers (excluding CancelToken).
Fields
private Handler[] _handlers
private size_t _dispatchDepth
Methods
bool hasHaps() const @safe nothrow @nogcDetermine whether at least one handler is attached.
size_t hapCount() const @safe nothrow @nogcGet the number of currently attached handlers.
void opOpAssign(string op : "~")(Handler handler) @safeAttach a delegate handler with `~=` syntax.
void attach(Handler handler) @safeAttach a delegate handler.
void detach(Handler handler) @safeDetach the first matching delegate handler.
void attach(FunctionHandler handler) @safeAttach a free-function handler.
void opOpAssign(string op : "~")(FunctionHandler handler) @safeAttach a free-function handler with `~=` syntax.
void detach(FunctionHandler handler) @safeDetach the first matching free-function handler.
void detachAll() @safe nothrowDetach all handlers.
void emit(Args args) @safeEmit this hap. Handlers receive a `CancelToken` followed by `args`.
void opCall(Args args) @safeInvocation-syntax counterpart of `emit`.
private void removeAt(size_t index) @safe
structQueryHap(Result, Args...)

A multicast hap whose handlers return a value that is collected.

QueryHap is an observer container where every handler answers the event: emit invokes all attached handlers in attachment order and returns their results as a Result[]. Use it to fan a request out to several responders and gather every answer, such as validation votes, plugin lookups, or metric samples.

Snapshot semantics: emit captures the handler list at dispatch start, so attaching or detaching during dispatch affects only future emissions.

Parameters

ResultThe return type of each handler.
ArgsArgument types passed to attached handlers.
Fields
private Handler[] _handlers
private size_t _dispatchDepth
Methods
bool hasHaps() const @safe nothrow @nogcDetermine whether at least one handler is attached.
size_t hapCount() const @safe nothrow @nogcGet the number of currently attached handlers.
void opOpAssign(string op : "~")(Handler handler) @safeAttach a delegate handler with `~=` syntax.
void attach(Handler handler) @safeAttach a delegate handler.
void attach(FunctionHandler handler) @safeAttach a free-function handler.
void opOpAssign(string op : "~")(FunctionHandler handler) @safeAttach a free-function handler with `~=` syntax.
void detach(Handler handler) @safeDetach the first matching delegate handler.
void detach(FunctionHandler handler) @safeDetach the first matching free-function handler.
void detachAll() @safe nothrowDetach all handlers.
Result[] emit(Args args) @safeInvoke every attached handler and collect the results.
Result[] opCall(Args args) @safeInvocation-syntax counterpart of `emit`.
private void removeAt(size_t index) @safe
structReplayHap(Args...)

A hap that replays the last emitted value to newly attached handlers.

ReplayHap stores the most recent arguments from the last emit call. When a handler is attached, it immediately receives the stored value if one exists. Subsequent emit calls update the stored value and invoke all handlers as usual.

This is useful for state-like events such as settings, selection state, or configuration values where new observers need the current value immediately.

Parameters

ArgsArgument types for the hap.
Fields
private Handler[] _handlers
private Tuple!Args _lastValue
private bool _hasValue
private size_t _dispatchDepth
Methods
bool hasHaps() const @safe nothrow @nogcDetermine whether at least one handler is attached.
size_t hapCount() const @safe nothrow @nogcGet the number of currently attached handlers.
bool hasValue() const @safe nothrow @nogcDetermine whether a value has been emitted.
void opOpAssign(string op : "~")(Handler handler) @safeAttach a delegate handler with `~=` syntax. Replays the stored value to the new handler when one exists.
void attach(Handler handler) @safeAttach a handler. If a value has been emitted, the handler is invoked immediately with the stored value.
void attach(FunctionHandler handler) @safeAttach a free-function handler. If a value has been emitted, the handler is invoked immediately with the stored value.
void opOpAssign(string op : "~")(FunctionHandler handler) @safeAttach a free-function handler with `~=` syntax.
void detach(Handler handler) @safeDetach the first matching delegate handler.
void detach(FunctionHandler handler) @safeDetach the first matching free-function handler.
void detachAll() @safe nothrowDetach all handlers. Stored values are kept; use `reset` to clear them.
void emit(Args args) @safeEmit and store the value. All handlers are invoked, and the value is stored for replay to future attach calls.
void opCall(Args args) @safeInvocation-syntax counterpart of `emit`.
void reset() @safe nothrowReset the stored value. Future attach calls will not replay until emit is called again.
private void removeAt(size_t index) @safe
structAggregate

Aggregate multiple upstream haps into a single callback.

Aggregate registers on each upstream hap and invokes the callback whenever any upstream fires. Cleanup happens through clear() or destruction.

Fields
private DetachRecord[] _records
Methods
void add(Args...)(ref Hap!Args hap, void delegate(Args) @safe handler) @safeConnect an upstream hap to this aggregate.
void add(Args...)(ref Hap!Args hap, void function(Args) @safe handler) @safeditto
void clear() @safeDisconnect all upstream registrations.
Destructors
~thisDestroy this aggregate, disconnecting all upstream registrations.

Functions 13

fnauto scopedRegister(Args...)(ref Hap!Args hap, void delegate(Args) @safe handler) @safeCreate a scoped registration that detaches automatically at scope exit.
fnauto scopedRegister(Args...)(ref Hap!Args hap, void function(Args) @safe handler) @safeditto
fnauto scopedRegister(Args...)(ref NothrowHap!Args hap, void delegate(Args) @safe nothrow handler) @safe nothrowditto
fnauto scopedRegister(Args...)(ref NothrowHap!Args hap, void function(Args) @safe nothrow handler) @safe nothrowditto
fnauto registerOneShot(Args...)(ref Hap!Args hap, void delegate(Args) @safe handler) @safeRegister a one-shot delegate handler on a Hap that detaches itself after the first invocation.
fnauto registerOneShot(Args...)(ref NothrowHap!Args hap, void delegate(Args) @safe nothrow handler) @safe nothrowditto
fnvoid registerInto(Args...)(ref RegistrationBag bag, ref Hap!Args hap, void delegate(Args) @safe handler) @safeRegister a handler on a hap and add the resulting registration to a bag.
fnvoid registerInto(Args...)(ref RegistrationBag bag, ref Hap!Args hap, void function(Args) @safe handler) @safeditto
fnvoid registerInto(Args...)(ref RegistrationBag bag, ref NothrowHap!Args hap, void delegate(Args) @safe nothrow handler) @safe nothrowditto
fnvoid registerInto(Args...)(ref RegistrationBag bag, ref NothrowHap!Args hap, void function(Args) @safe nothrow handler) @safe nothrowditto
fnauto registerFiltered(Args...)(ref Hap!Args hap, bool delegate(Args) @safe predicate, void delegate(Args) @safe handler) @safeRegister a filtered delegate handler that invokes only when a predicate matches.
fnauto registerFiltered(Args...)(ref NothrowHap!Args hap, bool delegate(Args) @safe nothrow predicate, void delegate(Args) @safe nothrow handler) @safe nothrowditto
fnauto pipe(SourceArgs, TargetArgs)( ref Hap!SourceArgs source, Hap!TargetArgs * target, void delegate(SourceArgs, ref Hap!TargetArgs) @safe transform) @safePipe events from a source hap through a transform function to a target hap.