@viewscript/renderer 0.1.0-202605140639

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.
Files changed (89) hide show
  1. package/dist/ast/types.d.ts +403 -0
  2. package/dist/ast/types.js +33 -0
  3. package/dist/compiler/chunk-splitter.d.ts +98 -0
  4. package/dist/compiler/chunk-splitter.js +361 -0
  5. package/dist/index.d.ts +55 -0
  6. package/dist/index.js +17 -0
  7. package/dist/rasterizer/__tests__/error-distribution.test.d.ts +7 -0
  8. package/dist/rasterizer/__tests__/error-distribution.test.js +322 -0
  9. package/dist/rasterizer/canvas-mapper.d.ts +280 -0
  10. package/dist/rasterizer/canvas-mapper.js +414 -0
  11. package/dist/rasterizer/error-distribution.d.ts +143 -0
  12. package/dist/rasterizer/error-distribution.js +231 -0
  13. package/dist/rasterizer/gradient-mapper.d.ts +223 -0
  14. package/dist/rasterizer/gradient-mapper.js +352 -0
  15. package/dist/rasterizer/topology-rounding.d.ts +151 -0
  16. package/dist/rasterizer/topology-rounding.js +347 -0
  17. package/dist/runtime/__tests__/event-backpressure.test.d.ts +10 -0
  18. package/dist/runtime/__tests__/event-backpressure.test.js +190 -0
  19. package/dist/runtime/event-backpressure.d.ts +393 -0
  20. package/dist/runtime/event-backpressure.js +458 -0
  21. package/dist/runtime/render-loop.d.ts +277 -0
  22. package/dist/runtime/render-loop.js +435 -0
  23. package/dist/runtime/wasm-resource-manager.d.ts +122 -0
  24. package/dist/runtime/wasm-resource-manager.js +253 -0
  25. package/dist/runtime/wgpu-renderer-adapter.d.ts +168 -0
  26. package/dist/runtime/wgpu-renderer-adapter.js +230 -0
  27. package/dist/semantic/__tests__/semantic-translator.test.d.ts +4 -0
  28. package/dist/semantic/__tests__/semantic-translator.test.js +203 -0
  29. package/dist/semantic/semantic-translator.d.ts +229 -0
  30. package/dist/semantic/semantic-translator.js +398 -0
  31. package/package.json +28 -0
  32. package/playwright-report/data/0bafe4e0863f0e244bba68a838f73241f8f2efaa.md +226 -0
  33. package/playwright-report/data/9281aca8abfb06c6cecb35d5ddd13d61f8c752d8.md +226 -0
  34. package/playwright-report/index.html +90 -0
  35. package/playwright.config.ts +160 -0
  36. package/screenshot-chrome.png +0 -0
  37. package/screenshots/visual-demo-verification.png +0 -0
  38. package/screenshots/visual-demo.png +0 -0
  39. package/src/ast/types.ts +473 -0
  40. package/src/compiler/chunk-splitter.ts +534 -0
  41. package/src/index.ts +62 -0
  42. package/src/rasterizer/__tests__/error-distribution.test.ts +382 -0
  43. package/src/rasterizer/canvas-mapper.ts +677 -0
  44. package/src/rasterizer/error-distribution.ts +344 -0
  45. package/src/rasterizer/gradient-mapper.ts +563 -0
  46. package/src/rasterizer/topology-rounding.ts +499 -0
  47. package/src/runtime/__tests__/event-backpressure.test.ts +254 -0
  48. package/src/runtime/event-backpressure.ts +622 -0
  49. package/src/runtime/render-loop.ts +660 -0
  50. package/src/runtime/wasm-resource-manager.ts +349 -0
  51. package/src/runtime/wgpu-renderer-adapter.ts +318 -0
  52. package/src/semantic/__tests__/semantic-translator.test.ts +263 -0
  53. package/src/semantic/semantic-translator.ts +637 -0
  54. package/test-results/.last-run.json +4 -0
  55. package/tests/e2e/async-race.spec.ts +612 -0
  56. package/tests/e2e/bilayer-sync.spec.ts +405 -0
  57. package/tests/e2e/failures/.gitkeep +0 -0
  58. package/tests/e2e/fullstack.spec.ts +681 -0
  59. package/tests/e2e/g1-continuity.spec.ts +703 -0
  60. package/tests/e2e/golden/.gitkeep +0 -0
  61. package/tests/e2e/golden/conic-color-wheel.raw +0 -0
  62. package/tests/e2e/golden/conic-color-wheel.sha256 +1 -0
  63. package/tests/e2e/golden/conic-rotated.raw +0 -0
  64. package/tests/e2e/golden/conic-rotated.sha256 +1 -0
  65. package/tests/e2e/golden/linear-45deg.raw +0 -0
  66. package/tests/e2e/golden/linear-45deg.sha256 +1 -0
  67. package/tests/e2e/golden/linear-horizontal.raw +0 -0
  68. package/tests/e2e/golden/linear-horizontal.sha256 +1 -0
  69. package/tests/e2e/golden/linear-multi-stop.raw +0 -0
  70. package/tests/e2e/golden/linear-multi-stop.sha256 +1 -0
  71. package/tests/e2e/golden/radial-circle-center.raw +0 -0
  72. package/tests/e2e/golden/radial-circle-center.sha256 +1 -0
  73. package/tests/e2e/golden/radial-offset.raw +0 -0
  74. package/tests/e2e/golden/radial-offset.sha256 +1 -0
  75. package/tests/e2e/golden/tile-mirror.raw +0 -0
  76. package/tests/e2e/golden/tile-mirror.sha256 +1 -0
  77. package/tests/e2e/golden/tile-repeat.raw +0 -0
  78. package/tests/e2e/golden/tile-repeat.sha256 +1 -0
  79. package/tests/e2e/gradient-animation.spec.ts +606 -0
  80. package/tests/e2e/memory-stability.spec.ts +396 -0
  81. package/tests/e2e/path-topology.spec.ts +674 -0
  82. package/tests/e2e/performance-profile.spec.ts +501 -0
  83. package/tests/e2e/screenshot.spec.ts +60 -0
  84. package/tests/e2e/test-harness.html +1005 -0
  85. package/tests/e2e/text-layout.spec.ts +451 -0
  86. package/tests/e2e/visual-demo.html +340 -0
  87. package/tests/e2e/visual-regression.spec.ts +335 -0
  88. package/tsconfig.json +12 -0
  89. package/vitest.config.ts +8 -0
@@ -0,0 +1,253 @@
1
+ /**
2
+ * WASM Resource Manager
3
+ *
4
+ * This module provides explicit lifecycle management for WASM GPU resources.
5
+ * JavaScript's garbage collector cannot free WASM heap memory - we must call
6
+ * delete() explicitly on GPU objects.
7
+ *
8
+ * ## Problem
9
+ *
10
+ * ```
11
+ * const paint = canvasKit.Paint(); // Allocates on WASM heap
12
+ * paint = null; // JS reference gone, but WASM memory leaked!
13
+ * ```
14
+ *
15
+ * ## Solution: FinalizationRegistry
16
+ *
17
+ * We track all WASM resources and ensure delete() is called when the JS wrapper
18
+ * is garbage collected, OR when explicitly released via the resource pool.
19
+ *
20
+ * ## Resource Categories
21
+ *
22
+ * | Resource | Lifetime | Release Strategy |
23
+ * |----------|----------|------------------|
24
+ * | PaintSpec | Entity | On entity remove / HMR update |
25
+ * | PathEntity | Entity | On path change / entity remove |
26
+ * | GpuImage | Async | On image unload / src change |
27
+ * | GpuFont | Global | On font unload (rare) |
28
+ */
29
+ // =============================================================================
30
+ // Resource Manager
31
+ // =============================================================================
32
+ export class WASMResourceManager {
33
+ /** FinalizationRegistry for automatic cleanup on GC */
34
+ registry;
35
+ /** Active resources (strong references for explicit management) */
36
+ resources;
37
+ /** Statistics */
38
+ stats;
39
+ /** Cleanup queue (for batch processing) */
40
+ cleanupQueue = [];
41
+ cleanupScheduled = false;
42
+ constructor() {
43
+ this.resources = new Map();
44
+ this.stats = { allocated: 0, released: 0 };
45
+ // Create registry with cleanup callback
46
+ this.registry = new FinalizationRegistry((token) => {
47
+ this.queueCleanup(token);
48
+ });
49
+ }
50
+ // ===========================================================================
51
+ // Public API
52
+ // ===========================================================================
53
+ /**
54
+ * Register a WASM resource for tracking.
55
+ *
56
+ * @param resource - GPU WASM object with delete() method
57
+ * @param type - Resource category
58
+ * @param entityId - Associated entity (optional)
59
+ * @returns Symbol token for explicit release
60
+ */
61
+ register(resource, type, entityId = null) {
62
+ const token = Symbol(`wasm-${type}-${this.stats.allocated}`);
63
+ const entry = {
64
+ type,
65
+ entityId,
66
+ createdAt: performance.now(),
67
+ accessed: performance.now(),
68
+ };
69
+ // Store weak reference for tracking
70
+ this.resources.set(token, {
71
+ ref: new WeakRef(resource),
72
+ entry,
73
+ });
74
+ // Register for finalization (GC-triggered cleanup)
75
+ const cleanupToken = { token, type };
76
+ this.registry.register(resource, cleanupToken, resource);
77
+ this.stats.allocated++;
78
+ return token;
79
+ }
80
+ /**
81
+ * Explicitly release a resource by token.
82
+ * Preferred over waiting for GC.
83
+ */
84
+ release(token) {
85
+ const entry = this.resources.get(token);
86
+ if (!entry)
87
+ return false;
88
+ const resource = entry.ref.deref();
89
+ if (resource && !resource.isDeleted?.()) {
90
+ try {
91
+ resource.delete();
92
+ this.registry.unregister(resource);
93
+ }
94
+ catch (e) {
95
+ console.warn('[WASM] Failed to delete resource:', e);
96
+ }
97
+ }
98
+ this.resources.delete(token);
99
+ this.stats.released++;
100
+ return true;
101
+ }
102
+ /**
103
+ * Release all resources associated with an entity.
104
+ * Called on entity removal or HMR update.
105
+ */
106
+ releaseByEntity(entityId) {
107
+ let released = 0;
108
+ for (const [token, { entry }] of this.resources) {
109
+ if (entry.entityId === entityId) {
110
+ if (this.release(token)) {
111
+ released++;
112
+ }
113
+ }
114
+ }
115
+ return released;
116
+ }
117
+ /**
118
+ * Release all resources of a specific type.
119
+ */
120
+ releaseByType(type) {
121
+ let released = 0;
122
+ for (const [token, { entry }] of this.resources) {
123
+ if (entry.type === type) {
124
+ if (this.release(token)) {
125
+ released++;
126
+ }
127
+ }
128
+ }
129
+ return released;
130
+ }
131
+ /**
132
+ * Force cleanup of all queued finalizations.
133
+ * Call after GC to ensure WASM memory is freed.
134
+ */
135
+ flushCleanupQueue() {
136
+ const count = this.cleanupQueue.length;
137
+ for (const token of this.cleanupQueue) {
138
+ this.release(token.token);
139
+ }
140
+ this.cleanupQueue = [];
141
+ return count;
142
+ }
143
+ /**
144
+ * Release ALL resources. Use with caution.
145
+ */
146
+ releaseAll() {
147
+ let released = 0;
148
+ for (const token of this.resources.keys()) {
149
+ if (this.release(token)) {
150
+ released++;
151
+ }
152
+ }
153
+ return released;
154
+ }
155
+ /**
156
+ * Get current resource statistics.
157
+ */
158
+ getStats() {
159
+ const byType = {
160
+ paint: 0,
161
+ path: 0,
162
+ image: 0,
163
+ font: 0,
164
+ shader: 0,
165
+ surface: 0,
166
+ };
167
+ let leakSuspects = 0;
168
+ const now = performance.now();
169
+ const LEAK_THRESHOLD_MS = 60000; // 1 minute without access
170
+ for (const { ref, entry } of this.resources.values()) {
171
+ const resource = ref.deref();
172
+ if (resource) {
173
+ byType[entry.type]++;
174
+ // Check for potential leaks (old, unaccessed resources)
175
+ if (now - entry.accessed > LEAK_THRESHOLD_MS) {
176
+ leakSuspects++;
177
+ }
178
+ }
179
+ }
180
+ return {
181
+ totalAllocated: this.stats.allocated,
182
+ totalReleased: this.stats.released,
183
+ currentActive: this.resources.size,
184
+ byType,
185
+ leakSuspects,
186
+ };
187
+ }
188
+ /**
189
+ * Mark a resource as recently accessed (resets leak timer).
190
+ */
191
+ touch(token) {
192
+ const entry = this.resources.get(token);
193
+ if (entry) {
194
+ entry.entry.accessed = performance.now();
195
+ }
196
+ }
197
+ // ===========================================================================
198
+ // Private Methods
199
+ // ===========================================================================
200
+ /**
201
+ * Queue a cleanup token for batch processing.
202
+ */
203
+ queueCleanup(token) {
204
+ this.cleanupQueue.push(token);
205
+ // Schedule batch cleanup on next microtask
206
+ if (!this.cleanupScheduled) {
207
+ this.cleanupScheduled = true;
208
+ queueMicrotask(() => {
209
+ this.flushCleanupQueue();
210
+ this.cleanupScheduled = false;
211
+ });
212
+ }
213
+ }
214
+ }
215
+ // =============================================================================
216
+ // Global Instance
217
+ // =============================================================================
218
+ /**
219
+ * Singleton resource manager for the renderer.
220
+ */
221
+ export const wasmResources = new WASMResourceManager();
222
+ // =============================================================================
223
+ // Helper: Scoped Resource Guard
224
+ // =============================================================================
225
+ /**
226
+ * RAII-style guard for temporary WASM resources.
227
+ *
228
+ * Usage:
229
+ * ```
230
+ * using(canvasKit.Paint(), paint => {
231
+ * canvas.drawRect(rect, paint);
232
+ * }); // paint.delete() called automatically
233
+ * ```
234
+ */
235
+ export function using(resource, fn) {
236
+ try {
237
+ return fn(resource);
238
+ }
239
+ finally {
240
+ resource.delete();
241
+ }
242
+ }
243
+ /**
244
+ * Async version of using().
245
+ */
246
+ export async function usingAsync(resource, fn) {
247
+ try {
248
+ return await fn(resource);
249
+ }
250
+ finally {
251
+ resource.delete();
252
+ }
253
+ }
@@ -0,0 +1,168 @@
1
+ /**
2
+ * wgpu Renderer Adapter
3
+ *
4
+ * This module provides a `CanvasRenderer` implementation that delegates to
5
+ * `WasmGpuRenderer` from the vsc-wasm crate.
6
+ *
7
+ * ## Phase F: wgpu Migration
8
+ *
9
+ * This adapter implements the wgpu-based renderer:
10
+ *
11
+ * ```text
12
+ * Before (legacy):
13
+ * RenderLoop → GpuRenderer → PathEntity → WebGL
14
+ *
15
+ * After (wgpu):
16
+ * RenderLoop → WgpuRendererAdapter → WasmGpuRenderer → wgpu → WebGPU/WebGL
17
+ * ```
18
+ *
19
+ * ## Key Differences from legacy renderer
20
+ *
21
+ * 1. **No Resource Management**: wgpu resources are managed on the Rust side.
22
+ * TypeScript does NOT need to call delete() on GPU objects.
23
+ *
24
+ * 2. **JSON Serialization**: DrawCommands are converted to CanvasNode JSON
25
+ * and passed to WASM. This adds serialization overhead but simplifies
26
+ * the boundary significantly.
27
+ *
28
+ * 3. **Batched Rendering**: draw() accumulates commands; flush() sends them
29
+ * all to the GPU in a single render pass.
30
+ */
31
+ import type { EntityId, RasterBounds, RenderableEntity } from '../ast/types.js';
32
+ /**
33
+ * Canvas draw command (batched for wgpu).
34
+ */
35
+ interface DrawCommand {
36
+ entityId: EntityId;
37
+ type: 'path' | 'text' | 'image' | 'group';
38
+ bounds: RasterBounds;
39
+ payload: unknown;
40
+ }
41
+ /**
42
+ * CanvasRenderer interface from render-loop.ts
43
+ */
44
+ interface CanvasRenderer {
45
+ getEntity(id: EntityId): RenderableEntity | undefined;
46
+ draw(command: DrawCommand): void;
47
+ flush(): void;
48
+ }
49
+ /**
50
+ * WasmGpuRenderer interface (from vsc-wasm with "gpu" feature).
51
+ *
52
+ * This is the TypeScript-side view of the Rust struct.
53
+ */
54
+ interface WasmGpuRenderer {
55
+ render(nodes_json: string): void;
56
+ resize(width: number, height: number): void;
57
+ readonly width: number;
58
+ readonly height: number;
59
+ }
60
+ /**
61
+ * Factory function for WasmGpuRenderer.
62
+ */
63
+ interface WasmGpuRendererStatic {
64
+ create(canvas: HTMLCanvasElement): Promise<WasmGpuRenderer>;
65
+ }
66
+ /**
67
+ * wgpu-based CanvasRenderer implementation.
68
+ *
69
+ * This adapter accumulates DrawCommands during a frame, converts them to
70
+ * CanvasNode JSON, and sends them to WasmGpuRenderer on flush().
71
+ *
72
+ * ## Usage
73
+ *
74
+ * ```typescript
75
+ * import { createWgpuRendererAdapter } from './wgpu-renderer-adapter';
76
+ *
77
+ * const canvas = document.getElementById('viewport') as HTMLCanvasElement;
78
+ * const adapter = await createWgpuRendererAdapter(canvas, entityStore);
79
+ *
80
+ * // In render loop:
81
+ * adapter.draw(command1);
82
+ * adapter.draw(command2);
83
+ * adapter.flush(); // Sends all commands to GPU
84
+ * ```
85
+ */
86
+ export declare class WgpuRendererAdapter implements CanvasRenderer {
87
+ /** WASM GPU renderer instance */
88
+ private renderer;
89
+ /** Entity store for getEntity() lookups */
90
+ private entityStore;
91
+ /** Accumulated draw commands for current frame */
92
+ private pendingCommands;
93
+ /** Canvas element for resize handling */
94
+ private canvas;
95
+ private constructor();
96
+ /**
97
+ * Create a new wgpu renderer adapter.
98
+ *
99
+ * @param canvas - HTML canvas element to render to
100
+ * @param entityStore - Map of entities for getEntity() lookups
101
+ * @param wasmModule - WASM module containing WasmGpuRenderer
102
+ */
103
+ static create(canvas: HTMLCanvasElement, entityStore: Map<EntityId, RenderableEntity>, wasmModule: {
104
+ WasmGpuRenderer: WasmGpuRendererStatic;
105
+ }): Promise<WgpuRendererAdapter>;
106
+ /**
107
+ * Get a renderable entity by ID.
108
+ */
109
+ getEntity(id: EntityId): RenderableEntity | undefined;
110
+ /**
111
+ * Queue a draw command for the current frame.
112
+ *
113
+ * Commands are accumulated until flush() is called.
114
+ */
115
+ draw(command: DrawCommand): void;
116
+ /**
117
+ * Flush all pending commands to the GPU.
118
+ *
119
+ * This converts accumulated DrawCommands to CanvasNode JSON and
120
+ * sends them to the WASM renderer in a single call.
121
+ *
122
+ * ## Serialization
123
+ *
124
+ * Uses `wasmBoundaryReplacer` to handle:
125
+ * - `bigint` values (not supported by standard JSON.stringify)
126
+ * - `Rational` objects → `"num/den"` string format (Rust expectation)
127
+ */
128
+ flush(): void;
129
+ /**
130
+ * Handle canvas resize.
131
+ *
132
+ * Call this when the canvas element is resized.
133
+ */
134
+ resize(width: number, height: number): void;
135
+ /**
136
+ * Get the current render surface dimensions.
137
+ */
138
+ getDimensions(): {
139
+ width: number;
140
+ height: number;
141
+ };
142
+ /**
143
+ * Update the entity store reference.
144
+ *
145
+ * Call this when the entity store is replaced (e.g., after HMR).
146
+ */
147
+ setEntityStore(store: Map<EntityId, RenderableEntity>): void;
148
+ /**
149
+ * Convert DrawCommands to CanvasNodes.
150
+ *
151
+ * This is the key translation layer between the render loop's command
152
+ * abstraction and the GPU renderer's scene graph.
153
+ */
154
+ private commandsToCanvasNodes;
155
+ }
156
+ /**
157
+ * Create a wgpu renderer adapter.
158
+ *
159
+ * This is the main entry point for creating a wgpu-based renderer.
160
+ *
161
+ * @param canvas - HTML canvas element to render to
162
+ * @param entityStore - Map of entities for getEntity() lookups
163
+ * @param wasmModule - WASM module containing WasmGpuRenderer
164
+ */
165
+ export declare function createWgpuRendererAdapter(canvas: HTMLCanvasElement, entityStore: Map<EntityId, RenderableEntity>, wasmModule: {
166
+ WasmGpuRenderer: WasmGpuRendererStatic;
167
+ }): Promise<WgpuRendererAdapter>;
168
+ export {};
@@ -0,0 +1,230 @@
1
+ /**
2
+ * wgpu Renderer Adapter
3
+ *
4
+ * This module provides a `CanvasRenderer` implementation that delegates to
5
+ * `WasmGpuRenderer` from the vsc-wasm crate.
6
+ *
7
+ * ## Phase F: wgpu Migration
8
+ *
9
+ * This adapter implements the wgpu-based renderer:
10
+ *
11
+ * ```text
12
+ * Before (legacy):
13
+ * RenderLoop → GpuRenderer → PathEntity → WebGL
14
+ *
15
+ * After (wgpu):
16
+ * RenderLoop → WgpuRendererAdapter → WasmGpuRenderer → wgpu → WebGPU/WebGL
17
+ * ```
18
+ *
19
+ * ## Key Differences from legacy renderer
20
+ *
21
+ * 1. **No Resource Management**: wgpu resources are managed on the Rust side.
22
+ * TypeScript does NOT need to call delete() on GPU objects.
23
+ *
24
+ * 2. **JSON Serialization**: DrawCommands are converted to CanvasNode JSON
25
+ * and passed to WASM. This adds serialization overhead but simplifies
26
+ * the boundary significantly.
27
+ *
28
+ * 3. **Batched Rendering**: draw() accumulates commands; flush() sends them
29
+ * all to the GPU in a single render pass.
30
+ */
31
+ // =============================================================================
32
+ // JSON Serialization Helpers
33
+ // =============================================================================
34
+ /**
35
+ * Custom JSON replacer for WASM boundary serialization.
36
+ *
37
+ * ## Problem
38
+ *
39
+ * JavaScript's `JSON.stringify()` does not support `bigint`:
40
+ * ```
41
+ * JSON.stringify({ n: 100n }) // TypeError: BigInt value can't be serialized
42
+ * ```
43
+ *
44
+ * ## Solution
45
+ *
46
+ * This replacer converts:
47
+ * - `bigint` → string (e.g., `100n` → `"100"`)
48
+ * - `Rational` object → `"numerator/denominator"` string
49
+ *
50
+ * The Rust side expects Rational as `"num/den"` string format
51
+ * (see `vsc-core/src/types.rs` Deserialize impl).
52
+ */
53
+ function wasmBoundaryReplacer(_key, value) {
54
+ // Handle raw bigint (shouldn't occur in well-typed code, but safety first)
55
+ if (typeof value === 'bigint') {
56
+ return value.toString();
57
+ }
58
+ // Handle Rational objects: { numerator: bigint, denominator: bigint }
59
+ if (isRational(value)) {
60
+ return `${value.numerator}/${value.denominator}`;
61
+ }
62
+ return value;
63
+ }
64
+ /**
65
+ * Type guard for Rational objects.
66
+ */
67
+ function isRational(value) {
68
+ return (value !== null &&
69
+ typeof value === 'object' &&
70
+ 'numerator' in value &&
71
+ 'denominator' in value &&
72
+ typeof value.numerator === 'bigint' &&
73
+ typeof value.denominator === 'bigint');
74
+ }
75
+ // =============================================================================
76
+ // Adapter Implementation
77
+ // =============================================================================
78
+ /**
79
+ * wgpu-based CanvasRenderer implementation.
80
+ *
81
+ * This adapter accumulates DrawCommands during a frame, converts them to
82
+ * CanvasNode JSON, and sends them to WasmGpuRenderer on flush().
83
+ *
84
+ * ## Usage
85
+ *
86
+ * ```typescript
87
+ * import { createWgpuRendererAdapter } from './wgpu-renderer-adapter';
88
+ *
89
+ * const canvas = document.getElementById('viewport') as HTMLCanvasElement;
90
+ * const adapter = await createWgpuRendererAdapter(canvas, entityStore);
91
+ *
92
+ * // In render loop:
93
+ * adapter.draw(command1);
94
+ * adapter.draw(command2);
95
+ * adapter.flush(); // Sends all commands to GPU
96
+ * ```
97
+ */
98
+ export class WgpuRendererAdapter {
99
+ /** WASM GPU renderer instance */
100
+ renderer;
101
+ /** Entity store for getEntity() lookups */
102
+ entityStore;
103
+ /** Accumulated draw commands for current frame */
104
+ pendingCommands = [];
105
+ /** Canvas element for resize handling */
106
+ canvas;
107
+ constructor(renderer, canvas, entityStore) {
108
+ this.renderer = renderer;
109
+ this.canvas = canvas;
110
+ this.entityStore = entityStore;
111
+ }
112
+ /**
113
+ * Create a new wgpu renderer adapter.
114
+ *
115
+ * @param canvas - HTML canvas element to render to
116
+ * @param entityStore - Map of entities for getEntity() lookups
117
+ * @param wasmModule - WASM module containing WasmGpuRenderer
118
+ */
119
+ static async create(canvas, entityStore, wasmModule) {
120
+ const renderer = await wasmModule.WasmGpuRenderer.create(canvas);
121
+ return new WgpuRendererAdapter(renderer, canvas, entityStore);
122
+ }
123
+ // ===========================================================================
124
+ // CanvasRenderer Interface
125
+ // ===========================================================================
126
+ /**
127
+ * Get a renderable entity by ID.
128
+ */
129
+ getEntity(id) {
130
+ return this.entityStore.get(id);
131
+ }
132
+ /**
133
+ * Queue a draw command for the current frame.
134
+ *
135
+ * Commands are accumulated until flush() is called.
136
+ */
137
+ draw(command) {
138
+ this.pendingCommands.push(command);
139
+ }
140
+ /**
141
+ * Flush all pending commands to the GPU.
142
+ *
143
+ * This converts accumulated DrawCommands to CanvasNode JSON and
144
+ * sends them to the WASM renderer in a single call.
145
+ *
146
+ * ## Serialization
147
+ *
148
+ * Uses `wasmBoundaryReplacer` to handle:
149
+ * - `bigint` values (not supported by standard JSON.stringify)
150
+ * - `Rational` objects → `"num/den"` string format (Rust expectation)
151
+ */
152
+ flush() {
153
+ if (this.pendingCommands.length === 0) {
154
+ return;
155
+ }
156
+ // Convert DrawCommands to CanvasNodes
157
+ const nodes = this.commandsToCanvasNodes(this.pendingCommands);
158
+ // Serialize with custom replacer for Rational/bigint handling
159
+ const json = JSON.stringify(nodes, wasmBoundaryReplacer);
160
+ this.renderer.render(json);
161
+ // Clear pending commands
162
+ this.pendingCommands = [];
163
+ }
164
+ // ===========================================================================
165
+ // Public Utilities
166
+ // ===========================================================================
167
+ /**
168
+ * Handle canvas resize.
169
+ *
170
+ * Call this when the canvas element is resized.
171
+ */
172
+ resize(width, height) {
173
+ this.renderer.resize(width, height);
174
+ }
175
+ /**
176
+ * Get the current render surface dimensions.
177
+ */
178
+ getDimensions() {
179
+ return {
180
+ width: this.renderer.width,
181
+ height: this.renderer.height,
182
+ };
183
+ }
184
+ /**
185
+ * Update the entity store reference.
186
+ *
187
+ * Call this when the entity store is replaced (e.g., after HMR).
188
+ */
189
+ setEntityStore(store) {
190
+ this.entityStore = store;
191
+ }
192
+ // ===========================================================================
193
+ // Private Methods
194
+ // ===========================================================================
195
+ /**
196
+ * Convert DrawCommands to CanvasNodes.
197
+ *
198
+ * This is the key translation layer between the render loop's command
199
+ * abstraction and the GPU renderer's scene graph.
200
+ */
201
+ commandsToCanvasNodes(commands) {
202
+ const nodes = [];
203
+ for (const cmd of commands) {
204
+ const entity = this.entityStore.get(cmd.entityId);
205
+ if (!entity?.canvas) {
206
+ continue;
207
+ }
208
+ // Use the entity's CanvasNode directly
209
+ // The payload in DrawCommand may contain overrides, but for now
210
+ // we use the entity's stored canvas representation
211
+ nodes.push(entity.canvas);
212
+ }
213
+ return nodes;
214
+ }
215
+ }
216
+ // =============================================================================
217
+ // Factory Function
218
+ // =============================================================================
219
+ /**
220
+ * Create a wgpu renderer adapter.
221
+ *
222
+ * This is the main entry point for creating a wgpu-based renderer.
223
+ *
224
+ * @param canvas - HTML canvas element to render to
225
+ * @param entityStore - Map of entities for getEntity() lookups
226
+ * @param wasmModule - WASM module containing WasmGpuRenderer
227
+ */
228
+ export async function createWgpuRendererAdapter(canvas, entityStore, wasmModule) {
229
+ return WgpuRendererAdapter.create(canvas, entityStore, wasmModule);
230
+ }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Tests for SemanticTranslator (Phase 7: Task 20)
3
+ */
4
+ export {};