@czap/worker 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +19 -0
- package/dist/compositor-script.d.ts +19 -0
- package/dist/compositor-script.d.ts.map +1 -0
- package/dist/compositor-script.js +374 -0
- package/dist/compositor-script.js.map +1 -0
- package/dist/compositor-startup.d.ts +200 -0
- package/dist/compositor-startup.d.ts.map +1 -0
- package/dist/compositor-startup.js +490 -0
- package/dist/compositor-startup.js.map +1 -0
- package/dist/compositor-types.d.ts +135 -0
- package/dist/compositor-types.d.ts.map +1 -0
- package/dist/compositor-types.js +7 -0
- package/dist/compositor-types.js.map +1 -0
- package/dist/compositor-worker.d.ts +65 -0
- package/dist/compositor-worker.d.ts.map +1 -0
- package/dist/compositor-worker.js +454 -0
- package/dist/compositor-worker.js.map +1 -0
- package/dist/evaluate-inline.d.ts +22 -0
- package/dist/evaluate-inline.d.ts.map +1 -0
- package/dist/evaluate-inline.js +42 -0
- package/dist/evaluate-inline.js.map +1 -0
- package/dist/host.d.ts +97 -0
- package/dist/host.d.ts.map +1 -0
- package/dist/host.js +115 -0
- package/dist/host.js.map +1 -0
- package/dist/index.d.ts +40 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +40 -0
- package/dist/index.js.map +1 -0
- package/dist/messages.d.ts +254 -0
- package/dist/messages.d.ts.map +1 -0
- package/dist/messages.js +55 -0
- package/dist/messages.js.map +1 -0
- package/dist/render-worker.d.ts +77 -0
- package/dist/render-worker.d.ts.map +1 -0
- package/dist/render-worker.js +396 -0
- package/dist/render-worker.js.map +1 -0
- package/dist/spsc-ring.d.ts +171 -0
- package/dist/spsc-ring.d.ts.map +1 -0
- package/dist/spsc-ring.js +240 -0
- package/dist/spsc-ring.js.map +1 -0
- package/package.json +51 -0
- package/src/compositor-script.ts +374 -0
- package/src/compositor-startup.ts +666 -0
- package/src/compositor-types.ts +175 -0
- package/src/compositor-worker.ts +613 -0
- package/src/evaluate-inline.ts +42 -0
- package/src/host.ts +189 -0
- package/src/index.ts +49 -0
- package/src/messages.ts +343 -0
- package/src/render-worker.ts +454 -0
- package/src/spsc-ring.ts +309 -0
package/src/host.ts
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WorkerHost -- main-thread coordinator for compositor and render workers.
|
|
3
|
+
*
|
|
4
|
+
* Provides a unified API for:
|
|
5
|
+
* - Managing a CompositorWorker (off-thread state computation)
|
|
6
|
+
* - Managing a RenderWorker (off-thread OffscreenCanvas rendering)
|
|
7
|
+
* - Attaching an HTMLCanvasElement for off-thread rendering
|
|
8
|
+
* - Subscribing to state updates from the compositor worker
|
|
9
|
+
*
|
|
10
|
+
* @module
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { CompositeState, VideoConfig } from '@czap/core';
|
|
14
|
+
import type { WorkerConfig } from './messages.js';
|
|
15
|
+
import { CompositorWorker, type CompositorWorkerStartupTelemetry } from './compositor-worker.js';
|
|
16
|
+
import { RenderWorker } from './render-worker.js';
|
|
17
|
+
|
|
18
|
+
type WorkerHostState = CompositeState & {
|
|
19
|
+
readonly resolvedStateGenerations?: Record<string, number>;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
// Types
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Host-facing surface of a worker host. Owns a compositor worker and,
|
|
28
|
+
* optionally, a render worker created on demand via
|
|
29
|
+
* {@link WorkerHostShape.attachCanvas}. Returned by {@link WorkerHost.create}.
|
|
30
|
+
*/
|
|
31
|
+
export interface WorkerHostShape {
|
|
32
|
+
/** The compositor worker instance. */
|
|
33
|
+
readonly compositor: CompositorWorker.Shape;
|
|
34
|
+
|
|
35
|
+
/** The render worker instance, or null if no canvas has been attached. */
|
|
36
|
+
readonly renderer: RenderWorker.Shape | null;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Attach an HTMLCanvasElement for off-thread rendering.
|
|
40
|
+
*
|
|
41
|
+
* Calls `canvas.transferControlToOffscreen()` and transfers the
|
|
42
|
+
* resulting OffscreenCanvas to the render worker. A render worker
|
|
43
|
+
* is created on demand if one does not already exist.
|
|
44
|
+
*
|
|
45
|
+
* This can only be called once per canvas element -- the browser
|
|
46
|
+
* does not allow transferring control multiple times.
|
|
47
|
+
*/
|
|
48
|
+
attachCanvas(canvas: HTMLCanvasElement): void;
|
|
49
|
+
|
|
50
|
+
/** Start off-thread video rendering with the given configuration. */
|
|
51
|
+
startRender(config: VideoConfig): void;
|
|
52
|
+
|
|
53
|
+
/** Stop an in-progress off-thread render. */
|
|
54
|
+
stopRender(): void;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Subscribe to CompositeState updates from the compositor worker.
|
|
58
|
+
* Returns an unsubscribe function.
|
|
59
|
+
*/
|
|
60
|
+
onState(callback: (state: WorkerHostState) => void): () => void;
|
|
61
|
+
|
|
62
|
+
/** Dispose both workers and release all resources. */
|
|
63
|
+
dispose(): void;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// ---------------------------------------------------------------------------
|
|
67
|
+
// Factory
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
|
|
70
|
+
function _createWorkerHost(
|
|
71
|
+
config?: WorkerConfig,
|
|
72
|
+
startupTelemetry?: CompositorWorkerStartupTelemetry,
|
|
73
|
+
): WorkerHostShape {
|
|
74
|
+
const compositor = CompositorWorker.create(config, startupTelemetry);
|
|
75
|
+
let renderer: RenderWorker.Shape | null = null;
|
|
76
|
+
|
|
77
|
+
// Forward compositor state to the render worker's quantizer registry.
|
|
78
|
+
// This keeps the render worker's internal state in sync when the
|
|
79
|
+
// compositor produces new states.
|
|
80
|
+
const stateUnsubscribers: Array<() => void> = [];
|
|
81
|
+
|
|
82
|
+
const host: WorkerHostShape = {
|
|
83
|
+
get compositor(): CompositorWorker.Shape {
|
|
84
|
+
return compositor;
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
get renderer(): RenderWorker.Shape | null {
|
|
88
|
+
return renderer;
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
attachCanvas(canvas: HTMLCanvasElement): void {
|
|
92
|
+
if (renderer === null) {
|
|
93
|
+
renderer = RenderWorker.create();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Transfer control to an OffscreenCanvas and send it to the worker.
|
|
97
|
+
// `transferControlToOffscreen()` can only be called once per element.
|
|
98
|
+
const offscreen = canvas.transferControlToOffscreen();
|
|
99
|
+
renderer.transferCanvas(offscreen);
|
|
100
|
+
},
|
|
101
|
+
|
|
102
|
+
startRender(videoConfig: VideoConfig): void {
|
|
103
|
+
if (renderer === null) {
|
|
104
|
+
throw new Error('WorkerHost: cannot start render -- no canvas attached. Call attachCanvas() first.');
|
|
105
|
+
}
|
|
106
|
+
renderer.startRender(videoConfig);
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
stopRender(): void {
|
|
110
|
+
if (renderer !== null) {
|
|
111
|
+
renderer.stopRender();
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
|
|
115
|
+
onState(callback: (state: WorkerHostState) => void): () => void {
|
|
116
|
+
const unsub = compositor.onState(callback);
|
|
117
|
+
stateUnsubscribers.push(unsub);
|
|
118
|
+
return () => {
|
|
119
|
+
const index = stateUnsubscribers.indexOf(unsub);
|
|
120
|
+
if (index >= 0) {
|
|
121
|
+
stateUnsubscribers.splice(index, 1);
|
|
122
|
+
}
|
|
123
|
+
unsub();
|
|
124
|
+
};
|
|
125
|
+
},
|
|
126
|
+
|
|
127
|
+
dispose(): void {
|
|
128
|
+
for (const unsub of stateUnsubscribers) unsub();
|
|
129
|
+
stateUnsubscribers.length = 0;
|
|
130
|
+
|
|
131
|
+
compositor.dispose();
|
|
132
|
+
if (renderer !== null) {
|
|
133
|
+
renderer.dispose();
|
|
134
|
+
renderer = null;
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
return host;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ---------------------------------------------------------------------------
|
|
143
|
+
// Export
|
|
144
|
+
// ---------------------------------------------------------------------------
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* `WorkerHost` -- main-thread lifecycle wrapper that owns a
|
|
148
|
+
* {@link CompositorWorker.Shape} and (optionally) a
|
|
149
|
+
* {@link RenderWorker.Shape}, exposing a single unified surface for DOM
|
|
150
|
+
* integration.
|
|
151
|
+
*
|
|
152
|
+
* Typical flow:
|
|
153
|
+
* 1. `const host = WorkerHost.create({...})` on the main thread.
|
|
154
|
+
* 2. `host.attachCanvas(canvasEl)` to lazily mint a render worker and
|
|
155
|
+
* transfer its `OffscreenCanvas`.
|
|
156
|
+
* 3. `host.startRender(videoConfig)` / `host.stopRender()` to control
|
|
157
|
+
* the render loop.
|
|
158
|
+
* 4. `host.onState(cb)` to subscribe to composite state updates.
|
|
159
|
+
* 5. `host.dispose()` when the host is unmounted -- releases both
|
|
160
|
+
* workers and every subscription.
|
|
161
|
+
*
|
|
162
|
+
* @example
|
|
163
|
+
* ```ts
|
|
164
|
+
* import { WorkerHost } from '@czap/worker';
|
|
165
|
+
*
|
|
166
|
+
* const host = WorkerHost.create({ poolCapacity: 64 });
|
|
167
|
+
* host.attachCanvas(canvas);
|
|
168
|
+
* host.startRender({ durationMs: 5000, fps: 60, width: 1280, height: 720 });
|
|
169
|
+
* const unsub = host.onState((state) => console.log(state.discrete));
|
|
170
|
+
* // ...
|
|
171
|
+
* unsub();
|
|
172
|
+
* host.dispose();
|
|
173
|
+
* ```
|
|
174
|
+
*/
|
|
175
|
+
export const WorkerHost = {
|
|
176
|
+
/**
|
|
177
|
+
* Create a worker host. The compositor worker starts immediately; the
|
|
178
|
+
* render worker is created lazily on the first
|
|
179
|
+
* {@link WorkerHostShape.attachCanvas} call.
|
|
180
|
+
*/
|
|
181
|
+
create: _createWorkerHost,
|
|
182
|
+
} as const;
|
|
183
|
+
|
|
184
|
+
export declare namespace WorkerHost {
|
|
185
|
+
/** Public host surface returned by {@link WorkerHost.create}. */
|
|
186
|
+
export type Shape = WorkerHostShape;
|
|
187
|
+
/** Telemetry sink forwarded to the inner compositor worker. */
|
|
188
|
+
export type StartupTelemetry = CompositorWorkerStartupTelemetry;
|
|
189
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@czap/worker` — **LiteShip** off-deck crew: compositor and render workers
|
|
3
|
+
* that keep the main thread trim while boundaries and media stay live.
|
|
4
|
+
*
|
|
5
|
+
* This package ships:
|
|
6
|
+
*
|
|
7
|
+
* - {@link SPSCRing}: lock-free single-producer/single-consumer ring
|
|
8
|
+
* backed by `SharedArrayBuffer`, used for real-time state streaming
|
|
9
|
+
* from a worker to the main thread.
|
|
10
|
+
* - {@link CompositorWorker}: a factory that spins up a worker which
|
|
11
|
+
* evaluates quantizer boundaries and emits `CompositeState`.
|
|
12
|
+
* - {@link RenderWorker}: a factory for a worker that renders
|
|
13
|
+
* `VideoFrameOutput` into an `OffscreenCanvas`.
|
|
14
|
+
* - {@link WorkerHost}: a thin lifecycle wrapper around `Worker` with
|
|
15
|
+
* typed message helpers.
|
|
16
|
+
*
|
|
17
|
+
* ## SharedArrayBuffer requirements
|
|
18
|
+
*
|
|
19
|
+
* The SPSC ring buffer uses `SharedArrayBuffer`, which requires the page
|
|
20
|
+
* to be served with the following HTTP headers:
|
|
21
|
+
*
|
|
22
|
+
* Cross-Origin-Opener-Policy: same-origin
|
|
23
|
+
* Cross-Origin-Embedder-Policy: require-corp
|
|
24
|
+
*
|
|
25
|
+
* Workers created by this package use inline Blob URLs and do not require
|
|
26
|
+
* separate worker entry files or bundler configuration.
|
|
27
|
+
*
|
|
28
|
+
* @module
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
// Messages
|
|
32
|
+
export { Messages } from './messages.js';
|
|
33
|
+
export type { ToWorkerMessage, FromWorkerMessage, WorkerConfig } from './messages.js';
|
|
34
|
+
|
|
35
|
+
// SPSC Ring Buffer
|
|
36
|
+
export { SPSCRing } from './spsc-ring.js';
|
|
37
|
+
export type { SPSCRingBufferShape } from './spsc-ring.js';
|
|
38
|
+
|
|
39
|
+
// Compositor Worker
|
|
40
|
+
export { CompositorWorker } from './compositor-worker.js';
|
|
41
|
+
export type { CompositorWorkerShape, CompositorWorkerState } from './compositor-types.js';
|
|
42
|
+
|
|
43
|
+
// Render Worker
|
|
44
|
+
export { RenderWorker } from './render-worker.js';
|
|
45
|
+
export type { RenderWorkerShape } from './render-worker.js';
|
|
46
|
+
|
|
47
|
+
// Host
|
|
48
|
+
export { WorkerHost } from './host.js';
|
|
49
|
+
export type { WorkerHostShape } from './host.js';
|
package/src/messages.ts
ADDED
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed message protocol for main-thread and worker communication.
|
|
3
|
+
*
|
|
4
|
+
* All message types are discriminated unions keyed on `type`.
|
|
5
|
+
* Every payload must be Structured Clone compatible -- no functions,
|
|
6
|
+
* no Effect objects, only plain serializable data.
|
|
7
|
+
*
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { CompositeState, VideoConfig, VideoFrameOutput } from '@czap/core';
|
|
12
|
+
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
// Worker configuration
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Tunable knobs that the main thread sends to a worker at construction time.
|
|
19
|
+
*
|
|
20
|
+
* Omitted fields fall back to worker-local defaults chosen by
|
|
21
|
+
* {@link CompositorWorker} / {@link RenderWorker}.
|
|
22
|
+
*/
|
|
23
|
+
export interface WorkerConfig {
|
|
24
|
+
/** Maximum number of pooled `CompositeState` slots the worker may hold. */
|
|
25
|
+
readonly poolCapacity?: number;
|
|
26
|
+
/** Target frames-per-second for the render loop (affects frame pacing). */
|
|
27
|
+
readonly targetFps?: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
// Main -> Worker messages
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
|
|
34
|
+
interface InitMessage {
|
|
35
|
+
readonly type: 'init';
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
interface AddQuantizerMessage {
|
|
39
|
+
readonly type: 'add-quantizer';
|
|
40
|
+
readonly name: string;
|
|
41
|
+
readonly boundaryId: string;
|
|
42
|
+
readonly states: readonly string[];
|
|
43
|
+
readonly thresholds: Float64Array | readonly number[];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Bootstrap registration for a single quantizer inside a worker.
|
|
48
|
+
*
|
|
49
|
+
* Sent from the main thread so the worker can reconstruct its quantizer
|
|
50
|
+
* registry without re-running the full build pipeline on cold start.
|
|
51
|
+
*/
|
|
52
|
+
export interface BootstrapQuantizerRegistration {
|
|
53
|
+
/** Stable quantizer name (key in the registry). */
|
|
54
|
+
readonly name: string;
|
|
55
|
+
/** Boundary content-address this quantizer is anchored to. */
|
|
56
|
+
readonly boundaryId: string;
|
|
57
|
+
/** Ordered discrete state labels. */
|
|
58
|
+
readonly states: readonly string[];
|
|
59
|
+
/** Threshold boundaries (length = states.length - 1). */
|
|
60
|
+
readonly thresholds: Float64Array | readonly number[];
|
|
61
|
+
/** Optional initial discrete state (defaults to the first state). */
|
|
62
|
+
readonly initialState?: string;
|
|
63
|
+
/** Optional initial blend weights, keyed by state label. */
|
|
64
|
+
readonly blendWeights?: Record<string, number>;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Aggregate startup payload: registrations plus any pending updates that
|
|
69
|
+
* should be replayed immediately after the worker finishes wiring its
|
|
70
|
+
* quantizer registry.
|
|
71
|
+
*/
|
|
72
|
+
export interface StartupComputePacket {
|
|
73
|
+
/** Whether the worker should boot empty, rehydrate from snapshot, or fully rebuild. */
|
|
74
|
+
readonly bootstrapMode: 'cold' | 'warm-snapshot' | 'rebuild';
|
|
75
|
+
/** Registrations to install. */
|
|
76
|
+
readonly registrations: readonly BootstrapQuantizerRegistration[];
|
|
77
|
+
/** Updates to replay after registration. */
|
|
78
|
+
readonly updates: readonly WorkerUpdate[];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* A single resolved discrete-state entry in a bootstrap/apply message.
|
|
83
|
+
*
|
|
84
|
+
* `generation` monotonically increases so receivers can discard stale
|
|
85
|
+
* out-of-order deliveries.
|
|
86
|
+
*/
|
|
87
|
+
export interface ResolvedStateEntry {
|
|
88
|
+
/** Quantizer name. */
|
|
89
|
+
readonly name: string;
|
|
90
|
+
/** Resolved discrete state label. */
|
|
91
|
+
readonly state: string;
|
|
92
|
+
/** Monotonically-increasing generation counter. */
|
|
93
|
+
readonly generation: number;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
interface BootstrapQuantizersMessage {
|
|
97
|
+
readonly type: 'bootstrap-quantizers';
|
|
98
|
+
readonly registrations: readonly BootstrapQuantizerRegistration[];
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
interface StartupComputeMessage {
|
|
102
|
+
readonly type: 'startup-compute';
|
|
103
|
+
readonly packet: StartupComputePacket;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
interface BootstrapResolvedStateMessage {
|
|
107
|
+
readonly type: 'bootstrap-resolved-state';
|
|
108
|
+
readonly states: readonly ResolvedStateEntry[];
|
|
109
|
+
readonly ack?: boolean;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
interface ApplyResolvedStateMessage {
|
|
113
|
+
readonly type: 'apply-resolved-state';
|
|
114
|
+
readonly states: readonly ResolvedStateEntry[];
|
|
115
|
+
readonly ack?: boolean;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
interface RemoveQuantizerMessage {
|
|
119
|
+
readonly type: 'remove-quantizer';
|
|
120
|
+
readonly name: string;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
interface RemoveQuantizerUpdate {
|
|
124
|
+
readonly type: 'remove-quantizer';
|
|
125
|
+
readonly name: string;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
interface EvaluateMessage {
|
|
129
|
+
readonly type: 'evaluate';
|
|
130
|
+
readonly name: string;
|
|
131
|
+
readonly value: number;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
interface EvaluateUpdate {
|
|
135
|
+
readonly type: 'evaluate';
|
|
136
|
+
readonly name: string;
|
|
137
|
+
readonly value: number;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
interface SetBlendMessage {
|
|
141
|
+
readonly type: 'set-blend';
|
|
142
|
+
readonly name: string;
|
|
143
|
+
readonly weights: Record<string, number>;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
interface SetBlendUpdate {
|
|
147
|
+
readonly type: 'set-blend';
|
|
148
|
+
readonly name: string;
|
|
149
|
+
readonly weights: Record<string, number>;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Incremental update applied to a worker's quantizer registry after
|
|
154
|
+
* bootstrap. Replayed as part of {@link StartupComputePacket.updates} or
|
|
155
|
+
* sent directly via an `apply-updates` message.
|
|
156
|
+
*/
|
|
157
|
+
export type WorkerUpdate = RemoveQuantizerUpdate | EvaluateUpdate | SetBlendUpdate;
|
|
158
|
+
|
|
159
|
+
interface ApplyUpdatesMessage {
|
|
160
|
+
readonly type: 'apply-updates';
|
|
161
|
+
readonly updates: readonly WorkerUpdate[];
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
interface ComputeMessage {
|
|
165
|
+
readonly type: 'compute';
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
interface WarmResetMessage {
|
|
169
|
+
readonly type: 'warm-reset';
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
interface StartRenderMessage {
|
|
173
|
+
readonly type: 'start-render';
|
|
174
|
+
readonly config: VideoConfig;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
interface StopRenderMessage {
|
|
178
|
+
readonly type: 'stop-render';
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Transfer an OffscreenCanvas to the worker.
|
|
183
|
+
* The canvas must be listed in the `transfer` array of postMessage.
|
|
184
|
+
*/
|
|
185
|
+
interface TransferCanvasMessage {
|
|
186
|
+
readonly type: 'transfer-canvas';
|
|
187
|
+
readonly canvas: OffscreenCanvas;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
interface DisposeMessage {
|
|
191
|
+
readonly type: 'dispose';
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Every message the main thread may send to a compositor/render worker.
|
|
196
|
+
* Discriminated on the `type` field.
|
|
197
|
+
*/
|
|
198
|
+
export type ToWorkerMessage =
|
|
199
|
+
| InitMessage
|
|
200
|
+
| AddQuantizerMessage
|
|
201
|
+
| BootstrapQuantizersMessage
|
|
202
|
+
| StartupComputeMessage
|
|
203
|
+
| BootstrapResolvedStateMessage
|
|
204
|
+
| ApplyResolvedStateMessage
|
|
205
|
+
| ApplyUpdatesMessage
|
|
206
|
+
| RemoveQuantizerMessage
|
|
207
|
+
| EvaluateMessage
|
|
208
|
+
| SetBlendMessage
|
|
209
|
+
| WarmResetMessage
|
|
210
|
+
| ComputeMessage
|
|
211
|
+
| StartRenderMessage
|
|
212
|
+
| StopRenderMessage
|
|
213
|
+
| TransferCanvasMessage
|
|
214
|
+
| DisposeMessage;
|
|
215
|
+
|
|
216
|
+
// ---------------------------------------------------------------------------
|
|
217
|
+
// Worker -> Main messages
|
|
218
|
+
// ---------------------------------------------------------------------------
|
|
219
|
+
|
|
220
|
+
interface ReadyMessage {
|
|
221
|
+
readonly type: 'ready';
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
interface StateMessage {
|
|
225
|
+
readonly type: 'state';
|
|
226
|
+
readonly state: CompositeState;
|
|
227
|
+
readonly resolvedStateGenerations?: Record<string, number>;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
interface ResolvedStateAckMessage {
|
|
231
|
+
readonly type: 'resolved-state-ack';
|
|
232
|
+
readonly generation: number;
|
|
233
|
+
readonly states: readonly {
|
|
234
|
+
readonly name: string;
|
|
235
|
+
readonly state: string;
|
|
236
|
+
}[];
|
|
237
|
+
readonly additionalOutputsChanged: boolean;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
interface FrameMessage {
|
|
241
|
+
readonly type: 'frame';
|
|
242
|
+
readonly output: VideoFrameOutput;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
interface RenderCompleteMessage {
|
|
246
|
+
readonly type: 'render-complete';
|
|
247
|
+
readonly totalFrames: number;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
interface ErrorMessage {
|
|
251
|
+
readonly type: 'error';
|
|
252
|
+
readonly message: string;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
interface MetricsMessage {
|
|
256
|
+
readonly type: 'metrics';
|
|
257
|
+
readonly fps: number;
|
|
258
|
+
readonly budgetUsed: number;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Build a bootstrap/apply resolved-state envelope.
|
|
263
|
+
*
|
|
264
|
+
* Used by the main thread to ship a batch of resolved discrete states
|
|
265
|
+
* either as part of bootstrap (`bootstrap-resolved-state`) or as an
|
|
266
|
+
* incremental reconciliation (`apply-resolved-state`).
|
|
267
|
+
*
|
|
268
|
+
* @param type - Which envelope to produce.
|
|
269
|
+
* @param states - The resolved entries to include.
|
|
270
|
+
* @param ack - Whether the worker is expected to acknowledge the delivery.
|
|
271
|
+
*/
|
|
272
|
+
export function makeResolvedStateEnvelope(
|
|
273
|
+
type: 'bootstrap-resolved-state' | 'apply-resolved-state',
|
|
274
|
+
states: readonly ResolvedStateEntry[],
|
|
275
|
+
ack: boolean,
|
|
276
|
+
): BootstrapResolvedStateMessage | ApplyResolvedStateMessage {
|
|
277
|
+
return {
|
|
278
|
+
type,
|
|
279
|
+
states,
|
|
280
|
+
ack,
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Every message a worker may send back to the main thread. Discriminated
|
|
286
|
+
* on the `type` field. Includes readiness, state updates, frame output,
|
|
287
|
+
* metrics, completion signals, and errors.
|
|
288
|
+
*/
|
|
289
|
+
export type FromWorkerMessage =
|
|
290
|
+
| ReadyMessage
|
|
291
|
+
| StateMessage
|
|
292
|
+
| ResolvedStateAckMessage
|
|
293
|
+
| FrameMessage
|
|
294
|
+
| RenderCompleteMessage
|
|
295
|
+
| ErrorMessage
|
|
296
|
+
| MetricsMessage;
|
|
297
|
+
|
|
298
|
+
// ---------------------------------------------------------------------------
|
|
299
|
+
// Namespace re-export
|
|
300
|
+
// ---------------------------------------------------------------------------
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Runtime type guards and type aliases for the worker message protocol.
|
|
304
|
+
* Consumers typically use {@link Messages.isToWorker} /
|
|
305
|
+
* {@link Messages.isFromWorker} inside a `message` handler to narrow
|
|
306
|
+
* `event.data` before switching on the `type` field.
|
|
307
|
+
*
|
|
308
|
+
* @example
|
|
309
|
+
* ```ts
|
|
310
|
+
* worker.addEventListener('message', (e) => {
|
|
311
|
+
* if (!Messages.isFromWorker(e.data)) return;
|
|
312
|
+
* if (e.data.type === 'state') { /* ... *\/ }
|
|
313
|
+
* });
|
|
314
|
+
* ```
|
|
315
|
+
*/
|
|
316
|
+
export const Messages = {
|
|
317
|
+
/** Type guard: is a ToWorkerMessage */
|
|
318
|
+
isToWorker(msg: unknown): msg is ToWorkerMessage {
|
|
319
|
+
return typeof msg === 'object' && msg !== null && 'type' in msg;
|
|
320
|
+
},
|
|
321
|
+
|
|
322
|
+
/** Type guard: is a FromWorkerMessage */
|
|
323
|
+
isFromWorker(msg: unknown): msg is FromWorkerMessage {
|
|
324
|
+
return typeof msg === 'object' && msg !== null && 'type' in msg;
|
|
325
|
+
},
|
|
326
|
+
} as const;
|
|
327
|
+
|
|
328
|
+
export declare namespace Messages {
|
|
329
|
+
/** Every message the main thread may send to a worker. */
|
|
330
|
+
export type ToWorker = ToWorkerMessage;
|
|
331
|
+
/** Every message a worker may send back to the main thread. */
|
|
332
|
+
export type FromWorker = FromWorkerMessage;
|
|
333
|
+
/** Tunable worker construction knobs. */
|
|
334
|
+
export type Config = WorkerConfig;
|
|
335
|
+
/** Incremental update applied post-bootstrap. */
|
|
336
|
+
export type Update = WorkerUpdate;
|
|
337
|
+
/** Single quantizer bootstrap registration. */
|
|
338
|
+
export type BootstrapRegistration = BootstrapQuantizerRegistration;
|
|
339
|
+
/** Full startup packet flushed once during worker boot. */
|
|
340
|
+
export type StartupPacket = StartupComputePacket;
|
|
341
|
+
/** Single resolved-state entry delivered to a worker. */
|
|
342
|
+
export type ResolvedState = ResolvedStateEntry;
|
|
343
|
+
}
|