Skip to main content

Module patch

Module patch 

Source
Expand description

The DOM patch stream: the byte format a compiled program writes and a host applies.

Tristen’s constraint is that the browser runs compiled LOGOS and JavaScript is a host SHIM, not a layer of the framework. For the DOM that means a BULK stream: the program builds a buffer of operations and the host applies it in a for loop, deciding nothing. The stream is the interface — every decision that used to live in runtime.js belongs to the side that emits it.

§The format

Little-endian, byte-oriented, self-delimiting. One record is a 1-byte OPCODE followed by its operands, each of which is one of:

  • a NODE, u32 — the creation ordinal every tier already agrees on, so u32::MAX (the “no such element” sentinel) rides through intact;
  • a FLAG, one byte, 0 or 1;
  • an i64, 8 bytes — an opaque host handle;
  • TEXT, a u32 byte length then that many UTF-8 bytes. LENGTH-PREFIXED, never delimited, so no byte is special: a NUL, a newline and a quote are ordinary content, and an EMPTY string is a string rather than an absence (setAttribute(el, "disabled", "") is how a boolean attribute is written).

§Why the decoder is strict

Its input comes from linear memory a compiled program wrote. A truncated record, a length that overruns the buffer, an unknown opcode or invalid UTF-8 all mean A BUG SOMEWHERE — so the decoder REFUSES rather than reading past the end, guessing, or lossily converting. A U+FFFD substituted for a bad byte would put a character on the page that nobody wrote.

§Why the opcode table is append-only

The emitter is compiled into a wasm module; the decoder ships in a host. They are updated at DIFFERENT TIMES, so renumbering an opcode silently reinterprets every stream a previously-built module emits. New operations go on the END, and OPCODES is pinned by patch_stream_lock so that has to be deliberate.

Constants§

OPCODES
The opcode table, in wire order. Append-only — see the module note.
RETIRED_OPCODES
The opcodes no emitter writes any more, each with why. Each keeps its number, so no later opcode is renumbered, and both decoders refuse it by name (retired_opcode) rather than as an unknown code.

Functions§

apply
Apply a decoded stream to the recorder — the DUMB SHIM, in Rust.
bytes_from_value
Read a patch buffer out of a LOGOS value — a Seq of Int whose every element is a byte.
decode
Decode a patch stream, or say exactly what is wrong with it.
decode_words
Decode a (words, strings) pair — encode_words’s inverse, and what a native tier applies vdom.lg’s own vdCommit output through.
encode
Encode ops as a patch stream.
encode_words
Word-oriented wire format v2: the same opcodes as encode/decode, but every operand is an i64 WORD and every string is a 1-based index into a separate STRING TABLE — the shape vdom.lg’s own emitter naturally produces (Seq of Int for the words, Seq of Text for the strings), with no byte-level length-prefixing to hand-roll in LOGOS. words/strings travel as a pair; a decoder given one without its matching other cannot make sense of either.
strings_from_value
The first count strings of a Seq of Text — the string half of the word-oriented wire format.
words_from_value
The first count words of a Seq of Int — the word half of the word-oriented wire format, as vdom.lg’s vdApply hands it over.