# 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: ```js { 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: ```js 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.