@vizij/runtime-react 0.3.1 → 0.4.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/README.md +1 -0
- package/dist/index.cjs +295 -50
- package/dist/index.d.cts +47 -2
- package/dist/index.d.ts +47 -2
- package/dist/index.js +292 -50
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -1443,7 +1443,7 @@ function fieldNumber(value) {
|
|
|
1443
1443
|
if (!value || typeof value !== "object") {
|
|
1444
1444
|
return null;
|
|
1445
1445
|
}
|
|
1446
|
-
for (const key of ["u64", "u32", "f32", "f64"]) {
|
|
1446
|
+
for (const key of ["u64", "u32", "f32", "f64", "float"]) {
|
|
1447
1447
|
if (key in value) {
|
|
1448
1448
|
const parsed = Number(value[key]);
|
|
1449
1449
|
return Number.isFinite(parsed) ? parsed : null;
|
|
@@ -1451,38 +1451,90 @@ function fieldNumber(value) {
|
|
|
1451
1451
|
}
|
|
1452
1452
|
return null;
|
|
1453
1453
|
}
|
|
1454
|
+
function fieldText(value) {
|
|
1455
|
+
if (!value || typeof value !== "object") {
|
|
1456
|
+
return "";
|
|
1457
|
+
}
|
|
1458
|
+
const record = value;
|
|
1459
|
+
for (const key of ["str", "text"]) {
|
|
1460
|
+
if (typeof record[key] === "string") {
|
|
1461
|
+
return record[key];
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
return "";
|
|
1465
|
+
}
|
|
1466
|
+
function playerStateFromFields(byId) {
|
|
1467
|
+
const player = fieldNumber(byId.get(ANIMATION_MODULE_FIELD.statePlayer));
|
|
1468
|
+
if (player === null) {
|
|
1469
|
+
return null;
|
|
1470
|
+
}
|
|
1471
|
+
return {
|
|
1472
|
+
player,
|
|
1473
|
+
state: fieldText(byId.get(ANIMATION_MODULE_FIELD.stateState)),
|
|
1474
|
+
time: (fieldNumber(byId.get(ANIMATION_MODULE_FIELD.stateTimeNs)) ?? 0) / 1e9,
|
|
1475
|
+
duration: (fieldNumber(byId.get(ANIMATION_MODULE_FIELD.stateDurationNs)) ?? 0) / 1e9,
|
|
1476
|
+
speed: fieldNumber(byId.get(ANIMATION_MODULE_FIELD.stateSpeed)) ?? 1
|
|
1477
|
+
};
|
|
1478
|
+
}
|
|
1479
|
+
function fieldsToMap(fields) {
|
|
1480
|
+
if (!Array.isArray(fields)) {
|
|
1481
|
+
return null;
|
|
1482
|
+
}
|
|
1483
|
+
const byId = /* @__PURE__ */ new Map();
|
|
1484
|
+
for (const entry of fields) {
|
|
1485
|
+
const id = entry?.id;
|
|
1486
|
+
const value = entry?.value;
|
|
1487
|
+
if (typeof id === "string" && value && typeof value === "object") {
|
|
1488
|
+
byId.set(id, value);
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
return byId;
|
|
1492
|
+
}
|
|
1493
|
+
function recordToMap(element) {
|
|
1494
|
+
if (!element || typeof element !== "object") {
|
|
1495
|
+
return null;
|
|
1496
|
+
}
|
|
1497
|
+
const record = element.record;
|
|
1498
|
+
if (!record || typeof record !== "object" || Array.isArray(record)) {
|
|
1499
|
+
return null;
|
|
1500
|
+
}
|
|
1501
|
+
const byId = /* @__PURE__ */ new Map();
|
|
1502
|
+
for (const [id, value] of Object.entries(record)) {
|
|
1503
|
+
if (value && typeof value === "object") {
|
|
1504
|
+
byId.set(id, value);
|
|
1505
|
+
}
|
|
1506
|
+
}
|
|
1507
|
+
return byId;
|
|
1508
|
+
}
|
|
1454
1509
|
function decodePlayerStates(raw) {
|
|
1455
|
-
if (!raw || typeof raw !== "object"
|
|
1510
|
+
if (!raw || typeof raw !== "object") {
|
|
1456
1511
|
return [];
|
|
1457
1512
|
}
|
|
1458
|
-
const
|
|
1459
|
-
const elements = Array.isArray(structs?.elements) ? structs.elements : [];
|
|
1513
|
+
const container = raw;
|
|
1460
1514
|
const states = [];
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
const id = entry?.id;
|
|
1469
|
-
const value = entry?.value;
|
|
1470
|
-
if (typeof id === "string" && value && typeof value === "object") {
|
|
1471
|
-
byId.set(id, value);
|
|
1515
|
+
const structs = container.structs;
|
|
1516
|
+
if (structs && Array.isArray(structs.elements)) {
|
|
1517
|
+
for (const element of structs.elements) {
|
|
1518
|
+
const byId = fieldsToMap(element?.fields);
|
|
1519
|
+
const state = byId ? playerStateFromFields(byId) : null;
|
|
1520
|
+
if (state) {
|
|
1521
|
+
states.push(state);
|
|
1472
1522
|
}
|
|
1473
1523
|
}
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1524
|
+
return states;
|
|
1525
|
+
}
|
|
1526
|
+
const elements = Array.isArray(container.list) ? container.list : Array.isArray(container.array) ? container.array : null;
|
|
1527
|
+
if (!elements) {
|
|
1528
|
+
return states;
|
|
1529
|
+
}
|
|
1530
|
+
for (const element of elements) {
|
|
1531
|
+
const byId = recordToMap(element) ?? fieldsToMap(
|
|
1532
|
+
element?.struct?.fields ?? element?.fields
|
|
1533
|
+
);
|
|
1534
|
+
const state = byId ? playerStateFromFields(byId) : null;
|
|
1535
|
+
if (state) {
|
|
1536
|
+
states.push(state);
|
|
1477
1537
|
}
|
|
1478
|
-
const stateValue = byId.get(ANIMATION_MODULE_FIELD.stateState);
|
|
1479
|
-
states.push({
|
|
1480
|
-
player,
|
|
1481
|
-
state: stateValue && "str" in stateValue ? String(stateValue.str) : "",
|
|
1482
|
-
time: (fieldNumber(byId.get(ANIMATION_MODULE_FIELD.stateTimeNs)) ?? 0) / 1e9,
|
|
1483
|
-
duration: (fieldNumber(byId.get(ANIMATION_MODULE_FIELD.stateDurationNs)) ?? 0) / 1e9,
|
|
1484
|
-
speed: fieldNumber(byId.get(ANIMATION_MODULE_FIELD.stateSpeed)) ?? 1
|
|
1485
|
-
});
|
|
1486
1538
|
}
|
|
1487
1539
|
return states;
|
|
1488
1540
|
}
|
|
@@ -1552,6 +1604,7 @@ var AnimationModuleHost = class {
|
|
|
1552
1604
|
speed: 1,
|
|
1553
1605
|
loop: "loop",
|
|
1554
1606
|
weight: 1,
|
|
1607
|
+
pendingSeekSeconds: null,
|
|
1555
1608
|
animId: null,
|
|
1556
1609
|
playerId: null,
|
|
1557
1610
|
instId: null
|
|
@@ -1590,6 +1643,8 @@ var AnimationModuleHost = class {
|
|
|
1590
1643
|
}
|
|
1591
1644
|
entry.playing = false;
|
|
1592
1645
|
this.dispatch(
|
|
1646
|
+
"pause",
|
|
1647
|
+
clipId,
|
|
1593
1648
|
(device) => entry.playerId !== null ? device.call(pauseCall(entry.playerId)) : null
|
|
1594
1649
|
);
|
|
1595
1650
|
}
|
|
@@ -1605,6 +1660,8 @@ var AnimationModuleHost = class {
|
|
|
1605
1660
|
}
|
|
1606
1661
|
entry.playing = false;
|
|
1607
1662
|
this.dispatch(
|
|
1663
|
+
"stop",
|
|
1664
|
+
clipId,
|
|
1608
1665
|
(device) => entry.playerId !== null ? device.call(stopCall(entry.playerId)) : null
|
|
1609
1666
|
);
|
|
1610
1667
|
}
|
|
@@ -1614,7 +1671,14 @@ var AnimationModuleHost = class {
|
|
|
1614
1671
|
if (!entry) {
|
|
1615
1672
|
return;
|
|
1616
1673
|
}
|
|
1674
|
+
if (entry.playerId === null) {
|
|
1675
|
+
entry.pendingSeekSeconds = seconds;
|
|
1676
|
+
return;
|
|
1677
|
+
}
|
|
1678
|
+
entry.pendingSeekSeconds = null;
|
|
1617
1679
|
this.dispatch(
|
|
1680
|
+
"seek",
|
|
1681
|
+
clipId,
|
|
1618
1682
|
(device) => entry.playerId !== null ? device.call(seekCall(entry.playerId, seconds * 1e9)) : null
|
|
1619
1683
|
);
|
|
1620
1684
|
}
|
|
@@ -1626,6 +1690,8 @@ var AnimationModuleHost = class {
|
|
|
1626
1690
|
}
|
|
1627
1691
|
entry.speed = speed;
|
|
1628
1692
|
this.dispatch(
|
|
1693
|
+
"set speed",
|
|
1694
|
+
clipId,
|
|
1629
1695
|
(device) => entry.playerId !== null ? device.call(setSpeedCall(entry.playerId, speed)) : null
|
|
1630
1696
|
);
|
|
1631
1697
|
}
|
|
@@ -1637,6 +1703,8 @@ var AnimationModuleHost = class {
|
|
|
1637
1703
|
}
|
|
1638
1704
|
entry.loop = mode;
|
|
1639
1705
|
this.dispatch(
|
|
1706
|
+
"set loop",
|
|
1707
|
+
clipId,
|
|
1640
1708
|
(device) => entry.playerId !== null ? device.call(setLoopCall(entry.playerId, mode)) : null
|
|
1641
1709
|
);
|
|
1642
1710
|
}
|
|
@@ -1648,6 +1716,8 @@ var AnimationModuleHost = class {
|
|
|
1648
1716
|
}
|
|
1649
1717
|
entry.weight = weight;
|
|
1650
1718
|
this.dispatch(
|
|
1719
|
+
"set weight",
|
|
1720
|
+
clipId,
|
|
1651
1721
|
(device) => entry.playerId !== null && entry.instId !== null ? device.call(setWeightCall(entry.playerId, entry.instId, weight)) : null
|
|
1652
1722
|
);
|
|
1653
1723
|
}
|
|
@@ -1655,6 +1725,9 @@ var AnimationModuleHost = class {
|
|
|
1655
1725
|
* Re-issue setup and transport for every playing clip against a freshly
|
|
1656
1726
|
* started device (module guest state did not survive the rebuild). Voids
|
|
1657
1727
|
* stale ids first. Called from `DeviceSlot.onDeviceStarted`.
|
|
1728
|
+
*
|
|
1729
|
+
* A freshly loaded player advances without an explicit `play` — asserted by
|
|
1730
|
+
* the rebuild case in `animationPipeline.device.test.ts`.
|
|
1658
1731
|
*/
|
|
1659
1732
|
replayInto(device) {
|
|
1660
1733
|
for (const entry of this.clips.values()) {
|
|
@@ -1667,12 +1740,35 @@ var AnimationModuleHost = class {
|
|
|
1667
1740
|
}
|
|
1668
1741
|
}
|
|
1669
1742
|
/** Fire-and-forget a transport call against the live device, if any. */
|
|
1670
|
-
|
|
1743
|
+
/**
|
|
1744
|
+
* Issue a transport call, reporting rather than swallowing a rejection.
|
|
1745
|
+
*
|
|
1746
|
+
* This used to discard the error on the assumption that the only cause was
|
|
1747
|
+
* a rebuild racing the call, which `replayInto` would repair. When that is
|
|
1748
|
+
* not the cause — the module missing from the live device, say — the call
|
|
1749
|
+
* fails, the player keeps running, and the next frame of `player_states`
|
|
1750
|
+
* feedback tells the transport UI it is still playing. The button flips
|
|
1751
|
+
* back and the pause looks like it did nothing, with nothing anywhere
|
|
1752
|
+
* saying why. Whatever the cause, a dropped transport command is worth a
|
|
1753
|
+
* line in the console.
|
|
1754
|
+
*/
|
|
1755
|
+
dispatch(what, clipId, issue) {
|
|
1671
1756
|
const device = this.getDevice();
|
|
1672
1757
|
if (!device) {
|
|
1673
1758
|
return;
|
|
1674
1759
|
}
|
|
1675
|
-
|
|
1760
|
+
const issued = issue(device);
|
|
1761
|
+
if (!issued) {
|
|
1762
|
+
console.warn(
|
|
1763
|
+
`[vizij-runtime] animation ${what} had no player to act on for "${clipId}"`
|
|
1764
|
+
);
|
|
1765
|
+
return;
|
|
1766
|
+
}
|
|
1767
|
+
void issued.catch((err) => {
|
|
1768
|
+
console.warn(
|
|
1769
|
+
`[vizij-runtime] animation ${what} was dropped for "${clipId}"`,
|
|
1770
|
+
err
|
|
1771
|
+
);
|
|
1676
1772
|
});
|
|
1677
1773
|
}
|
|
1678
1774
|
async ensureLoaded(device, entry) {
|
|
@@ -1710,6 +1806,12 @@ var AnimationModuleHost = class {
|
|
|
1710
1806
|
setWeightCall(entry.playerId, entry.instId, entry.weight)
|
|
1711
1807
|
);
|
|
1712
1808
|
}
|
|
1809
|
+
if (entry.pendingSeekSeconds !== null) {
|
|
1810
|
+
followups.push(
|
|
1811
|
+
seekCall(entry.playerId, entry.pendingSeekSeconds * 1e9)
|
|
1812
|
+
);
|
|
1813
|
+
entry.pendingSeekSeconds = null;
|
|
1814
|
+
}
|
|
1713
1815
|
}
|
|
1714
1816
|
for (const call of followups) {
|
|
1715
1817
|
await device.call(call);
|
|
@@ -2290,6 +2392,23 @@ function convertBundlePrograms(entries) {
|
|
|
2290
2392
|
};
|
|
2291
2393
|
}).filter(Boolean);
|
|
2292
2394
|
}
|
|
2395
|
+
function convertBundleStarred(section) {
|
|
2396
|
+
const items = section?.items;
|
|
2397
|
+
if (!Array.isArray(items) || items.length === 0) {
|
|
2398
|
+
return void 0;
|
|
2399
|
+
}
|
|
2400
|
+
const resolved = [];
|
|
2401
|
+
for (const item of items) {
|
|
2402
|
+
if (!item || typeof item !== "object") {
|
|
2403
|
+
continue;
|
|
2404
|
+
}
|
|
2405
|
+
const { kind, id } = item;
|
|
2406
|
+
if ((kind === "driver" || kind === "pose") && typeof id === "string" && id) {
|
|
2407
|
+
resolved.push({ kind, id });
|
|
2408
|
+
}
|
|
2409
|
+
}
|
|
2410
|
+
return resolved.length > 0 ? resolved : void 0;
|
|
2411
|
+
}
|
|
2293
2412
|
function resolveConstraintDefault(path, namespace, inputConstraints) {
|
|
2294
2413
|
const trimmed = path.trim();
|
|
2295
2414
|
if (!trimmed) {
|
|
@@ -2447,6 +2566,12 @@ function mergeAssetBundle(base, extracted, extractedAnimations) {
|
|
|
2447
2566
|
hasBaseProgramsOverride,
|
|
2448
2567
|
convertBundlePrograms(resolvedBundle?.graphs)
|
|
2449
2568
|
);
|
|
2569
|
+
const hasBaseStarredOverride = Object.prototype.hasOwnProperty.call(
|
|
2570
|
+
base,
|
|
2571
|
+
"starred"
|
|
2572
|
+
);
|
|
2573
|
+
const starredFromBundle = convertBundleStarred(resolvedBundle?.starred);
|
|
2574
|
+
const resolvedStarred = hasBaseStarredOverride ? base.starred : base.starred ?? starredFromBundle;
|
|
2450
2575
|
const merged = {
|
|
2451
2576
|
...base
|
|
2452
2577
|
};
|
|
@@ -2458,6 +2583,7 @@ function mergeAssetBundle(base, extracted, extractedAnimations) {
|
|
|
2458
2583
|
merged.pose = resolvedPose;
|
|
2459
2584
|
merged.animations = resolvedAnimations;
|
|
2460
2585
|
merged.programs = programsFromBundle;
|
|
2586
|
+
merged.starred = resolvedStarred;
|
|
2461
2587
|
merged.bundle = resolvedBundle;
|
|
2462
2588
|
return merged;
|
|
2463
2589
|
}
|
|
@@ -2746,6 +2872,8 @@ function VizijRuntimeProviderInner({
|
|
|
2746
2872
|
const animationModuleRef = useRef(null);
|
|
2747
2873
|
const animationHostRef = useRef(null);
|
|
2748
2874
|
const animationsSourceRegisteredRef = useRef(false);
|
|
2875
|
+
const lastRegisteredModuleRevisionRef = useRef(0);
|
|
2876
|
+
const lastBehaviorErrorRef = useRef(void 0);
|
|
2749
2877
|
const animationGapWarnedRef = useRef(/* @__PURE__ */ new Set());
|
|
2750
2878
|
const animationSystemActiveRef = useRef(true);
|
|
2751
2879
|
const stagedInputsRef = useRef(/* @__PURE__ */ new Map());
|
|
@@ -2972,14 +3100,15 @@ function VizijRuntimeProviderInner({
|
|
|
2972
3100
|
return null;
|
|
2973
3101
|
}
|
|
2974
3102
|
if (!animationHostRef.current) {
|
|
2975
|
-
animationHostRef.current = new AnimationModuleHost(
|
|
2976
|
-
|
|
2977
|
-
|
|
3103
|
+
animationHostRef.current = new AnimationModuleHost(getDevice, (key) => {
|
|
3104
|
+
const namespace2 = namespaceRef.current;
|
|
3105
|
+
const resolved = resolveAnimationBridgeOutputPaths(
|
|
2978
3106
|
key,
|
|
2979
3107
|
faceIdRef.current ?? void 0,
|
|
2980
3108
|
rigInputMapRef.current
|
|
2981
|
-
)
|
|
2982
|
-
|
|
3109
|
+
).map((path) => namespaceTypedPath(path, namespace2));
|
|
3110
|
+
return [...new Set(resolved)];
|
|
3111
|
+
});
|
|
2983
3112
|
}
|
|
2984
3113
|
return animationHostRef.current;
|
|
2985
3114
|
}, [getDevice]);
|
|
@@ -3008,6 +3137,7 @@ function VizijRuntimeProviderInner({
|
|
|
3008
3137
|
setReady(true);
|
|
3009
3138
|
}, []);
|
|
3010
3139
|
const hasBundleAnimations = (assetBundle.animations?.length ?? 0) > 0;
|
|
3140
|
+
const [animationModuleReadyRevision, setAnimationModuleReadyRevision] = useState(0);
|
|
3011
3141
|
useEffect(() => {
|
|
3012
3142
|
if (!hasBundleAnimations || animationModuleRef.current) {
|
|
3013
3143
|
return;
|
|
@@ -3023,6 +3153,7 @@ function VizijRuntimeProviderInner({
|
|
|
3023
3153
|
animationHostRef.current?.replayInto(handle.device);
|
|
3024
3154
|
};
|
|
3025
3155
|
ensureAnimationHost();
|
|
3156
|
+
setAnimationModuleReadyRevision((previous) => previous + 1);
|
|
3026
3157
|
}).catch((err) => {
|
|
3027
3158
|
pushError({
|
|
3028
3159
|
message: "Failed to load the animation module",
|
|
@@ -3155,6 +3286,17 @@ function VizijRuntimeProviderInner({
|
|
|
3155
3286
|
if (!handle.device.running) {
|
|
3156
3287
|
handle.device.step(dt * 1e3);
|
|
3157
3288
|
}
|
|
3289
|
+
const behaviorError = handle.device.behaviorError;
|
|
3290
|
+
if (behaviorError !== lastBehaviorErrorRef.current) {
|
|
3291
|
+
lastBehaviorErrorRef.current = behaviorError;
|
|
3292
|
+
if (behaviorError) {
|
|
3293
|
+
pushError({
|
|
3294
|
+
message: `Graph tick failed: ${behaviorError}`,
|
|
3295
|
+
phase: "engine",
|
|
3296
|
+
timestamp: performance.now()
|
|
3297
|
+
});
|
|
3298
|
+
}
|
|
3299
|
+
}
|
|
3158
3300
|
const changes = handle.device.drainChanges();
|
|
3159
3301
|
applyEngineChangesRef.current(changes);
|
|
3160
3302
|
if (storeChangeListenersRef.current.size > 0) {
|
|
@@ -3286,7 +3428,6 @@ function VizijRuntimeProviderInner({
|
|
|
3286
3428
|
if (glbAsset.kind === "url") {
|
|
3287
3429
|
const loaded = await loadGLTFWithBundle(
|
|
3288
3430
|
glbAsset.src,
|
|
3289
|
-
[namespace],
|
|
3290
3431
|
glbAsset.aggressiveImport ?? false,
|
|
3291
3432
|
glbAsset.rootBounds
|
|
3292
3433
|
);
|
|
@@ -3297,7 +3438,6 @@ function VizijRuntimeProviderInner({
|
|
|
3297
3438
|
} else if (glbAsset.kind === "blob") {
|
|
3298
3439
|
const loaded = await loadGLTFFromBlobWithBundle(
|
|
3299
3440
|
glbAsset.blob,
|
|
3300
|
-
[namespace],
|
|
3301
3441
|
glbAsset.aggressiveImport ?? false,
|
|
3302
3442
|
glbAsset.rootBounds
|
|
3303
3443
|
);
|
|
@@ -3484,11 +3624,9 @@ function VizijRuntimeProviderInner({
|
|
|
3484
3624
|
rigInputMapRef.current
|
|
3485
3625
|
);
|
|
3486
3626
|
if (isRuntimeDebugEnabled()) {
|
|
3487
|
-
console.log(
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
bridgeOutputsText: bridgeOutputs.join(" | ")
|
|
3491
|
-
});
|
|
3627
|
+
console.log(
|
|
3628
|
+
`[vizij-runtime] animation output routing ${animation.id} (${bridgeOutputs.length}) | ` + bridgeOutputs.join(" | ")
|
|
3629
|
+
);
|
|
3492
3630
|
}
|
|
3493
3631
|
recordOutputs(bridgeOutputs);
|
|
3494
3632
|
}
|
|
@@ -3560,7 +3698,35 @@ function VizijRuntimeProviderInner({
|
|
|
3560
3698
|
});
|
|
3561
3699
|
}
|
|
3562
3700
|
}
|
|
3563
|
-
ensureAnimationHost()
|
|
3701
|
+
const host = ensureAnimationHost();
|
|
3702
|
+
host?.setClips(hostClips);
|
|
3703
|
+
if (host) {
|
|
3704
|
+
let resumed = false;
|
|
3705
|
+
clipPlaybackRef.current.forEach((clipState, clipId) => {
|
|
3706
|
+
if (!clipState.playing || !host.has(clipId)) {
|
|
3707
|
+
return;
|
|
3708
|
+
}
|
|
3709
|
+
resumed = true;
|
|
3710
|
+
host.setSpeed(clipId, clipState.speed);
|
|
3711
|
+
host.setWeight(clipId, clipState.weight);
|
|
3712
|
+
host.setLoop(clipId, clipState.loop ? "loop" : "once");
|
|
3713
|
+
if (clipState.playhead > 0) {
|
|
3714
|
+
host.seek(clipId, clipState.playhead);
|
|
3715
|
+
}
|
|
3716
|
+
void host.play(clipId).catch((err) => {
|
|
3717
|
+
pushError({
|
|
3718
|
+
message: `Failed to resume animation ${clipId} after the module became ready`,
|
|
3719
|
+
cause: err,
|
|
3720
|
+
phase: "animation",
|
|
3721
|
+
timestamp: performance.now()
|
|
3722
|
+
});
|
|
3723
|
+
});
|
|
3724
|
+
});
|
|
3725
|
+
if (resumed) {
|
|
3726
|
+
setAnimationsSourceRegistered(host.hasPlaying());
|
|
3727
|
+
updateLoopMode();
|
|
3728
|
+
}
|
|
3729
|
+
}
|
|
3564
3730
|
registeredAnimationsRef.current = animationIds;
|
|
3565
3731
|
if (assetBundle.initialInputs) {
|
|
3566
3732
|
Object.entries(assetBundle.initialInputs).forEach(([path, value]) => {
|
|
@@ -3605,7 +3771,9 @@ function VizijRuntimeProviderInner({
|
|
|
3605
3771
|
registerMergedGraph,
|
|
3606
3772
|
reportStatus,
|
|
3607
3773
|
resolvedProgramAssets,
|
|
3608
|
-
setInput
|
|
3774
|
+
setInput,
|
|
3775
|
+
setAnimationsSourceRegistered,
|
|
3776
|
+
updateLoopMode
|
|
3609
3777
|
]);
|
|
3610
3778
|
useEffect(() => {
|
|
3611
3779
|
if (!ready || status.loading) {
|
|
@@ -3613,7 +3781,9 @@ function VizijRuntimeProviderInner({
|
|
|
3613
3781
|
}
|
|
3614
3782
|
const plan = pendingPlanRef.current;
|
|
3615
3783
|
const hasRegistered = registeredGraphsRef.current.length > 0 || registeredAnimationsRef.current.length > 0 || programControllerIdsRef.current.size > 0;
|
|
3616
|
-
|
|
3784
|
+
const forcedByModuleReady = animationModuleReadyRevision !== lastRegisteredModuleRevisionRef.current;
|
|
3785
|
+
lastRegisteredModuleRevisionRef.current = animationModuleReadyRevision;
|
|
3786
|
+
if (plan && !plan.reregisterGraphs && hasRegistered && !forcedByModuleReady) {
|
|
3617
3787
|
return;
|
|
3618
3788
|
}
|
|
3619
3789
|
registerControllers().catch((err) => {
|
|
@@ -3624,7 +3794,14 @@ function VizijRuntimeProviderInner({
|
|
|
3624
3794
|
timestamp: performance.now()
|
|
3625
3795
|
});
|
|
3626
3796
|
});
|
|
3627
|
-
}, [
|
|
3797
|
+
}, [
|
|
3798
|
+
ready,
|
|
3799
|
+
status.loading,
|
|
3800
|
+
graphUpdateToken,
|
|
3801
|
+
registerControllers,
|
|
3802
|
+
pushError,
|
|
3803
|
+
animationModuleReadyRevision
|
|
3804
|
+
]);
|
|
3628
3805
|
useEffect(() => {
|
|
3629
3806
|
applyEngineChangesRef.current = (changes) => {
|
|
3630
3807
|
const entries = Object.entries(changes);
|
|
@@ -3742,6 +3919,23 @@ function VizijRuntimeProviderInner({
|
|
|
3742
3919
|
entry?.resolve();
|
|
3743
3920
|
}
|
|
3744
3921
|
}, []);
|
|
3922
|
+
const advanceClipPlayheads = useCallback((dt) => {
|
|
3923
|
+
if (!(dt > 0) || !Number.isFinite(dt)) {
|
|
3924
|
+
return;
|
|
3925
|
+
}
|
|
3926
|
+
clipPlaybackRef.current.forEach((state) => {
|
|
3927
|
+
if (!state.playing) {
|
|
3928
|
+
return;
|
|
3929
|
+
}
|
|
3930
|
+
const speed = Number.isFinite(state.speed) && state.speed > 0 ? state.speed : 1;
|
|
3931
|
+
const next = state.playhead + dt * speed;
|
|
3932
|
+
if (!(state.duration > 0)) {
|
|
3933
|
+
state.playhead = next;
|
|
3934
|
+
return;
|
|
3935
|
+
}
|
|
3936
|
+
state.playhead = state.loop ? next % state.duration : Math.min(next, state.duration);
|
|
3937
|
+
});
|
|
3938
|
+
}, []);
|
|
3745
3939
|
const advanceAnimationTweens = useCallback(
|
|
3746
3940
|
(dt) => {
|
|
3747
3941
|
if (animationTweensRef.current.size === 0) {
|
|
@@ -3806,6 +4000,7 @@ function VizijRuntimeProviderInner({
|
|
|
3806
4000
|
weight: 1,
|
|
3807
4001
|
loop: true,
|
|
3808
4002
|
playing: false,
|
|
4003
|
+
playhead: 0,
|
|
3809
4004
|
resolve: null,
|
|
3810
4005
|
completion: null
|
|
3811
4006
|
};
|
|
@@ -3904,8 +4099,17 @@ function VizijRuntimeProviderInner({
|
|
|
3904
4099
|
return [];
|
|
3905
4100
|
}
|
|
3906
4101
|
const raw = device.readValues([ANIMATION_PLAYERS_PATH])[ANIMATION_PLAYERS_PATH];
|
|
3907
|
-
|
|
3908
|
-
|
|
4102
|
+
const states = decodePlayerStates(raw);
|
|
4103
|
+
if (DEV_MODE && states.length === 0 && raw && typeof raw === "object") {
|
|
4104
|
+
warnAnimationGap(
|
|
4105
|
+
"player-states-undecodable",
|
|
4106
|
+
`Animation player_states feedback at "${ANIMATION_PLAYERS_PATH}" could not be decoded (top-level keys: ${Object.keys(
|
|
4107
|
+
raw
|
|
4108
|
+
).join(", ")}). The transport falls back to a host-side playhead.`
|
|
4109
|
+
);
|
|
4110
|
+
}
|
|
4111
|
+
return states;
|
|
4112
|
+
}, [deviceSlot, warnAnimationGap]);
|
|
3909
4113
|
const settleFinishedClips = useCallback(() => {
|
|
3910
4114
|
let pending = false;
|
|
3911
4115
|
clipPlaybackRef.current.forEach((state) => {
|
|
@@ -3961,6 +4165,7 @@ function VizijRuntimeProviderInner({
|
|
|
3961
4165
|
if (host) {
|
|
3962
4166
|
if (options?.reset === true) {
|
|
3963
4167
|
host.seek(id, 0);
|
|
4168
|
+
state.playhead = 0;
|
|
3964
4169
|
}
|
|
3965
4170
|
host.setSpeed(id, state.speed);
|
|
3966
4171
|
host.setWeight(id, state.weight);
|
|
@@ -3997,6 +4202,9 @@ function VizijRuntimeProviderInner({
|
|
|
3997
4202
|
(id) => {
|
|
3998
4203
|
const state = clipPlaybackRef.current.get(id);
|
|
3999
4204
|
if (!state || !state.playing) {
|
|
4205
|
+
console.warn(
|
|
4206
|
+
`[vizij-runtime] pause ignored for "${id}": ${state ? "the clip is not marked playing" : "no clip state"}`
|
|
4207
|
+
);
|
|
4000
4208
|
return;
|
|
4001
4209
|
}
|
|
4002
4210
|
state.playing = false;
|
|
@@ -4006,10 +4214,13 @@ function VizijRuntimeProviderInner({
|
|
|
4006
4214
|
[syncAnimationsSource]
|
|
4007
4215
|
);
|
|
4008
4216
|
const seekAnimation = useCallback((id, timeSeconds) => {
|
|
4009
|
-
|
|
4217
|
+
const state = clipPlaybackRef.current.get(id);
|
|
4218
|
+
if (!state) {
|
|
4010
4219
|
return;
|
|
4011
4220
|
}
|
|
4012
|
-
|
|
4221
|
+
const target = Math.max(0, timeSeconds);
|
|
4222
|
+
state.playhead = state.duration > 0 ? Math.min(target, state.duration) : target;
|
|
4223
|
+
animationHostRef.current?.seek(id, target);
|
|
4013
4224
|
}, []);
|
|
4014
4225
|
const setAnimationLoop = useCallback(
|
|
4015
4226
|
(id, enabled) => {
|
|
@@ -4032,9 +4243,23 @@ function VizijRuntimeProviderInner({
|
|
|
4032
4243
|
const playerId = animationHostRef.current?.playerIdOf(id) ?? null;
|
|
4033
4244
|
const feedback = playerId !== null ? readPlayerStates().find((entry) => entry.player === playerId) : void 0;
|
|
4034
4245
|
return {
|
|
4035
|
-
|
|
4246
|
+
// Feedback is authoritative; the host-side playhead stands in when it
|
|
4247
|
+
// is unavailable. Never fall back to a hard 0 — that reads as "the
|
|
4248
|
+
// animation is parked at the start" rather than "no telemetry", and it
|
|
4249
|
+
// pins the transport clock and defeats seeking.
|
|
4250
|
+
time: feedback?.time ?? state.playhead,
|
|
4036
4251
|
duration: feedback && feedback.duration > 0 ? feedback.duration : state.duration,
|
|
4037
|
-
|
|
4252
|
+
// A clip commanded to pause is paused, whatever the feedback says.
|
|
4253
|
+
//
|
|
4254
|
+
// Feedback goes stale by construction: pausing the last playing clip
|
|
4255
|
+
// unregisters the animations graph source, so the module stops being
|
|
4256
|
+
// stepped and `player_states` keeps reporting whatever it last said —
|
|
4257
|
+
// `state: "playing"`, with the playhead frozen. Treating that as
|
|
4258
|
+
// authoritative meant the transport reverted to playing a frame after
|
|
4259
|
+
// every pause, leaving a Pause button over a clip that was not
|
|
4260
|
+
// advancing. Feedback still decides while the clip is *meant* to be
|
|
4261
|
+
// playing, which is what notices a clip reaching its end.
|
|
4262
|
+
playing: state.playing && (feedback ? feedback.state === "playing" : true),
|
|
4038
4263
|
loop: state.loop,
|
|
4039
4264
|
speed: feedback?.speed ?? state.speed
|
|
4040
4265
|
};
|
|
@@ -4055,6 +4280,9 @@ function VizijRuntimeProviderInner({
|
|
|
4055
4280
|
syncAnimationsSource();
|
|
4056
4281
|
} else {
|
|
4057
4282
|
host?.stop(id);
|
|
4283
|
+
if (state) {
|
|
4284
|
+
state.playhead = 0;
|
|
4285
|
+
}
|
|
4058
4286
|
const unsubscribe = subscribeToStep(() => {
|
|
4059
4287
|
unsubscribe();
|
|
4060
4288
|
syncAnimationsSource();
|
|
@@ -4389,6 +4617,13 @@ function VizijRuntimeProviderInner({
|
|
|
4389
4617
|
},
|
|
4390
4618
|
[faceId, namespace, pushError, setInput, setRendererValue]
|
|
4391
4619
|
);
|
|
4620
|
+
const reconcileAnimationsSource = useCallback(() => {
|
|
4621
|
+
const host = animationHostRef.current;
|
|
4622
|
+
if (!host || !host.hasPlaying()) {
|
|
4623
|
+
return;
|
|
4624
|
+
}
|
|
4625
|
+
setAnimationsSourceRegistered(true);
|
|
4626
|
+
}, [setAnimationsSourceRegistered]);
|
|
4392
4627
|
const flushStagedInputs = useCallback(() => {
|
|
4393
4628
|
if (stagedInputsRef.current.size === 0) {
|
|
4394
4629
|
return;
|
|
@@ -4406,6 +4641,8 @@ function VizijRuntimeProviderInner({
|
|
|
4406
4641
|
avgStepDtRef.current = prev * (1 - alpha) + dt * alpha;
|
|
4407
4642
|
}
|
|
4408
4643
|
advanceAnimationTweens(dt);
|
|
4644
|
+
advanceClipPlayheads(dt);
|
|
4645
|
+
reconcileAnimationsSource();
|
|
4409
4646
|
flushStagedInputs();
|
|
4410
4647
|
if (driveRuntimeRef.current || opts?.forceRuntime) {
|
|
4411
4648
|
stepRuntime(dt);
|
|
@@ -4414,7 +4651,9 @@ function VizijRuntimeProviderInner({
|
|
|
4414
4651
|
},
|
|
4415
4652
|
[
|
|
4416
4653
|
advanceAnimationTweens,
|
|
4654
|
+
advanceClipPlayheads,
|
|
4417
4655
|
flushStagedInputs,
|
|
4656
|
+
reconcileAnimationsSource,
|
|
4418
4657
|
settleFinishedClips,
|
|
4419
4658
|
stepRuntime
|
|
4420
4659
|
]
|
|
@@ -5074,11 +5313,14 @@ export {
|
|
|
5074
5313
|
buildPoseWeightRelativePath,
|
|
5075
5314
|
buildRigInputPath,
|
|
5076
5315
|
buildSemanticPoseWeightPathMap,
|
|
5316
|
+
collectInputPathMap,
|
|
5317
|
+
composeGraphSpecs,
|
|
5077
5318
|
filterPosesBySemanticKind,
|
|
5078
5319
|
getPoseSemanticKey,
|
|
5079
5320
|
mapNormalizedControlValue,
|
|
5080
5321
|
mapUnitControlValue,
|
|
5081
5322
|
normalizePoseSemanticKey,
|
|
5323
|
+
resolveAnimationBridgeOutputPaths,
|
|
5082
5324
|
resolveFaceControls,
|
|
5083
5325
|
resolvePoseMembership,
|
|
5084
5326
|
resolvePoseSemantics,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vizij/runtime-react",
|
|
3
3
|
"description": "Runtime provider that drives Vizij renderer assets with an arora device engine for React apps.",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.4.1",
|
|
5
5
|
"main": "dist/index.cjs",
|
|
6
6
|
"module": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
@@ -38,9 +38,9 @@
|
|
|
38
38
|
"prepack": "pnpm run build && pnpm run test && pnpm run typecheck && pnpm run lint"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@vizij/animation-module": "^0.
|
|
42
|
-
"@vizij/node-graph-authoring": "^0.2.
|
|
43
|
-
"@vizij/render": "^0.
|
|
41
|
+
"@vizij/animation-module": "^1.0.0",
|
|
42
|
+
"@vizij/node-graph-authoring": "^0.2.2",
|
|
43
|
+
"@vizij/render": "^1.0.0",
|
|
44
44
|
"@vizij/runtime": "^2.4.0",
|
|
45
45
|
"@vizij/utils": "^0.2.0",
|
|
46
46
|
"@vizij/value-json": "^0.2.0"
|