@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.
- package/dist/runtime/__tests__/ffi-dispatcher.test.d.ts +11 -0
- package/dist/runtime/__tests__/ffi-dispatcher.test.js +408 -0
- package/dist/runtime/__tests__/ffi-integration.test.d.ts +9 -0
- package/dist/runtime/__tests__/ffi-integration.test.js +514 -0
- package/dist/runtime/__tests__/wasm-solver-bridge.test.d.ts +9 -0
- package/dist/runtime/__tests__/wasm-solver-bridge.test.js +214 -0
- package/dist/runtime/ffi-dispatcher.d.ts +217 -0
- package/dist/runtime/ffi-dispatcher.js +291 -0
- package/dist/runtime/render-loop.d.ts +46 -1
- package/dist/runtime/render-loop.js +67 -1
- package/dist/runtime/wasm-solver-bridge.d.ts +122 -0
- package/dist/runtime/wasm-solver-bridge.js +168 -0
- package/package.json +1 -1
- package/src/runtime/__tests__/ffi-dispatcher.test.ts +519 -0
- package/src/runtime/__tests__/ffi-integration.test.ts +650 -0
- package/src/runtime/__tests__/wasm-solver-bridge.test.ts +276 -0
- package/src/runtime/ffi-dispatcher.ts +451 -0
- package/src/runtime/render-loop.ts +90 -2
- package/src/runtime/wasm-solver-bridge.ts +268 -0
|
@@ -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
|
+
}
|