@holoscript/core 1.0.0-alpha.2 → 2.0.1

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 (74) hide show
  1. package/package.json +2 -2
  2. package/src/HoloScript2DParser.js +227 -0
  3. package/src/HoloScript2DParser.ts +5 -0
  4. package/src/HoloScriptCodeParser.js +1102 -0
  5. package/src/HoloScriptCodeParser.ts +145 -20
  6. package/src/HoloScriptDebugger.js +458 -0
  7. package/src/HoloScriptParser.js +338 -0
  8. package/src/HoloScriptPlusParser.js +371 -0
  9. package/src/HoloScriptPlusParser.ts +543 -0
  10. package/src/HoloScriptRuntime.js +1399 -0
  11. package/src/HoloScriptRuntime.test.js +351 -0
  12. package/src/HoloScriptRuntime.ts +17 -3
  13. package/src/HoloScriptTypeChecker.js +356 -0
  14. package/src/__tests__/GraphicsServices.test.js +357 -0
  15. package/src/__tests__/GraphicsServices.test.ts +427 -0
  16. package/src/__tests__/HoloScriptPlusParser.test.js +317 -0
  17. package/src/__tests__/HoloScriptPlusParser.test.ts +392 -0
  18. package/src/__tests__/integration.test.js +336 -0
  19. package/src/__tests__/performance.bench.js +218 -0
  20. package/src/__tests__/type-checker.test.js +60 -0
  21. package/src/__tests__/type-checker.test.ts +73 -0
  22. package/src/index.js +217 -0
  23. package/src/index.ts +158 -18
  24. package/src/interop/Interoperability.js +413 -0
  25. package/src/interop/Interoperability.ts +494 -0
  26. package/src/logger.js +42 -0
  27. package/src/parser/EnhancedParser.js +205 -0
  28. package/src/parser/EnhancedParser.ts +251 -0
  29. package/src/parser/HoloScriptPlusParser.js +928 -0
  30. package/src/parser/HoloScriptPlusParser.ts +1089 -0
  31. package/src/runtime/HoloScriptPlusRuntime.js +674 -0
  32. package/src/runtime/HoloScriptPlusRuntime.ts +861 -0
  33. package/src/runtime/PerformanceTelemetry.js +323 -0
  34. package/src/runtime/PerformanceTelemetry.ts +467 -0
  35. package/src/runtime/RuntimeOptimization.js +361 -0
  36. package/src/runtime/RuntimeOptimization.ts +416 -0
  37. package/src/services/HololandGraphicsPipelineService.js +506 -0
  38. package/src/services/HololandGraphicsPipelineService.ts +662 -0
  39. package/src/services/PlatformPerformanceOptimizer.js +356 -0
  40. package/src/services/PlatformPerformanceOptimizer.ts +503 -0
  41. package/src/state/ReactiveState.js +427 -0
  42. package/src/state/ReactiveState.ts +572 -0
  43. package/src/tools/DeveloperExperience.js +376 -0
  44. package/src/tools/DeveloperExperience.ts +438 -0
  45. package/src/traits/AIDriverTrait.js +322 -0
  46. package/src/traits/AIDriverTrait.test.js +329 -0
  47. package/src/traits/AIDriverTrait.test.ts +357 -0
  48. package/src/traits/AIDriverTrait.ts +474 -0
  49. package/src/traits/LightingTrait.js +313 -0
  50. package/src/traits/LightingTrait.test.js +410 -0
  51. package/src/traits/LightingTrait.test.ts +462 -0
  52. package/src/traits/LightingTrait.ts +505 -0
  53. package/src/traits/MaterialTrait.js +194 -0
  54. package/src/traits/MaterialTrait.test.js +286 -0
  55. package/src/traits/MaterialTrait.test.ts +329 -0
  56. package/src/traits/MaterialTrait.ts +324 -0
  57. package/src/traits/RenderingTrait.js +356 -0
  58. package/src/traits/RenderingTrait.test.js +363 -0
  59. package/src/traits/RenderingTrait.test.ts +427 -0
  60. package/src/traits/RenderingTrait.ts +555 -0
  61. package/src/traits/VRTraitSystem.js +740 -0
  62. package/src/traits/VRTraitSystem.ts +1040 -0
  63. package/src/traits/VoiceInputTrait.js +284 -0
  64. package/src/traits/VoiceInputTrait.test.js +226 -0
  65. package/src/traits/VoiceInputTrait.test.ts +252 -0
  66. package/src/traits/VoiceInputTrait.ts +401 -0
  67. package/src/types/AdvancedTypeSystem.js +226 -0
  68. package/src/types/AdvancedTypeSystem.ts +494 -0
  69. package/src/types/HoloScriptPlus.d.ts +853 -0
  70. package/src/types.js +6 -0
  71. package/src/types.ts +96 -1
  72. package/tsconfig.json +1 -1
  73. package/tsup.config.d.ts +2 -0
  74. package/tsup.config.js +18 -0
@@ -0,0 +1,674 @@
1
+ /**
2
+ * HoloScript+ Runtime Engine
3
+ *
4
+ * Executes parsed HoloScript+ AST with:
5
+ * - Control flow (@for, @if) evaluation
6
+ * - Lifecycle hook management
7
+ * - VR trait integration
8
+ * - Reactive state binding
9
+ * - TypeScript companion integration
10
+ *
11
+ * @version 1.0.0
12
+ */
13
+ import { createState, ExpressionEvaluator } from '../state/ReactiveState';
14
+ import { vrTraitRegistry } from '../traits/VRTraitSystem';
15
+ // =============================================================================
16
+ // BUILT-IN FUNCTIONS
17
+ // =============================================================================
18
+ function createBuiltins(runtime) {
19
+ return {
20
+ Math,
21
+ range: (start, end, step = 1) => {
22
+ const result = [];
23
+ if (step > 0) {
24
+ for (let i = start; i < end; i += step) {
25
+ result.push(i);
26
+ }
27
+ }
28
+ else if (step < 0) {
29
+ for (let i = start; i > end; i += step) {
30
+ result.push(i);
31
+ }
32
+ }
33
+ return result;
34
+ },
35
+ interpolate_color: (t, from, to) => {
36
+ // Parse hex colors
37
+ const parseHex = (hex) => {
38
+ const clean = hex.replace('#', '');
39
+ return [
40
+ parseInt(clean.substring(0, 2), 16),
41
+ parseInt(clean.substring(2, 4), 16),
42
+ parseInt(clean.substring(4, 6), 16),
43
+ ];
44
+ };
45
+ const toHex = (r, g, b) => {
46
+ const clamp = (v) => Math.max(0, Math.min(255, Math.round(v)));
47
+ return `#${clamp(r).toString(16).padStart(2, '0')}${clamp(g).toString(16).padStart(2, '0')}${clamp(b).toString(16).padStart(2, '0')}`;
48
+ };
49
+ const [r1, g1, b1] = parseHex(from);
50
+ const [r2, g2, b2] = parseHex(to);
51
+ return toHex(r1 + (r2 - r1) * t, g1 + (g2 - g1) * t, b1 + (b2 - b1) * t);
52
+ },
53
+ distance_to: (point) => {
54
+ const viewer = runtime.vrContext.headset.position;
55
+ return Math.sqrt(Math.pow(point[0] - viewer[0], 2) +
56
+ Math.pow(point[1] - viewer[1], 2) +
57
+ Math.pow(point[2] - viewer[2], 2));
58
+ },
59
+ distance_to_viewer: () => {
60
+ return 0; // Override in node context
61
+ },
62
+ hand_position: (handId) => {
63
+ const hand = handId === 'left' ? runtime.vrContext.hands.left : runtime.vrContext.hands.right;
64
+ return hand?.position || [0, 0, 0];
65
+ },
66
+ hand_velocity: (handId) => {
67
+ const hand = handId === 'left' ? runtime.vrContext.hands.left : runtime.vrContext.hands.right;
68
+ return hand?.velocity || [0, 0, 0];
69
+ },
70
+ dominant_hand: () => {
71
+ // Default to right hand
72
+ return runtime.vrContext.hands.right || runtime.vrContext.hands.left || {
73
+ id: 'right',
74
+ position: [0, 0, 0],
75
+ rotation: [0, 0, 0],
76
+ velocity: [0, 0, 0],
77
+ grip: 0,
78
+ trigger: 0,
79
+ };
80
+ },
81
+ play_sound: (source, options) => {
82
+ runtime.emit('play_sound', { source, ...options });
83
+ },
84
+ haptic_feedback: (hand, intensity) => {
85
+ const handId = typeof hand === 'string' ? hand : hand.id;
86
+ runtime.emit('haptic', { hand: handId, intensity });
87
+ },
88
+ haptic_pulse: (intensity) => {
89
+ runtime.emit('haptic', { hand: 'both', intensity });
90
+ },
91
+ apply_velocity: (node, velocity) => {
92
+ runtime.emit('apply_velocity', { node, velocity });
93
+ },
94
+ spawn: (template, position) => {
95
+ return runtime.spawnTemplate(template, position);
96
+ },
97
+ destroy: (node) => {
98
+ runtime.destroyNode(node);
99
+ },
100
+ api_call: async (url, method, body) => {
101
+ const response = await fetch(url, {
102
+ method,
103
+ headers: body ? { 'Content-Type': 'application/json' } : undefined,
104
+ body: body ? JSON.stringify(body) : undefined,
105
+ });
106
+ return response.json();
107
+ },
108
+ open_modal: (modalId) => {
109
+ runtime.emit('open_modal', { id: modalId });
110
+ },
111
+ close_modal: (modalId) => {
112
+ runtime.emit('close_modal', { id: modalId });
113
+ },
114
+ setTimeout: (callback, delay) => {
115
+ return window.setTimeout(callback, delay);
116
+ },
117
+ clearTimeout: (id) => {
118
+ window.clearTimeout(id);
119
+ },
120
+ };
121
+ }
122
+ // =============================================================================
123
+ // RUNTIME IMPLEMENTATION
124
+ // =============================================================================
125
+ class HoloScriptPlusRuntimeImpl {
126
+ constructor(ast, options = {}) {
127
+ this.rootInstance = null;
128
+ this.eventHandlers = new Map();
129
+ this.templates = new Map();
130
+ this.updateLoopId = null;
131
+ this.lastUpdateTime = 0;
132
+ this.mounted = false;
133
+ // VR context
134
+ this.vrContext = {
135
+ hands: {
136
+ left: null,
137
+ right: null,
138
+ },
139
+ headset: {
140
+ position: [0, 1.6, 0],
141
+ rotation: [0, 0, 0],
142
+ },
143
+ controllers: {
144
+ left: null,
145
+ right: null,
146
+ },
147
+ };
148
+ this.ast = ast;
149
+ this.options = options;
150
+ this.state = createState({});
151
+ this.traitRegistry = vrTraitRegistry;
152
+ this.companions = options.companions || {};
153
+ this.builtins = createBuiltins(this);
154
+ // Create expression evaluator with context
155
+ this.evaluator = new ExpressionEvaluator(this.state.getSnapshot(), this.builtins);
156
+ // Initialize state from AST
157
+ this.initializeState();
158
+ // Load imports
159
+ this.loadImports();
160
+ }
161
+ // ==========================================================================
162
+ // INITIALIZATION
163
+ // ==========================================================================
164
+ initializeState() {
165
+ const stateDirective = this.ast.root.directives.find((d) => d.type === 'state');
166
+ if (stateDirective && stateDirective.type === 'state') {
167
+ this.state.update(stateDirective.body);
168
+ }
169
+ }
170
+ loadImports() {
171
+ for (const imp of this.ast.imports) {
172
+ // Companions should be provided via options
173
+ if (this.companions[imp.alias]) {
174
+ // Already loaded
175
+ continue;
176
+ }
177
+ console.warn(`Import ${imp.path} not found. Provide via companions option.`);
178
+ }
179
+ }
180
+ // ==========================================================================
181
+ // MOUNTING
182
+ // ==========================================================================
183
+ mount(container) {
184
+ if (this.mounted) {
185
+ console.warn('Runtime already mounted');
186
+ return;
187
+ }
188
+ this.mounted = true;
189
+ // Build node tree
190
+ this.rootInstance = this.instantiateNode(this.ast.root, null);
191
+ // Mount to container
192
+ if (this.options.renderer && this.rootInstance) {
193
+ this.options.renderer.appendChild(container, this.rootInstance.renderedNode);
194
+ }
195
+ // Call mount lifecycle
196
+ this.callLifecycle(this.rootInstance, 'on_mount');
197
+ // Start update loop
198
+ this.startUpdateLoop();
199
+ }
200
+ unmount() {
201
+ if (!this.mounted)
202
+ return;
203
+ // Stop update loop
204
+ this.stopUpdateLoop();
205
+ // Call unmount lifecycle
206
+ if (this.rootInstance) {
207
+ this.callLifecycle(this.rootInstance, 'on_unmount');
208
+ this.destroyInstance(this.rootInstance);
209
+ }
210
+ this.rootInstance = null;
211
+ this.mounted = false;
212
+ }
213
+ // ==========================================================================
214
+ // NODE INSTANTIATION
215
+ // ==========================================================================
216
+ instantiateNode(node, parent) {
217
+ const instance = {
218
+ node,
219
+ renderedNode: null,
220
+ lifecycleHandlers: new Map(),
221
+ children: [],
222
+ parent,
223
+ destroyed: false,
224
+ };
225
+ // Process directives
226
+ this.processDirectives(instance);
227
+ // Create rendered element
228
+ if (this.options.renderer) {
229
+ const properties = this.evaluateProperties(node.properties);
230
+ instance.renderedNode = this.options.renderer.createElement(node.type, properties);
231
+ }
232
+ // Attach VR traits
233
+ for (const [traitName, config] of node.traits) {
234
+ this.traitRegistry.attachTrait(node, traitName, config, this.createTraitContext(instance));
235
+ }
236
+ // Process children with control flow
237
+ const children = this.processControlFlow(node.children, node.directives);
238
+ for (const childNode of children) {
239
+ const childInstance = this.instantiateNode(childNode, instance);
240
+ instance.children.push(childInstance);
241
+ if (this.options.renderer && instance.renderedNode) {
242
+ this.options.renderer.appendChild(instance.renderedNode, childInstance.renderedNode);
243
+ }
244
+ }
245
+ return instance;
246
+ }
247
+ processDirectives(instance) {
248
+ for (const directive of instance.node.directives) {
249
+ if (directive.type === 'lifecycle') {
250
+ this.registerLifecycleHandler(instance, directive);
251
+ }
252
+ }
253
+ }
254
+ registerLifecycleHandler(instance, directive) {
255
+ const { hook, params, body } = directive;
256
+ // Create handler function
257
+ const handler = (...args) => {
258
+ // Build parameter context
259
+ const paramContext = {};
260
+ if (params) {
261
+ params.forEach((param, i) => {
262
+ paramContext[param] = args[i];
263
+ });
264
+ }
265
+ // Evaluate body
266
+ this.evaluator.updateContext({
267
+ ...this.state.getSnapshot(),
268
+ ...paramContext,
269
+ node: instance.node,
270
+ self: instance.node,
271
+ });
272
+ try {
273
+ // Check if body looks like code or expression
274
+ if (body.includes(';') || body.includes('{')) {
275
+ // Execute as code block
276
+ new Function(...Object.keys(this.builtins), ...Object.keys(paramContext), 'state', 'node', body)(...Object.values(this.builtins), ...Object.values(paramContext), this.state, instance.node);
277
+ }
278
+ else {
279
+ // Evaluate as expression
280
+ this.evaluator.evaluate(body);
281
+ }
282
+ }
283
+ catch (error) {
284
+ console.error(`Error in lifecycle handler ${hook}:`, error);
285
+ }
286
+ };
287
+ // Register handler
288
+ if (!instance.lifecycleHandlers.has(hook)) {
289
+ instance.lifecycleHandlers.set(hook, []);
290
+ }
291
+ instance.lifecycleHandlers.get(hook).push(handler);
292
+ }
293
+ // ==========================================================================
294
+ // CONTROL FLOW
295
+ // ==========================================================================
296
+ processControlFlow(children, directives) {
297
+ const result = [];
298
+ // Process @for directives
299
+ for (const directive of directives) {
300
+ if (directive.type === 'for') {
301
+ const items = this.evaluateExpression(directive.iterable);
302
+ if (Array.isArray(items)) {
303
+ items.forEach((item, index) => {
304
+ // Create context for each iteration
305
+ const iterContext = {
306
+ [directive.variable]: item,
307
+ index,
308
+ first: index === 0,
309
+ last: index === items.length - 1,
310
+ even: index % 2 === 0,
311
+ odd: index % 2 !== 0,
312
+ };
313
+ // Clone and process body nodes
314
+ for (const bodyNode of directive.body) {
315
+ const cloned = this.cloneNodeWithContext(bodyNode, iterContext);
316
+ result.push(cloned);
317
+ }
318
+ });
319
+ }
320
+ }
321
+ else if (directive.type === 'if') {
322
+ const condition = this.evaluateExpression(directive.condition);
323
+ if (condition) {
324
+ result.push(...directive.body);
325
+ }
326
+ else if (directive.else) {
327
+ result.push(...directive.else);
328
+ }
329
+ }
330
+ }
331
+ // Add regular children
332
+ result.push(...children);
333
+ return result;
334
+ }
335
+ cloneNodeWithContext(node, context) {
336
+ // Deep clone the node
337
+ const cloned = {
338
+ type: node.type,
339
+ id: node.id ? this.interpolateString(node.id, context) : undefined,
340
+ properties: this.interpolateProperties(node.properties, context),
341
+ directives: [...node.directives],
342
+ children: node.children.map((child) => this.cloneNodeWithContext(child, context)),
343
+ traits: new Map(node.traits),
344
+ loc: node.loc,
345
+ };
346
+ return cloned;
347
+ }
348
+ interpolateString(str, context) {
349
+ return str.replace(/\$\{([^}]+)\}/g, (_match, expr) => {
350
+ this.evaluator.updateContext(context);
351
+ const value = this.evaluator.evaluate(expr);
352
+ return String(value ?? '');
353
+ });
354
+ }
355
+ interpolateProperties(properties, context) {
356
+ const result = {};
357
+ for (const [key, value] of Object.entries(properties)) {
358
+ if (typeof value === 'string') {
359
+ result[key] = this.interpolateString(value, context);
360
+ }
361
+ else if (value && typeof value === 'object' && '__expr' in value) {
362
+ this.evaluator.updateContext(context);
363
+ result[key] = this.evaluator.evaluate(value.__raw);
364
+ }
365
+ else {
366
+ result[key] = value;
367
+ }
368
+ }
369
+ return result;
370
+ }
371
+ // ==========================================================================
372
+ // EXPRESSION EVALUATION
373
+ // ==========================================================================
374
+ evaluateExpression(expr) {
375
+ this.evaluator.updateContext(this.state.getSnapshot());
376
+ return this.evaluator.evaluate(expr);
377
+ }
378
+ evaluateProperties(properties) {
379
+ const result = {};
380
+ for (const [key, value] of Object.entries(properties)) {
381
+ if (value && typeof value === 'object' && '__expr' in value) {
382
+ result[key] = this.evaluateExpression(value.__raw);
383
+ }
384
+ else if (value && typeof value === 'object' && '__ref' in value) {
385
+ // Reference to state or companion
386
+ const ref = value.__ref;
387
+ result[key] = this.state.get(ref) ?? ref;
388
+ }
389
+ else if (typeof value === 'string' && value.includes('${')) {
390
+ // String interpolation
391
+ result[key] = this.interpolateString(value, this.state.getSnapshot());
392
+ }
393
+ else {
394
+ result[key] = value;
395
+ }
396
+ }
397
+ return result;
398
+ }
399
+ // ==========================================================================
400
+ // LIFECYCLE
401
+ // ==========================================================================
402
+ callLifecycle(instance, hook, ...args) {
403
+ if (!instance || instance.destroyed)
404
+ return;
405
+ const handlers = instance.lifecycleHandlers.get(hook);
406
+ if (handlers) {
407
+ handlers.forEach((handler) => {
408
+ try {
409
+ handler(...args);
410
+ }
411
+ catch (error) {
412
+ console.error(`Error in lifecycle ${hook}:`, error);
413
+ }
414
+ });
415
+ }
416
+ // Recurse to children
417
+ for (const child of instance.children) {
418
+ this.callLifecycle(child, hook, ...args);
419
+ }
420
+ }
421
+ // ==========================================================================
422
+ // UPDATE LOOP
423
+ // ==========================================================================
424
+ startUpdateLoop() {
425
+ this.lastUpdateTime = performance.now();
426
+ const update = () => {
427
+ const now = performance.now();
428
+ const delta = (now - this.lastUpdateTime) / 1000; // Convert to seconds
429
+ this.lastUpdateTime = now;
430
+ this.update(delta);
431
+ this.updateLoopId = requestAnimationFrame(update);
432
+ };
433
+ this.updateLoopId = requestAnimationFrame(update);
434
+ }
435
+ stopUpdateLoop() {
436
+ if (this.updateLoopId !== null) {
437
+ cancelAnimationFrame(this.updateLoopId);
438
+ this.updateLoopId = null;
439
+ }
440
+ }
441
+ update(delta) {
442
+ if (!this.rootInstance)
443
+ return;
444
+ // Update all instances
445
+ this.updateInstance(this.rootInstance, delta);
446
+ // Call update lifecycle
447
+ this.callLifecycle(this.rootInstance, 'on_update', delta);
448
+ }
449
+ updateInstance(instance, delta) {
450
+ if (instance.destroyed)
451
+ return;
452
+ // Update VR traits
453
+ const traitContext = this.createTraitContext(instance);
454
+ this.traitRegistry.updateAllTraits(instance.node, traitContext, delta);
455
+ // Update rendered element if properties changed
456
+ if (this.options.renderer && instance.renderedNode) {
457
+ const properties = this.evaluateProperties(instance.node.properties);
458
+ this.options.renderer.updateElement(instance.renderedNode, properties);
459
+ }
460
+ // Update children
461
+ for (const child of instance.children) {
462
+ this.updateInstance(child, delta);
463
+ }
464
+ }
465
+ // ==========================================================================
466
+ // TRAIT CONTEXT
467
+ // ==========================================================================
468
+ createTraitContext(_instance) {
469
+ return {
470
+ vr: {
471
+ hands: this.vrContext.hands,
472
+ headset: this.vrContext.headset,
473
+ getPointerRay: (hand) => {
474
+ const vrHand = hand === 'left' ? this.vrContext.hands.left : this.vrContext.hands.right;
475
+ if (!vrHand)
476
+ return null;
477
+ return {
478
+ origin: vrHand.position,
479
+ direction: [0, 0, -1], // Forward direction - should be calculated from rotation
480
+ };
481
+ },
482
+ getDominantHand: () => this.vrContext.hands.right || this.vrContext.hands.left,
483
+ },
484
+ physics: {
485
+ applyVelocity: (node, velocity) => {
486
+ this.emit('apply_velocity', { node, velocity });
487
+ },
488
+ applyAngularVelocity: (node, angularVelocity) => {
489
+ this.emit('apply_angular_velocity', { node, angularVelocity });
490
+ },
491
+ setKinematic: (node, kinematic) => {
492
+ this.emit('set_kinematic', { node, kinematic });
493
+ },
494
+ raycast: (_origin, _direction, _maxDistance) => {
495
+ // Would need physics engine integration
496
+ return null;
497
+ },
498
+ },
499
+ audio: {
500
+ playSound: (source, options) => {
501
+ this.emit('play_sound', { source, ...options });
502
+ },
503
+ },
504
+ haptics: {
505
+ pulse: (hand, intensity, duration) => {
506
+ this.emit('haptic', { hand, intensity, duration, type: 'pulse' });
507
+ },
508
+ rumble: (hand, intensity) => {
509
+ this.emit('haptic', { hand, intensity, type: 'rumble' });
510
+ },
511
+ },
512
+ emit: this.emit.bind(this),
513
+ getState: () => this.state.getSnapshot(),
514
+ setState: (updates) => this.state.update(updates),
515
+ };
516
+ }
517
+ // ==========================================================================
518
+ // NODE DESTRUCTION
519
+ // ==========================================================================
520
+ destroyInstance(instance) {
521
+ if (instance.destroyed)
522
+ return;
523
+ instance.destroyed = true;
524
+ // Destroy children first
525
+ for (const child of instance.children) {
526
+ this.destroyInstance(child);
527
+ }
528
+ // Detach traits
529
+ const traitContext = this.createTraitContext(instance);
530
+ for (const traitName of instance.node.traits.keys()) {
531
+ this.traitRegistry.detachTrait(instance.node, traitName, traitContext);
532
+ }
533
+ // Destroy rendered element
534
+ if (this.options.renderer && instance.renderedNode) {
535
+ this.options.renderer.destroy(instance.renderedNode);
536
+ }
537
+ // Clear handlers
538
+ instance.lifecycleHandlers.clear();
539
+ instance.children = [];
540
+ }
541
+ // ==========================================================================
542
+ // PUBLIC API
543
+ // ==========================================================================
544
+ updateData(data) {
545
+ this.state.set('data', data);
546
+ this.callLifecycle(this.rootInstance, 'on_data_update', data);
547
+ }
548
+ getState() {
549
+ return this.state.getSnapshot();
550
+ }
551
+ setState(updates) {
552
+ this.state.update(updates);
553
+ }
554
+ emit(event, payload) {
555
+ const handlers = this.eventHandlers.get(event);
556
+ if (handlers) {
557
+ handlers.forEach((handler) => {
558
+ try {
559
+ handler(payload);
560
+ }
561
+ catch (error) {
562
+ console.error(`Error in event handler for ${event}:`, error);
563
+ }
564
+ });
565
+ }
566
+ }
567
+ on(event, handler) {
568
+ if (!this.eventHandlers.has(event)) {
569
+ this.eventHandlers.set(event, new Set());
570
+ }
571
+ this.eventHandlers.get(event).add(handler);
572
+ return () => {
573
+ this.eventHandlers.get(event)?.delete(handler);
574
+ };
575
+ }
576
+ // ==========================================================================
577
+ // VR INTEGRATION
578
+ // ==========================================================================
579
+ updateVRContext(context) {
580
+ this.vrContext = context;
581
+ }
582
+ handleVREvent(event, node) {
583
+ // Find instance for node
584
+ const instance = this.findInstance(node);
585
+ if (!instance)
586
+ return;
587
+ // Dispatch to traits
588
+ const traitContext = this.createTraitContext(instance);
589
+ this.traitRegistry.handleEventForAllTraits(node, traitContext, event);
590
+ // Call lifecycle hooks based on event type
591
+ const hookMapping = {
592
+ grab_start: 'on_grab',
593
+ grab_end: 'on_release',
594
+ hover_enter: 'on_hover_enter',
595
+ hover_exit: 'on_hover_exit',
596
+ point_enter: 'on_point_enter',
597
+ point_exit: 'on_point_exit',
598
+ collision: 'on_collision',
599
+ trigger_enter: 'on_trigger_enter',
600
+ trigger_exit: 'on_trigger_exit',
601
+ click: 'on_click',
602
+ };
603
+ const hook = hookMapping[event.type];
604
+ if (hook) {
605
+ this.callLifecycle(instance, hook, event);
606
+ }
607
+ }
608
+ findInstance(node, root = this.rootInstance) {
609
+ if (!root)
610
+ return null;
611
+ if (root.node === node)
612
+ return root;
613
+ for (const child of root.children) {
614
+ const found = this.findInstance(node, child);
615
+ if (found)
616
+ return found;
617
+ }
618
+ return null;
619
+ }
620
+ // ==========================================================================
621
+ // TEMPLATES & SPAWNING
622
+ // ==========================================================================
623
+ registerTemplate(name, node) {
624
+ this.templates.set(name, node);
625
+ }
626
+ spawnTemplate(name, position) {
627
+ const template = this.templates.get(name);
628
+ if (!template) {
629
+ throw new Error(`Template "${name}" not found`);
630
+ }
631
+ // Clone template
632
+ const cloned = this.cloneNodeWithContext(template, { position });
633
+ cloned.properties.position = position;
634
+ // Instantiate
635
+ if (this.rootInstance) {
636
+ const instance = this.instantiateNode(cloned, this.rootInstance);
637
+ this.rootInstance.children.push(instance);
638
+ if (this.options.renderer && this.rootInstance.renderedNode) {
639
+ this.options.renderer.appendChild(this.rootInstance.renderedNode, instance.renderedNode);
640
+ }
641
+ this.callLifecycle(instance, 'on_mount');
642
+ }
643
+ return cloned;
644
+ }
645
+ destroyNode(node) {
646
+ const instance = this.findInstance(node);
647
+ if (!instance)
648
+ return;
649
+ // Call unmount
650
+ this.callLifecycle(instance, 'on_unmount');
651
+ // Remove from parent
652
+ if (instance.parent) {
653
+ const index = instance.parent.children.indexOf(instance);
654
+ if (index > -1) {
655
+ instance.parent.children.splice(index, 1);
656
+ }
657
+ if (this.options.renderer && instance.parent.renderedNode && instance.renderedNode) {
658
+ this.options.renderer.removeChild(instance.parent.renderedNode, instance.renderedNode);
659
+ }
660
+ }
661
+ // Destroy
662
+ this.destroyInstance(instance);
663
+ }
664
+ }
665
+ // =============================================================================
666
+ // FACTORY FUNCTION
667
+ // =============================================================================
668
+ export function createRuntime(ast, options) {
669
+ return new HoloScriptPlusRuntimeImpl(ast, options);
670
+ }
671
+ // =============================================================================
672
+ // EXPORTS
673
+ // =============================================================================
674
+ export { HoloScriptPlusRuntimeImpl };