# `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. ```nupp local events = nupp.events @derive(events.Event) local record Damage amount: number source: integer kind: string = "physical" end local bus: events.MessageBus = 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` is one, and any record with an `observers` field implements [`nupp.events.Source`](#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](../../neps/0029-typed-events.md) records the design. ## Constructors ### `events.newMessageBus` _constructor_ ```nupp function events.newMessageBus(): events.MessageBus ``` A bus with no observers. The address type is whatever the binding asks for: `local bus: events.MessageBus = events.newMessageBus()`. #### Type parameters | Name | Description | | --- | --- | | `A` | | #### Returns | Type | Description | | --- | --- | | `events.MessageBus\` | the bus | ### `events.newObservers` _constructor_ ```nupp function events.newObservers(): events.Observers ``` Empty observer state for a new source. #### Type parameters | Name | Description | | --- | --- | | `A` | | #### Returns | Type | Description | | --- | --- | | `events.Observers\` | the state, with nothing registered | ## Types ### `Allocator` _interface_ ```nupp interface events.Allocator acquire: function(exclusive self: Allocator): E release: function(exclusive self: Allocator, 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 | Name | Description | | --- | --- | | `E` | | #### Methods ##### `acquire` ```nupp acquire: function(exclusive self: Allocator): E ``` ###### Arguments | Name | Type | Description | | --- | --- | --- | | `exclusive self` | `Allocator\` | | ###### Returns | Type | Description | | --- | --- | | `E` | | ##### `release` ```nupp release: function(exclusive self: Allocator, value: E): nil ``` ###### Arguments | Name | Type | Description | | --- | --- | --- | | `exclusive self` | `Allocator\` | | | `value` | `E` | | ###### Returns | Type | Description | | --- | --- | | `nil` | | ### `Emittable` _interface_ ```nupp 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. ### `MessageBus` _record_ ```nupp record events.MessageBus is events.Source observers: events.Observers observe: function( exclusive self: MessageBus, address: A, event: Type, callback: events.Observer, id: string? ): nil observeOnce: function( exclusive self: MessageBus, address: A, event: Type, callback: events.Observer, id: string? ): nil stopObserving: function( exclusive self: MessageBus, address: A, event: Type, callbackOrId: events.Observer | string ): boolean hasObservers: function(self: MessageBus, address: A, event: Type): boolean emit: function( exclusive self: MessageBus, address: A, event: Type, ...: unpackof Construction(E) ): nil deliver: function( exclusive self: MessageBus, address: A, event: Type, instance: E ): nil clearAddress: function(exclusive self: MessageBus, address: A): nil reset: function(exclusive self: MessageBus): nil setAllocator: function( exclusive self: MessageBus, event: Type, allocator: events.Allocator ): 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 | Name | Description | | --- | --- | | `A` | | #### Methods ##### `observe` ```nupp observe: function( exclusive self: MessageBus, address: A, event: Type, callback: events.Observer, id: string? ): nil ``` Registers an observer; see [`nupp.events.observe`](#nupp.events.observe). ###### Arguments | Name | Type | Description | | --- | --- | --- | | `exclusive self` | `MessageBus\` | | | `address` | `A` | | | `event` | `Type\` | | | `callback` | `events.Observer\` | | | `id` | `string?` | | ###### Returns | Type | Description | | --- | --- | | `nil` | | ##### `observeOnce` ```nupp observeOnce: function( exclusive self: MessageBus, address: A, event: Type, callback: events.Observer, id: string? ): nil ``` Registers an observer consumed on first delivery; see [`nupp.events.observeOnce`](#nupp.events.observeOnce). ###### Arguments | Name | Type | Description | | --- | --- | --- | | `exclusive self` | `MessageBus\` | | | `address` | `A` | | | `event` | `Type\` | | | `callback` | `events.Observer\` | | | `id` | `string?` | | ###### Returns | Type | Description | | --- | --- | | `nil` | | ##### `stopObserving` ```nupp stopObserving: function( exclusive self: MessageBus, address: A, event: Type, callbackOrId: events.Observer | string ): boolean ``` Removes registrations; see [`nupp.events.stopObserving`](#nupp.events.stopObserving). ###### Arguments | Name | Type | Description | | --- | --- | --- | | `exclusive self` | `MessageBus\` | | | `address` | `A` | | | `event` | `Type\` | | | `callbackOrId` | `events.Observer\ | string` | | ###### Returns | Type | Description | | --- | --- | | `boolean` | | ##### `hasObservers` ```nupp hasObservers: function(self: MessageBus, address: A, event: Type): boolean ``` Whether anything observes an event here; see [`nupp.events.hasObservers`](#nupp.events.hasObservers). ###### Arguments | Name | Type | Description | | --- | --- | --- | | `self` | `MessageBus\` | | | `address` | `A` | | | `event` | `Type\` | | ###### Returns | Type | Description | | --- | --- | | `boolean` | | ##### `emit` ```nupp emit: function( exclusive self: MessageBus, address: A, event: Type, ...: unpackof Construction(E) ): nil ``` Constructs and delivers an event; see [`nupp.events.emit`](#nupp.events.emit). ###### Arguments | Name | Type | Description | | --- | --- | --- | | `exclusive self` | `MessageBus\` | | | `address` | `A` | | | `event` | `Type\` | | | `...` | `unpackof Construction(E)` | | ###### Returns | Type | Description | | --- | --- | | `nil` | | ##### `deliver` ```nupp deliver: function( exclusive self: MessageBus, address: A, event: Type, instance: E ): nil ``` Delivers an instance; see [`nupp.events.deliver`](#nupp.events.deliver). ###### Arguments | Name | Type | Description | | --- | --- | --- | | `exclusive self` | `MessageBus\` | | | `address` | `A` | | | `event` | `Type\` | | | `instance` | `E` | | ###### Returns | Type | Description | | --- | --- | | `nil` | | ##### `clearAddress` ```nupp clearAddress: function(exclusive self: MessageBus, address: A): nil ``` Removes every registration at an address; see [`nupp.events.clearAddress`](#nupp.events.clearAddress). ###### Arguments | Name | Type | Description | | --- | --- | --- | | `exclusive self` | `MessageBus\` | | | `address` | `A` | | ###### Returns | Type | Description | | --- | --- | | `nil` | | ##### `reset` ```nupp reset: function(exclusive self: MessageBus): nil ``` Removes every registration; see [`nupp.events.reset`](#nupp.events.reset). ###### Arguments | Name | Type | Description | | --- | --- | --- | | `exclusive self` | `MessageBus\` | | ###### Returns | Type | Description | | --- | --- | | `nil` | | ##### `setAllocator` ```nupp setAllocator: function( exclusive self: MessageBus, event: Type, allocator: events.Allocator ): nil ``` Installs storage for an event type; see [`nupp.events.setAllocator`](#nupp.events.setAllocator). ###### Arguments | Name | Type | Description | | --- | --- | --- | | `exclusive self` | `MessageBus\` | | | `event` | `Type\` | | | `allocator` | `events.Allocator\` | | ###### Returns | Type | Description | | --- | --- | | `nil` | | #### Fields ##### `observers` ```nupp observers: events.Observers ``` ### `Observer` _type_ ```nupp type events.Observer = 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 | Name | Description | | --- | --- | | `E` | | ### `Observers` _record_ ```nupp record events.Observers 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 | Name | Description | | --- | --- | | `A` | | #### Fields ##### `count` ```nupp count: integer ``` How many registrations are live across every address. ### `Source` _interface_ ```nupp interface events.Source observers: events.Observers 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 | Name | Description | | --- | --- | | `A` | | #### Fields ##### `observers` ```nupp observers: events.Observers ``` ## Functions ### `events.clearAddress` _function_ ```nupp function events.clearAddress>(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 | Name | Description | | --- | --- | | `A` | | | `S` | | #### Arguments | Name | Type | Description | | --- | --- | --- | | `exclusive source` | `S` | the bus or world | | `borrows address` | `A` | the address to clear | #### Returns | Type | Description | | --- | --- | | `nil` | | ### `events.deliver` _function_ ```nupp function events.deliver, E is events.Emittable>(exclusive source: S, borrows address: A, event: Type, 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 | Name | Description | | --- | --- | | `A` | | | `S` | | | `E` | | #### Arguments | Name | Type | Description | | --- | --- | --- | | `exclusive source` | `S` | the bus or world | | `borrows address` | `A` | where to deliver | | `event` | `Type\` | the event declaration | | `instance` | `E` | the instance to deliver | #### Returns | Type | Description | | --- | --- | | `nil` | | #### Raises - what an observer raised ### `events.emit` _function_ ```nupp function events.emit, E is events.Emittable>(exclusive source: S, borrows address: A, event: Type, ...: 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 | Name | Description | | --- | --- | | `A` | | | `S` | | | `E` | | #### Arguments | Name | Type | Description | | --- | --- | --- | | `exclusive source` | `S` | the bus or world | | `borrows address` | `A` | where to deliver | | `event` | `Type\` | the event declaration | | `...` | `unpackof Construction(E)` | the event's constructor arguments, positional or named | #### Returns | Type | Description | | --- | --- | | `nil` | | #### Raises - what an observer or the initializer raised, after the lease is released ### `events.Event` _comptime function_ ```nupp comptime function events.Event(info: nupp.derive.Info): nupp.derive.Result ``` 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 | Name | Type | Description | | --- | --- | --- | | `info` | `nupp.derive.Info` | the declaration being derived | #### Returns | Type | Description | | --- | --- | | `nupp.derive.Result\` | the recipe claiming `events.Emittable` | ### `events.hasObservers` _function_ ```nupp function events.hasObservers, E is events.Emittable>(borrows source: S, borrows address: A, event: Type): boolean ``` Whether anything observes one event type at one address. #### Type parameters | Name | Description | | --- | --- | | `A` | | | `S` | | | `E` | | #### Arguments | Name | Type | Description | | --- | --- | --- | | `borrows source` | `S` | the bus or world | | `borrows address` | `A` | the address to ask about | | `event` | `Type\` | the event declaration | #### Returns | Type | Description | | --- | --- | | `boolean` | whether a delivery there would reach an observer | ### `events.id` _function_ ```nupp function events.id(event: Type): 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 | Name | Description | | --- | --- | | `E` | | #### Arguments | Name | Type | Description | | --- | --- | --- | | `event` | `Type\` | the event declaration | #### Returns | Type | Description | | --- | --- | | `integer` | its identity | #### Raises - when the type did not derive `events.Event` ### `events.name` _function_ ```nupp function events.name(event: Type): string ``` The name an event type registered under. #### Type parameters | Name | Description | | --- | --- | | `E` | | #### Arguments | Name | Type | Description | | --- | --- | --- | | `event` | `Type\` | the event declaration | #### Returns | Type | Description | | --- | --- | | `string` | the declared or `@event`-given name | #### Raises - when the type did not derive `events.Event` ### `events.observe` _function_ ```nupp function events.observe, E is events.Emittable>(exclusive source: S, borrows address: A, event: Type, callback: events.Observer, 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 | Name | Description | | --- | --- | | `A` | | | `S` | | | `E` | | #### Arguments | Name | Type | Description | | --- | --- | --- | | `exclusive source` | `S` | the bus or world that owns the observer registry and event storage | | `borrows address` | `A` | where the event is delivered | | `event` | `Type\` | the event declaration | | `callback` | `events.Observer\` | what runs when one is delivered | | `id` | `string?` | an optional name for removal | #### Returns | Type | Description | | --- | --- | | `nil` | | #### Raises - when the type did not derive `events.Event`, or the name is taken ### `events.observeOnce` _function_ ```nupp function events.observeOnce, E is events.Emittable>(exclusive source: S, borrows address: A, event: Type, callback: events.Observer, 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 | Name | Description | | --- | --- | | `A` | | | `S` | | | `E` | | #### Arguments | Name | Type | Description | | --- | --- | --- | | `exclusive source` | `S` | the bus or world to observe through | | `borrows address` | `A` | where the event is delivered | | `event` | `Type\` | the event declaration | | `callback` | `events.Observer\` | what runs, once | | `id` | `string?` | an optional name for removal | #### Returns | Type | Description | | --- | --- | | `nil` | | #### Raises - when the type did not derive `events.Event`, or the name is taken ### `events.reset` _function_ ```nupp function events.reset>(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 | Name | Description | | --- | --- | | `A` | | | `S` | | #### Arguments | Name | Type | Description | | --- | --- | --- | | `exclusive source` | `S` | the bus or world | #### Returns | Type | Description | | --- | --- | | `nil` | | ### `events.setAllocator` _function_ ```nupp function events.setAllocator, E is events.Emittable>(exclusive source: S, event: Type, allocator: events.Allocator): 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 | Name | Description | | --- | --- | | `A` | | | `S` | | | `E` | | #### Arguments | Name | Type | Description | | --- | --- | --- | | `exclusive source` | `S` | the bus or world | | `event` | `Type\` | the event declaration | | `allocator` | `events.Allocator\` | storage of the event's representation | #### Returns | Type | Description | | --- | --- | | `nil` | | #### Raises - when a delivery of that event is active ### `events.stopObserving` _function_ ```nupp function events.stopObserving, E is events.Emittable>(exclusive source: S, borrows address: A, event: Type, callbackOrId: events.Observer | 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 | Name | Description | | --- | --- | | `A` | | | `S` | | | `E` | | #### Arguments | Name | Type | Description | | --- | --- | --- | | `exclusive source` | `S` | the bus or world observed through | | `borrows address` | `A` | where the registration was made | | `event` | `Type\` | the event declaration | | `callbackOrId` | `events.Observer\ | string` | the callback registered, or the name it was given | #### Returns | Type | Description | | --- | --- | | `boolean` | whether anything was removed |