nupp.events

Addressed, synchronous events whose payloads are ordinary declarations.

An event is a record or a fixed-layout struct marked @derive(events.Event). Emitting one by type constructs it into storage the source already owns, and only when something at that address is observing; delivering an instance the caller built allocates nothing. Observers receive the event as a call-scoped borrow they may write to but not keep.

local events = nupp.events

@derive(events.Event)
local record Damage
    amount: number
    source: integer
    kind: string = "physical"
end

local bus: events.MessageBus<integer> = events.newMessageBus()
bus:observe(7, Damage, |event| -> print(event.amount, event.kind), "log")
bus:emit(7, Damage, amount = 10, source = 3)

Here bus is the event source: it owns the observer registry and reusable event storage. 7 is the address within that source, so observers registered at another entity id do not receive this delivery. The MessageBus methods forward to the module functions below; the equivalent direct call is events.observe(bus, 7, Damage, callback, "log").

Observer state belongs to a source: MessageBus<A> is one, and any record with an observers field implements nupp.events.Source and dispatches through the same functions. Record events draw from a nupp.mem.pool and struct events from a nupp.mem.arena, each created the first time an event type is emitted through a source; setAllocator installs one the caller owns instead. NEP 29 records the design.

Module contents

Constructors

ConstructorDescription
newMessageBusA bus with no observers.
newObserversEmpty observer state for a new source.

Types

TypeKindDescription
AllocatorinterfaceStorage an event type is constructed into and returned to.
EmittableinterfaceWhat a declaration becomes by deriving events.Event.
MessageBusrecordA source that is nothing but its observers.
ObservertypeAn observer of one event type.
ObserversrecordObserver registrations for one source, keyed by address and event identity.
SourceinterfaceSomething that owns observer state.

Functions

FunctionKindDescription
clearAddressfunctionRemoves every registration at one address.
deliverfunctionDelivers an event instance the caller already holds.
emitfunctionConstructs an event into the source's storage and delivers it.
Eventcomptime functionMarks a record or a fixed-layout struct as an event.
hasObserversfunctionWhether anything observes one event type at one address.
idfunctionThe identity an event type has in this runtime.
namefunctionThe name an event type registered under.
observefunctionRegisters an observer of one event type at one address.
observeOncefunctionRegisters an observer that is consumed before its first delivery.
resetfunctionRemoves every registration at every address.
setAllocatorfunctionInstalls the storage one event type is constructed into at this source.
stopObservingfunctionRemoves registrations at one address and event.

Constructors#

events.newMessageBusconstructor#

function events.newMessageBus<A>(): events.MessageBus<A>

A bus with no observers.

The address type is whatever the binding asks for: local bus: events.MessageBus<integer> = events.newMessageBus().

Type parameters

NameDescription
A

Returns

TypeDescription
events.MessageBus<A>

the bus

events.newObserversconstructor#

function events.newObservers<A>(): events.Observers<A>

Empty observer state for a new source.

Type parameters

NameDescription
A

Returns

TypeDescription
events.Observers<A>

the state, with nothing registered

Types#

Allocatorinterface#

interface events.Allocator<E>
    acquire: function(exclusive self: Allocator<E>): E
    release: function(exclusive self: Allocator<E>, value: E): nil
end

Storage an event type is constructed into and returned to.

acquire hands out reusable storage of the event's own representation, which the emitting source initializes; release ends that lease when delivery is over. nupp.mem.pool and nupp.mem.arena implement it.

Type parameters

NameDescription
E

Methods

acquire#
acquire: function(exclusive self: Allocator<E>): E
Arguments
NameTypeDescription
exclusive selfAllocator<E>
Returns
TypeDescription
E
release#
release: function(exclusive self: Allocator<E>, value: E): nil
Arguments
NameTypeDescription
exclusive selfAllocator<E>
valueE
Returns
TypeDescription
nil

Emittableinterface#

interface events.Emittable
end

What a declaration becomes by deriving events.Event.

The bound an event type parameter carries, so a source refuses a record that never derived the event contract before anything runs.

MessageBusrecord#

record events.MessageBus<A> is events.Source<A>
    observers: events.Observers<A>
    observe: function<E is events.Emittable>(
        exclusive self: MessageBus<A>,
        address: A,
        event: Type<E>,
        callback: events.Observer<E>,
        id: string?
    ): nil
    observeOnce: function<E is events.Emittable>(
        exclusive self: MessageBus<A>,
        address: A,
        event: Type<E>,
        callback: events.Observer<E>,
        id: string?
    ): nil
    stopObserving: function<E is events.Emittable>(
        exclusive self: MessageBus<A>,
        address: A,
        event: Type<E>,
        callbackOrId: events.Observer<E> | string
    ): boolean
    hasObservers: function<E is events.Emittable>(self: MessageBus<A>, address: A, event: Type<E>): boolean
    emit: function<E is events.Emittable>(
        exclusive self: MessageBus<A>,
        address: A,
        event: Type<E>,
        ...: unpackof Construction(E)
    ): nil
    deliver: function<E is events.Emittable>(
        exclusive self: MessageBus<A>,
        address: A,
        event: Type<E>,
        instance: E
    ): nil
    clearAddress: function(exclusive self: MessageBus<A>, address: A): nil
    reset: function(exclusive self: MessageBus<A>): nil
    setAllocator: function<E is events.Emittable>(
        exclusive self: MessageBus<A>,
        event: Type<E>,
        allocator: events.Allocator<E>
    ): nil
end

A source that is nothing but its observers.

Every method forwards to the function of the same name, passing the bus as the source that owns observer state.

Type parameters

NameDescription
A

Methods

observe#
observe: function<E is events.Emittable>(
    exclusive self: MessageBus<A>,
    address: A,
    event: Type<E>,
    callback: events.Observer<E>,
    id: string?
): nil

Registers an observer; see nupp.events.observe.

Arguments
NameTypeDescription
exclusive selfMessageBus<A>
addressA
eventType<E>
callbackevents.Observer<E>
idstring?
Returns
TypeDescription
nil
observeOnce#
observeOnce: function<E is events.Emittable>(
    exclusive self: MessageBus<A>,
    address: A,
    event: Type<E>,
    callback: events.Observer<E>,
    id: string?
): nil

Registers an observer consumed on first delivery; see nupp.events.observeOnce.

Arguments
NameTypeDescription
exclusive selfMessageBus<A>
addressA
eventType<E>
callbackevents.Observer<E>
idstring?
Returns
TypeDescription
nil
stopObserving#
stopObserving: function<E is events.Emittable>(
    exclusive self: MessageBus<A>,
    address: A,
    event: Type<E>,
    callbackOrId: events.Observer<E> | string
): boolean

Removes registrations; see nupp.events.stopObserving.

Arguments
NameTypeDescription
exclusive selfMessageBus<A>
addressA
eventType<E>
callbackOrIdevents.Observer<E> | string
Returns
TypeDescription
boolean
hasObservers#
hasObservers: function<E is events.Emittable>(self: MessageBus<A>, address: A, event: Type<E>): boolean

Whether anything observes an event here; see nupp.events.hasObservers.

Arguments
NameTypeDescription
selfMessageBus<A>
addressA
eventType<E>
Returns
TypeDescription
boolean
emit#
emit: function<E is events.Emittable>(
    exclusive self: MessageBus<A>,
    address: A,
    event: Type<E>,
    ...: unpackof Construction(E)
): nil

Constructs and delivers an event; see nupp.events.emit.

Arguments
NameTypeDescription
exclusive selfMessageBus<A>
addressA
eventType<E>
...unpackof Construction(E)
Returns
TypeDescription
nil
deliver#
deliver: function<E is events.Emittable>(
    exclusive self: MessageBus<A>,
    address: A,
    event: Type<E>,
    instance: E
): nil

Delivers an instance; see nupp.events.deliver.

Arguments
NameTypeDescription
exclusive selfMessageBus<A>
addressA
eventType<E>
instanceE
Returns
TypeDescription
nil
clearAddress#
clearAddress: function(exclusive self: MessageBus<A>, address: A): nil

Removes every registration at an address; see nupp.events.clearAddress.

Arguments
NameTypeDescription
exclusive selfMessageBus<A>
addressA
Returns
TypeDescription
nil
reset#
reset: function(exclusive self: MessageBus<A>): nil

Removes every registration; see nupp.events.reset.

Arguments
NameTypeDescription
exclusive selfMessageBus<A>
Returns
TypeDescription
nil
setAllocator#
setAllocator: function<E is events.Emittable>(
    exclusive self: MessageBus<A>,
    event: Type<E>,
    allocator: events.Allocator<E>
): nil

Installs storage for an event type; see nupp.events.setAllocator.

Arguments
NameTypeDescription
exclusive selfMessageBus<A>
eventType<E>
allocatorevents.Allocator<E>
Returns
TypeDescription
nil

Fields

observers#
observers: events.Observers<A>

Observertype#

type events.Observer<E> = function(borrows event: E): nil

An observer of one event type.

The event is borrowed for the call: it may be read and written, and cannot be stored or returned. State beyond the event is explicit: capture it in the observer or carry it as an event field.

Type parameters

NameDescription
E

Observersrecord#

record events.Observers<A>
    readonly count: integer
end

Observer registrations for one source, keyed by address and event identity.

A source holds one of these in its observers field. Nothing exists for an address until something observes there, so a world of millions of entities pays for the observers it has and not for the entities it could have.

Type parameters

NameDescription
A

Fields

count#
count: integer

How many registrations are live across every address.

Sourceinterface#

interface events.Source<A>
    observers: events.Observers<A>
end

Something that owns observer state.

A bus or a world implements this by holding an observers field; the dispatch functions take the source exclusively and reach its state through that field. The source is not implicitly passed to observers.

Type parameters

NameDescription
A

Fields

observers#
observers: events.Observers<A>

Functions#

events.clearAddressfunction#

function events.clearAddress<A, S is events.Source<A>>(exclusive source: S, borrows address: A): nil

Removes every registration at one address.

A delivery in flight at that address finishes the list it already holds; the next emission there finds nothing. Registrations at other addresses are untouched, which is how a world clears an entity without losing what observes the world itself.

Type parameters

NameDescription
A
S

Arguments

NameTypeDescription
exclusive sourceS

the bus or world

borrows addressA

the address to clear

Returns

TypeDescription
nil

events.deliverfunction#

function events.deliver<A, S is events.Source<A>, E is events.Emittable>(exclusive source: S, borrows address: A, event: Type<E>, instance: E): nil

Delivers an event instance the caller already holds.

The instance is neither acquired nor released: what the caller built stays the caller's, and is handed to each observer as the borrow its contract names. Everything else is as emit.

Type parameters

NameDescription
A
S
E

Arguments

NameTypeDescription
exclusive sourceS

the bus or world

borrows addressA

where to deliver

eventType<E>

the event declaration

instanceE

the instance to deliver

Returns

TypeDescription
nil

Raises

  • what an observer raised

events.emitfunction#

function events.emit<A, S is events.Source<A>, E is events.Emittable>(exclusive source: S, borrows address: A, event: Type<E>, ...: unpackof Construction(E)): nil

Constructs an event into the source's storage and delivers it.

Nothing is acquired, initialized, or run when no observer is registered at the address for the event: the argument expressions are evaluated as they are for any call, and that is all. Otherwise storage is leased from the event's allocator, the declaration's initializer fills it from the arguments, applying a field default where an argument is nil, every observer registered when the delivery began runs in order, and the lease is released whether the delivery returned, raised, or was cancelled while an observer was suspended.

Type parameters

NameDescription
A
S
E

Arguments

NameTypeDescription
exclusive sourceS

the bus or world

borrows addressA

where to deliver

eventType<E>

the event declaration

...unpackof Construction(E)

the event's constructor arguments, positional or named

Returns

TypeDescription
nil

Raises

  • what an observer or the initializer raised, after the lease is released

events.Eventcomptime function#

comptime function events.Event(info: nupp.derive.Info): nupp.derive.Result<events.Emittable>

Marks a record or a fixed-layout struct as an event.

The derive records the event's name and representation, and asks the compiler for the declaration's initializer: its constructor body, or its field list, run against storage a source already holds. A declaration with several constructors, one whose constructor keeps self or moves an owner into it, an affine field, or a generic owner is refused, since none of those can run against reused storage.

Arguments

NameTypeDescription
infonupp.derive.Info

the declaration being derived

Returns

TypeDescription
nupp.derive.Result<events.Emittable>

the recipe claiming events.Emittable

events.hasObserversfunction#

function events.hasObservers<A, S is events.Source<A>, E is events.Emittable>(borrows source: S, borrows address: A, event: Type<E>): boolean

Whether anything observes one event type at one address.

Type parameters

NameDescription
A
S
E

Arguments

NameTypeDescription
borrows sourceS

the bus or world

borrows addressA

the address to ask about

eventType<E>

the event declaration

Returns

TypeDescription
boolean

whether a delivery there would reach an observer

events.idfunction#

function events.id<E is events.Emittable>(event: Type<E>): integer

The identity an event type has in this runtime.

Assigned once, the first time a source, id, or name asks, from a counter local to this Lua state. It is stable for the life of the state and is never a persistent or wire identity.

Type parameters

NameDescription
E

Arguments

NameTypeDescription
eventType<E>

the event declaration

Returns

TypeDescription
integer

its identity

Raises

  • when the type did not derive events.Event

events.namefunction#

function events.name<E is events.Emittable>(event: Type<E>): string

The name an event type registered under.

Type parameters

NameDescription
E

Arguments

NameTypeDescription
eventType<E>

the event declaration

Returns

TypeDescription
string

the declared or @event-given name

Raises

  • when the type did not derive events.Event

events.observefunction#

function events.observe<A, S is events.Source<A>, E is events.Emittable>(exclusive source: S, borrows address: A, event: Type<E>, callback: events.Observer<E>, id: string?): nil

Registers an observer of one event type at one address.

Observers run in registration order. A name lets stopObserving remove this registration by name; a name already registered at the same address and event is an error, since the second could never be removed alone.

Type parameters

NameDescription
A
S
E

Arguments

NameTypeDescription
exclusive sourceS

the bus or world that owns the observer registry and event storage

borrows addressA

where the event is delivered

eventType<E>

the event declaration

callbackevents.Observer<E>

what runs when one is delivered

idstring?

an optional name for removal

Returns

TypeDescription
nil

Raises

  • when the type did not derive events.Event, or the name is taken

events.observeOncefunction#

function events.observeOnce<A, S is events.Source<A>, E is events.Emittable>(exclusive source: S, borrows address: A, event: Type<E>, callback: events.Observer<E>, id: string?): nil

Registers an observer that is consumed before its first delivery.

The registration is tombstoned before the callback is invoked, so a nested or interleaved delivery of the same event never reaches it again, whatever the callback goes on to do.

Type parameters

NameDescription
A
S
E

Arguments

NameTypeDescription
exclusive sourceS

the bus or world to observe through

borrows addressA

where the event is delivered

eventType<E>

the event declaration

callbackevents.Observer<E>

what runs, once

idstring?

an optional name for removal

Returns

TypeDescription
nil

Raises

  • when the type did not derive events.Event, or the name is taken

events.resetfunction#

function events.reset<A, S is events.Source<A>>(exclusive source: S): nil

Removes every registration at every address.

Allocators are left alone: an arena the caller installed is still theirs, and the source's own storage is reused by the next emission.

Type parameters

NameDescription
A
S

Arguments

NameTypeDescription
exclusive sourceS

the bus or world

Returns

TypeDescription
nil

events.setAllocatorfunction#

function events.setAllocator<A, S is events.Source<A>, E is events.Emittable>(exclusive source: S, event: Type<E>, allocator: events.Allocator<E>): nil

Installs the storage one event type is constructed into at this source.

The allocator is the caller's: it must outlive the source's use of it, and the source never resets or closes it. Replacing one while a delivery holds storage from it is refused, since the lease would have nowhere to return.

Type parameters

NameDescription
A
S
E

Arguments

NameTypeDescription
exclusive sourceS

the bus or world

eventType<E>

the event declaration

allocatorevents.Allocator<E>

storage of the event's representation

Returns

TypeDescription
nil

Raises

  • when a delivery of that event is active

events.stopObservingfunction#

function events.stopObserving<A, S is events.Source<A>, E is events.Emittable>(exclusive source: S, borrows address: A, event: Type<E>, callbackOrId: events.Observer<E> | string): boolean

Removes registrations at one address and event.

Given a name, the first registration with that name goes; given a callback, every registration of it goes. A removal during a delivery takes effect at once for anything not yet reached and is compacted out after the outermost delivery leaves. A caller removing by callback keeps the value it registered: a short function is a fresh object each time its expression runs.

Type parameters

NameDescription
A
S
E

Arguments

NameTypeDescription
exclusive sourceS

the bus or world observed through

borrows addressA

where the registration was made

eventType<E>

the event declaration

callbackOrIdevents.Observer<E> | string

the callback registered, or the name it was given

Returns

TypeDescription
boolean

whether anything was removed