@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.cjs
CHANGED
|
@@ -50,16 +50,356 @@ module.exports = __toCommonJS(index_exports);
|
|
|
50
50
|
// src/VizijRuntimeProvider.tsx
|
|
51
51
|
var import_react2 = require("react");
|
|
52
52
|
var import_render = require("@vizij/render");
|
|
53
|
-
var import_orchestrator_react = require("@vizij/orchestrator-react");
|
|
54
53
|
var import_node_graph_authoring = require("@vizij/node-graph-authoring");
|
|
55
54
|
var import_value_json2 = require("@vizij/value-json");
|
|
56
55
|
var import_utils = require("@vizij/utils");
|
|
56
|
+
var import_animation_module = require("@vizij/animation-module");
|
|
57
|
+
|
|
58
|
+
// src/engine/aroraEngine.ts
|
|
59
|
+
var import_runtime = require("@vizij/runtime");
|
|
60
|
+
|
|
61
|
+
// src/engine/graphSpecDiff.ts
|
|
62
|
+
function asRecords(value) {
|
|
63
|
+
return Array.isArray(value) ? value : [];
|
|
64
|
+
}
|
|
65
|
+
function nodeId(node) {
|
|
66
|
+
return typeof node.id === "string" ? node.id : void 0;
|
|
67
|
+
}
|
|
68
|
+
function endpointId(endpoint) {
|
|
69
|
+
const id = endpoint?.node_id;
|
|
70
|
+
return typeof id === "string" ? id : void 0;
|
|
71
|
+
}
|
|
72
|
+
function endpointInput(endpoint) {
|
|
73
|
+
const input = endpoint?.input;
|
|
74
|
+
return typeof input === "string" ? input : void 0;
|
|
75
|
+
}
|
|
76
|
+
function stable(value) {
|
|
77
|
+
return JSON.stringify(sortKeys(value));
|
|
78
|
+
}
|
|
79
|
+
function sortKeys(value) {
|
|
80
|
+
if (Array.isArray(value)) return value.map(sortKeys);
|
|
81
|
+
if (value && typeof value === "object") {
|
|
82
|
+
const out = {};
|
|
83
|
+
for (const key of Object.keys(value).sort()) {
|
|
84
|
+
out[key] = sortKeys(value[key]);
|
|
85
|
+
}
|
|
86
|
+
return out;
|
|
87
|
+
}
|
|
88
|
+
return value;
|
|
89
|
+
}
|
|
90
|
+
function edgeKey(edge) {
|
|
91
|
+
return stable([
|
|
92
|
+
endpointId(edge.from),
|
|
93
|
+
edge.from?.output ?? "out",
|
|
94
|
+
endpointId(edge.to),
|
|
95
|
+
endpointInput(edge.to),
|
|
96
|
+
edge.selector ?? null
|
|
97
|
+
]);
|
|
98
|
+
}
|
|
99
|
+
function graphSpecDiff(prev, next) {
|
|
100
|
+
const prevNodes = /* @__PURE__ */ new Map();
|
|
101
|
+
for (const node of asRecords(prev.nodes)) {
|
|
102
|
+
const id = nodeId(node);
|
|
103
|
+
if (id) prevNodes.set(id, node);
|
|
104
|
+
}
|
|
105
|
+
const nextNodes = /* @__PURE__ */ new Map();
|
|
106
|
+
for (const node of asRecords(next.nodes)) {
|
|
107
|
+
const id = nodeId(node);
|
|
108
|
+
if (id) nextNodes.set(id, node);
|
|
109
|
+
}
|
|
110
|
+
const prevEdges = asRecords(prev.edges);
|
|
111
|
+
const nextEdges = asRecords(next.edges);
|
|
112
|
+
const remove_nodes = [...prevNodes.keys()].filter((id) => !nextNodes.has(id));
|
|
113
|
+
const upsertIds = /* @__PURE__ */ new Set();
|
|
114
|
+
for (const [id, node] of nextNodes) {
|
|
115
|
+
const before = prevNodes.get(id);
|
|
116
|
+
if (!before || stable(before) !== stable(node)) upsertIds.add(id);
|
|
117
|
+
}
|
|
118
|
+
const prevEdgeKeys = new Set(prevEdges.map(edgeKey));
|
|
119
|
+
const nextEdgeKeys = new Set(nextEdges.map(edgeKey));
|
|
120
|
+
const removedEdges = prevEdges.filter((e) => !nextEdgeKeys.has(edgeKey(e)));
|
|
121
|
+
const changedEdges = [
|
|
122
|
+
...nextEdges.filter((e) => !prevEdgeKeys.has(edgeKey(e))),
|
|
123
|
+
...removedEdges
|
|
124
|
+
];
|
|
125
|
+
for (const edge of changedEdges) {
|
|
126
|
+
for (const id of [endpointId(edge.from), endpointId(edge.to)]) {
|
|
127
|
+
if (id && nextNodes.has(id)) upsertIds.add(id);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
const incidentToUpsert = (edge) => {
|
|
131
|
+
const from = endpointId(edge.from);
|
|
132
|
+
const to = endpointId(edge.to);
|
|
133
|
+
return from !== void 0 && upsertIds.has(from) || to !== void 0 && upsertIds.has(to);
|
|
134
|
+
};
|
|
135
|
+
const upsert_nodes = [...upsertIds].map((id) => nextNodes.get(id));
|
|
136
|
+
const upsert_edges = nextEdges.filter(incidentToUpsert);
|
|
137
|
+
const removeNodeSet = new Set(remove_nodes);
|
|
138
|
+
const remove_edges = removedEdges.flatMap((edge) => {
|
|
139
|
+
const to = endpointId(edge.to);
|
|
140
|
+
const input = endpointInput(edge.to);
|
|
141
|
+
if (!to || !input || removeNodeSet.has(to) || upsertIds.has(to)) return [];
|
|
142
|
+
return [{ node_id: to, input }];
|
|
143
|
+
});
|
|
144
|
+
if (upsert_nodes.length === 0 && remove_nodes.length === 0 && upsert_edges.length === 0 && remove_edges.length === 0) {
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
return { upsert_nodes, remove_nodes, upsert_edges, remove_edges };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// src/engine/aroraEngine.ts
|
|
151
|
+
var BUILT_IN_PREFIX = "arora/";
|
|
152
|
+
function isBuiltInPath(path) {
|
|
153
|
+
return path.startsWith(BUILT_IN_PREFIX);
|
|
154
|
+
}
|
|
155
|
+
var RUN_PERIOD_MS = 1e3 / 60;
|
|
156
|
+
var initPromise = null;
|
|
157
|
+
function ensureWasmInit(input) {
|
|
158
|
+
if (!initPromise) {
|
|
159
|
+
initPromise = (0, import_runtime.init)(input);
|
|
160
|
+
}
|
|
161
|
+
return initPromise;
|
|
162
|
+
}
|
|
163
|
+
var DeviceSlot = class {
|
|
164
|
+
constructor() {
|
|
165
|
+
this.handle = null;
|
|
166
|
+
/** Module loading a boot must wait out before it reads `modules`. */
|
|
167
|
+
this.modulesLoading = null;
|
|
168
|
+
/**
|
|
169
|
+
* When set, a booted device is handed to its own loop (`device.run`) at
|
|
170
|
+
* this period. `null`: the host drives `step()` itself.
|
|
171
|
+
*/
|
|
172
|
+
this.runPeriodMs = null;
|
|
173
|
+
/**
|
|
174
|
+
* Serializes device operations. Every `recompose` chains behind the
|
|
175
|
+
* previous op so two never run concurrently — a bundle whose import fans
|
|
176
|
+
* out into several graph registrations recomposes in a burst. Failures are
|
|
177
|
+
* swallowed on the chain so one failed op doesn't wedge later ones; each
|
|
178
|
+
* caller still sees its own rejection through the promise `enqueue`
|
|
179
|
+
* returns.
|
|
180
|
+
*/
|
|
181
|
+
this.opChain = Promise.resolve();
|
|
182
|
+
}
|
|
183
|
+
get current() {
|
|
184
|
+
return this.handle;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* The modules every subsequent device boot loads. The live device (if any)
|
|
188
|
+
* is not touched: a later `recompose` that sees the list changed rebuilds.
|
|
189
|
+
*/
|
|
190
|
+
setModules(modules) {
|
|
191
|
+
this.modules = modules && modules.length > 0 ? modules : void 0;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Announce module loading in flight: boots wait for `loading` to settle
|
|
195
|
+
* before reading the module list, so a device never boots without modules
|
|
196
|
+
* that were already on their way. Pass a promise that cannot reject (the
|
|
197
|
+
* owner handles its own failures) — boots resume either way.
|
|
198
|
+
*/
|
|
199
|
+
waitForModules(loading) {
|
|
200
|
+
this.modulesLoading = loading;
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Hand the live device to its own loop, if there is one and `runPeriodMs`
|
|
204
|
+
* says so — for a host whose drive mode turns self-paced after boot.
|
|
205
|
+
*/
|
|
206
|
+
startRun() {
|
|
207
|
+
if (this.handle) {
|
|
208
|
+
this.maybeRun(this.handle.device);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
maybeRun(device) {
|
|
212
|
+
if (this.runPeriodMs === null || device.running) {
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
device.run(this.runPeriodMs).catch((error) => {
|
|
216
|
+
this.onRunEnded?.(error);
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
enqueue(op) {
|
|
220
|
+
const run = this.opChain.then(op, op);
|
|
221
|
+
this.opChain = run.then(
|
|
222
|
+
() => void 0,
|
|
223
|
+
() => void 0
|
|
224
|
+
);
|
|
225
|
+
return run;
|
|
226
|
+
}
|
|
227
|
+
async boot(spec) {
|
|
228
|
+
await ensureWasmInit();
|
|
229
|
+
if (this.modulesLoading) {
|
|
230
|
+
await this.modulesLoading;
|
|
231
|
+
}
|
|
232
|
+
const device = await (0, import_runtime.startRuntime)(spec, void 0, this.modules);
|
|
233
|
+
this.handle = { device, spec, modules: this.modules };
|
|
234
|
+
this.onDeviceStarted?.(this.handle);
|
|
235
|
+
this.maybeRun(device);
|
|
236
|
+
return this.handle;
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Rebuild the device because its module set changed since boot — the one
|
|
240
|
+
* change a live device cannot take. Carries the store across (snapshot
|
|
241
|
+
* minus built-in keys) and replays module setup via `onDeviceStarted`. The
|
|
242
|
+
* old device's wrapper is freed; if it was self-paced its loop keeps
|
|
243
|
+
* ticking unreachable — which is why boots wait for announced modules
|
|
244
|
+
* instead of leaning on this path.
|
|
245
|
+
*/
|
|
246
|
+
async rebuild(old, spec) {
|
|
247
|
+
const carried = {};
|
|
248
|
+
for (const [path, value] of Object.entries(old.device.snapshot())) {
|
|
249
|
+
if (!isBuiltInPath(path)) {
|
|
250
|
+
carried[path] = value;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
const device = await (0, import_runtime.startRuntime)(spec, void 0, this.modules);
|
|
254
|
+
if (Object.keys(carried).length > 0) {
|
|
255
|
+
device.writeValues(carried);
|
|
256
|
+
}
|
|
257
|
+
this.handle = { device, spec, modules: this.modules };
|
|
258
|
+
old.device.dispose();
|
|
259
|
+
this.onDeviceStarted?.(this.handle);
|
|
260
|
+
this.maybeRun(device);
|
|
261
|
+
return this.handle;
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Make the device run `spec`: boot it if none is live, otherwise swap the
|
|
265
|
+
* graph in place (the store is the same store, so nothing is carried).
|
|
266
|
+
* No-op if the spec is identical by reference. Serialized behind any
|
|
267
|
+
* in-flight op (see `opChain`).
|
|
268
|
+
*/
|
|
269
|
+
recompose(spec) {
|
|
270
|
+
return this.enqueue(async () => {
|
|
271
|
+
const old = this.handle;
|
|
272
|
+
if (!old) {
|
|
273
|
+
return this.boot(spec);
|
|
274
|
+
}
|
|
275
|
+
if (old.modules !== this.modules) {
|
|
276
|
+
return this.rebuild(old, spec);
|
|
277
|
+
}
|
|
278
|
+
if (old.spec === spec) {
|
|
279
|
+
return old;
|
|
280
|
+
}
|
|
281
|
+
const edits = graphSpecDiff(old.spec, spec);
|
|
282
|
+
if (edits) {
|
|
283
|
+
try {
|
|
284
|
+
await old.device.applyGraphEdits(edits);
|
|
285
|
+
} catch (err) {
|
|
286
|
+
console.warn(
|
|
287
|
+
"[vizij-runtime] applyGraphEdits failed; reloading the whole graph",
|
|
288
|
+
err
|
|
289
|
+
);
|
|
290
|
+
await old.device.loadGraph(spec);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
old.spec = spec;
|
|
294
|
+
return old;
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
// src/utils/composeGraph.ts
|
|
300
|
+
function asArray(value) {
|
|
301
|
+
return Array.isArray(value) ? value.filter((n) => n && typeof n === "object") : [];
|
|
302
|
+
}
|
|
303
|
+
function prefixId(sourceId, id) {
|
|
304
|
+
return typeof id === "string" && id ? `${sourceId}::${id}` : id;
|
|
305
|
+
}
|
|
306
|
+
function prefixEndpoint(sourceId, endpoint) {
|
|
307
|
+
if (!endpoint || typeof endpoint !== "object") {
|
|
308
|
+
return endpoint;
|
|
309
|
+
}
|
|
310
|
+
const record = endpoint;
|
|
311
|
+
if (typeof record.node_id !== "string") {
|
|
312
|
+
return endpoint;
|
|
313
|
+
}
|
|
314
|
+
return { ...record, node_id: prefixId(sourceId, record.node_id) };
|
|
315
|
+
}
|
|
316
|
+
function outputPathsOf(spec) {
|
|
317
|
+
const paths = [];
|
|
318
|
+
for (const node of asArray(spec.nodes)) {
|
|
319
|
+
if (String(node.type ?? "").toLowerCase() !== "output") {
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
const path = node.params?.path;
|
|
323
|
+
if (typeof path === "string" && path.trim()) {
|
|
324
|
+
paths.push(path.trim());
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
return paths;
|
|
328
|
+
}
|
|
329
|
+
function composeGraphSpecs(sources) {
|
|
330
|
+
const nodes = [];
|
|
331
|
+
const edges = [];
|
|
332
|
+
const outputOwners = /* @__PURE__ */ new Map();
|
|
333
|
+
for (const { sourceId, spec } of sources) {
|
|
334
|
+
for (const path of outputPathsOf(spec)) {
|
|
335
|
+
const owner = outputOwners.get(path);
|
|
336
|
+
if (owner && owner !== sourceId) {
|
|
337
|
+
console.warn(
|
|
338
|
+
`[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.`
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
outputOwners.set(path, sourceId);
|
|
342
|
+
}
|
|
343
|
+
for (const node of asArray(spec.nodes)) {
|
|
344
|
+
nodes.push({ ...node, id: prefixId(sourceId, node.id) });
|
|
345
|
+
}
|
|
346
|
+
for (const edge of asArray(spec.edges)) {
|
|
347
|
+
edges.push({
|
|
348
|
+
...edge,
|
|
349
|
+
from: prefixEndpoint(sourceId, edge.from),
|
|
350
|
+
to: prefixEndpoint(sourceId, edge.to)
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
return { nodes, edges };
|
|
355
|
+
}
|
|
57
356
|
|
|
58
357
|
// src/context.ts
|
|
59
358
|
var import_react = require("react");
|
|
60
359
|
var VizijRuntimeContext = (0, import_react.createContext)(null);
|
|
61
360
|
|
|
361
|
+
// src/memoryInvestigation.ts
|
|
362
|
+
function getGlobalMemoryInvestigation() {
|
|
363
|
+
return globalThis;
|
|
364
|
+
}
|
|
365
|
+
function updateRuntimeMap(updater) {
|
|
366
|
+
const globalObj = getGlobalMemoryInvestigation();
|
|
367
|
+
if (!globalObj.__VIZIJ_MEMORY_INVESTIGATION__?.enabled) {
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
if (!globalObj.__vizijMemoryDebugState) {
|
|
371
|
+
globalObj.__vizijMemoryDebugState = {};
|
|
372
|
+
}
|
|
373
|
+
if (!globalObj.__vizijMemoryDebugState.runtimes) {
|
|
374
|
+
globalObj.__vizijMemoryDebugState.runtimes = {};
|
|
375
|
+
}
|
|
376
|
+
updater(globalObj.__vizijMemoryDebugState.runtimes);
|
|
377
|
+
}
|
|
378
|
+
function setRuntimeDebugState(runtimeId, state) {
|
|
379
|
+
updateRuntimeMap((runtimeMap) => {
|
|
380
|
+
runtimeMap[runtimeId] = state;
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
function clearRuntimeDebugState(runtimeId) {
|
|
384
|
+
updateRuntimeMap((runtimeMap) => {
|
|
385
|
+
delete runtimeMap[runtimeId];
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
|
|
62
389
|
// src/updatePolicy.ts
|
|
390
|
+
var blobIdentityMap = /* @__PURE__ */ new WeakMap();
|
|
391
|
+
var nextBlobIdentity = 0;
|
|
392
|
+
function getBlobIdentity(blob) {
|
|
393
|
+
if (!blob) {
|
|
394
|
+
return "missing";
|
|
395
|
+
}
|
|
396
|
+
let identity = blobIdentityMap.get(blob);
|
|
397
|
+
if (identity === void 0) {
|
|
398
|
+
identity = nextBlobIdentity++;
|
|
399
|
+
blobIdentityMap.set(blob, identity);
|
|
400
|
+
}
|
|
401
|
+
return `${identity}:${blob.size}:${blob.type}`;
|
|
402
|
+
}
|
|
63
403
|
function applyRuntimeGraphBundle(base, bundle) {
|
|
64
404
|
const next = {
|
|
65
405
|
...base
|
|
@@ -75,18 +415,10 @@ function applyRuntimeGraphBundle(base, bundle) {
|
|
|
75
415
|
"programs"
|
|
76
416
|
);
|
|
77
417
|
if (hasRigOverride) {
|
|
78
|
-
|
|
79
|
-
next.rig = bundle.rig;
|
|
80
|
-
} else {
|
|
81
|
-
delete next.rig;
|
|
82
|
-
}
|
|
418
|
+
next.rig = bundle.rig;
|
|
83
419
|
}
|
|
84
420
|
if (hasPoseOverride) {
|
|
85
|
-
|
|
86
|
-
next.pose = bundle.pose;
|
|
87
|
-
} else {
|
|
88
|
-
delete next.pose;
|
|
89
|
-
}
|
|
421
|
+
next.pose = bundle.pose;
|
|
90
422
|
}
|
|
91
423
|
if (hasAnimationsOverride) {
|
|
92
424
|
next.animations = Array.isArray(bundle.animations) ? bundle.animations : void 0;
|
|
@@ -108,10 +440,18 @@ function normalizeSpecPayload(value) {
|
|
|
108
440
|
}
|
|
109
441
|
function glbSignature(glb) {
|
|
110
442
|
if (glb.kind === "url") {
|
|
111
|
-
|
|
443
|
+
const importOptions = normalizeSpecPayload({
|
|
444
|
+
aggressiveImport: glb.aggressiveImport ?? false,
|
|
445
|
+
rootBounds: glb.rootBounds ?? null
|
|
446
|
+
});
|
|
447
|
+
return `url:${glb.src}:${importOptions}`;
|
|
112
448
|
}
|
|
113
449
|
if (glb.kind === "blob") {
|
|
114
|
-
|
|
450
|
+
const importOptions = normalizeSpecPayload({
|
|
451
|
+
aggressiveImport: glb.aggressiveImport ?? false,
|
|
452
|
+
rootBounds: glb.rootBounds ?? null
|
|
453
|
+
});
|
|
454
|
+
return `blob:${getBlobIdentity(glb.blob)}:${importOptions}`;
|
|
115
455
|
}
|
|
116
456
|
return `world:${normalizeSpecPayload(glb.world)}`;
|
|
117
457
|
}
|
|
@@ -605,65 +945,20 @@ function resolvePoseControlInputPath({
|
|
|
605
945
|
}
|
|
606
946
|
|
|
607
947
|
// src/utils/clipPlayback.ts
|
|
608
|
-
var EPSILON = 1e-6;
|
|
609
948
|
function toFiniteNumber(value, fallback) {
|
|
610
949
|
const parsed = Number(value);
|
|
611
950
|
return Number.isFinite(parsed) ? parsed : fallback;
|
|
612
951
|
}
|
|
613
|
-
function
|
|
614
|
-
const mode = typeof interpolation === "string" ? interpolation.trim().toLowerCase() : "linear";
|
|
615
|
-
if (mode === "step") {
|
|
616
|
-
return "step";
|
|
617
|
-
}
|
|
618
|
-
if (mode === "cubic" || mode === "cubicspline") {
|
|
619
|
-
return "cubic";
|
|
620
|
-
}
|
|
621
|
-
return "linear";
|
|
622
|
-
}
|
|
623
|
-
function asNumericKeyframe(keyframe) {
|
|
624
|
-
if (!keyframe || typeof keyframe !== "object") {
|
|
625
|
-
return null;
|
|
626
|
-
}
|
|
627
|
-
const time = Number(keyframe.time);
|
|
628
|
-
const value = Number(keyframe.value);
|
|
629
|
-
if (!Number.isFinite(time) || !Number.isFinite(value)) {
|
|
630
|
-
return null;
|
|
631
|
-
}
|
|
632
|
-
const inTangentRaw = keyframe.inTangent;
|
|
633
|
-
const outTangentRaw = keyframe.outTangent;
|
|
634
|
-
const inTangent = inTangentRaw == null || Number.isFinite(Number(inTangentRaw)) ? inTangentRaw : void 0;
|
|
635
|
-
const outTangent = outTangentRaw == null || Number.isFinite(Number(outTangentRaw)) ? outTangentRaw : void 0;
|
|
636
|
-
return {
|
|
637
|
-
time,
|
|
638
|
-
value,
|
|
639
|
-
inTangent,
|
|
640
|
-
outTangent
|
|
641
|
-
};
|
|
642
|
-
}
|
|
643
|
-
function getNumericKeyframes(track) {
|
|
952
|
+
function lastKeyframeTime(track) {
|
|
644
953
|
const keyframes = Array.isArray(track.keyframes) ? track.keyframes : [];
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
if (Number.isFinite(parsed)) {
|
|
654
|
-
return parsed;
|
|
655
|
-
}
|
|
656
|
-
return fallback;
|
|
657
|
-
}
|
|
658
|
-
function sampleHermite(startValue, endValue, outTangent, inTangent, factor, duration) {
|
|
659
|
-
const t = factor;
|
|
660
|
-
const t2 = t * t;
|
|
661
|
-
const t3 = t2 * t;
|
|
662
|
-
const h00 = 2 * t3 - 3 * t2 + 1;
|
|
663
|
-
const h10 = t3 - 2 * t2 + t;
|
|
664
|
-
const h01 = -2 * t3 + 3 * t2;
|
|
665
|
-
const h11 = t3 - t2;
|
|
666
|
-
return h00 * startValue + h10 * outTangent * duration + h01 * endValue + h11 * inTangent * duration;
|
|
954
|
+
let maxTime = 0;
|
|
955
|
+
keyframes.forEach((keyframe) => {
|
|
956
|
+
const time = Number(keyframe?.time);
|
|
957
|
+
if (Number.isFinite(time) && time > maxTime) {
|
|
958
|
+
maxTime = time;
|
|
959
|
+
}
|
|
960
|
+
});
|
|
961
|
+
return maxTime;
|
|
667
962
|
}
|
|
668
963
|
function resolveClipDurationSeconds(clip, fallbackDurationSeconds = 0) {
|
|
669
964
|
const fallback = Math.max(0, toFiniteNumber(fallbackDurationSeconds, 0));
|
|
@@ -677,109 +972,13 @@ function resolveClipDurationSeconds(clip, fallbackDurationSeconds = 0) {
|
|
|
677
972
|
const tracks = Array.isArray(clip.tracks) ? clip.tracks : [];
|
|
678
973
|
let maxTime = 0;
|
|
679
974
|
tracks.forEach((track) => {
|
|
680
|
-
const
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
maxTime = last.time;
|
|
975
|
+
const time = lastKeyframeTime(track);
|
|
976
|
+
if (time > maxTime) {
|
|
977
|
+
maxTime = time;
|
|
684
978
|
}
|
|
685
979
|
});
|
|
686
980
|
return maxTime > 0 ? maxTime : fallback;
|
|
687
981
|
}
|
|
688
|
-
function clampAnimationTime(time, duration) {
|
|
689
|
-
if (!Number.isFinite(duration) || duration <= 0) {
|
|
690
|
-
return 0;
|
|
691
|
-
}
|
|
692
|
-
if (!Number.isFinite(time)) {
|
|
693
|
-
return 0;
|
|
694
|
-
}
|
|
695
|
-
if (time <= 0) {
|
|
696
|
-
return 0;
|
|
697
|
-
}
|
|
698
|
-
if (time >= duration) {
|
|
699
|
-
return duration;
|
|
700
|
-
}
|
|
701
|
-
return time;
|
|
702
|
-
}
|
|
703
|
-
function advanceClipTime(state, dt) {
|
|
704
|
-
const duration = Math.max(0, toFiniteNumber(state.duration, 0));
|
|
705
|
-
const speed = Number.isFinite(state.speed) && state.speed > 0 ? state.speed : 1;
|
|
706
|
-
const currentTime = clampAnimationTime(state.time, duration);
|
|
707
|
-
const delta = Number.isFinite(dt) && dt > 0 ? Math.max(0, dt) * speed : 0;
|
|
708
|
-
if (!state.playing || delta <= 0) {
|
|
709
|
-
return { time: currentTime, completed: false };
|
|
710
|
-
}
|
|
711
|
-
if (duration <= 0) {
|
|
712
|
-
return { time: 0, completed: true };
|
|
713
|
-
}
|
|
714
|
-
const nextTime = currentTime + delta;
|
|
715
|
-
if (state.loop) {
|
|
716
|
-
if (nextTime < duration) {
|
|
717
|
-
return { time: nextTime, completed: false };
|
|
718
|
-
}
|
|
719
|
-
const wrapped = (nextTime % duration + duration) % duration;
|
|
720
|
-
return { time: wrapped, completed: false };
|
|
721
|
-
}
|
|
722
|
-
if (nextTime >= duration - EPSILON) {
|
|
723
|
-
return { time: duration, completed: true };
|
|
724
|
-
}
|
|
725
|
-
return { time: nextTime, completed: false };
|
|
726
|
-
}
|
|
727
|
-
function sampleTrackAtTime(track, timeSeconds) {
|
|
728
|
-
const keyframes = getNumericKeyframes(track);
|
|
729
|
-
if (keyframes.length === 0) {
|
|
730
|
-
return 0;
|
|
731
|
-
}
|
|
732
|
-
if (keyframes.length === 1) {
|
|
733
|
-
return keyframes[0].value;
|
|
734
|
-
}
|
|
735
|
-
const mode = normaliseInterpolation(track.interpolation);
|
|
736
|
-
const time = Number.isFinite(timeSeconds) ? timeSeconds : 0;
|
|
737
|
-
const first = keyframes[0];
|
|
738
|
-
if (time <= first.time + EPSILON) {
|
|
739
|
-
return first.value;
|
|
740
|
-
}
|
|
741
|
-
const last = keyframes[keyframes.length - 1];
|
|
742
|
-
if (time >= last.time - EPSILON) {
|
|
743
|
-
return last.value;
|
|
744
|
-
}
|
|
745
|
-
for (let i = 0; i < keyframes.length - 1; i += 1) {
|
|
746
|
-
const current = keyframes[i];
|
|
747
|
-
const next = keyframes[i + 1];
|
|
748
|
-
const start = current.time;
|
|
749
|
-
const end = next.time;
|
|
750
|
-
const duration = end - start;
|
|
751
|
-
if (duration <= EPSILON) {
|
|
752
|
-
if (time <= end + EPSILON) {
|
|
753
|
-
return next.value;
|
|
754
|
-
}
|
|
755
|
-
continue;
|
|
756
|
-
}
|
|
757
|
-
if (Math.abs(time - end) <= EPSILON) {
|
|
758
|
-
return next.value;
|
|
759
|
-
}
|
|
760
|
-
if (time < end) {
|
|
761
|
-
const factor = (time - start) / duration;
|
|
762
|
-
if (mode === "step") {
|
|
763
|
-
return current.value;
|
|
764
|
-
}
|
|
765
|
-
if (mode === "cubic") {
|
|
766
|
-
const slope = (next.value - current.value) / duration;
|
|
767
|
-
const outTangent = resolveTangent(current.outTangent, slope);
|
|
768
|
-
const inTangent = resolveTangent(next.inTangent, slope);
|
|
769
|
-
return sampleHermite(
|
|
770
|
-
current.value,
|
|
771
|
-
next.value,
|
|
772
|
-
outTangent,
|
|
773
|
-
inTangent,
|
|
774
|
-
factor,
|
|
775
|
-
duration
|
|
776
|
-
);
|
|
777
|
-
}
|
|
778
|
-
return current.value + (next.value - current.value) * factor;
|
|
779
|
-
}
|
|
780
|
-
}
|
|
781
|
-
return last.value;
|
|
782
|
-
}
|
|
783
982
|
|
|
784
983
|
// src/utils/animationBridge.ts
|
|
785
984
|
function resolveAnimationBridgeOutputPaths(channel, faceId, rigInputMap) {
|
|
@@ -850,53 +1049,6 @@ function collectAnimationClipOutputPaths(clip, faceId, rigInputMap) {
|
|
|
850
1049
|
(left, right) => left.localeCompare(right)
|
|
851
1050
|
);
|
|
852
1051
|
}
|
|
853
|
-
function sampleAnimationClipOutputValues(clip, timeSeconds, weight = 1, faceId, rigInputMap) {
|
|
854
|
-
const appliedWeight = Number.isFinite(weight) && weight >= 0 ? Number(weight) : 1;
|
|
855
|
-
const outputValues = /* @__PURE__ */ new Map();
|
|
856
|
-
const tracks = Array.isArray(clip.tracks) ? clip.tracks : [];
|
|
857
|
-
tracks.forEach((track) => {
|
|
858
|
-
const channel = typeof track.channel === "string" ? track.channel.trim() : "";
|
|
859
|
-
if (!channel) {
|
|
860
|
-
return;
|
|
861
|
-
}
|
|
862
|
-
const sampledValue = sampleTrackAtTime(
|
|
863
|
-
track,
|
|
864
|
-
timeSeconds
|
|
865
|
-
);
|
|
866
|
-
const weightedValue = sampledValue * appliedWeight;
|
|
867
|
-
resolveAnimationBridgeOutputPaths(channel, faceId, rigInputMap).forEach(
|
|
868
|
-
(path) => {
|
|
869
|
-
outputValues.set(path, (outputValues.get(path) ?? 0) + weightedValue);
|
|
870
|
-
}
|
|
871
|
-
);
|
|
872
|
-
});
|
|
873
|
-
return outputValues;
|
|
874
|
-
}
|
|
875
|
-
function diffAnimationAggregateValues(previousAggregate, nextAggregate, epsilon = 1e-6) {
|
|
876
|
-
const operations = [];
|
|
877
|
-
const changedPaths = /* @__PURE__ */ new Set();
|
|
878
|
-
previousAggregate.forEach((previousValue, path) => {
|
|
879
|
-
const nextValue = nextAggregate.get(path);
|
|
880
|
-
if (nextValue === void 0 || Math.abs(nextValue - previousValue) > epsilon) {
|
|
881
|
-
changedPaths.add(path);
|
|
882
|
-
}
|
|
883
|
-
});
|
|
884
|
-
nextAggregate.forEach((nextValue, path) => {
|
|
885
|
-
const previousValue = previousAggregate.get(path);
|
|
886
|
-
if (previousValue === void 0 || Math.abs(nextValue - previousValue) > epsilon) {
|
|
887
|
-
changedPaths.add(path);
|
|
888
|
-
}
|
|
889
|
-
});
|
|
890
|
-
changedPaths.forEach((path) => {
|
|
891
|
-
const nextValue = nextAggregate.get(path);
|
|
892
|
-
if (nextValue === void 0) {
|
|
893
|
-
operations.push({ kind: "clear", path });
|
|
894
|
-
return;
|
|
895
|
-
}
|
|
896
|
-
operations.push({ kind: "set", path, value: nextValue });
|
|
897
|
-
});
|
|
898
|
-
return operations;
|
|
899
|
-
}
|
|
900
1052
|
|
|
901
1053
|
// src/utils/valueConversion.ts
|
|
902
1054
|
var import_value_json = require("@vizij/value-json");
|
|
@@ -944,27 +1096,28 @@ function valueJSONToRaw(value) {
|
|
|
944
1096
|
if (Array.isArray(value)) {
|
|
945
1097
|
return value.map((entry) => valueJSONToRaw(entry));
|
|
946
1098
|
}
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
1099
|
+
const normalized = (0, import_value_json.isNormalizedValue)(value) ? value : (0, import_value_json.fromAroraValueJSON)(value);
|
|
1100
|
+
if (normalized === void 0) {
|
|
1101
|
+
if (typeof value === "object" && !("type" in value)) {
|
|
1102
|
+
const entries = Object.entries(value).map(([key, entry]) => [
|
|
1103
|
+
key,
|
|
1104
|
+
valueJSONToRaw(entry)
|
|
1105
|
+
]);
|
|
1106
|
+
return Object.fromEntries(entries);
|
|
1107
|
+
}
|
|
955
1108
|
return void 0;
|
|
956
1109
|
}
|
|
957
|
-
switch (
|
|
1110
|
+
switch (normalized.type) {
|
|
958
1111
|
case "float": {
|
|
959
|
-
const num = (0, import_value_json.valueAsNumber)(
|
|
1112
|
+
const num = (0, import_value_json.valueAsNumber)(normalized);
|
|
960
1113
|
return typeof num === "number" ? num : void 0;
|
|
961
1114
|
}
|
|
962
1115
|
case "bool": {
|
|
963
|
-
const boolVal = (0, import_value_json.valueAsBool)(
|
|
1116
|
+
const boolVal = (0, import_value_json.valueAsBool)(normalized);
|
|
964
1117
|
return typeof boolVal === "boolean" ? boolVal : void 0;
|
|
965
1118
|
}
|
|
966
1119
|
case "text": {
|
|
967
|
-
const text = (0, import_value_json.valueAsText)(
|
|
1120
|
+
const text = (0, import_value_json.valueAsText)(normalized);
|
|
968
1121
|
return typeof text === "string" ? text : void 0;
|
|
969
1122
|
}
|
|
970
1123
|
case "vec2":
|
|
@@ -972,11 +1125,11 @@ function valueJSONToRaw(value) {
|
|
|
972
1125
|
case "vec4":
|
|
973
1126
|
case "quat":
|
|
974
1127
|
case "vector": {
|
|
975
|
-
const vec = (0, import_value_json.valueAsVector)(
|
|
1128
|
+
const vec = (0, import_value_json.valueAsVector)(normalized);
|
|
976
1129
|
return vec ? numericArrayToRaw(vec) : void 0;
|
|
977
1130
|
}
|
|
978
1131
|
case "colorrgba": {
|
|
979
|
-
const color = (0, import_value_json.valueAsColorRgba)(
|
|
1132
|
+
const color = (0, import_value_json.valueAsColorRgba)(normalized);
|
|
980
1133
|
if (!color) {
|
|
981
1134
|
return void 0;
|
|
982
1135
|
}
|
|
@@ -984,7 +1137,7 @@ function valueJSONToRaw(value) {
|
|
|
984
1137
|
return { r, g, b, a };
|
|
985
1138
|
}
|
|
986
1139
|
case "transform": {
|
|
987
|
-
const transform = (0, import_value_json.valueAsTransform)(
|
|
1140
|
+
const transform = (0, import_value_json.valueAsTransform)(normalized);
|
|
988
1141
|
if (!transform) {
|
|
989
1142
|
return void 0;
|
|
990
1143
|
}
|
|
@@ -994,15 +1147,20 @@ function valueJSONToRaw(value) {
|
|
|
994
1147
|
scale: numericArrayToRaw(transform.scale)
|
|
995
1148
|
};
|
|
996
1149
|
}
|
|
1150
|
+
case "array":
|
|
1151
|
+
case "list":
|
|
1152
|
+
case "tuple":
|
|
1153
|
+
return (normalized.data ?? []).map(
|
|
1154
|
+
(entry) => valueJSONToRaw(entry)
|
|
1155
|
+
);
|
|
997
1156
|
case "record": {
|
|
998
|
-
const entries = Object.entries(
|
|
999
|
-
key,
|
|
1000
|
-
|
|
1001
|
-
]);
|
|
1157
|
+
const entries = Object.entries(normalized.data ?? {}).map(
|
|
1158
|
+
([key, entry]) => [key, valueJSONToRaw(entry)]
|
|
1159
|
+
);
|
|
1002
1160
|
return Object.fromEntries(entries);
|
|
1003
1161
|
}
|
|
1004
1162
|
case "enum": {
|
|
1005
|
-
const [tag, inner] =
|
|
1163
|
+
const [tag, inner] = normalized.data;
|
|
1006
1164
|
return {
|
|
1007
1165
|
tag,
|
|
1008
1166
|
value: valueJSONToRaw(inner)
|
|
@@ -1013,17 +1171,608 @@ function valueJSONToRaw(value) {
|
|
|
1013
1171
|
}
|
|
1014
1172
|
}
|
|
1015
1173
|
|
|
1174
|
+
// src/engine/animationModule.ts
|
|
1175
|
+
var ANIMATION_MODULE_FN = {
|
|
1176
|
+
loadAnimation: "76697a69-6a00-0000-0f00-000000000001",
|
|
1177
|
+
createPlayer: "76697a69-6a00-0000-0f00-000000000002",
|
|
1178
|
+
addInstance: "76697a69-6a00-0000-0f00-000000000003",
|
|
1179
|
+
step: "76697a69-6a00-0000-0f00-000000000004",
|
|
1180
|
+
play: "76697a69-6a00-0000-0f00-000000000005",
|
|
1181
|
+
pause: "76697a69-6a00-0000-0f00-000000000006",
|
|
1182
|
+
stop: "76697a69-6a00-0000-0f00-000000000007",
|
|
1183
|
+
seek: "76697a69-6a00-0000-0f00-000000000008",
|
|
1184
|
+
setSpeed: "76697a69-6a00-0000-0f00-000000000009",
|
|
1185
|
+
setLoop: "76697a69-6a00-0000-0f00-00000000000a",
|
|
1186
|
+
setWeight: "76697a69-6a00-0000-0f00-00000000000b",
|
|
1187
|
+
removeInstance: "76697a69-6a00-0000-0f00-00000000000c",
|
|
1188
|
+
playerStates: "76697a69-6a00-0000-0f00-00000000000d"
|
|
1189
|
+
};
|
|
1190
|
+
var ANIMATION_MODULE_PARAM = {
|
|
1191
|
+
clip: "76697a69-6a00-0000-0f01-000000000001",
|
|
1192
|
+
playerName: "76697a69-6a00-0000-0f02-000000000001",
|
|
1193
|
+
player: "76697a69-6a00-0000-0f03-000000000001",
|
|
1194
|
+
anim: "76697a69-6a00-0000-0f03-000000000002",
|
|
1195
|
+
dtNs: "76697a69-6a00-0000-0f04-000000000001",
|
|
1196
|
+
playPlayer: "76697a69-6a00-0000-0f05-000000000001",
|
|
1197
|
+
pausePlayer: "76697a69-6a00-0000-0f06-000000000001",
|
|
1198
|
+
stopPlayer: "76697a69-6a00-0000-0f07-000000000001",
|
|
1199
|
+
seekPlayer: "76697a69-6a00-0000-0f08-000000000001",
|
|
1200
|
+
seekTimeNs: "76697a69-6a00-0000-0f08-000000000002",
|
|
1201
|
+
speedPlayer: "76697a69-6a00-0000-0f09-000000000001",
|
|
1202
|
+
speedValue: "76697a69-6a00-0000-0f09-000000000002",
|
|
1203
|
+
loopPlayer: "76697a69-6a00-0000-0f0a-000000000001",
|
|
1204
|
+
loopMode: "76697a69-6a00-0000-0f0a-000000000002",
|
|
1205
|
+
weightPlayer: "76697a69-6a00-0000-0f0b-000000000001",
|
|
1206
|
+
weightInstance: "76697a69-6a00-0000-0f0b-000000000002",
|
|
1207
|
+
weightValue: "76697a69-6a00-0000-0f0b-000000000003",
|
|
1208
|
+
removePlayer: "76697a69-6a00-0000-0f0c-000000000001",
|
|
1209
|
+
removeInstance: "76697a69-6a00-0000-0f0c-000000000002"
|
|
1210
|
+
};
|
|
1211
|
+
var ANIMATION_MODULE_TYPE = {
|
|
1212
|
+
clip: "76697a69-6a00-0000-0000-000000000100",
|
|
1213
|
+
track: "76697a69-6a00-0000-0000-000000000101",
|
|
1214
|
+
keypoint: "76697a69-6a00-0000-0000-000000000102",
|
|
1215
|
+
transitionHandle: "76697a69-6a00-0000-0000-000000000103",
|
|
1216
|
+
trackOutput: "76697a69-6a00-0000-0000-000000000110",
|
|
1217
|
+
playerState: "76697a69-6a00-0000-0000-000000000111"
|
|
1218
|
+
};
|
|
1219
|
+
var ANIMATION_MODULE_FIELD = {
|
|
1220
|
+
clipName: "76697a69-6a00-0000-0100-000000000001",
|
|
1221
|
+
clipDuration: "76697a69-6a00-0000-0100-000000000002",
|
|
1222
|
+
clipTracks: "76697a69-6a00-0000-0100-000000000003",
|
|
1223
|
+
trackId: "76697a69-6a00-0000-0101-000000000001",
|
|
1224
|
+
trackName: "76697a69-6a00-0000-0101-000000000002",
|
|
1225
|
+
trackAnimatable: "76697a69-6a00-0000-0101-000000000003",
|
|
1226
|
+
trackPoints: "76697a69-6a00-0000-0101-000000000004",
|
|
1227
|
+
keypointId: "76697a69-6a00-0000-0102-000000000001",
|
|
1228
|
+
keypointStamp: "76697a69-6a00-0000-0102-000000000002",
|
|
1229
|
+
keypointValue: "76697a69-6a00-0000-0102-000000000003",
|
|
1230
|
+
keypointTransitionsIn: "76697a69-6a00-0000-0102-000000000004",
|
|
1231
|
+
keypointTransitionsOut: "76697a69-6a00-0000-0102-000000000005",
|
|
1232
|
+
handleX: "76697a69-6a00-0000-0103-000000000001",
|
|
1233
|
+
handleY: "76697a69-6a00-0000-0103-000000000002",
|
|
1234
|
+
outputTrackId: "76697a69-6a00-0000-0110-000000000001",
|
|
1235
|
+
outputDefaultKey: "76697a69-6a00-0000-0110-000000000002",
|
|
1236
|
+
outputValue: "76697a69-6a00-0000-0110-000000000003",
|
|
1237
|
+
statePlayer: "76697a69-6a00-0000-0111-000000000001",
|
|
1238
|
+
stateState: "76697a69-6a00-0000-0111-000000000002",
|
|
1239
|
+
stateTimeNs: "76697a69-6a00-0000-0111-000000000003",
|
|
1240
|
+
stateDurationNs: "76697a69-6a00-0000-0111-000000000004",
|
|
1241
|
+
stateSpeed: "76697a69-6a00-0000-0111-000000000005"
|
|
1242
|
+
};
|
|
1243
|
+
var field = (id, value) => ({
|
|
1244
|
+
id,
|
|
1245
|
+
value
|
|
1246
|
+
});
|
|
1247
|
+
function storedClipToModuleValue(clip, resolveKeys) {
|
|
1248
|
+
const name = typeof clip.name === "string" && clip.name.trim().length > 0 ? clip.name.trim() : typeof clip.id === "string" ? clip.id : "";
|
|
1249
|
+
const durationMs = Number(clip.duration);
|
|
1250
|
+
const duration = Number.isFinite(durationMs) && durationMs > 0 ? Math.round(durationMs) : 1;
|
|
1251
|
+
const tracks = (Array.isArray(clip.tracks) ? clip.tracks : []).map((rawTrack, index) => {
|
|
1252
|
+
if (!rawTrack || typeof rawTrack !== "object") {
|
|
1253
|
+
return null;
|
|
1254
|
+
}
|
|
1255
|
+
const animatableId = typeof rawTrack.animatableId === "string" ? rawTrack.animatableId.trim() : "";
|
|
1256
|
+
if (!animatableId) {
|
|
1257
|
+
return null;
|
|
1258
|
+
}
|
|
1259
|
+
const trackId = typeof rawTrack.id === "string" && rawTrack.id.trim().length > 0 ? rawTrack.id.trim() : `track-${index}`;
|
|
1260
|
+
const trackName = typeof rawTrack.name === "string" && rawTrack.name.trim().length > 0 ? rawTrack.name.trim() : trackId;
|
|
1261
|
+
const points = (Array.isArray(rawTrack.points) ? rawTrack.points : []).map((rawPoint, pointIndex) => {
|
|
1262
|
+
if (!rawPoint || typeof rawPoint !== "object") {
|
|
1263
|
+
return null;
|
|
1264
|
+
}
|
|
1265
|
+
const stamp = Number(rawPoint.stamp);
|
|
1266
|
+
const value = Number(rawPoint.value);
|
|
1267
|
+
if (!Number.isFinite(stamp) || !Number.isFinite(value)) {
|
|
1268
|
+
return null;
|
|
1269
|
+
}
|
|
1270
|
+
const pointId = typeof rawPoint.id === "string" && rawPoint.id.trim().length > 0 ? rawPoint.id.trim() : `${trackId}:point-${pointIndex}`;
|
|
1271
|
+
const handles = (handle) => ({
|
|
1272
|
+
structs: {
|
|
1273
|
+
id: ANIMATION_MODULE_TYPE.transitionHandle,
|
|
1274
|
+
elements: handle ? [
|
|
1275
|
+
{
|
|
1276
|
+
fields: [
|
|
1277
|
+
field(ANIMATION_MODULE_FIELD.handleX, {
|
|
1278
|
+
f32: handle.x
|
|
1279
|
+
}),
|
|
1280
|
+
field(ANIMATION_MODULE_FIELD.handleY, {
|
|
1281
|
+
f32: handle.y
|
|
1282
|
+
})
|
|
1283
|
+
]
|
|
1284
|
+
}
|
|
1285
|
+
] : []
|
|
1286
|
+
}
|
|
1287
|
+
});
|
|
1288
|
+
return {
|
|
1289
|
+
fields: [
|
|
1290
|
+
field(ANIMATION_MODULE_FIELD.keypointId, { str: pointId }),
|
|
1291
|
+
field(ANIMATION_MODULE_FIELD.keypointStamp, {
|
|
1292
|
+
f32: Math.max(0, Math.min(1, stamp))
|
|
1293
|
+
}),
|
|
1294
|
+
field(ANIMATION_MODULE_FIELD.keypointValue, { f32: value }),
|
|
1295
|
+
field(
|
|
1296
|
+
ANIMATION_MODULE_FIELD.keypointTransitionsIn,
|
|
1297
|
+
handles(rawPoint.transitions?.in)
|
|
1298
|
+
),
|
|
1299
|
+
field(
|
|
1300
|
+
ANIMATION_MODULE_FIELD.keypointTransitionsOut,
|
|
1301
|
+
handles(rawPoint.transitions?.out)
|
|
1302
|
+
)
|
|
1303
|
+
]
|
|
1304
|
+
};
|
|
1305
|
+
}).filter(Boolean);
|
|
1306
|
+
if (points.length === 0) {
|
|
1307
|
+
return null;
|
|
1308
|
+
}
|
|
1309
|
+
const targets = resolveKeys?.(animatableId) ?? [animatableId];
|
|
1310
|
+
return targets.map((target, targetIndex) => ({
|
|
1311
|
+
fields: [
|
|
1312
|
+
field(ANIMATION_MODULE_FIELD.trackId, {
|
|
1313
|
+
str: targetIndex === 0 ? trackId : `${trackId}~${targetIndex}`
|
|
1314
|
+
}),
|
|
1315
|
+
field(ANIMATION_MODULE_FIELD.trackName, { str: trackName }),
|
|
1316
|
+
field(ANIMATION_MODULE_FIELD.trackAnimatable, { str: target }),
|
|
1317
|
+
field(ANIMATION_MODULE_FIELD.trackPoints, {
|
|
1318
|
+
structs: {
|
|
1319
|
+
id: ANIMATION_MODULE_TYPE.keypoint,
|
|
1320
|
+
elements: points
|
|
1321
|
+
}
|
|
1322
|
+
})
|
|
1323
|
+
]
|
|
1324
|
+
}));
|
|
1325
|
+
}).filter(Boolean).flat();
|
|
1326
|
+
return {
|
|
1327
|
+
struct: {
|
|
1328
|
+
id: ANIMATION_MODULE_TYPE.clip,
|
|
1329
|
+
fields: [
|
|
1330
|
+
field(ANIMATION_MODULE_FIELD.clipName, { str: name }),
|
|
1331
|
+
field(ANIMATION_MODULE_FIELD.clipDuration, { u32: duration }),
|
|
1332
|
+
field(ANIMATION_MODULE_FIELD.clipTracks, {
|
|
1333
|
+
structs: {
|
|
1334
|
+
id: ANIMATION_MODULE_TYPE.track,
|
|
1335
|
+
elements: tracks
|
|
1336
|
+
}
|
|
1337
|
+
})
|
|
1338
|
+
]
|
|
1339
|
+
}
|
|
1340
|
+
};
|
|
1341
|
+
}
|
|
1342
|
+
function loadAnimationCall(clipValue) {
|
|
1343
|
+
return {
|
|
1344
|
+
id: ANIMATION_MODULE_FN.loadAnimation,
|
|
1345
|
+
args: [field(ANIMATION_MODULE_PARAM.clip, clipValue)]
|
|
1346
|
+
};
|
|
1347
|
+
}
|
|
1348
|
+
function createPlayerCall(name) {
|
|
1349
|
+
return {
|
|
1350
|
+
id: ANIMATION_MODULE_FN.createPlayer,
|
|
1351
|
+
args: [field(ANIMATION_MODULE_PARAM.playerName, { str: name })]
|
|
1352
|
+
};
|
|
1353
|
+
}
|
|
1354
|
+
function addInstanceCall(player, anim) {
|
|
1355
|
+
return {
|
|
1356
|
+
id: ANIMATION_MODULE_FN.addInstance,
|
|
1357
|
+
args: [
|
|
1358
|
+
field(ANIMATION_MODULE_PARAM.player, { u32: player }),
|
|
1359
|
+
field(ANIMATION_MODULE_PARAM.anim, { u32: anim })
|
|
1360
|
+
]
|
|
1361
|
+
};
|
|
1362
|
+
}
|
|
1363
|
+
function playCall(player) {
|
|
1364
|
+
return {
|
|
1365
|
+
id: ANIMATION_MODULE_FN.play,
|
|
1366
|
+
args: [field(ANIMATION_MODULE_PARAM.playPlayer, { u32: player })]
|
|
1367
|
+
};
|
|
1368
|
+
}
|
|
1369
|
+
function pauseCall(player) {
|
|
1370
|
+
return {
|
|
1371
|
+
id: ANIMATION_MODULE_FN.pause,
|
|
1372
|
+
args: [field(ANIMATION_MODULE_PARAM.pausePlayer, { u32: player })]
|
|
1373
|
+
};
|
|
1374
|
+
}
|
|
1375
|
+
function stopCall(player) {
|
|
1376
|
+
return {
|
|
1377
|
+
id: ANIMATION_MODULE_FN.stop,
|
|
1378
|
+
args: [field(ANIMATION_MODULE_PARAM.stopPlayer, { u32: player })]
|
|
1379
|
+
};
|
|
1380
|
+
}
|
|
1381
|
+
function seekCall(player, timeNs) {
|
|
1382
|
+
return {
|
|
1383
|
+
id: ANIMATION_MODULE_FN.seek,
|
|
1384
|
+
args: [
|
|
1385
|
+
field(ANIMATION_MODULE_PARAM.seekPlayer, { u32: player }),
|
|
1386
|
+
field(ANIMATION_MODULE_PARAM.seekTimeNs, {
|
|
1387
|
+
u64: Math.max(0, Math.round(timeNs))
|
|
1388
|
+
})
|
|
1389
|
+
]
|
|
1390
|
+
};
|
|
1391
|
+
}
|
|
1392
|
+
function setSpeedCall(player, speed) {
|
|
1393
|
+
return {
|
|
1394
|
+
id: ANIMATION_MODULE_FN.setSpeed,
|
|
1395
|
+
args: [
|
|
1396
|
+
field(ANIMATION_MODULE_PARAM.speedPlayer, { u32: player }),
|
|
1397
|
+
field(ANIMATION_MODULE_PARAM.speedValue, { f32: speed })
|
|
1398
|
+
]
|
|
1399
|
+
};
|
|
1400
|
+
}
|
|
1401
|
+
function setLoopCall(player, mode) {
|
|
1402
|
+
return {
|
|
1403
|
+
id: ANIMATION_MODULE_FN.setLoop,
|
|
1404
|
+
args: [
|
|
1405
|
+
field(ANIMATION_MODULE_PARAM.loopPlayer, { u32: player }),
|
|
1406
|
+
field(ANIMATION_MODULE_PARAM.loopMode, { str: mode })
|
|
1407
|
+
]
|
|
1408
|
+
};
|
|
1409
|
+
}
|
|
1410
|
+
function setWeightCall(player, instance, weight) {
|
|
1411
|
+
return {
|
|
1412
|
+
id: ANIMATION_MODULE_FN.setWeight,
|
|
1413
|
+
args: [
|
|
1414
|
+
field(ANIMATION_MODULE_PARAM.weightPlayer, { u32: player }),
|
|
1415
|
+
field(ANIMATION_MODULE_PARAM.weightInstance, { u32: instance }),
|
|
1416
|
+
field(ANIMATION_MODULE_PARAM.weightValue, { f32: weight })
|
|
1417
|
+
]
|
|
1418
|
+
};
|
|
1419
|
+
}
|
|
1420
|
+
function callResultU32(result) {
|
|
1421
|
+
const ret = result.ret;
|
|
1422
|
+
if (ret && typeof ret === "object" && "u32" in ret) {
|
|
1423
|
+
const value = Number(ret.u32);
|
|
1424
|
+
return Number.isFinite(value) ? value : null;
|
|
1425
|
+
}
|
|
1426
|
+
return null;
|
|
1427
|
+
}
|
|
1428
|
+
var ANIMATION_PLAYERS_PATH = "vizij/animations/players";
|
|
1429
|
+
var ANIMATIONS_SOURCE_ID = "animations";
|
|
1430
|
+
function animationsGraphSource() {
|
|
1431
|
+
return {
|
|
1432
|
+
sourceId: ANIMATIONS_SOURCE_ID,
|
|
1433
|
+
spec: {
|
|
1434
|
+
nodes: [
|
|
1435
|
+
{ id: "dt", type: "input", params: { path: "arora/dt" } },
|
|
1436
|
+
{
|
|
1437
|
+
id: "step",
|
|
1438
|
+
type: "externalfunction",
|
|
1439
|
+
params: {
|
|
1440
|
+
function: ANIMATION_MODULE_FN.step,
|
|
1441
|
+
param_ids: [ANIMATION_MODULE_PARAM.dtNs]
|
|
1442
|
+
}
|
|
1443
|
+
},
|
|
1444
|
+
{
|
|
1445
|
+
id: "apply",
|
|
1446
|
+
type: "output",
|
|
1447
|
+
params: {
|
|
1448
|
+
key_field: ANIMATION_MODULE_FIELD.outputDefaultKey,
|
|
1449
|
+
value_field: ANIMATION_MODULE_FIELD.outputValue
|
|
1450
|
+
}
|
|
1451
|
+
},
|
|
1452
|
+
{
|
|
1453
|
+
id: "states",
|
|
1454
|
+
type: "externalfunction",
|
|
1455
|
+
params: { function: ANIMATION_MODULE_FN.playerStates, param_ids: [] }
|
|
1456
|
+
},
|
|
1457
|
+
{
|
|
1458
|
+
id: "states-out",
|
|
1459
|
+
type: "output",
|
|
1460
|
+
params: { path: ANIMATION_PLAYERS_PATH }
|
|
1461
|
+
}
|
|
1462
|
+
],
|
|
1463
|
+
edges: [
|
|
1464
|
+
{ from: { node_id: "dt" }, to: { node_id: "step", input: "args_0" } },
|
|
1465
|
+
{ from: { node_id: "step" }, to: { node_id: "apply", input: "in" } },
|
|
1466
|
+
{
|
|
1467
|
+
from: { node_id: "states" },
|
|
1468
|
+
to: { node_id: "states-out", input: "in" }
|
|
1469
|
+
}
|
|
1470
|
+
]
|
|
1471
|
+
}
|
|
1472
|
+
};
|
|
1473
|
+
}
|
|
1474
|
+
function fieldNumber(value) {
|
|
1475
|
+
if (!value || typeof value !== "object") {
|
|
1476
|
+
return null;
|
|
1477
|
+
}
|
|
1478
|
+
for (const key of ["u64", "u32", "f32", "f64"]) {
|
|
1479
|
+
if (key in value) {
|
|
1480
|
+
const parsed = Number(value[key]);
|
|
1481
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
return null;
|
|
1485
|
+
}
|
|
1486
|
+
function decodePlayerStates(raw) {
|
|
1487
|
+
if (!raw || typeof raw !== "object" || !("structs" in raw)) {
|
|
1488
|
+
return [];
|
|
1489
|
+
}
|
|
1490
|
+
const structs = raw.structs;
|
|
1491
|
+
const elements = Array.isArray(structs?.elements) ? structs.elements : [];
|
|
1492
|
+
const states = [];
|
|
1493
|
+
for (const element of elements) {
|
|
1494
|
+
const fields = element?.fields;
|
|
1495
|
+
if (!Array.isArray(fields)) {
|
|
1496
|
+
continue;
|
|
1497
|
+
}
|
|
1498
|
+
const byId = /* @__PURE__ */ new Map();
|
|
1499
|
+
for (const entry of fields) {
|
|
1500
|
+
const id = entry?.id;
|
|
1501
|
+
const value = entry?.value;
|
|
1502
|
+
if (typeof id === "string" && value && typeof value === "object") {
|
|
1503
|
+
byId.set(id, value);
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1506
|
+
const player = fieldNumber(byId.get(ANIMATION_MODULE_FIELD.statePlayer));
|
|
1507
|
+
if (player === null) {
|
|
1508
|
+
continue;
|
|
1509
|
+
}
|
|
1510
|
+
const stateValue = byId.get(ANIMATION_MODULE_FIELD.stateState);
|
|
1511
|
+
states.push({
|
|
1512
|
+
player,
|
|
1513
|
+
state: stateValue && "str" in stateValue ? String(stateValue.str) : "",
|
|
1514
|
+
time: (fieldNumber(byId.get(ANIMATION_MODULE_FIELD.stateTimeNs)) ?? 0) / 1e9,
|
|
1515
|
+
duration: (fieldNumber(byId.get(ANIMATION_MODULE_FIELD.stateDurationNs)) ?? 0) / 1e9,
|
|
1516
|
+
speed: fieldNumber(byId.get(ANIMATION_MODULE_FIELD.stateSpeed)) ?? 1
|
|
1517
|
+
});
|
|
1518
|
+
}
|
|
1519
|
+
return states;
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1522
|
+
// src/engine/animationModuleHost.ts
|
|
1523
|
+
var AnimationModuleHost = class {
|
|
1524
|
+
constructor(getDevice, resolveKeys = (key) => [key]) {
|
|
1525
|
+
this.getDevice = getDevice;
|
|
1526
|
+
this.resolveKeys = resolveKeys;
|
|
1527
|
+
this.clips = /* @__PURE__ */ new Map();
|
|
1528
|
+
}
|
|
1529
|
+
/** Whether any clip is currently marked playing (drives source lifecycle). */
|
|
1530
|
+
hasPlaying() {
|
|
1531
|
+
for (const entry of this.clips.values()) {
|
|
1532
|
+
if (entry.playing) {
|
|
1533
|
+
return true;
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
return false;
|
|
1537
|
+
}
|
|
1538
|
+
/** Whether a clip id is known to the host. */
|
|
1539
|
+
has(clipId) {
|
|
1540
|
+
return this.clips.has(clipId);
|
|
1541
|
+
}
|
|
1542
|
+
/** The clip's module `PlayerId` on the live device (feedback correlation). */
|
|
1543
|
+
playerIdOf(clipId) {
|
|
1544
|
+
return this.clips.get(clipId)?.playerId ?? null;
|
|
1545
|
+
}
|
|
1546
|
+
/** The clip's transport state as last commanded. */
|
|
1547
|
+
transportOf(clipId) {
|
|
1548
|
+
const entry = this.clips.get(clipId);
|
|
1549
|
+
if (!entry) {
|
|
1550
|
+
return null;
|
|
1551
|
+
}
|
|
1552
|
+
return {
|
|
1553
|
+
playing: entry.playing,
|
|
1554
|
+
speed: entry.speed,
|
|
1555
|
+
loop: entry.loop,
|
|
1556
|
+
weight: entry.weight
|
|
1557
|
+
};
|
|
1558
|
+
}
|
|
1559
|
+
/**
|
|
1560
|
+
* Declare the clips the bundle carries, as data. Keeps playing/transport
|
|
1561
|
+
* state for clips that survive; clips no longer present are dropped. Does
|
|
1562
|
+
* not touch the device — loading is lazy (on first play), so muted/unused
|
|
1563
|
+
* clips never enter the module, and conversion happens at load with the
|
|
1564
|
+
* key resolver's state at that moment.
|
|
1565
|
+
*/
|
|
1566
|
+
setClips(clips) {
|
|
1567
|
+
const nextIds = new Set(clips.map((clip) => clip.id));
|
|
1568
|
+
for (const id of Array.from(this.clips.keys())) {
|
|
1569
|
+
if (!nextIds.has(id)) {
|
|
1570
|
+
this.clips.delete(id);
|
|
1571
|
+
}
|
|
1572
|
+
}
|
|
1573
|
+
for (const clip of clips) {
|
|
1574
|
+
const existing = this.clips.get(clip.id);
|
|
1575
|
+
if (existing) {
|
|
1576
|
+
existing.stored = clip.stored;
|
|
1577
|
+
existing.animId = null;
|
|
1578
|
+
existing.playerId = null;
|
|
1579
|
+
existing.instId = null;
|
|
1580
|
+
} else {
|
|
1581
|
+
this.clips.set(clip.id, {
|
|
1582
|
+
stored: clip.stored,
|
|
1583
|
+
playing: false,
|
|
1584
|
+
speed: 1,
|
|
1585
|
+
loop: "loop",
|
|
1586
|
+
weight: 1,
|
|
1587
|
+
animId: null,
|
|
1588
|
+
playerId: null,
|
|
1589
|
+
instId: null
|
|
1590
|
+
});
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1593
|
+
}
|
|
1594
|
+
/**
|
|
1595
|
+
* Mark a clip playing, ensure it is loaded into the live device with a
|
|
1596
|
+
* player + instance, and issue `play` (a resume after `pause` needs it; on
|
|
1597
|
+
* a fresh player it is a no-op). Idempotent. Resolves once the calls have
|
|
1598
|
+
* dispatched (or immediately when there is no live device yet — the
|
|
1599
|
+
* provider registers the source, which boots the device and replays via
|
|
1600
|
+
* `replayInto`).
|
|
1601
|
+
*/
|
|
1602
|
+
async play(clipId) {
|
|
1603
|
+
const entry = this.clips.get(clipId);
|
|
1604
|
+
if (!entry) {
|
|
1605
|
+
return;
|
|
1606
|
+
}
|
|
1607
|
+
entry.playing = true;
|
|
1608
|
+
const device = this.getDevice();
|
|
1609
|
+
if (!device) {
|
|
1610
|
+
return;
|
|
1611
|
+
}
|
|
1612
|
+
await this.ensureLoaded(device, entry);
|
|
1613
|
+
if (entry.playerId !== null) {
|
|
1614
|
+
await device.call(playCall(entry.playerId));
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
/** Hold the clip's playhead (`pause` on its player). */
|
|
1618
|
+
pause(clipId) {
|
|
1619
|
+
const entry = this.clips.get(clipId);
|
|
1620
|
+
if (!entry) {
|
|
1621
|
+
return;
|
|
1622
|
+
}
|
|
1623
|
+
entry.playing = false;
|
|
1624
|
+
this.dispatch(
|
|
1625
|
+
(device) => entry.playerId !== null ? device.call(pauseCall(entry.playerId)) : null
|
|
1626
|
+
);
|
|
1627
|
+
}
|
|
1628
|
+
/**
|
|
1629
|
+
* Stop a clip: not playing, playhead reset to the window start (`stop` on
|
|
1630
|
+
* its player — the next tick emits the clip's t=0 pose). The clip stays
|
|
1631
|
+
* loaded for a cheap replay.
|
|
1632
|
+
*/
|
|
1633
|
+
stop(clipId) {
|
|
1634
|
+
const entry = this.clips.get(clipId);
|
|
1635
|
+
if (!entry) {
|
|
1636
|
+
return;
|
|
1637
|
+
}
|
|
1638
|
+
entry.playing = false;
|
|
1639
|
+
this.dispatch(
|
|
1640
|
+
(device) => entry.playerId !== null ? device.call(stopCall(entry.playerId)) : null
|
|
1641
|
+
);
|
|
1642
|
+
}
|
|
1643
|
+
/** Move the clip's playhead to `seconds`. */
|
|
1644
|
+
seek(clipId, seconds) {
|
|
1645
|
+
const entry = this.clips.get(clipId);
|
|
1646
|
+
if (!entry) {
|
|
1647
|
+
return;
|
|
1648
|
+
}
|
|
1649
|
+
this.dispatch(
|
|
1650
|
+
(device) => entry.playerId !== null ? device.call(seekCall(entry.playerId, seconds * 1e9)) : null
|
|
1651
|
+
);
|
|
1652
|
+
}
|
|
1653
|
+
/** Set the clip's playback speed multiplier. */
|
|
1654
|
+
setSpeed(clipId, speed) {
|
|
1655
|
+
const entry = this.clips.get(clipId);
|
|
1656
|
+
if (!entry) {
|
|
1657
|
+
return;
|
|
1658
|
+
}
|
|
1659
|
+
entry.speed = speed;
|
|
1660
|
+
this.dispatch(
|
|
1661
|
+
(device) => entry.playerId !== null ? device.call(setSpeedCall(entry.playerId, speed)) : null
|
|
1662
|
+
);
|
|
1663
|
+
}
|
|
1664
|
+
/** Set how the clip's player time maps into clip time. */
|
|
1665
|
+
setLoop(clipId, mode) {
|
|
1666
|
+
const entry = this.clips.get(clipId);
|
|
1667
|
+
if (!entry) {
|
|
1668
|
+
return;
|
|
1669
|
+
}
|
|
1670
|
+
entry.loop = mode;
|
|
1671
|
+
this.dispatch(
|
|
1672
|
+
(device) => entry.playerId !== null ? device.call(setLoopCall(entry.playerId, mode)) : null
|
|
1673
|
+
);
|
|
1674
|
+
}
|
|
1675
|
+
/** Set the clip instance's blend weight. */
|
|
1676
|
+
setWeight(clipId, weight) {
|
|
1677
|
+
const entry = this.clips.get(clipId);
|
|
1678
|
+
if (!entry) {
|
|
1679
|
+
return;
|
|
1680
|
+
}
|
|
1681
|
+
entry.weight = weight;
|
|
1682
|
+
this.dispatch(
|
|
1683
|
+
(device) => entry.playerId !== null && entry.instId !== null ? device.call(setWeightCall(entry.playerId, entry.instId, weight)) : null
|
|
1684
|
+
);
|
|
1685
|
+
}
|
|
1686
|
+
/**
|
|
1687
|
+
* Re-issue setup and transport for every playing clip against a freshly
|
|
1688
|
+
* started device (module guest state did not survive the rebuild). Voids
|
|
1689
|
+
* stale ids first. Called from `DeviceSlot.onDeviceStarted`.
|
|
1690
|
+
*/
|
|
1691
|
+
replayInto(device) {
|
|
1692
|
+
for (const entry of this.clips.values()) {
|
|
1693
|
+
entry.animId = null;
|
|
1694
|
+
entry.playerId = null;
|
|
1695
|
+
entry.instId = null;
|
|
1696
|
+
if (entry.playing) {
|
|
1697
|
+
void this.ensureLoaded(device, entry);
|
|
1698
|
+
}
|
|
1699
|
+
}
|
|
1700
|
+
}
|
|
1701
|
+
/** Fire-and-forget a transport call against the live device, if any. */
|
|
1702
|
+
dispatch(issue) {
|
|
1703
|
+
const device = this.getDevice();
|
|
1704
|
+
if (!device) {
|
|
1705
|
+
return;
|
|
1706
|
+
}
|
|
1707
|
+
void issue(device)?.catch(() => {
|
|
1708
|
+
});
|
|
1709
|
+
}
|
|
1710
|
+
async ensureLoaded(device, entry) {
|
|
1711
|
+
if (entry.instId !== null) {
|
|
1712
|
+
return;
|
|
1713
|
+
}
|
|
1714
|
+
if (entry.animId === null) {
|
|
1715
|
+
const anim = await device.call(
|
|
1716
|
+
loadAnimationCall(
|
|
1717
|
+
storedClipToModuleValue(entry.stored, this.resolveKeys)
|
|
1718
|
+
)
|
|
1719
|
+
);
|
|
1720
|
+
entry.animId = callResultU32(anim);
|
|
1721
|
+
}
|
|
1722
|
+
if (entry.playerId === null) {
|
|
1723
|
+
const player = await device.call(createPlayerCall(""));
|
|
1724
|
+
entry.playerId = callResultU32(player);
|
|
1725
|
+
}
|
|
1726
|
+
if (entry.instId === null && entry.animId !== null && entry.playerId !== null) {
|
|
1727
|
+
const inst = await device.call(
|
|
1728
|
+
addInstanceCall(entry.playerId, entry.animId)
|
|
1729
|
+
);
|
|
1730
|
+
entry.instId = callResultU32(inst);
|
|
1731
|
+
}
|
|
1732
|
+
const followups = [];
|
|
1733
|
+
if (entry.playerId !== null) {
|
|
1734
|
+
if (entry.speed !== 1) {
|
|
1735
|
+
followups.push(setSpeedCall(entry.playerId, entry.speed));
|
|
1736
|
+
}
|
|
1737
|
+
if (entry.loop !== "loop") {
|
|
1738
|
+
followups.push(setLoopCall(entry.playerId, entry.loop));
|
|
1739
|
+
}
|
|
1740
|
+
if (entry.instId !== null && entry.weight !== 1) {
|
|
1741
|
+
followups.push(
|
|
1742
|
+
setWeightCall(entry.playerId, entry.instId, entry.weight)
|
|
1743
|
+
);
|
|
1744
|
+
}
|
|
1745
|
+
}
|
|
1746
|
+
for (const call of followups) {
|
|
1747
|
+
await device.call(call);
|
|
1748
|
+
}
|
|
1749
|
+
}
|
|
1750
|
+
};
|
|
1751
|
+
|
|
1016
1752
|
// src/VizijRuntimeProvider.tsx
|
|
1017
1753
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
1018
1754
|
var ACTIVE_GRACE_MS = 250;
|
|
1019
1755
|
var VISIBLE_IDLE_FPS = 30;
|
|
1020
1756
|
var HIDDEN_IDLE_FPS = 1;
|
|
1757
|
+
var IMPLICIT_PAUSE_GAP_S = 5;
|
|
1758
|
+
var inferImplicitPause = (dt) => {
|
|
1759
|
+
if (dt <= IMPLICIT_PAUSE_GAP_S) {
|
|
1760
|
+
return dt;
|
|
1761
|
+
}
|
|
1762
|
+
console.warn(
|
|
1763
|
+
`[vizij-runtime] step loop woke after a ${dt.toFixed(
|
|
1764
|
+
1
|
|
1765
|
+
)}s wall-clock gap; treating it as a suspension (dt = 0)`
|
|
1766
|
+
);
|
|
1767
|
+
return 0;
|
|
1768
|
+
};
|
|
1021
1769
|
var DEFAULT_MERGE = {
|
|
1022
1770
|
outputs: "add",
|
|
1023
1771
|
intermediate: "add"
|
|
1024
1772
|
};
|
|
1025
1773
|
var DEFAULT_DURATION = 0.35;
|
|
1026
1774
|
var POSE_CONTROL_BRIDGE_EPSILON = 1e-6;
|
|
1775
|
+
var runtimeDebugInstanceSequence = 0;
|
|
1027
1776
|
var DEV_MODE = (() => {
|
|
1028
1777
|
const nodeEnv = globalThis.process?.env?.NODE_ENV;
|
|
1029
1778
|
return typeof nodeEnv === "string" && nodeEnv === "development";
|
|
@@ -1514,12 +2263,15 @@ function pickExtractedAnimations(asset) {
|
|
|
1514
2263
|
}
|
|
1515
2264
|
return animations;
|
|
1516
2265
|
}
|
|
1517
|
-
function mergeAnimationLists(explicit, fromBundle) {
|
|
1518
|
-
if (!
|
|
2266
|
+
function mergeAnimationLists(explicit, hasExplicitOverride, fromBundle) {
|
|
2267
|
+
if (!hasExplicitOverride) {
|
|
2268
|
+
return fromBundle.length > 0 ? fromBundle : void 0;
|
|
2269
|
+
}
|
|
2270
|
+
if (!Array.isArray(explicit)) {
|
|
1519
2271
|
return void 0;
|
|
1520
2272
|
}
|
|
1521
|
-
if (
|
|
1522
|
-
return
|
|
2273
|
+
if (explicit.length === 0) {
|
|
2274
|
+
return [];
|
|
1523
2275
|
}
|
|
1524
2276
|
if (fromBundle.length === 0) {
|
|
1525
2277
|
return explicit;
|
|
@@ -1570,13 +2322,68 @@ function convertBundlePrograms(entries) {
|
|
|
1570
2322
|
};
|
|
1571
2323
|
}).filter(Boolean);
|
|
1572
2324
|
}
|
|
1573
|
-
function
|
|
1574
|
-
|
|
2325
|
+
function resolveConstraintDefault(path, namespace, inputConstraints) {
|
|
2326
|
+
const trimmed = path.trim();
|
|
2327
|
+
if (!trimmed) {
|
|
1575
2328
|
return void 0;
|
|
1576
2329
|
}
|
|
1577
|
-
|
|
2330
|
+
const stripped = stripRigFacePrefix(trimmed);
|
|
2331
|
+
const relativePath = stripped ? `/${stripped}` : "";
|
|
2332
|
+
const candidates = [
|
|
2333
|
+
namespaceTypedPath(trimmed, namespace),
|
|
2334
|
+
trimmed,
|
|
2335
|
+
stripped ? namespaceTypedPath(stripped, namespace) : void 0,
|
|
2336
|
+
stripped || void 0,
|
|
2337
|
+
relativePath || void 0
|
|
2338
|
+
];
|
|
2339
|
+
for (const candidate of candidates) {
|
|
2340
|
+
if (!candidate) {
|
|
2341
|
+
continue;
|
|
2342
|
+
}
|
|
2343
|
+
const defaultValue = inputConstraints[candidate]?.defaultValue;
|
|
2344
|
+
if (Number.isFinite(defaultValue)) {
|
|
2345
|
+
return Number(defaultValue);
|
|
2346
|
+
}
|
|
2347
|
+
}
|
|
2348
|
+
return void 0;
|
|
2349
|
+
}
|
|
2350
|
+
function deriveProgramInputSeedValues(args) {
|
|
2351
|
+
const graphSpec = resolveGraphSpec(
|
|
2352
|
+
args.program.graph,
|
|
2353
|
+
`${args.program.id ?? "program"} graph (seed defaults)`
|
|
2354
|
+
);
|
|
2355
|
+
if (!graphSpec) {
|
|
2356
|
+
return [];
|
|
2357
|
+
}
|
|
2358
|
+
return collectInputPaths(graphSpec).map((path) => path.trim()).filter((path) => path.length > 0).flatMap((path) => {
|
|
2359
|
+
const namespacedPath = namespaceTypedPath(path, args.namespace);
|
|
2360
|
+
if (args.stagedInputs.has(namespacedPath)) {
|
|
2361
|
+
return [];
|
|
2362
|
+
}
|
|
2363
|
+
if (args.getPathSnapshot(namespacedPath) !== void 0) {
|
|
2364
|
+
return [];
|
|
2365
|
+
}
|
|
2366
|
+
const defaultValue = resolveConstraintDefault(
|
|
2367
|
+
path,
|
|
2368
|
+
args.namespace,
|
|
2369
|
+
args.inputConstraints
|
|
2370
|
+
);
|
|
2371
|
+
if (!Number.isFinite(defaultValue)) {
|
|
2372
|
+
return [];
|
|
2373
|
+
}
|
|
2374
|
+
return [{ path, value: { float: Number(defaultValue) } }];
|
|
2375
|
+
});
|
|
2376
|
+
}
|
|
2377
|
+
function mergeProgramLists(explicit, hasExplicitOverride, fromBundle) {
|
|
2378
|
+
if (!hasExplicitOverride) {
|
|
1578
2379
|
return fromBundle.length > 0 ? fromBundle : void 0;
|
|
1579
2380
|
}
|
|
2381
|
+
if (!Array.isArray(explicit)) {
|
|
2382
|
+
return void 0;
|
|
2383
|
+
}
|
|
2384
|
+
if (explicit.length === 0) {
|
|
2385
|
+
return [];
|
|
2386
|
+
}
|
|
1580
2387
|
if (fromBundle.length === 0) {
|
|
1581
2388
|
return explicit;
|
|
1582
2389
|
}
|
|
@@ -1595,10 +2402,23 @@ function mergeProgramLists(explicit, fromBundle) {
|
|
|
1595
2402
|
}
|
|
1596
2403
|
function mergeAssetBundle(base, extracted, extractedAnimations) {
|
|
1597
2404
|
const resolvedBundle = base.bundle ?? extracted ?? null;
|
|
2405
|
+
const hasBaseRigOverride = Object.prototype.hasOwnProperty.call(base, "rig");
|
|
2406
|
+
const hasBasePoseOverride = Object.prototype.hasOwnProperty.call(
|
|
2407
|
+
base,
|
|
2408
|
+
"pose"
|
|
2409
|
+
);
|
|
2410
|
+
const hasBaseAnimationsOverride = Object.prototype.hasOwnProperty.call(
|
|
2411
|
+
base,
|
|
2412
|
+
"animations"
|
|
2413
|
+
);
|
|
2414
|
+
const hasBaseProgramsOverride = Object.prototype.hasOwnProperty.call(
|
|
2415
|
+
base,
|
|
2416
|
+
"programs"
|
|
2417
|
+
);
|
|
1598
2418
|
const rigFromBundle = convertBundleGraph(
|
|
1599
2419
|
pickBundleGraph(resolvedBundle, ["rig"])
|
|
1600
2420
|
);
|
|
1601
|
-
const resolvedRig = base.rig ?? rigFromBundle ?? void 0;
|
|
2421
|
+
const resolvedRig = hasBaseRigOverride ? base.rig : base.rig ?? rigFromBundle ?? void 0;
|
|
1602
2422
|
const basePose = base.pose;
|
|
1603
2423
|
const hasBasePoseGraphOverride = Boolean(
|
|
1604
2424
|
basePose && Object.prototype.hasOwnProperty.call(basePose, "graph")
|
|
@@ -1607,13 +2427,15 @@ function mergeAssetBundle(base, extracted, extractedAnimations) {
|
|
|
1607
2427
|
basePose && Object.prototype.hasOwnProperty.call(basePose, "config")
|
|
1608
2428
|
);
|
|
1609
2429
|
const poseStageFilter = basePose?.stageNeutralFilter;
|
|
1610
|
-
const poseGraphFromBundle = hasBasePoseGraphOverride ? null : convertBundleGraph(
|
|
2430
|
+
const poseGraphFromBundle = hasBasePoseOverride && !basePose ? null : hasBasePoseGraphOverride ? null : convertBundleGraph(
|
|
1611
2431
|
pickBundleGraph(resolvedBundle, ["pose-driver", "pose"])
|
|
1612
2432
|
);
|
|
1613
2433
|
const resolvedPoseGraph = hasBasePoseGraphOverride ? basePose?.graph : basePose?.graph ?? poseGraphFromBundle ?? void 0;
|
|
1614
2434
|
const resolvedPoseConfig = hasBasePoseConfigOverride ? basePose?.config : basePose?.config ?? resolvedBundle?.poses?.config ?? void 0;
|
|
1615
2435
|
let resolvedPose = basePose;
|
|
1616
|
-
if (basePose) {
|
|
2436
|
+
if (hasBasePoseOverride && !basePose) {
|
|
2437
|
+
resolvedPose = void 0;
|
|
2438
|
+
} else if (basePose) {
|
|
1617
2439
|
const nextPose = { ...basePose };
|
|
1618
2440
|
let changed = false;
|
|
1619
2441
|
if (resolvedPoseGraph && basePose.graph !== resolvedPoseGraph) {
|
|
@@ -1641,17 +2463,20 @@ function mergeAssetBundle(base, extracted, extractedAnimations) {
|
|
|
1641
2463
|
);
|
|
1642
2464
|
let resolvedAnimations = mergeAnimationLists(
|
|
1643
2465
|
base.animations,
|
|
2466
|
+
hasBaseAnimationsOverride,
|
|
1644
2467
|
animationsFromBundle
|
|
1645
2468
|
);
|
|
1646
2469
|
const animationsFromAsset = extractedAnimations && extractedAnimations.length > 0 ? extractedAnimations : [];
|
|
1647
2470
|
if (animationsFromAsset.length > 0) {
|
|
1648
2471
|
resolvedAnimations = mergeAnimationLists(
|
|
1649
2472
|
resolvedAnimations,
|
|
2473
|
+
true,
|
|
1650
2474
|
animationsFromAsset
|
|
1651
2475
|
);
|
|
1652
2476
|
}
|
|
1653
2477
|
const programsFromBundle = mergeProgramLists(
|
|
1654
2478
|
base.programs,
|
|
2479
|
+
hasBaseProgramsOverride,
|
|
1655
2480
|
convertBundlePrograms(resolvedBundle?.graphs)
|
|
1656
2481
|
);
|
|
1657
2482
|
const merged = {
|
|
@@ -1765,28 +2590,18 @@ function VizijRuntimeProvider({
|
|
|
1765
2590
|
faceId: faceIdProp,
|
|
1766
2591
|
updateTier = "auto",
|
|
1767
2592
|
autoCreate = true,
|
|
1768
|
-
createOptions,
|
|
1769
2593
|
autostart = false,
|
|
1770
|
-
|
|
2594
|
+
driveRuntime = true,
|
|
1771
2595
|
mergeStrategy,
|
|
1772
2596
|
onRegisterControllers,
|
|
1773
2597
|
onStatusChange,
|
|
1774
|
-
transformOutputWrite
|
|
1775
|
-
orchestratorScope = "auto"
|
|
2598
|
+
transformOutputWrite
|
|
1776
2599
|
}) {
|
|
1777
2600
|
const storeRef = (0, import_react2.useRef)(null);
|
|
1778
2601
|
if (!storeRef.current) {
|
|
1779
2602
|
storeRef.current = (0, import_render.createVizijStore)();
|
|
1780
2603
|
}
|
|
1781
|
-
|
|
1782
|
-
const hasParentOrchestrator = Boolean(parentOrchestrator);
|
|
1783
|
-
const shouldProvideOrchestrator = orchestratorScope === "isolated" || !hasParentOrchestrator && orchestratorScope !== "shared";
|
|
1784
|
-
if (orchestratorScope === "shared" && !hasParentOrchestrator) {
|
|
1785
|
-
console.warn(
|
|
1786
|
-
'[vizij-runtime] orchestratorScope="shared" requires an OrchestratorProvider higher in the tree; falling back to an isolated provider.'
|
|
1787
|
-
);
|
|
1788
|
-
}
|
|
1789
|
-
const runtimeTree = /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_render.VizijContext.Provider, { value: storeRef.current, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
2604
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_render.VizijContext.Provider, { value: storeRef.current, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1790
2605
|
VizijRuntimeProviderInner,
|
|
1791
2606
|
{
|
|
1792
2607
|
assetBundle,
|
|
@@ -1795,8 +2610,7 @@ function VizijRuntimeProvider({
|
|
|
1795
2610
|
updateTier,
|
|
1796
2611
|
autoCreate,
|
|
1797
2612
|
autostart,
|
|
1798
|
-
|
|
1799
|
-
createOptions,
|
|
2613
|
+
driveRuntime,
|
|
1800
2614
|
mergeStrategy,
|
|
1801
2615
|
onRegisterControllers,
|
|
1802
2616
|
onStatusChange,
|
|
@@ -1805,18 +2619,6 @@ function VizijRuntimeProvider({
|
|
|
1805
2619
|
children
|
|
1806
2620
|
}
|
|
1807
2621
|
) });
|
|
1808
|
-
if (!shouldProvideOrchestrator) {
|
|
1809
|
-
return runtimeTree;
|
|
1810
|
-
}
|
|
1811
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1812
|
-
import_orchestrator_react.OrchestratorProvider,
|
|
1813
|
-
{
|
|
1814
|
-
autoCreate,
|
|
1815
|
-
createOptions,
|
|
1816
|
-
autostart: false,
|
|
1817
|
-
children: runtimeTree
|
|
1818
|
-
}
|
|
1819
|
-
);
|
|
1820
2622
|
}
|
|
1821
2623
|
function VizijRuntimeProviderInner({
|
|
1822
2624
|
assetBundle: initialAssetBundle,
|
|
@@ -1831,8 +2633,7 @@ function VizijRuntimeProviderInner({
|
|
|
1831
2633
|
children,
|
|
1832
2634
|
autoCreate,
|
|
1833
2635
|
autostart,
|
|
1834
|
-
|
|
1835
|
-
driveOrchestrator
|
|
2636
|
+
driveRuntime
|
|
1836
2637
|
}) {
|
|
1837
2638
|
const [assetBundleOverride, setAssetBundleOverride] = (0, import_react2.useState)(null);
|
|
1838
2639
|
const [graphUpdateToken, setGraphUpdateToken] = (0, import_react2.useState)(0);
|
|
@@ -1894,23 +2695,17 @@ function VizijRuntimeProviderInner({
|
|
|
1894
2695
|
setGraphUpdateToken((prev) => prev + 1);
|
|
1895
2696
|
}
|
|
1896
2697
|
}, [effectiveAssetBundle]);
|
|
1897
|
-
const
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
removeAnimation,
|
|
1905
|
-
removeInput,
|
|
1906
|
-
listControllers,
|
|
1907
|
-
setInput: orchestratorSetInput,
|
|
1908
|
-
getPathSnapshot,
|
|
1909
|
-
step: stepRuntime
|
|
1910
|
-
} = (0, import_orchestrator_react.useOrchestrator)();
|
|
1911
|
-
const frame = (0, import_orchestrator_react.useOrchFrame)();
|
|
2698
|
+
const deviceSlotRef = (0, import_react2.useRef)(null);
|
|
2699
|
+
if (!deviceSlotRef.current) {
|
|
2700
|
+
deviceSlotRef.current = new DeviceSlot();
|
|
2701
|
+
}
|
|
2702
|
+
const deviceSlot = deviceSlotRef.current;
|
|
2703
|
+
const [ready, setReady] = (0, import_react2.useState)(false);
|
|
2704
|
+
const graphSourcesRef = (0, import_react2.useRef)([]);
|
|
1912
2705
|
const namespace = namespaceProp ?? assetBundle.namespace ?? "default";
|
|
1913
2706
|
const faceId = faceIdProp ?? assetBundle.faceId ?? assetBundle.pose?.config?.faceId ?? assetBundle.pose?.config?.faceId ?? void 0;
|
|
2707
|
+
const faceIdRef = (0, import_react2.useRef)(faceId);
|
|
2708
|
+
faceIdRef.current = faceId;
|
|
1914
2709
|
const [status, setStatus] = (0, import_react2.useState)({
|
|
1915
2710
|
loading: true,
|
|
1916
2711
|
ready: false,
|
|
@@ -1923,12 +2718,15 @@ function VizijRuntimeProviderInner({
|
|
|
1923
2718
|
stepHz: void 0,
|
|
1924
2719
|
controllers: { graphs: [], anims: [] }
|
|
1925
2720
|
});
|
|
2721
|
+
const runtimeDebugInstanceIdRef = (0, import_react2.useRef)(
|
|
2722
|
+
`vizij-runtime:${runtimeDebugInstanceSequence++}`
|
|
2723
|
+
);
|
|
1926
2724
|
const errorsRef = (0, import_react2.useRef)([]);
|
|
1927
2725
|
const outputPathsRef = (0, import_react2.useRef)(/* @__PURE__ */ new Set());
|
|
1928
2726
|
const baseOutputPathsRef = (0, import_react2.useRef)(/* @__PURE__ */ new Set());
|
|
1929
2727
|
const namespacedOutputPathsRef = (0, import_react2.useRef)(/* @__PURE__ */ new Set());
|
|
1930
2728
|
const namespaceRef = (0, import_react2.useRef)(namespace);
|
|
1931
|
-
const
|
|
2729
|
+
const driveRuntimeRef = (0, import_react2.useRef)(driveRuntime);
|
|
1932
2730
|
const rigInputMapRef = (0, import_react2.useRef)({});
|
|
1933
2731
|
const rigPoseControlInputIdsRef = (0, import_react2.useRef)(/* @__PURE__ */ new Set());
|
|
1934
2732
|
const registeredGraphsRef = (0, import_react2.useRef)([]);
|
|
@@ -1970,16 +2768,17 @@ function VizijRuntimeProviderInner({
|
|
|
1970
2768
|
const [inputConstraints, setInputConstraints] = (0, import_react2.useState)({});
|
|
1971
2769
|
const inputConstraintsRef = (0, import_react2.useRef)({});
|
|
1972
2770
|
const avgStepDtRef = (0, import_react2.useRef)(null);
|
|
2771
|
+
const inputDriverIdsRef = (0, import_react2.useRef)(/* @__PURE__ */ new Set());
|
|
1973
2772
|
const animationTweensRef = (0, import_react2.useRef)(/* @__PURE__ */ new Map());
|
|
1974
2773
|
const clipPlaybackRef = (0, import_react2.useRef)(/* @__PURE__ */ new Map());
|
|
1975
2774
|
const programPlaybackRef = (0, import_react2.useRef)(
|
|
1976
2775
|
/* @__PURE__ */ new Map()
|
|
1977
2776
|
);
|
|
1978
2777
|
const programControllerIdsRef = (0, import_react2.useRef)(/* @__PURE__ */ new Map());
|
|
1979
|
-
const
|
|
1980
|
-
|
|
1981
|
-
);
|
|
1982
|
-
const
|
|
2778
|
+
const animationModuleRef = (0, import_react2.useRef)(null);
|
|
2779
|
+
const animationHostRef = (0, import_react2.useRef)(null);
|
|
2780
|
+
const animationsSourceRegisteredRef = (0, import_react2.useRef)(false);
|
|
2781
|
+
const animationGapWarnedRef = (0, import_react2.useRef)(/* @__PURE__ */ new Set());
|
|
1983
2782
|
const animationSystemActiveRef = (0, import_react2.useRef)(true);
|
|
1984
2783
|
const stagedInputsRef = (0, import_react2.useRef)(/* @__PURE__ */ new Map());
|
|
1985
2784
|
const autostartRef = (0, import_react2.useRef)(autostart);
|
|
@@ -1995,6 +2794,58 @@ function VizijRuntimeProviderInner({
|
|
|
1995
2794
|
runtimeMountedRef.current = false;
|
|
1996
2795
|
};
|
|
1997
2796
|
}, []);
|
|
2797
|
+
const publishRuntimeDebugState = (0, import_react2.useCallback)(() => {
|
|
2798
|
+
const storeState = store.getState();
|
|
2799
|
+
setRuntimeDebugState(runtimeDebugInstanceIdRef.current, {
|
|
2800
|
+
namespace,
|
|
2801
|
+
faceId: faceId ?? null,
|
|
2802
|
+
rootId: status.rootId,
|
|
2803
|
+
ready: status.ready,
|
|
2804
|
+
loading: status.loading,
|
|
2805
|
+
autostart,
|
|
2806
|
+
driveRuntime,
|
|
2807
|
+
loopMode,
|
|
2808
|
+
outputCount: status.outputPaths.length,
|
|
2809
|
+
graphControllerCount: status.controllers.graphs.length,
|
|
2810
|
+
animationControllerCount: status.controllers.anims.length,
|
|
2811
|
+
registeredGraphCount: registeredGraphsRef.current.length,
|
|
2812
|
+
registeredAnimationCount: registeredAnimationsRef.current.length,
|
|
2813
|
+
programControllerCount: programControllerIdsRef.current.size,
|
|
2814
|
+
animationTweenCount: animationTweensRef.current.size,
|
|
2815
|
+
clipPlaybackCount: clipPlaybackRef.current.size,
|
|
2816
|
+
programPlaybackCount: programPlaybackRef.current.size,
|
|
2817
|
+
stagedInputCount: stagedInputsRef.current.size,
|
|
2818
|
+
activeDriverCount: inputDriverIdsRef.current.size,
|
|
2819
|
+
worldEntryCount: Object.keys(storeState.world ?? {}).length,
|
|
2820
|
+
animatableCount: Object.keys(storeState.animatables ?? {}).length,
|
|
2821
|
+
valuesSize: storeState.values instanceof Map ? storeState.values.size : null,
|
|
2822
|
+
stepHz: status.stepHz ?? null
|
|
2823
|
+
});
|
|
2824
|
+
}, [
|
|
2825
|
+
autostart,
|
|
2826
|
+
driveRuntime,
|
|
2827
|
+
faceId,
|
|
2828
|
+
loopMode,
|
|
2829
|
+
namespace,
|
|
2830
|
+
status.controllers.anims.length,
|
|
2831
|
+
status.controllers.graphs.length,
|
|
2832
|
+
status.loading,
|
|
2833
|
+
status.outputPaths.length,
|
|
2834
|
+
status.ready,
|
|
2835
|
+
status.rootId,
|
|
2836
|
+
status.stepHz,
|
|
2837
|
+
store
|
|
2838
|
+
]);
|
|
2839
|
+
(0, import_react2.useEffect)(() => {
|
|
2840
|
+
publishRuntimeDebugState();
|
|
2841
|
+
const unsubscribe = store.subscribe(() => {
|
|
2842
|
+
publishRuntimeDebugState();
|
|
2843
|
+
});
|
|
2844
|
+
return () => {
|
|
2845
|
+
unsubscribe();
|
|
2846
|
+
clearRuntimeDebugState(runtimeDebugInstanceIdRef.current);
|
|
2847
|
+
};
|
|
2848
|
+
}, [publishRuntimeDebugState, store]);
|
|
1998
2849
|
(0, import_react2.useEffect)(() => {
|
|
1999
2850
|
const rigAsset = assetBundle.rig;
|
|
2000
2851
|
if (!rigAsset) {
|
|
@@ -2090,59 +2941,289 @@ function VizijRuntimeProviderInner({
|
|
|
2090
2941
|
});
|
|
2091
2942
|
return;
|
|
2092
2943
|
}
|
|
2093
|
-
markActivity();
|
|
2094
|
-
const namespacedPath = namespaceTypedPath(path, namespaceRef.current);
|
|
2095
|
-
if (isRuntimeDebugEnabled() && (namespacedPath.includes("animation/authoring.timeline.main") || namespacedPath.endsWith("/blink"))) {
|
|
2096
|
-
console.log("[vizij-runtime] stage input", {
|
|
2097
|
-
path,
|
|
2098
|
-
namespacedPath,
|
|
2099
|
-
value
|
|
2100
|
-
});
|
|
2944
|
+
markActivity();
|
|
2945
|
+
const namespacedPath = namespaceTypedPath(path, namespaceRef.current);
|
|
2946
|
+
if (isRuntimeDebugEnabled() && (namespacedPath.includes("animation/authoring.timeline.main") || namespacedPath.endsWith("/blink"))) {
|
|
2947
|
+
console.log("[vizij-runtime] stage input", {
|
|
2948
|
+
path,
|
|
2949
|
+
namespacedPath,
|
|
2950
|
+
value
|
|
2951
|
+
});
|
|
2952
|
+
}
|
|
2953
|
+
stagedInputsRef.current.set(namespacedPath, { value, shape });
|
|
2954
|
+
},
|
|
2955
|
+
[
|
|
2956
|
+
assetBundle.pose?.config?.faceId,
|
|
2957
|
+
faceId,
|
|
2958
|
+
markActivity,
|
|
2959
|
+
poseWeightFallbackMap,
|
|
2960
|
+
useLegacyPoseWeightFallback
|
|
2961
|
+
]
|
|
2962
|
+
);
|
|
2963
|
+
const reportStatus = (0, import_react2.useCallback)(
|
|
2964
|
+
(updater) => {
|
|
2965
|
+
setStatus((prev) => {
|
|
2966
|
+
const next = updater(prev);
|
|
2967
|
+
onStatusChange?.(next);
|
|
2968
|
+
return next;
|
|
2969
|
+
});
|
|
2970
|
+
},
|
|
2971
|
+
[onStatusChange]
|
|
2972
|
+
);
|
|
2973
|
+
const pushError = (0, import_react2.useCallback)(
|
|
2974
|
+
(error) => {
|
|
2975
|
+
errorsRef.current = [...errorsRef.current, error];
|
|
2976
|
+
reportStatus((prev) => ({
|
|
2977
|
+
...prev,
|
|
2978
|
+
error,
|
|
2979
|
+
errors: errorsRef.current
|
|
2980
|
+
}));
|
|
2981
|
+
console.warn("[vizij-runtime]", error.message, error.cause);
|
|
2982
|
+
},
|
|
2983
|
+
[reportStatus]
|
|
2984
|
+
);
|
|
2985
|
+
const resetErrors = (0, import_react2.useCallback)(() => {
|
|
2986
|
+
errorsRef.current = [];
|
|
2987
|
+
reportStatus((prev) => ({
|
|
2988
|
+
...prev,
|
|
2989
|
+
error: null,
|
|
2990
|
+
errors: []
|
|
2991
|
+
}));
|
|
2992
|
+
}, [reportStatus]);
|
|
2993
|
+
(0, import_react2.useEffect)(() => {
|
|
2994
|
+
autostartRef.current = autostart;
|
|
2995
|
+
updateLoopMode();
|
|
2996
|
+
}, [autostart, updateLoopMode]);
|
|
2997
|
+
const pendingWritesRef = (0, import_react2.useRef)(/* @__PURE__ */ new Map());
|
|
2998
|
+
const getDevice = (0, import_react2.useCallback)(
|
|
2999
|
+
() => deviceSlot.current?.device ?? null,
|
|
3000
|
+
[deviceSlot]
|
|
3001
|
+
);
|
|
3002
|
+
const ensureAnimationHost = (0, import_react2.useCallback)(() => {
|
|
3003
|
+
if (!animationModuleRef.current) {
|
|
3004
|
+
return null;
|
|
3005
|
+
}
|
|
3006
|
+
if (!animationHostRef.current) {
|
|
3007
|
+
animationHostRef.current = new AnimationModuleHost(
|
|
3008
|
+
getDevice,
|
|
3009
|
+
(key) => resolveAnimationBridgeOutputPaths(
|
|
3010
|
+
key,
|
|
3011
|
+
faceIdRef.current ?? void 0,
|
|
3012
|
+
rigInputMapRef.current
|
|
3013
|
+
)
|
|
3014
|
+
);
|
|
3015
|
+
}
|
|
3016
|
+
return animationHostRef.current;
|
|
3017
|
+
}, [getDevice]);
|
|
3018
|
+
const applyEngineChangesRef = (0, import_react2.useRef)(() => {
|
|
3019
|
+
});
|
|
3020
|
+
const recomposeDevice = (0, import_react2.useCallback)(() => {
|
|
3021
|
+
const spec = composeGraphSpecs(graphSourcesRef.current);
|
|
3022
|
+
deviceSlot.recompose(spec).then((handle) => {
|
|
3023
|
+
if (pendingWritesRef.current.size > 0) {
|
|
3024
|
+
handle.device.writeValues(
|
|
3025
|
+
Object.fromEntries(pendingWritesRef.current)
|
|
3026
|
+
);
|
|
3027
|
+
pendingWritesRef.current.clear();
|
|
3028
|
+
}
|
|
3029
|
+
}).catch((err) => {
|
|
3030
|
+
pushError({
|
|
3031
|
+
message: "Failed to (re)compose the arora device",
|
|
3032
|
+
cause: err,
|
|
3033
|
+
phase: "engine",
|
|
3034
|
+
timestamp: performance.now()
|
|
3035
|
+
});
|
|
3036
|
+
});
|
|
3037
|
+
}, [deviceSlot, pushError]);
|
|
3038
|
+
const initEngine = (0, import_react2.useCallback)(async () => {
|
|
3039
|
+
await ensureWasmInit();
|
|
3040
|
+
setReady(true);
|
|
3041
|
+
}, []);
|
|
3042
|
+
const hasBundleAnimations = (assetBundle.animations?.length ?? 0) > 0;
|
|
3043
|
+
(0, import_react2.useEffect)(() => {
|
|
3044
|
+
if (!hasBundleAnimations || animationModuleRef.current) {
|
|
3045
|
+
return;
|
|
3046
|
+
}
|
|
3047
|
+
let cancelled = false;
|
|
3048
|
+
const loading = (0, import_animation_module.loadAnimationModule)().then((module2) => {
|
|
3049
|
+
if (cancelled) {
|
|
3050
|
+
return;
|
|
3051
|
+
}
|
|
3052
|
+
animationModuleRef.current = module2;
|
|
3053
|
+
deviceSlot.setModules([module2]);
|
|
3054
|
+
deviceSlot.onDeviceStarted = (handle) => {
|
|
3055
|
+
animationHostRef.current?.replayInto(handle.device);
|
|
3056
|
+
};
|
|
3057
|
+
ensureAnimationHost();
|
|
3058
|
+
}).catch((err) => {
|
|
3059
|
+
pushError({
|
|
3060
|
+
message: "Failed to load the animation module",
|
|
3061
|
+
cause: err,
|
|
3062
|
+
phase: "engine",
|
|
3063
|
+
timestamp: performance.now()
|
|
3064
|
+
});
|
|
3065
|
+
});
|
|
3066
|
+
deviceSlot.waitForModules(loading);
|
|
3067
|
+
return () => {
|
|
3068
|
+
cancelled = true;
|
|
3069
|
+
};
|
|
3070
|
+
}, [deviceSlot, ensureAnimationHost, hasBundleAnimations, pushError]);
|
|
3071
|
+
const removeGraph = (0, import_react2.useCallback)(
|
|
3072
|
+
(id) => {
|
|
3073
|
+
graphSourcesRef.current = graphSourcesRef.current.filter(
|
|
3074
|
+
(s) => s.sourceId !== id && !s.sourceId.startsWith(`${id}#`)
|
|
3075
|
+
);
|
|
3076
|
+
recomposeDevice();
|
|
3077
|
+
},
|
|
3078
|
+
[recomposeDevice]
|
|
3079
|
+
);
|
|
3080
|
+
const registerGraph = (0, import_react2.useCallback)(
|
|
3081
|
+
(cfg) => {
|
|
3082
|
+
const id = cfg.id ?? `graph-${graphSourcesRef.current.length}`;
|
|
3083
|
+
graphSourcesRef.current = [
|
|
3084
|
+
...graphSourcesRef.current.filter((s) => s.sourceId !== id),
|
|
3085
|
+
{ sourceId: id, spec: cfg.spec ?? {} }
|
|
3086
|
+
];
|
|
3087
|
+
recomposeDevice();
|
|
3088
|
+
return id;
|
|
3089
|
+
},
|
|
3090
|
+
[recomposeDevice]
|
|
3091
|
+
);
|
|
3092
|
+
const registerMergedGraph = (0, import_react2.useCallback)(
|
|
3093
|
+
(cfg) => {
|
|
3094
|
+
const id = cfg.id ?? `merged-${graphSourcesRef.current.length}`;
|
|
3095
|
+
const members = cfg.graphs.map((graph, index) => ({
|
|
3096
|
+
sourceId: `${id}#${graph.id ?? index}`,
|
|
3097
|
+
spec: graph.spec ?? {}
|
|
3098
|
+
}));
|
|
3099
|
+
graphSourcesRef.current = [
|
|
3100
|
+
...graphSourcesRef.current.filter(
|
|
3101
|
+
(s) => s.sourceId !== id && !s.sourceId.startsWith(`${id}#`)
|
|
3102
|
+
),
|
|
3103
|
+
...members
|
|
3104
|
+
];
|
|
3105
|
+
recomposeDevice();
|
|
3106
|
+
return id;
|
|
3107
|
+
},
|
|
3108
|
+
[recomposeDevice]
|
|
3109
|
+
);
|
|
3110
|
+
const registerAnimation = (0, import_react2.useCallback)(
|
|
3111
|
+
(cfg) => cfg.id ?? `animation-${registeredAnimationsRef.current.length}`,
|
|
3112
|
+
[]
|
|
3113
|
+
);
|
|
3114
|
+
const removeAnimation = (0, import_react2.useCallback)((_id) => {
|
|
3115
|
+
}, []);
|
|
3116
|
+
const setAnimationsSourceRegistered = (0, import_react2.useCallback)(
|
|
3117
|
+
(registered) => {
|
|
3118
|
+
if (registered === animationsSourceRegisteredRef.current) {
|
|
3119
|
+
return;
|
|
3120
|
+
}
|
|
3121
|
+
animationsSourceRegisteredRef.current = registered;
|
|
3122
|
+
if (registered) {
|
|
3123
|
+
graphSourcesRef.current = [
|
|
3124
|
+
...graphSourcesRef.current.filter(
|
|
3125
|
+
(s) => s.sourceId !== animationsGraphSource().sourceId
|
|
3126
|
+
),
|
|
3127
|
+
animationsGraphSource()
|
|
3128
|
+
];
|
|
3129
|
+
} else {
|
|
3130
|
+
graphSourcesRef.current = graphSourcesRef.current.filter(
|
|
3131
|
+
(s) => s.sourceId !== animationsGraphSource().sourceId
|
|
3132
|
+
);
|
|
2101
3133
|
}
|
|
2102
|
-
|
|
3134
|
+
recomposeDevice();
|
|
2103
3135
|
},
|
|
2104
|
-
[
|
|
2105
|
-
assetBundle.pose?.config?.faceId,
|
|
2106
|
-
faceId,
|
|
2107
|
-
markActivity,
|
|
2108
|
-
poseWeightFallbackMap,
|
|
2109
|
-
useLegacyPoseWeightFallback
|
|
2110
|
-
]
|
|
3136
|
+
[recomposeDevice]
|
|
2111
3137
|
);
|
|
2112
|
-
const
|
|
2113
|
-
(
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
3138
|
+
const listControllers = (0, import_react2.useCallback)(
|
|
3139
|
+
() => ({
|
|
3140
|
+
graphs: [...registeredGraphsRef.current],
|
|
3141
|
+
anims: [...registeredAnimationsRef.current]
|
|
3142
|
+
}),
|
|
3143
|
+
[]
|
|
3144
|
+
);
|
|
3145
|
+
const deviceSetInput = (0, import_react2.useCallback)(
|
|
3146
|
+
(path, value, _shape) => {
|
|
3147
|
+
const handle = deviceSlot.current;
|
|
3148
|
+
if (handle) {
|
|
3149
|
+
handle.device.setValue(path, value);
|
|
3150
|
+
} else {
|
|
3151
|
+
pendingWritesRef.current.set(path, value);
|
|
3152
|
+
}
|
|
3153
|
+
},
|
|
3154
|
+
[deviceSlot]
|
|
3155
|
+
);
|
|
3156
|
+
const getPathSnapshot = (0, import_react2.useCallback)(
|
|
3157
|
+
(path) => {
|
|
3158
|
+
const handle = deviceSlot.current;
|
|
3159
|
+
if (!handle) {
|
|
3160
|
+
return pendingWritesRef.current.get(path);
|
|
3161
|
+
}
|
|
3162
|
+
return handle.device.readValues([path])[path] ?? void 0;
|
|
3163
|
+
},
|
|
3164
|
+
[deviceSlot]
|
|
3165
|
+
);
|
|
3166
|
+
const getStoreSnapshot = (0, import_react2.useCallback)(() => {
|
|
3167
|
+
const handle = deviceSlot.current;
|
|
3168
|
+
if (!handle) {
|
|
3169
|
+
return void 0;
|
|
3170
|
+
}
|
|
3171
|
+
return handle.device.snapshot();
|
|
3172
|
+
}, [deviceSlot]);
|
|
3173
|
+
const stepListenersRef = (0, import_react2.useRef)(/* @__PURE__ */ new Set());
|
|
3174
|
+
const subscribeToStep = (0, import_react2.useCallback)((listener) => {
|
|
3175
|
+
const listeners = stepListenersRef.current;
|
|
3176
|
+
listeners.add(listener);
|
|
3177
|
+
return () => {
|
|
3178
|
+
listeners.delete(listener);
|
|
3179
|
+
};
|
|
3180
|
+
}, []);
|
|
3181
|
+
const stepRuntime = (0, import_react2.useCallback)(
|
|
3182
|
+
(dt) => {
|
|
3183
|
+
const handle = deviceSlot.current;
|
|
3184
|
+
if (!handle) {
|
|
3185
|
+
return;
|
|
3186
|
+
}
|
|
3187
|
+
if (!handle.device.running) {
|
|
3188
|
+
handle.device.step(dt * 1e3);
|
|
3189
|
+
}
|
|
3190
|
+
const changes = handle.device.drainChanges();
|
|
3191
|
+
applyEngineChangesRef.current(changes);
|
|
3192
|
+
if (storeChangeListenersRef.current.size > 0) {
|
|
3193
|
+
const paths = Object.keys(changes);
|
|
3194
|
+
if (paths.length > 0) {
|
|
3195
|
+
storeChangeListenersRef.current.forEach((listener) => {
|
|
3196
|
+
try {
|
|
3197
|
+
listener(changes);
|
|
3198
|
+
} catch (err) {
|
|
3199
|
+
console.error("[vizij-runtime] store-change listener error", err);
|
|
3200
|
+
}
|
|
3201
|
+
});
|
|
3202
|
+
}
|
|
3203
|
+
}
|
|
3204
|
+
stepListenersRef.current.forEach((listener) => {
|
|
3205
|
+
try {
|
|
3206
|
+
listener();
|
|
3207
|
+
} catch (err) {
|
|
3208
|
+
console.error("[vizij-runtime] step listener error", err);
|
|
3209
|
+
}
|
|
2118
3210
|
});
|
|
2119
3211
|
},
|
|
2120
|
-
[
|
|
3212
|
+
[deviceSlot]
|
|
2121
3213
|
);
|
|
2122
|
-
const
|
|
2123
|
-
(
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
3214
|
+
const storeChangeListenersRef = (0, import_react2.useRef)(
|
|
3215
|
+
/* @__PURE__ */ new Set()
|
|
3216
|
+
);
|
|
3217
|
+
const subscribeToStoreChanges = (0, import_react2.useCallback)(
|
|
3218
|
+
(listener) => {
|
|
3219
|
+
const listeners = storeChangeListenersRef.current;
|
|
3220
|
+
listeners.add(listener);
|
|
3221
|
+
return () => {
|
|
3222
|
+
listeners.delete(listener);
|
|
3223
|
+
};
|
|
2131
3224
|
},
|
|
2132
|
-
[
|
|
3225
|
+
[]
|
|
2133
3226
|
);
|
|
2134
|
-
const resetErrors = (0, import_react2.useCallback)(() => {
|
|
2135
|
-
errorsRef.current = [];
|
|
2136
|
-
reportStatus((prev) => ({
|
|
2137
|
-
...prev,
|
|
2138
|
-
error: null,
|
|
2139
|
-
errors: []
|
|
2140
|
-
}));
|
|
2141
|
-
}, [reportStatus]);
|
|
2142
|
-
(0, import_react2.useEffect)(() => {
|
|
2143
|
-
autostartRef.current = autostart;
|
|
2144
|
-
updateLoopMode();
|
|
2145
|
-
}, [autostart, updateLoopMode]);
|
|
2146
3227
|
const clearControllers = (0, import_react2.useCallback)(() => {
|
|
2147
3228
|
const existing = listControllers();
|
|
2148
3229
|
existing.graphs.forEach((id) => {
|
|
@@ -2177,8 +3258,6 @@ function VizijRuntimeProviderInner({
|
|
|
2177
3258
|
baseOutputPathsRef.current = /* @__PURE__ */ new Set();
|
|
2178
3259
|
namespacedOutputPathsRef.current = /* @__PURE__ */ new Set();
|
|
2179
3260
|
rigPoseControlInputIdsRef.current = /* @__PURE__ */ new Set();
|
|
2180
|
-
clipOutputValuesRef.current.clear();
|
|
2181
|
-
clipAggregateValuesRef.current.clear();
|
|
2182
3261
|
}, [listControllers, removeAnimation, removeGraph, pushError]);
|
|
2183
3262
|
(0, import_react2.useEffect)(() => {
|
|
2184
3263
|
namespaceRef.current = namespace;
|
|
@@ -2189,8 +3268,24 @@ function VizijRuntimeProviderInner({
|
|
|
2189
3268
|
}));
|
|
2190
3269
|
}, [namespace, faceId, reportStatus]);
|
|
2191
3270
|
(0, import_react2.useEffect)(() => {
|
|
2192
|
-
|
|
2193
|
-
}, [
|
|
3271
|
+
driveRuntimeRef.current = driveRuntime;
|
|
3272
|
+
}, [driveRuntime]);
|
|
3273
|
+
(0, import_react2.useEffect)(() => {
|
|
3274
|
+
deviceSlot.onRunEnded = (error) => {
|
|
3275
|
+
pushError({
|
|
3276
|
+
message: "The device's run loop ended: stepping failed",
|
|
3277
|
+
cause: error,
|
|
3278
|
+
phase: "engine",
|
|
3279
|
+
timestamp: performance.now()
|
|
3280
|
+
});
|
|
3281
|
+
};
|
|
3282
|
+
if (driveRuntime) {
|
|
3283
|
+
deviceSlot.runPeriodMs = RUN_PERIOD_MS;
|
|
3284
|
+
deviceSlot.startRun();
|
|
3285
|
+
} else {
|
|
3286
|
+
deviceSlot.runPeriodMs = null;
|
|
3287
|
+
}
|
|
3288
|
+
}, [deviceSlot, driveRuntime, pushError]);
|
|
2194
3289
|
const glbAsset = effectiveAssetBundle.glb;
|
|
2195
3290
|
const baseBundle = effectiveAssetBundle.bundle ?? null;
|
|
2196
3291
|
(0, import_react2.useEffect)(() => {
|
|
@@ -2303,16 +3398,16 @@ function VizijRuntimeProviderInner({
|
|
|
2303
3398
|
]);
|
|
2304
3399
|
(0, import_react2.useEffect)(() => {
|
|
2305
3400
|
if (!ready && autoCreate) {
|
|
2306
|
-
|
|
3401
|
+
initEngine().catch((err) => {
|
|
2307
3402
|
pushError({
|
|
2308
|
-
message: "Failed to
|
|
3403
|
+
message: "Failed to initialize the engine",
|
|
2309
3404
|
cause: err,
|
|
2310
|
-
phase: "
|
|
3405
|
+
phase: "engine",
|
|
2311
3406
|
timestamp: performance.now()
|
|
2312
3407
|
});
|
|
2313
3408
|
});
|
|
2314
3409
|
}
|
|
2315
|
-
}, [ready, autoCreate,
|
|
3410
|
+
}, [ready, autoCreate, initEngine, pushError]);
|
|
2316
3411
|
const registerControllers = (0, import_react2.useCallback)(async () => {
|
|
2317
3412
|
clearControllers();
|
|
2318
3413
|
if (isRuntimeDebugEnabled()) {
|
|
@@ -2475,19 +3570,13 @@ function VizijRuntimeProviderInner({
|
|
|
2475
3570
|
console.log("[vizij-runtime] registered graph ids", graphIds);
|
|
2476
3571
|
}
|
|
2477
3572
|
const animationIds = [];
|
|
3573
|
+
const hostClips = [];
|
|
2478
3574
|
for (const anim of assetBundle.animations ?? []) {
|
|
2479
3575
|
try {
|
|
2480
|
-
const
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
setup: {
|
|
2485
|
-
...anim.setup ?? {},
|
|
2486
|
-
animation: animationPayload
|
|
2487
|
-
}
|
|
2488
|
-
};
|
|
2489
|
-
const id = registerAnimation(config);
|
|
2490
|
-
animationIds.push(id);
|
|
3576
|
+
const stored = anim.setup?.animation ?? toStoredAnimationClip(anim.id, anim.clip);
|
|
3577
|
+
hostClips.push({ id: anim.id, stored });
|
|
3578
|
+
registerAnimation({ id: anim.id });
|
|
3579
|
+
animationIds.push(anim.id);
|
|
2491
3580
|
} catch (err) {
|
|
2492
3581
|
if (isRuntimeDebugEnabled()) {
|
|
2493
3582
|
console.warn("[vizij-runtime] failed animation registration", {
|
|
@@ -2503,6 +3592,7 @@ function VizijRuntimeProviderInner({
|
|
|
2503
3592
|
});
|
|
2504
3593
|
}
|
|
2505
3594
|
}
|
|
3595
|
+
ensureAnimationHost()?.setClips(hostClips);
|
|
2506
3596
|
registeredAnimationsRef.current = animationIds;
|
|
2507
3597
|
if (assetBundle.initialInputs) {
|
|
2508
3598
|
Object.entries(assetBundle.initialInputs).forEach(([path, value]) => {
|
|
@@ -2536,6 +3626,7 @@ function VizijRuntimeProviderInner({
|
|
|
2536
3626
|
}, [
|
|
2537
3627
|
assetBundle,
|
|
2538
3628
|
clearControllers,
|
|
3629
|
+
ensureAnimationHost,
|
|
2539
3630
|
listControllers,
|
|
2540
3631
|
mergeStrategy,
|
|
2541
3632
|
namespace,
|
|
@@ -2567,75 +3658,77 @@ function VizijRuntimeProviderInner({
|
|
|
2567
3658
|
});
|
|
2568
3659
|
}, [ready, status.loading, graphUpdateToken, registerControllers, pushError]);
|
|
2569
3660
|
(0, import_react2.useEffect)(() => {
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
const writes = frame.merged_writes ?? [];
|
|
2574
|
-
if (!writes.length) {
|
|
2575
|
-
return;
|
|
2576
|
-
}
|
|
2577
|
-
const setWorldValues = store.getState().setValues;
|
|
2578
|
-
const namespaceValue = status.namespace;
|
|
2579
|
-
const currentValues = store.getState().values;
|
|
2580
|
-
const rigInputPathMap = rigInputMapRef.current;
|
|
2581
|
-
const rigPoseControlInputIds = rigPoseControlInputIdsRef.current;
|
|
2582
|
-
const batched = [];
|
|
2583
|
-
const namespacedOutputs = namespacedOutputPathsRef.current;
|
|
2584
|
-
const baseOutputs = baseOutputPathsRef.current;
|
|
2585
|
-
writes.forEach((write) => {
|
|
2586
|
-
const path = normalisePath(write.path);
|
|
2587
|
-
const basePath = stripNamespace(path, namespaceValue);
|
|
2588
|
-
const isTrackedOutput = namespacedOutputs.has(path) || baseOutputs.has(basePath);
|
|
2589
|
-
if (!isTrackedOutput) {
|
|
2590
|
-
return;
|
|
2591
|
-
}
|
|
2592
|
-
const raw = valueJSONToRaw(write.value);
|
|
2593
|
-
if (raw === void 0) {
|
|
3661
|
+
applyEngineChangesRef.current = (changes) => {
|
|
3662
|
+
const entries = Object.entries(changes);
|
|
3663
|
+
if (!entries.length) {
|
|
2594
3664
|
return;
|
|
2595
3665
|
}
|
|
2596
|
-
const
|
|
2597
|
-
|
|
2598
|
-
);
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
}
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
3666
|
+
const setWorldValues = store.getState().setValues;
|
|
3667
|
+
const namespaceValue = status.namespace;
|
|
3668
|
+
const currentValues = store.getState().values;
|
|
3669
|
+
const rigInputPathMap = rigInputMapRef.current;
|
|
3670
|
+
const rigPoseControlInputIds = rigPoseControlInputIdsRef.current;
|
|
3671
|
+
const batched = [];
|
|
3672
|
+
const namespacedOutputs = namespacedOutputPathsRef.current;
|
|
3673
|
+
const baseOutputs = baseOutputPathsRef.current;
|
|
3674
|
+
entries.forEach(([changedPath, changedValue]) => {
|
|
3675
|
+
if (changedValue === null || isBuiltInPath(changedPath)) {
|
|
3676
|
+
return;
|
|
3677
|
+
}
|
|
3678
|
+
const path = normalisePath(changedPath);
|
|
3679
|
+
const basePath = stripNamespace(path, namespaceValue);
|
|
3680
|
+
const isTrackedOutput = namespacedOutputs.has(path) || baseOutputs.has(basePath);
|
|
3681
|
+
if (!isTrackedOutput) {
|
|
3682
|
+
return;
|
|
3683
|
+
}
|
|
3684
|
+
const raw = valueJSONToRaw(changedValue);
|
|
3685
|
+
if (raw === void 0) {
|
|
3686
|
+
return;
|
|
3687
|
+
}
|
|
3688
|
+
const poseControlMatch = /^rig\/[^/]+\/pose\/control\/(.+)$/.exec(
|
|
3689
|
+
basePath
|
|
3690
|
+
);
|
|
3691
|
+
if (poseControlMatch && typeof raw === "number" && Number.isFinite(raw)) {
|
|
3692
|
+
const inputId = (poseControlMatch[1] ?? "").trim();
|
|
3693
|
+
const hasNativePoseControlInput = inputId.length > 0 && rigPoseControlInputIds.has(inputId);
|
|
3694
|
+
const mappedInputPath = inputId.length === 0 ? void 0 : resolvePoseControlInputPath({
|
|
3695
|
+
inputId,
|
|
3696
|
+
basePath,
|
|
3697
|
+
rigInputPathMap,
|
|
3698
|
+
hasNativePoseControlInput
|
|
3699
|
+
});
|
|
3700
|
+
if (mappedInputPath) {
|
|
3701
|
+
const bridgeKey = `${namespaceValue}:${mappedInputPath}`;
|
|
3702
|
+
const previousValue = poseControlBridgeValuesRef.current.get(bridgeKey);
|
|
3703
|
+
if (previousValue === void 0 || Math.abs(previousValue - raw) > POSE_CONTROL_BRIDGE_EPSILON) {
|
|
3704
|
+
poseControlBridgeValuesRef.current.set(bridgeKey, raw);
|
|
3705
|
+
setInput(mappedInputPath, { float: raw });
|
|
3706
|
+
}
|
|
2614
3707
|
}
|
|
2615
3708
|
}
|
|
3709
|
+
const targetPath = baseOutputs.has(basePath) ? basePath : path;
|
|
3710
|
+
const currentValue = currentValues.get(
|
|
3711
|
+
(0, import_utils.getLookup)(namespaceValue, targetPath)
|
|
3712
|
+
);
|
|
3713
|
+
const nextWrite = transformOutputWrite ? transformOutputWrite({
|
|
3714
|
+
id: targetPath,
|
|
3715
|
+
namespace: namespaceValue,
|
|
3716
|
+
value: raw,
|
|
3717
|
+
currentValue
|
|
3718
|
+
}) : {
|
|
3719
|
+
id: targetPath,
|
|
3720
|
+
namespace: namespaceValue,
|
|
3721
|
+
value: raw
|
|
3722
|
+
};
|
|
3723
|
+
if (nextWrite) {
|
|
3724
|
+
batched.push(nextWrite);
|
|
3725
|
+
}
|
|
3726
|
+
});
|
|
3727
|
+
if (batched.length > 0) {
|
|
3728
|
+
setWorldValues(batched);
|
|
2616
3729
|
}
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
(0, import_utils.getLookup)(namespaceValue, targetPath)
|
|
2620
|
-
);
|
|
2621
|
-
const nextWrite = transformOutputWrite ? transformOutputWrite({
|
|
2622
|
-
id: targetPath,
|
|
2623
|
-
namespace: namespaceValue,
|
|
2624
|
-
value: raw,
|
|
2625
|
-
currentValue
|
|
2626
|
-
}) : {
|
|
2627
|
-
id: targetPath,
|
|
2628
|
-
namespace: namespaceValue,
|
|
2629
|
-
value: raw
|
|
2630
|
-
};
|
|
2631
|
-
if (nextWrite) {
|
|
2632
|
-
batched.push(nextWrite);
|
|
2633
|
-
}
|
|
2634
|
-
});
|
|
2635
|
-
if (batched.length > 0) {
|
|
2636
|
-
setWorldValues(batched);
|
|
2637
|
-
}
|
|
2638
|
-
}, [frame, status.namespace, store, transformOutputWrite]);
|
|
3730
|
+
};
|
|
3731
|
+
}, [status.namespace, store, transformOutputWrite]);
|
|
2639
3732
|
const stagePoseNeutral = (0, import_react2.useCallback)(
|
|
2640
3733
|
(force = false) => {
|
|
2641
3734
|
const neutral = assetBundle.pose?.config?.neutralInputs ?? {};
|
|
@@ -2724,180 +3817,34 @@ function VizijRuntimeProviderInner({
|
|
|
2724
3817
|
state.completion = completion;
|
|
2725
3818
|
return completion;
|
|
2726
3819
|
}, []);
|
|
2727
|
-
const
|
|
2728
|
-
(
|
|
2729
|
-
|
|
2730
|
-
if (!
|
|
2731
|
-
return;
|
|
2732
|
-
}
|
|
2733
|
-
const namespacedPath = namespaceTypedPath(path, namespaceRef.current);
|
|
2734
|
-
const staged = stagedInputsRef.current.get(namespacedPath);
|
|
2735
|
-
if (staged) {
|
|
2736
|
-
orchestratorSetInput(namespacedPath, staged.value, staged.shape);
|
|
2737
|
-
stagedInputsRef.current.delete(namespacedPath);
|
|
2738
|
-
return;
|
|
2739
|
-
}
|
|
2740
|
-
orchestratorSetInput(namespacedPath, { float: value });
|
|
2741
|
-
},
|
|
2742
|
-
[orchestratorSetInput, setInput]
|
|
2743
|
-
);
|
|
2744
|
-
const clearAnimationInput = (0, import_react2.useCallback)(
|
|
2745
|
-
(path) => {
|
|
2746
|
-
const namespacedPath = namespaceTypedPath(path, namespaceRef.current);
|
|
2747
|
-
stagedInputsRef.current.delete(namespacedPath);
|
|
2748
|
-
removeInput(namespacedPath);
|
|
2749
|
-
},
|
|
2750
|
-
[removeInput]
|
|
2751
|
-
);
|
|
2752
|
-
const buildClipOutputValues = (0, import_react2.useCallback)(
|
|
2753
|
-
(clip, state) => sampleAnimationClipOutputValues(
|
|
2754
|
-
clip.clip,
|
|
2755
|
-
state.time,
|
|
2756
|
-
state.weight,
|
|
2757
|
-
faceId ?? void 0,
|
|
2758
|
-
rigInputMapRef.current
|
|
2759
|
-
),
|
|
2760
|
-
[faceId]
|
|
2761
|
-
);
|
|
2762
|
-
const computeClipAggregateValues = (0, import_react2.useCallback)(() => {
|
|
2763
|
-
const aggregate = /* @__PURE__ */ new Map();
|
|
2764
|
-
clipOutputValuesRef.current.forEach((outputValues) => {
|
|
2765
|
-
outputValues.forEach((value, path) => {
|
|
2766
|
-
aggregate.set(path, (aggregate.get(path) ?? 0) + value);
|
|
2767
|
-
});
|
|
2768
|
-
});
|
|
2769
|
-
return aggregate;
|
|
2770
|
-
}, []);
|
|
2771
|
-
const stageClipAggregateValues = (0, import_react2.useCallback)(
|
|
2772
|
-
(nextAggregate, options) => {
|
|
2773
|
-
diffAnimationAggregateValues(
|
|
2774
|
-
clipAggregateValuesRef.current,
|
|
2775
|
-
nextAggregate,
|
|
2776
|
-
POSE_CONTROL_BRIDGE_EPSILON
|
|
2777
|
-
).forEach((operation) => {
|
|
2778
|
-
if (operation.kind === "clear") {
|
|
2779
|
-
clearAnimationInput(operation.path);
|
|
2780
|
-
return;
|
|
2781
|
-
}
|
|
2782
|
-
setAnimationInput(operation.path, operation.value, options);
|
|
2783
|
-
});
|
|
2784
|
-
clipAggregateValuesRef.current = nextAggregate;
|
|
2785
|
-
},
|
|
2786
|
-
[clearAnimationInput, setAnimationInput]
|
|
2787
|
-
);
|
|
2788
|
-
const syncClipOutputs = (0, import_react2.useCallback)(
|
|
2789
|
-
(options) => {
|
|
2790
|
-
stageClipAggregateValues(computeClipAggregateValues(), options);
|
|
2791
|
-
},
|
|
2792
|
-
[computeClipAggregateValues, stageClipAggregateValues]
|
|
2793
|
-
);
|
|
2794
|
-
const writeClipOutputs = (0, import_react2.useCallback)(
|
|
2795
|
-
(clip, state, options) => {
|
|
2796
|
-
if (!animationSystemActiveRef.current) {
|
|
2797
|
-
return;
|
|
2798
|
-
}
|
|
2799
|
-
clipOutputValuesRef.current.set(
|
|
2800
|
-
clip.id,
|
|
2801
|
-
buildClipOutputValues(clip, state)
|
|
2802
|
-
);
|
|
2803
|
-
syncClipOutputs(options);
|
|
2804
|
-
},
|
|
2805
|
-
[buildClipOutputValues, syncClipOutputs]
|
|
2806
|
-
);
|
|
2807
|
-
const clearClipOutputs = (0, import_react2.useCallback)(
|
|
2808
|
-
(clipId, options) => {
|
|
2809
|
-
if (!clipOutputValuesRef.current.has(clipId)) {
|
|
2810
|
-
return;
|
|
3820
|
+
const ensureClipState = (0, import_react2.useCallback)(
|
|
3821
|
+
(id) => {
|
|
3822
|
+
const clip = resolveClipById(id);
|
|
3823
|
+
if (!clip) {
|
|
3824
|
+
return null;
|
|
2811
3825
|
}
|
|
2812
|
-
clipOutputValuesRef.current.delete(clipId);
|
|
2813
|
-
syncClipOutputs(options);
|
|
2814
|
-
},
|
|
2815
|
-
[syncClipOutputs]
|
|
2816
|
-
);
|
|
2817
|
-
const createClipPlaybackState = (0, import_react2.useCallback)(
|
|
2818
|
-
(clip) => {
|
|
2819
3826
|
const duration = resolveClipDurationSeconds(
|
|
2820
3827
|
clip.clip
|
|
2821
3828
|
);
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
3829
|
+
const existing = clipPlaybackRef.current.get(id);
|
|
3830
|
+
if (existing) {
|
|
3831
|
+
existing.duration = duration;
|
|
3832
|
+
return { clip, state: existing };
|
|
3833
|
+
}
|
|
3834
|
+
const state = {
|
|
3835
|
+
id,
|
|
2825
3836
|
duration,
|
|
2826
3837
|
speed: 1,
|
|
2827
|
-
weight:
|
|
2828
|
-
loop:
|
|
3838
|
+
weight: 1,
|
|
3839
|
+
loop: true,
|
|
2829
3840
|
playing: false,
|
|
2830
3841
|
resolve: null,
|
|
2831
3842
|
completion: null
|
|
2832
3843
|
};
|
|
3844
|
+
clipPlaybackRef.current.set(id, state);
|
|
3845
|
+
return { clip, state };
|
|
2833
3846
|
},
|
|
2834
|
-
[]
|
|
2835
|
-
);
|
|
2836
|
-
const ensureClipPlaybackState = (0, import_react2.useCallback)(
|
|
2837
|
-
(id) => {
|
|
2838
|
-
const clip = resolveClipById(id);
|
|
2839
|
-
if (!clip) {
|
|
2840
|
-
return null;
|
|
2841
|
-
}
|
|
2842
|
-
const existing = clipPlaybackRef.current.get(id);
|
|
2843
|
-
if (existing) {
|
|
2844
|
-
existing.duration = resolveClipDurationSeconds(
|
|
2845
|
-
clip.clip,
|
|
2846
|
-
existing.duration
|
|
2847
|
-
);
|
|
2848
|
-
existing.time = clampAnimationTime(existing.time, existing.duration);
|
|
2849
|
-
return { clip, state: existing };
|
|
2850
|
-
}
|
|
2851
|
-
const next = createClipPlaybackState(clip);
|
|
2852
|
-
clipPlaybackRef.current.set(id, next);
|
|
2853
|
-
return { clip, state: next };
|
|
2854
|
-
},
|
|
2855
|
-
[createClipPlaybackState, resolveClipById]
|
|
2856
|
-
);
|
|
2857
|
-
const advanceClipPlayback = (0, import_react2.useCallback)(
|
|
2858
|
-
(dt) => {
|
|
2859
|
-
if (clipPlaybackRef.current.size === 0) {
|
|
2860
|
-
return;
|
|
2861
|
-
}
|
|
2862
|
-
const toDelete = [];
|
|
2863
|
-
clipPlaybackRef.current.forEach((state, key) => {
|
|
2864
|
-
const clip = resolveClipById(state.id);
|
|
2865
|
-
if (!clip) {
|
|
2866
|
-
toDelete.push(key);
|
|
2867
|
-
resolveClipPromise(state);
|
|
2868
|
-
return;
|
|
2869
|
-
}
|
|
2870
|
-
state.duration = resolveClipDurationSeconds(
|
|
2871
|
-
clip.clip,
|
|
2872
|
-
state.duration
|
|
2873
|
-
);
|
|
2874
|
-
const { time, completed } = advanceClipTime(
|
|
2875
|
-
{
|
|
2876
|
-
time: state.time,
|
|
2877
|
-
duration: state.duration,
|
|
2878
|
-
speed: state.speed,
|
|
2879
|
-
loop: state.loop,
|
|
2880
|
-
playing: state.playing
|
|
2881
|
-
},
|
|
2882
|
-
dt
|
|
2883
|
-
);
|
|
2884
|
-
state.time = clampAnimationTime(time, state.duration);
|
|
2885
|
-
if (state.playing || completed) {
|
|
2886
|
-
writeClipOutputs(clip, state);
|
|
2887
|
-
}
|
|
2888
|
-
if (completed) {
|
|
2889
|
-
toDelete.push(key);
|
|
2890
|
-
resolveClipPromise(state);
|
|
2891
|
-
}
|
|
2892
|
-
});
|
|
2893
|
-
toDelete.forEach((key) => {
|
|
2894
|
-
clipPlaybackRef.current.delete(key);
|
|
2895
|
-
if (animationSystemActiveRef.current) {
|
|
2896
|
-
clearClipOutputs(key);
|
|
2897
|
-
}
|
|
2898
|
-
});
|
|
2899
|
-
},
|
|
2900
|
-
[clearClipOutputs, resolveClipById, resolveClipPromise, writeClipOutputs]
|
|
3847
|
+
[resolveClipById]
|
|
2901
3848
|
);
|
|
2902
3849
|
const animateValue = (0, import_react2.useCallback)(
|
|
2903
3850
|
(path, target, options) => {
|
|
@@ -2971,42 +3918,111 @@ function VizijRuntimeProviderInner({
|
|
|
2971
3918
|
useLegacyPoseWeightFallback
|
|
2972
3919
|
]
|
|
2973
3920
|
);
|
|
3921
|
+
const warnAnimationGap = (0, import_react2.useCallback)((key, message) => {
|
|
3922
|
+
if (animationGapWarnedRef.current.has(key)) {
|
|
3923
|
+
return;
|
|
3924
|
+
}
|
|
3925
|
+
animationGapWarnedRef.current.add(key);
|
|
3926
|
+
console.warn(`[vizij-runtime] ${message}`);
|
|
3927
|
+
}, []);
|
|
3928
|
+
const syncAnimationsSource = (0, import_react2.useCallback)(() => {
|
|
3929
|
+
const host = animationHostRef.current;
|
|
3930
|
+
setAnimationsSourceRegistered(host ? host.hasPlaying() : false);
|
|
3931
|
+
updateLoopMode();
|
|
3932
|
+
}, [setAnimationsSourceRegistered, updateLoopMode]);
|
|
3933
|
+
const readPlayerStates = (0, import_react2.useCallback)(() => {
|
|
3934
|
+
const device = deviceSlot.current?.device;
|
|
3935
|
+
if (!device) {
|
|
3936
|
+
return [];
|
|
3937
|
+
}
|
|
3938
|
+
const raw = device.readValues([ANIMATION_PLAYERS_PATH])[ANIMATION_PLAYERS_PATH];
|
|
3939
|
+
return decodePlayerStates(raw);
|
|
3940
|
+
}, [deviceSlot]);
|
|
3941
|
+
const settleFinishedClips = (0, import_react2.useCallback)(() => {
|
|
3942
|
+
let pending = false;
|
|
3943
|
+
clipPlaybackRef.current.forEach((state) => {
|
|
3944
|
+
if (state.completion && !state.loop) {
|
|
3945
|
+
pending = true;
|
|
3946
|
+
}
|
|
3947
|
+
});
|
|
3948
|
+
const host = animationHostRef.current;
|
|
3949
|
+
if (!pending || !host) {
|
|
3950
|
+
return;
|
|
3951
|
+
}
|
|
3952
|
+
const states = readPlayerStates();
|
|
3953
|
+
if (states.length === 0) {
|
|
3954
|
+
return;
|
|
3955
|
+
}
|
|
3956
|
+
clipPlaybackRef.current.forEach((state, id) => {
|
|
3957
|
+
if (!state.completion || state.loop) {
|
|
3958
|
+
return;
|
|
3959
|
+
}
|
|
3960
|
+
const playerId = host.playerIdOf(id);
|
|
3961
|
+
if (playerId === null) {
|
|
3962
|
+
return;
|
|
3963
|
+
}
|
|
3964
|
+
const feedback = states.find((entry) => entry.player === playerId);
|
|
3965
|
+
if (!feedback) {
|
|
3966
|
+
return;
|
|
3967
|
+
}
|
|
3968
|
+
const duration = feedback.duration > 0 ? feedback.duration : state.duration;
|
|
3969
|
+
const atEnd = duration > 0 && feedback.time >= duration - 1e-3;
|
|
3970
|
+
if (feedback.state === "stopped" || atEnd) {
|
|
3971
|
+
state.playing = false;
|
|
3972
|
+
resolveClipPromise(state);
|
|
3973
|
+
host.pause(id);
|
|
3974
|
+
syncAnimationsSource();
|
|
3975
|
+
}
|
|
3976
|
+
});
|
|
3977
|
+
}, [readPlayerStates, resolveClipPromise, syncAnimationsSource]);
|
|
2974
3978
|
const playAnimation = (0, import_react2.useCallback)(
|
|
2975
3979
|
(id, options) => {
|
|
2976
|
-
const ensured =
|
|
3980
|
+
const ensured = ensureClipState(id);
|
|
2977
3981
|
if (!ensured) {
|
|
2978
3982
|
return Promise.reject(
|
|
2979
3983
|
new Error(`Animation ${id} is not part of the current asset bundle.`)
|
|
2980
3984
|
);
|
|
2981
3985
|
}
|
|
2982
|
-
const {
|
|
2983
|
-
const
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
state.time = 0;
|
|
2987
|
-
}
|
|
2988
|
-
const speed = options?.speed ?? state.speed ?? 1;
|
|
2989
|
-
const weight = options?.weight ?? state.weight ?? clip.weight ?? 1;
|
|
2990
|
-
state.speed = Number.isFinite(speed) && speed > 0 ? speed : 1;
|
|
2991
|
-
state.weight = Number.isFinite(weight) ? Number(weight) : 1;
|
|
2992
|
-
state.duration = resolveClipDurationSeconds(
|
|
2993
|
-
clip.clip,
|
|
2994
|
-
state.duration
|
|
2995
|
-
);
|
|
2996
|
-
state.time = clampAnimationTime(state.time, state.duration);
|
|
3986
|
+
const { state } = ensured;
|
|
3987
|
+
const host = ensureAnimationHost();
|
|
3988
|
+
state.speed = options?.speed ?? state.speed;
|
|
3989
|
+
state.weight = options?.weight ?? state.weight;
|
|
2997
3990
|
state.playing = true;
|
|
2998
3991
|
const completion = ensureClipPromise(state);
|
|
2999
3992
|
clipPlaybackRef.current.set(id, state);
|
|
3000
|
-
|
|
3993
|
+
if (host) {
|
|
3994
|
+
if (options?.reset === true) {
|
|
3995
|
+
host.seek(id, 0);
|
|
3996
|
+
}
|
|
3997
|
+
host.setSpeed(id, state.speed);
|
|
3998
|
+
host.setWeight(id, state.weight);
|
|
3999
|
+
host.setLoop(id, state.loop ? "loop" : "once");
|
|
4000
|
+
void host.play(id).catch((err) => {
|
|
4001
|
+
pushError({
|
|
4002
|
+
message: `Failed to start animation ${id} on the device`,
|
|
4003
|
+
cause: err,
|
|
4004
|
+
phase: "animation",
|
|
4005
|
+
timestamp: performance.now()
|
|
4006
|
+
});
|
|
4007
|
+
});
|
|
4008
|
+
} else {
|
|
4009
|
+
warnAnimationGap(
|
|
4010
|
+
"module-not-ready",
|
|
4011
|
+
"playAnimation called before the animation module finished loading; playback starts once it is ready."
|
|
4012
|
+
);
|
|
4013
|
+
}
|
|
4014
|
+
syncAnimationsSource();
|
|
3001
4015
|
markActivity();
|
|
3002
4016
|
return completion;
|
|
3003
4017
|
},
|
|
3004
4018
|
[
|
|
3005
|
-
|
|
4019
|
+
ensureAnimationHost,
|
|
3006
4020
|
ensureClipPromise,
|
|
4021
|
+
ensureClipState,
|
|
3007
4022
|
markActivity,
|
|
3008
|
-
|
|
3009
|
-
|
|
4023
|
+
pushError,
|
|
4024
|
+
syncAnimationsSource,
|
|
4025
|
+
warnAnimationGap
|
|
3010
4026
|
]
|
|
3011
4027
|
);
|
|
3012
4028
|
const pauseAnimation = (0, import_react2.useCallback)(
|
|
@@ -3016,34 +4032,28 @@ function VizijRuntimeProviderInner({
|
|
|
3016
4032
|
return;
|
|
3017
4033
|
}
|
|
3018
4034
|
state.playing = false;
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
[updateLoopMode]
|
|
3022
|
-
);
|
|
3023
|
-
const seekAnimation = (0, import_react2.useCallback)(
|
|
3024
|
-
(id, timeSeconds) => {
|
|
3025
|
-
const ensured = ensureClipPlaybackState(id);
|
|
3026
|
-
if (!ensured) {
|
|
3027
|
-
return;
|
|
3028
|
-
}
|
|
3029
|
-
const { clip, state } = ensured;
|
|
3030
|
-
state.time = clampAnimationTime(timeSeconds, state.duration);
|
|
3031
|
-
clipPlaybackRef.current.set(id, state);
|
|
3032
|
-
writeClipOutputs(clip, state, { immediate: true });
|
|
4035
|
+
animationHostRef.current?.pause(id);
|
|
4036
|
+
syncAnimationsSource();
|
|
3033
4037
|
},
|
|
3034
|
-
[
|
|
4038
|
+
[syncAnimationsSource]
|
|
3035
4039
|
);
|
|
4040
|
+
const seekAnimation = (0, import_react2.useCallback)((id, timeSeconds) => {
|
|
4041
|
+
if (!clipPlaybackRef.current.has(id)) {
|
|
4042
|
+
return;
|
|
4043
|
+
}
|
|
4044
|
+
animationHostRef.current?.seek(id, Math.max(0, timeSeconds));
|
|
4045
|
+
}, []);
|
|
3036
4046
|
const setAnimationLoop = (0, import_react2.useCallback)(
|
|
3037
4047
|
(id, enabled) => {
|
|
3038
|
-
const ensured =
|
|
4048
|
+
const ensured = ensureClipState(id);
|
|
3039
4049
|
if (!ensured) {
|
|
3040
4050
|
return;
|
|
3041
4051
|
}
|
|
3042
4052
|
ensured.state.loop = Boolean(enabled);
|
|
3043
|
-
|
|
4053
|
+
animationHostRef.current?.setLoop(id, enabled ? "loop" : "once");
|
|
3044
4054
|
updateLoopMode();
|
|
3045
4055
|
},
|
|
3046
|
-
[
|
|
4056
|
+
[ensureClipState, updateLoopMode]
|
|
3047
4057
|
);
|
|
3048
4058
|
const getAnimationState = (0, import_react2.useCallback)(
|
|
3049
4059
|
(id) => {
|
|
@@ -3051,15 +4061,17 @@ function VizijRuntimeProviderInner({
|
|
|
3051
4061
|
if (!state) {
|
|
3052
4062
|
return null;
|
|
3053
4063
|
}
|
|
4064
|
+
const playerId = animationHostRef.current?.playerIdOf(id) ?? null;
|
|
4065
|
+
const feedback = playerId !== null ? readPlayerStates().find((entry) => entry.player === playerId) : void 0;
|
|
3054
4066
|
return {
|
|
3055
|
-
time:
|
|
3056
|
-
duration: state.duration,
|
|
3057
|
-
playing: state.playing,
|
|
4067
|
+
time: feedback?.time ?? 0,
|
|
4068
|
+
duration: feedback && feedback.duration > 0 ? feedback.duration : state.duration,
|
|
4069
|
+
playing: feedback ? feedback.state === "playing" : state.playing,
|
|
3058
4070
|
loop: state.loop,
|
|
3059
|
-
speed: state.speed
|
|
4071
|
+
speed: feedback?.speed ?? state.speed
|
|
3060
4072
|
};
|
|
3061
4073
|
},
|
|
3062
|
-
[]
|
|
4074
|
+
[readPlayerStates]
|
|
3063
4075
|
);
|
|
3064
4076
|
const stopAnimation = (0, import_react2.useCallback)(
|
|
3065
4077
|
(id, options) => {
|
|
@@ -3069,12 +4081,19 @@ function VizijRuntimeProviderInner({
|
|
|
3069
4081
|
state.playing = false;
|
|
3070
4082
|
resolveClipPromise(state);
|
|
3071
4083
|
}
|
|
3072
|
-
|
|
3073
|
-
|
|
4084
|
+
const host = animationHostRef.current;
|
|
4085
|
+
if (options?.clearOutputs === false) {
|
|
4086
|
+
host?.pause(id);
|
|
4087
|
+
syncAnimationsSource();
|
|
4088
|
+
} else {
|
|
4089
|
+
host?.stop(id);
|
|
4090
|
+
const unsubscribe = subscribeToStep(() => {
|
|
4091
|
+
unsubscribe();
|
|
4092
|
+
syncAnimationsSource();
|
|
4093
|
+
});
|
|
3074
4094
|
}
|
|
3075
|
-
updateLoopMode();
|
|
3076
4095
|
},
|
|
3077
|
-
[
|
|
4096
|
+
[resolveClipPromise, subscribeToStep, syncAnimationsSource]
|
|
3078
4097
|
);
|
|
3079
4098
|
const refreshControllerStatus = (0, import_react2.useCallback)(() => {
|
|
3080
4099
|
const controllers = listControllers();
|
|
@@ -3223,11 +4242,21 @@ function VizijRuntimeProviderInner({
|
|
|
3223
4242
|
]);
|
|
3224
4243
|
const playProgram = (0, import_react2.useCallback)(
|
|
3225
4244
|
(id) => {
|
|
3226
|
-
|
|
4245
|
+
const program = resolveProgramById(id);
|
|
4246
|
+
if (!program) {
|
|
3227
4247
|
throw new Error(
|
|
3228
4248
|
`Program ${id} is not part of the current asset bundle.`
|
|
3229
4249
|
);
|
|
3230
4250
|
}
|
|
4251
|
+
deriveProgramInputSeedValues({
|
|
4252
|
+
program,
|
|
4253
|
+
namespace,
|
|
4254
|
+
inputConstraints: inputConstraintsRef.current,
|
|
4255
|
+
getPathSnapshot,
|
|
4256
|
+
stagedInputs: stagedInputsRef.current
|
|
4257
|
+
}).forEach(({ path, value }) => {
|
|
4258
|
+
setInput(path, value);
|
|
4259
|
+
});
|
|
3231
4260
|
programPlaybackRef.current.set(id, {
|
|
3232
4261
|
id,
|
|
3233
4262
|
state: "playing"
|
|
@@ -3235,7 +4264,14 @@ function VizijRuntimeProviderInner({
|
|
|
3235
4264
|
syncProgramPlaybackControllers();
|
|
3236
4265
|
markActivity();
|
|
3237
4266
|
},
|
|
3238
|
-
[
|
|
4267
|
+
[
|
|
4268
|
+
getPathSnapshot,
|
|
4269
|
+
markActivity,
|
|
4270
|
+
namespace,
|
|
4271
|
+
resolveProgramById,
|
|
4272
|
+
setInput,
|
|
4273
|
+
syncProgramPlaybackControllers
|
|
4274
|
+
]
|
|
3239
4275
|
);
|
|
3240
4276
|
const pauseProgram = (0, import_react2.useCallback)(
|
|
3241
4277
|
(id) => {
|
|
@@ -3334,6 +4370,7 @@ function VizijRuntimeProviderInner({
|
|
|
3334
4370
|
);
|
|
3335
4371
|
const registerInputDriver = (0, import_react2.useCallback)(
|
|
3336
4372
|
(id, factory) => {
|
|
4373
|
+
inputDriverIdsRef.current.add(id);
|
|
3337
4374
|
const driver = factory({
|
|
3338
4375
|
setInput,
|
|
3339
4376
|
setRendererValue,
|
|
@@ -3375,6 +4412,8 @@ function VizijRuntimeProviderInner({
|
|
|
3375
4412
|
phase: "driver",
|
|
3376
4413
|
timestamp: performance.now()
|
|
3377
4414
|
});
|
|
4415
|
+
} finally {
|
|
4416
|
+
inputDriverIdsRef.current.delete(id);
|
|
3378
4417
|
}
|
|
3379
4418
|
}
|
|
3380
4419
|
};
|
|
@@ -3382,22 +4421,15 @@ function VizijRuntimeProviderInner({
|
|
|
3382
4421
|
},
|
|
3383
4422
|
[faceId, namespace, pushError, setInput, setRendererValue]
|
|
3384
4423
|
);
|
|
3385
|
-
const advanceAnimations = (0, import_react2.useCallback)(
|
|
3386
|
-
(dt) => {
|
|
3387
|
-
advanceAnimationTweens(dt);
|
|
3388
|
-
advanceClipPlayback(dt);
|
|
3389
|
-
},
|
|
3390
|
-
[advanceAnimationTweens, advanceClipPlayback]
|
|
3391
|
-
);
|
|
3392
4424
|
const flushStagedInputs = (0, import_react2.useCallback)(() => {
|
|
3393
4425
|
if (stagedInputsRef.current.size === 0) {
|
|
3394
4426
|
return;
|
|
3395
4427
|
}
|
|
3396
4428
|
stagedInputsRef.current.forEach(({ value, shape }, path) => {
|
|
3397
|
-
|
|
4429
|
+
deviceSetInput(path, value, shape);
|
|
3398
4430
|
});
|
|
3399
4431
|
stagedInputsRef.current.clear();
|
|
3400
|
-
}, [
|
|
4432
|
+
}, [deviceSetInput]);
|
|
3401
4433
|
const step = (0, import_react2.useCallback)(
|
|
3402
4434
|
(dt, opts) => {
|
|
3403
4435
|
if (dt > 0 && Number.isFinite(dt)) {
|
|
@@ -3405,13 +4437,19 @@ function VizijRuntimeProviderInner({
|
|
|
3405
4437
|
const alpha = 0.1;
|
|
3406
4438
|
avgStepDtRef.current = prev * (1 - alpha) + dt * alpha;
|
|
3407
4439
|
}
|
|
3408
|
-
|
|
4440
|
+
advanceAnimationTweens(dt);
|
|
3409
4441
|
flushStagedInputs();
|
|
3410
|
-
if (
|
|
4442
|
+
if (driveRuntimeRef.current || opts?.forceRuntime) {
|
|
3411
4443
|
stepRuntime(dt);
|
|
3412
4444
|
}
|
|
4445
|
+
settleFinishedClips();
|
|
3413
4446
|
},
|
|
3414
|
-
[
|
|
4447
|
+
[
|
|
4448
|
+
advanceAnimationTweens,
|
|
4449
|
+
flushStagedInputs,
|
|
4450
|
+
settleFinishedClips,
|
|
4451
|
+
stepRuntime
|
|
4452
|
+
]
|
|
3415
4453
|
);
|
|
3416
4454
|
(0, import_react2.useEffect)(() => {
|
|
3417
4455
|
if (loopMode !== "active") {
|
|
@@ -3426,7 +4464,7 @@ function VizijRuntimeProviderInner({
|
|
|
3426
4464
|
if (lastTime == null) {
|
|
3427
4465
|
lastTime = timestamp;
|
|
3428
4466
|
}
|
|
3429
|
-
const dt = Math.max(0, (timestamp - lastTime) / 1e3);
|
|
4467
|
+
const dt = inferImplicitPause(Math.max(0, (timestamp - lastTime) / 1e3));
|
|
3430
4468
|
lastTime = timestamp;
|
|
3431
4469
|
step(dt || 0);
|
|
3432
4470
|
requestLoopMode(computeDesiredLoopMode());
|
|
@@ -3457,7 +4495,7 @@ function VizijRuntimeProviderInner({
|
|
|
3457
4495
|
return;
|
|
3458
4496
|
}
|
|
3459
4497
|
const current = now();
|
|
3460
|
-
const dt = Math.max(0, (current - lastTime) / 1e3);
|
|
4498
|
+
const dt = inferImplicitPause(Math.max(0, (current - lastTime) / 1e3));
|
|
3461
4499
|
lastTime = current;
|
|
3462
4500
|
step(dt || 0);
|
|
3463
4501
|
requestLoopMode(computeDesiredLoopMode());
|
|
@@ -3473,8 +4511,6 @@ function VizijRuntimeProviderInner({
|
|
|
3473
4511
|
clipPlaybackRef.current.clear();
|
|
3474
4512
|
programPlaybackRef.current.clear();
|
|
3475
4513
|
programControllerIdsRef.current.clear();
|
|
3476
|
-
clipOutputValuesRef.current.clear();
|
|
3477
|
-
clipAggregateValuesRef.current.clear();
|
|
3478
4514
|
};
|
|
3479
4515
|
}, []);
|
|
3480
4516
|
(0, import_react2.useEffect)(() => {
|
|
@@ -3527,6 +4563,10 @@ function VizijRuntimeProviderInner({
|
|
|
3527
4563
|
...status,
|
|
3528
4564
|
assetBundle,
|
|
3529
4565
|
setInput,
|
|
4566
|
+
getValueSnapshot: getPathSnapshot,
|
|
4567
|
+
getStoreSnapshot,
|
|
4568
|
+
subscribeToStoreChanges,
|
|
4569
|
+
subscribeToStep,
|
|
3530
4570
|
setGraphBundle,
|
|
3531
4571
|
setValue: setRendererValue,
|
|
3532
4572
|
stagePoseNeutral,
|
|
@@ -3546,13 +4586,16 @@ function VizijRuntimeProviderInner({
|
|
|
3546
4586
|
setAnimationActive,
|
|
3547
4587
|
isAnimationActive,
|
|
3548
4588
|
step,
|
|
3549
|
-
advanceAnimations,
|
|
3550
4589
|
inputConstraints
|
|
3551
4590
|
}),
|
|
3552
4591
|
[
|
|
3553
4592
|
status,
|
|
3554
4593
|
assetBundle,
|
|
3555
4594
|
setInput,
|
|
4595
|
+
getPathSnapshot,
|
|
4596
|
+
getStoreSnapshot,
|
|
4597
|
+
subscribeToStoreChanges,
|
|
4598
|
+
subscribeToStep,
|
|
3556
4599
|
setGraphBundle,
|
|
3557
4600
|
setRendererValue,
|
|
3558
4601
|
stagePoseNeutral,
|
|
@@ -3572,7 +4615,6 @@ function VizijRuntimeProviderInner({
|
|
|
3572
4615
|
setAnimationActive,
|
|
3573
4616
|
isAnimationActive,
|
|
3574
4617
|
step,
|
|
3575
|
-
advanceAnimations,
|
|
3576
4618
|
inputConstraints
|
|
3577
4619
|
]
|
|
3578
4620
|
);
|