Files
yawn/coredocs/reference/worker.md
T

5.7 KiB

Worker message reference

All fields below are top-level beside type and request. Except for init and upload-texture, transfer lists are empty.

type Fields Transfer list Success result Constraints / effects
init canvas: OffscreenCanvas, arenaBytes: number, profileBuffer: SharedArrayBuffer [canvas] { buffer: SharedArrayBuffer, rows: Descriptor[], features: string[] } Once only; initializes WASM/WebGPU. features lists optional device features that were available (timestamp-query, indirect-first-instance). profileBuffer is shared, not transferred.
create-rows name, rows, stride, format — { rows: descriptor, commit: boolean } Nonempty name; rows > 0; stride ≥16 and multiple of 16; format f32/u32/i32. Existing name may only retain format/stride and grow. commit: true means the rows moved: copy the old bytes into the descriptor and send commit-rows.
create-rows-batch rows: Array<{name,rows,stride,format}> — { rows: descriptor[] (input order), commit: string[] } Nonempty. Sequential; in-place creations/growths survive a later failure, staged relocations are discarded. commit lists the arrays that moved. Active GPU resources refresh once for in-place growth.
commit-rows names: string[] — undefined Publishes staged relocations after the caller copied them. Must name every staged array. Frees the old rows, refreshes bound GPU buffers once, sets sabDirty. While a relocation is staged the worker queues every other request until this arrives.
delete-rows name — undefined Not signals, not active slots, not referenced by active graph.
allocate-object name, count: u32 = 1 — { id, rows: descriptor, commit: boolean } Not signals; count > 0; reserves count contiguous rows starting at id; may grow. commit as in create-rows.
delete-object name, id: u32 — undefined ID must be the first row of an active span; the whole span is zeroed.
sync-rows name, first: u32 = 0, count: u32 — undefined Uploads rows first .. first + count of the array into every active GPU buffer bound to it (any sync) and sets sabDirty. ROW_RANGE when the span exceeds the array. No-op without an active loadout.
compile-graph serialized: string — graph ID string Parses and stores; same ID replaces previously stored graph. Does not activate it.
switch-loadout id — undefined Builds GPU resources and activates stored graph. Clears bundleDirty, sets sabDirty.
upload-texture name, mipLevel: u32, image: ImageBitmap [image] undefined Uploads immediately if active graph has the texture and caches source for future switches. Source extent must fit destination/mip. Sets dirty.
delete-texture name — undefined Deletes/closes cached mip sources; does not remove graph texture or mark dirty.
play — — undefined Enables render-loop ticks; resets last-time baseline.
pause — — undefined Stops loop updates/renders.
set-fps fps: u32 — undefined 0 uncapped; maximum 1000. Does not itself dirty a frame.
set-profiler enabled (boolean-coerced) — boolean Result says timestamp queries are supported. Enables only when requested and supported.

Rust/WASM numeric conversion applies to u32 fields; callers should send finite nonnegative integers in range rather than rely on coercion.

Shared profiler results

When enabled and supported, GPU timestamp queries are sampled at most once every 100 ms, with only one readback in flight. Completion of map_async wakes an awaiting future; neither the worker nor render loop polls for GPU completion. Rendering continues independently.

Completed results are published into profileBuffer, not sent as worker messages. YawnCore.onProfile reads this buffer locally every 100 ms while listeners exist and delivers the same stats shape:

{
    frame: 42,
    milliseconds: 0.31,
    readbackMilliseconds: 4.8,
    adapter: "Adapter name · DeviceType · Backend",
    canvas: { width: 1280, height: 720 },
    passes: [{ name: "triangle", milliseconds: 0.31 }]
}

One pass timing corresponds to a compiled execution; compatible adjacent render passes are merged into one execution labelled first … last (N draws). milliseconds is GPU time; readbackMilliseconds includes the asynchronous wait. Disabling clears published statistics and suppresses results from older profiling sessions.

The default mailbox is a separate 1 MiB SAB. Its first two Int32 values are ownership state and UTF-8 JSON length; the payload starts at byte 8. State 0 means empty, 1 means owned by a reader or writer, and 2 means a complete sample. Both sides claim ownership using Atomics.compareExchange and never wait. An unread sample is replaced by the latest sample; a publication during a read, or larger than the payload capacity, is skipped. The reader copies the bytes before releasing ownership, so it cannot observe a partially written result. Initialization and profiler enable/disable remain ordinary control requests; results never use postMessage.

Texture lifecycle

Create an ImageBitmap, then relinquish it to the worker:

const image = await createImageBitmap(blob);
await request("upload-texture", { name: "albedo", mipLevel: 0, image }, [image]);

The name is a graph texture ID, not an arbitrary row name. Upload can precede graph activation because sources are cached. Non-transient textures with an unchanged descriptor can be retained across switches; cached levels are reapplied when needed. delete-texture only removes these cached sources.