@vizij/runtime-react 0.2.0 → 0.3.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/README.md +420 -121
- package/dist/index.cjs +1692 -650
- package/dist/index.d.cts +63 -8
- package/dist/index.d.ts +63 -8
- package/dist/index.js +1701 -667
- package/package.json +23 -21
- package/LICENSE +0 -201
package/dist/index.js
CHANGED
|
@@ -1,33 +1,364 @@
|
|
|
1
1
|
// src/VizijRuntimeProvider.tsx
|
|
2
|
-
import {
|
|
3
|
-
useCallback,
|
|
4
|
-
useContext,
|
|
5
|
-
useEffect,
|
|
6
|
-
useMemo,
|
|
7
|
-
useRef,
|
|
8
|
-
useState
|
|
9
|
-
} from "react";
|
|
2
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
10
3
|
import {
|
|
11
4
|
VizijContext,
|
|
12
5
|
createVizijStore,
|
|
13
6
|
loadGLTFWithBundle,
|
|
14
7
|
loadGLTFFromBlobWithBundle
|
|
15
8
|
} from "@vizij/render";
|
|
16
|
-
import {
|
|
17
|
-
OrchestratorProvider,
|
|
18
|
-
OrchestratorContext,
|
|
19
|
-
useOrchestrator,
|
|
20
|
-
useOrchFrame
|
|
21
|
-
} from "@vizij/orchestrator-react";
|
|
22
9
|
import { compileIrGraph } from "@vizij/node-graph-authoring";
|
|
23
10
|
import { valueAsNumber as valueAsNumber2 } from "@vizij/value-json";
|
|
24
11
|
import { getLookup } from "@vizij/utils";
|
|
12
|
+
import { loadAnimationModule } from "@vizij/animation-module";
|
|
13
|
+
|
|
14
|
+
// src/engine/aroraEngine.ts
|
|
15
|
+
import {
|
|
16
|
+
init,
|
|
17
|
+
startRuntime
|
|
18
|
+
} from "@vizij/runtime";
|
|
19
|
+
|
|
20
|
+
// src/engine/graphSpecDiff.ts
|
|
21
|
+
function asRecords(value) {
|
|
22
|
+
return Array.isArray(value) ? value : [];
|
|
23
|
+
}
|
|
24
|
+
function nodeId(node) {
|
|
25
|
+
return typeof node.id === "string" ? node.id : void 0;
|
|
26
|
+
}
|
|
27
|
+
function endpointId(endpoint) {
|
|
28
|
+
const id = endpoint?.node_id;
|
|
29
|
+
return typeof id === "string" ? id : void 0;
|
|
30
|
+
}
|
|
31
|
+
function endpointInput(endpoint) {
|
|
32
|
+
const input = endpoint?.input;
|
|
33
|
+
return typeof input === "string" ? input : void 0;
|
|
34
|
+
}
|
|
35
|
+
function stable(value) {
|
|
36
|
+
return JSON.stringify(sortKeys(value));
|
|
37
|
+
}
|
|
38
|
+
function sortKeys(value) {
|
|
39
|
+
if (Array.isArray(value)) return value.map(sortKeys);
|
|
40
|
+
if (value && typeof value === "object") {
|
|
41
|
+
const out = {};
|
|
42
|
+
for (const key of Object.keys(value).sort()) {
|
|
43
|
+
out[key] = sortKeys(value[key]);
|
|
44
|
+
}
|
|
45
|
+
return out;
|
|
46
|
+
}
|
|
47
|
+
return value;
|
|
48
|
+
}
|
|
49
|
+
function edgeKey(edge) {
|
|
50
|
+
return stable([
|
|
51
|
+
endpointId(edge.from),
|
|
52
|
+
edge.from?.output ?? "out",
|
|
53
|
+
endpointId(edge.to),
|
|
54
|
+
endpointInput(edge.to),
|
|
55
|
+
edge.selector ?? null
|
|
56
|
+
]);
|
|
57
|
+
}
|
|
58
|
+
function graphSpecDiff(prev, next) {
|
|
59
|
+
const prevNodes = /* @__PURE__ */ new Map();
|
|
60
|
+
for (const node of asRecords(prev.nodes)) {
|
|
61
|
+
const id = nodeId(node);
|
|
62
|
+
if (id) prevNodes.set(id, node);
|
|
63
|
+
}
|
|
64
|
+
const nextNodes = /* @__PURE__ */ new Map();
|
|
65
|
+
for (const node of asRecords(next.nodes)) {
|
|
66
|
+
const id = nodeId(node);
|
|
67
|
+
if (id) nextNodes.set(id, node);
|
|
68
|
+
}
|
|
69
|
+
const prevEdges = asRecords(prev.edges);
|
|
70
|
+
const nextEdges = asRecords(next.edges);
|
|
71
|
+
const remove_nodes = [...prevNodes.keys()].filter((id) => !nextNodes.has(id));
|
|
72
|
+
const upsertIds = /* @__PURE__ */ new Set();
|
|
73
|
+
for (const [id, node] of nextNodes) {
|
|
74
|
+
const before = prevNodes.get(id);
|
|
75
|
+
if (!before || stable(before) !== stable(node)) upsertIds.add(id);
|
|
76
|
+
}
|
|
77
|
+
const prevEdgeKeys = new Set(prevEdges.map(edgeKey));
|
|
78
|
+
const nextEdgeKeys = new Set(nextEdges.map(edgeKey));
|
|
79
|
+
const removedEdges = prevEdges.filter((e) => !nextEdgeKeys.has(edgeKey(e)));
|
|
80
|
+
const changedEdges = [
|
|
81
|
+
...nextEdges.filter((e) => !prevEdgeKeys.has(edgeKey(e))),
|
|
82
|
+
...removedEdges
|
|
83
|
+
];
|
|
84
|
+
for (const edge of changedEdges) {
|
|
85
|
+
for (const id of [endpointId(edge.from), endpointId(edge.to)]) {
|
|
86
|
+
if (id && nextNodes.has(id)) upsertIds.add(id);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
const incidentToUpsert = (edge) => {
|
|
90
|
+
const from = endpointId(edge.from);
|
|
91
|
+
const to = endpointId(edge.to);
|
|
92
|
+
return from !== void 0 && upsertIds.has(from) || to !== void 0 && upsertIds.has(to);
|
|
93
|
+
};
|
|
94
|
+
const upsert_nodes = [...upsertIds].map((id) => nextNodes.get(id));
|
|
95
|
+
const upsert_edges = nextEdges.filter(incidentToUpsert);
|
|
96
|
+
const removeNodeSet = new Set(remove_nodes);
|
|
97
|
+
const remove_edges = removedEdges.flatMap((edge) => {
|
|
98
|
+
const to = endpointId(edge.to);
|
|
99
|
+
const input = endpointInput(edge.to);
|
|
100
|
+
if (!to || !input || removeNodeSet.has(to) || upsertIds.has(to)) return [];
|
|
101
|
+
return [{ node_id: to, input }];
|
|
102
|
+
});
|
|
103
|
+
if (upsert_nodes.length === 0 && remove_nodes.length === 0 && upsert_edges.length === 0 && remove_edges.length === 0) {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
return { upsert_nodes, remove_nodes, upsert_edges, remove_edges };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// src/engine/aroraEngine.ts
|
|
110
|
+
var BUILT_IN_PREFIX = "arora/";
|
|
111
|
+
function isBuiltInPath(path) {
|
|
112
|
+
return path.startsWith(BUILT_IN_PREFIX);
|
|
113
|
+
}
|
|
114
|
+
var RUN_PERIOD_MS = 1e3 / 60;
|
|
115
|
+
var initPromise = null;
|
|
116
|
+
function ensureWasmInit(input) {
|
|
117
|
+
if (!initPromise) {
|
|
118
|
+
initPromise = init(input);
|
|
119
|
+
}
|
|
120
|
+
return initPromise;
|
|
121
|
+
}
|
|
122
|
+
var DeviceSlot = class {
|
|
123
|
+
constructor() {
|
|
124
|
+
this.handle = null;
|
|
125
|
+
/** Module loading a boot must wait out before it reads `modules`. */
|
|
126
|
+
this.modulesLoading = null;
|
|
127
|
+
/**
|
|
128
|
+
* When set, a booted device is handed to its own loop (`device.run`) at
|
|
129
|
+
* this period. `null`: the host drives `step()` itself.
|
|
130
|
+
*/
|
|
131
|
+
this.runPeriodMs = null;
|
|
132
|
+
/**
|
|
133
|
+
* Serializes device operations. Every `recompose` chains behind the
|
|
134
|
+
* previous op so two never run concurrently — a bundle whose import fans
|
|
135
|
+
* out into several graph registrations recomposes in a burst. Failures are
|
|
136
|
+
* swallowed on the chain so one failed op doesn't wedge later ones; each
|
|
137
|
+
* caller still sees its own rejection through the promise `enqueue`
|
|
138
|
+
* returns.
|
|
139
|
+
*/
|
|
140
|
+
this.opChain = Promise.resolve();
|
|
141
|
+
}
|
|
142
|
+
get current() {
|
|
143
|
+
return this.handle;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* The modules every subsequent device boot loads. The live device (if any)
|
|
147
|
+
* is not touched: a later `recompose` that sees the list changed rebuilds.
|
|
148
|
+
*/
|
|
149
|
+
setModules(modules) {
|
|
150
|
+
this.modules = modules && modules.length > 0 ? modules : void 0;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Announce module loading in flight: boots wait for `loading` to settle
|
|
154
|
+
* before reading the module list, so a device never boots without modules
|
|
155
|
+
* that were already on their way. Pass a promise that cannot reject (the
|
|
156
|
+
* owner handles its own failures) — boots resume either way.
|
|
157
|
+
*/
|
|
158
|
+
waitForModules(loading) {
|
|
159
|
+
this.modulesLoading = loading;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Hand the live device to its own loop, if there is one and `runPeriodMs`
|
|
163
|
+
* says so — for a host whose drive mode turns self-paced after boot.
|
|
164
|
+
*/
|
|
165
|
+
startRun() {
|
|
166
|
+
if (this.handle) {
|
|
167
|
+
this.maybeRun(this.handle.device);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
maybeRun(device) {
|
|
171
|
+
if (this.runPeriodMs === null || device.running) {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
device.run(this.runPeriodMs).catch((error) => {
|
|
175
|
+
this.onRunEnded?.(error);
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
enqueue(op) {
|
|
179
|
+
const run = this.opChain.then(op, op);
|
|
180
|
+
this.opChain = run.then(
|
|
181
|
+
() => void 0,
|
|
182
|
+
() => void 0
|
|
183
|
+
);
|
|
184
|
+
return run;
|
|
185
|
+
}
|
|
186
|
+
async boot(spec) {
|
|
187
|
+
await ensureWasmInit();
|
|
188
|
+
if (this.modulesLoading) {
|
|
189
|
+
await this.modulesLoading;
|
|
190
|
+
}
|
|
191
|
+
const device = await startRuntime(spec, void 0, this.modules);
|
|
192
|
+
this.handle = { device, spec, modules: this.modules };
|
|
193
|
+
this.onDeviceStarted?.(this.handle);
|
|
194
|
+
this.maybeRun(device);
|
|
195
|
+
return this.handle;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Rebuild the device because its module set changed since boot — the one
|
|
199
|
+
* change a live device cannot take. Carries the store across (snapshot
|
|
200
|
+
* minus built-in keys) and replays module setup via `onDeviceStarted`. The
|
|
201
|
+
* old device's wrapper is freed; if it was self-paced its loop keeps
|
|
202
|
+
* ticking unreachable — which is why boots wait for announced modules
|
|
203
|
+
* instead of leaning on this path.
|
|
204
|
+
*/
|
|
205
|
+
async rebuild(old, spec) {
|
|
206
|
+
const carried = {};
|
|
207
|
+
for (const [path, value] of Object.entries(old.device.snapshot())) {
|
|
208
|
+
if (!isBuiltInPath(path)) {
|
|
209
|
+
carried[path] = value;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
const device = await startRuntime(spec, void 0, this.modules);
|
|
213
|
+
if (Object.keys(carried).length > 0) {
|
|
214
|
+
device.writeValues(carried);
|
|
215
|
+
}
|
|
216
|
+
this.handle = { device, spec, modules: this.modules };
|
|
217
|
+
old.device.dispose();
|
|
218
|
+
this.onDeviceStarted?.(this.handle);
|
|
219
|
+
this.maybeRun(device);
|
|
220
|
+
return this.handle;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Make the device run `spec`: boot it if none is live, otherwise swap the
|
|
224
|
+
* graph in place (the store is the same store, so nothing is carried).
|
|
225
|
+
* No-op if the spec is identical by reference. Serialized behind any
|
|
226
|
+
* in-flight op (see `opChain`).
|
|
227
|
+
*/
|
|
228
|
+
recompose(spec) {
|
|
229
|
+
return this.enqueue(async () => {
|
|
230
|
+
const old = this.handle;
|
|
231
|
+
if (!old) {
|
|
232
|
+
return this.boot(spec);
|
|
233
|
+
}
|
|
234
|
+
if (old.modules !== this.modules) {
|
|
235
|
+
return this.rebuild(old, spec);
|
|
236
|
+
}
|
|
237
|
+
if (old.spec === spec) {
|
|
238
|
+
return old;
|
|
239
|
+
}
|
|
240
|
+
const edits = graphSpecDiff(old.spec, spec);
|
|
241
|
+
if (edits) {
|
|
242
|
+
try {
|
|
243
|
+
await old.device.applyGraphEdits(edits);
|
|
244
|
+
} catch (err) {
|
|
245
|
+
console.warn(
|
|
246
|
+
"[vizij-runtime] applyGraphEdits failed; reloading the whole graph",
|
|
247
|
+
err
|
|
248
|
+
);
|
|
249
|
+
await old.device.loadGraph(spec);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
old.spec = spec;
|
|
253
|
+
return old;
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
// src/utils/composeGraph.ts
|
|
259
|
+
function asArray(value) {
|
|
260
|
+
return Array.isArray(value) ? value.filter((n) => n && typeof n === "object") : [];
|
|
261
|
+
}
|
|
262
|
+
function prefixId(sourceId, id) {
|
|
263
|
+
return typeof id === "string" && id ? `${sourceId}::${id}` : id;
|
|
264
|
+
}
|
|
265
|
+
function prefixEndpoint(sourceId, endpoint) {
|
|
266
|
+
if (!endpoint || typeof endpoint !== "object") {
|
|
267
|
+
return endpoint;
|
|
268
|
+
}
|
|
269
|
+
const record = endpoint;
|
|
270
|
+
if (typeof record.node_id !== "string") {
|
|
271
|
+
return endpoint;
|
|
272
|
+
}
|
|
273
|
+
return { ...record, node_id: prefixId(sourceId, record.node_id) };
|
|
274
|
+
}
|
|
275
|
+
function outputPathsOf(spec) {
|
|
276
|
+
const paths = [];
|
|
277
|
+
for (const node of asArray(spec.nodes)) {
|
|
278
|
+
if (String(node.type ?? "").toLowerCase() !== "output") {
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
const path = node.params?.path;
|
|
282
|
+
if (typeof path === "string" && path.trim()) {
|
|
283
|
+
paths.push(path.trim());
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
return paths;
|
|
287
|
+
}
|
|
288
|
+
function composeGraphSpecs(sources) {
|
|
289
|
+
const nodes = [];
|
|
290
|
+
const edges = [];
|
|
291
|
+
const outputOwners = /* @__PURE__ */ new Map();
|
|
292
|
+
for (const { sourceId, spec } of sources) {
|
|
293
|
+
for (const path of outputPathsOf(spec)) {
|
|
294
|
+
const owner = outputOwners.get(path);
|
|
295
|
+
if (owner && owner !== sourceId) {
|
|
296
|
+
console.warn(
|
|
297
|
+
`[vizij-runtime] output path "${path}" is written by both "${owner}" and "${sourceId}"; last writer wins ("${sourceId}"). There is no combiner node for additive merges yet.`
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
outputOwners.set(path, sourceId);
|
|
301
|
+
}
|
|
302
|
+
for (const node of asArray(spec.nodes)) {
|
|
303
|
+
nodes.push({ ...node, id: prefixId(sourceId, node.id) });
|
|
304
|
+
}
|
|
305
|
+
for (const edge of asArray(spec.edges)) {
|
|
306
|
+
edges.push({
|
|
307
|
+
...edge,
|
|
308
|
+
from: prefixEndpoint(sourceId, edge.from),
|
|
309
|
+
to: prefixEndpoint(sourceId, edge.to)
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
return { nodes, edges };
|
|
314
|
+
}
|
|
25
315
|
|
|
26
316
|
// src/context.ts
|
|
27
317
|
import { createContext } from "react";
|
|
28
318
|
var VizijRuntimeContext = createContext(null);
|
|
29
319
|
|
|
320
|
+
// src/memoryInvestigation.ts
|
|
321
|
+
function getGlobalMemoryInvestigation() {
|
|
322
|
+
return globalThis;
|
|
323
|
+
}
|
|
324
|
+
function updateRuntimeMap(updater) {
|
|
325
|
+
const globalObj = getGlobalMemoryInvestigation();
|
|
326
|
+
if (!globalObj.__VIZIJ_MEMORY_INVESTIGATION__?.enabled) {
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
if (!globalObj.__vizijMemoryDebugState) {
|
|
330
|
+
globalObj.__vizijMemoryDebugState = {};
|
|
331
|
+
}
|
|
332
|
+
if (!globalObj.__vizijMemoryDebugState.runtimes) {
|
|
333
|
+
globalObj.__vizijMemoryDebugState.runtimes = {};
|
|
334
|
+
}
|
|
335
|
+
updater(globalObj.__vizijMemoryDebugState.runtimes);
|
|
336
|
+
}
|
|
337
|
+
function setRuntimeDebugState(runtimeId, state) {
|
|
338
|
+
updateRuntimeMap((runtimeMap) => {
|
|
339
|
+
runtimeMap[runtimeId] = state;
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
function clearRuntimeDebugState(runtimeId) {
|
|
343
|
+
updateRuntimeMap((runtimeMap) => {
|
|
344
|
+
delete runtimeMap[runtimeId];
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
|
|
30
348
|
// src/updatePolicy.ts
|
|
349
|
+
var blobIdentityMap = /* @__PURE__ */ new WeakMap();
|
|
350
|
+
var nextBlobIdentity = 0;
|
|
351
|
+
function getBlobIdentity(blob) {
|
|
352
|
+
if (!blob) {
|
|
353
|
+
return "missing";
|
|
354
|
+
}
|
|
355
|
+
let identity = blobIdentityMap.get(blob);
|
|
356
|
+
if (identity === void 0) {
|
|
357
|
+
identity = nextBlobIdentity++;
|
|
358
|
+
blobIdentityMap.set(blob, identity);
|
|
359
|
+
}
|
|
360
|
+
return `${identity}:${blob.size}:${blob.type}`;
|
|
361
|
+
}
|
|
31
362
|
function applyRuntimeGraphBundle(base, bundle) {
|
|
32
363
|
const next = {
|
|
33
364
|
...base
|
|
@@ -43,18 +374,10 @@ function applyRuntimeGraphBundle(base, bundle) {
|
|
|
43
374
|
"programs"
|
|
44
375
|
);
|
|
45
376
|
if (hasRigOverride) {
|
|
46
|
-
|
|
47
|
-
next.rig = bundle.rig;
|
|
48
|
-
} else {
|
|
49
|
-
delete next.rig;
|
|
50
|
-
}
|
|
377
|
+
next.rig = bundle.rig;
|
|
51
378
|
}
|
|
52
379
|
if (hasPoseOverride) {
|
|
53
|
-
|
|
54
|
-
next.pose = bundle.pose;
|
|
55
|
-
} else {
|
|
56
|
-
delete next.pose;
|
|
57
|
-
}
|
|
380
|
+
next.pose = bundle.pose;
|
|
58
381
|
}
|
|
59
382
|
if (hasAnimationsOverride) {
|
|
60
383
|
next.animations = Array.isArray(bundle.animations) ? bundle.animations : void 0;
|
|
@@ -76,10 +399,18 @@ function normalizeSpecPayload(value) {
|
|
|
76
399
|
}
|
|
77
400
|
function glbSignature(glb) {
|
|
78
401
|
if (glb.kind === "url") {
|
|
79
|
-
|
|
402
|
+
const importOptions = normalizeSpecPayload({
|
|
403
|
+
aggressiveImport: glb.aggressiveImport ?? false,
|
|
404
|
+
rootBounds: glb.rootBounds ?? null
|
|
405
|
+
});
|
|
406
|
+
return `url:${glb.src}:${importOptions}`;
|
|
80
407
|
}
|
|
81
408
|
if (glb.kind === "blob") {
|
|
82
|
-
|
|
409
|
+
const importOptions = normalizeSpecPayload({
|
|
410
|
+
aggressiveImport: glb.aggressiveImport ?? false,
|
|
411
|
+
rootBounds: glb.rootBounds ?? null
|
|
412
|
+
});
|
|
413
|
+
return `blob:${getBlobIdentity(glb.blob)}:${importOptions}`;
|
|
83
414
|
}
|
|
84
415
|
return `world:${normalizeSpecPayload(glb.world)}`;
|
|
85
416
|
}
|
|
@@ -573,65 +904,20 @@ function resolvePoseControlInputPath({
|
|
|
573
904
|
}
|
|
574
905
|
|
|
575
906
|
// src/utils/clipPlayback.ts
|
|
576
|
-
var EPSILON = 1e-6;
|
|
577
907
|
function toFiniteNumber(value, fallback) {
|
|
578
908
|
const parsed = Number(value);
|
|
579
909
|
return Number.isFinite(parsed) ? parsed : fallback;
|
|
580
910
|
}
|
|
581
|
-
function
|
|
582
|
-
const mode = typeof interpolation === "string" ? interpolation.trim().toLowerCase() : "linear";
|
|
583
|
-
if (mode === "step") {
|
|
584
|
-
return "step";
|
|
585
|
-
}
|
|
586
|
-
if (mode === "cubic" || mode === "cubicspline") {
|
|
587
|
-
return "cubic";
|
|
588
|
-
}
|
|
589
|
-
return "linear";
|
|
590
|
-
}
|
|
591
|
-
function asNumericKeyframe(keyframe) {
|
|
592
|
-
if (!keyframe || typeof keyframe !== "object") {
|
|
593
|
-
return null;
|
|
594
|
-
}
|
|
595
|
-
const time = Number(keyframe.time);
|
|
596
|
-
const value = Number(keyframe.value);
|
|
597
|
-
if (!Number.isFinite(time) || !Number.isFinite(value)) {
|
|
598
|
-
return null;
|
|
599
|
-
}
|
|
600
|
-
const inTangentRaw = keyframe.inTangent;
|
|
601
|
-
const outTangentRaw = keyframe.outTangent;
|
|
602
|
-
const inTangent = inTangentRaw == null || Number.isFinite(Number(inTangentRaw)) ? inTangentRaw : void 0;
|
|
603
|
-
const outTangent = outTangentRaw == null || Number.isFinite(Number(outTangentRaw)) ? outTangentRaw : void 0;
|
|
604
|
-
return {
|
|
605
|
-
time,
|
|
606
|
-
value,
|
|
607
|
-
inTangent,
|
|
608
|
-
outTangent
|
|
609
|
-
};
|
|
610
|
-
}
|
|
611
|
-
function getNumericKeyframes(track) {
|
|
911
|
+
function lastKeyframeTime(track) {
|
|
612
912
|
const keyframes = Array.isArray(track.keyframes) ? track.keyframes : [];
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
if (Number.isFinite(parsed)) {
|
|
622
|
-
return parsed;
|
|
623
|
-
}
|
|
624
|
-
return fallback;
|
|
625
|
-
}
|
|
626
|
-
function sampleHermite(startValue, endValue, outTangent, inTangent, factor, duration) {
|
|
627
|
-
const t = factor;
|
|
628
|
-
const t2 = t * t;
|
|
629
|
-
const t3 = t2 * t;
|
|
630
|
-
const h00 = 2 * t3 - 3 * t2 + 1;
|
|
631
|
-
const h10 = t3 - 2 * t2 + t;
|
|
632
|
-
const h01 = -2 * t3 + 3 * t2;
|
|
633
|
-
const h11 = t3 - t2;
|
|
634
|
-
return h00 * startValue + h10 * outTangent * duration + h01 * endValue + h11 * inTangent * duration;
|
|
913
|
+
let maxTime = 0;
|
|
914
|
+
keyframes.forEach((keyframe) => {
|
|
915
|
+
const time = Number(keyframe?.time);
|
|
916
|
+
if (Number.isFinite(time) && time > maxTime) {
|
|
917
|
+
maxTime = time;
|
|
918
|
+
}
|
|
919
|
+
});
|
|
920
|
+
return maxTime;
|
|
635
921
|
}
|
|
636
922
|
function resolveClipDurationSeconds(clip, fallbackDurationSeconds = 0) {
|
|
637
923
|
const fallback = Math.max(0, toFiniteNumber(fallbackDurationSeconds, 0));
|
|
@@ -645,109 +931,13 @@ function resolveClipDurationSeconds(clip, fallbackDurationSeconds = 0) {
|
|
|
645
931
|
const tracks = Array.isArray(clip.tracks) ? clip.tracks : [];
|
|
646
932
|
let maxTime = 0;
|
|
647
933
|
tracks.forEach((track) => {
|
|
648
|
-
const
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
maxTime = last.time;
|
|
934
|
+
const time = lastKeyframeTime(track);
|
|
935
|
+
if (time > maxTime) {
|
|
936
|
+
maxTime = time;
|
|
652
937
|
}
|
|
653
938
|
});
|
|
654
939
|
return maxTime > 0 ? maxTime : fallback;
|
|
655
940
|
}
|
|
656
|
-
function clampAnimationTime(time, duration) {
|
|
657
|
-
if (!Number.isFinite(duration) || duration <= 0) {
|
|
658
|
-
return 0;
|
|
659
|
-
}
|
|
660
|
-
if (!Number.isFinite(time)) {
|
|
661
|
-
return 0;
|
|
662
|
-
}
|
|
663
|
-
if (time <= 0) {
|
|
664
|
-
return 0;
|
|
665
|
-
}
|
|
666
|
-
if (time >= duration) {
|
|
667
|
-
return duration;
|
|
668
|
-
}
|
|
669
|
-
return time;
|
|
670
|
-
}
|
|
671
|
-
function advanceClipTime(state, dt) {
|
|
672
|
-
const duration = Math.max(0, toFiniteNumber(state.duration, 0));
|
|
673
|
-
const speed = Number.isFinite(state.speed) && state.speed > 0 ? state.speed : 1;
|
|
674
|
-
const currentTime = clampAnimationTime(state.time, duration);
|
|
675
|
-
const delta = Number.isFinite(dt) && dt > 0 ? Math.max(0, dt) * speed : 0;
|
|
676
|
-
if (!state.playing || delta <= 0) {
|
|
677
|
-
return { time: currentTime, completed: false };
|
|
678
|
-
}
|
|
679
|
-
if (duration <= 0) {
|
|
680
|
-
return { time: 0, completed: true };
|
|
681
|
-
}
|
|
682
|
-
const nextTime = currentTime + delta;
|
|
683
|
-
if (state.loop) {
|
|
684
|
-
if (nextTime < duration) {
|
|
685
|
-
return { time: nextTime, completed: false };
|
|
686
|
-
}
|
|
687
|
-
const wrapped = (nextTime % duration + duration) % duration;
|
|
688
|
-
return { time: wrapped, completed: false };
|
|
689
|
-
}
|
|
690
|
-
if (nextTime >= duration - EPSILON) {
|
|
691
|
-
return { time: duration, completed: true };
|
|
692
|
-
}
|
|
693
|
-
return { time: nextTime, completed: false };
|
|
694
|
-
}
|
|
695
|
-
function sampleTrackAtTime(track, timeSeconds) {
|
|
696
|
-
const keyframes = getNumericKeyframes(track);
|
|
697
|
-
if (keyframes.length === 0) {
|
|
698
|
-
return 0;
|
|
699
|
-
}
|
|
700
|
-
if (keyframes.length === 1) {
|
|
701
|
-
return keyframes[0].value;
|
|
702
|
-
}
|
|
703
|
-
const mode = normaliseInterpolation(track.interpolation);
|
|
704
|
-
const time = Number.isFinite(timeSeconds) ? timeSeconds : 0;
|
|
705
|
-
const first = keyframes[0];
|
|
706
|
-
if (time <= first.time + EPSILON) {
|
|
707
|
-
return first.value;
|
|
708
|
-
}
|
|
709
|
-
const last = keyframes[keyframes.length - 1];
|
|
710
|
-
if (time >= last.time - EPSILON) {
|
|
711
|
-
return last.value;
|
|
712
|
-
}
|
|
713
|
-
for (let i = 0; i < keyframes.length - 1; i += 1) {
|
|
714
|
-
const current = keyframes[i];
|
|
715
|
-
const next = keyframes[i + 1];
|
|
716
|
-
const start = current.time;
|
|
717
|
-
const end = next.time;
|
|
718
|
-
const duration = end - start;
|
|
719
|
-
if (duration <= EPSILON) {
|
|
720
|
-
if (time <= end + EPSILON) {
|
|
721
|
-
return next.value;
|
|
722
|
-
}
|
|
723
|
-
continue;
|
|
724
|
-
}
|
|
725
|
-
if (Math.abs(time - end) <= EPSILON) {
|
|
726
|
-
return next.value;
|
|
727
|
-
}
|
|
728
|
-
if (time < end) {
|
|
729
|
-
const factor = (time - start) / duration;
|
|
730
|
-
if (mode === "step") {
|
|
731
|
-
return current.value;
|
|
732
|
-
}
|
|
733
|
-
if (mode === "cubic") {
|
|
734
|
-
const slope = (next.value - current.value) / duration;
|
|
735
|
-
const outTangent = resolveTangent(current.outTangent, slope);
|
|
736
|
-
const inTangent = resolveTangent(next.inTangent, slope);
|
|
737
|
-
return sampleHermite(
|
|
738
|
-
current.value,
|
|
739
|
-
next.value,
|
|
740
|
-
outTangent,
|
|
741
|
-
inTangent,
|
|
742
|
-
factor,
|
|
743
|
-
duration
|
|
744
|
-
);
|
|
745
|
-
}
|
|
746
|
-
return current.value + (next.value - current.value) * factor;
|
|
747
|
-
}
|
|
748
|
-
}
|
|
749
|
-
return last.value;
|
|
750
|
-
}
|
|
751
941
|
|
|
752
942
|
// src/utils/animationBridge.ts
|
|
753
943
|
function resolveAnimationBridgeOutputPaths(channel, faceId, rigInputMap) {
|
|
@@ -818,56 +1008,10 @@ function collectAnimationClipOutputPaths(clip, faceId, rigInputMap) {
|
|
|
818
1008
|
(left, right) => left.localeCompare(right)
|
|
819
1009
|
);
|
|
820
1010
|
}
|
|
821
|
-
function sampleAnimationClipOutputValues(clip, timeSeconds, weight = 1, faceId, rigInputMap) {
|
|
822
|
-
const appliedWeight = Number.isFinite(weight) && weight >= 0 ? Number(weight) : 1;
|
|
823
|
-
const outputValues = /* @__PURE__ */ new Map();
|
|
824
|
-
const tracks = Array.isArray(clip.tracks) ? clip.tracks : [];
|
|
825
|
-
tracks.forEach((track) => {
|
|
826
|
-
const channel = typeof track.channel === "string" ? track.channel.trim() : "";
|
|
827
|
-
if (!channel) {
|
|
828
|
-
return;
|
|
829
|
-
}
|
|
830
|
-
const sampledValue = sampleTrackAtTime(
|
|
831
|
-
track,
|
|
832
|
-
timeSeconds
|
|
833
|
-
);
|
|
834
|
-
const weightedValue = sampledValue * appliedWeight;
|
|
835
|
-
resolveAnimationBridgeOutputPaths(channel, faceId, rigInputMap).forEach(
|
|
836
|
-
(path) => {
|
|
837
|
-
outputValues.set(path, (outputValues.get(path) ?? 0) + weightedValue);
|
|
838
|
-
}
|
|
839
|
-
);
|
|
840
|
-
});
|
|
841
|
-
return outputValues;
|
|
842
|
-
}
|
|
843
|
-
function diffAnimationAggregateValues(previousAggregate, nextAggregate, epsilon = 1e-6) {
|
|
844
|
-
const operations = [];
|
|
845
|
-
const changedPaths = /* @__PURE__ */ new Set();
|
|
846
|
-
previousAggregate.forEach((previousValue, path) => {
|
|
847
|
-
const nextValue = nextAggregate.get(path);
|
|
848
|
-
if (nextValue === void 0 || Math.abs(nextValue - previousValue) > epsilon) {
|
|
849
|
-
changedPaths.add(path);
|
|
850
|
-
}
|
|
851
|
-
});
|
|
852
|
-
nextAggregate.forEach((nextValue, path) => {
|
|
853
|
-
const previousValue = previousAggregate.get(path);
|
|
854
|
-
if (previousValue === void 0 || Math.abs(nextValue - previousValue) > epsilon) {
|
|
855
|
-
changedPaths.add(path);
|
|
856
|
-
}
|
|
857
|
-
});
|
|
858
|
-
changedPaths.forEach((path) => {
|
|
859
|
-
const nextValue = nextAggregate.get(path);
|
|
860
|
-
if (nextValue === void 0) {
|
|
861
|
-
operations.push({ kind: "clear", path });
|
|
862
|
-
return;
|
|
863
|
-
}
|
|
864
|
-
operations.push({ kind: "set", path, value: nextValue });
|
|
865
|
-
});
|
|
866
|
-
return operations;
|
|
867
|
-
}
|
|
868
1011
|
|
|
869
1012
|
// src/utils/valueConversion.ts
|
|
870
1013
|
import {
|
|
1014
|
+
fromAroraValueJSON,
|
|
871
1015
|
isNormalizedValue,
|
|
872
1016
|
valueAsBool,
|
|
873
1017
|
valueAsColorRgba,
|
|
@@ -920,27 +1064,28 @@ function valueJSONToRaw(value) {
|
|
|
920
1064
|
if (Array.isArray(value)) {
|
|
921
1065
|
return value.map((entry) => valueJSONToRaw(entry));
|
|
922
1066
|
}
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
1067
|
+
const normalized = isNormalizedValue(value) ? value : fromAroraValueJSON(value);
|
|
1068
|
+
if (normalized === void 0) {
|
|
1069
|
+
if (typeof value === "object" && !("type" in value)) {
|
|
1070
|
+
const entries = Object.entries(value).map(([key, entry]) => [
|
|
1071
|
+
key,
|
|
1072
|
+
valueJSONToRaw(entry)
|
|
1073
|
+
]);
|
|
1074
|
+
return Object.fromEntries(entries);
|
|
1075
|
+
}
|
|
931
1076
|
return void 0;
|
|
932
1077
|
}
|
|
933
|
-
switch (
|
|
1078
|
+
switch (normalized.type) {
|
|
934
1079
|
case "float": {
|
|
935
|
-
const num = valueAsNumber(
|
|
1080
|
+
const num = valueAsNumber(normalized);
|
|
936
1081
|
return typeof num === "number" ? num : void 0;
|
|
937
1082
|
}
|
|
938
1083
|
case "bool": {
|
|
939
|
-
const boolVal = valueAsBool(
|
|
1084
|
+
const boolVal = valueAsBool(normalized);
|
|
940
1085
|
return typeof boolVal === "boolean" ? boolVal : void 0;
|
|
941
1086
|
}
|
|
942
1087
|
case "text": {
|
|
943
|
-
const text = valueAsText(
|
|
1088
|
+
const text = valueAsText(normalized);
|
|
944
1089
|
return typeof text === "string" ? text : void 0;
|
|
945
1090
|
}
|
|
946
1091
|
case "vec2":
|
|
@@ -948,11 +1093,11 @@ function valueJSONToRaw(value) {
|
|
|
948
1093
|
case "vec4":
|
|
949
1094
|
case "quat":
|
|
950
1095
|
case "vector": {
|
|
951
|
-
const vec = valueAsVector(
|
|
1096
|
+
const vec = valueAsVector(normalized);
|
|
952
1097
|
return vec ? numericArrayToRaw(vec) : void 0;
|
|
953
1098
|
}
|
|
954
1099
|
case "colorrgba": {
|
|
955
|
-
const color = valueAsColorRgba(
|
|
1100
|
+
const color = valueAsColorRgba(normalized);
|
|
956
1101
|
if (!color) {
|
|
957
1102
|
return void 0;
|
|
958
1103
|
}
|
|
@@ -960,7 +1105,7 @@ function valueJSONToRaw(value) {
|
|
|
960
1105
|
return { r, g, b, a };
|
|
961
1106
|
}
|
|
962
1107
|
case "transform": {
|
|
963
|
-
const transform = valueAsTransform(
|
|
1108
|
+
const transform = valueAsTransform(normalized);
|
|
964
1109
|
if (!transform) {
|
|
965
1110
|
return void 0;
|
|
966
1111
|
}
|
|
@@ -970,15 +1115,20 @@ function valueJSONToRaw(value) {
|
|
|
970
1115
|
scale: numericArrayToRaw(transform.scale)
|
|
971
1116
|
};
|
|
972
1117
|
}
|
|
1118
|
+
case "array":
|
|
1119
|
+
case "list":
|
|
1120
|
+
case "tuple":
|
|
1121
|
+
return (normalized.data ?? []).map(
|
|
1122
|
+
(entry) => valueJSONToRaw(entry)
|
|
1123
|
+
);
|
|
973
1124
|
case "record": {
|
|
974
|
-
const entries = Object.entries(
|
|
975
|
-
key,
|
|
976
|
-
|
|
977
|
-
]);
|
|
1125
|
+
const entries = Object.entries(normalized.data ?? {}).map(
|
|
1126
|
+
([key, entry]) => [key, valueJSONToRaw(entry)]
|
|
1127
|
+
);
|
|
978
1128
|
return Object.fromEntries(entries);
|
|
979
1129
|
}
|
|
980
1130
|
case "enum": {
|
|
981
|
-
const [tag, inner] =
|
|
1131
|
+
const [tag, inner] = normalized.data;
|
|
982
1132
|
return {
|
|
983
1133
|
tag,
|
|
984
1134
|
value: valueJSONToRaw(inner)
|
|
@@ -989,17 +1139,608 @@ function valueJSONToRaw(value) {
|
|
|
989
1139
|
}
|
|
990
1140
|
}
|
|
991
1141
|
|
|
1142
|
+
// src/engine/animationModule.ts
|
|
1143
|
+
var ANIMATION_MODULE_FN = {
|
|
1144
|
+
loadAnimation: "76697a69-6a00-0000-0f00-000000000001",
|
|
1145
|
+
createPlayer: "76697a69-6a00-0000-0f00-000000000002",
|
|
1146
|
+
addInstance: "76697a69-6a00-0000-0f00-000000000003",
|
|
1147
|
+
step: "76697a69-6a00-0000-0f00-000000000004",
|
|
1148
|
+
play: "76697a69-6a00-0000-0f00-000000000005",
|
|
1149
|
+
pause: "76697a69-6a00-0000-0f00-000000000006",
|
|
1150
|
+
stop: "76697a69-6a00-0000-0f00-000000000007",
|
|
1151
|
+
seek: "76697a69-6a00-0000-0f00-000000000008",
|
|
1152
|
+
setSpeed: "76697a69-6a00-0000-0f00-000000000009",
|
|
1153
|
+
setLoop: "76697a69-6a00-0000-0f00-00000000000a",
|
|
1154
|
+
setWeight: "76697a69-6a00-0000-0f00-00000000000b",
|
|
1155
|
+
removeInstance: "76697a69-6a00-0000-0f00-00000000000c",
|
|
1156
|
+
playerStates: "76697a69-6a00-0000-0f00-00000000000d"
|
|
1157
|
+
};
|
|
1158
|
+
var ANIMATION_MODULE_PARAM = {
|
|
1159
|
+
clip: "76697a69-6a00-0000-0f01-000000000001",
|
|
1160
|
+
playerName: "76697a69-6a00-0000-0f02-000000000001",
|
|
1161
|
+
player: "76697a69-6a00-0000-0f03-000000000001",
|
|
1162
|
+
anim: "76697a69-6a00-0000-0f03-000000000002",
|
|
1163
|
+
dtNs: "76697a69-6a00-0000-0f04-000000000001",
|
|
1164
|
+
playPlayer: "76697a69-6a00-0000-0f05-000000000001",
|
|
1165
|
+
pausePlayer: "76697a69-6a00-0000-0f06-000000000001",
|
|
1166
|
+
stopPlayer: "76697a69-6a00-0000-0f07-000000000001",
|
|
1167
|
+
seekPlayer: "76697a69-6a00-0000-0f08-000000000001",
|
|
1168
|
+
seekTimeNs: "76697a69-6a00-0000-0f08-000000000002",
|
|
1169
|
+
speedPlayer: "76697a69-6a00-0000-0f09-000000000001",
|
|
1170
|
+
speedValue: "76697a69-6a00-0000-0f09-000000000002",
|
|
1171
|
+
loopPlayer: "76697a69-6a00-0000-0f0a-000000000001",
|
|
1172
|
+
loopMode: "76697a69-6a00-0000-0f0a-000000000002",
|
|
1173
|
+
weightPlayer: "76697a69-6a00-0000-0f0b-000000000001",
|
|
1174
|
+
weightInstance: "76697a69-6a00-0000-0f0b-000000000002",
|
|
1175
|
+
weightValue: "76697a69-6a00-0000-0f0b-000000000003",
|
|
1176
|
+
removePlayer: "76697a69-6a00-0000-0f0c-000000000001",
|
|
1177
|
+
removeInstance: "76697a69-6a00-0000-0f0c-000000000002"
|
|
1178
|
+
};
|
|
1179
|
+
var ANIMATION_MODULE_TYPE = {
|
|
1180
|
+
clip: "76697a69-6a00-0000-0000-000000000100",
|
|
1181
|
+
track: "76697a69-6a00-0000-0000-000000000101",
|
|
1182
|
+
keypoint: "76697a69-6a00-0000-0000-000000000102",
|
|
1183
|
+
transitionHandle: "76697a69-6a00-0000-0000-000000000103",
|
|
1184
|
+
trackOutput: "76697a69-6a00-0000-0000-000000000110",
|
|
1185
|
+
playerState: "76697a69-6a00-0000-0000-000000000111"
|
|
1186
|
+
};
|
|
1187
|
+
var ANIMATION_MODULE_FIELD = {
|
|
1188
|
+
clipName: "76697a69-6a00-0000-0100-000000000001",
|
|
1189
|
+
clipDuration: "76697a69-6a00-0000-0100-000000000002",
|
|
1190
|
+
clipTracks: "76697a69-6a00-0000-0100-000000000003",
|
|
1191
|
+
trackId: "76697a69-6a00-0000-0101-000000000001",
|
|
1192
|
+
trackName: "76697a69-6a00-0000-0101-000000000002",
|
|
1193
|
+
trackAnimatable: "76697a69-6a00-0000-0101-000000000003",
|
|
1194
|
+
trackPoints: "76697a69-6a00-0000-0101-000000000004",
|
|
1195
|
+
keypointId: "76697a69-6a00-0000-0102-000000000001",
|
|
1196
|
+
keypointStamp: "76697a69-6a00-0000-0102-000000000002",
|
|
1197
|
+
keypointValue: "76697a69-6a00-0000-0102-000000000003",
|
|
1198
|
+
keypointTransitionsIn: "76697a69-6a00-0000-0102-000000000004",
|
|
1199
|
+
keypointTransitionsOut: "76697a69-6a00-0000-0102-000000000005",
|
|
1200
|
+
handleX: "76697a69-6a00-0000-0103-000000000001",
|
|
1201
|
+
handleY: "76697a69-6a00-0000-0103-000000000002",
|
|
1202
|
+
outputTrackId: "76697a69-6a00-0000-0110-000000000001",
|
|
1203
|
+
outputDefaultKey: "76697a69-6a00-0000-0110-000000000002",
|
|
1204
|
+
outputValue: "76697a69-6a00-0000-0110-000000000003",
|
|
1205
|
+
statePlayer: "76697a69-6a00-0000-0111-000000000001",
|
|
1206
|
+
stateState: "76697a69-6a00-0000-0111-000000000002",
|
|
1207
|
+
stateTimeNs: "76697a69-6a00-0000-0111-000000000003",
|
|
1208
|
+
stateDurationNs: "76697a69-6a00-0000-0111-000000000004",
|
|
1209
|
+
stateSpeed: "76697a69-6a00-0000-0111-000000000005"
|
|
1210
|
+
};
|
|
1211
|
+
var field = (id, value) => ({
|
|
1212
|
+
id,
|
|
1213
|
+
value
|
|
1214
|
+
});
|
|
1215
|
+
function storedClipToModuleValue(clip, resolveKeys) {
|
|
1216
|
+
const name = typeof clip.name === "string" && clip.name.trim().length > 0 ? clip.name.trim() : typeof clip.id === "string" ? clip.id : "";
|
|
1217
|
+
const durationMs = Number(clip.duration);
|
|
1218
|
+
const duration = Number.isFinite(durationMs) && durationMs > 0 ? Math.round(durationMs) : 1;
|
|
1219
|
+
const tracks = (Array.isArray(clip.tracks) ? clip.tracks : []).map((rawTrack, index) => {
|
|
1220
|
+
if (!rawTrack || typeof rawTrack !== "object") {
|
|
1221
|
+
return null;
|
|
1222
|
+
}
|
|
1223
|
+
const animatableId = typeof rawTrack.animatableId === "string" ? rawTrack.animatableId.trim() : "";
|
|
1224
|
+
if (!animatableId) {
|
|
1225
|
+
return null;
|
|
1226
|
+
}
|
|
1227
|
+
const trackId = typeof rawTrack.id === "string" && rawTrack.id.trim().length > 0 ? rawTrack.id.trim() : `track-${index}`;
|
|
1228
|
+
const trackName = typeof rawTrack.name === "string" && rawTrack.name.trim().length > 0 ? rawTrack.name.trim() : trackId;
|
|
1229
|
+
const points = (Array.isArray(rawTrack.points) ? rawTrack.points : []).map((rawPoint, pointIndex) => {
|
|
1230
|
+
if (!rawPoint || typeof rawPoint !== "object") {
|
|
1231
|
+
return null;
|
|
1232
|
+
}
|
|
1233
|
+
const stamp = Number(rawPoint.stamp);
|
|
1234
|
+
const value = Number(rawPoint.value);
|
|
1235
|
+
if (!Number.isFinite(stamp) || !Number.isFinite(value)) {
|
|
1236
|
+
return null;
|
|
1237
|
+
}
|
|
1238
|
+
const pointId = typeof rawPoint.id === "string" && rawPoint.id.trim().length > 0 ? rawPoint.id.trim() : `${trackId}:point-${pointIndex}`;
|
|
1239
|
+
const handles = (handle) => ({
|
|
1240
|
+
structs: {
|
|
1241
|
+
id: ANIMATION_MODULE_TYPE.transitionHandle,
|
|
1242
|
+
elements: handle ? [
|
|
1243
|
+
{
|
|
1244
|
+
fields: [
|
|
1245
|
+
field(ANIMATION_MODULE_FIELD.handleX, {
|
|
1246
|
+
f32: handle.x
|
|
1247
|
+
}),
|
|
1248
|
+
field(ANIMATION_MODULE_FIELD.handleY, {
|
|
1249
|
+
f32: handle.y
|
|
1250
|
+
})
|
|
1251
|
+
]
|
|
1252
|
+
}
|
|
1253
|
+
] : []
|
|
1254
|
+
}
|
|
1255
|
+
});
|
|
1256
|
+
return {
|
|
1257
|
+
fields: [
|
|
1258
|
+
field(ANIMATION_MODULE_FIELD.keypointId, { str: pointId }),
|
|
1259
|
+
field(ANIMATION_MODULE_FIELD.keypointStamp, {
|
|
1260
|
+
f32: Math.max(0, Math.min(1, stamp))
|
|
1261
|
+
}),
|
|
1262
|
+
field(ANIMATION_MODULE_FIELD.keypointValue, { f32: value }),
|
|
1263
|
+
field(
|
|
1264
|
+
ANIMATION_MODULE_FIELD.keypointTransitionsIn,
|
|
1265
|
+
handles(rawPoint.transitions?.in)
|
|
1266
|
+
),
|
|
1267
|
+
field(
|
|
1268
|
+
ANIMATION_MODULE_FIELD.keypointTransitionsOut,
|
|
1269
|
+
handles(rawPoint.transitions?.out)
|
|
1270
|
+
)
|
|
1271
|
+
]
|
|
1272
|
+
};
|
|
1273
|
+
}).filter(Boolean);
|
|
1274
|
+
if (points.length === 0) {
|
|
1275
|
+
return null;
|
|
1276
|
+
}
|
|
1277
|
+
const targets = resolveKeys?.(animatableId) ?? [animatableId];
|
|
1278
|
+
return targets.map((target, targetIndex) => ({
|
|
1279
|
+
fields: [
|
|
1280
|
+
field(ANIMATION_MODULE_FIELD.trackId, {
|
|
1281
|
+
str: targetIndex === 0 ? trackId : `${trackId}~${targetIndex}`
|
|
1282
|
+
}),
|
|
1283
|
+
field(ANIMATION_MODULE_FIELD.trackName, { str: trackName }),
|
|
1284
|
+
field(ANIMATION_MODULE_FIELD.trackAnimatable, { str: target }),
|
|
1285
|
+
field(ANIMATION_MODULE_FIELD.trackPoints, {
|
|
1286
|
+
structs: {
|
|
1287
|
+
id: ANIMATION_MODULE_TYPE.keypoint,
|
|
1288
|
+
elements: points
|
|
1289
|
+
}
|
|
1290
|
+
})
|
|
1291
|
+
]
|
|
1292
|
+
}));
|
|
1293
|
+
}).filter(Boolean).flat();
|
|
1294
|
+
return {
|
|
1295
|
+
struct: {
|
|
1296
|
+
id: ANIMATION_MODULE_TYPE.clip,
|
|
1297
|
+
fields: [
|
|
1298
|
+
field(ANIMATION_MODULE_FIELD.clipName, { str: name }),
|
|
1299
|
+
field(ANIMATION_MODULE_FIELD.clipDuration, { u32: duration }),
|
|
1300
|
+
field(ANIMATION_MODULE_FIELD.clipTracks, {
|
|
1301
|
+
structs: {
|
|
1302
|
+
id: ANIMATION_MODULE_TYPE.track,
|
|
1303
|
+
elements: tracks
|
|
1304
|
+
}
|
|
1305
|
+
})
|
|
1306
|
+
]
|
|
1307
|
+
}
|
|
1308
|
+
};
|
|
1309
|
+
}
|
|
1310
|
+
function loadAnimationCall(clipValue) {
|
|
1311
|
+
return {
|
|
1312
|
+
id: ANIMATION_MODULE_FN.loadAnimation,
|
|
1313
|
+
args: [field(ANIMATION_MODULE_PARAM.clip, clipValue)]
|
|
1314
|
+
};
|
|
1315
|
+
}
|
|
1316
|
+
function createPlayerCall(name) {
|
|
1317
|
+
return {
|
|
1318
|
+
id: ANIMATION_MODULE_FN.createPlayer,
|
|
1319
|
+
args: [field(ANIMATION_MODULE_PARAM.playerName, { str: name })]
|
|
1320
|
+
};
|
|
1321
|
+
}
|
|
1322
|
+
function addInstanceCall(player, anim) {
|
|
1323
|
+
return {
|
|
1324
|
+
id: ANIMATION_MODULE_FN.addInstance,
|
|
1325
|
+
args: [
|
|
1326
|
+
field(ANIMATION_MODULE_PARAM.player, { u32: player }),
|
|
1327
|
+
field(ANIMATION_MODULE_PARAM.anim, { u32: anim })
|
|
1328
|
+
]
|
|
1329
|
+
};
|
|
1330
|
+
}
|
|
1331
|
+
function playCall(player) {
|
|
1332
|
+
return {
|
|
1333
|
+
id: ANIMATION_MODULE_FN.play,
|
|
1334
|
+
args: [field(ANIMATION_MODULE_PARAM.playPlayer, { u32: player })]
|
|
1335
|
+
};
|
|
1336
|
+
}
|
|
1337
|
+
function pauseCall(player) {
|
|
1338
|
+
return {
|
|
1339
|
+
id: ANIMATION_MODULE_FN.pause,
|
|
1340
|
+
args: [field(ANIMATION_MODULE_PARAM.pausePlayer, { u32: player })]
|
|
1341
|
+
};
|
|
1342
|
+
}
|
|
1343
|
+
function stopCall(player) {
|
|
1344
|
+
return {
|
|
1345
|
+
id: ANIMATION_MODULE_FN.stop,
|
|
1346
|
+
args: [field(ANIMATION_MODULE_PARAM.stopPlayer, { u32: player })]
|
|
1347
|
+
};
|
|
1348
|
+
}
|
|
1349
|
+
function seekCall(player, timeNs) {
|
|
1350
|
+
return {
|
|
1351
|
+
id: ANIMATION_MODULE_FN.seek,
|
|
1352
|
+
args: [
|
|
1353
|
+
field(ANIMATION_MODULE_PARAM.seekPlayer, { u32: player }),
|
|
1354
|
+
field(ANIMATION_MODULE_PARAM.seekTimeNs, {
|
|
1355
|
+
u64: Math.max(0, Math.round(timeNs))
|
|
1356
|
+
})
|
|
1357
|
+
]
|
|
1358
|
+
};
|
|
1359
|
+
}
|
|
1360
|
+
function setSpeedCall(player, speed) {
|
|
1361
|
+
return {
|
|
1362
|
+
id: ANIMATION_MODULE_FN.setSpeed,
|
|
1363
|
+
args: [
|
|
1364
|
+
field(ANIMATION_MODULE_PARAM.speedPlayer, { u32: player }),
|
|
1365
|
+
field(ANIMATION_MODULE_PARAM.speedValue, { f32: speed })
|
|
1366
|
+
]
|
|
1367
|
+
};
|
|
1368
|
+
}
|
|
1369
|
+
function setLoopCall(player, mode) {
|
|
1370
|
+
return {
|
|
1371
|
+
id: ANIMATION_MODULE_FN.setLoop,
|
|
1372
|
+
args: [
|
|
1373
|
+
field(ANIMATION_MODULE_PARAM.loopPlayer, { u32: player }),
|
|
1374
|
+
field(ANIMATION_MODULE_PARAM.loopMode, { str: mode })
|
|
1375
|
+
]
|
|
1376
|
+
};
|
|
1377
|
+
}
|
|
1378
|
+
function setWeightCall(player, instance, weight) {
|
|
1379
|
+
return {
|
|
1380
|
+
id: ANIMATION_MODULE_FN.setWeight,
|
|
1381
|
+
args: [
|
|
1382
|
+
field(ANIMATION_MODULE_PARAM.weightPlayer, { u32: player }),
|
|
1383
|
+
field(ANIMATION_MODULE_PARAM.weightInstance, { u32: instance }),
|
|
1384
|
+
field(ANIMATION_MODULE_PARAM.weightValue, { f32: weight })
|
|
1385
|
+
]
|
|
1386
|
+
};
|
|
1387
|
+
}
|
|
1388
|
+
function callResultU32(result) {
|
|
1389
|
+
const ret = result.ret;
|
|
1390
|
+
if (ret && typeof ret === "object" && "u32" in ret) {
|
|
1391
|
+
const value = Number(ret.u32);
|
|
1392
|
+
return Number.isFinite(value) ? value : null;
|
|
1393
|
+
}
|
|
1394
|
+
return null;
|
|
1395
|
+
}
|
|
1396
|
+
var ANIMATION_PLAYERS_PATH = "vizij/animations/players";
|
|
1397
|
+
var ANIMATIONS_SOURCE_ID = "animations";
|
|
1398
|
+
function animationsGraphSource() {
|
|
1399
|
+
return {
|
|
1400
|
+
sourceId: ANIMATIONS_SOURCE_ID,
|
|
1401
|
+
spec: {
|
|
1402
|
+
nodes: [
|
|
1403
|
+
{ id: "dt", type: "input", params: { path: "arora/dt" } },
|
|
1404
|
+
{
|
|
1405
|
+
id: "step",
|
|
1406
|
+
type: "externalfunction",
|
|
1407
|
+
params: {
|
|
1408
|
+
function: ANIMATION_MODULE_FN.step,
|
|
1409
|
+
param_ids: [ANIMATION_MODULE_PARAM.dtNs]
|
|
1410
|
+
}
|
|
1411
|
+
},
|
|
1412
|
+
{
|
|
1413
|
+
id: "apply",
|
|
1414
|
+
type: "output",
|
|
1415
|
+
params: {
|
|
1416
|
+
key_field: ANIMATION_MODULE_FIELD.outputDefaultKey,
|
|
1417
|
+
value_field: ANIMATION_MODULE_FIELD.outputValue
|
|
1418
|
+
}
|
|
1419
|
+
},
|
|
1420
|
+
{
|
|
1421
|
+
id: "states",
|
|
1422
|
+
type: "externalfunction",
|
|
1423
|
+
params: { function: ANIMATION_MODULE_FN.playerStates, param_ids: [] }
|
|
1424
|
+
},
|
|
1425
|
+
{
|
|
1426
|
+
id: "states-out",
|
|
1427
|
+
type: "output",
|
|
1428
|
+
params: { path: ANIMATION_PLAYERS_PATH }
|
|
1429
|
+
}
|
|
1430
|
+
],
|
|
1431
|
+
edges: [
|
|
1432
|
+
{ from: { node_id: "dt" }, to: { node_id: "step", input: "args_0" } },
|
|
1433
|
+
{ from: { node_id: "step" }, to: { node_id: "apply", input: "in" } },
|
|
1434
|
+
{
|
|
1435
|
+
from: { node_id: "states" },
|
|
1436
|
+
to: { node_id: "states-out", input: "in" }
|
|
1437
|
+
}
|
|
1438
|
+
]
|
|
1439
|
+
}
|
|
1440
|
+
};
|
|
1441
|
+
}
|
|
1442
|
+
function fieldNumber(value) {
|
|
1443
|
+
if (!value || typeof value !== "object") {
|
|
1444
|
+
return null;
|
|
1445
|
+
}
|
|
1446
|
+
for (const key of ["u64", "u32", "f32", "f64"]) {
|
|
1447
|
+
if (key in value) {
|
|
1448
|
+
const parsed = Number(value[key]);
|
|
1449
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
return null;
|
|
1453
|
+
}
|
|
1454
|
+
function decodePlayerStates(raw) {
|
|
1455
|
+
if (!raw || typeof raw !== "object" || !("structs" in raw)) {
|
|
1456
|
+
return [];
|
|
1457
|
+
}
|
|
1458
|
+
const structs = raw.structs;
|
|
1459
|
+
const elements = Array.isArray(structs?.elements) ? structs.elements : [];
|
|
1460
|
+
const states = [];
|
|
1461
|
+
for (const element of elements) {
|
|
1462
|
+
const fields = element?.fields;
|
|
1463
|
+
if (!Array.isArray(fields)) {
|
|
1464
|
+
continue;
|
|
1465
|
+
}
|
|
1466
|
+
const byId = /* @__PURE__ */ new Map();
|
|
1467
|
+
for (const entry of fields) {
|
|
1468
|
+
const id = entry?.id;
|
|
1469
|
+
const value = entry?.value;
|
|
1470
|
+
if (typeof id === "string" && value && typeof value === "object") {
|
|
1471
|
+
byId.set(id, value);
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
1474
|
+
const player = fieldNumber(byId.get(ANIMATION_MODULE_FIELD.statePlayer));
|
|
1475
|
+
if (player === null) {
|
|
1476
|
+
continue;
|
|
1477
|
+
}
|
|
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
|
+
}
|
|
1487
|
+
return states;
|
|
1488
|
+
}
|
|
1489
|
+
|
|
1490
|
+
// src/engine/animationModuleHost.ts
|
|
1491
|
+
var AnimationModuleHost = class {
|
|
1492
|
+
constructor(getDevice, resolveKeys = (key) => [key]) {
|
|
1493
|
+
this.getDevice = getDevice;
|
|
1494
|
+
this.resolveKeys = resolveKeys;
|
|
1495
|
+
this.clips = /* @__PURE__ */ new Map();
|
|
1496
|
+
}
|
|
1497
|
+
/** Whether any clip is currently marked playing (drives source lifecycle). */
|
|
1498
|
+
hasPlaying() {
|
|
1499
|
+
for (const entry of this.clips.values()) {
|
|
1500
|
+
if (entry.playing) {
|
|
1501
|
+
return true;
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
return false;
|
|
1505
|
+
}
|
|
1506
|
+
/** Whether a clip id is known to the host. */
|
|
1507
|
+
has(clipId) {
|
|
1508
|
+
return this.clips.has(clipId);
|
|
1509
|
+
}
|
|
1510
|
+
/** The clip's module `PlayerId` on the live device (feedback correlation). */
|
|
1511
|
+
playerIdOf(clipId) {
|
|
1512
|
+
return this.clips.get(clipId)?.playerId ?? null;
|
|
1513
|
+
}
|
|
1514
|
+
/** The clip's transport state as last commanded. */
|
|
1515
|
+
transportOf(clipId) {
|
|
1516
|
+
const entry = this.clips.get(clipId);
|
|
1517
|
+
if (!entry) {
|
|
1518
|
+
return null;
|
|
1519
|
+
}
|
|
1520
|
+
return {
|
|
1521
|
+
playing: entry.playing,
|
|
1522
|
+
speed: entry.speed,
|
|
1523
|
+
loop: entry.loop,
|
|
1524
|
+
weight: entry.weight
|
|
1525
|
+
};
|
|
1526
|
+
}
|
|
1527
|
+
/**
|
|
1528
|
+
* Declare the clips the bundle carries, as data. Keeps playing/transport
|
|
1529
|
+
* state for clips that survive; clips no longer present are dropped. Does
|
|
1530
|
+
* not touch the device — loading is lazy (on first play), so muted/unused
|
|
1531
|
+
* clips never enter the module, and conversion happens at load with the
|
|
1532
|
+
* key resolver's state at that moment.
|
|
1533
|
+
*/
|
|
1534
|
+
setClips(clips) {
|
|
1535
|
+
const nextIds = new Set(clips.map((clip) => clip.id));
|
|
1536
|
+
for (const id of Array.from(this.clips.keys())) {
|
|
1537
|
+
if (!nextIds.has(id)) {
|
|
1538
|
+
this.clips.delete(id);
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
for (const clip of clips) {
|
|
1542
|
+
const existing = this.clips.get(clip.id);
|
|
1543
|
+
if (existing) {
|
|
1544
|
+
existing.stored = clip.stored;
|
|
1545
|
+
existing.animId = null;
|
|
1546
|
+
existing.playerId = null;
|
|
1547
|
+
existing.instId = null;
|
|
1548
|
+
} else {
|
|
1549
|
+
this.clips.set(clip.id, {
|
|
1550
|
+
stored: clip.stored,
|
|
1551
|
+
playing: false,
|
|
1552
|
+
speed: 1,
|
|
1553
|
+
loop: "loop",
|
|
1554
|
+
weight: 1,
|
|
1555
|
+
animId: null,
|
|
1556
|
+
playerId: null,
|
|
1557
|
+
instId: null
|
|
1558
|
+
});
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1562
|
+
/**
|
|
1563
|
+
* Mark a clip playing, ensure it is loaded into the live device with a
|
|
1564
|
+
* player + instance, and issue `play` (a resume after `pause` needs it; on
|
|
1565
|
+
* a fresh player it is a no-op). Idempotent. Resolves once the calls have
|
|
1566
|
+
* dispatched (or immediately when there is no live device yet — the
|
|
1567
|
+
* provider registers the source, which boots the device and replays via
|
|
1568
|
+
* `replayInto`).
|
|
1569
|
+
*/
|
|
1570
|
+
async play(clipId) {
|
|
1571
|
+
const entry = this.clips.get(clipId);
|
|
1572
|
+
if (!entry) {
|
|
1573
|
+
return;
|
|
1574
|
+
}
|
|
1575
|
+
entry.playing = true;
|
|
1576
|
+
const device = this.getDevice();
|
|
1577
|
+
if (!device) {
|
|
1578
|
+
return;
|
|
1579
|
+
}
|
|
1580
|
+
await this.ensureLoaded(device, entry);
|
|
1581
|
+
if (entry.playerId !== null) {
|
|
1582
|
+
await device.call(playCall(entry.playerId));
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
/** Hold the clip's playhead (`pause` on its player). */
|
|
1586
|
+
pause(clipId) {
|
|
1587
|
+
const entry = this.clips.get(clipId);
|
|
1588
|
+
if (!entry) {
|
|
1589
|
+
return;
|
|
1590
|
+
}
|
|
1591
|
+
entry.playing = false;
|
|
1592
|
+
this.dispatch(
|
|
1593
|
+
(device) => entry.playerId !== null ? device.call(pauseCall(entry.playerId)) : null
|
|
1594
|
+
);
|
|
1595
|
+
}
|
|
1596
|
+
/**
|
|
1597
|
+
* Stop a clip: not playing, playhead reset to the window start (`stop` on
|
|
1598
|
+
* its player — the next tick emits the clip's t=0 pose). The clip stays
|
|
1599
|
+
* loaded for a cheap replay.
|
|
1600
|
+
*/
|
|
1601
|
+
stop(clipId) {
|
|
1602
|
+
const entry = this.clips.get(clipId);
|
|
1603
|
+
if (!entry) {
|
|
1604
|
+
return;
|
|
1605
|
+
}
|
|
1606
|
+
entry.playing = false;
|
|
1607
|
+
this.dispatch(
|
|
1608
|
+
(device) => entry.playerId !== null ? device.call(stopCall(entry.playerId)) : null
|
|
1609
|
+
);
|
|
1610
|
+
}
|
|
1611
|
+
/** Move the clip's playhead to `seconds`. */
|
|
1612
|
+
seek(clipId, seconds) {
|
|
1613
|
+
const entry = this.clips.get(clipId);
|
|
1614
|
+
if (!entry) {
|
|
1615
|
+
return;
|
|
1616
|
+
}
|
|
1617
|
+
this.dispatch(
|
|
1618
|
+
(device) => entry.playerId !== null ? device.call(seekCall(entry.playerId, seconds * 1e9)) : null
|
|
1619
|
+
);
|
|
1620
|
+
}
|
|
1621
|
+
/** Set the clip's playback speed multiplier. */
|
|
1622
|
+
setSpeed(clipId, speed) {
|
|
1623
|
+
const entry = this.clips.get(clipId);
|
|
1624
|
+
if (!entry) {
|
|
1625
|
+
return;
|
|
1626
|
+
}
|
|
1627
|
+
entry.speed = speed;
|
|
1628
|
+
this.dispatch(
|
|
1629
|
+
(device) => entry.playerId !== null ? device.call(setSpeedCall(entry.playerId, speed)) : null
|
|
1630
|
+
);
|
|
1631
|
+
}
|
|
1632
|
+
/** Set how the clip's player time maps into clip time. */
|
|
1633
|
+
setLoop(clipId, mode) {
|
|
1634
|
+
const entry = this.clips.get(clipId);
|
|
1635
|
+
if (!entry) {
|
|
1636
|
+
return;
|
|
1637
|
+
}
|
|
1638
|
+
entry.loop = mode;
|
|
1639
|
+
this.dispatch(
|
|
1640
|
+
(device) => entry.playerId !== null ? device.call(setLoopCall(entry.playerId, mode)) : null
|
|
1641
|
+
);
|
|
1642
|
+
}
|
|
1643
|
+
/** Set the clip instance's blend weight. */
|
|
1644
|
+
setWeight(clipId, weight) {
|
|
1645
|
+
const entry = this.clips.get(clipId);
|
|
1646
|
+
if (!entry) {
|
|
1647
|
+
return;
|
|
1648
|
+
}
|
|
1649
|
+
entry.weight = weight;
|
|
1650
|
+
this.dispatch(
|
|
1651
|
+
(device) => entry.playerId !== null && entry.instId !== null ? device.call(setWeightCall(entry.playerId, entry.instId, weight)) : null
|
|
1652
|
+
);
|
|
1653
|
+
}
|
|
1654
|
+
/**
|
|
1655
|
+
* Re-issue setup and transport for every playing clip against a freshly
|
|
1656
|
+
* started device (module guest state did not survive the rebuild). Voids
|
|
1657
|
+
* stale ids first. Called from `DeviceSlot.onDeviceStarted`.
|
|
1658
|
+
*/
|
|
1659
|
+
replayInto(device) {
|
|
1660
|
+
for (const entry of this.clips.values()) {
|
|
1661
|
+
entry.animId = null;
|
|
1662
|
+
entry.playerId = null;
|
|
1663
|
+
entry.instId = null;
|
|
1664
|
+
if (entry.playing) {
|
|
1665
|
+
void this.ensureLoaded(device, entry);
|
|
1666
|
+
}
|
|
1667
|
+
}
|
|
1668
|
+
}
|
|
1669
|
+
/** Fire-and-forget a transport call against the live device, if any. */
|
|
1670
|
+
dispatch(issue) {
|
|
1671
|
+
const device = this.getDevice();
|
|
1672
|
+
if (!device) {
|
|
1673
|
+
return;
|
|
1674
|
+
}
|
|
1675
|
+
void issue(device)?.catch(() => {
|
|
1676
|
+
});
|
|
1677
|
+
}
|
|
1678
|
+
async ensureLoaded(device, entry) {
|
|
1679
|
+
if (entry.instId !== null) {
|
|
1680
|
+
return;
|
|
1681
|
+
}
|
|
1682
|
+
if (entry.animId === null) {
|
|
1683
|
+
const anim = await device.call(
|
|
1684
|
+
loadAnimationCall(
|
|
1685
|
+
storedClipToModuleValue(entry.stored, this.resolveKeys)
|
|
1686
|
+
)
|
|
1687
|
+
);
|
|
1688
|
+
entry.animId = callResultU32(anim);
|
|
1689
|
+
}
|
|
1690
|
+
if (entry.playerId === null) {
|
|
1691
|
+
const player = await device.call(createPlayerCall(""));
|
|
1692
|
+
entry.playerId = callResultU32(player);
|
|
1693
|
+
}
|
|
1694
|
+
if (entry.instId === null && entry.animId !== null && entry.playerId !== null) {
|
|
1695
|
+
const inst = await device.call(
|
|
1696
|
+
addInstanceCall(entry.playerId, entry.animId)
|
|
1697
|
+
);
|
|
1698
|
+
entry.instId = callResultU32(inst);
|
|
1699
|
+
}
|
|
1700
|
+
const followups = [];
|
|
1701
|
+
if (entry.playerId !== null) {
|
|
1702
|
+
if (entry.speed !== 1) {
|
|
1703
|
+
followups.push(setSpeedCall(entry.playerId, entry.speed));
|
|
1704
|
+
}
|
|
1705
|
+
if (entry.loop !== "loop") {
|
|
1706
|
+
followups.push(setLoopCall(entry.playerId, entry.loop));
|
|
1707
|
+
}
|
|
1708
|
+
if (entry.instId !== null && entry.weight !== 1) {
|
|
1709
|
+
followups.push(
|
|
1710
|
+
setWeightCall(entry.playerId, entry.instId, entry.weight)
|
|
1711
|
+
);
|
|
1712
|
+
}
|
|
1713
|
+
}
|
|
1714
|
+
for (const call of followups) {
|
|
1715
|
+
await device.call(call);
|
|
1716
|
+
}
|
|
1717
|
+
}
|
|
1718
|
+
};
|
|
1719
|
+
|
|
992
1720
|
// src/VizijRuntimeProvider.tsx
|
|
993
1721
|
import { jsx } from "react/jsx-runtime";
|
|
994
1722
|
var ACTIVE_GRACE_MS = 250;
|
|
995
1723
|
var VISIBLE_IDLE_FPS = 30;
|
|
996
1724
|
var HIDDEN_IDLE_FPS = 1;
|
|
1725
|
+
var IMPLICIT_PAUSE_GAP_S = 5;
|
|
1726
|
+
var inferImplicitPause = (dt) => {
|
|
1727
|
+
if (dt <= IMPLICIT_PAUSE_GAP_S) {
|
|
1728
|
+
return dt;
|
|
1729
|
+
}
|
|
1730
|
+
console.warn(
|
|
1731
|
+
`[vizij-runtime] step loop woke after a ${dt.toFixed(
|
|
1732
|
+
1
|
|
1733
|
+
)}s wall-clock gap; treating it as a suspension (dt = 0)`
|
|
1734
|
+
);
|
|
1735
|
+
return 0;
|
|
1736
|
+
};
|
|
997
1737
|
var DEFAULT_MERGE = {
|
|
998
1738
|
outputs: "add",
|
|
999
1739
|
intermediate: "add"
|
|
1000
1740
|
};
|
|
1001
1741
|
var DEFAULT_DURATION = 0.35;
|
|
1002
1742
|
var POSE_CONTROL_BRIDGE_EPSILON = 1e-6;
|
|
1743
|
+
var runtimeDebugInstanceSequence = 0;
|
|
1003
1744
|
var DEV_MODE = (() => {
|
|
1004
1745
|
const nodeEnv = globalThis.process?.env?.NODE_ENV;
|
|
1005
1746
|
return typeof nodeEnv === "string" && nodeEnv === "development";
|
|
@@ -1490,12 +2231,15 @@ function pickExtractedAnimations(asset) {
|
|
|
1490
2231
|
}
|
|
1491
2232
|
return animations;
|
|
1492
2233
|
}
|
|
1493
|
-
function mergeAnimationLists(explicit, fromBundle) {
|
|
1494
|
-
if (!
|
|
2234
|
+
function mergeAnimationLists(explicit, hasExplicitOverride, fromBundle) {
|
|
2235
|
+
if (!hasExplicitOverride) {
|
|
2236
|
+
return fromBundle.length > 0 ? fromBundle : void 0;
|
|
2237
|
+
}
|
|
2238
|
+
if (!Array.isArray(explicit)) {
|
|
1495
2239
|
return void 0;
|
|
1496
2240
|
}
|
|
1497
|
-
if (
|
|
1498
|
-
return
|
|
2241
|
+
if (explicit.length === 0) {
|
|
2242
|
+
return [];
|
|
1499
2243
|
}
|
|
1500
2244
|
if (fromBundle.length === 0) {
|
|
1501
2245
|
return explicit;
|
|
@@ -1546,13 +2290,68 @@ function convertBundlePrograms(entries) {
|
|
|
1546
2290
|
};
|
|
1547
2291
|
}).filter(Boolean);
|
|
1548
2292
|
}
|
|
1549
|
-
function
|
|
1550
|
-
|
|
2293
|
+
function resolveConstraintDefault(path, namespace, inputConstraints) {
|
|
2294
|
+
const trimmed = path.trim();
|
|
2295
|
+
if (!trimmed) {
|
|
1551
2296
|
return void 0;
|
|
1552
2297
|
}
|
|
1553
|
-
|
|
2298
|
+
const stripped = stripRigFacePrefix(trimmed);
|
|
2299
|
+
const relativePath = stripped ? `/${stripped}` : "";
|
|
2300
|
+
const candidates = [
|
|
2301
|
+
namespaceTypedPath(trimmed, namespace),
|
|
2302
|
+
trimmed,
|
|
2303
|
+
stripped ? namespaceTypedPath(stripped, namespace) : void 0,
|
|
2304
|
+
stripped || void 0,
|
|
2305
|
+
relativePath || void 0
|
|
2306
|
+
];
|
|
2307
|
+
for (const candidate of candidates) {
|
|
2308
|
+
if (!candidate) {
|
|
2309
|
+
continue;
|
|
2310
|
+
}
|
|
2311
|
+
const defaultValue = inputConstraints[candidate]?.defaultValue;
|
|
2312
|
+
if (Number.isFinite(defaultValue)) {
|
|
2313
|
+
return Number(defaultValue);
|
|
2314
|
+
}
|
|
2315
|
+
}
|
|
2316
|
+
return void 0;
|
|
2317
|
+
}
|
|
2318
|
+
function deriveProgramInputSeedValues(args) {
|
|
2319
|
+
const graphSpec = resolveGraphSpec(
|
|
2320
|
+
args.program.graph,
|
|
2321
|
+
`${args.program.id ?? "program"} graph (seed defaults)`
|
|
2322
|
+
);
|
|
2323
|
+
if (!graphSpec) {
|
|
2324
|
+
return [];
|
|
2325
|
+
}
|
|
2326
|
+
return collectInputPaths(graphSpec).map((path) => path.trim()).filter((path) => path.length > 0).flatMap((path) => {
|
|
2327
|
+
const namespacedPath = namespaceTypedPath(path, args.namespace);
|
|
2328
|
+
if (args.stagedInputs.has(namespacedPath)) {
|
|
2329
|
+
return [];
|
|
2330
|
+
}
|
|
2331
|
+
if (args.getPathSnapshot(namespacedPath) !== void 0) {
|
|
2332
|
+
return [];
|
|
2333
|
+
}
|
|
2334
|
+
const defaultValue = resolveConstraintDefault(
|
|
2335
|
+
path,
|
|
2336
|
+
args.namespace,
|
|
2337
|
+
args.inputConstraints
|
|
2338
|
+
);
|
|
2339
|
+
if (!Number.isFinite(defaultValue)) {
|
|
2340
|
+
return [];
|
|
2341
|
+
}
|
|
2342
|
+
return [{ path, value: { float: Number(defaultValue) } }];
|
|
2343
|
+
});
|
|
2344
|
+
}
|
|
2345
|
+
function mergeProgramLists(explicit, hasExplicitOverride, fromBundle) {
|
|
2346
|
+
if (!hasExplicitOverride) {
|
|
1554
2347
|
return fromBundle.length > 0 ? fromBundle : void 0;
|
|
1555
2348
|
}
|
|
2349
|
+
if (!Array.isArray(explicit)) {
|
|
2350
|
+
return void 0;
|
|
2351
|
+
}
|
|
2352
|
+
if (explicit.length === 0) {
|
|
2353
|
+
return [];
|
|
2354
|
+
}
|
|
1556
2355
|
if (fromBundle.length === 0) {
|
|
1557
2356
|
return explicit;
|
|
1558
2357
|
}
|
|
@@ -1571,10 +2370,23 @@ function mergeProgramLists(explicit, fromBundle) {
|
|
|
1571
2370
|
}
|
|
1572
2371
|
function mergeAssetBundle(base, extracted, extractedAnimations) {
|
|
1573
2372
|
const resolvedBundle = base.bundle ?? extracted ?? null;
|
|
2373
|
+
const hasBaseRigOverride = Object.prototype.hasOwnProperty.call(base, "rig");
|
|
2374
|
+
const hasBasePoseOverride = Object.prototype.hasOwnProperty.call(
|
|
2375
|
+
base,
|
|
2376
|
+
"pose"
|
|
2377
|
+
);
|
|
2378
|
+
const hasBaseAnimationsOverride = Object.prototype.hasOwnProperty.call(
|
|
2379
|
+
base,
|
|
2380
|
+
"animations"
|
|
2381
|
+
);
|
|
2382
|
+
const hasBaseProgramsOverride = Object.prototype.hasOwnProperty.call(
|
|
2383
|
+
base,
|
|
2384
|
+
"programs"
|
|
2385
|
+
);
|
|
1574
2386
|
const rigFromBundle = convertBundleGraph(
|
|
1575
2387
|
pickBundleGraph(resolvedBundle, ["rig"])
|
|
1576
2388
|
);
|
|
1577
|
-
const resolvedRig = base.rig ?? rigFromBundle ?? void 0;
|
|
2389
|
+
const resolvedRig = hasBaseRigOverride ? base.rig : base.rig ?? rigFromBundle ?? void 0;
|
|
1578
2390
|
const basePose = base.pose;
|
|
1579
2391
|
const hasBasePoseGraphOverride = Boolean(
|
|
1580
2392
|
basePose && Object.prototype.hasOwnProperty.call(basePose, "graph")
|
|
@@ -1583,13 +2395,15 @@ function mergeAssetBundle(base, extracted, extractedAnimations) {
|
|
|
1583
2395
|
basePose && Object.prototype.hasOwnProperty.call(basePose, "config")
|
|
1584
2396
|
);
|
|
1585
2397
|
const poseStageFilter = basePose?.stageNeutralFilter;
|
|
1586
|
-
const poseGraphFromBundle = hasBasePoseGraphOverride ? null : convertBundleGraph(
|
|
2398
|
+
const poseGraphFromBundle = hasBasePoseOverride && !basePose ? null : hasBasePoseGraphOverride ? null : convertBundleGraph(
|
|
1587
2399
|
pickBundleGraph(resolvedBundle, ["pose-driver", "pose"])
|
|
1588
2400
|
);
|
|
1589
2401
|
const resolvedPoseGraph = hasBasePoseGraphOverride ? basePose?.graph : basePose?.graph ?? poseGraphFromBundle ?? void 0;
|
|
1590
2402
|
const resolvedPoseConfig = hasBasePoseConfigOverride ? basePose?.config : basePose?.config ?? resolvedBundle?.poses?.config ?? void 0;
|
|
1591
2403
|
let resolvedPose = basePose;
|
|
1592
|
-
if (basePose) {
|
|
2404
|
+
if (hasBasePoseOverride && !basePose) {
|
|
2405
|
+
resolvedPose = void 0;
|
|
2406
|
+
} else if (basePose) {
|
|
1593
2407
|
const nextPose = { ...basePose };
|
|
1594
2408
|
let changed = false;
|
|
1595
2409
|
if (resolvedPoseGraph && basePose.graph !== resolvedPoseGraph) {
|
|
@@ -1617,17 +2431,20 @@ function mergeAssetBundle(base, extracted, extractedAnimations) {
|
|
|
1617
2431
|
);
|
|
1618
2432
|
let resolvedAnimations = mergeAnimationLists(
|
|
1619
2433
|
base.animations,
|
|
2434
|
+
hasBaseAnimationsOverride,
|
|
1620
2435
|
animationsFromBundle
|
|
1621
2436
|
);
|
|
1622
2437
|
const animationsFromAsset = extractedAnimations && extractedAnimations.length > 0 ? extractedAnimations : [];
|
|
1623
2438
|
if (animationsFromAsset.length > 0) {
|
|
1624
2439
|
resolvedAnimations = mergeAnimationLists(
|
|
1625
2440
|
resolvedAnimations,
|
|
2441
|
+
true,
|
|
1626
2442
|
animationsFromAsset
|
|
1627
2443
|
);
|
|
1628
2444
|
}
|
|
1629
2445
|
const programsFromBundle = mergeProgramLists(
|
|
1630
2446
|
base.programs,
|
|
2447
|
+
hasBaseProgramsOverride,
|
|
1631
2448
|
convertBundlePrograms(resolvedBundle?.graphs)
|
|
1632
2449
|
);
|
|
1633
2450
|
const merged = {
|
|
@@ -1741,28 +2558,18 @@ function VizijRuntimeProvider({
|
|
|
1741
2558
|
faceId: faceIdProp,
|
|
1742
2559
|
updateTier = "auto",
|
|
1743
2560
|
autoCreate = true,
|
|
1744
|
-
createOptions,
|
|
1745
2561
|
autostart = false,
|
|
1746
|
-
|
|
2562
|
+
driveRuntime = true,
|
|
1747
2563
|
mergeStrategy,
|
|
1748
2564
|
onRegisterControllers,
|
|
1749
2565
|
onStatusChange,
|
|
1750
|
-
transformOutputWrite
|
|
1751
|
-
orchestratorScope = "auto"
|
|
2566
|
+
transformOutputWrite
|
|
1752
2567
|
}) {
|
|
1753
2568
|
const storeRef = useRef(null);
|
|
1754
2569
|
if (!storeRef.current) {
|
|
1755
2570
|
storeRef.current = createVizijStore();
|
|
1756
2571
|
}
|
|
1757
|
-
|
|
1758
|
-
const hasParentOrchestrator = Boolean(parentOrchestrator);
|
|
1759
|
-
const shouldProvideOrchestrator = orchestratorScope === "isolated" || !hasParentOrchestrator && orchestratorScope !== "shared";
|
|
1760
|
-
if (orchestratorScope === "shared" && !hasParentOrchestrator) {
|
|
1761
|
-
console.warn(
|
|
1762
|
-
'[vizij-runtime] orchestratorScope="shared" requires an OrchestratorProvider higher in the tree; falling back to an isolated provider.'
|
|
1763
|
-
);
|
|
1764
|
-
}
|
|
1765
|
-
const runtimeTree = /* @__PURE__ */ jsx(VizijContext.Provider, { value: storeRef.current, children: /* @__PURE__ */ jsx(
|
|
2572
|
+
return /* @__PURE__ */ jsx(VizijContext.Provider, { value: storeRef.current, children: /* @__PURE__ */ jsx(
|
|
1766
2573
|
VizijRuntimeProviderInner,
|
|
1767
2574
|
{
|
|
1768
2575
|
assetBundle,
|
|
@@ -1771,8 +2578,7 @@ function VizijRuntimeProvider({
|
|
|
1771
2578
|
updateTier,
|
|
1772
2579
|
autoCreate,
|
|
1773
2580
|
autostart,
|
|
1774
|
-
|
|
1775
|
-
createOptions,
|
|
2581
|
+
driveRuntime,
|
|
1776
2582
|
mergeStrategy,
|
|
1777
2583
|
onRegisterControllers,
|
|
1778
2584
|
onStatusChange,
|
|
@@ -1781,18 +2587,6 @@ function VizijRuntimeProvider({
|
|
|
1781
2587
|
children
|
|
1782
2588
|
}
|
|
1783
2589
|
) });
|
|
1784
|
-
if (!shouldProvideOrchestrator) {
|
|
1785
|
-
return runtimeTree;
|
|
1786
|
-
}
|
|
1787
|
-
return /* @__PURE__ */ jsx(
|
|
1788
|
-
OrchestratorProvider,
|
|
1789
|
-
{
|
|
1790
|
-
autoCreate,
|
|
1791
|
-
createOptions,
|
|
1792
|
-
autostart: false,
|
|
1793
|
-
children: runtimeTree
|
|
1794
|
-
}
|
|
1795
|
-
);
|
|
1796
2590
|
}
|
|
1797
2591
|
function VizijRuntimeProviderInner({
|
|
1798
2592
|
assetBundle: initialAssetBundle,
|
|
@@ -1807,8 +2601,7 @@ function VizijRuntimeProviderInner({
|
|
|
1807
2601
|
children,
|
|
1808
2602
|
autoCreate,
|
|
1809
2603
|
autostart,
|
|
1810
|
-
|
|
1811
|
-
driveOrchestrator
|
|
2604
|
+
driveRuntime
|
|
1812
2605
|
}) {
|
|
1813
2606
|
const [assetBundleOverride, setAssetBundleOverride] = useState(null);
|
|
1814
2607
|
const [graphUpdateToken, setGraphUpdateToken] = useState(0);
|
|
@@ -1870,23 +2663,17 @@ function VizijRuntimeProviderInner({
|
|
|
1870
2663
|
setGraphUpdateToken((prev) => prev + 1);
|
|
1871
2664
|
}
|
|
1872
2665
|
}, [effectiveAssetBundle]);
|
|
1873
|
-
const
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
removeAnimation,
|
|
1881
|
-
removeInput,
|
|
1882
|
-
listControllers,
|
|
1883
|
-
setInput: orchestratorSetInput,
|
|
1884
|
-
getPathSnapshot,
|
|
1885
|
-
step: stepRuntime
|
|
1886
|
-
} = useOrchestrator();
|
|
1887
|
-
const frame = useOrchFrame();
|
|
2666
|
+
const deviceSlotRef = useRef(null);
|
|
2667
|
+
if (!deviceSlotRef.current) {
|
|
2668
|
+
deviceSlotRef.current = new DeviceSlot();
|
|
2669
|
+
}
|
|
2670
|
+
const deviceSlot = deviceSlotRef.current;
|
|
2671
|
+
const [ready, setReady] = useState(false);
|
|
2672
|
+
const graphSourcesRef = useRef([]);
|
|
1888
2673
|
const namespace = namespaceProp ?? assetBundle.namespace ?? "default";
|
|
1889
2674
|
const faceId = faceIdProp ?? assetBundle.faceId ?? assetBundle.pose?.config?.faceId ?? assetBundle.pose?.config?.faceId ?? void 0;
|
|
2675
|
+
const faceIdRef = useRef(faceId);
|
|
2676
|
+
faceIdRef.current = faceId;
|
|
1890
2677
|
const [status, setStatus] = useState({
|
|
1891
2678
|
loading: true,
|
|
1892
2679
|
ready: false,
|
|
@@ -1899,12 +2686,15 @@ function VizijRuntimeProviderInner({
|
|
|
1899
2686
|
stepHz: void 0,
|
|
1900
2687
|
controllers: { graphs: [], anims: [] }
|
|
1901
2688
|
});
|
|
2689
|
+
const runtimeDebugInstanceIdRef = useRef(
|
|
2690
|
+
`vizij-runtime:${runtimeDebugInstanceSequence++}`
|
|
2691
|
+
);
|
|
1902
2692
|
const errorsRef = useRef([]);
|
|
1903
2693
|
const outputPathsRef = useRef(/* @__PURE__ */ new Set());
|
|
1904
2694
|
const baseOutputPathsRef = useRef(/* @__PURE__ */ new Set());
|
|
1905
2695
|
const namespacedOutputPathsRef = useRef(/* @__PURE__ */ new Set());
|
|
1906
2696
|
const namespaceRef = useRef(namespace);
|
|
1907
|
-
const
|
|
2697
|
+
const driveRuntimeRef = useRef(driveRuntime);
|
|
1908
2698
|
const rigInputMapRef = useRef({});
|
|
1909
2699
|
const rigPoseControlInputIdsRef = useRef(/* @__PURE__ */ new Set());
|
|
1910
2700
|
const registeredGraphsRef = useRef([]);
|
|
@@ -1946,16 +2736,17 @@ function VizijRuntimeProviderInner({
|
|
|
1946
2736
|
const [inputConstraints, setInputConstraints] = useState({});
|
|
1947
2737
|
const inputConstraintsRef = useRef({});
|
|
1948
2738
|
const avgStepDtRef = useRef(null);
|
|
2739
|
+
const inputDriverIdsRef = useRef(/* @__PURE__ */ new Set());
|
|
1949
2740
|
const animationTweensRef = useRef(/* @__PURE__ */ new Map());
|
|
1950
2741
|
const clipPlaybackRef = useRef(/* @__PURE__ */ new Map());
|
|
1951
2742
|
const programPlaybackRef = useRef(
|
|
1952
2743
|
/* @__PURE__ */ new Map()
|
|
1953
2744
|
);
|
|
1954
2745
|
const programControllerIdsRef = useRef(/* @__PURE__ */ new Map());
|
|
1955
|
-
const
|
|
1956
|
-
|
|
1957
|
-
);
|
|
1958
|
-
const
|
|
2746
|
+
const animationModuleRef = useRef(null);
|
|
2747
|
+
const animationHostRef = useRef(null);
|
|
2748
|
+
const animationsSourceRegisteredRef = useRef(false);
|
|
2749
|
+
const animationGapWarnedRef = useRef(/* @__PURE__ */ new Set());
|
|
1959
2750
|
const animationSystemActiveRef = useRef(true);
|
|
1960
2751
|
const stagedInputsRef = useRef(/* @__PURE__ */ new Map());
|
|
1961
2752
|
const autostartRef = useRef(autostart);
|
|
@@ -1971,6 +2762,58 @@ function VizijRuntimeProviderInner({
|
|
|
1971
2762
|
runtimeMountedRef.current = false;
|
|
1972
2763
|
};
|
|
1973
2764
|
}, []);
|
|
2765
|
+
const publishRuntimeDebugState = useCallback(() => {
|
|
2766
|
+
const storeState = store.getState();
|
|
2767
|
+
setRuntimeDebugState(runtimeDebugInstanceIdRef.current, {
|
|
2768
|
+
namespace,
|
|
2769
|
+
faceId: faceId ?? null,
|
|
2770
|
+
rootId: status.rootId,
|
|
2771
|
+
ready: status.ready,
|
|
2772
|
+
loading: status.loading,
|
|
2773
|
+
autostart,
|
|
2774
|
+
driveRuntime,
|
|
2775
|
+
loopMode,
|
|
2776
|
+
outputCount: status.outputPaths.length,
|
|
2777
|
+
graphControllerCount: status.controllers.graphs.length,
|
|
2778
|
+
animationControllerCount: status.controllers.anims.length,
|
|
2779
|
+
registeredGraphCount: registeredGraphsRef.current.length,
|
|
2780
|
+
registeredAnimationCount: registeredAnimationsRef.current.length,
|
|
2781
|
+
programControllerCount: programControllerIdsRef.current.size,
|
|
2782
|
+
animationTweenCount: animationTweensRef.current.size,
|
|
2783
|
+
clipPlaybackCount: clipPlaybackRef.current.size,
|
|
2784
|
+
programPlaybackCount: programPlaybackRef.current.size,
|
|
2785
|
+
stagedInputCount: stagedInputsRef.current.size,
|
|
2786
|
+
activeDriverCount: inputDriverIdsRef.current.size,
|
|
2787
|
+
worldEntryCount: Object.keys(storeState.world ?? {}).length,
|
|
2788
|
+
animatableCount: Object.keys(storeState.animatables ?? {}).length,
|
|
2789
|
+
valuesSize: storeState.values instanceof Map ? storeState.values.size : null,
|
|
2790
|
+
stepHz: status.stepHz ?? null
|
|
2791
|
+
});
|
|
2792
|
+
}, [
|
|
2793
|
+
autostart,
|
|
2794
|
+
driveRuntime,
|
|
2795
|
+
faceId,
|
|
2796
|
+
loopMode,
|
|
2797
|
+
namespace,
|
|
2798
|
+
status.controllers.anims.length,
|
|
2799
|
+
status.controllers.graphs.length,
|
|
2800
|
+
status.loading,
|
|
2801
|
+
status.outputPaths.length,
|
|
2802
|
+
status.ready,
|
|
2803
|
+
status.rootId,
|
|
2804
|
+
status.stepHz,
|
|
2805
|
+
store
|
|
2806
|
+
]);
|
|
2807
|
+
useEffect(() => {
|
|
2808
|
+
publishRuntimeDebugState();
|
|
2809
|
+
const unsubscribe = store.subscribe(() => {
|
|
2810
|
+
publishRuntimeDebugState();
|
|
2811
|
+
});
|
|
2812
|
+
return () => {
|
|
2813
|
+
unsubscribe();
|
|
2814
|
+
clearRuntimeDebugState(runtimeDebugInstanceIdRef.current);
|
|
2815
|
+
};
|
|
2816
|
+
}, [publishRuntimeDebugState, store]);
|
|
1974
2817
|
useEffect(() => {
|
|
1975
2818
|
const rigAsset = assetBundle.rig;
|
|
1976
2819
|
if (!rigAsset) {
|
|
@@ -2066,59 +2909,289 @@ function VizijRuntimeProviderInner({
|
|
|
2066
2909
|
});
|
|
2067
2910
|
return;
|
|
2068
2911
|
}
|
|
2069
|
-
markActivity();
|
|
2070
|
-
const namespacedPath = namespaceTypedPath(path, namespaceRef.current);
|
|
2071
|
-
if (isRuntimeDebugEnabled() && (namespacedPath.includes("animation/authoring.timeline.main") || namespacedPath.endsWith("/blink"))) {
|
|
2072
|
-
console.log("[vizij-runtime] stage input", {
|
|
2073
|
-
path,
|
|
2074
|
-
namespacedPath,
|
|
2075
|
-
value
|
|
2076
|
-
});
|
|
2912
|
+
markActivity();
|
|
2913
|
+
const namespacedPath = namespaceTypedPath(path, namespaceRef.current);
|
|
2914
|
+
if (isRuntimeDebugEnabled() && (namespacedPath.includes("animation/authoring.timeline.main") || namespacedPath.endsWith("/blink"))) {
|
|
2915
|
+
console.log("[vizij-runtime] stage input", {
|
|
2916
|
+
path,
|
|
2917
|
+
namespacedPath,
|
|
2918
|
+
value
|
|
2919
|
+
});
|
|
2920
|
+
}
|
|
2921
|
+
stagedInputsRef.current.set(namespacedPath, { value, shape });
|
|
2922
|
+
},
|
|
2923
|
+
[
|
|
2924
|
+
assetBundle.pose?.config?.faceId,
|
|
2925
|
+
faceId,
|
|
2926
|
+
markActivity,
|
|
2927
|
+
poseWeightFallbackMap,
|
|
2928
|
+
useLegacyPoseWeightFallback
|
|
2929
|
+
]
|
|
2930
|
+
);
|
|
2931
|
+
const reportStatus = useCallback(
|
|
2932
|
+
(updater) => {
|
|
2933
|
+
setStatus((prev) => {
|
|
2934
|
+
const next = updater(prev);
|
|
2935
|
+
onStatusChange?.(next);
|
|
2936
|
+
return next;
|
|
2937
|
+
});
|
|
2938
|
+
},
|
|
2939
|
+
[onStatusChange]
|
|
2940
|
+
);
|
|
2941
|
+
const pushError = useCallback(
|
|
2942
|
+
(error) => {
|
|
2943
|
+
errorsRef.current = [...errorsRef.current, error];
|
|
2944
|
+
reportStatus((prev) => ({
|
|
2945
|
+
...prev,
|
|
2946
|
+
error,
|
|
2947
|
+
errors: errorsRef.current
|
|
2948
|
+
}));
|
|
2949
|
+
console.warn("[vizij-runtime]", error.message, error.cause);
|
|
2950
|
+
},
|
|
2951
|
+
[reportStatus]
|
|
2952
|
+
);
|
|
2953
|
+
const resetErrors = useCallback(() => {
|
|
2954
|
+
errorsRef.current = [];
|
|
2955
|
+
reportStatus((prev) => ({
|
|
2956
|
+
...prev,
|
|
2957
|
+
error: null,
|
|
2958
|
+
errors: []
|
|
2959
|
+
}));
|
|
2960
|
+
}, [reportStatus]);
|
|
2961
|
+
useEffect(() => {
|
|
2962
|
+
autostartRef.current = autostart;
|
|
2963
|
+
updateLoopMode();
|
|
2964
|
+
}, [autostart, updateLoopMode]);
|
|
2965
|
+
const pendingWritesRef = useRef(/* @__PURE__ */ new Map());
|
|
2966
|
+
const getDevice = useCallback(
|
|
2967
|
+
() => deviceSlot.current?.device ?? null,
|
|
2968
|
+
[deviceSlot]
|
|
2969
|
+
);
|
|
2970
|
+
const ensureAnimationHost = useCallback(() => {
|
|
2971
|
+
if (!animationModuleRef.current) {
|
|
2972
|
+
return null;
|
|
2973
|
+
}
|
|
2974
|
+
if (!animationHostRef.current) {
|
|
2975
|
+
animationHostRef.current = new AnimationModuleHost(
|
|
2976
|
+
getDevice,
|
|
2977
|
+
(key) => resolveAnimationBridgeOutputPaths(
|
|
2978
|
+
key,
|
|
2979
|
+
faceIdRef.current ?? void 0,
|
|
2980
|
+
rigInputMapRef.current
|
|
2981
|
+
)
|
|
2982
|
+
);
|
|
2983
|
+
}
|
|
2984
|
+
return animationHostRef.current;
|
|
2985
|
+
}, [getDevice]);
|
|
2986
|
+
const applyEngineChangesRef = useRef(() => {
|
|
2987
|
+
});
|
|
2988
|
+
const recomposeDevice = useCallback(() => {
|
|
2989
|
+
const spec = composeGraphSpecs(graphSourcesRef.current);
|
|
2990
|
+
deviceSlot.recompose(spec).then((handle) => {
|
|
2991
|
+
if (pendingWritesRef.current.size > 0) {
|
|
2992
|
+
handle.device.writeValues(
|
|
2993
|
+
Object.fromEntries(pendingWritesRef.current)
|
|
2994
|
+
);
|
|
2995
|
+
pendingWritesRef.current.clear();
|
|
2996
|
+
}
|
|
2997
|
+
}).catch((err) => {
|
|
2998
|
+
pushError({
|
|
2999
|
+
message: "Failed to (re)compose the arora device",
|
|
3000
|
+
cause: err,
|
|
3001
|
+
phase: "engine",
|
|
3002
|
+
timestamp: performance.now()
|
|
3003
|
+
});
|
|
3004
|
+
});
|
|
3005
|
+
}, [deviceSlot, pushError]);
|
|
3006
|
+
const initEngine = useCallback(async () => {
|
|
3007
|
+
await ensureWasmInit();
|
|
3008
|
+
setReady(true);
|
|
3009
|
+
}, []);
|
|
3010
|
+
const hasBundleAnimations = (assetBundle.animations?.length ?? 0) > 0;
|
|
3011
|
+
useEffect(() => {
|
|
3012
|
+
if (!hasBundleAnimations || animationModuleRef.current) {
|
|
3013
|
+
return;
|
|
3014
|
+
}
|
|
3015
|
+
let cancelled = false;
|
|
3016
|
+
const loading = loadAnimationModule().then((module) => {
|
|
3017
|
+
if (cancelled) {
|
|
3018
|
+
return;
|
|
3019
|
+
}
|
|
3020
|
+
animationModuleRef.current = module;
|
|
3021
|
+
deviceSlot.setModules([module]);
|
|
3022
|
+
deviceSlot.onDeviceStarted = (handle) => {
|
|
3023
|
+
animationHostRef.current?.replayInto(handle.device);
|
|
3024
|
+
};
|
|
3025
|
+
ensureAnimationHost();
|
|
3026
|
+
}).catch((err) => {
|
|
3027
|
+
pushError({
|
|
3028
|
+
message: "Failed to load the animation module",
|
|
3029
|
+
cause: err,
|
|
3030
|
+
phase: "engine",
|
|
3031
|
+
timestamp: performance.now()
|
|
3032
|
+
});
|
|
3033
|
+
});
|
|
3034
|
+
deviceSlot.waitForModules(loading);
|
|
3035
|
+
return () => {
|
|
3036
|
+
cancelled = true;
|
|
3037
|
+
};
|
|
3038
|
+
}, [deviceSlot, ensureAnimationHost, hasBundleAnimations, pushError]);
|
|
3039
|
+
const removeGraph = useCallback(
|
|
3040
|
+
(id) => {
|
|
3041
|
+
graphSourcesRef.current = graphSourcesRef.current.filter(
|
|
3042
|
+
(s) => s.sourceId !== id && !s.sourceId.startsWith(`${id}#`)
|
|
3043
|
+
);
|
|
3044
|
+
recomposeDevice();
|
|
3045
|
+
},
|
|
3046
|
+
[recomposeDevice]
|
|
3047
|
+
);
|
|
3048
|
+
const registerGraph = useCallback(
|
|
3049
|
+
(cfg) => {
|
|
3050
|
+
const id = cfg.id ?? `graph-${graphSourcesRef.current.length}`;
|
|
3051
|
+
graphSourcesRef.current = [
|
|
3052
|
+
...graphSourcesRef.current.filter((s) => s.sourceId !== id),
|
|
3053
|
+
{ sourceId: id, spec: cfg.spec ?? {} }
|
|
3054
|
+
];
|
|
3055
|
+
recomposeDevice();
|
|
3056
|
+
return id;
|
|
3057
|
+
},
|
|
3058
|
+
[recomposeDevice]
|
|
3059
|
+
);
|
|
3060
|
+
const registerMergedGraph = useCallback(
|
|
3061
|
+
(cfg) => {
|
|
3062
|
+
const id = cfg.id ?? `merged-${graphSourcesRef.current.length}`;
|
|
3063
|
+
const members = cfg.graphs.map((graph, index) => ({
|
|
3064
|
+
sourceId: `${id}#${graph.id ?? index}`,
|
|
3065
|
+
spec: graph.spec ?? {}
|
|
3066
|
+
}));
|
|
3067
|
+
graphSourcesRef.current = [
|
|
3068
|
+
...graphSourcesRef.current.filter(
|
|
3069
|
+
(s) => s.sourceId !== id && !s.sourceId.startsWith(`${id}#`)
|
|
3070
|
+
),
|
|
3071
|
+
...members
|
|
3072
|
+
];
|
|
3073
|
+
recomposeDevice();
|
|
3074
|
+
return id;
|
|
3075
|
+
},
|
|
3076
|
+
[recomposeDevice]
|
|
3077
|
+
);
|
|
3078
|
+
const registerAnimation = useCallback(
|
|
3079
|
+
(cfg) => cfg.id ?? `animation-${registeredAnimationsRef.current.length}`,
|
|
3080
|
+
[]
|
|
3081
|
+
);
|
|
3082
|
+
const removeAnimation = useCallback((_id) => {
|
|
3083
|
+
}, []);
|
|
3084
|
+
const setAnimationsSourceRegistered = useCallback(
|
|
3085
|
+
(registered) => {
|
|
3086
|
+
if (registered === animationsSourceRegisteredRef.current) {
|
|
3087
|
+
return;
|
|
3088
|
+
}
|
|
3089
|
+
animationsSourceRegisteredRef.current = registered;
|
|
3090
|
+
if (registered) {
|
|
3091
|
+
graphSourcesRef.current = [
|
|
3092
|
+
...graphSourcesRef.current.filter(
|
|
3093
|
+
(s) => s.sourceId !== animationsGraphSource().sourceId
|
|
3094
|
+
),
|
|
3095
|
+
animationsGraphSource()
|
|
3096
|
+
];
|
|
3097
|
+
} else {
|
|
3098
|
+
graphSourcesRef.current = graphSourcesRef.current.filter(
|
|
3099
|
+
(s) => s.sourceId !== animationsGraphSource().sourceId
|
|
3100
|
+
);
|
|
3101
|
+
}
|
|
3102
|
+
recomposeDevice();
|
|
3103
|
+
},
|
|
3104
|
+
[recomposeDevice]
|
|
3105
|
+
);
|
|
3106
|
+
const listControllers = useCallback(
|
|
3107
|
+
() => ({
|
|
3108
|
+
graphs: [...registeredGraphsRef.current],
|
|
3109
|
+
anims: [...registeredAnimationsRef.current]
|
|
3110
|
+
}),
|
|
3111
|
+
[]
|
|
3112
|
+
);
|
|
3113
|
+
const deviceSetInput = useCallback(
|
|
3114
|
+
(path, value, _shape) => {
|
|
3115
|
+
const handle = deviceSlot.current;
|
|
3116
|
+
if (handle) {
|
|
3117
|
+
handle.device.setValue(path, value);
|
|
3118
|
+
} else {
|
|
3119
|
+
pendingWritesRef.current.set(path, value);
|
|
2077
3120
|
}
|
|
2078
|
-
stagedInputsRef.current.set(namespacedPath, { value, shape });
|
|
2079
3121
|
},
|
|
2080
|
-
[
|
|
2081
|
-
assetBundle.pose?.config?.faceId,
|
|
2082
|
-
faceId,
|
|
2083
|
-
markActivity,
|
|
2084
|
-
poseWeightFallbackMap,
|
|
2085
|
-
useLegacyPoseWeightFallback
|
|
2086
|
-
]
|
|
3122
|
+
[deviceSlot]
|
|
2087
3123
|
);
|
|
2088
|
-
const
|
|
2089
|
-
(
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
3124
|
+
const getPathSnapshot = useCallback(
|
|
3125
|
+
(path) => {
|
|
3126
|
+
const handle = deviceSlot.current;
|
|
3127
|
+
if (!handle) {
|
|
3128
|
+
return pendingWritesRef.current.get(path);
|
|
3129
|
+
}
|
|
3130
|
+
return handle.device.readValues([path])[path] ?? void 0;
|
|
3131
|
+
},
|
|
3132
|
+
[deviceSlot]
|
|
3133
|
+
);
|
|
3134
|
+
const getStoreSnapshot = useCallback(() => {
|
|
3135
|
+
const handle = deviceSlot.current;
|
|
3136
|
+
if (!handle) {
|
|
3137
|
+
return void 0;
|
|
3138
|
+
}
|
|
3139
|
+
return handle.device.snapshot();
|
|
3140
|
+
}, [deviceSlot]);
|
|
3141
|
+
const stepListenersRef = useRef(/* @__PURE__ */ new Set());
|
|
3142
|
+
const subscribeToStep = useCallback((listener) => {
|
|
3143
|
+
const listeners = stepListenersRef.current;
|
|
3144
|
+
listeners.add(listener);
|
|
3145
|
+
return () => {
|
|
3146
|
+
listeners.delete(listener);
|
|
3147
|
+
};
|
|
3148
|
+
}, []);
|
|
3149
|
+
const stepRuntime = useCallback(
|
|
3150
|
+
(dt) => {
|
|
3151
|
+
const handle = deviceSlot.current;
|
|
3152
|
+
if (!handle) {
|
|
3153
|
+
return;
|
|
3154
|
+
}
|
|
3155
|
+
if (!handle.device.running) {
|
|
3156
|
+
handle.device.step(dt * 1e3);
|
|
3157
|
+
}
|
|
3158
|
+
const changes = handle.device.drainChanges();
|
|
3159
|
+
applyEngineChangesRef.current(changes);
|
|
3160
|
+
if (storeChangeListenersRef.current.size > 0) {
|
|
3161
|
+
const paths = Object.keys(changes);
|
|
3162
|
+
if (paths.length > 0) {
|
|
3163
|
+
storeChangeListenersRef.current.forEach((listener) => {
|
|
3164
|
+
try {
|
|
3165
|
+
listener(changes);
|
|
3166
|
+
} catch (err) {
|
|
3167
|
+
console.error("[vizij-runtime] store-change listener error", err);
|
|
3168
|
+
}
|
|
3169
|
+
});
|
|
3170
|
+
}
|
|
3171
|
+
}
|
|
3172
|
+
stepListenersRef.current.forEach((listener) => {
|
|
3173
|
+
try {
|
|
3174
|
+
listener();
|
|
3175
|
+
} catch (err) {
|
|
3176
|
+
console.error("[vizij-runtime] step listener error", err);
|
|
3177
|
+
}
|
|
2094
3178
|
});
|
|
2095
3179
|
},
|
|
2096
|
-
[
|
|
3180
|
+
[deviceSlot]
|
|
2097
3181
|
);
|
|
2098
|
-
const
|
|
2099
|
-
(
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
3182
|
+
const storeChangeListenersRef = useRef(
|
|
3183
|
+
/* @__PURE__ */ new Set()
|
|
3184
|
+
);
|
|
3185
|
+
const subscribeToStoreChanges = useCallback(
|
|
3186
|
+
(listener) => {
|
|
3187
|
+
const listeners = storeChangeListenersRef.current;
|
|
3188
|
+
listeners.add(listener);
|
|
3189
|
+
return () => {
|
|
3190
|
+
listeners.delete(listener);
|
|
3191
|
+
};
|
|
2107
3192
|
},
|
|
2108
|
-
[
|
|
3193
|
+
[]
|
|
2109
3194
|
);
|
|
2110
|
-
const resetErrors = useCallback(() => {
|
|
2111
|
-
errorsRef.current = [];
|
|
2112
|
-
reportStatus((prev) => ({
|
|
2113
|
-
...prev,
|
|
2114
|
-
error: null,
|
|
2115
|
-
errors: []
|
|
2116
|
-
}));
|
|
2117
|
-
}, [reportStatus]);
|
|
2118
|
-
useEffect(() => {
|
|
2119
|
-
autostartRef.current = autostart;
|
|
2120
|
-
updateLoopMode();
|
|
2121
|
-
}, [autostart, updateLoopMode]);
|
|
2122
3195
|
const clearControllers = useCallback(() => {
|
|
2123
3196
|
const existing = listControllers();
|
|
2124
3197
|
existing.graphs.forEach((id) => {
|
|
@@ -2153,8 +3226,6 @@ function VizijRuntimeProviderInner({
|
|
|
2153
3226
|
baseOutputPathsRef.current = /* @__PURE__ */ new Set();
|
|
2154
3227
|
namespacedOutputPathsRef.current = /* @__PURE__ */ new Set();
|
|
2155
3228
|
rigPoseControlInputIdsRef.current = /* @__PURE__ */ new Set();
|
|
2156
|
-
clipOutputValuesRef.current.clear();
|
|
2157
|
-
clipAggregateValuesRef.current.clear();
|
|
2158
3229
|
}, [listControllers, removeAnimation, removeGraph, pushError]);
|
|
2159
3230
|
useEffect(() => {
|
|
2160
3231
|
namespaceRef.current = namespace;
|
|
@@ -2165,8 +3236,24 @@ function VizijRuntimeProviderInner({
|
|
|
2165
3236
|
}));
|
|
2166
3237
|
}, [namespace, faceId, reportStatus]);
|
|
2167
3238
|
useEffect(() => {
|
|
2168
|
-
|
|
2169
|
-
}, [
|
|
3239
|
+
driveRuntimeRef.current = driveRuntime;
|
|
3240
|
+
}, [driveRuntime]);
|
|
3241
|
+
useEffect(() => {
|
|
3242
|
+
deviceSlot.onRunEnded = (error) => {
|
|
3243
|
+
pushError({
|
|
3244
|
+
message: "The device's run loop ended: stepping failed",
|
|
3245
|
+
cause: error,
|
|
3246
|
+
phase: "engine",
|
|
3247
|
+
timestamp: performance.now()
|
|
3248
|
+
});
|
|
3249
|
+
};
|
|
3250
|
+
if (driveRuntime) {
|
|
3251
|
+
deviceSlot.runPeriodMs = RUN_PERIOD_MS;
|
|
3252
|
+
deviceSlot.startRun();
|
|
3253
|
+
} else {
|
|
3254
|
+
deviceSlot.runPeriodMs = null;
|
|
3255
|
+
}
|
|
3256
|
+
}, [deviceSlot, driveRuntime, pushError]);
|
|
2170
3257
|
const glbAsset = effectiveAssetBundle.glb;
|
|
2171
3258
|
const baseBundle = effectiveAssetBundle.bundle ?? null;
|
|
2172
3259
|
useEffect(() => {
|
|
@@ -2279,16 +3366,16 @@ function VizijRuntimeProviderInner({
|
|
|
2279
3366
|
]);
|
|
2280
3367
|
useEffect(() => {
|
|
2281
3368
|
if (!ready && autoCreate) {
|
|
2282
|
-
|
|
3369
|
+
initEngine().catch((err) => {
|
|
2283
3370
|
pushError({
|
|
2284
|
-
message: "Failed to
|
|
3371
|
+
message: "Failed to initialize the engine",
|
|
2285
3372
|
cause: err,
|
|
2286
|
-
phase: "
|
|
3373
|
+
phase: "engine",
|
|
2287
3374
|
timestamp: performance.now()
|
|
2288
3375
|
});
|
|
2289
3376
|
});
|
|
2290
3377
|
}
|
|
2291
|
-
}, [ready, autoCreate,
|
|
3378
|
+
}, [ready, autoCreate, initEngine, pushError]);
|
|
2292
3379
|
const registerControllers = useCallback(async () => {
|
|
2293
3380
|
clearControllers();
|
|
2294
3381
|
if (isRuntimeDebugEnabled()) {
|
|
@@ -2451,19 +3538,13 @@ function VizijRuntimeProviderInner({
|
|
|
2451
3538
|
console.log("[vizij-runtime] registered graph ids", graphIds);
|
|
2452
3539
|
}
|
|
2453
3540
|
const animationIds = [];
|
|
3541
|
+
const hostClips = [];
|
|
2454
3542
|
for (const anim of assetBundle.animations ?? []) {
|
|
2455
3543
|
try {
|
|
2456
|
-
const
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
setup: {
|
|
2461
|
-
...anim.setup ?? {},
|
|
2462
|
-
animation: animationPayload
|
|
2463
|
-
}
|
|
2464
|
-
};
|
|
2465
|
-
const id = registerAnimation(config);
|
|
2466
|
-
animationIds.push(id);
|
|
3544
|
+
const stored = anim.setup?.animation ?? toStoredAnimationClip(anim.id, anim.clip);
|
|
3545
|
+
hostClips.push({ id: anim.id, stored });
|
|
3546
|
+
registerAnimation({ id: anim.id });
|
|
3547
|
+
animationIds.push(anim.id);
|
|
2467
3548
|
} catch (err) {
|
|
2468
3549
|
if (isRuntimeDebugEnabled()) {
|
|
2469
3550
|
console.warn("[vizij-runtime] failed animation registration", {
|
|
@@ -2479,6 +3560,7 @@ function VizijRuntimeProviderInner({
|
|
|
2479
3560
|
});
|
|
2480
3561
|
}
|
|
2481
3562
|
}
|
|
3563
|
+
ensureAnimationHost()?.setClips(hostClips);
|
|
2482
3564
|
registeredAnimationsRef.current = animationIds;
|
|
2483
3565
|
if (assetBundle.initialInputs) {
|
|
2484
3566
|
Object.entries(assetBundle.initialInputs).forEach(([path, value]) => {
|
|
@@ -2512,6 +3594,7 @@ function VizijRuntimeProviderInner({
|
|
|
2512
3594
|
}, [
|
|
2513
3595
|
assetBundle,
|
|
2514
3596
|
clearControllers,
|
|
3597
|
+
ensureAnimationHost,
|
|
2515
3598
|
listControllers,
|
|
2516
3599
|
mergeStrategy,
|
|
2517
3600
|
namespace,
|
|
@@ -2543,75 +3626,77 @@ function VizijRuntimeProviderInner({
|
|
|
2543
3626
|
});
|
|
2544
3627
|
}, [ready, status.loading, graphUpdateToken, registerControllers, pushError]);
|
|
2545
3628
|
useEffect(() => {
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
const writes = frame.merged_writes ?? [];
|
|
2550
|
-
if (!writes.length) {
|
|
2551
|
-
return;
|
|
2552
|
-
}
|
|
2553
|
-
const setWorldValues = store.getState().setValues;
|
|
2554
|
-
const namespaceValue = status.namespace;
|
|
2555
|
-
const currentValues = store.getState().values;
|
|
2556
|
-
const rigInputPathMap = rigInputMapRef.current;
|
|
2557
|
-
const rigPoseControlInputIds = rigPoseControlInputIdsRef.current;
|
|
2558
|
-
const batched = [];
|
|
2559
|
-
const namespacedOutputs = namespacedOutputPathsRef.current;
|
|
2560
|
-
const baseOutputs = baseOutputPathsRef.current;
|
|
2561
|
-
writes.forEach((write) => {
|
|
2562
|
-
const path = normalisePath(write.path);
|
|
2563
|
-
const basePath = stripNamespace(path, namespaceValue);
|
|
2564
|
-
const isTrackedOutput = namespacedOutputs.has(path) || baseOutputs.has(basePath);
|
|
2565
|
-
if (!isTrackedOutput) {
|
|
2566
|
-
return;
|
|
2567
|
-
}
|
|
2568
|
-
const raw = valueJSONToRaw(write.value);
|
|
2569
|
-
if (raw === void 0) {
|
|
3629
|
+
applyEngineChangesRef.current = (changes) => {
|
|
3630
|
+
const entries = Object.entries(changes);
|
|
3631
|
+
if (!entries.length) {
|
|
2570
3632
|
return;
|
|
2571
3633
|
}
|
|
2572
|
-
const
|
|
2573
|
-
|
|
2574
|
-
);
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
}
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
3634
|
+
const setWorldValues = store.getState().setValues;
|
|
3635
|
+
const namespaceValue = status.namespace;
|
|
3636
|
+
const currentValues = store.getState().values;
|
|
3637
|
+
const rigInputPathMap = rigInputMapRef.current;
|
|
3638
|
+
const rigPoseControlInputIds = rigPoseControlInputIdsRef.current;
|
|
3639
|
+
const batched = [];
|
|
3640
|
+
const namespacedOutputs = namespacedOutputPathsRef.current;
|
|
3641
|
+
const baseOutputs = baseOutputPathsRef.current;
|
|
3642
|
+
entries.forEach(([changedPath, changedValue]) => {
|
|
3643
|
+
if (changedValue === null || isBuiltInPath(changedPath)) {
|
|
3644
|
+
return;
|
|
3645
|
+
}
|
|
3646
|
+
const path = normalisePath(changedPath);
|
|
3647
|
+
const basePath = stripNamespace(path, namespaceValue);
|
|
3648
|
+
const isTrackedOutput = namespacedOutputs.has(path) || baseOutputs.has(basePath);
|
|
3649
|
+
if (!isTrackedOutput) {
|
|
3650
|
+
return;
|
|
3651
|
+
}
|
|
3652
|
+
const raw = valueJSONToRaw(changedValue);
|
|
3653
|
+
if (raw === void 0) {
|
|
3654
|
+
return;
|
|
3655
|
+
}
|
|
3656
|
+
const poseControlMatch = /^rig\/[^/]+\/pose\/control\/(.+)$/.exec(
|
|
3657
|
+
basePath
|
|
3658
|
+
);
|
|
3659
|
+
if (poseControlMatch && typeof raw === "number" && Number.isFinite(raw)) {
|
|
3660
|
+
const inputId = (poseControlMatch[1] ?? "").trim();
|
|
3661
|
+
const hasNativePoseControlInput = inputId.length > 0 && rigPoseControlInputIds.has(inputId);
|
|
3662
|
+
const mappedInputPath = inputId.length === 0 ? void 0 : resolvePoseControlInputPath({
|
|
3663
|
+
inputId,
|
|
3664
|
+
basePath,
|
|
3665
|
+
rigInputPathMap,
|
|
3666
|
+
hasNativePoseControlInput
|
|
3667
|
+
});
|
|
3668
|
+
if (mappedInputPath) {
|
|
3669
|
+
const bridgeKey = `${namespaceValue}:${mappedInputPath}`;
|
|
3670
|
+
const previousValue = poseControlBridgeValuesRef.current.get(bridgeKey);
|
|
3671
|
+
if (previousValue === void 0 || Math.abs(previousValue - raw) > POSE_CONTROL_BRIDGE_EPSILON) {
|
|
3672
|
+
poseControlBridgeValuesRef.current.set(bridgeKey, raw);
|
|
3673
|
+
setInput(mappedInputPath, { float: raw });
|
|
3674
|
+
}
|
|
2590
3675
|
}
|
|
2591
3676
|
}
|
|
3677
|
+
const targetPath = baseOutputs.has(basePath) ? basePath : path;
|
|
3678
|
+
const currentValue = currentValues.get(
|
|
3679
|
+
getLookup(namespaceValue, targetPath)
|
|
3680
|
+
);
|
|
3681
|
+
const nextWrite = transformOutputWrite ? transformOutputWrite({
|
|
3682
|
+
id: targetPath,
|
|
3683
|
+
namespace: namespaceValue,
|
|
3684
|
+
value: raw,
|
|
3685
|
+
currentValue
|
|
3686
|
+
}) : {
|
|
3687
|
+
id: targetPath,
|
|
3688
|
+
namespace: namespaceValue,
|
|
3689
|
+
value: raw
|
|
3690
|
+
};
|
|
3691
|
+
if (nextWrite) {
|
|
3692
|
+
batched.push(nextWrite);
|
|
3693
|
+
}
|
|
3694
|
+
});
|
|
3695
|
+
if (batched.length > 0) {
|
|
3696
|
+
setWorldValues(batched);
|
|
2592
3697
|
}
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
getLookup(namespaceValue, targetPath)
|
|
2596
|
-
);
|
|
2597
|
-
const nextWrite = transformOutputWrite ? transformOutputWrite({
|
|
2598
|
-
id: targetPath,
|
|
2599
|
-
namespace: namespaceValue,
|
|
2600
|
-
value: raw,
|
|
2601
|
-
currentValue
|
|
2602
|
-
}) : {
|
|
2603
|
-
id: targetPath,
|
|
2604
|
-
namespace: namespaceValue,
|
|
2605
|
-
value: raw
|
|
2606
|
-
};
|
|
2607
|
-
if (nextWrite) {
|
|
2608
|
-
batched.push(nextWrite);
|
|
2609
|
-
}
|
|
2610
|
-
});
|
|
2611
|
-
if (batched.length > 0) {
|
|
2612
|
-
setWorldValues(batched);
|
|
2613
|
-
}
|
|
2614
|
-
}, [frame, status.namespace, store, transformOutputWrite]);
|
|
3698
|
+
};
|
|
3699
|
+
}, [status.namespace, store, transformOutputWrite]);
|
|
2615
3700
|
const stagePoseNeutral = useCallback(
|
|
2616
3701
|
(force = false) => {
|
|
2617
3702
|
const neutral = assetBundle.pose?.config?.neutralInputs ?? {};
|
|
@@ -2700,180 +3785,34 @@ function VizijRuntimeProviderInner({
|
|
|
2700
3785
|
state.completion = completion;
|
|
2701
3786
|
return completion;
|
|
2702
3787
|
}, []);
|
|
2703
|
-
const
|
|
2704
|
-
(
|
|
2705
|
-
|
|
2706
|
-
if (!
|
|
2707
|
-
return;
|
|
2708
|
-
}
|
|
2709
|
-
const namespacedPath = namespaceTypedPath(path, namespaceRef.current);
|
|
2710
|
-
const staged = stagedInputsRef.current.get(namespacedPath);
|
|
2711
|
-
if (staged) {
|
|
2712
|
-
orchestratorSetInput(namespacedPath, staged.value, staged.shape);
|
|
2713
|
-
stagedInputsRef.current.delete(namespacedPath);
|
|
2714
|
-
return;
|
|
2715
|
-
}
|
|
2716
|
-
orchestratorSetInput(namespacedPath, { float: value });
|
|
2717
|
-
},
|
|
2718
|
-
[orchestratorSetInput, setInput]
|
|
2719
|
-
);
|
|
2720
|
-
const clearAnimationInput = useCallback(
|
|
2721
|
-
(path) => {
|
|
2722
|
-
const namespacedPath = namespaceTypedPath(path, namespaceRef.current);
|
|
2723
|
-
stagedInputsRef.current.delete(namespacedPath);
|
|
2724
|
-
removeInput(namespacedPath);
|
|
2725
|
-
},
|
|
2726
|
-
[removeInput]
|
|
2727
|
-
);
|
|
2728
|
-
const buildClipOutputValues = useCallback(
|
|
2729
|
-
(clip, state) => sampleAnimationClipOutputValues(
|
|
2730
|
-
clip.clip,
|
|
2731
|
-
state.time,
|
|
2732
|
-
state.weight,
|
|
2733
|
-
faceId ?? void 0,
|
|
2734
|
-
rigInputMapRef.current
|
|
2735
|
-
),
|
|
2736
|
-
[faceId]
|
|
2737
|
-
);
|
|
2738
|
-
const computeClipAggregateValues = useCallback(() => {
|
|
2739
|
-
const aggregate = /* @__PURE__ */ new Map();
|
|
2740
|
-
clipOutputValuesRef.current.forEach((outputValues) => {
|
|
2741
|
-
outputValues.forEach((value, path) => {
|
|
2742
|
-
aggregate.set(path, (aggregate.get(path) ?? 0) + value);
|
|
2743
|
-
});
|
|
2744
|
-
});
|
|
2745
|
-
return aggregate;
|
|
2746
|
-
}, []);
|
|
2747
|
-
const stageClipAggregateValues = useCallback(
|
|
2748
|
-
(nextAggregate, options) => {
|
|
2749
|
-
diffAnimationAggregateValues(
|
|
2750
|
-
clipAggregateValuesRef.current,
|
|
2751
|
-
nextAggregate,
|
|
2752
|
-
POSE_CONTROL_BRIDGE_EPSILON
|
|
2753
|
-
).forEach((operation) => {
|
|
2754
|
-
if (operation.kind === "clear") {
|
|
2755
|
-
clearAnimationInput(operation.path);
|
|
2756
|
-
return;
|
|
2757
|
-
}
|
|
2758
|
-
setAnimationInput(operation.path, operation.value, options);
|
|
2759
|
-
});
|
|
2760
|
-
clipAggregateValuesRef.current = nextAggregate;
|
|
2761
|
-
},
|
|
2762
|
-
[clearAnimationInput, setAnimationInput]
|
|
2763
|
-
);
|
|
2764
|
-
const syncClipOutputs = useCallback(
|
|
2765
|
-
(options) => {
|
|
2766
|
-
stageClipAggregateValues(computeClipAggregateValues(), options);
|
|
2767
|
-
},
|
|
2768
|
-
[computeClipAggregateValues, stageClipAggregateValues]
|
|
2769
|
-
);
|
|
2770
|
-
const writeClipOutputs = useCallback(
|
|
2771
|
-
(clip, state, options) => {
|
|
2772
|
-
if (!animationSystemActiveRef.current) {
|
|
2773
|
-
return;
|
|
2774
|
-
}
|
|
2775
|
-
clipOutputValuesRef.current.set(
|
|
2776
|
-
clip.id,
|
|
2777
|
-
buildClipOutputValues(clip, state)
|
|
2778
|
-
);
|
|
2779
|
-
syncClipOutputs(options);
|
|
2780
|
-
},
|
|
2781
|
-
[buildClipOutputValues, syncClipOutputs]
|
|
2782
|
-
);
|
|
2783
|
-
const clearClipOutputs = useCallback(
|
|
2784
|
-
(clipId, options) => {
|
|
2785
|
-
if (!clipOutputValuesRef.current.has(clipId)) {
|
|
2786
|
-
return;
|
|
3788
|
+
const ensureClipState = useCallback(
|
|
3789
|
+
(id) => {
|
|
3790
|
+
const clip = resolveClipById(id);
|
|
3791
|
+
if (!clip) {
|
|
3792
|
+
return null;
|
|
2787
3793
|
}
|
|
2788
|
-
clipOutputValuesRef.current.delete(clipId);
|
|
2789
|
-
syncClipOutputs(options);
|
|
2790
|
-
},
|
|
2791
|
-
[syncClipOutputs]
|
|
2792
|
-
);
|
|
2793
|
-
const createClipPlaybackState = useCallback(
|
|
2794
|
-
(clip) => {
|
|
2795
3794
|
const duration = resolveClipDurationSeconds(
|
|
2796
3795
|
clip.clip
|
|
2797
3796
|
);
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
3797
|
+
const existing = clipPlaybackRef.current.get(id);
|
|
3798
|
+
if (existing) {
|
|
3799
|
+
existing.duration = duration;
|
|
3800
|
+
return { clip, state: existing };
|
|
3801
|
+
}
|
|
3802
|
+
const state = {
|
|
3803
|
+
id,
|
|
2801
3804
|
duration,
|
|
2802
3805
|
speed: 1,
|
|
2803
|
-
weight:
|
|
2804
|
-
loop:
|
|
3806
|
+
weight: 1,
|
|
3807
|
+
loop: true,
|
|
2805
3808
|
playing: false,
|
|
2806
3809
|
resolve: null,
|
|
2807
3810
|
completion: null
|
|
2808
3811
|
};
|
|
3812
|
+
clipPlaybackRef.current.set(id, state);
|
|
3813
|
+
return { clip, state };
|
|
2809
3814
|
},
|
|
2810
|
-
[]
|
|
2811
|
-
);
|
|
2812
|
-
const ensureClipPlaybackState = useCallback(
|
|
2813
|
-
(id) => {
|
|
2814
|
-
const clip = resolveClipById(id);
|
|
2815
|
-
if (!clip) {
|
|
2816
|
-
return null;
|
|
2817
|
-
}
|
|
2818
|
-
const existing = clipPlaybackRef.current.get(id);
|
|
2819
|
-
if (existing) {
|
|
2820
|
-
existing.duration = resolveClipDurationSeconds(
|
|
2821
|
-
clip.clip,
|
|
2822
|
-
existing.duration
|
|
2823
|
-
);
|
|
2824
|
-
existing.time = clampAnimationTime(existing.time, existing.duration);
|
|
2825
|
-
return { clip, state: existing };
|
|
2826
|
-
}
|
|
2827
|
-
const next = createClipPlaybackState(clip);
|
|
2828
|
-
clipPlaybackRef.current.set(id, next);
|
|
2829
|
-
return { clip, state: next };
|
|
2830
|
-
},
|
|
2831
|
-
[createClipPlaybackState, resolveClipById]
|
|
2832
|
-
);
|
|
2833
|
-
const advanceClipPlayback = useCallback(
|
|
2834
|
-
(dt) => {
|
|
2835
|
-
if (clipPlaybackRef.current.size === 0) {
|
|
2836
|
-
return;
|
|
2837
|
-
}
|
|
2838
|
-
const toDelete = [];
|
|
2839
|
-
clipPlaybackRef.current.forEach((state, key) => {
|
|
2840
|
-
const clip = resolveClipById(state.id);
|
|
2841
|
-
if (!clip) {
|
|
2842
|
-
toDelete.push(key);
|
|
2843
|
-
resolveClipPromise(state);
|
|
2844
|
-
return;
|
|
2845
|
-
}
|
|
2846
|
-
state.duration = resolveClipDurationSeconds(
|
|
2847
|
-
clip.clip,
|
|
2848
|
-
state.duration
|
|
2849
|
-
);
|
|
2850
|
-
const { time, completed } = advanceClipTime(
|
|
2851
|
-
{
|
|
2852
|
-
time: state.time,
|
|
2853
|
-
duration: state.duration,
|
|
2854
|
-
speed: state.speed,
|
|
2855
|
-
loop: state.loop,
|
|
2856
|
-
playing: state.playing
|
|
2857
|
-
},
|
|
2858
|
-
dt
|
|
2859
|
-
);
|
|
2860
|
-
state.time = clampAnimationTime(time, state.duration);
|
|
2861
|
-
if (state.playing || completed) {
|
|
2862
|
-
writeClipOutputs(clip, state);
|
|
2863
|
-
}
|
|
2864
|
-
if (completed) {
|
|
2865
|
-
toDelete.push(key);
|
|
2866
|
-
resolveClipPromise(state);
|
|
2867
|
-
}
|
|
2868
|
-
});
|
|
2869
|
-
toDelete.forEach((key) => {
|
|
2870
|
-
clipPlaybackRef.current.delete(key);
|
|
2871
|
-
if (animationSystemActiveRef.current) {
|
|
2872
|
-
clearClipOutputs(key);
|
|
2873
|
-
}
|
|
2874
|
-
});
|
|
2875
|
-
},
|
|
2876
|
-
[clearClipOutputs, resolveClipById, resolveClipPromise, writeClipOutputs]
|
|
3815
|
+
[resolveClipById]
|
|
2877
3816
|
);
|
|
2878
3817
|
const animateValue = useCallback(
|
|
2879
3818
|
(path, target, options) => {
|
|
@@ -2947,42 +3886,111 @@ function VizijRuntimeProviderInner({
|
|
|
2947
3886
|
useLegacyPoseWeightFallback
|
|
2948
3887
|
]
|
|
2949
3888
|
);
|
|
3889
|
+
const warnAnimationGap = useCallback((key, message) => {
|
|
3890
|
+
if (animationGapWarnedRef.current.has(key)) {
|
|
3891
|
+
return;
|
|
3892
|
+
}
|
|
3893
|
+
animationGapWarnedRef.current.add(key);
|
|
3894
|
+
console.warn(`[vizij-runtime] ${message}`);
|
|
3895
|
+
}, []);
|
|
3896
|
+
const syncAnimationsSource = useCallback(() => {
|
|
3897
|
+
const host = animationHostRef.current;
|
|
3898
|
+
setAnimationsSourceRegistered(host ? host.hasPlaying() : false);
|
|
3899
|
+
updateLoopMode();
|
|
3900
|
+
}, [setAnimationsSourceRegistered, updateLoopMode]);
|
|
3901
|
+
const readPlayerStates = useCallback(() => {
|
|
3902
|
+
const device = deviceSlot.current?.device;
|
|
3903
|
+
if (!device) {
|
|
3904
|
+
return [];
|
|
3905
|
+
}
|
|
3906
|
+
const raw = device.readValues([ANIMATION_PLAYERS_PATH])[ANIMATION_PLAYERS_PATH];
|
|
3907
|
+
return decodePlayerStates(raw);
|
|
3908
|
+
}, [deviceSlot]);
|
|
3909
|
+
const settleFinishedClips = useCallback(() => {
|
|
3910
|
+
let pending = false;
|
|
3911
|
+
clipPlaybackRef.current.forEach((state) => {
|
|
3912
|
+
if (state.completion && !state.loop) {
|
|
3913
|
+
pending = true;
|
|
3914
|
+
}
|
|
3915
|
+
});
|
|
3916
|
+
const host = animationHostRef.current;
|
|
3917
|
+
if (!pending || !host) {
|
|
3918
|
+
return;
|
|
3919
|
+
}
|
|
3920
|
+
const states = readPlayerStates();
|
|
3921
|
+
if (states.length === 0) {
|
|
3922
|
+
return;
|
|
3923
|
+
}
|
|
3924
|
+
clipPlaybackRef.current.forEach((state, id) => {
|
|
3925
|
+
if (!state.completion || state.loop) {
|
|
3926
|
+
return;
|
|
3927
|
+
}
|
|
3928
|
+
const playerId = host.playerIdOf(id);
|
|
3929
|
+
if (playerId === null) {
|
|
3930
|
+
return;
|
|
3931
|
+
}
|
|
3932
|
+
const feedback = states.find((entry) => entry.player === playerId);
|
|
3933
|
+
if (!feedback) {
|
|
3934
|
+
return;
|
|
3935
|
+
}
|
|
3936
|
+
const duration = feedback.duration > 0 ? feedback.duration : state.duration;
|
|
3937
|
+
const atEnd = duration > 0 && feedback.time >= duration - 1e-3;
|
|
3938
|
+
if (feedback.state === "stopped" || atEnd) {
|
|
3939
|
+
state.playing = false;
|
|
3940
|
+
resolveClipPromise(state);
|
|
3941
|
+
host.pause(id);
|
|
3942
|
+
syncAnimationsSource();
|
|
3943
|
+
}
|
|
3944
|
+
});
|
|
3945
|
+
}, [readPlayerStates, resolveClipPromise, syncAnimationsSource]);
|
|
2950
3946
|
const playAnimation = useCallback(
|
|
2951
3947
|
(id, options) => {
|
|
2952
|
-
const ensured =
|
|
3948
|
+
const ensured = ensureClipState(id);
|
|
2953
3949
|
if (!ensured) {
|
|
2954
3950
|
return Promise.reject(
|
|
2955
3951
|
new Error(`Animation ${id} is not part of the current asset bundle.`)
|
|
2956
3952
|
);
|
|
2957
3953
|
}
|
|
2958
|
-
const {
|
|
2959
|
-
const
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
state.time = 0;
|
|
2963
|
-
}
|
|
2964
|
-
const speed = options?.speed ?? state.speed ?? 1;
|
|
2965
|
-
const weight = options?.weight ?? state.weight ?? clip.weight ?? 1;
|
|
2966
|
-
state.speed = Number.isFinite(speed) && speed > 0 ? speed : 1;
|
|
2967
|
-
state.weight = Number.isFinite(weight) ? Number(weight) : 1;
|
|
2968
|
-
state.duration = resolveClipDurationSeconds(
|
|
2969
|
-
clip.clip,
|
|
2970
|
-
state.duration
|
|
2971
|
-
);
|
|
2972
|
-
state.time = clampAnimationTime(state.time, state.duration);
|
|
3954
|
+
const { state } = ensured;
|
|
3955
|
+
const host = ensureAnimationHost();
|
|
3956
|
+
state.speed = options?.speed ?? state.speed;
|
|
3957
|
+
state.weight = options?.weight ?? state.weight;
|
|
2973
3958
|
state.playing = true;
|
|
2974
3959
|
const completion = ensureClipPromise(state);
|
|
2975
3960
|
clipPlaybackRef.current.set(id, state);
|
|
2976
|
-
|
|
3961
|
+
if (host) {
|
|
3962
|
+
if (options?.reset === true) {
|
|
3963
|
+
host.seek(id, 0);
|
|
3964
|
+
}
|
|
3965
|
+
host.setSpeed(id, state.speed);
|
|
3966
|
+
host.setWeight(id, state.weight);
|
|
3967
|
+
host.setLoop(id, state.loop ? "loop" : "once");
|
|
3968
|
+
void host.play(id).catch((err) => {
|
|
3969
|
+
pushError({
|
|
3970
|
+
message: `Failed to start animation ${id} on the device`,
|
|
3971
|
+
cause: err,
|
|
3972
|
+
phase: "animation",
|
|
3973
|
+
timestamp: performance.now()
|
|
3974
|
+
});
|
|
3975
|
+
});
|
|
3976
|
+
} else {
|
|
3977
|
+
warnAnimationGap(
|
|
3978
|
+
"module-not-ready",
|
|
3979
|
+
"playAnimation called before the animation module finished loading; playback starts once it is ready."
|
|
3980
|
+
);
|
|
3981
|
+
}
|
|
3982
|
+
syncAnimationsSource();
|
|
2977
3983
|
markActivity();
|
|
2978
3984
|
return completion;
|
|
2979
3985
|
},
|
|
2980
3986
|
[
|
|
2981
|
-
|
|
3987
|
+
ensureAnimationHost,
|
|
2982
3988
|
ensureClipPromise,
|
|
3989
|
+
ensureClipState,
|
|
2983
3990
|
markActivity,
|
|
2984
|
-
|
|
2985
|
-
|
|
3991
|
+
pushError,
|
|
3992
|
+
syncAnimationsSource,
|
|
3993
|
+
warnAnimationGap
|
|
2986
3994
|
]
|
|
2987
3995
|
);
|
|
2988
3996
|
const pauseAnimation = useCallback(
|
|
@@ -2992,34 +4000,28 @@ function VizijRuntimeProviderInner({
|
|
|
2992
4000
|
return;
|
|
2993
4001
|
}
|
|
2994
4002
|
state.playing = false;
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
[updateLoopMode]
|
|
2998
|
-
);
|
|
2999
|
-
const seekAnimation = useCallback(
|
|
3000
|
-
(id, timeSeconds) => {
|
|
3001
|
-
const ensured = ensureClipPlaybackState(id);
|
|
3002
|
-
if (!ensured) {
|
|
3003
|
-
return;
|
|
3004
|
-
}
|
|
3005
|
-
const { clip, state } = ensured;
|
|
3006
|
-
state.time = clampAnimationTime(timeSeconds, state.duration);
|
|
3007
|
-
clipPlaybackRef.current.set(id, state);
|
|
3008
|
-
writeClipOutputs(clip, state, { immediate: true });
|
|
4003
|
+
animationHostRef.current?.pause(id);
|
|
4004
|
+
syncAnimationsSource();
|
|
3009
4005
|
},
|
|
3010
|
-
[
|
|
4006
|
+
[syncAnimationsSource]
|
|
3011
4007
|
);
|
|
4008
|
+
const seekAnimation = useCallback((id, timeSeconds) => {
|
|
4009
|
+
if (!clipPlaybackRef.current.has(id)) {
|
|
4010
|
+
return;
|
|
4011
|
+
}
|
|
4012
|
+
animationHostRef.current?.seek(id, Math.max(0, timeSeconds));
|
|
4013
|
+
}, []);
|
|
3012
4014
|
const setAnimationLoop = useCallback(
|
|
3013
4015
|
(id, enabled) => {
|
|
3014
|
-
const ensured =
|
|
4016
|
+
const ensured = ensureClipState(id);
|
|
3015
4017
|
if (!ensured) {
|
|
3016
4018
|
return;
|
|
3017
4019
|
}
|
|
3018
4020
|
ensured.state.loop = Boolean(enabled);
|
|
3019
|
-
|
|
4021
|
+
animationHostRef.current?.setLoop(id, enabled ? "loop" : "once");
|
|
3020
4022
|
updateLoopMode();
|
|
3021
4023
|
},
|
|
3022
|
-
[
|
|
4024
|
+
[ensureClipState, updateLoopMode]
|
|
3023
4025
|
);
|
|
3024
4026
|
const getAnimationState = useCallback(
|
|
3025
4027
|
(id) => {
|
|
@@ -3027,15 +4029,17 @@ function VizijRuntimeProviderInner({
|
|
|
3027
4029
|
if (!state) {
|
|
3028
4030
|
return null;
|
|
3029
4031
|
}
|
|
4032
|
+
const playerId = animationHostRef.current?.playerIdOf(id) ?? null;
|
|
4033
|
+
const feedback = playerId !== null ? readPlayerStates().find((entry) => entry.player === playerId) : void 0;
|
|
3030
4034
|
return {
|
|
3031
|
-
time:
|
|
3032
|
-
duration: state.duration,
|
|
3033
|
-
playing: state.playing,
|
|
4035
|
+
time: feedback?.time ?? 0,
|
|
4036
|
+
duration: feedback && feedback.duration > 0 ? feedback.duration : state.duration,
|
|
4037
|
+
playing: feedback ? feedback.state === "playing" : state.playing,
|
|
3034
4038
|
loop: state.loop,
|
|
3035
|
-
speed: state.speed
|
|
4039
|
+
speed: feedback?.speed ?? state.speed
|
|
3036
4040
|
};
|
|
3037
4041
|
},
|
|
3038
|
-
[]
|
|
4042
|
+
[readPlayerStates]
|
|
3039
4043
|
);
|
|
3040
4044
|
const stopAnimation = useCallback(
|
|
3041
4045
|
(id, options) => {
|
|
@@ -3045,12 +4049,19 @@ function VizijRuntimeProviderInner({
|
|
|
3045
4049
|
state.playing = false;
|
|
3046
4050
|
resolveClipPromise(state);
|
|
3047
4051
|
}
|
|
3048
|
-
|
|
3049
|
-
|
|
4052
|
+
const host = animationHostRef.current;
|
|
4053
|
+
if (options?.clearOutputs === false) {
|
|
4054
|
+
host?.pause(id);
|
|
4055
|
+
syncAnimationsSource();
|
|
4056
|
+
} else {
|
|
4057
|
+
host?.stop(id);
|
|
4058
|
+
const unsubscribe = subscribeToStep(() => {
|
|
4059
|
+
unsubscribe();
|
|
4060
|
+
syncAnimationsSource();
|
|
4061
|
+
});
|
|
3050
4062
|
}
|
|
3051
|
-
updateLoopMode();
|
|
3052
4063
|
},
|
|
3053
|
-
[
|
|
4064
|
+
[resolveClipPromise, subscribeToStep, syncAnimationsSource]
|
|
3054
4065
|
);
|
|
3055
4066
|
const refreshControllerStatus = useCallback(() => {
|
|
3056
4067
|
const controllers = listControllers();
|
|
@@ -3199,11 +4210,21 @@ function VizijRuntimeProviderInner({
|
|
|
3199
4210
|
]);
|
|
3200
4211
|
const playProgram = useCallback(
|
|
3201
4212
|
(id) => {
|
|
3202
|
-
|
|
4213
|
+
const program = resolveProgramById(id);
|
|
4214
|
+
if (!program) {
|
|
3203
4215
|
throw new Error(
|
|
3204
4216
|
`Program ${id} is not part of the current asset bundle.`
|
|
3205
4217
|
);
|
|
3206
4218
|
}
|
|
4219
|
+
deriveProgramInputSeedValues({
|
|
4220
|
+
program,
|
|
4221
|
+
namespace,
|
|
4222
|
+
inputConstraints: inputConstraintsRef.current,
|
|
4223
|
+
getPathSnapshot,
|
|
4224
|
+
stagedInputs: stagedInputsRef.current
|
|
4225
|
+
}).forEach(({ path, value }) => {
|
|
4226
|
+
setInput(path, value);
|
|
4227
|
+
});
|
|
3207
4228
|
programPlaybackRef.current.set(id, {
|
|
3208
4229
|
id,
|
|
3209
4230
|
state: "playing"
|
|
@@ -3211,7 +4232,14 @@ function VizijRuntimeProviderInner({
|
|
|
3211
4232
|
syncProgramPlaybackControllers();
|
|
3212
4233
|
markActivity();
|
|
3213
4234
|
},
|
|
3214
|
-
[
|
|
4235
|
+
[
|
|
4236
|
+
getPathSnapshot,
|
|
4237
|
+
markActivity,
|
|
4238
|
+
namespace,
|
|
4239
|
+
resolveProgramById,
|
|
4240
|
+
setInput,
|
|
4241
|
+
syncProgramPlaybackControllers
|
|
4242
|
+
]
|
|
3215
4243
|
);
|
|
3216
4244
|
const pauseProgram = useCallback(
|
|
3217
4245
|
(id) => {
|
|
@@ -3310,6 +4338,7 @@ function VizijRuntimeProviderInner({
|
|
|
3310
4338
|
);
|
|
3311
4339
|
const registerInputDriver = useCallback(
|
|
3312
4340
|
(id, factory) => {
|
|
4341
|
+
inputDriverIdsRef.current.add(id);
|
|
3313
4342
|
const driver = factory({
|
|
3314
4343
|
setInput,
|
|
3315
4344
|
setRendererValue,
|
|
@@ -3351,6 +4380,8 @@ function VizijRuntimeProviderInner({
|
|
|
3351
4380
|
phase: "driver",
|
|
3352
4381
|
timestamp: performance.now()
|
|
3353
4382
|
});
|
|
4383
|
+
} finally {
|
|
4384
|
+
inputDriverIdsRef.current.delete(id);
|
|
3354
4385
|
}
|
|
3355
4386
|
}
|
|
3356
4387
|
};
|
|
@@ -3358,22 +4389,15 @@ function VizijRuntimeProviderInner({
|
|
|
3358
4389
|
},
|
|
3359
4390
|
[faceId, namespace, pushError, setInput, setRendererValue]
|
|
3360
4391
|
);
|
|
3361
|
-
const advanceAnimations = useCallback(
|
|
3362
|
-
(dt) => {
|
|
3363
|
-
advanceAnimationTweens(dt);
|
|
3364
|
-
advanceClipPlayback(dt);
|
|
3365
|
-
},
|
|
3366
|
-
[advanceAnimationTweens, advanceClipPlayback]
|
|
3367
|
-
);
|
|
3368
4392
|
const flushStagedInputs = useCallback(() => {
|
|
3369
4393
|
if (stagedInputsRef.current.size === 0) {
|
|
3370
4394
|
return;
|
|
3371
4395
|
}
|
|
3372
4396
|
stagedInputsRef.current.forEach(({ value, shape }, path) => {
|
|
3373
|
-
|
|
4397
|
+
deviceSetInput(path, value, shape);
|
|
3374
4398
|
});
|
|
3375
4399
|
stagedInputsRef.current.clear();
|
|
3376
|
-
}, [
|
|
4400
|
+
}, [deviceSetInput]);
|
|
3377
4401
|
const step = useCallback(
|
|
3378
4402
|
(dt, opts) => {
|
|
3379
4403
|
if (dt > 0 && Number.isFinite(dt)) {
|
|
@@ -3381,13 +4405,19 @@ function VizijRuntimeProviderInner({
|
|
|
3381
4405
|
const alpha = 0.1;
|
|
3382
4406
|
avgStepDtRef.current = prev * (1 - alpha) + dt * alpha;
|
|
3383
4407
|
}
|
|
3384
|
-
|
|
4408
|
+
advanceAnimationTweens(dt);
|
|
3385
4409
|
flushStagedInputs();
|
|
3386
|
-
if (
|
|
4410
|
+
if (driveRuntimeRef.current || opts?.forceRuntime) {
|
|
3387
4411
|
stepRuntime(dt);
|
|
3388
4412
|
}
|
|
4413
|
+
settleFinishedClips();
|
|
3389
4414
|
},
|
|
3390
|
-
[
|
|
4415
|
+
[
|
|
4416
|
+
advanceAnimationTweens,
|
|
4417
|
+
flushStagedInputs,
|
|
4418
|
+
settleFinishedClips,
|
|
4419
|
+
stepRuntime
|
|
4420
|
+
]
|
|
3391
4421
|
);
|
|
3392
4422
|
useEffect(() => {
|
|
3393
4423
|
if (loopMode !== "active") {
|
|
@@ -3402,7 +4432,7 @@ function VizijRuntimeProviderInner({
|
|
|
3402
4432
|
if (lastTime == null) {
|
|
3403
4433
|
lastTime = timestamp;
|
|
3404
4434
|
}
|
|
3405
|
-
const dt = Math.max(0, (timestamp - lastTime) / 1e3);
|
|
4435
|
+
const dt = inferImplicitPause(Math.max(0, (timestamp - lastTime) / 1e3));
|
|
3406
4436
|
lastTime = timestamp;
|
|
3407
4437
|
step(dt || 0);
|
|
3408
4438
|
requestLoopMode(computeDesiredLoopMode());
|
|
@@ -3433,7 +4463,7 @@ function VizijRuntimeProviderInner({
|
|
|
3433
4463
|
return;
|
|
3434
4464
|
}
|
|
3435
4465
|
const current = now();
|
|
3436
|
-
const dt = Math.max(0, (current - lastTime) / 1e3);
|
|
4466
|
+
const dt = inferImplicitPause(Math.max(0, (current - lastTime) / 1e3));
|
|
3437
4467
|
lastTime = current;
|
|
3438
4468
|
step(dt || 0);
|
|
3439
4469
|
requestLoopMode(computeDesiredLoopMode());
|
|
@@ -3449,8 +4479,6 @@ function VizijRuntimeProviderInner({
|
|
|
3449
4479
|
clipPlaybackRef.current.clear();
|
|
3450
4480
|
programPlaybackRef.current.clear();
|
|
3451
4481
|
programControllerIdsRef.current.clear();
|
|
3452
|
-
clipOutputValuesRef.current.clear();
|
|
3453
|
-
clipAggregateValuesRef.current.clear();
|
|
3454
4482
|
};
|
|
3455
4483
|
}, []);
|
|
3456
4484
|
useEffect(() => {
|
|
@@ -3503,6 +4531,10 @@ function VizijRuntimeProviderInner({
|
|
|
3503
4531
|
...status,
|
|
3504
4532
|
assetBundle,
|
|
3505
4533
|
setInput,
|
|
4534
|
+
getValueSnapshot: getPathSnapshot,
|
|
4535
|
+
getStoreSnapshot,
|
|
4536
|
+
subscribeToStoreChanges,
|
|
4537
|
+
subscribeToStep,
|
|
3506
4538
|
setGraphBundle,
|
|
3507
4539
|
setValue: setRendererValue,
|
|
3508
4540
|
stagePoseNeutral,
|
|
@@ -3522,13 +4554,16 @@ function VizijRuntimeProviderInner({
|
|
|
3522
4554
|
setAnimationActive,
|
|
3523
4555
|
isAnimationActive,
|
|
3524
4556
|
step,
|
|
3525
|
-
advanceAnimations,
|
|
3526
4557
|
inputConstraints
|
|
3527
4558
|
}),
|
|
3528
4559
|
[
|
|
3529
4560
|
status,
|
|
3530
4561
|
assetBundle,
|
|
3531
4562
|
setInput,
|
|
4563
|
+
getPathSnapshot,
|
|
4564
|
+
getStoreSnapshot,
|
|
4565
|
+
subscribeToStoreChanges,
|
|
4566
|
+
subscribeToStep,
|
|
3532
4567
|
setGraphBundle,
|
|
3533
4568
|
setRendererValue,
|
|
3534
4569
|
stagePoseNeutral,
|
|
@@ -3548,7 +4583,6 @@ function VizijRuntimeProviderInner({
|
|
|
3548
4583
|
setAnimationActive,
|
|
3549
4584
|
isAnimationActive,
|
|
3550
4585
|
step,
|
|
3551
|
-
advanceAnimations,
|
|
3552
4586
|
inputConstraints
|
|
3553
4587
|
]
|
|
3554
4588
|
);
|
|
@@ -3560,9 +4594,9 @@ import { memo } from "react";
|
|
|
3560
4594
|
import { Vizij } from "@vizij/render";
|
|
3561
4595
|
|
|
3562
4596
|
// src/hooks/useVizijRuntime.ts
|
|
3563
|
-
import { useContext
|
|
4597
|
+
import { useContext } from "react";
|
|
3564
4598
|
function useVizijRuntime() {
|
|
3565
|
-
const ctx =
|
|
4599
|
+
const ctx = useContext(VizijRuntimeContext);
|
|
3566
4600
|
if (!ctx) {
|
|
3567
4601
|
throw new Error(
|
|
3568
4602
|
"useVizijRuntime must be used within a VizijRuntimeProvider."
|
|
@@ -3593,9 +4627,9 @@ function VizijRuntimeFaceInner({
|
|
|
3593
4627
|
var VizijRuntimeFace = memo(VizijRuntimeFaceInner);
|
|
3594
4628
|
|
|
3595
4629
|
// src/hooks/useOptionalVizijRuntime.ts
|
|
3596
|
-
import { useContext as
|
|
4630
|
+
import { useContext as useContext2 } from "react";
|
|
3597
4631
|
function useOptionalVizijRuntime() {
|
|
3598
|
-
return
|
|
4632
|
+
return useContext2(VizijRuntimeContext);
|
|
3599
4633
|
}
|
|
3600
4634
|
|
|
3601
4635
|
// src/hooks/useVizijOutputs.ts
|