The control channel¶
Protocol 11. Generated from the channel's declaration (tools/lusine/sdk/channel.py) at build, as the clients are: this page and every client come from the one declaration, so none can disagree with another. A client speaks JSON lines: one request, {"id": n, "op": name, ...arguments}, and one reply, the envelope and the operation's own fields.
Each example below is rendered from the declaration: the JSON line a client sends, and the call in each generated client: Python (lusine_channel.py, shipped in sdk/clients/python/), JavaScript (lusine_channel.js, in sdk/clients/javascript/, for Node) and Lua (lusine_channel.lua, in sdk/clients/lua/). An operation's reply is its table's fields beside the envelope.
A session compiles Trait and calls procedures. compile takes Trait source (functions, procedures and the session's own output quantities) and compiles it against the running game; call runs a procedure by name, the session's, the game's, or step, one step of the game. An action is a compiled procedure that inserts; a read is an output a procedure writes, answered by the call that wrote it, a float in its declared unit and an integer as its count of quanta. The Text tab shows each example as the console takes it (lusine.sdk.wire): NAME(); is a call, anything else Trait source.
The envelope¶
| Field | Type | Unit | |
|---|---|---|---|
id |
int | the request's id, echoed | |
ok |
bool | true, or false for a refusal | |
step |
int | step | the step the engine is at |
refused |
text | a refusal's code, one of the closed set (optional) | |
message |
text | a refusal's reason, for a person (optional) | |
line |
int | a refusal of compiled text: the line in the declaration it names (declaration), else in the text sent (optional) |
|
column |
int | a refusal of compiled text: the column in the line (optional) | |
declaration |
text | a refusal of compiled text: the declaration its line counts in (optional) |
Refusals¶
A refusal is an answer: the world is unchanged and the session carries on. The codes are a closed set; a code outside it is a protocol failure, and a new code is a row of tools/lusine/errors.py first.
| Code | When |
|---|---|
bad-request |
the request's shape: a missing or ill-typed argument, a field outside its declared range |
name |
a name the definition does not declare, refused where it is written |
stamp |
an action stamped with a step already passed, or a readout past the engine's history |
action |
an action the game's own rule refuses: out of turn, out of balls |
size |
a load whose extents the running scene cannot take |
io |
a file that cannot be read or written |
not-in-this-build |
an operation or a readout the release does not carry (ship=False), in a release build: an instrument, refused by code rather than absent |
script-timeout |
a script's done for a step that came after the step's budget: the engine went on without its operations |
declare |
a declare whose text is not one line of Trait's runtime subset (readout, event, band, say, sound, setting, panel, on), or is not well formed |
unresolved |
a name in declared text that resolves to no declaration the session can reach |
ambiguous |
a name declared twice under one owner |
owner |
a declaration under another owner's name, or a reach into another session's |
unit |
an expression whose dimensions disagree, or a readout whose expression is not in its declared unit |
slot |
a line's slot with no band and no event field to fill it, or a key no declaration names |
budget |
a declaration past the session's budget of live reduce instances |
device |
a Vulkan object or call the engine needs failed |
unsupported |
an operation this device or this build cannot do: GPU timestamps a run needs, a second compute queue a measurement needs, a developer build a ship=false pass needs |
ir |
a game's compiled program refused at load: one this engine cannot run, or a tuning table beside it of another shape |
save |
a save refused for what it holds, its size and its file aside: another game's, a quantity missing, or one of another kind, element, unit or quantum |
capacity |
a list's appends in one step past its capacity: the step keeps the first in item order and drops the rest |
command-line |
a launch argument the engine does not take |
compile |
a session's Trait source the compiler refuses: its refusals in trait's words, session:LINE:COLUMN: code: why, the line and column of the first in the text sent |
token |
a listener's connection whose first request does not carry the token its token setting asks: the connection is closed |
Events¶
Lines the engine sends unasked, {"event": name, ...fields}.
ready¶
the first line: the protocol and what ready answers, before any request
| Field | Type | Unit | |
|---|---|---|---|
protocol |
int | the protocol's version; a client speaking another refuses the session | |
game |
text | the game | |
version |
text | its version | |
unit |
text | its unit's id (UNIT_ID): what a session compiles against, and a transcript replays on (optional) | |
step |
int | step | the step the world is at |
dt |
real | s | the step's duration |
spaces |
list of SpaceExtent | each space and its extent | |
seed |
int | the world's seed (optional) |
Operations¶
describe¶
The game's spaces and the unit's catalog (what the program declares: its spaces, quantities, kernels and procedures), what the session has compiled so far, and the ready record again (the game, the step, each space's extent) with the protocol's and Trait's versions, for a client attaching to a running engine. What a session may name.
It answers, beside the envelope:
| Field | Type | Unit | |
|---|---|---|---|
describe |
Definition | the game's spaces and the unit's catalog | |
protocol |
int | the channel protocol's version | |
trait |
text | the Trait version the engine compiles a session's source under | |
session |
Session | what this session has compiled: its procedures and outputs | |
mode |
text | this session's mode: driving (it steps the engine by calling its step) | |
game |
text | the game | |
version |
text | its version | |
unit |
text | its unit's id (UNIT_ID): what a session compiles against, and a transcript replays on (optional) | |
step |
int | step | the step the world is at |
dt |
real | s | the step's duration |
spaces |
list of SpaceExtent | each space and its extent | |
seed |
int | the world's seed (optional) |
May refuse: bad-request.
{"id":1,"op":"describe"}
client.describe()
await client.describe();
client:describe()
> describe
compile¶
Compile Trait source into this session: functions, procedures and the session's own output quantities, against the running game, its shaders made and loaded; nothing runs. Its names are the session's: an unqualified name in it is looked up in the session's declarations, then the game's root namespace; a name the game declares too is refused. The session keeps its declarations by name: one the session declared before is replaced where it stands, whatever its kind, and a new one is added last. A recompile that leaves the outputs' layout as it was keeps their values (kept); otherwise they start over. A refusal is the compiler's: every refusal in its words, session[NAME]:LINE:COLUMN: code: why with LINE in the declaration NAME's own text (a syntax refusal session:LINE:COLUMN, LINE in the text sent), the first's line, column and declaration in the envelope, and the session is as it was.
| Argument | Type | Unit | |
|---|---|---|---|
text |
text | the Trait source |
It answers, beside the envelope:
| Field | Type | Unit | |
|---|---|---|---|
procedures |
list of text | the session's procedures now, by their qualified names | |
outputs |
list of text | the session's outputs now, by their qualified names | |
declarations |
list of text | the session's declarations now, by their names, in order | |
kept |
bool | whether the outputs kept their values (trait: their layout unchanged) | |
ms |
int | ms | the compiler's time for it |
refusals |
list of text | a refusal's: every refusal of trait's, in its words (optional) |
May refuse: bad-request, compile, unsupported.
{"id":2,"op":"compile","text":"output struct { integer highest; } seen;\nvoid track() { seen.highest = max(seen.highest, max(board.level)); }\n"}
client.compile('output struct { integer highest; } seen;\nvoid track() { seen.highest = max(seen.highest, max(board.level)); }\n')
await client.compile("output struct { integer highest; } seen;\nvoid track() { seen.highest = max(seen.highest, max(board.level)); }\n");
client:compile("output struct { integer highest; } seen;\nvoid track() { seen.highest = max(seen.highest, max(board.level)); }\n")
> output struct { integer highest; } seen;
void track() { seen.highest = max(seen.highest, max(board.level)); }
call¶
Run a procedure by name, now, between steps: the session's own, the game's (a render among them), or a step. A call takes arguments, values for host quantities stored right before it runs: a session's procedure the session's own it reads (a host struct), a game's procedure or a step the game's, by their qualified names; a name that is none of them is refused name, a value of the wrong kind bad-request. step (or the game's main) is one step of the game, answered once it ran; any other runs outside the steps, with nothing of the game's in flight. Its answer is the procedure run, the step its values are as of, the session's outputs it wrote (each in its declared unit, units; an integer as its count of quanta, quanta; a session's table as its live rows) and the errors it raised. A session changes the game only through what its procedures write.
| Argument | Type | Unit | |
|---|---|---|---|
procedure |
text | the procedure: its name in the session or the game, qualified or not; step for a step |
|
arguments |
map of any | values for host quantities, stored right before it runs: a session's procedure's by their names in the session (args.speed) or their last part where it is one's alone (speed); a game's procedure's or a step's by their qualified names; a float in its declared unit, an integer as its count of quanta, a column of many items the list of them (optional) |
It answers, beside the envelope:
| Field | Type | Unit | |
|---|---|---|---|
procedure |
text | the procedure run, by its qualified name | |
as_of |
int | step | the step the values are as of: the steps run so far |
outputs |
map of any | each session output the call wrote, by its name in the session | |
units |
map of text | each float output's declared unit, where it declares one (optional) | |
quanta |
map of list of int | each integer output's quantum, [numerator, denominator], where it is not 1 (optional) | |
errors |
list of text | the errors the call raised, as the engine reports them | |
appended |
list of any | a game's launch's: each row append queued for it, in the order queued, as its receipt gave it: its relation [slot, generation], or the refusal's text (its table full) (optional) |
|
removed |
list of any |
May refuse: bad-request, name, unsupported.
{"id":3,"op":"call","procedure":"throw_red","arguments":{"speed":6.8}}
client.call('throw_red', arguments={'speed': 6.8})
await client.call("throw_red", {arguments: {"speed": 6.8}});
client:call("throw_red", {arguments = {speed = 6.8}})
> throw_red(speed = 6.8);
read¶
Read the game's columns and tables whole, now, between steps, in one answer: each name a space or a stored column of the game's, qualified or in the game's root namespace. A table is its live rows in slot order, each {"row": slot, FIELD: value, ...} (a table's column its rows with that field alone); a lattice's column its items in the unit's order, with its extents in extents; a single's column its value; a single's or a lattice's space an object of its columns. Each value as a call's outputs are: a float in its declared unit (units), an integer as its count of quanta (quanta), a relation [slot, generation] or null, a float that is no number null. A name the game has no space or stored column of is refused name.
| Argument | Type | Unit | |
|---|---|---|---|
names |
list of text | the spaces and columns to read, by their names |
It answers, beside the envelope:
| Field | Type | Unit | |
|---|---|---|---|
as_of |
int | step | the step the values are as of: the steps run so far |
values |
map of any | each name's value, by the name as given | |
extents |
map of list of int | each lattice's extents, by the name as given (optional) | |
units |
map of text | each float column's declared unit, where it declares one, by the name as given (a space's column's as NAME.FIELD) (optional) | |
quanta |
map of list of int | each integer column's quantum, [numerator, denominator], where it is not 1, named as units names (optional) |
May refuse: bad-request, name, unsupported.
{"id":4,"op":"read","names":["board.level","contacts"]}
client.read(['board.level', 'contacts'])
await client.read(["board.level", "contacts"]);
client:read({"board.level", "contacts"})
> read
load¶
Set a game's stored column whole, now, between steps: its every item in the layout's order (a lattice's in the unit's order, a table's by slot), each given as a call's argument is. Any stored column, not only a host quantity: a starting state the source does not state (a board's pattern). Only the column's own bits are written; a record's other fields keep theirs. A name the game stores no column of is refused name; a count or a value that does not fit, bad-request.
| Argument | Type | Unit | |
|---|---|---|---|
name |
text | the stored column, by its name | |
items |
list of any | its every item, in the layout's order |
It answers, beside the envelope:
| Field | Type | Unit | |
|---|---|---|---|
as_of |
int | step | the step the column is set at: the steps run so far |
May refuse: bad-request, name, unsupported.
{"id":5,"op":"load","name":"board.alive","items":[true,false,false,true]}
client.load('board.alive', [True, False, False, True])
await client.load("board.alive", [true, false, false, true]);
client:load("board.alive", {true, false, false, true})
> load
append¶
Add rows to one of the game's input tables as the host appends them: queued for the game's next launch (a step, or a call of a game's procedure), applied by its host operations before it runs. Each row is {FIELD: value, ...} by its fields' names within the row, every stored field given, each value as a call's argument is. That launch's answer carries each row's relation, or the refusal its receipt gave (appended). A name that is no input table of the game's, or a field its row has not, is refused name; a value of the wrong kind, or a row lacking a field, bad-request; an append with a launch in flight, unsupported.
| Argument | Type | Unit | |
|---|---|---|---|
table |
text | the input table, by its name | |
rows |
list of map of any | the rows, each by its fields' names |
It answers, beside the envelope:
| Field | Type | Unit | |
|---|---|---|---|
queued |
int | the rows queued for the next launch so far, these among them | |
lands |
int | step | the step the next launch is numbered: the step they land in |
May refuse: bad-request, name, unsupported.
{"id":6,"op":"append","table":"throws","rows":[{"owner":[0,1],"speed":6.8,"aim":0.02}]}
client.append('throws', [{'owner': [0, 1], 'speed': 6.8, 'aim': 0.02}])
await client.append("throws", [{"owner": [0, 1], "speed": 6.8, "aim": 0.02}]);
client:append("throws", {{owner = {0, 1}, speed = 6.8, aim = 0.02}})
> append
remove¶
Remove rows from one of the game's tables the host may remove from (the unit's removable: a table no binding writes), each by its relation [slot, generation]: queued for the game's next launch (a step, or a call of a game's procedure) as append is, applied by its host operations before it runs, in the order queued with the host's appends. A removed row's slot is freed and its generation moves on, so every relation to it reads none and a later removal by it is refused. A name that is no table the host may remove from is refused name; a relation that is not two whole numbers, bad-request; a removal with a launch in flight, unsupported.
| Argument | Type | Unit | |
|---|---|---|---|
table |
text | the table, by its name | |
relations |
list of list of int | the rows, each its relation [slot, generation] |
It answers, beside the envelope:
| Field | Type | Unit | |
|---|---|---|---|
queued |
int | the removals queued for the next launch so far, these among them | |
lands |
int | step | the step the next launch is numbered: the step they apply in |
May refuse: bad-request, name, unsupported.
{"id":7,"op":"remove","table":"pucks","relations":[[0,0]]}
client.remove('pucks', [[0, 0]])
await client.remove("pucks", [[0, 0]]);
client:remove("pucks", {{0, 0}})
> remove
save¶
Save the whole world by name, at the step boundary, with nothing of the game's in flight: every value the game stores, what its next step reads of the steps before, the values the host keeps, the step and the clock, so the world restored from it steps bit for bit as this one does. A name already saved is written over. The C++ engine writes user/saves/<name>.fsw beside its executable; the browser host keeps it in the page's IndexedDB. A name a save may not take is refused name; a file that cannot be written, io.
| Argument | Type | Unit | |
|---|---|---|---|
name |
text | the save's name: / separates folders, each part a file name |
It answers, beside the envelope:
| Field | Type | Unit | |
|---|---|---|---|
as_of |
int | step | the step the world was saved at: the steps run so far |
May refuse: bad-request, name, io, unsupported.
{"id":8,"op":"save","name":"end3"}
client.save('end3')
await client.save("end3");
client:save("end3")
> save
restore¶
Put back the world saved by name, at the step boundary, with nothing of the game's in flight and no record queued: the steps are counted on from the save's, so the next step is the save's next and runs bit for bit as it did after the save. A save is the game's while what it holds is the same (the same quantities, alike on every target of the game), whichever build wrote it; another is refused save, by name, as old saves are not kept. No save of that name is refused name; one taken at other extents size; a file that cannot be read io; a restore with records queued (append, remove, a call's stores not yet launched) unsupported. The session's own outputs are untouched.
| Argument | Type | Unit | |
|---|---|---|---|
name |
text | the save's name, as save was given it |
It answers, beside the envelope:
| Field | Type | Unit | |
|---|---|---|---|
as_of |
int | step | the step the world is at now: the save's |
May refuse: bad-request, name, save, size, io, unsupported.
{"id":9,"op":"restore","name":"end3"}
client.restore('end3')
await client.restore("end3");
client:restore("end3")
> restore
drop¶
Remove declarations from this session by name (its procedures, functions and outputs, as describe's session and a compile's declarations name them), and compile the rest again, answered as a compile is. A name the session does not hold is refused name; a declaration the rest still names refuses the drop compile, and the session is as it was.
| Argument | Type | Unit | |
|---|---|---|---|
names |
list of text | the declarations to remove, by their names |
It answers, beside the envelope:
| Field | Type | Unit | |
|---|---|---|---|
procedures |
list of text | the session's procedures now, by their qualified names | |
outputs |
list of text | the session's outputs now, by their qualified names | |
declarations |
list of text | the session's declarations now, by their names, in order | |
kept |
bool | whether the outputs kept their values (trait: their layout unchanged) | |
ms |
int | ms | the compiler's time for it |
refusals |
list of text | a refusal's: every refusal of trait's, in its words (optional) |
May refuse: bad-request, name, compile, unsupported.
{"id":10,"op":"drop","names":["track"]}
client.drop(['track'])
await client.drop(["track"]);
client:drop({"track"})
> del track;
quit¶
End the session; the engine exits.
May refuse: bad-request.
{"id":11,"op":"quit"}
client.quit()
await client.quit();
client:quit()
> quit
Records¶
The named records the operations carry.
SpaceExtent¶
A space and its extent, as ready and describe give it.
| Field | Type | Unit | |
|---|---|---|---|
name |
text | the space's name | |
extent |
list of int | 1 | its count on each axis (a list's count; a level's extent per axis) |
CatalogSpace¶
A space the program declares.
| Field | Type | Unit | |
|---|---|---|---|
name |
text | its qualified name | |
kind |
text | single, table or lattice | |
size |
int | 1 | a table's capacity, a lattice's rank, 1 for a single item |
element |
text | its struct or type | |
package |
text | the package declaring it: lusine, or the game's root namespace |
CatalogQuantity¶
A quantity a space holds, as the unit states it.
| Field | Type | Unit | |
|---|---|---|---|
name |
text | its qualified name | |
space |
text | its space | |
path |
text | its name within the space | |
type |
text | its type, in base dimensions | |
declared |
text | the unit it is declared in, as written; empty for none | |
scale |
real | a value v in the declared unit is (v + offset) * scale in the base unit | |
offset |
real | see scale | |
range_min |
real | its declared range's least, in the base unit (optional) | |
range_max |
real | its declared range's greatest, in the base unit (optional) | |
overflow |
text | none, wrap, saturate or refuse | |
writer |
text | program, host, lusine, or empty for a constant | |
qualifier |
text | host, input, output, const, or empty | |
place |
text | device, host, dropped or release-absent | |
element |
text | bool, float, integer, bits, code, relation, text, ref or struct | |
lanes |
int | 1 | a vector's count, else 1 |
CatalogKernel¶
A kernel the program reaches.
| Field | Type | Unit | |
|---|---|---|---|
name |
text | its qualified name | |
signature |
text | its parameters and result, in base dimensions |
CatalogProcedure¶
A procedure the program runs by its switch.
| Field | Type | Unit | |
|---|---|---|---|
name |
text | its qualified name |
Catalog¶
The unit's catalog: what the program declares, for the clients to be generated from.
| Field | Type | Unit | |
|---|---|---|---|
bases |
list of text | the base dimensions, by name | |
spaces |
list of CatalogSpace | the spaces | |
quantities |
list of CatalogQuantity | the quantities, a table's own [live], [generation] and [born] among them | |
kernels |
list of CatalogKernel | the kernels the program reaches | |
procedures |
list of CatalogProcedure | the procedures the program runs |
Definition¶
What a session may name: the game's spaces and the unit's catalog.
| Field | Type | Unit | |
|---|---|---|---|
spaces |
list of SpaceExtent | each space and its extent | |
catalog |
Catalog | the unit's catalog: what the program declares |
Session¶
What the session has compiled so far.
| Field | Type | Unit | |
|---|---|---|---|
procedures |
list of text | its procedures, by their qualified names, each a call may name | |
outputs |
list of text | its outputs, by their qualified names: what its calls answer | |
declarations |
list of text | its declarations, by their names, in order: what a drop may name | |
hosts |
map of text | its own host quantities, by their qualified names, each with the kind a call's argument for it takes: bool, float (a number in its declared unit), integer (a whole count of quanta), code or relation |