nupp.host

nupp.host asks whatever is running the program for something and waits for the answer: the page's JavaScript in a browser, or the C or Rust application that embeds the runtime natively. Reach for it when a library needs a facility the host has and Nupp does not, such as a canvas, a clipboard or an engine's asset loader, and should not care which host it is running under.

A request names a kind, a dotted name the host answers, and carries a flat list of values. Declare the kind's signature once as a function type and bind it; callers then get named arguments and a checked call:

local host = nupp.host

--- Returns the host's id for the decoded image, and its width and height.
local type DecodeImage = function(url: string, format: string?): (integer, integer, integer)
local type ReleaseImage = function(id: integer): nil

local releaseImage = host.bindPost<ReleaseImage>("tecs.image.release")
local decodeImage = host.bind<DecodeImage>("tecs.image.decode",
    late = function(id: any, ...: any): nil releaseImage(id) end)

local id, width, height = decodeImage(url = "sprites.png")

A call suspends through nupp.suspension until the host answers, so it composes with task scopes, deadlines and cancellation. A host failure is raised in the caller with the host's message. bind allocates its closure once; a call through it passes its arguments straight through rather than gathering them into a table.

Values#

Arguments and results are each a count of flat values: nil, booleans, numbers, strings and byte spans. Every position is kept, so interior and trailing nils arrive as written. A number must be finite and a string must be UTF-8 text; arbitrary bytes are a nupp.mem.span of bytes, never a string holding binary. Tables and functions are refused, naming the argument's position. Structured data is encoded into bytes by the library that owns it.

A byte span is a borrow, so it crosses through a bound kind whose signature says so; call and post take their arguments as any, which a borrow cannot cross, and carry the other values only:

local host = nupp.host
local span = nupp.mem.span

local type Digest = function(borrows bytes: span.ByteSpan): (integer, span.ByteSpan)
local digest = host.bind<Digest>("app.digest")

const text = "hello"
local length, echoed = digest(bytes = span.fromString(text))

A byte result arrives as a byte span the caller owns. A string or byte value has a size limit: a string at most 64 KiB, bytes sent to the host at most 64 MiB, and bytes answered at most 8 MiB. A host names its own resources with integers it assigns.

Results nobody is waiting for#

A call cancelled after it reached the host may still be answered. When the results name a host resource, declare late: it receives those results, runs where no task is waiting, and must not suspend, so it releases through post, which queues a request without waiting for it. A kind without late drops a late answer.

post and bindPost never suspend. The host runs the request like any call; its answer is discarded and its failure reported rather than raised.

Streams#

A stream carries messages one way, without answers. send appends one to a kind's outbound stream, which ships with the frame it was sent in; bindSend binds a kind to a typed function as bind does, which is how bytes are sent. route delivers a kind's inbound messages as nupp.events on a bus the program owns, at an address it picks, with each message's values filling the event's fields in declaration order:

local host = nupp.host
local events = nupp.events

@derive(events.Event)
local struct PointerMove
    x: number
    y: number
end

const WINDOW = 1
local input: events.MessageBus<integer> = events.newMessageBus()
input:observe(WINDOW, PointerMove, |move| -> print(move.x, move.y), "cursor")
local route = host.route("app.pointer.move", PointerMove, input, WINDOW, "dropOldest", 64)

Messages arrive at a turn boundary, never in the middle of the code that runs between them, and an event nothing observes is never built. A stream that fills applies its policy: block suspends the sender until the frame ships, latest keeps only the newest message, and dropOldest drops the oldest past the limit and counts it. An inbound message carries scalars and strings, which is what an event's fields hold; bytes travel on a call.

Natively a send reaches the application's handler at once, so nothing waits to ship and a policy never applies.

Kinds#

A kind is two or more dot-separated segments of letters, digits, _ and -, such as tecs.image.decode. Names under nupp. are reserved for the runtime.

Submodules

ModuleDescription
nupp.host.pumpnupp.host.pump runs one operation at a time for an application that embeds the runtime and owns its own loop.
nupp.host.spiThe interface a nupp.host implementation provides.

Module contents

Types

TypeKindDescription
LatetypeCalled with the results of a call whose caller stopped waiting.
PolicytypeWhat a full stream does with one more message.
RouterecordAn inbound stream's delivery to a bus; close it to stop.

Functions

FunctionKindDescription
attachedfunctionReports whether a host is attached to answer requests.
bindfunctionBinds kind to a function of type F that calls it.
bindPostfunctionBinds kind to a function of type F that posts it.
bindSendfunctionBinds kind's outbound stream to a function of type F that sends on it.
callfunctionSends a request of kind and waits for the host to answer it.
postfunctionQueues a request of kind and returns without waiting for its answer.
routefunctionDelivers kind's inbound messages to bus as events at address.
sendfunctionSends a message on kind's outbound stream.

Types#

Latetype#

type Late = function(...: any): nil

Called with the results of a call whose caller stopped waiting.

Policytype#

type Policy = "block" | "latest" | "dropOldest"

What a full stream does with one more message.

Routerecord#

record host.Route ...

An inbound stream's delivery to a bus; close it to stop.

Members

NameKindDescription
stopmethod
droppedCountmethod
closemethodStops delivering; messages already on their way are discarded.
droppedmethodHow many inbound messages this route has dropped under its policy.

stopmethod#

stop: function(): nil
@private
Returns
TypeDescription
nil

droppedCountmethod#

droppedCount: function(): integer
@private
Returns
TypeDescription
integer

closemethod#

close: function close(self: host.Route): nil

Stops delivering; messages already on their way are discarded.

Arguments
NameTypeDescription
selfhost.Route
Returns
TypeDescription
nil

droppedmethod#

dropped: function dropped(self: host.Route): integer

How many inbound messages this route has dropped under its policy.

Arguments
NameTypeDescription
selfhost.Route
Returns
TypeDescription
integer

Functions#

host.attachedfunction#

function host.attached(): boolean

Reports whether a host is attached to answer requests.

Natively that means an embedding application; a program run on its own has no host. In a browser the page is always attached, though it may answer a kind it does not know with a failure.

Returns

TypeDescription
boolean

host.bindfunction#

function host.bind<F>(kind: string, late: contracts.Late?): F

Binds kind to a function of type F that calls it.

F supplies the parameter names and result types callers see, so a bound kind takes named arguments and checks them. Nothing checks that the host's answer matches F's results.

Type parameters

NameDescription
F

Arguments

NameTypeDescription
kindstring

the dotted name the host answers

latecontracts.Late?

receives the results of a call answered after its caller was cancelled; it must not suspend

Returns

TypeDescription
F

a function calling kind with its arguments

host.bindPostfunction#

function host.bindPost<F>(kind: string): F

Binds kind to a function of type F that posts it.

Type parameters

NameDescription
F

Arguments

NameTypeDescription
kindstring

the dotted name the host answers

Returns

TypeDescription
F

a function posting kind with its arguments

host.bindSendfunction#

function host.bindSend<F>(kind: string, policy: contracts.Policy?, limit: integer?): F

Binds kind's outbound stream to a function of type F that sends on it.

Type parameters

NameDescription
F

Arguments

NameTypeDescription
kindstring

the dotted name the host answers

policycontracts.Policy?

what a full stream does with one more message, block unless named

limitinteger?

how many unshipped messages make the stream full, 1024 unless named

Returns

TypeDescription
F

a function sending on kind with its arguments

host.callfunction#

function host.call(kind: string, ...: any): ...any

Sends a request of kind and waits for the host to answer it.

Arguments

NameTypeDescription
kindstring

the dotted name the host answers

...any

Returns

TypeDescription
...any

the host's results, each position kept

Raises

TypeCondition
string

when a value cannot cross, no host answers the kind, or the host fails the request

host.postfunction#

function host.post(kind: string, ...: any): nil

Queues a request of kind and returns without waiting for its answer.

Arguments

NameTypeDescription
kindstring

the dotted name the host answers

...any

Returns

TypeDescription
nil

Raises

TypeCondition
string

when a value cannot cross or too many posts are pending

host.routefunction#

function host.route<E is events.Emittable>(kind: string, event: Type<E>, bus: events.MessageBus<integer>, address: integer, policy: contracts.Policy?, limit: integer?): host.Route

Delivers kind's inbound messages to bus as events at address.

Addresses are integers, the ids a host gives its windows, pads and devices.

Type parameters

NameDescription
E

Arguments

NameTypeDescription
kindstring

the dotted name the host pushes

eventType<E>

the event each message fills, its fields in declaration order

busevents.MessageBus<integer>
addressinteger
policycontracts.Policy?

latest or dropOldest; dropOldest unless named

limitinteger?

how many messages one turn delivers, 1024 unless named

Returns

TypeDescription
host.Route

the route, which stops when closed

Raises

TypeCondition
string

when policy is block, which an inbound stream cannot do

host.sendfunction#

function host.send(kind: string, ...: any): nil

Sends a message on kind's outbound stream.

Arguments

NameTypeDescription
kindstring

the dotted name the host answers

...any

Returns

TypeDescription
nil

Raises

TypeCondition
string

when a value cannot cross