@vizij/runtime-react 0.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.
package/dist/index.js ADDED
@@ -0,0 +1,1065 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ VizijRuntimeFace: () => VizijRuntimeFace,
24
+ VizijRuntimeProvider: () => VizijRuntimeProvider,
25
+ useRigInput: () => useRigInput,
26
+ useVizijOutputs: () => useVizijOutputs,
27
+ useVizijRuntime: () => useVizijRuntime
28
+ });
29
+ module.exports = __toCommonJS(index_exports);
30
+
31
+ // src/VizijRuntimeProvider.tsx
32
+ var import_react2 = require("react");
33
+ var import_render = require("@vizij/render");
34
+ var import_orchestrator_react = require("@vizij/orchestrator-react");
35
+ var import_value_json2 = require("@vizij/value-json");
36
+
37
+ // src/context.ts
38
+ var import_react = require("react");
39
+ var VizijRuntimeContext = (0, import_react.createContext)(null);
40
+
41
+ // src/utils/graph.ts
42
+ function getNodes(spec) {
43
+ if (!spec || typeof spec !== "object") {
44
+ return [];
45
+ }
46
+ const maybeNodes = spec.nodes;
47
+ if (!Array.isArray(maybeNodes)) {
48
+ return [];
49
+ }
50
+ return maybeNodes;
51
+ }
52
+ function collectOutputPaths(spec) {
53
+ const nodes = getNodes(spec);
54
+ const outputs = /* @__PURE__ */ new Set();
55
+ nodes.forEach((node) => {
56
+ if (typeof node !== "object" || !node) {
57
+ return;
58
+ }
59
+ if (String(node.type ?? "").toLowerCase() !== "output") {
60
+ return;
61
+ }
62
+ const path = node.params?.path;
63
+ if (typeof path === "string" && path.trim()) {
64
+ outputs.add(path.trim());
65
+ }
66
+ });
67
+ return Array.from(outputs);
68
+ }
69
+ function collectInputPaths(spec) {
70
+ const nodes = getNodes(spec);
71
+ const inputs = /* @__PURE__ */ new Set();
72
+ nodes.forEach((node) => {
73
+ if (String(node.type ?? "").toLowerCase() !== "input") {
74
+ return;
75
+ }
76
+ const path = node.params?.path;
77
+ if (typeof path === "string" && path.trim()) {
78
+ inputs.add(path.trim());
79
+ }
80
+ });
81
+ return Array.from(inputs);
82
+ }
83
+ function collectInputPathMap(spec) {
84
+ const map = {};
85
+ const nodes = getNodes(spec);
86
+ nodes.forEach((node) => {
87
+ if (String(node.type ?? "").toLowerCase() !== "input") {
88
+ return;
89
+ }
90
+ const path = node.params?.path;
91
+ if (typeof path !== "string" || !path.trim()) {
92
+ return;
93
+ }
94
+ const id = String(node.id ?? "");
95
+ const key = id.startsWith("input_") ? id.slice("input_".length) : id || path.trim();
96
+ map[key] = path.trim();
97
+ });
98
+ return map;
99
+ }
100
+
101
+ // src/utils/valueConversion.ts
102
+ var import_value_json = require("@vizij/value-json");
103
+ function numericArrayToRaw(arr) {
104
+ const normalised = arr.map((entry) => Number(entry ?? 0));
105
+ switch (normalised.length) {
106
+ case 2:
107
+ return {
108
+ x: normalised[0],
109
+ y: normalised[1],
110
+ r: normalised[0],
111
+ g: normalised[1]
112
+ };
113
+ case 3:
114
+ return {
115
+ x: normalised[0],
116
+ y: normalised[1],
117
+ z: normalised[2],
118
+ r: normalised[0],
119
+ g: normalised[1],
120
+ b: normalised[2]
121
+ };
122
+ case 4:
123
+ return {
124
+ x: normalised[0],
125
+ y: normalised[1],
126
+ z: normalised[2],
127
+ w: normalised[3],
128
+ r: normalised[0],
129
+ g: normalised[1],
130
+ b: normalised[2],
131
+ a: normalised[3]
132
+ };
133
+ default:
134
+ return normalised;
135
+ }
136
+ }
137
+ function valueJSONToRaw(value) {
138
+ if (value == null) {
139
+ return void 0;
140
+ }
141
+ if (typeof value === "number" || typeof value === "string" || typeof value === "boolean") {
142
+ return value;
143
+ }
144
+ if (Array.isArray(value)) {
145
+ return value.map((entry) => valueJSONToRaw(entry));
146
+ }
147
+ if (typeof value === "object" && !("type" in value)) {
148
+ const entries = Object.entries(value).map(([key, entry]) => [
149
+ key,
150
+ valueJSONToRaw(entry)
151
+ ]);
152
+ return Object.fromEntries(entries);
153
+ }
154
+ if (!(0, import_value_json.isNormalizedValue)(value)) {
155
+ return void 0;
156
+ }
157
+ switch (value.type) {
158
+ case "float": {
159
+ const num = (0, import_value_json.valueAsNumber)(value);
160
+ return typeof num === "number" ? num : void 0;
161
+ }
162
+ case "bool": {
163
+ const boolVal = (0, import_value_json.valueAsBool)(value);
164
+ return typeof boolVal === "boolean" ? boolVal : void 0;
165
+ }
166
+ case "text": {
167
+ const text = (0, import_value_json.valueAsText)(value);
168
+ return typeof text === "string" ? text : void 0;
169
+ }
170
+ case "vec2":
171
+ case "vec3":
172
+ case "vec4":
173
+ case "quat":
174
+ case "vector": {
175
+ const vec = (0, import_value_json.valueAsVector)(value);
176
+ return vec ? numericArrayToRaw(vec) : void 0;
177
+ }
178
+ case "colorrgba": {
179
+ const color = (0, import_value_json.valueAsColorRgba)(value);
180
+ if (!color) {
181
+ return void 0;
182
+ }
183
+ const [r = 0, g = 0, b = 0, a = 1] = color;
184
+ return { r, g, b, a };
185
+ }
186
+ case "transform": {
187
+ const transform = (0, import_value_json.valueAsTransform)(value);
188
+ if (!transform) {
189
+ return void 0;
190
+ }
191
+ return {
192
+ translation: numericArrayToRaw(transform.translation),
193
+ rotation: numericArrayToRaw(transform.rotation),
194
+ scale: numericArrayToRaw(transform.scale)
195
+ };
196
+ }
197
+ case "record": {
198
+ const entries = Object.entries(value.data ?? {}).map(([key, entry]) => [
199
+ key,
200
+ valueJSONToRaw(entry)
201
+ ]);
202
+ return Object.fromEntries(entries);
203
+ }
204
+ case "enum": {
205
+ const [tag, inner] = value.data;
206
+ return {
207
+ tag,
208
+ value: valueJSONToRaw(inner)
209
+ };
210
+ }
211
+ default:
212
+ return void 0;
213
+ }
214
+ }
215
+
216
+ // src/VizijRuntimeProvider.tsx
217
+ var import_render2 = require("@vizij/render");
218
+ var import_jsx_runtime = require("react/jsx-runtime");
219
+ var DEFAULT_MERGE = {
220
+ outputs: "add",
221
+ intermediate: "add"
222
+ };
223
+ var DEFAULT_DURATION = 0.35;
224
+ var EASINGS = {
225
+ linear: (t) => t,
226
+ easeIn: (t) => t * t,
227
+ easeOut: (t) => 1 - (1 - t) * (1 - t),
228
+ easeInOut: (t) => t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2
229
+ };
230
+ function resolveEasing(easing) {
231
+ if (typeof easing === "function") {
232
+ return easing;
233
+ }
234
+ if (typeof easing === "string" && easing in EASINGS) {
235
+ return EASINGS[easing];
236
+ }
237
+ return EASINGS.linear;
238
+ }
239
+ function findRootId(world) {
240
+ for (const entry of Object.values(world)) {
241
+ if (entry && typeof entry === "object" && entry.type === "group" && entry.rootBounds && entry.id) {
242
+ return entry.id;
243
+ }
244
+ }
245
+ return null;
246
+ }
247
+ function normalisePath(path) {
248
+ if (!path) {
249
+ return path;
250
+ }
251
+ return path.startsWith("debug/") ? path.slice("debug/".length) : path;
252
+ }
253
+ function VizijRuntimeProvider({
254
+ assetBundle,
255
+ children,
256
+ namespace: namespaceProp,
257
+ faceId: faceIdProp,
258
+ autoCreate = true,
259
+ createOptions,
260
+ autostart = false,
261
+ mergeStrategy,
262
+ onRegisterControllers,
263
+ onStatusChange
264
+ }) {
265
+ const storeRef = (0, import_react2.useRef)();
266
+ if (!storeRef.current) {
267
+ storeRef.current = (0, import_render.createVizijStore)();
268
+ }
269
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_render.VizijContext.Provider, { value: storeRef.current, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
270
+ import_orchestrator_react.OrchestratorProvider,
271
+ {
272
+ autoCreate,
273
+ createOptions,
274
+ autostart,
275
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
276
+ VizijRuntimeProviderInner,
277
+ {
278
+ assetBundle,
279
+ namespace: namespaceProp,
280
+ faceId: faceIdProp,
281
+ autoCreate,
282
+ mergeStrategy,
283
+ onRegisterControllers,
284
+ onStatusChange,
285
+ store: storeRef.current,
286
+ children
287
+ }
288
+ )
289
+ }
290
+ ) });
291
+ }
292
+ function VizijRuntimeProviderInner({
293
+ assetBundle,
294
+ namespace: namespaceProp,
295
+ faceId: faceIdProp,
296
+ mergeStrategy,
297
+ onRegisterControllers,
298
+ onStatusChange,
299
+ store,
300
+ children,
301
+ autoCreate
302
+ }) {
303
+ const {
304
+ ready,
305
+ createOrchestrator,
306
+ registerGraph,
307
+ registerMergedGraph,
308
+ registerAnimation,
309
+ removeGraph,
310
+ removeAnimation,
311
+ listControllers,
312
+ setInput,
313
+ getPathSnapshot,
314
+ step: stepRuntime
315
+ } = (0, import_orchestrator_react.useOrchestrator)();
316
+ const frame = (0, import_orchestrator_react.useOrchFrame)();
317
+ const namespace = namespaceProp ?? assetBundle.namespace ?? "default";
318
+ const faceId = faceIdProp ?? assetBundle.faceId ?? assetBundle.pose?.config?.faceId ?? assetBundle.pose?.config?.faceId ?? void 0;
319
+ const [status, setStatus] = (0, import_react2.useState)({
320
+ loading: true,
321
+ ready: false,
322
+ error: null,
323
+ errors: [],
324
+ namespace,
325
+ faceId,
326
+ rootId: null,
327
+ outputPaths: [],
328
+ controllers: { graphs: [], anims: [] }
329
+ });
330
+ const errorsRef = (0, import_react2.useRef)([]);
331
+ const outputPathsRef = (0, import_react2.useRef)(/* @__PURE__ */ new Set());
332
+ const rigInputMapRef = (0, import_react2.useRef)({});
333
+ const registeredGraphsRef = (0, import_react2.useRef)([]);
334
+ const registeredAnimationsRef = (0, import_react2.useRef)([]);
335
+ const mergedGraphRef = (0, import_react2.useRef)(null);
336
+ const animationTweensRef = (0, import_react2.useRef)(/* @__PURE__ */ new Map());
337
+ const clipPlaybackRef = (0, import_react2.useRef)(/* @__PURE__ */ new Map());
338
+ const rafHandleRef = (0, import_react2.useRef)(null);
339
+ const lastFrameTimeRef = (0, import_react2.useRef)(null);
340
+ const reportStatus = (0, import_react2.useCallback)(
341
+ (updater) => {
342
+ setStatus((prev) => {
343
+ const next = updater(prev);
344
+ onStatusChange?.(next);
345
+ return next;
346
+ });
347
+ },
348
+ [onStatusChange]
349
+ );
350
+ const pushError = (0, import_react2.useCallback)(
351
+ (error) => {
352
+ errorsRef.current = [...errorsRef.current, error];
353
+ reportStatus((prev) => ({
354
+ ...prev,
355
+ error,
356
+ errors: errorsRef.current
357
+ }));
358
+ console.warn("[vizij-runtime]", error.message, error.cause);
359
+ },
360
+ [reportStatus]
361
+ );
362
+ const resetErrors = (0, import_react2.useCallback)(() => {
363
+ errorsRef.current = [];
364
+ reportStatus((prev) => ({
365
+ ...prev,
366
+ error: null,
367
+ errors: []
368
+ }));
369
+ }, [reportStatus]);
370
+ const clearControllers = (0, import_react2.useCallback)(() => {
371
+ const existing = listControllers();
372
+ existing.graphs.forEach((id) => {
373
+ try {
374
+ removeGraph(id);
375
+ } catch (err) {
376
+ pushError({
377
+ message: `Failed to remove graph ${id}`,
378
+ cause: err,
379
+ phase: "registration",
380
+ timestamp: performance.now()
381
+ });
382
+ }
383
+ });
384
+ existing.anims.forEach((id) => {
385
+ try {
386
+ removeAnimation(id);
387
+ } catch (err) {
388
+ pushError({
389
+ message: `Failed to remove animation ${id}`,
390
+ cause: err,
391
+ phase: "registration",
392
+ timestamp: performance.now()
393
+ });
394
+ }
395
+ });
396
+ registeredGraphsRef.current = [];
397
+ registeredAnimationsRef.current = [];
398
+ mergedGraphRef.current = null;
399
+ }, [listControllers, removeAnimation, removeGraph, pushError]);
400
+ (0, import_react2.useEffect)(() => {
401
+ reportStatus((prev) => ({
402
+ ...prev,
403
+ namespace,
404
+ faceId
405
+ }));
406
+ }, [namespace, faceId, reportStatus]);
407
+ (0, import_react2.useEffect)(() => {
408
+ let cancelled = false;
409
+ resetErrors();
410
+ reportStatus((prev) => ({
411
+ ...prev,
412
+ loading: true,
413
+ rootId: null,
414
+ ready: false,
415
+ outputPaths: [],
416
+ controllers: { graphs: [], anims: [] }
417
+ }));
418
+ const loadAssets = async () => {
419
+ try {
420
+ let world;
421
+ let animatables;
422
+ if (assetBundle.glb.kind === "url") {
423
+ [world, animatables] = await (0, import_render2.loadGLTF)(
424
+ assetBundle.glb.src,
425
+ [namespace],
426
+ assetBundle.glb.aggressiveImport ?? false,
427
+ assetBundle.glb.rootBounds
428
+ );
429
+ } else if (assetBundle.glb.kind === "blob") {
430
+ [world, animatables] = await (0, import_render2.loadGLTFFromBlob)(
431
+ assetBundle.glb.blob,
432
+ [namespace],
433
+ assetBundle.glb.aggressiveImport ?? false,
434
+ assetBundle.glb.rootBounds
435
+ );
436
+ } else {
437
+ world = assetBundle.glb.world;
438
+ animatables = assetBundle.glb.animatables;
439
+ }
440
+ if (cancelled) {
441
+ return;
442
+ }
443
+ const rootId = findRootId(world);
444
+ store.getState().addWorldElements(world, animatables, true);
445
+ reportStatus((prev) => ({
446
+ ...prev,
447
+ loading: false,
448
+ rootId,
449
+ namespace,
450
+ faceId
451
+ }));
452
+ } catch (err) {
453
+ if (cancelled) {
454
+ return;
455
+ }
456
+ pushError({
457
+ message: "Failed to load Vizij assets",
458
+ cause: err,
459
+ phase: "assets",
460
+ timestamp: performance.now()
461
+ });
462
+ reportStatus((prev) => ({
463
+ ...prev,
464
+ loading: false
465
+ }));
466
+ }
467
+ };
468
+ loadAssets();
469
+ return () => {
470
+ cancelled = true;
471
+ };
472
+ }, [
473
+ assetBundle,
474
+ namespace,
475
+ faceId,
476
+ store,
477
+ pushError,
478
+ reportStatus,
479
+ resetErrors
480
+ ]);
481
+ (0, import_react2.useEffect)(() => {
482
+ if (!ready && autoCreate) {
483
+ createOrchestrator().catch((err) => {
484
+ pushError({
485
+ message: "Failed to create orchestrator runtime",
486
+ cause: err,
487
+ phase: "orchestrator",
488
+ timestamp: performance.now()
489
+ });
490
+ });
491
+ }
492
+ }, [ready, autoCreate, createOrchestrator, pushError]);
493
+ const registerControllers = (0, import_react2.useCallback)(async () => {
494
+ const normalize = (spec) => spec;
495
+ const rigSpec = normalize(assetBundle.rig.spec);
496
+ const rigOutputs = collectOutputPaths(rigSpec);
497
+ const rigInputs = collectInputPaths(rigSpec);
498
+ rigInputMapRef.current = collectInputPathMap(rigSpec);
499
+ outputPathsRef.current = new Set(rigOutputs);
500
+ const rigConfig = {
501
+ id: assetBundle.rig.id,
502
+ spec: rigSpec,
503
+ subs: assetBundle.rig.subscriptions ?? {
504
+ inputs: rigInputs,
505
+ outputs: rigOutputs
506
+ }
507
+ };
508
+ const graphConfigs = [rigConfig];
509
+ if (assetBundle.pose?.graph) {
510
+ const poseSpec = normalize(assetBundle.pose.graph.spec);
511
+ const poseOutputs = collectOutputPaths(poseSpec);
512
+ const poseInputs = collectInputPaths(poseSpec);
513
+ graphConfigs.push({
514
+ id: assetBundle.pose.graph.id,
515
+ spec: poseSpec,
516
+ subs: assetBundle.pose.graph.subscriptions ?? {
517
+ inputs: poseInputs,
518
+ outputs: poseOutputs
519
+ }
520
+ });
521
+ }
522
+ clearControllers();
523
+ const graphIds = [];
524
+ try {
525
+ if (graphConfigs.length > 1) {
526
+ const mergedId = registerMergedGraph({
527
+ graphs: graphConfigs,
528
+ strategy: mergeStrategy ?? DEFAULT_MERGE
529
+ });
530
+ mergedGraphRef.current = mergedId;
531
+ graphIds.push(mergedId);
532
+ } else {
533
+ graphConfigs.forEach((cfg) => {
534
+ const id = registerGraph(cfg);
535
+ graphIds.push(id);
536
+ });
537
+ }
538
+ } catch (err) {
539
+ pushError({
540
+ message: "Failed to register rig graphs",
541
+ cause: err,
542
+ phase: "registration",
543
+ timestamp: performance.now()
544
+ });
545
+ }
546
+ registeredGraphsRef.current = graphIds;
547
+ const animationIds = [];
548
+ for (const anim of assetBundle.animations ?? []) {
549
+ try {
550
+ const config = {
551
+ id: anim.id,
552
+ setup: {
553
+ animation: anim.clip,
554
+ ...anim.setup ?? {}
555
+ }
556
+ };
557
+ const id = registerAnimation(config);
558
+ animationIds.push(id);
559
+ } catch (err) {
560
+ pushError({
561
+ message: `Failed to register animation ${anim.id}`,
562
+ cause: err,
563
+ phase: "animation",
564
+ timestamp: performance.now()
565
+ });
566
+ }
567
+ }
568
+ registeredAnimationsRef.current = animationIds;
569
+ if (assetBundle.initialInputs) {
570
+ Object.entries(assetBundle.initialInputs).forEach(([path, value]) => {
571
+ try {
572
+ setInput(path, value);
573
+ } catch (err) {
574
+ pushError({
575
+ message: `Failed to stage initial input ${path}`,
576
+ cause: err,
577
+ phase: "registration",
578
+ timestamp: performance.now()
579
+ });
580
+ }
581
+ });
582
+ }
583
+ const controllers = listControllers();
584
+ reportStatus((prev) => ({
585
+ ...prev,
586
+ ready: true,
587
+ controllers,
588
+ outputPaths: Array.from(outputPathsRef.current)
589
+ }));
590
+ onRegisterControllers?.(controllers);
591
+ }, [
592
+ assetBundle,
593
+ clearControllers,
594
+ listControllers,
595
+ mergeStrategy,
596
+ onRegisterControllers,
597
+ pushError,
598
+ registerAnimation,
599
+ registerGraph,
600
+ registerMergedGraph,
601
+ reportStatus,
602
+ setInput
603
+ ]);
604
+ (0, import_react2.useEffect)(() => {
605
+ if (!ready || status.loading) {
606
+ return;
607
+ }
608
+ registerControllers().catch((err) => {
609
+ pushError({
610
+ message: "Failed to register controllers",
611
+ cause: err,
612
+ phase: "registration",
613
+ timestamp: performance.now()
614
+ });
615
+ });
616
+ }, [ready, status.loading, registerControllers, pushError]);
617
+ (0, import_react2.useEffect)(() => {
618
+ if (!frame) {
619
+ return;
620
+ }
621
+ const writes = frame.merged_writes ?? [];
622
+ if (!writes.length) {
623
+ return;
624
+ }
625
+ const setWorldValue = store.getState().setValue;
626
+ const namespaceValue = status.namespace;
627
+ writes.forEach((write) => {
628
+ const path = normalisePath(write.path);
629
+ if (!outputPathsRef.current.has(path)) {
630
+ return;
631
+ }
632
+ const raw = valueJSONToRaw(write.value);
633
+ if (raw === void 0) {
634
+ return;
635
+ }
636
+ setWorldValue(path, namespaceValue, raw);
637
+ });
638
+ }, [frame, status.namespace, store]);
639
+ const stagePoseNeutral = (0, import_react2.useCallback)(
640
+ (force = false) => {
641
+ const neutral = assetBundle.pose?.config?.neutralInputs ?? {};
642
+ const rigMap = rigInputMapRef.current;
643
+ const staged = /* @__PURE__ */ new Set();
644
+ Object.entries(neutral).forEach(([id, value]) => {
645
+ const path = rigMap[id];
646
+ if (!path) {
647
+ return;
648
+ }
649
+ const include = assetBundle.pose?.stageNeutralFilter;
650
+ if (include && !include(id, path)) {
651
+ return;
652
+ }
653
+ setInput(path, { float: Number.isFinite(value) ? value : 0 });
654
+ staged.add(path);
655
+ });
656
+ if (force) {
657
+ Object.entries(rigMap).forEach(([id, path]) => {
658
+ if (staged.has(path)) {
659
+ return;
660
+ }
661
+ const include = assetBundle.pose?.stageNeutralFilter;
662
+ if (include && !include(id, path)) {
663
+ return;
664
+ }
665
+ setInput(path, { float: 0 });
666
+ });
667
+ }
668
+ },
669
+ [assetBundle.pose?.config?.neutralInputs, setInput]
670
+ );
671
+ const setRendererValue = (0, import_react2.useCallback)(
672
+ (id, ns, value) => {
673
+ store.getState().setValue(id, ns, value);
674
+ },
675
+ [store]
676
+ );
677
+ const cancelAnimation = (0, import_react2.useCallback)((path) => {
678
+ if (animationTweensRef.current.has(path)) {
679
+ const entry = animationTweensRef.current.get(path);
680
+ animationTweensRef.current.delete(path);
681
+ entry?.resolve();
682
+ }
683
+ }, []);
684
+ const scheduleLoop = (0, import_react2.useCallback)(() => {
685
+ if (rafHandleRef.current !== null) {
686
+ return;
687
+ }
688
+ const tick = (timestamp) => {
689
+ if (lastFrameTimeRef.current == null) {
690
+ lastFrameTimeRef.current = timestamp;
691
+ }
692
+ const dt = Math.max(0, (timestamp - lastFrameTimeRef.current) / 1e3);
693
+ lastFrameTimeRef.current = timestamp;
694
+ advanceAnimationTweens(dt);
695
+ advanceClipPlayback(dt);
696
+ if (animationTweensRef.current.size > 0 || clipPlaybackRef.current.size > 0) {
697
+ rafHandleRef.current = requestAnimationFrame(tick);
698
+ } else {
699
+ rafHandleRef.current = null;
700
+ lastFrameTimeRef.current = null;
701
+ }
702
+ };
703
+ rafHandleRef.current = requestAnimationFrame(tick);
704
+ }, []);
705
+ const advanceAnimationTweens = (0, import_react2.useCallback)(
706
+ (dt) => {
707
+ if (animationTweensRef.current.size === 0) {
708
+ return;
709
+ }
710
+ const map = animationTweensRef.current;
711
+ const toDelete = [];
712
+ map.forEach((state, key) => {
713
+ state.elapsed += dt;
714
+ const progress = state.duration === 0 ? 1 : Math.min(state.elapsed / state.duration, 1);
715
+ const eased = state.easing(progress);
716
+ const value = state.from + (state.to - state.from) * eased;
717
+ setInput(state.path, { float: value });
718
+ if (progress >= 1) {
719
+ toDelete.push(key);
720
+ state.resolve();
721
+ }
722
+ });
723
+ toDelete.forEach((key) => map.delete(key));
724
+ },
725
+ [setInput]
726
+ );
727
+ const sampleTrack = (0, import_react2.useCallback)(
728
+ (track, time) => {
729
+ const keyframes = Array.isArray(track.keyframes) ? track.keyframes : [];
730
+ if (!keyframes.length) {
731
+ return 0;
732
+ }
733
+ const first = keyframes[0];
734
+ if (!first) {
735
+ return 0;
736
+ }
737
+ if (time <= Number(first.time ?? 0)) {
738
+ return Number(first.value ?? 0);
739
+ }
740
+ for (let i = 0; i < keyframes.length - 1; i += 1) {
741
+ const current = keyframes[i] ?? {};
742
+ const next = keyframes[i + 1] ?? {};
743
+ const start = Number(current.time ?? 0);
744
+ const end = Number(next.time ?? start);
745
+ if (time >= start && time <= end) {
746
+ const range = end - start || 1;
747
+ const factor = (time - start) / range;
748
+ const currentValue = Number(current.value ?? 0);
749
+ const nextValue = Number(next.value ?? currentValue);
750
+ return currentValue + (nextValue - currentValue) * factor;
751
+ }
752
+ }
753
+ const last = keyframes[keyframes.length - 1];
754
+ return Number(last?.value ?? 0);
755
+ },
756
+ []
757
+ );
758
+ const advanceClipPlayback = (0, import_react2.useCallback)(
759
+ (dt) => {
760
+ if (clipPlaybackRef.current.size === 0) {
761
+ return;
762
+ }
763
+ const map = clipPlaybackRef.current;
764
+ const toDelete = [];
765
+ map.forEach((state, key) => {
766
+ state.time += dt * state.speed;
767
+ const clip = assetBundle.animations?.find(
768
+ (anim) => anim.id === state.id
769
+ );
770
+ if (!clip) {
771
+ toDelete.push(key);
772
+ state.resolve();
773
+ return;
774
+ }
775
+ const clipData = clip.clip;
776
+ const duration = Number(clipData?.duration ?? state.duration);
777
+ state.duration = Number.isFinite(duration) && duration > 0 ? duration : state.duration;
778
+ if (state.time >= state.duration) {
779
+ toDelete.push(key);
780
+ state.time = state.duration;
781
+ }
782
+ const tracks = Array.isArray(clipData?.tracks) ? clipData.tracks : [];
783
+ tracks.forEach((track) => {
784
+ const value = sampleTrack(track, state.time) * state.weight;
785
+ const path = `animation/${clip.id}/${track.channel}`;
786
+ setInput(path, { float: value });
787
+ });
788
+ if (toDelete.includes(key)) {
789
+ state.resolve();
790
+ }
791
+ });
792
+ toDelete.forEach((key) => {
793
+ clipPlaybackRef.current.delete(key);
794
+ const clip = assetBundle.animations?.find((anim) => anim.id === key);
795
+ if (clip) {
796
+ const clipData = clip.clip;
797
+ const tracks = Array.isArray(clipData?.tracks) ? clipData.tracks : [];
798
+ tracks.forEach((track) => {
799
+ const path = `animation/${clip.id}/${track.channel}`;
800
+ setInput(path, { float: 0 });
801
+ });
802
+ }
803
+ });
804
+ },
805
+ [assetBundle.animations, sampleTrack, setInput]
806
+ );
807
+ const animateValue = (0, import_react2.useCallback)(
808
+ (path, target, options) => {
809
+ const easing = resolveEasing(options?.easing);
810
+ const duration = Math.max(0, options?.duration ?? DEFAULT_DURATION);
811
+ cancelAnimation(path);
812
+ const current = getPathSnapshot(path);
813
+ const fromValue = (0, import_value_json2.valueAsNumber)(current);
814
+ const toValue = (0, import_value_json2.valueAsNumber)(target);
815
+ if (fromValue == null || toValue == null || duration === 0) {
816
+ setInput(path, target);
817
+ return Promise.resolve();
818
+ }
819
+ return new Promise((resolve) => {
820
+ animationTweensRef.current.set(path, {
821
+ path,
822
+ from: fromValue,
823
+ to: toValue,
824
+ duration,
825
+ elapsed: 0,
826
+ easing,
827
+ resolve
828
+ });
829
+ scheduleLoop();
830
+ });
831
+ },
832
+ [cancelAnimation, getPathSnapshot, scheduleLoop, setInput]
833
+ );
834
+ const playAnimation = (0, import_react2.useCallback)(
835
+ (id, options) => {
836
+ const clip = assetBundle.animations?.find((anim) => anim.id === id);
837
+ if (!clip) {
838
+ return Promise.reject(
839
+ new Error(`Animation ${id} is not part of the current asset bundle.`)
840
+ );
841
+ }
842
+ if (clipPlaybackRef.current.has(id)) {
843
+ clipPlaybackRef.current.delete(id);
844
+ }
845
+ return new Promise((resolve) => {
846
+ const speed = options?.speed ?? 1;
847
+ const weight = options?.weight ?? clip.weight ?? 1;
848
+ const clipData = clip.clip;
849
+ clipPlaybackRef.current.set(id, {
850
+ id,
851
+ time: options?.reset ? 0 : 0,
852
+ duration: Number(clipData?.duration ?? 0),
853
+ speed: Number.isFinite(speed) && speed > 0 ? speed : 1,
854
+ weight,
855
+ resolve
856
+ });
857
+ scheduleLoop();
858
+ });
859
+ },
860
+ [assetBundle.animations, scheduleLoop]
861
+ );
862
+ const stopAnimation = (0, import_react2.useCallback)(
863
+ (id) => {
864
+ const clip = assetBundle.animations?.find((anim) => anim.id === id);
865
+ const state = clipPlaybackRef.current.get(id);
866
+ if (state) {
867
+ clipPlaybackRef.current.delete(id);
868
+ state.resolve();
869
+ }
870
+ if (clip) {
871
+ const clipData = clip.clip;
872
+ const tracks = Array.isArray(clipData?.tracks) ? clipData.tracks : [];
873
+ tracks.forEach((track) => {
874
+ const path = `animation/${clip.id}/${track.channel}`;
875
+ setInput(path, { float: 0 });
876
+ });
877
+ }
878
+ },
879
+ [assetBundle.animations, setInput]
880
+ );
881
+ const registerInputDriver = (0, import_react2.useCallback)(
882
+ (id, factory) => {
883
+ const driver = factory({
884
+ setInput,
885
+ setRendererValue,
886
+ namespace,
887
+ faceId
888
+ });
889
+ const wrapped = {
890
+ start: () => {
891
+ try {
892
+ driver.start();
893
+ } catch (err) {
894
+ pushError({
895
+ message: `Input driver ${id} failed to start`,
896
+ cause: err,
897
+ phase: "driver",
898
+ timestamp: performance.now()
899
+ });
900
+ }
901
+ },
902
+ stop: () => {
903
+ try {
904
+ driver.stop();
905
+ } catch (err) {
906
+ pushError({
907
+ message: `Input driver ${id} failed to stop`,
908
+ cause: err,
909
+ phase: "driver",
910
+ timestamp: performance.now()
911
+ });
912
+ }
913
+ },
914
+ dispose: () => {
915
+ try {
916
+ driver.dispose();
917
+ } catch (err) {
918
+ pushError({
919
+ message: `Input driver ${id} failed to dispose`,
920
+ cause: err,
921
+ phase: "driver",
922
+ timestamp: performance.now()
923
+ });
924
+ }
925
+ }
926
+ };
927
+ return wrapped;
928
+ },
929
+ [faceId, namespace, pushError, setInput, setRendererValue]
930
+ );
931
+ const advanceAnimations = (0, import_react2.useCallback)(
932
+ (dt) => {
933
+ advanceAnimationTweens(dt);
934
+ advanceClipPlayback(dt);
935
+ },
936
+ [advanceAnimationTweens, advanceClipPlayback]
937
+ );
938
+ const step = (0, import_react2.useCallback)(
939
+ (dt) => {
940
+ advanceAnimations(dt);
941
+ stepRuntime(dt);
942
+ },
943
+ [advanceAnimations, stepRuntime]
944
+ );
945
+ (0, import_react2.useEffect)(() => {
946
+ return () => {
947
+ if (rafHandleRef.current !== null) {
948
+ cancelAnimationFrame(rafHandleRef.current);
949
+ rafHandleRef.current = null;
950
+ }
951
+ lastFrameTimeRef.current = null;
952
+ animationTweensRef.current.clear();
953
+ clipPlaybackRef.current.clear();
954
+ };
955
+ }, []);
956
+ const contextValue = (0, import_react2.useMemo)(
957
+ () => ({
958
+ ...status,
959
+ assetBundle,
960
+ setInput,
961
+ setValue: setRendererValue,
962
+ stagePoseNeutral,
963
+ animateValue,
964
+ cancelAnimation,
965
+ registerInputDriver,
966
+ playAnimation,
967
+ stopAnimation,
968
+ step,
969
+ advanceAnimations
970
+ }),
971
+ [
972
+ status,
973
+ assetBundle,
974
+ setInput,
975
+ setRendererValue,
976
+ stagePoseNeutral,
977
+ animateValue,
978
+ cancelAnimation,
979
+ registerInputDriver,
980
+ playAnimation,
981
+ stopAnimation,
982
+ step,
983
+ advanceAnimations
984
+ ]
985
+ );
986
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(VizijRuntimeContext.Provider, { value: contextValue, children });
987
+ }
988
+
989
+ // src/VizijRuntimeFace.tsx
990
+ var import_react4 = require("react");
991
+ var import_render3 = require("@vizij/render");
992
+
993
+ // src/hooks/useVizijRuntime.ts
994
+ var import_react3 = require("react");
995
+ function useVizijRuntime() {
996
+ const ctx = (0, import_react3.useContext)(VizijRuntimeContext);
997
+ if (!ctx) {
998
+ throw new Error(
999
+ "useVizijRuntime must be used within a VizijRuntimeProvider."
1000
+ );
1001
+ }
1002
+ return ctx;
1003
+ }
1004
+
1005
+ // src/VizijRuntimeFace.tsx
1006
+ var import_jsx_runtime2 = require("react/jsx-runtime");
1007
+ function VizijRuntimeFaceInner({
1008
+ namespaceOverride,
1009
+ ...props
1010
+ }) {
1011
+ const { rootId, namespace } = useVizijRuntime();
1012
+ if (!rootId) {
1013
+ return null;
1014
+ }
1015
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1016
+ import_render3.Vizij,
1017
+ {
1018
+ ...props,
1019
+ rootId,
1020
+ namespace: namespaceOverride ?? namespace
1021
+ }
1022
+ );
1023
+ }
1024
+ var VizijRuntimeFace = (0, import_react4.memo)(VizijRuntimeFaceInner);
1025
+
1026
+ // src/hooks/useVizijOutputs.ts
1027
+ var import_render4 = require("@vizij/render");
1028
+ var import_utils = require("@vizij/utils");
1029
+ function useVizijOutputs(paths) {
1030
+ const { namespace } = useVizijRuntime();
1031
+ return (0, import_render4.useVizijStore)((state) => {
1032
+ const result = {};
1033
+ paths.forEach((path) => {
1034
+ const lookup = (0, import_utils.getLookup)(namespace, path);
1035
+ result[path] = state.values.get(lookup);
1036
+ });
1037
+ return result;
1038
+ });
1039
+ }
1040
+
1041
+ // src/hooks/useRigInput.ts
1042
+ var import_react5 = require("react");
1043
+ var import_render5 = require("@vizij/render");
1044
+ var import_utils2 = require("@vizij/utils");
1045
+ function useRigInput(path) {
1046
+ const { namespace, setInput } = useVizijRuntime();
1047
+ const value = (0, import_render5.useVizijStore)(
1048
+ (state) => state.values.get((0, import_utils2.getLookup)(namespace, path))
1049
+ );
1050
+ const setter = (0, import_react5.useCallback)(
1051
+ (next, shape) => {
1052
+ setInput(path, next, shape);
1053
+ },
1054
+ [path, setInput]
1055
+ );
1056
+ return [value, setter];
1057
+ }
1058
+ // Annotate the CommonJS export names for ESM import in node:
1059
+ 0 && (module.exports = {
1060
+ VizijRuntimeFace,
1061
+ VizijRuntimeProvider,
1062
+ useRigInput,
1063
+ useVizijOutputs,
1064
+ useVizijRuntime
1065
+ });