@viewscript/renderer 0.1.0-202605140721 → 0.1.0-202605141229

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.
@@ -0,0 +1,268 @@
1
+ /**
2
+ * WASM Solver Bridge
3
+ *
4
+ * Implements ConstraintSolver by delegating to WasmViewScriptEngine.tick().
5
+ * Handles QSnapshot construction and TickResult parsing.
6
+ *
7
+ * ## Responsibilities
8
+ *
9
+ * 1. Convert TStateMutation[] to QSnapshot JSON format
10
+ * 2. Inject FFI results into QSnapshot.values (via injectFfiResults)
11
+ * 3. Call WasmViewScriptEngine.tick() with JSON payload
12
+ * 4. Parse TickResult and extract bounds + pendingFfiCalls
13
+ *
14
+ * ## Data Flow
15
+ *
16
+ * ```
17
+ * TStateMutation[] + FfiResult[]
18
+ * │
19
+ * ▼
20
+ * buildQSnapshot()
21
+ * │
22
+ * ▼
23
+ * QSnapshot JSON ─────► WasmViewScriptEngine.tick()
24
+ * │
25
+ * ▼
26
+ * TickResult JSON
27
+ * │
28
+ * ▼
29
+ * SolverEvaluationResult
30
+ * ```
31
+ */
32
+
33
+ import type { EntityId, PVectorBounds } from '../ast/types.js';
34
+ import type { FfiResult, PendingFfiCall } from './ffi-dispatcher.js';
35
+
36
+ // =============================================================================
37
+ // Types
38
+ // =============================================================================
39
+
40
+ /**
41
+ * Semantic T-vector state keys.
42
+ */
43
+ type TStateKey =
44
+ | 'hover'
45
+ | 'pressed'
46
+ | 'focused'
47
+ | 'scroll_x'
48
+ | 'scroll_y'
49
+ | 'drag_progress'
50
+ | 'animation_t'
51
+ | 'gesture_phase';
52
+
53
+ /**
54
+ * T-vector state mutation from Q-dimension event.
55
+ */
56
+ export interface TStateMutation {
57
+ entityId: EntityId;
58
+ state: TStateKey;
59
+ value: number;
60
+ timestamp: number;
61
+ }
62
+
63
+ /**
64
+ * Result of constraint solver evaluation.
65
+ */
66
+ export interface SolverEvaluationResult {
67
+ bounds: Map<EntityId, PVectorBounds>;
68
+ pendingFfiCalls: PendingFfiCall[];
69
+ }
70
+
71
+ /**
72
+ * ConstraintSolver interface.
73
+ *
74
+ * Note: This interface is FFI-agnostic. FFI result injection
75
+ * is handled by the concrete WasmSolverBridge implementation.
76
+ */
77
+ export interface ConstraintSolver {
78
+ evaluate(mutations: TStateMutation[]): SolverEvaluationResult;
79
+ }
80
+
81
+ /**
82
+ * QSnapshot format for WASM tick() input.
83
+ */
84
+ interface QSnapshot {
85
+ values: Record<string, QValue>;
86
+ mutations: unknown[];
87
+ }
88
+
89
+ /**
90
+ * Q-dimension value types.
91
+ */
92
+ type QValue =
93
+ | { type: 'float'; value: number }
94
+ | { type: 'int'; value: number }
95
+ | { type: 'bool'; value: boolean };
96
+
97
+ /**
98
+ * TickResult from WASM tick() output.
99
+ */
100
+ interface TickResult {
101
+ pending_ffi_calls: PendingFfiCall[];
102
+ }
103
+
104
+ /**
105
+ * WASM engine interface (subset used by bridge).
106
+ */
107
+ export interface WasmEngine {
108
+ tick(inputJson: string): string;
109
+ }
110
+
111
+ // =============================================================================
112
+ // WASM Solver Bridge
113
+ // =============================================================================
114
+
115
+ export class WasmSolverBridge implements ConstraintSolver {
116
+ private engine: WasmEngine;
117
+
118
+ /** Pending FFI results to inject into next QSnapshot */
119
+ private pendingFfiResults: FfiResult[] = [];
120
+
121
+ /** Entity name to ID mapping (for mutation conversion) */
122
+ private entityNameToId: Map<string, EntityId> = new Map();
123
+
124
+ /** Q-variable name to entity+state mapping */
125
+ private qVarMapping: Map<string, { entityId: EntityId; state: TStateKey }> = new Map();
126
+
127
+ constructor(engine: WasmEngine) {
128
+ this.engine = engine;
129
+ }
130
+
131
+ // ===========================================================================
132
+ // Public API
133
+ // ===========================================================================
134
+
135
+ /**
136
+ * Inject FFI results for the next evaluate() call.
137
+ *
138
+ * Called from render-loop Phase 0.5. Results are consumed
139
+ * and cleared when evaluate() runs.
140
+ *
141
+ * @param results - FFI function results from previous frame
142
+ */
143
+ injectFfiResults(results: FfiResult[]): void {
144
+ this.pendingFfiResults = results;
145
+ }
146
+
147
+ /**
148
+ * Register entity name to ID mapping.
149
+ *
150
+ * Called during initialization to enable mutation conversion.
151
+ */
152
+ registerEntity(name: string, id: EntityId): void {
153
+ this.entityNameToId.set(name, id);
154
+ }
155
+
156
+ /**
157
+ * Register Q-variable mapping.
158
+ *
159
+ * Maps Q-variable names to entity+state for mutation routing.
160
+ */
161
+ registerQVariable(name: string, entityId: EntityId, state: TStateKey): void {
162
+ this.qVarMapping.set(name, { entityId, state });
163
+ }
164
+
165
+ /**
166
+ * Evaluate constraints and return solver result.
167
+ *
168
+ * Implements ConstraintSolver interface.
169
+ */
170
+ evaluate(mutations: TStateMutation[]): SolverEvaluationResult {
171
+ // 1. Build QSnapshot from mutations + FFI results
172
+ const qSnapshot = this.buildQSnapshot(mutations);
173
+
174
+ // 2. Call WASM tick()
175
+ let resultJson: string;
176
+ try {
177
+ resultJson = this.engine.tick(JSON.stringify(qSnapshot));
178
+ } catch (error) {
179
+ // tick() failed - FFI results are preserved for next frame retry
180
+ console.error('[WasmSolverBridge] tick() error:', error);
181
+ return {
182
+ bounds: new Map(),
183
+ pendingFfiCalls: [],
184
+ };
185
+ }
186
+
187
+ // 3. tick() succeeded - now safe to clear FFI results
188
+ this.pendingFfiResults = [];
189
+
190
+ // 4. Parse TickResult
191
+ let tickResult: TickResult;
192
+ try {
193
+ tickResult = JSON.parse(resultJson) as TickResult;
194
+ } catch (error) {
195
+ console.error('[WasmSolverBridge] Failed to parse TickResult:', error);
196
+ return {
197
+ bounds: new Map(),
198
+ pendingFfiCalls: [],
199
+ };
200
+ }
201
+
202
+ // 5. Build SolverEvaluationResult
203
+ // Note: bounds extraction is deferred - WASM tick() currently
204
+ // handles rendering internally. This will be refactored when
205
+ // bounds are returned explicitly from tick().
206
+ return {
207
+ bounds: new Map(), // TODO: Extract from tick() when available
208
+ pendingFfiCalls: tickResult.pending_ffi_calls ?? [],
209
+ };
210
+ }
211
+
212
+ // ===========================================================================
213
+ // Internal Helpers
214
+ // ===========================================================================
215
+
216
+ /**
217
+ * Build QSnapshot from mutations and FFI results.
218
+ */
219
+ private buildQSnapshot(mutations: TStateMutation[]): QSnapshot {
220
+ const values: Record<string, QValue> = {};
221
+
222
+ // 1. Inject FFI results into values
223
+ for (const result of this.pendingFfiResults) {
224
+ values[result.name] = { type: 'float', value: result.value };
225
+ }
226
+
227
+ // 2. Convert TStateMutations to Q-values
228
+ // Each mutation targets an entity's T-state, which maps to a Q-variable
229
+ for (const mutation of mutations) {
230
+ const qVarName = this.getQVarName(mutation.entityId, mutation.state);
231
+ values[qVarName] = { type: 'float', value: mutation.value };
232
+ }
233
+
234
+ // 3. Build mutation array for legacy format compatibility
235
+ const mutationArray = mutations.map((m) => ({
236
+ entity_id: m.entityId,
237
+ state: m.state,
238
+ value: m.value,
239
+ }));
240
+
241
+ return {
242
+ values,
243
+ mutations: mutationArray,
244
+ };
245
+ }
246
+
247
+ /**
248
+ * Get Q-variable name for an entity's T-state.
249
+ *
250
+ * Convention: `{entityId}_{state}` (e.g., "1_hover", "2_scroll_y")
251
+ */
252
+ private getQVarName(entityId: EntityId, state: TStateKey): string {
253
+ return `${entityId}_${state}`;
254
+ }
255
+ }
256
+
257
+ // =============================================================================
258
+ // Factory Function
259
+ // =============================================================================
260
+
261
+ /**
262
+ * Create a WASM solver bridge instance.
263
+ *
264
+ * @param engine - WasmViewScriptEngine instance
265
+ */
266
+ export function createWasmSolverBridge(engine: WasmEngine): WasmSolverBridge {
267
+ return new WasmSolverBridge(engine);
268
+ }