@vizij/runtime-react 0.3.1 → 0.4.0
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.cjs +295 -48
- package/dist/index.d.cts +47 -2
- package/dist/index.d.ts +47 -2
- package/dist/index.js +292 -48
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -31,11 +31,14 @@ __export(index_exports, {
|
|
|
31
31
|
buildPoseWeightRelativePath: () => buildPoseWeightRelativePath,
|
|
32
32
|
buildRigInputPath: () => buildRigInputPath,
|
|
33
33
|
buildSemanticPoseWeightPathMap: () => buildSemanticPoseWeightPathMap,
|
|
34
|
+
collectInputPathMap: () => collectInputPathMap,
|
|
35
|
+
composeGraphSpecs: () => composeGraphSpecs,
|
|
34
36
|
filterPosesBySemanticKind: () => filterPosesBySemanticKind,
|
|
35
37
|
getPoseSemanticKey: () => getPoseSemanticKey,
|
|
36
38
|
mapNormalizedControlValue: () => mapNormalizedControlValue,
|
|
37
39
|
mapUnitControlValue: () => mapUnitControlValue,
|
|
38
40
|
normalizePoseSemanticKey: () => normalizePoseSemanticKey,
|
|
41
|
+
resolveAnimationBridgeOutputPaths: () => resolveAnimationBridgeOutputPaths,
|
|
39
42
|
resolveFaceControls: () => resolveFaceControls,
|
|
40
43
|
resolvePoseMembership: () => resolvePoseMembership,
|
|
41
44
|
resolvePoseSemantics: () => resolvePoseSemantics,
|
|
@@ -1475,7 +1478,7 @@ function fieldNumber(value) {
|
|
|
1475
1478
|
if (!value || typeof value !== "object") {
|
|
1476
1479
|
return null;
|
|
1477
1480
|
}
|
|
1478
|
-
for (const key of ["u64", "u32", "f32", "f64"]) {
|
|
1481
|
+
for (const key of ["u64", "u32", "f32", "f64", "float"]) {
|
|
1479
1482
|
if (key in value) {
|
|
1480
1483
|
const parsed = Number(value[key]);
|
|
1481
1484
|
return Number.isFinite(parsed) ? parsed : null;
|
|
@@ -1483,38 +1486,90 @@ function fieldNumber(value) {
|
|
|
1483
1486
|
}
|
|
1484
1487
|
return null;
|
|
1485
1488
|
}
|
|
1489
|
+
function fieldText(value) {
|
|
1490
|
+
if (!value || typeof value !== "object") {
|
|
1491
|
+
return "";
|
|
1492
|
+
}
|
|
1493
|
+
const record = value;
|
|
1494
|
+
for (const key of ["str", "text"]) {
|
|
1495
|
+
if (typeof record[key] === "string") {
|
|
1496
|
+
return record[key];
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
return "";
|
|
1500
|
+
}
|
|
1501
|
+
function playerStateFromFields(byId) {
|
|
1502
|
+
const player = fieldNumber(byId.get(ANIMATION_MODULE_FIELD.statePlayer));
|
|
1503
|
+
if (player === null) {
|
|
1504
|
+
return null;
|
|
1505
|
+
}
|
|
1506
|
+
return {
|
|
1507
|
+
player,
|
|
1508
|
+
state: fieldText(byId.get(ANIMATION_MODULE_FIELD.stateState)),
|
|
1509
|
+
time: (fieldNumber(byId.get(ANIMATION_MODULE_FIELD.stateTimeNs)) ?? 0) / 1e9,
|
|
1510
|
+
duration: (fieldNumber(byId.get(ANIMATION_MODULE_FIELD.stateDurationNs)) ?? 0) / 1e9,
|
|
1511
|
+
speed: fieldNumber(byId.get(ANIMATION_MODULE_FIELD.stateSpeed)) ?? 1
|
|
1512
|
+
};
|
|
1513
|
+
}
|
|
1514
|
+
function fieldsToMap(fields) {
|
|
1515
|
+
if (!Array.isArray(fields)) {
|
|
1516
|
+
return null;
|
|
1517
|
+
}
|
|
1518
|
+
const byId = /* @__PURE__ */ new Map();
|
|
1519
|
+
for (const entry of fields) {
|
|
1520
|
+
const id = entry?.id;
|
|
1521
|
+
const value = entry?.value;
|
|
1522
|
+
if (typeof id === "string" && value && typeof value === "object") {
|
|
1523
|
+
byId.set(id, value);
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
return byId;
|
|
1527
|
+
}
|
|
1528
|
+
function recordToMap(element) {
|
|
1529
|
+
if (!element || typeof element !== "object") {
|
|
1530
|
+
return null;
|
|
1531
|
+
}
|
|
1532
|
+
const record = element.record;
|
|
1533
|
+
if (!record || typeof record !== "object" || Array.isArray(record)) {
|
|
1534
|
+
return null;
|
|
1535
|
+
}
|
|
1536
|
+
const byId = /* @__PURE__ */ new Map();
|
|
1537
|
+
for (const [id, value] of Object.entries(record)) {
|
|
1538
|
+
if (value && typeof value === "object") {
|
|
1539
|
+
byId.set(id, value);
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1542
|
+
return byId;
|
|
1543
|
+
}
|
|
1486
1544
|
function decodePlayerStates(raw) {
|
|
1487
|
-
if (!raw || typeof raw !== "object"
|
|
1545
|
+
if (!raw || typeof raw !== "object") {
|
|
1488
1546
|
return [];
|
|
1489
1547
|
}
|
|
1490
|
-
const
|
|
1491
|
-
const elements = Array.isArray(structs?.elements) ? structs.elements : [];
|
|
1548
|
+
const container = raw;
|
|
1492
1549
|
const states = [];
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
const id = entry?.id;
|
|
1501
|
-
const value = entry?.value;
|
|
1502
|
-
if (typeof id === "string" && value && typeof value === "object") {
|
|
1503
|
-
byId.set(id, value);
|
|
1550
|
+
const structs = container.structs;
|
|
1551
|
+
if (structs && Array.isArray(structs.elements)) {
|
|
1552
|
+
for (const element of structs.elements) {
|
|
1553
|
+
const byId = fieldsToMap(element?.fields);
|
|
1554
|
+
const state = byId ? playerStateFromFields(byId) : null;
|
|
1555
|
+
if (state) {
|
|
1556
|
+
states.push(state);
|
|
1504
1557
|
}
|
|
1505
1558
|
}
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1559
|
+
return states;
|
|
1560
|
+
}
|
|
1561
|
+
const elements = Array.isArray(container.list) ? container.list : Array.isArray(container.array) ? container.array : null;
|
|
1562
|
+
if (!elements) {
|
|
1563
|
+
return states;
|
|
1564
|
+
}
|
|
1565
|
+
for (const element of elements) {
|
|
1566
|
+
const byId = recordToMap(element) ?? fieldsToMap(
|
|
1567
|
+
element?.struct?.fields ?? element?.fields
|
|
1568
|
+
);
|
|
1569
|
+
const state = byId ? playerStateFromFields(byId) : null;
|
|
1570
|
+
if (state) {
|
|
1571
|
+
states.push(state);
|
|
1509
1572
|
}
|
|
1510
|
-
const stateValue = byId.get(ANIMATION_MODULE_FIELD.stateState);
|
|
1511
|
-
states.push({
|
|
1512
|
-
player,
|
|
1513
|
-
state: stateValue && "str" in stateValue ? String(stateValue.str) : "",
|
|
1514
|
-
time: (fieldNumber(byId.get(ANIMATION_MODULE_FIELD.stateTimeNs)) ?? 0) / 1e9,
|
|
1515
|
-
duration: (fieldNumber(byId.get(ANIMATION_MODULE_FIELD.stateDurationNs)) ?? 0) / 1e9,
|
|
1516
|
-
speed: fieldNumber(byId.get(ANIMATION_MODULE_FIELD.stateSpeed)) ?? 1
|
|
1517
|
-
});
|
|
1518
1573
|
}
|
|
1519
1574
|
return states;
|
|
1520
1575
|
}
|
|
@@ -1584,6 +1639,7 @@ var AnimationModuleHost = class {
|
|
|
1584
1639
|
speed: 1,
|
|
1585
1640
|
loop: "loop",
|
|
1586
1641
|
weight: 1,
|
|
1642
|
+
pendingSeekSeconds: null,
|
|
1587
1643
|
animId: null,
|
|
1588
1644
|
playerId: null,
|
|
1589
1645
|
instId: null
|
|
@@ -1622,6 +1678,8 @@ var AnimationModuleHost = class {
|
|
|
1622
1678
|
}
|
|
1623
1679
|
entry.playing = false;
|
|
1624
1680
|
this.dispatch(
|
|
1681
|
+
"pause",
|
|
1682
|
+
clipId,
|
|
1625
1683
|
(device) => entry.playerId !== null ? device.call(pauseCall(entry.playerId)) : null
|
|
1626
1684
|
);
|
|
1627
1685
|
}
|
|
@@ -1637,6 +1695,8 @@ var AnimationModuleHost = class {
|
|
|
1637
1695
|
}
|
|
1638
1696
|
entry.playing = false;
|
|
1639
1697
|
this.dispatch(
|
|
1698
|
+
"stop",
|
|
1699
|
+
clipId,
|
|
1640
1700
|
(device) => entry.playerId !== null ? device.call(stopCall(entry.playerId)) : null
|
|
1641
1701
|
);
|
|
1642
1702
|
}
|
|
@@ -1646,7 +1706,14 @@ var AnimationModuleHost = class {
|
|
|
1646
1706
|
if (!entry) {
|
|
1647
1707
|
return;
|
|
1648
1708
|
}
|
|
1709
|
+
if (entry.playerId === null) {
|
|
1710
|
+
entry.pendingSeekSeconds = seconds;
|
|
1711
|
+
return;
|
|
1712
|
+
}
|
|
1713
|
+
entry.pendingSeekSeconds = null;
|
|
1649
1714
|
this.dispatch(
|
|
1715
|
+
"seek",
|
|
1716
|
+
clipId,
|
|
1650
1717
|
(device) => entry.playerId !== null ? device.call(seekCall(entry.playerId, seconds * 1e9)) : null
|
|
1651
1718
|
);
|
|
1652
1719
|
}
|
|
@@ -1658,6 +1725,8 @@ var AnimationModuleHost = class {
|
|
|
1658
1725
|
}
|
|
1659
1726
|
entry.speed = speed;
|
|
1660
1727
|
this.dispatch(
|
|
1728
|
+
"set speed",
|
|
1729
|
+
clipId,
|
|
1661
1730
|
(device) => entry.playerId !== null ? device.call(setSpeedCall(entry.playerId, speed)) : null
|
|
1662
1731
|
);
|
|
1663
1732
|
}
|
|
@@ -1669,6 +1738,8 @@ var AnimationModuleHost = class {
|
|
|
1669
1738
|
}
|
|
1670
1739
|
entry.loop = mode;
|
|
1671
1740
|
this.dispatch(
|
|
1741
|
+
"set loop",
|
|
1742
|
+
clipId,
|
|
1672
1743
|
(device) => entry.playerId !== null ? device.call(setLoopCall(entry.playerId, mode)) : null
|
|
1673
1744
|
);
|
|
1674
1745
|
}
|
|
@@ -1680,6 +1751,8 @@ var AnimationModuleHost = class {
|
|
|
1680
1751
|
}
|
|
1681
1752
|
entry.weight = weight;
|
|
1682
1753
|
this.dispatch(
|
|
1754
|
+
"set weight",
|
|
1755
|
+
clipId,
|
|
1683
1756
|
(device) => entry.playerId !== null && entry.instId !== null ? device.call(setWeightCall(entry.playerId, entry.instId, weight)) : null
|
|
1684
1757
|
);
|
|
1685
1758
|
}
|
|
@@ -1687,6 +1760,9 @@ var AnimationModuleHost = class {
|
|
|
1687
1760
|
* Re-issue setup and transport for every playing clip against a freshly
|
|
1688
1761
|
* started device (module guest state did not survive the rebuild). Voids
|
|
1689
1762
|
* stale ids first. Called from `DeviceSlot.onDeviceStarted`.
|
|
1763
|
+
*
|
|
1764
|
+
* A freshly loaded player advances without an explicit `play` — asserted by
|
|
1765
|
+
* the rebuild case in `animationPipeline.device.test.ts`.
|
|
1690
1766
|
*/
|
|
1691
1767
|
replayInto(device) {
|
|
1692
1768
|
for (const entry of this.clips.values()) {
|
|
@@ -1699,12 +1775,35 @@ var AnimationModuleHost = class {
|
|
|
1699
1775
|
}
|
|
1700
1776
|
}
|
|
1701
1777
|
/** Fire-and-forget a transport call against the live device, if any. */
|
|
1702
|
-
|
|
1778
|
+
/**
|
|
1779
|
+
* Issue a transport call, reporting rather than swallowing a rejection.
|
|
1780
|
+
*
|
|
1781
|
+
* This used to discard the error on the assumption that the only cause was
|
|
1782
|
+
* a rebuild racing the call, which `replayInto` would repair. When that is
|
|
1783
|
+
* not the cause — the module missing from the live device, say — the call
|
|
1784
|
+
* fails, the player keeps running, and the next frame of `player_states`
|
|
1785
|
+
* feedback tells the transport UI it is still playing. The button flips
|
|
1786
|
+
* back and the pause looks like it did nothing, with nothing anywhere
|
|
1787
|
+
* saying why. Whatever the cause, a dropped transport command is worth a
|
|
1788
|
+
* line in the console.
|
|
1789
|
+
*/
|
|
1790
|
+
dispatch(what, clipId, issue) {
|
|
1703
1791
|
const device = this.getDevice();
|
|
1704
1792
|
if (!device) {
|
|
1705
1793
|
return;
|
|
1706
1794
|
}
|
|
1707
|
-
|
|
1795
|
+
const issued = issue(device);
|
|
1796
|
+
if (!issued) {
|
|
1797
|
+
console.warn(
|
|
1798
|
+
`[vizij-runtime] animation ${what} had no player to act on for "${clipId}"`
|
|
1799
|
+
);
|
|
1800
|
+
return;
|
|
1801
|
+
}
|
|
1802
|
+
void issued.catch((err) => {
|
|
1803
|
+
console.warn(
|
|
1804
|
+
`[vizij-runtime] animation ${what} was dropped for "${clipId}"`,
|
|
1805
|
+
err
|
|
1806
|
+
);
|
|
1708
1807
|
});
|
|
1709
1808
|
}
|
|
1710
1809
|
async ensureLoaded(device, entry) {
|
|
@@ -1742,6 +1841,12 @@ var AnimationModuleHost = class {
|
|
|
1742
1841
|
setWeightCall(entry.playerId, entry.instId, entry.weight)
|
|
1743
1842
|
);
|
|
1744
1843
|
}
|
|
1844
|
+
if (entry.pendingSeekSeconds !== null) {
|
|
1845
|
+
followups.push(
|
|
1846
|
+
seekCall(entry.playerId, entry.pendingSeekSeconds * 1e9)
|
|
1847
|
+
);
|
|
1848
|
+
entry.pendingSeekSeconds = null;
|
|
1849
|
+
}
|
|
1745
1850
|
}
|
|
1746
1851
|
for (const call of followups) {
|
|
1747
1852
|
await device.call(call);
|
|
@@ -2322,6 +2427,23 @@ function convertBundlePrograms(entries) {
|
|
|
2322
2427
|
};
|
|
2323
2428
|
}).filter(Boolean);
|
|
2324
2429
|
}
|
|
2430
|
+
function convertBundleStarred(section) {
|
|
2431
|
+
const items = section?.items;
|
|
2432
|
+
if (!Array.isArray(items) || items.length === 0) {
|
|
2433
|
+
return void 0;
|
|
2434
|
+
}
|
|
2435
|
+
const resolved = [];
|
|
2436
|
+
for (const item of items) {
|
|
2437
|
+
if (!item || typeof item !== "object") {
|
|
2438
|
+
continue;
|
|
2439
|
+
}
|
|
2440
|
+
const { kind, id } = item;
|
|
2441
|
+
if ((kind === "driver" || kind === "pose") && typeof id === "string" && id) {
|
|
2442
|
+
resolved.push({ kind, id });
|
|
2443
|
+
}
|
|
2444
|
+
}
|
|
2445
|
+
return resolved.length > 0 ? resolved : void 0;
|
|
2446
|
+
}
|
|
2325
2447
|
function resolveConstraintDefault(path, namespace, inputConstraints) {
|
|
2326
2448
|
const trimmed = path.trim();
|
|
2327
2449
|
if (!trimmed) {
|
|
@@ -2479,6 +2601,12 @@ function mergeAssetBundle(base, extracted, extractedAnimations) {
|
|
|
2479
2601
|
hasBaseProgramsOverride,
|
|
2480
2602
|
convertBundlePrograms(resolvedBundle?.graphs)
|
|
2481
2603
|
);
|
|
2604
|
+
const hasBaseStarredOverride = Object.prototype.hasOwnProperty.call(
|
|
2605
|
+
base,
|
|
2606
|
+
"starred"
|
|
2607
|
+
);
|
|
2608
|
+
const starredFromBundle = convertBundleStarred(resolvedBundle?.starred);
|
|
2609
|
+
const resolvedStarred = hasBaseStarredOverride ? base.starred : base.starred ?? starredFromBundle;
|
|
2482
2610
|
const merged = {
|
|
2483
2611
|
...base
|
|
2484
2612
|
};
|
|
@@ -2490,6 +2618,7 @@ function mergeAssetBundle(base, extracted, extractedAnimations) {
|
|
|
2490
2618
|
merged.pose = resolvedPose;
|
|
2491
2619
|
merged.animations = resolvedAnimations;
|
|
2492
2620
|
merged.programs = programsFromBundle;
|
|
2621
|
+
merged.starred = resolvedStarred;
|
|
2493
2622
|
merged.bundle = resolvedBundle;
|
|
2494
2623
|
return merged;
|
|
2495
2624
|
}
|
|
@@ -2778,6 +2907,8 @@ function VizijRuntimeProviderInner({
|
|
|
2778
2907
|
const animationModuleRef = (0, import_react2.useRef)(null);
|
|
2779
2908
|
const animationHostRef = (0, import_react2.useRef)(null);
|
|
2780
2909
|
const animationsSourceRegisteredRef = (0, import_react2.useRef)(false);
|
|
2910
|
+
const lastRegisteredModuleRevisionRef = (0, import_react2.useRef)(0);
|
|
2911
|
+
const lastBehaviorErrorRef = (0, import_react2.useRef)(void 0);
|
|
2781
2912
|
const animationGapWarnedRef = (0, import_react2.useRef)(/* @__PURE__ */ new Set());
|
|
2782
2913
|
const animationSystemActiveRef = (0, import_react2.useRef)(true);
|
|
2783
2914
|
const stagedInputsRef = (0, import_react2.useRef)(/* @__PURE__ */ new Map());
|
|
@@ -3004,14 +3135,15 @@ function VizijRuntimeProviderInner({
|
|
|
3004
3135
|
return null;
|
|
3005
3136
|
}
|
|
3006
3137
|
if (!animationHostRef.current) {
|
|
3007
|
-
animationHostRef.current = new AnimationModuleHost(
|
|
3008
|
-
|
|
3009
|
-
|
|
3138
|
+
animationHostRef.current = new AnimationModuleHost(getDevice, (key) => {
|
|
3139
|
+
const namespace2 = namespaceRef.current;
|
|
3140
|
+
const resolved = resolveAnimationBridgeOutputPaths(
|
|
3010
3141
|
key,
|
|
3011
3142
|
faceIdRef.current ?? void 0,
|
|
3012
3143
|
rigInputMapRef.current
|
|
3013
|
-
)
|
|
3014
|
-
|
|
3144
|
+
).map((path) => namespaceTypedPath(path, namespace2));
|
|
3145
|
+
return [...new Set(resolved)];
|
|
3146
|
+
});
|
|
3015
3147
|
}
|
|
3016
3148
|
return animationHostRef.current;
|
|
3017
3149
|
}, [getDevice]);
|
|
@@ -3040,6 +3172,7 @@ function VizijRuntimeProviderInner({
|
|
|
3040
3172
|
setReady(true);
|
|
3041
3173
|
}, []);
|
|
3042
3174
|
const hasBundleAnimations = (assetBundle.animations?.length ?? 0) > 0;
|
|
3175
|
+
const [animationModuleReadyRevision, setAnimationModuleReadyRevision] = (0, import_react2.useState)(0);
|
|
3043
3176
|
(0, import_react2.useEffect)(() => {
|
|
3044
3177
|
if (!hasBundleAnimations || animationModuleRef.current) {
|
|
3045
3178
|
return;
|
|
@@ -3055,6 +3188,7 @@ function VizijRuntimeProviderInner({
|
|
|
3055
3188
|
animationHostRef.current?.replayInto(handle.device);
|
|
3056
3189
|
};
|
|
3057
3190
|
ensureAnimationHost();
|
|
3191
|
+
setAnimationModuleReadyRevision((previous) => previous + 1);
|
|
3058
3192
|
}).catch((err) => {
|
|
3059
3193
|
pushError({
|
|
3060
3194
|
message: "Failed to load the animation module",
|
|
@@ -3187,6 +3321,17 @@ function VizijRuntimeProviderInner({
|
|
|
3187
3321
|
if (!handle.device.running) {
|
|
3188
3322
|
handle.device.step(dt * 1e3);
|
|
3189
3323
|
}
|
|
3324
|
+
const behaviorError = handle.device.behaviorError;
|
|
3325
|
+
if (behaviorError !== lastBehaviorErrorRef.current) {
|
|
3326
|
+
lastBehaviorErrorRef.current = behaviorError;
|
|
3327
|
+
if (behaviorError) {
|
|
3328
|
+
pushError({
|
|
3329
|
+
message: `Graph tick failed: ${behaviorError}`,
|
|
3330
|
+
phase: "engine",
|
|
3331
|
+
timestamp: performance.now()
|
|
3332
|
+
});
|
|
3333
|
+
}
|
|
3334
|
+
}
|
|
3190
3335
|
const changes = handle.device.drainChanges();
|
|
3191
3336
|
applyEngineChangesRef.current(changes);
|
|
3192
3337
|
if (storeChangeListenersRef.current.size > 0) {
|
|
@@ -3516,11 +3661,9 @@ function VizijRuntimeProviderInner({
|
|
|
3516
3661
|
rigInputMapRef.current
|
|
3517
3662
|
);
|
|
3518
3663
|
if (isRuntimeDebugEnabled()) {
|
|
3519
|
-
console.log(
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
bridgeOutputsText: bridgeOutputs.join(" | ")
|
|
3523
|
-
});
|
|
3664
|
+
console.log(
|
|
3665
|
+
`[vizij-runtime] animation output routing ${animation.id} (${bridgeOutputs.length}) | ` + bridgeOutputs.join(" | ")
|
|
3666
|
+
);
|
|
3524
3667
|
}
|
|
3525
3668
|
recordOutputs(bridgeOutputs);
|
|
3526
3669
|
}
|
|
@@ -3592,7 +3735,35 @@ function VizijRuntimeProviderInner({
|
|
|
3592
3735
|
});
|
|
3593
3736
|
}
|
|
3594
3737
|
}
|
|
3595
|
-
ensureAnimationHost()
|
|
3738
|
+
const host = ensureAnimationHost();
|
|
3739
|
+
host?.setClips(hostClips);
|
|
3740
|
+
if (host) {
|
|
3741
|
+
let resumed = false;
|
|
3742
|
+
clipPlaybackRef.current.forEach((clipState, clipId) => {
|
|
3743
|
+
if (!clipState.playing || !host.has(clipId)) {
|
|
3744
|
+
return;
|
|
3745
|
+
}
|
|
3746
|
+
resumed = true;
|
|
3747
|
+
host.setSpeed(clipId, clipState.speed);
|
|
3748
|
+
host.setWeight(clipId, clipState.weight);
|
|
3749
|
+
host.setLoop(clipId, clipState.loop ? "loop" : "once");
|
|
3750
|
+
if (clipState.playhead > 0) {
|
|
3751
|
+
host.seek(clipId, clipState.playhead);
|
|
3752
|
+
}
|
|
3753
|
+
void host.play(clipId).catch((err) => {
|
|
3754
|
+
pushError({
|
|
3755
|
+
message: `Failed to resume animation ${clipId} after the module became ready`,
|
|
3756
|
+
cause: err,
|
|
3757
|
+
phase: "animation",
|
|
3758
|
+
timestamp: performance.now()
|
|
3759
|
+
});
|
|
3760
|
+
});
|
|
3761
|
+
});
|
|
3762
|
+
if (resumed) {
|
|
3763
|
+
setAnimationsSourceRegistered(host.hasPlaying());
|
|
3764
|
+
updateLoopMode();
|
|
3765
|
+
}
|
|
3766
|
+
}
|
|
3596
3767
|
registeredAnimationsRef.current = animationIds;
|
|
3597
3768
|
if (assetBundle.initialInputs) {
|
|
3598
3769
|
Object.entries(assetBundle.initialInputs).forEach(([path, value]) => {
|
|
@@ -3637,7 +3808,9 @@ function VizijRuntimeProviderInner({
|
|
|
3637
3808
|
registerMergedGraph,
|
|
3638
3809
|
reportStatus,
|
|
3639
3810
|
resolvedProgramAssets,
|
|
3640
|
-
setInput
|
|
3811
|
+
setInput,
|
|
3812
|
+
setAnimationsSourceRegistered,
|
|
3813
|
+
updateLoopMode
|
|
3641
3814
|
]);
|
|
3642
3815
|
(0, import_react2.useEffect)(() => {
|
|
3643
3816
|
if (!ready || status.loading) {
|
|
@@ -3645,7 +3818,9 @@ function VizijRuntimeProviderInner({
|
|
|
3645
3818
|
}
|
|
3646
3819
|
const plan = pendingPlanRef.current;
|
|
3647
3820
|
const hasRegistered = registeredGraphsRef.current.length > 0 || registeredAnimationsRef.current.length > 0 || programControllerIdsRef.current.size > 0;
|
|
3648
|
-
|
|
3821
|
+
const forcedByModuleReady = animationModuleReadyRevision !== lastRegisteredModuleRevisionRef.current;
|
|
3822
|
+
lastRegisteredModuleRevisionRef.current = animationModuleReadyRevision;
|
|
3823
|
+
if (plan && !plan.reregisterGraphs && hasRegistered && !forcedByModuleReady) {
|
|
3649
3824
|
return;
|
|
3650
3825
|
}
|
|
3651
3826
|
registerControllers().catch((err) => {
|
|
@@ -3656,7 +3831,14 @@ function VizijRuntimeProviderInner({
|
|
|
3656
3831
|
timestamp: performance.now()
|
|
3657
3832
|
});
|
|
3658
3833
|
});
|
|
3659
|
-
}, [
|
|
3834
|
+
}, [
|
|
3835
|
+
ready,
|
|
3836
|
+
status.loading,
|
|
3837
|
+
graphUpdateToken,
|
|
3838
|
+
registerControllers,
|
|
3839
|
+
pushError,
|
|
3840
|
+
animationModuleReadyRevision
|
|
3841
|
+
]);
|
|
3660
3842
|
(0, import_react2.useEffect)(() => {
|
|
3661
3843
|
applyEngineChangesRef.current = (changes) => {
|
|
3662
3844
|
const entries = Object.entries(changes);
|
|
@@ -3774,6 +3956,23 @@ function VizijRuntimeProviderInner({
|
|
|
3774
3956
|
entry?.resolve();
|
|
3775
3957
|
}
|
|
3776
3958
|
}, []);
|
|
3959
|
+
const advanceClipPlayheads = (0, import_react2.useCallback)((dt) => {
|
|
3960
|
+
if (!(dt > 0) || !Number.isFinite(dt)) {
|
|
3961
|
+
return;
|
|
3962
|
+
}
|
|
3963
|
+
clipPlaybackRef.current.forEach((state) => {
|
|
3964
|
+
if (!state.playing) {
|
|
3965
|
+
return;
|
|
3966
|
+
}
|
|
3967
|
+
const speed = Number.isFinite(state.speed) && state.speed > 0 ? state.speed : 1;
|
|
3968
|
+
const next = state.playhead + dt * speed;
|
|
3969
|
+
if (!(state.duration > 0)) {
|
|
3970
|
+
state.playhead = next;
|
|
3971
|
+
return;
|
|
3972
|
+
}
|
|
3973
|
+
state.playhead = state.loop ? next % state.duration : Math.min(next, state.duration);
|
|
3974
|
+
});
|
|
3975
|
+
}, []);
|
|
3777
3976
|
const advanceAnimationTweens = (0, import_react2.useCallback)(
|
|
3778
3977
|
(dt) => {
|
|
3779
3978
|
if (animationTweensRef.current.size === 0) {
|
|
@@ -3838,6 +4037,7 @@ function VizijRuntimeProviderInner({
|
|
|
3838
4037
|
weight: 1,
|
|
3839
4038
|
loop: true,
|
|
3840
4039
|
playing: false,
|
|
4040
|
+
playhead: 0,
|
|
3841
4041
|
resolve: null,
|
|
3842
4042
|
completion: null
|
|
3843
4043
|
};
|
|
@@ -3936,8 +4136,17 @@ function VizijRuntimeProviderInner({
|
|
|
3936
4136
|
return [];
|
|
3937
4137
|
}
|
|
3938
4138
|
const raw = device.readValues([ANIMATION_PLAYERS_PATH])[ANIMATION_PLAYERS_PATH];
|
|
3939
|
-
|
|
3940
|
-
|
|
4139
|
+
const states = decodePlayerStates(raw);
|
|
4140
|
+
if (DEV_MODE && states.length === 0 && raw && typeof raw === "object") {
|
|
4141
|
+
warnAnimationGap(
|
|
4142
|
+
"player-states-undecodable",
|
|
4143
|
+
`Animation player_states feedback at "${ANIMATION_PLAYERS_PATH}" could not be decoded (top-level keys: ${Object.keys(
|
|
4144
|
+
raw
|
|
4145
|
+
).join(", ")}). The transport falls back to a host-side playhead.`
|
|
4146
|
+
);
|
|
4147
|
+
}
|
|
4148
|
+
return states;
|
|
4149
|
+
}, [deviceSlot, warnAnimationGap]);
|
|
3941
4150
|
const settleFinishedClips = (0, import_react2.useCallback)(() => {
|
|
3942
4151
|
let pending = false;
|
|
3943
4152
|
clipPlaybackRef.current.forEach((state) => {
|
|
@@ -3993,6 +4202,7 @@ function VizijRuntimeProviderInner({
|
|
|
3993
4202
|
if (host) {
|
|
3994
4203
|
if (options?.reset === true) {
|
|
3995
4204
|
host.seek(id, 0);
|
|
4205
|
+
state.playhead = 0;
|
|
3996
4206
|
}
|
|
3997
4207
|
host.setSpeed(id, state.speed);
|
|
3998
4208
|
host.setWeight(id, state.weight);
|
|
@@ -4029,6 +4239,9 @@ function VizijRuntimeProviderInner({
|
|
|
4029
4239
|
(id) => {
|
|
4030
4240
|
const state = clipPlaybackRef.current.get(id);
|
|
4031
4241
|
if (!state || !state.playing) {
|
|
4242
|
+
console.warn(
|
|
4243
|
+
`[vizij-runtime] pause ignored for "${id}": ${state ? "the clip is not marked playing" : "no clip state"}`
|
|
4244
|
+
);
|
|
4032
4245
|
return;
|
|
4033
4246
|
}
|
|
4034
4247
|
state.playing = false;
|
|
@@ -4038,10 +4251,13 @@ function VizijRuntimeProviderInner({
|
|
|
4038
4251
|
[syncAnimationsSource]
|
|
4039
4252
|
);
|
|
4040
4253
|
const seekAnimation = (0, import_react2.useCallback)((id, timeSeconds) => {
|
|
4041
|
-
|
|
4254
|
+
const state = clipPlaybackRef.current.get(id);
|
|
4255
|
+
if (!state) {
|
|
4042
4256
|
return;
|
|
4043
4257
|
}
|
|
4044
|
-
|
|
4258
|
+
const target = Math.max(0, timeSeconds);
|
|
4259
|
+
state.playhead = state.duration > 0 ? Math.min(target, state.duration) : target;
|
|
4260
|
+
animationHostRef.current?.seek(id, target);
|
|
4045
4261
|
}, []);
|
|
4046
4262
|
const setAnimationLoop = (0, import_react2.useCallback)(
|
|
4047
4263
|
(id, enabled) => {
|
|
@@ -4064,9 +4280,23 @@ function VizijRuntimeProviderInner({
|
|
|
4064
4280
|
const playerId = animationHostRef.current?.playerIdOf(id) ?? null;
|
|
4065
4281
|
const feedback = playerId !== null ? readPlayerStates().find((entry) => entry.player === playerId) : void 0;
|
|
4066
4282
|
return {
|
|
4067
|
-
|
|
4283
|
+
// Feedback is authoritative; the host-side playhead stands in when it
|
|
4284
|
+
// is unavailable. Never fall back to a hard 0 — that reads as "the
|
|
4285
|
+
// animation is parked at the start" rather than "no telemetry", and it
|
|
4286
|
+
// pins the transport clock and defeats seeking.
|
|
4287
|
+
time: feedback?.time ?? state.playhead,
|
|
4068
4288
|
duration: feedback && feedback.duration > 0 ? feedback.duration : state.duration,
|
|
4069
|
-
|
|
4289
|
+
// A clip commanded to pause is paused, whatever the feedback says.
|
|
4290
|
+
//
|
|
4291
|
+
// Feedback goes stale by construction: pausing the last playing clip
|
|
4292
|
+
// unregisters the animations graph source, so the module stops being
|
|
4293
|
+
// stepped and `player_states` keeps reporting whatever it last said —
|
|
4294
|
+
// `state: "playing"`, with the playhead frozen. Treating that as
|
|
4295
|
+
// authoritative meant the transport reverted to playing a frame after
|
|
4296
|
+
// every pause, leaving a Pause button over a clip that was not
|
|
4297
|
+
// advancing. Feedback still decides while the clip is *meant* to be
|
|
4298
|
+
// playing, which is what notices a clip reaching its end.
|
|
4299
|
+
playing: state.playing && (feedback ? feedback.state === "playing" : true),
|
|
4070
4300
|
loop: state.loop,
|
|
4071
4301
|
speed: feedback?.speed ?? state.speed
|
|
4072
4302
|
};
|
|
@@ -4087,6 +4317,9 @@ function VizijRuntimeProviderInner({
|
|
|
4087
4317
|
syncAnimationsSource();
|
|
4088
4318
|
} else {
|
|
4089
4319
|
host?.stop(id);
|
|
4320
|
+
if (state) {
|
|
4321
|
+
state.playhead = 0;
|
|
4322
|
+
}
|
|
4090
4323
|
const unsubscribe = subscribeToStep(() => {
|
|
4091
4324
|
unsubscribe();
|
|
4092
4325
|
syncAnimationsSource();
|
|
@@ -4421,6 +4654,13 @@ function VizijRuntimeProviderInner({
|
|
|
4421
4654
|
},
|
|
4422
4655
|
[faceId, namespace, pushError, setInput, setRendererValue]
|
|
4423
4656
|
);
|
|
4657
|
+
const reconcileAnimationsSource = (0, import_react2.useCallback)(() => {
|
|
4658
|
+
const host = animationHostRef.current;
|
|
4659
|
+
if (!host || !host.hasPlaying()) {
|
|
4660
|
+
return;
|
|
4661
|
+
}
|
|
4662
|
+
setAnimationsSourceRegistered(true);
|
|
4663
|
+
}, [setAnimationsSourceRegistered]);
|
|
4424
4664
|
const flushStagedInputs = (0, import_react2.useCallback)(() => {
|
|
4425
4665
|
if (stagedInputsRef.current.size === 0) {
|
|
4426
4666
|
return;
|
|
@@ -4438,6 +4678,8 @@ function VizijRuntimeProviderInner({
|
|
|
4438
4678
|
avgStepDtRef.current = prev * (1 - alpha) + dt * alpha;
|
|
4439
4679
|
}
|
|
4440
4680
|
advanceAnimationTweens(dt);
|
|
4681
|
+
advanceClipPlayheads(dt);
|
|
4682
|
+
reconcileAnimationsSource();
|
|
4441
4683
|
flushStagedInputs();
|
|
4442
4684
|
if (driveRuntimeRef.current || opts?.forceRuntime) {
|
|
4443
4685
|
stepRuntime(dt);
|
|
@@ -4446,7 +4688,9 @@ function VizijRuntimeProviderInner({
|
|
|
4446
4688
|
},
|
|
4447
4689
|
[
|
|
4448
4690
|
advanceAnimationTweens,
|
|
4691
|
+
advanceClipPlayheads,
|
|
4449
4692
|
flushStagedInputs,
|
|
4693
|
+
reconcileAnimationsSource,
|
|
4450
4694
|
settleFinishedClips,
|
|
4451
4695
|
stepRuntime
|
|
4452
4696
|
]
|
|
@@ -5103,11 +5347,14 @@ function mapUnitControlValue(control, unitValue) {
|
|
|
5103
5347
|
buildPoseWeightRelativePath,
|
|
5104
5348
|
buildRigInputPath,
|
|
5105
5349
|
buildSemanticPoseWeightPathMap,
|
|
5350
|
+
collectInputPathMap,
|
|
5351
|
+
composeGraphSpecs,
|
|
5106
5352
|
filterPosesBySemanticKind,
|
|
5107
5353
|
getPoseSemanticKey,
|
|
5108
5354
|
mapNormalizedControlValue,
|
|
5109
5355
|
mapUnitControlValue,
|
|
5110
5356
|
normalizePoseSemanticKey,
|
|
5357
|
+
resolveAnimationBridgeOutputPaths,
|
|
5111
5358
|
resolveFaceControls,
|
|
5112
5359
|
resolvePoseMembership,
|
|
5113
5360
|
resolvePoseSemantics,
|
package/dist/index.d.cts
CHANGED
|
@@ -4,7 +4,7 @@ import { ReactNode, PropsWithChildren } from 'react';
|
|
|
4
4
|
import { ValueJSON } from '@vizij/value-json';
|
|
5
5
|
import { IrGraph } from '@vizij/node-graph-authoring';
|
|
6
6
|
import { RawValue, AnimatableValue } from '@vizij/utils';
|
|
7
|
-
import { World, VizijBundleExtension, VizijProps } from '@vizij/render';
|
|
7
|
+
import { World, VizijBundleExtension, VizijStarredItem, VizijProps } from '@vizij/render';
|
|
8
8
|
|
|
9
9
|
/** Value shape hint. Accepted through the public surface, unused by the device. */
|
|
10
10
|
type ShapeJSON = Record<string, unknown>;
|
|
@@ -156,6 +156,12 @@ type VizijAssetBundle = {
|
|
|
156
156
|
};
|
|
157
157
|
animations?: VizijAnimationAsset[];
|
|
158
158
|
programs?: VizijProgramAsset[];
|
|
159
|
+
/**
|
|
160
|
+
* Designer-curated set of starred drivers/poses, resolved from the loaded
|
|
161
|
+
* bundle's `starred` section. References real functionality by stable id
|
|
162
|
+
* (driver = standard-input id, pose = pose id).
|
|
163
|
+
*/
|
|
164
|
+
starred?: VizijStarredItem[];
|
|
159
165
|
initialInputs?: Record<string, ValueJSON>;
|
|
160
166
|
metadata?: Record<string, unknown>;
|
|
161
167
|
bundle?: VizijBundleExtension | null;
|
|
@@ -394,4 +400,43 @@ declare function resolveFaceControls(assetBundle: VizijAssetBundle, runtimeFaceI
|
|
|
394
400
|
declare function mapNormalizedControlValue(control: FaceScalarControl, normalizedValue: number): number;
|
|
395
401
|
declare function mapUnitControlValue(control: FaceScalarControl, unitValue: number): number;
|
|
396
402
|
|
|
397
|
-
|
|
403
|
+
/**
|
|
404
|
+
* Compose several Vizij graph specs into the ONE graph an Arora device runs.
|
|
405
|
+
*
|
|
406
|
+
* The device runs a single ProcessingGraph as its behavior, so the separate
|
|
407
|
+
* graph sources become a union of nodes and edges here. Sources and their
|
|
408
|
+
* provenance (see `graphSourcesRef` in VizijRuntimeProvider for details):
|
|
409
|
+
* the **rig** graph shipped in the loaded GLB/asset bundle, the bundle's
|
|
410
|
+
* **pose-driver** graph, and one source per **playing program** (bundle
|
|
411
|
+
* programs and the authoring motiongraph, which publishes the editor's
|
|
412
|
+
* graph as a program). Node ids
|
|
413
|
+
* are prefixed `${sourceId}::` so sources can't collide; `params.path` is
|
|
414
|
+
* deliberately NOT prefixed — path identity on the device's shared store is
|
|
415
|
+
* the cross-graph contract (a pose output written to a store path is read by
|
|
416
|
+
* a rig input node with the same path on the next tick).
|
|
417
|
+
*
|
|
418
|
+
* Output-path collisions across sources (two `output` nodes writing one
|
|
419
|
+
* path) are warned and resolved last-writer-wins by evaluation order; there
|
|
420
|
+
* is no additive merge for that case yet, and current bundles don't collide.
|
|
421
|
+
* If a real bundle does, insert an explicit combiner node here instead of
|
|
422
|
+
* widening this warning.
|
|
423
|
+
*/
|
|
424
|
+
type SpecRecord = Record<string, unknown>;
|
|
425
|
+
/** A graph spec treated structurally (same tolerance as `utils/graph.ts`). */
|
|
426
|
+
type ComposableSpec = SpecRecord;
|
|
427
|
+
interface GraphSource {
|
|
428
|
+
/** Stable id of the source graph (e.g. "rig", "pose", a program id). */
|
|
429
|
+
sourceId: string;
|
|
430
|
+
spec: ComposableSpec;
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* Union `sources` into one spec. Later sources evaluate after earlier ones,
|
|
434
|
+
* so on an output-path collision the last source wins (warned).
|
|
435
|
+
*/
|
|
436
|
+
declare function composeGraphSpecs(sources: GraphSource[]): ComposableSpec;
|
|
437
|
+
|
|
438
|
+
declare function collectInputPathMap(spec: GraphRegistrationConfig["spec"]): Record<string, string>;
|
|
439
|
+
|
|
440
|
+
declare function resolveAnimationBridgeOutputPaths(channel: string, faceId?: string, rigInputMap?: Record<string, string>): string[];
|
|
441
|
+
|
|
442
|
+
export { type AnimateValueOptions, type AnimationClipLike, type AnimationKeyframeLike, type AnimationPlaybackState, type AnimationTrackLike, type ComposableSpec, EMOTION_POSE_KEYS, EXPRESSIVE_EMOTION_POSE_KEYS, type FaceScalarControl, type GraphSource, type InputDriverContext, type InputDriverFactory, type InputDriverLifecycle, POSE_WEIGHT_INPUT_PATH_PREFIX, type PlayAnimationOptions, type PoseDefinition, type PoseGroupDefinition, type PoseRigConfig, type ProgramPlaybackState, type RootBounds, type RuntimeGraphBundle, type RuntimeOutputWrite, type RuntimeUpdatePlan, type RuntimeUpdateTier, type StopProgramOptions, VISEME_POSE_KEYS, type VizijAnimationAsset, type VizijAssetBundle, type VizijGlbAsset, type VizijGraphAsset, type VizijProgramAsset, VizijRuntimeFace, type VizijRuntimeFaceProps, VizijRuntimeProvider, type VizijRuntimeProviderProps, type VizijRuntimeStatus, buildPoseWeightInputPathSegment, buildPoseWeightPathMap, buildPoseWeightRelativePath, buildRigInputPath, buildSemanticPoseWeightPathMap, collectInputPathMap, composeGraphSpecs, filterPosesBySemanticKind, getPoseSemanticKey, mapNormalizedControlValue, mapUnitControlValue, normalizePoseSemanticKey, resolveAnimationBridgeOutputPaths, resolveFaceControls, resolvePoseMembership, resolvePoseSemantics, resolveRuntimeUpdatePlan, useOptionalVizijRuntime, useRigInput, useVizijOutputs, useVizijRuntime };
|