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
(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
| Module | Description |
|---|---|
nupp.host.pump | nupp.host.pump runs one operation at a time for an application that embeds the runtime and owns its own loop. |
nupp.host.spi | The interface a nupp.host implementation provides. |
Module contents
Types
| Type | Kind | Description |
|---|---|---|
Late | type | Called with the results of a call whose caller stopped waiting. |
Policy | type | What a full stream does with one more message. |
Route | record | An inbound stream's delivery to a bus; close it to stop. |
Functions
| Function | Kind | Description |
|---|---|---|
attached | function | Reports whether a host is attached to answer requests. |
bind | function | Binds kind to a function of type F that calls it. |
bindPost | function | Binds kind to a function of type F that posts it. |
bindSend | function | Binds kind's outbound stream to a function of type F that sends on it. |
call | function | Sends a request of kind and waits for the host to answer it. |
post | function | Queues a request of kind and returns without waiting for its answer. |
route | function | Delivers kind's inbound messages to bus as events at address. |
send | function | Sends a message on kind's outbound stream. |
Types#
Latetype#
type Late = function(...: any): nilCalled 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
| Name | Kind | Description |
|---|---|---|
stop | method | |
droppedCount | method | |
close | method | Stops delivering; messages already on their way are discarded. |
dropped | method | How many inbound messages this route has dropped under its policy. |
Functions#
host.attachedfunction#
function host.attached(): booleanReports 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
| Type | Description |
|---|---|
boolean |
host.bindfunction#
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
| Name | Description |
|---|---|
F |
Arguments
| Name | Type | Description |
|---|---|---|
kind | string | the dotted name the host answers |
late | contracts.Late? | receives the results of a call answered after its caller was cancelled; it must not suspend |
Returns
| Type | Description |
|---|---|
F | a function calling |
host.bindPostfunction#
function host.bindPost<F>(kind: string): FBinds kind to a function of type F that posts it.
Type parameters
| Name | Description |
|---|---|
F |
Arguments
| Name | Type | Description |
|---|---|---|
kind | string | the dotted name the host answers |
Returns
| Type | Description |
|---|---|
F | a function posting |
host.bindSendfunction#
Binds kind's outbound stream to a function of type F that sends on it.
Type parameters
| Name | Description |
|---|---|
F |
Arguments
| Name | Type | Description |
|---|---|---|
kind | string | the dotted name the host answers |
policy | contracts.Policy? | what a full stream does with one more message, |
limit | integer? | how many unshipped messages make the stream full, 1024 unless named |
Returns
| Type | Description |
|---|---|
F | a function sending on |
host.callfunction#
function host.call(kind: string, ...: any): ...anySends a request of kind and waits for the host to answer it.
Arguments
| Name | Type | Description |
|---|---|---|
kind | string | the dotted name the host answers |
... | any |
Returns
| Type | Description |
|---|---|
...any | the host's results, each position kept |
Raises
| Type | Condition |
|---|---|
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): nilQueues a request of kind and returns without waiting for its answer.
Arguments
| Name | Type | Description |
|---|---|---|
kind | string | the dotted name the host answers |
... | any |
Returns
| Type | Description |
|---|---|
nil |
Raises
| Type | Condition |
|---|---|
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.RouteDelivers 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
| Name | Description |
|---|---|
E |
Arguments
| Name | Type | Description |
|---|---|---|
kind | string | the dotted name the host pushes |
event | Type<E> | the event each message fills, its fields in declaration order |
bus | events.MessageBus<integer> | |
address | integer | |
policy | contracts.Policy? |
|
limit | integer? | how many messages one turn delivers, 1024 unless named |
Returns
| Type | Description |
|---|---|
host.Route | the route, which stops when closed |
Raises
| Type | Condition |
|---|---|
string | when |
host.sendfunction#
function host.send(kind: string, ...: any): nilSends a message on kind's outbound stream.
Arguments
| Name | Type | Description |
|---|---|---|
kind | string | the dotted name the host answers |
... | any |
Returns
| Type | Description |
|---|---|
nil |
Raises
| Type | Condition |
|---|---|
string | when a value cannot cross |