@moku-labs/game 0.14.8 → 0.14.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app/page.d.mts +1 -1
- package/dist/app/system.d.mts +3 -3
- package/dist/app.d.mts +2 -2
- package/dist/app.mjs +4 -3
- package/dist/assets.d.mts +1 -1
- package/dist/{commands-BiZwnDBd.d.mts → commands-fUjYUA67.d.mts} +5 -5
- package/dist/control.d.mts +4 -4
- package/dist/{define-TUpLvorL.d.mts → define-BN15lAWb.d.mts} +2 -2
- package/dist/{headless-TowWxARz.d.mts → headless-CrhefcLu.d.mts} +1 -1
- package/dist/{index-BRdctuIb.d.mts → index-pXWn22ql.d.mts} +7 -7
- package/dist/index.d.mts +5 -5
- package/dist/index.mjs +1 -1
- package/dist/inspect.d.mts +7 -7
- package/dist/jsx-dev-runtime.d.mts +1 -1
- package/dist/jsx-runtime.d.mts +1 -1
- package/dist/{memory-D6S7BUAo.d.mts → memory-DR1ibF_s.d.mts} +1 -1
- package/dist/{runtime-BYbkAbM9.d.mts → runtime-DhiqvW6v.d.mts} +1 -1
- package/dist/{src-DiPISDVO.mjs → src-CzfMhkry.mjs} +583 -106
- package/dist/testing.d.mts +4 -4
- package/dist/testing.mjs +1 -1
- package/dist/{types-p12uOu00.d.mts → types-6yCbzHHn.d.mts} +1 -1
- package/dist/{types-CUNcMkD1.d.mts → types-BT9S_Lo2.d.mts} +103 -31
- package/dist/{types-CsejHij6.d.mts → types-Bc9AvZVO.d.mts} +2 -2
- package/dist/{types-HbY-NiRP.d.mts → types-Biehq93s.d.mts} +18 -8
- package/dist/{types-CmhczThS.d.mts → types-DxKupZ6I.d.mts} +15 -4
- package/dist/{types-Ck18SF1P.d.mts → types-S8UOeAML.d.mts} +2 -2
- package/dist/visual.d.mts +6 -6
- package/llms.txt +9 -4
- package/package.json +6 -6
|
@@ -387,7 +387,7 @@ function stopLoop(state) {
|
|
|
387
387
|
*
|
|
388
388
|
* @param ctx - Domain context of the time plugin.
|
|
389
389
|
*/
|
|
390
|
-
function startLoop$
|
|
390
|
+
function startLoop$2(ctx) {
|
|
391
391
|
if (typeof globalThis.requestAnimationFrame !== "function") return;
|
|
392
392
|
ctx.state.running = true;
|
|
393
393
|
ctx.state.rafId = globalThis.requestAnimationFrame(createFrameFunction(ctx));
|
|
@@ -451,7 +451,7 @@ const timePlugin = /*#__PURE__*/ createPlugin$1("time", {
|
|
|
451
451
|
},
|
|
452
452
|
createState: createTimeState,
|
|
453
453
|
api: createTimeApi,
|
|
454
|
-
onStart: startLoop$
|
|
454
|
+
onStart: startLoop$2,
|
|
455
455
|
onStop: ({ state }) => stopLoop(state)
|
|
456
456
|
});
|
|
457
457
|
//#endregion
|
|
@@ -2941,7 +2941,7 @@ function continueSlot(frames, finished) {
|
|
|
2941
2941
|
//#endregion
|
|
2942
2942
|
//#region src/plugins/flow/runner/edge.ts
|
|
2943
2943
|
/**
|
|
2944
|
-
* Turns whatever a node failed with into an error the log takes.
|
|
2944
|
+
* Turns whatever a node, or an effect it started, failed with into an error the log takes.
|
|
2945
2945
|
*
|
|
2946
2946
|
* @param value - The thrown value or the rejection reason.
|
|
2947
2947
|
* @returns The value itself when it is an error, a new error with its text otherwise.
|
|
@@ -3340,14 +3340,16 @@ function refuseEffect(signal) {
|
|
|
3340
3340
|
return refused;
|
|
3341
3341
|
}
|
|
3342
3342
|
/**
|
|
3343
|
-
* Builds the `fx` of a node context: awaited effects by call,
|
|
3344
|
-
* narrows the gate while it runs; the runner lifts
|
|
3345
|
-
* was aborted keeps running, since the loop only
|
|
3346
|
-
* any more: the gate and the narrow belong to the
|
|
3347
|
-
* of a rest node gets the same `fx`, bound to the
|
|
3343
|
+
* Builds the `fx` of a node context: awaited effects by call, effects the body does not wait for
|
|
3344
|
+
* by `start`, cosmetic hints by `emit`. A `guide` narrows the gate while it runs; the runner lifts
|
|
3345
|
+
* the narrow when the node is left. A body that was aborted keeps running, since the loop only
|
|
3346
|
+
* stops waiting for it, so its `fx` starts nothing any more: the gate and the narrow belong to the
|
|
3347
|
+
* node the loop entered after it. The `recover` of a rest node gets the same `fx`, bound to the
|
|
3348
|
+
* signal of its recovery.
|
|
3348
3349
|
*
|
|
3349
3350
|
* @param modules - Injected sibling APIs.
|
|
3350
|
-
* @param step - The run in progress: its abort signal,
|
|
3351
|
+
* @param step - The run in progress: its abort signal, how it tells a walk that a gate opened, and
|
|
3352
|
+
* the path and the log a failed `start` is reported with.
|
|
3351
3353
|
* @returns The effects gateway of one node run.
|
|
3352
3354
|
*/
|
|
3353
3355
|
function nodeFx(modules, step) {
|
|
@@ -3367,15 +3369,39 @@ function nodeFx(modules, step) {
|
|
|
3367
3369
|
if (descriptor.answers !== void 0) step.gateOpened();
|
|
3368
3370
|
return value;
|
|
3369
3371
|
};
|
|
3370
|
-
return Object.assign(run, {
|
|
3371
3372
|
/**
|
|
3372
|
-
*
|
|
3373
|
+
* Starts one effect and does not wait for it. It takes the path of an awaited effect, so the
|
|
3374
|
+
* mode and the node's signal apply; only its end is nobody's wait. A failure is the handler's:
|
|
3375
|
+
* it goes to the log and never to the node. A `cosmetic` descriptor does not get that far: the
|
|
3376
|
+
* gateway swallows its failure. After the abort of the node nothing is started and nothing is
|
|
3377
|
+
* thrown, not even for a descriptor refused otherwise. An effect that ends with a rejection
|
|
3378
|
+
* after the abort did not fail: its handler was told to stop, so nothing is logged.
|
|
3373
3379
|
*
|
|
3374
|
-
* @param
|
|
3380
|
+
* @param descriptor - The descriptor the node starts.
|
|
3381
|
+
* @throws {Error} When the descriptor has `answers` or is a `guide`.
|
|
3375
3382
|
*/
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
|
|
3383
|
+
const start = (descriptor) => {
|
|
3384
|
+
if (step.signal.aborted) return;
|
|
3385
|
+
if (descriptor.answers !== void 0 || descriptor.kind === "guide") throw new Error(`[game] fx.start() got the effect "${descriptor.kind}" in the node "${step.path}", and its answer has to be awaited.\n Write await fx(...) for a descriptor with answers and for a guide.`);
|
|
3386
|
+
modules.fx.run(descriptor, step.signal).catch((error) => {
|
|
3387
|
+
if (step.signal.aborted) return;
|
|
3388
|
+
step.log.error("flow:fx-start-failed", {
|
|
3389
|
+
path: step.path,
|
|
3390
|
+
kind: descriptor.kind
|
|
3391
|
+
}, asError$4(error));
|
|
3392
|
+
});
|
|
3393
|
+
};
|
|
3394
|
+
return Object.assign(run, {
|
|
3395
|
+
/**
|
|
3396
|
+
* Buffers one cosmetic hint until the transaction of this node commits.
|
|
3397
|
+
*
|
|
3398
|
+
* @param item - The hint.
|
|
3399
|
+
*/
|
|
3400
|
+
emit: (item) => {
|
|
3401
|
+
modules.fx.buffer(item);
|
|
3402
|
+
},
|
|
3403
|
+
start
|
|
3404
|
+
});
|
|
3379
3405
|
}
|
|
3380
3406
|
/**
|
|
3381
3407
|
* Builds the one object a node body receives.
|
|
@@ -3791,7 +3817,8 @@ async function recoverNode(ctx, modules, place, safe) {
|
|
|
3791
3817
|
*/
|
|
3792
3818
|
gateOpened: () => {
|
|
3793
3819
|
notifyGateOpen(state);
|
|
3794
|
-
}
|
|
3820
|
+
},
|
|
3821
|
+
log: ctx.log
|
|
3795
3822
|
};
|
|
3796
3823
|
const outcome = await Promise.race([callRecover(modules, recovery), abortOutcome(abort.signal)]);
|
|
3797
3824
|
modules.gate.narrow(void 0);
|
|
@@ -3926,7 +3953,8 @@ async function runNode(ctx, modules, location, node, safe) {
|
|
|
3926
3953
|
*/
|
|
3927
3954
|
gateOpened: () => {
|
|
3928
3955
|
notifyGateOpen(state);
|
|
3929
|
-
}
|
|
3956
|
+
},
|
|
3957
|
+
log: ctx.log
|
|
3930
3958
|
};
|
|
3931
3959
|
const outcome = await nodeOutcome(modules, step, { event: void 0 });
|
|
3932
3960
|
modules.gate.narrow(void 0);
|
|
@@ -6579,6 +6607,8 @@ function noFields$1() {
|
|
|
6579
6607
|
function joinFields(driven, owned) {
|
|
6580
6608
|
return /* @__PURE__ */ new Set([...driven ?? [], ...owned]);
|
|
6581
6609
|
}
|
|
6610
|
+
/** What a component no loop adds to answers: no offset on any field. */
|
|
6611
|
+
const NO_OFFSETS = Object.freeze({});
|
|
6582
6612
|
/** A handle for a motion that is already done. */
|
|
6583
6613
|
const INERT = {
|
|
6584
6614
|
finish: () => {},
|
|
@@ -6682,7 +6712,8 @@ function startTrack$2(pctx, entity, component, to, options) {
|
|
|
6682
6712
|
if (handle.active()) state.tracks.push({
|
|
6683
6713
|
entity,
|
|
6684
6714
|
component: component.componentName,
|
|
6685
|
-
handle
|
|
6715
|
+
handle,
|
|
6716
|
+
additive: options.additive === true
|
|
6686
6717
|
});
|
|
6687
6718
|
return handle;
|
|
6688
6719
|
}
|
|
@@ -6708,7 +6739,9 @@ function forgetEndedTracks(pctx) {
|
|
|
6708
6739
|
state.tracks = state.tracks.filter((track) => track.handle.active());
|
|
6709
6740
|
}
|
|
6710
6741
|
/**
|
|
6711
|
-
* The component names a running track drives on one entity.
|
|
6742
|
+
* The component names a running track drives on one entity. An additive track drives none: the
|
|
6743
|
+
* loop of a view adds an offset to the rest pose and owns no component, so a settle may bring
|
|
6744
|
+
* the component home under it.
|
|
6712
6745
|
*
|
|
6713
6746
|
* @param pctx - Domain context of the projection module.
|
|
6714
6747
|
* @param entity - The entity to ask about.
|
|
@@ -6717,10 +6750,23 @@ function forgetEndedTracks(pctx) {
|
|
|
6717
6750
|
function drivenComponents(pctx, entity) {
|
|
6718
6751
|
forgetEndedTracks(pctx);
|
|
6719
6752
|
const driven = noFields$1();
|
|
6720
|
-
for (const track of pctx.ctx.state.projection.tracks) if (track.entity === entity) driven.add(track.component);
|
|
6753
|
+
for (const track of pctx.ctx.state.projection.tracks) if (track.entity === entity && !track.additive) driven.add(track.component);
|
|
6721
6754
|
return driven;
|
|
6722
6755
|
}
|
|
6723
6756
|
/**
|
|
6757
|
+
* What the additive tracks of the installed driver add to one component of one entity right now:
|
|
6758
|
+
* the offset of a loop. A driver that does not report it, and a world without a driver, answer
|
|
6759
|
+
* no offset.
|
|
6760
|
+
*
|
|
6761
|
+
* @param pctx - Domain context of the projection module.
|
|
6762
|
+
* @param entity - The entity to ask about.
|
|
6763
|
+
* @param component - The component to ask about.
|
|
6764
|
+
* @returns Field name to what is added to it, empty for a component no loop adds to.
|
|
6765
|
+
*/
|
|
6766
|
+
function loopOffsets(pctx, entity, component) {
|
|
6767
|
+
return pctx.ctx.state.projection.driver?.offsetsOf?.(entity, component) ?? NO_OFFSETS;
|
|
6768
|
+
}
|
|
6769
|
+
/**
|
|
6724
6770
|
* The components a running track drives on one entity, in start order, each name once. It only
|
|
6725
6771
|
* reads: an ended track is skipped, not forgotten.
|
|
6726
6772
|
*
|
|
@@ -6916,6 +6962,7 @@ function createViewRecord(entity, projection, key, item, rest) {
|
|
|
6916
6962
|
handles: [],
|
|
6917
6963
|
lifted: false,
|
|
6918
6964
|
dropWhenStill: false,
|
|
6965
|
+
loopOwed: false,
|
|
6919
6966
|
exiting: false
|
|
6920
6967
|
};
|
|
6921
6968
|
}
|
|
@@ -7055,7 +7102,69 @@ function runHook(pctx, view, hook, run) {
|
|
|
7055
7102
|
}
|
|
7056
7103
|
}
|
|
7057
7104
|
/**
|
|
7058
|
-
* The
|
|
7105
|
+
* The peers of a call outside a reconcile, as `settle` and the loop the sweep starts make it:
|
|
7106
|
+
* both sides are the items of the last reconcile.
|
|
7107
|
+
*
|
|
7108
|
+
* @param mounted - The mounted projection.
|
|
7109
|
+
* @returns The peers, the same item table as new and as old.
|
|
7110
|
+
*/
|
|
7111
|
+
function restingPeers(mounted) {
|
|
7112
|
+
return {
|
|
7113
|
+
next: mounted.items,
|
|
7114
|
+
previous: mounted.items
|
|
7115
|
+
};
|
|
7116
|
+
}
|
|
7117
|
+
/**
|
|
7118
|
+
* Plays the `loop` hook of a view, when its projection has one. The motion is not kept with the
|
|
7119
|
+
* view's handles: a change cancels those, and the loop runs until the view dies or a flush ends
|
|
7120
|
+
* its tracks.
|
|
7121
|
+
*
|
|
7122
|
+
* @param pctx - Domain context of the projection module.
|
|
7123
|
+
* @param view - The view that loops.
|
|
7124
|
+
* @param spec - The projection spec.
|
|
7125
|
+
* @param peers - The new and the old items of the projection.
|
|
7126
|
+
*/
|
|
7127
|
+
function startLoop$1(pctx, view, spec, peers) {
|
|
7128
|
+
const loop = spec.motion?.loop;
|
|
7129
|
+
if (loop === void 0) return;
|
|
7130
|
+
runHook(pctx, view, "loop", () => loop(createViewHandle(pctx, view, peers)));
|
|
7131
|
+
}
|
|
7132
|
+
/**
|
|
7133
|
+
* The stored value of one rest component as it stands under the loop of the view: what the
|
|
7134
|
+
* additive tracks of the driver add right now is taken off each numeric field. Where the result
|
|
7135
|
+
* is at rest within `REST_TOLERANCE`, the exact rest value stands in, so the float noise of the
|
|
7136
|
+
* subtraction is no difference. Without a loop on the component, and with a driver that reports
|
|
7137
|
+
* no offsets, it is the stored value itself.
|
|
7138
|
+
*
|
|
7139
|
+
* It is what every comparison with the rest pose reads: the loose components of a settle, the
|
|
7140
|
+
* question of a rollback and the convergence check. So a view that only sways or bobs is home,
|
|
7141
|
+
* and a view something moved is not, in whatever field.
|
|
7142
|
+
*
|
|
7143
|
+
* @param pctx - Domain context of the projection module.
|
|
7144
|
+
* @param view - The view to read.
|
|
7145
|
+
* @param value - The rest value of the component, which names its type.
|
|
7146
|
+
* @returns The value under the loop, or `undefined` for a tag and for a missing component.
|
|
7147
|
+
*/
|
|
7148
|
+
function valueUnderLoop(pctx, view, value) {
|
|
7149
|
+
const stored = storedValue(pctx, view.entity, value.type);
|
|
7150
|
+
if (stored === void 0 || value.value === true) return stored;
|
|
7151
|
+
const rest = value.value;
|
|
7152
|
+
const offsets = loopOffsets(pctx, view.entity, asComponent$1(value.type));
|
|
7153
|
+
let under;
|
|
7154
|
+
for (const [field, offset] of Object.entries(offsets)) {
|
|
7155
|
+
const current = stored[field];
|
|
7156
|
+
if (typeof current !== "number") continue;
|
|
7157
|
+
const below = current - offset;
|
|
7158
|
+
under ??= { ...stored };
|
|
7159
|
+
under[field] = atRest(below, rest[field]) ? rest[field] : below;
|
|
7160
|
+
}
|
|
7161
|
+
return under ?? stored;
|
|
7162
|
+
}
|
|
7163
|
+
/**
|
|
7164
|
+
* The rest components that are off their rest value and that no running track drives. The loop
|
|
7165
|
+
* of a view counts for neither. It drives no component: it adds an offset to the rest pose and
|
|
7166
|
+
* owns nothing. And its offset is no distance from the rest pose: the value under the loop is
|
|
7167
|
+
* what is compared. So a looping view is loose exactly when a view without a loop would be.
|
|
7059
7168
|
*
|
|
7060
7169
|
* @param pctx - Domain context of the projection module.
|
|
7061
7170
|
* @param view - The view to check.
|
|
@@ -7066,8 +7175,8 @@ function looseComponents(pctx, view) {
|
|
|
7066
7175
|
const loose = [];
|
|
7067
7176
|
for (const [name, value] of view.rest) {
|
|
7068
7177
|
if (driven.has(name) || value.value === true) continue;
|
|
7069
|
-
const
|
|
7070
|
-
if (
|
|
7178
|
+
const under = valueUnderLoop(pctx, view, value);
|
|
7179
|
+
if (under !== void 0 && !deepEquals(under, value.value)) loose.push(name);
|
|
7071
7180
|
}
|
|
7072
7181
|
return loose;
|
|
7073
7182
|
}
|
|
@@ -7094,13 +7203,69 @@ function playSettle(pctx, view, spec, peers, components) {
|
|
|
7094
7203
|
}
|
|
7095
7204
|
const played = runHook(pctx, view, "settle", () => custom(handle, components));
|
|
7096
7205
|
if (played === void 0) {
|
|
7097
|
-
|
|
7206
|
+
writeRestUnderLoops(pctx, view, components);
|
|
7098
7207
|
return;
|
|
7099
7208
|
}
|
|
7100
7209
|
if (played.motion !== void 0) view.handles.push(played.motion);
|
|
7101
7210
|
}
|
|
7102
7211
|
/**
|
|
7103
|
-
* Writes
|
|
7212
|
+
* Writes fields of one rest component at once while a loop of the view may run. A field a loop
|
|
7213
|
+
* adds to is written with the offset on top, so the picture does not lose the offset for a
|
|
7214
|
+
* frame. Its new value is first handed to the driver: the driver keeps its own base for such a
|
|
7215
|
+
* field and would write the old one back with its next frame. A track of 0 ms to the rest value
|
|
7216
|
+
* does that, and it is finished at once. So the base is moved before this call returns, whatever
|
|
7217
|
+
* cancels the motions of the view next and also in a world that is not live, and no handle is
|
|
7218
|
+
* left with the view. The shown value is written last: a driver that adds no offset on `finish`
|
|
7219
|
+
* must not have the last word. A field no loop adds to is written as it is, and muted fields are
|
|
7220
|
+
* left out.
|
|
7221
|
+
*
|
|
7222
|
+
* @param pctx - Domain context of the projection module.
|
|
7223
|
+
* @param view - The view to write.
|
|
7224
|
+
* @param value - The rest value of the component, which names its type.
|
|
7225
|
+
* @param patch - The fields to write, with their rest values.
|
|
7226
|
+
*/
|
|
7227
|
+
function writeUnderLoop(pctx, view, value, patch) {
|
|
7228
|
+
const offsets = loopOffsets(pctx, view.entity, asComponent$1(value.type));
|
|
7229
|
+
const shown = {};
|
|
7230
|
+
const based = {};
|
|
7231
|
+
for (const [field, entry] of Object.entries(patch)) {
|
|
7232
|
+
const offset = offsets[field];
|
|
7233
|
+
if (offset === void 0 || typeof entry !== "number") {
|
|
7234
|
+
shown[field] = entry;
|
|
7235
|
+
continue;
|
|
7236
|
+
}
|
|
7237
|
+
shown[field] = entry + offset;
|
|
7238
|
+
based[field] = entry;
|
|
7239
|
+
}
|
|
7240
|
+
if (Object.keys(based).length > 0) startTrack$2(pctx, view.entity, asComponent$1(value.type), based, {
|
|
7241
|
+
ms: 0,
|
|
7242
|
+
ease: "linear",
|
|
7243
|
+
delayMs: 0,
|
|
7244
|
+
additive: false
|
|
7245
|
+
}).finish();
|
|
7246
|
+
writeUnmuted(pctx, view.entity, value, shown);
|
|
7247
|
+
}
|
|
7248
|
+
/**
|
|
7249
|
+
* Writes the rest values of the named components at once where a loop of the view may still
|
|
7250
|
+
* run: in play mode, and when `settle(entity)` writes in a world that is not live. A value under
|
|
7251
|
+
* a loop keeps the offset on top and reaches the driver too, as `writeUnderLoop` says. A direct
|
|
7252
|
+
* reconcile needs none of this and uses `writeRestNow`: its flush ends every loop.
|
|
7253
|
+
*
|
|
7254
|
+
* @param pctx - Domain context of the projection module.
|
|
7255
|
+
* @param view - The view to correct.
|
|
7256
|
+
* @param components - The component names to write.
|
|
7257
|
+
*/
|
|
7258
|
+
function writeRestUnderLoops(pctx, view, components) {
|
|
7259
|
+
for (const name of components) {
|
|
7260
|
+
const value = view.rest.get(name);
|
|
7261
|
+
if (value === void 0 || value.value === true) continue;
|
|
7262
|
+
writeUnderLoop(pctx, view, value, value.value);
|
|
7263
|
+
}
|
|
7264
|
+
}
|
|
7265
|
+
/**
|
|
7266
|
+
* Writes the rest values of the named components at once, leaving out muted fields. It is the
|
|
7267
|
+
* write of a direct reconcile, whose flush ends every loop of the view: a value written under a
|
|
7268
|
+
* loop that runs on goes through `writeRestUnderLoops`.
|
|
7104
7269
|
*
|
|
7105
7270
|
* @param pctx - Domain context of the projection module.
|
|
7106
7271
|
* @param view - The view to correct.
|
|
@@ -7159,23 +7324,54 @@ function atRest(stored, rest) {
|
|
|
7159
7324
|
return deepEquals(stored, rest);
|
|
7160
7325
|
}
|
|
7161
7326
|
/**
|
|
7162
|
-
* The
|
|
7163
|
-
* value
|
|
7164
|
-
*
|
|
7327
|
+
* The fields of one rest component that are off their rest value: muted fields left out, numbers
|
|
7328
|
+
* within `REST_TOLERANCE`. The value under the loop of the view is what is compared, so the
|
|
7329
|
+
* offset of a loop is no miss, and a miss in a field a loop adds to is one. A tag and a component
|
|
7330
|
+
* the entity does not carry answer nothing.
|
|
7331
|
+
*
|
|
7332
|
+
* @param pctx - Domain context of the projection module.
|
|
7333
|
+
* @param view - The view to check.
|
|
7334
|
+
* @param name - Name of the rest component.
|
|
7335
|
+
* @param value - The rest value of that component.
|
|
7336
|
+
* @returns The fields to write to bring the component home, by field name, with their rest
|
|
7337
|
+
* values.
|
|
7338
|
+
*/
|
|
7339
|
+
function offRestFields(pctx, view, name, value) {
|
|
7340
|
+
const patch = {};
|
|
7341
|
+
if (value.value === true) return patch;
|
|
7342
|
+
const under = valueUnderLoop(pctx, view, value);
|
|
7343
|
+
if (under === void 0) return patch;
|
|
7344
|
+
const muted = mutedFields(pctx, view.entity, name);
|
|
7345
|
+
for (const [field, entry] of Object.entries(value.value)) if (!muted.has(field) && !atRest(under[field], entry)) patch[field] = entry;
|
|
7346
|
+
return patch;
|
|
7347
|
+
}
|
|
7348
|
+
/**
|
|
7349
|
+
* Tells whether a view carries a component off its rest value: a field another writer owns does
|
|
7350
|
+
* not count, a number counts as at rest within `REST_TOLERANCE`, and what a loop adds does not
|
|
7351
|
+
* count. A rollback asks it before it settles a view whose item did not change.
|
|
7352
|
+
*
|
|
7353
|
+
* @param pctx - Domain context of the projection module.
|
|
7354
|
+
* @param view - The view to check.
|
|
7355
|
+
* @returns True when at least one field of one rest component is off its rest value.
|
|
7356
|
+
*/
|
|
7357
|
+
function standsOffRest(pctx, view) {
|
|
7358
|
+
for (const [name, value] of view.rest) if (Object.keys(offRestFields(pctx, view, name, value)).length > 0) return true;
|
|
7359
|
+
return false;
|
|
7360
|
+
}
|
|
7361
|
+
/**
|
|
7362
|
+
* The convergence check of the last motion: every rest component is compared with the value
|
|
7363
|
+
* under the loop of the view, muted fields excluded, numbers within `REST_TOLERANCE`. A
|
|
7364
|
+
* difference is written and reported. A field a loop adds to is handed to the driver and written
|
|
7365
|
+
* with the offset on top, so the correction does not jump and is not undone by the loop.
|
|
7165
7366
|
*
|
|
7166
7367
|
* @param pctx - Domain context of the projection module.
|
|
7167
7368
|
* @param view - The view that came to rest.
|
|
7168
7369
|
*/
|
|
7169
7370
|
function checkConvergence(pctx, view) {
|
|
7170
7371
|
for (const [name, value] of view.rest) {
|
|
7171
|
-
|
|
7172
|
-
const stored = storedValue(pctx, view.entity, value.type);
|
|
7173
|
-
if (stored === void 0) continue;
|
|
7174
|
-
const muted = mutedFields(pctx, view.entity, name);
|
|
7175
|
-
const patch = {};
|
|
7176
|
-
for (const [field, entry] of Object.entries(value.value)) if (!muted.has(field) && !atRest(stored[field], entry)) patch[field] = entry;
|
|
7372
|
+
const patch = offRestFields(pctx, view, name, value);
|
|
7177
7373
|
if (Object.keys(patch).length === 0) continue;
|
|
7178
|
-
pctx
|
|
7374
|
+
writeUnderLoop(pctx, view, value, patch);
|
|
7179
7375
|
pctx.ctx.log.warn("world:view-corrected", {
|
|
7180
7376
|
projection: view.projection,
|
|
7181
7377
|
key: view.key,
|
|
@@ -7186,8 +7382,10 @@ function checkConvergence(pctx, view) {
|
|
|
7186
7382
|
//#endregion
|
|
7187
7383
|
//#region src/plugins/world/projection/queue.ts
|
|
7188
7384
|
/**
|
|
7189
|
-
* @file world/projection — the despawn queue: an exited view stays drawn
|
|
7190
|
-
* ends, and every flush empties
|
|
7385
|
+
* @file world/projection — the despawn queue and the frame sweep: an exited view stays drawn
|
|
7386
|
+
* until its last motion ends, and every flush empties the queue in the same call. The sweep and
|
|
7387
|
+
* the flush also apply a pending `lift(false)`, the sweep forgets what is kept of a dead element
|
|
7388
|
+
* and starts the loops the views owe.
|
|
7191
7389
|
*/
|
|
7192
7390
|
/**
|
|
7193
7391
|
* Creates the empty item table of a flushed projection. It lives in its own non-exported function
|
|
@@ -7249,6 +7447,32 @@ function dequeueRevive(pctx, mounted, view, spec) {
|
|
|
7249
7447
|
mounted.live.set(view.key, view);
|
|
7250
7448
|
}
|
|
7251
7449
|
/**
|
|
7450
|
+
* Applies a pending `lift(false)`: the view leaves the lift layer for the layer of its
|
|
7451
|
+
* projection. A view the finger still holds carries no pending drop and stays lifted.
|
|
7452
|
+
*
|
|
7453
|
+
* @param pctx - Domain context of the projection module.
|
|
7454
|
+
* @param view - The view whose last motion ended.
|
|
7455
|
+
*/
|
|
7456
|
+
function dropPending(pctx, view) {
|
|
7457
|
+
if (!view.dropWhenStill) return;
|
|
7458
|
+
const spec = pctx.ctx.state.projection.specs.get(view.projection);
|
|
7459
|
+
if (spec === void 0) return;
|
|
7460
|
+
view.dropWhenStill = false;
|
|
7461
|
+
view.lifted = false;
|
|
7462
|
+
writeLayer(pctx, view.entity, spec.layer);
|
|
7463
|
+
}
|
|
7464
|
+
/**
|
|
7465
|
+
* Applies the pending `lift(false)` of a view no motion moves any more. A play reconcile and
|
|
7466
|
+
* `settle(entity)` call it where they cancelled the motions of a view: with nothing loose no new
|
|
7467
|
+
* motion starts, the view holds no handle, and the sweep skips a view without handles.
|
|
7468
|
+
*
|
|
7469
|
+
* @param pctx - Domain context of the projection module.
|
|
7470
|
+
* @param view - The view whose motions were cancelled.
|
|
7471
|
+
*/
|
|
7472
|
+
function dropIfStill(pctx, view) {
|
|
7473
|
+
if (!stillMoving(view)) dropPending(pctx, view);
|
|
7474
|
+
}
|
|
7475
|
+
/**
|
|
7252
7476
|
* The sweep of phase `animate`: queued views whose motions ended are despawned, and a live view
|
|
7253
7477
|
* that came to rest is checked against its rest pose and gets its pending `lift(false)`.
|
|
7254
7478
|
*
|
|
@@ -7257,24 +7481,63 @@ function dequeueRevive(pctx, mounted, view, spec) {
|
|
|
7257
7481
|
function sweepQueue(pctx) {
|
|
7258
7482
|
const state = pctx.ctx.state.projection;
|
|
7259
7483
|
forgetEndedTracks(pctx);
|
|
7260
|
-
for (const
|
|
7261
|
-
const spec = state.specs.get(name);
|
|
7484
|
+
for (const mounted of state.mounted.values()) {
|
|
7262
7485
|
for (const view of [...mounted.queue]) if (!stillMoving(view)) despawnView(pctx, mounted, view);
|
|
7263
7486
|
for (const view of [...mounted.live.values()]) {
|
|
7264
7487
|
if (view.handles.length === 0 || stillMoving(view)) continue;
|
|
7265
7488
|
view.handles = [];
|
|
7266
7489
|
checkConvergence(pctx, view);
|
|
7267
|
-
|
|
7268
|
-
|
|
7269
|
-
|
|
7270
|
-
|
|
7271
|
-
|
|
7490
|
+
dropPending(pctx, view);
|
|
7491
|
+
}
|
|
7492
|
+
}
|
|
7493
|
+
}
|
|
7494
|
+
/**
|
|
7495
|
+
* Drops what the projection keeps about an element whose entity is gone: the rest pose `setRest`
|
|
7496
|
+
* or `viewOf` recorded, and the key `registerKey` published. An owner that despawns its element
|
|
7497
|
+
* without calling the key remover leaves both, and `entityOf` would go on answering with an
|
|
7498
|
+
* entity that is gone. The sweep calls it once per frame, and the converge step before it
|
|
7499
|
+
* writes. A key a newer element took over stays, and the remover that comes late is still safe.
|
|
7500
|
+
*
|
|
7501
|
+
* @param pctx - Domain context of the projection module.
|
|
7502
|
+
*/
|
|
7503
|
+
function forgetDeadElements(pctx) {
|
|
7504
|
+
const { ecs } = pctx.deps;
|
|
7505
|
+
const state = pctx.ctx.state.projection;
|
|
7506
|
+
for (const entity of state.rests.keys()) if (ecs.ownerOf(entity) === void 0) state.rests.delete(entity);
|
|
7507
|
+
for (const [entity, { projection, key }] of state.keysByEntity) {
|
|
7508
|
+
if (ecs.ownerOf(entity) !== void 0) continue;
|
|
7509
|
+
state.keysByEntity.delete(entity);
|
|
7510
|
+
const byKey = state.keys.get(projection);
|
|
7511
|
+
if (byKey?.get(key) === entity) byKey.delete(key);
|
|
7512
|
+
}
|
|
7513
|
+
}
|
|
7514
|
+
/**
|
|
7515
|
+
* Starts the loop of every live view that owes one and clears the flag: a view that entered in a
|
|
7516
|
+
* direct reconcile, and a view whose tracks a flush ended. The sweep calls it, so it runs only
|
|
7517
|
+
* while the world is live, which also covers a world that comes back from `paused` or `fast`
|
|
7518
|
+
* with no reconcile of its own. Without a tween driver nothing starts and the views keep owing:
|
|
7519
|
+
* the instant driver would write the end pose of the loop once. A view of a projection without
|
|
7520
|
+
* a `loop` hook only has its flag cleared.
|
|
7521
|
+
*
|
|
7522
|
+
* @param pctx - Domain context of the projection module.
|
|
7523
|
+
*/
|
|
7524
|
+
function startOwedLoops(pctx) {
|
|
7525
|
+
const state = pctx.ctx.state.projection;
|
|
7526
|
+
if (state.driver === void 0) return;
|
|
7527
|
+
for (const [name, mounted] of state.mounted) {
|
|
7528
|
+
const spec = state.specs.get(name);
|
|
7529
|
+
for (const view of mounted.live.values()) {
|
|
7530
|
+
if (!view.loopOwed) continue;
|
|
7531
|
+
view.loopOwed = false;
|
|
7532
|
+
if (spec !== void 0) startLoop$1(pctx, view, spec, restingPeers(mounted));
|
|
7272
7533
|
}
|
|
7273
7534
|
}
|
|
7274
7535
|
}
|
|
7275
7536
|
/**
|
|
7276
7537
|
* Flushes one mounted projection: every motion is finished and the despawn queue is emptied in
|
|
7277
|
-
* the same call.
|
|
7538
|
+
* the same call. The flush leaves a live view without a handle, and the sweep skips such a view,
|
|
7539
|
+
* so a pending `lift(false)` is applied here. It also ends the loop of a live view with its
|
|
7540
|
+
* other tracks, so the view owes its loop from here on.
|
|
7278
7541
|
*
|
|
7279
7542
|
* @param pctx - Domain context of the projection module.
|
|
7280
7543
|
* @param mounted - The mounted projection.
|
|
@@ -7283,6 +7546,8 @@ function flushMounted(pctx, mounted) {
|
|
|
7283
7546
|
for (const view of [...mounted.live.values()]) {
|
|
7284
7547
|
finishHandles(view);
|
|
7285
7548
|
cancelTracks(pctx, view.entity);
|
|
7549
|
+
dropPending(pctx, view);
|
|
7550
|
+
view.loopOwed = true;
|
|
7286
7551
|
}
|
|
7287
7552
|
for (const view of [...mounted.queue]) {
|
|
7288
7553
|
finishHandles(view);
|
|
@@ -7410,6 +7675,7 @@ function hintFor$1(pctx, projection, key, used) {
|
|
|
7410
7675
|
* A key that is new: spawn the entity with the view components and the projection's layer, then
|
|
7411
7676
|
* play the `enter` hook and the `loop` hook when the reconcile plays motions. The loop is not
|
|
7412
7677
|
* kept with the view's handles: a change cancels those, and the loop runs until the view dies.
|
|
7678
|
+
* A direct reconcile plays neither hook. The view then owes its loop, which the sweep starts.
|
|
7413
7679
|
*
|
|
7414
7680
|
* @param pctx - Domain context of the projection module.
|
|
7415
7681
|
* @param spec - The projection spec.
|
|
@@ -7432,7 +7698,11 @@ function enterEntry(pctx, spec, mounted, entry) {
|
|
|
7432
7698
|
pctx.ctx.state.projection.byEntity.set(entity, view);
|
|
7433
7699
|
const hook = spec.motion?.enter;
|
|
7434
7700
|
const loop = spec.motion?.loop;
|
|
7435
|
-
if (entry.direct
|
|
7701
|
+
if (entry.direct) {
|
|
7702
|
+
view.loopOwed = true;
|
|
7703
|
+
return;
|
|
7704
|
+
}
|
|
7705
|
+
if (hook === void 0 && loop === void 0) return;
|
|
7436
7706
|
const handle = createViewHandle(pctx, view, entry.peers);
|
|
7437
7707
|
if (hook !== void 0) {
|
|
7438
7708
|
const hint = hintFor$1(pctx, spec.name, entry.key, entry.used);
|
|
@@ -7443,22 +7713,37 @@ function enterEntry(pctx, spec, mounted, entry) {
|
|
|
7443
7713
|
playSettle(pctx, view, spec, entry.peers, looseComponents(pctx, view));
|
|
7444
7714
|
}
|
|
7445
7715
|
/**
|
|
7446
|
-
* A key whose item
|
|
7447
|
-
* handed to it and one without is written directly, then the retarget
|
|
7716
|
+
* A key whose item is another object than before: structural changes are written, a changed
|
|
7717
|
+
* component with a hook is handed to it and one without is written directly, then the retarget
|
|
7718
|
+
* settles what is loose. A view no motion moves after that gets its pending `lift(false)`.
|
|
7719
|
+
*
|
|
7720
|
+
* In play mode an item with the same content is no change: when the component diff is empty the
|
|
7721
|
+
* new item is stored and nothing else happens. No motion of the view is cancelled, no hook runs,
|
|
7722
|
+
* nothing is written, no loose component is settled and no hint is used. A `from` that builds a
|
|
7723
|
+
* new object on every commit so keeps the motions of its views. A direct reconcile, and a forced
|
|
7724
|
+
* one, project the item as before.
|
|
7448
7725
|
*
|
|
7449
7726
|
* @param pctx - Domain context of the projection module.
|
|
7450
7727
|
* @param spec - The projection spec.
|
|
7451
7728
|
* @param view - The live view.
|
|
7452
|
-
* @param entry - The item, the peers, the mode
|
|
7729
|
+
* @param entry - The item, the peers, the mode, the force flag, the used hints and the list a
|
|
7730
|
+
* direct reconcile fills.
|
|
7453
7731
|
* @param entry.item - The new model item.
|
|
7454
7732
|
* @param entry.peers - The new and the old items.
|
|
7455
7733
|
* @param entry.direct - Whether the reconcile writes without motions.
|
|
7734
|
+
* @param entry.force - Whether an unchanged item is projected again.
|
|
7456
7735
|
* @param entry.used - The hints that reached an entry.
|
|
7736
|
+
* @param entry.rewritten - The views a direct reconcile wrote, written again after its flush.
|
|
7737
|
+
* @returns False when the item has the same content as before and was only stored.
|
|
7457
7738
|
*/
|
|
7458
7739
|
function changeEntry(pctx, spec, view, entry) {
|
|
7459
7740
|
const next = collectView(pctx, spec, entry.item);
|
|
7460
7741
|
const { added, changed, removed } = diffComponents(view.rest, next, treeName(pctx));
|
|
7461
7742
|
const previous = view.item;
|
|
7743
|
+
if (!entry.direct && !entry.force && added.length + changed.length + removed.length === 0) {
|
|
7744
|
+
view.item = entry.item;
|
|
7745
|
+
return false;
|
|
7746
|
+
}
|
|
7462
7747
|
for (const value of valuesOf(next, added)) writeValue(pctx, view.entity, value);
|
|
7463
7748
|
for (const value of valuesOf(view.rest, removed)) pctx.deps.ecs.remove(view.entity, value.type);
|
|
7464
7749
|
view.rest = next;
|
|
@@ -7466,7 +7751,8 @@ function changeEntry(pctx, spec, view, entry) {
|
|
|
7466
7751
|
if (entry.direct) {
|
|
7467
7752
|
finishHandles(view);
|
|
7468
7753
|
writeRestNow(pctx, view, [...next.keys()]);
|
|
7469
|
-
|
|
7754
|
+
entry.rewritten.push(view);
|
|
7755
|
+
return true;
|
|
7470
7756
|
}
|
|
7471
7757
|
cancelHandles(view);
|
|
7472
7758
|
const handle = createViewHandle(pctx, view, entry.peers);
|
|
@@ -7482,21 +7768,25 @@ function changeEntry(pctx, spec, view, entry) {
|
|
|
7482
7768
|
if (played === void 0) written.push(name);
|
|
7483
7769
|
else if (played.motion !== void 0) view.handles.push(played.motion);
|
|
7484
7770
|
}
|
|
7485
|
-
|
|
7771
|
+
writeRestUnderLoops(pctx, view, written);
|
|
7486
7772
|
playSettle(pctx, view, spec, entry.peers, looseComponents(pctx, view));
|
|
7773
|
+
dropIfStill(pctx, view);
|
|
7774
|
+
return true;
|
|
7487
7775
|
}
|
|
7488
7776
|
/**
|
|
7489
7777
|
* A key that came back while its exit still played: the SAME view is revived, no `enter` and no
|
|
7490
|
-
* `change` hook runs, and one settle over the loose components brings it home.
|
|
7778
|
+
* `change` hook runs, and one settle over the loose components brings it home. A view no motion
|
|
7779
|
+
* moves after that gets its pending `lift(false)`.
|
|
7491
7780
|
*
|
|
7492
7781
|
* @param pctx - Domain context of the projection module.
|
|
7493
7782
|
* @param spec - The projection spec.
|
|
7494
7783
|
* @param mounted - The mounted projection.
|
|
7495
7784
|
* @param view - The queued view.
|
|
7496
|
-
* @param entry - The item, the peers and the
|
|
7785
|
+
* @param entry - The item, the peers, the mode and the list a direct reconcile fills.
|
|
7497
7786
|
* @param entry.item - The new model item.
|
|
7498
7787
|
* @param entry.peers - The new and the old items.
|
|
7499
7788
|
* @param entry.direct - Whether the reconcile writes without motions.
|
|
7789
|
+
* @param entry.rewritten - The views a direct reconcile wrote, written again after its flush.
|
|
7500
7790
|
*/
|
|
7501
7791
|
function reviveEntry(pctx, spec, mounted, view, entry) {
|
|
7502
7792
|
cancelHandles(view);
|
|
@@ -7509,9 +7799,11 @@ function reviveEntry(pctx, spec, mounted, view, entry) {
|
|
|
7509
7799
|
view.item = entry.item;
|
|
7510
7800
|
if (entry.direct) {
|
|
7511
7801
|
writeRestNow(pctx, view, [...next.keys()]);
|
|
7802
|
+
entry.rewritten.push(view);
|
|
7512
7803
|
return;
|
|
7513
7804
|
}
|
|
7514
7805
|
playSettle(pctx, view, spec, entry.peers, looseComponents(pctx, view));
|
|
7806
|
+
dropIfStill(pctx, view);
|
|
7515
7807
|
}
|
|
7516
7808
|
/**
|
|
7517
7809
|
* A live key that is gone: despawned at once without an `exit` hook, otherwise moved into the
|
|
@@ -7542,27 +7834,57 @@ function exitEntry(pctx, spec, mounted, view, entry) {
|
|
|
7542
7834
|
if (!stillMoving(view)) despawnView(pctx, mounted, view);
|
|
7543
7835
|
}
|
|
7544
7836
|
/**
|
|
7837
|
+
* A live key whose item did not change, on a rollback: the same object, or in play mode a new
|
|
7838
|
+
* one with the same content. The failed node committed nothing, so no entry brings back a view
|
|
7839
|
+
* it moved with a motion or a timeline. A view that stands off its rest pose is settled as
|
|
7840
|
+
* `projection.settle(entity)` does: its motions are cancelled and the settle is played for the
|
|
7841
|
+
* loose components. With nothing loose a pending `lift(false)` takes effect. A view that stands
|
|
7842
|
+
* on its rest pose is left alone, so its motions run on. That holds for a view that loops too:
|
|
7843
|
+
* the value under its loop is what is compared. A direct reconcile only remembers the view: its
|
|
7844
|
+
* rest pose is written after the flush, in `writeUnchanged`.
|
|
7845
|
+
*
|
|
7846
|
+
* @param pctx - Domain context of the projection module.
|
|
7847
|
+
* @param spec - The projection spec.
|
|
7848
|
+
* @param view - The live view whose item is the same as before.
|
|
7849
|
+
* @param entry - The peers, the mode and the list a direct reconcile fills.
|
|
7850
|
+
* @param entry.peers - The new and the old items.
|
|
7851
|
+
* @param entry.direct - Whether the reconcile writes without motions.
|
|
7852
|
+
* @param entry.unchanged - The unchanged views of a direct reconcile, written after its flush.
|
|
7853
|
+
*/
|
|
7854
|
+
function settleUnchanged(pctx, spec, view, entry) {
|
|
7855
|
+
if (entry.direct) {
|
|
7856
|
+
entry.unchanged.push(view);
|
|
7857
|
+
return;
|
|
7858
|
+
}
|
|
7859
|
+
if (!standsOffRest(pctx, view)) return;
|
|
7860
|
+
cancelHandles(view);
|
|
7861
|
+
playSettle(pctx, view, spec, entry.peers, looseComponents(pctx, view));
|
|
7862
|
+
dropIfStill(pctx, view);
|
|
7863
|
+
}
|
|
7864
|
+
/**
|
|
7545
7865
|
* Routes one key of the new state to the entry it belongs to: a live view changes, a queued view
|
|
7546
7866
|
* revives, an unknown key enters.
|
|
7547
7867
|
*
|
|
7548
7868
|
* @param pctx - Domain context of the projection module.
|
|
7549
7869
|
* @param spec - The projection spec.
|
|
7550
7870
|
* @param mounted - The mounted projection.
|
|
7551
|
-
* @param entry - The key, its item, the peers, the mode, the
|
|
7871
|
+
* @param entry - The key, its item, the peers, the mode, the flags and the used hints.
|
|
7552
7872
|
* @param entry.key - The model key.
|
|
7553
7873
|
* @param entry.item - The model item.
|
|
7554
7874
|
* @param entry.peers - The new and the old items.
|
|
7555
7875
|
* @param entry.direct - Whether the reconcile writes without motions.
|
|
7556
7876
|
* @param entry.force - Whether an unchanged item is projected again.
|
|
7877
|
+
* @param entry.settle - Whether an unchanged view that stands off its rest pose is settled.
|
|
7557
7878
|
* @param entry.used - The hints that reached an entry.
|
|
7879
|
+
* @param entry.unchanged - The unchanged views of a direct reconcile that settles.
|
|
7880
|
+
* @param entry.rewritten - The views a direct reconcile wrote, written again after its flush.
|
|
7558
7881
|
* @param counts - The counters of this reconcile.
|
|
7559
7882
|
*/
|
|
7560
7883
|
function applyEntry(pctx, spec, mounted, entry, counts) {
|
|
7561
7884
|
const live = mounted.live.get(entry.key);
|
|
7562
7885
|
if (live !== void 0) {
|
|
7563
|
-
if (!entry.force && live.item === entry.item)
|
|
7564
|
-
|
|
7565
|
-
counts.changed += 1;
|
|
7886
|
+
if (!(!entry.force && live.item === entry.item) && changeEntry(pctx, spec, live, entry)) counts.changed += 1;
|
|
7887
|
+
else if (entry.settle) settleUnchanged(pctx, spec, live, entry);
|
|
7566
7888
|
return;
|
|
7567
7889
|
}
|
|
7568
7890
|
const queued = mounted.queue.find((view) => view.key === entry.key);
|
|
@@ -7645,20 +7967,47 @@ function convergeMounted(pctx, mounted) {
|
|
|
7645
7967
|
for (const view of mounted.live.values()) writeRestNow(pctx, view, [...view.rest.keys()]);
|
|
7646
7968
|
}
|
|
7647
7969
|
/**
|
|
7970
|
+
* The last step of a direct reconcile that does not converge: every view an entry wrote is
|
|
7971
|
+
* written to its rest pose once more. The flush cancels the loop of a view with its other
|
|
7972
|
+
* tracks, and the driver writes the base it kept for the fields the loop added to back: the old
|
|
7973
|
+
* rest value. So the write of the entry, which came before the flush, would be lost in those
|
|
7974
|
+
* fields. Muted fields are left out.
|
|
7975
|
+
*
|
|
7976
|
+
* @param pctx - Domain context of the projection module.
|
|
7977
|
+
* @param rewritten - The live views a changed or revived entry wrote in this reconcile.
|
|
7978
|
+
*/
|
|
7979
|
+
function writeAgain(pctx, rewritten) {
|
|
7980
|
+
for (const view of rewritten) writeRestNow(pctx, view, [...view.rest.keys()]);
|
|
7981
|
+
}
|
|
7982
|
+
/**
|
|
7983
|
+
* The last step of a direct reconcile a rollback caused: an unchanged view that stands off its
|
|
7984
|
+
* rest pose is written to it. It runs after the flush, as `convergeMounted` does and for the same
|
|
7985
|
+
* reason: the flush finishes the motions of the view, and a cancelled additive track writes its
|
|
7986
|
+
* base back, so the rest write has to come last. Muted fields are left out.
|
|
7987
|
+
*
|
|
7988
|
+
* @param pctx - Domain context of the projection module.
|
|
7989
|
+
* @param unchanged - The live views whose item did not change in this reconcile.
|
|
7990
|
+
*/
|
|
7991
|
+
function writeUnchanged(pctx, unchanged) {
|
|
7992
|
+
for (const view of unchanged) if (standsOffRest(pctx, view)) writeRestNow(pctx, view, [...view.rest.keys()]);
|
|
7993
|
+
}
|
|
7994
|
+
/**
|
|
7648
7995
|
* The same last step for the elements another plugin owns, which no reconcile of a projection
|
|
7649
7996
|
* meets: each is written to the rest pose its owner recorded with `setRest`. Every track on the
|
|
7650
7997
|
* entity is ended first, so nothing writes after it. Only a component the entity still carries
|
|
7651
7998
|
* is written and muted fields are left out. An element that is leaving has its tracks ended and
|
|
7652
|
-
* keeps its place
|
|
7653
|
-
*
|
|
7999
|
+
* keeps its place. The recorded pose and the registered key of an entity that is gone are
|
|
8000
|
+
* dropped before anything is written. A pose recorded for a projection view is skipped: its own
|
|
8001
|
+
* rest pose wins, as in `restOf`, and it is already home.
|
|
7654
8002
|
*
|
|
7655
8003
|
* @param pctx - Domain context of the projection module.
|
|
7656
8004
|
*/
|
|
7657
8005
|
function convergeElements(pctx) {
|
|
7658
8006
|
const { ecs, components } = pctx.deps;
|
|
7659
8007
|
const state = pctx.ctx.state.projection;
|
|
8008
|
+
forgetDeadElements(pctx);
|
|
7660
8009
|
for (const [entity, rest] of state.rests) {
|
|
7661
|
-
if (
|
|
8010
|
+
if (state.byEntity.has(entity)) continue;
|
|
7662
8011
|
cancelTracks(pctx, entity);
|
|
7663
8012
|
if (ecs.has(entity, components.Exiting)) continue;
|
|
7664
8013
|
const carried = [];
|
|
@@ -7687,20 +8036,26 @@ function tellConverged(pctx) {
|
|
|
7687
8036
|
* @param spec - The projection spec.
|
|
7688
8037
|
* @param mounted - The mounted projection.
|
|
7689
8038
|
* @param snapshot - The model snapshot of this reconcile.
|
|
7690
|
-
* @param options - Mode, force flag and
|
|
8039
|
+
* @param options - Mode, force flag, converge flag and settle flag of this reconcile.
|
|
7691
8040
|
* @param counts - The counters of this reconcile.
|
|
7692
8041
|
* @param used - The hints that reached an entry.
|
|
7693
8042
|
*/
|
|
7694
8043
|
function reconcileOne(pctx, spec, mounted, snapshot, options, counts, used) {
|
|
7695
8044
|
const next = keyedItems(pctx, spec, snapshot);
|
|
7696
8045
|
if (next === void 0) return;
|
|
8046
|
+
const peers = {
|
|
8047
|
+
next,
|
|
8048
|
+
previous: mounted.items
|
|
8049
|
+
};
|
|
8050
|
+
const unchanged = [];
|
|
8051
|
+
const rewritten = [];
|
|
7697
8052
|
const entry = {
|
|
7698
|
-
peers
|
|
7699
|
-
next,
|
|
7700
|
-
previous: mounted.items
|
|
7701
|
-
},
|
|
8053
|
+
peers,
|
|
7702
8054
|
direct: options.direct,
|
|
7703
|
-
|
|
8055
|
+
settle: options.settle,
|
|
8056
|
+
used,
|
|
8057
|
+
unchanged,
|
|
8058
|
+
rewritten
|
|
7704
8059
|
};
|
|
7705
8060
|
try {
|
|
7706
8061
|
for (const [key, item] of next) applyEntry(pctx, spec, mounted, {
|
|
@@ -7717,6 +8072,10 @@ function reconcileOne(pctx, spec, mounted, snapshot, options, counts, used) {
|
|
|
7717
8072
|
mounted.items = next;
|
|
7718
8073
|
if (options.direct) flushMounted(pctx, mounted);
|
|
7719
8074
|
if (options.converge) convergeMounted(pctx, mounted);
|
|
8075
|
+
else {
|
|
8076
|
+
writeAgain(pctx, rewritten);
|
|
8077
|
+
writeUnchanged(pctx, unchanged);
|
|
8078
|
+
}
|
|
7720
8079
|
counts.queued += mounted.queue.length;
|
|
7721
8080
|
}
|
|
7722
8081
|
/**
|
|
@@ -7724,8 +8083,9 @@ function reconcileOne(pctx, spec, mounted, snapshot, options, counts, used) {
|
|
|
7724
8083
|
* one then brings the elements other plugins own home and tells their owners.
|
|
7725
8084
|
*
|
|
7726
8085
|
* @param pctx - Domain context of the projection module.
|
|
7727
|
-
* @param options - Which projections, play or direct, whether `view` is forced,
|
|
7728
|
-
*
|
|
8086
|
+
* @param options - Which projections, play or direct, whether `view` is forced, whether every
|
|
8087
|
+
* live view and every element is brought to its rest pose at the end, and whether an unchanged
|
|
8088
|
+
* view that stands off its rest pose is settled.
|
|
7729
8089
|
*/
|
|
7730
8090
|
function reconcile(pctx, options) {
|
|
7731
8091
|
const state = pctx.ctx.state.projection;
|
|
@@ -7750,18 +8110,6 @@ function reconcile(pctx, options) {
|
|
|
7750
8110
|
...counts
|
|
7751
8111
|
});
|
|
7752
8112
|
}
|
|
7753
|
-
/**
|
|
7754
|
-
* Re-projects one view with the same item, used by `lift` and `settle` to reach the peers.
|
|
7755
|
-
*
|
|
7756
|
-
* @param mounted - The mounted projection.
|
|
7757
|
-
* @returns The peers of a call outside a reconcile: the items of the last one.
|
|
7758
|
-
*/
|
|
7759
|
-
function restingPeers(mounted) {
|
|
7760
|
-
return {
|
|
7761
|
-
next: mounted.items,
|
|
7762
|
-
previous: mounted.items
|
|
7763
|
-
};
|
|
7764
|
-
}
|
|
7765
8113
|
//#endregion
|
|
7766
8114
|
//#region src/plugins/world/projection/api.ts
|
|
7767
8115
|
/**
|
|
@@ -7925,8 +8273,9 @@ function createProjectionApi(ctx, deps) {
|
|
|
7925
8273
|
};
|
|
7926
8274
|
const state = ctx.state.projection;
|
|
7927
8275
|
/**
|
|
7928
|
-
* Reconciles what is dirty. The cause table decides play or direct,
|
|
7929
|
-
* and every element with a recorded rest pose is brought to it
|
|
8276
|
+
* Reconciles what is dirty. The cause table decides play or direct, whether every live view
|
|
8277
|
+
* and every element with a recorded rest pose is brought to it (a restore and a load do that),
|
|
8278
|
+
* and whether an unchanged view that stands off its rest pose is settled (a rollback does).
|
|
7930
8279
|
*/
|
|
7931
8280
|
const reconcileIfDirty = () => {
|
|
7932
8281
|
const dirty = state.dirty;
|
|
@@ -7934,10 +8283,12 @@ function createProjectionApi(ctx, deps) {
|
|
|
7934
8283
|
if (dirty !== void 0 && (dirty.roots.has("player") || dirty.roots.has("session"))) {
|
|
7935
8284
|
const direct = dirty.force || dirty.causes.some((cause) => isDirectCause(cause)) || deps.ecs.mode() !== "live";
|
|
7936
8285
|
const converge = dirty.causes.some((cause) => isConvergeCause(cause));
|
|
8286
|
+
const settle = dirty.causes.includes("rollback");
|
|
7937
8287
|
reconcile(pctx, {
|
|
7938
8288
|
direct,
|
|
7939
8289
|
force: dirty.force,
|
|
7940
|
-
converge
|
|
8290
|
+
converge,
|
|
8291
|
+
settle
|
|
7941
8292
|
});
|
|
7942
8293
|
}
|
|
7943
8294
|
state.hints.length = 0;
|
|
@@ -7963,6 +8314,7 @@ function createProjectionApi(ctx, deps) {
|
|
|
7963
8314
|
handles: [],
|
|
7964
8315
|
lifted: false,
|
|
7965
8316
|
dropWhenStill: false,
|
|
8317
|
+
loopOwed: false,
|
|
7966
8318
|
exiting: false
|
|
7967
8319
|
});
|
|
7968
8320
|
return {
|
|
@@ -7998,7 +8350,8 @@ function createProjectionApi(ctx, deps) {
|
|
|
7998
8350
|
names: fresh.map((spec) => spec.name),
|
|
7999
8351
|
direct: true,
|
|
8000
8352
|
force: true,
|
|
8001
|
-
converge: false
|
|
8353
|
+
converge: false,
|
|
8354
|
+
settle: false
|
|
8002
8355
|
});
|
|
8003
8356
|
},
|
|
8004
8357
|
unmount: (names) => {
|
|
@@ -8022,15 +8375,12 @@ function createProjectionApi(ctx, deps) {
|
|
|
8022
8375
|
cancelHandles(view);
|
|
8023
8376
|
forgetEndedTracks(pctx);
|
|
8024
8377
|
if (deps.ecs.mode() !== "live") {
|
|
8025
|
-
|
|
8026
|
-
|
|
8027
|
-
view.dropWhenStill = false;
|
|
8028
|
-
view.lifted = false;
|
|
8029
|
-
writeLayer(pctx, entity, spec.layer);
|
|
8030
|
-
}
|
|
8378
|
+
writeRestUnderLoops(pctx, view, [...view.rest.keys()]);
|
|
8379
|
+
dropPending(pctx, view);
|
|
8031
8380
|
return;
|
|
8032
8381
|
}
|
|
8033
8382
|
playSettle(pctx, view, spec, restingPeers(mounted), looseComponents(pctx, view));
|
|
8383
|
+
dropIfStill(pctx, view);
|
|
8034
8384
|
},
|
|
8035
8385
|
mute: (entity, component, fields) => {
|
|
8036
8386
|
const byComponent = state.mutes.get(entity) ?? emptyMuteTable();
|
|
@@ -8137,6 +8487,8 @@ function createProjectionApi(ctx, deps) {
|
|
|
8137
8487
|
sweep: () => {
|
|
8138
8488
|
if (deps.ecs.mode() !== "live") return;
|
|
8139
8489
|
sweepQueue(pctx);
|
|
8490
|
+
forgetDeadElements(pctx);
|
|
8491
|
+
startOwedLoops(pctx);
|
|
8140
8492
|
},
|
|
8141
8493
|
flushAll: () => {
|
|
8142
8494
|
for (const view of state.byEntity.values()) {
|
|
@@ -14354,6 +14706,52 @@ function sumOffsets(actx, key) {
|
|
|
14354
14706
|
return sum;
|
|
14355
14707
|
}
|
|
14356
14708
|
/**
|
|
14709
|
+
* What the running additive tracks of one component of one entity add to each of its fields
|
|
14710
|
+
* right now: the offset of a loop. The `TweenDriver` seam hands it to `world`, which takes it off
|
|
14711
|
+
* the stored value before it compares a view with its rest pose. A field no additive track adds
|
|
14712
|
+
* to is left out.
|
|
14713
|
+
*
|
|
14714
|
+
* A field another writer owns keeps the offset of the last write of its track. That is what
|
|
14715
|
+
* stands in the field while the writer only holds it, and what the track adds again when the
|
|
14716
|
+
* field is given back. So a view the hand held still reads as home, and a view the hand put
|
|
14717
|
+
* somewhere reads as moved.
|
|
14718
|
+
*
|
|
14719
|
+
* @param actx - Domain context of the anim plugin.
|
|
14720
|
+
* @param entity - The entity to ask about.
|
|
14721
|
+
* @param component - The component to ask about.
|
|
14722
|
+
* @returns Field name to the sum of the offsets on it.
|
|
14723
|
+
*/
|
|
14724
|
+
function addedOffsets(actx, entity, component) {
|
|
14725
|
+
const sums = {};
|
|
14726
|
+
for (const track of actx.state.additive.get(entity) ?? []) {
|
|
14727
|
+
if (track.component.componentName !== component.componentName) continue;
|
|
14728
|
+
for (const field of Object.keys(track.to)) sums[field] = sumOffsets(actx, keyOf$1(track, field));
|
|
14729
|
+
}
|
|
14730
|
+
return sums;
|
|
14731
|
+
}
|
|
14732
|
+
/**
|
|
14733
|
+
* Files a new additive track under its entity, so `addedOffsets` finds it without a walk of the
|
|
14734
|
+
* whole track table.
|
|
14735
|
+
*
|
|
14736
|
+
* @param actx - Domain context of the anim plugin.
|
|
14737
|
+
* @param track - The additive track that starts.
|
|
14738
|
+
*/
|
|
14739
|
+
function fileAdditive(actx, track) {
|
|
14740
|
+
const tracks = actx.state.additive.get(track.entity) ?? /* @__PURE__ */ new Set();
|
|
14741
|
+
tracks.add(track);
|
|
14742
|
+
actx.state.additive.set(track.entity, tracks);
|
|
14743
|
+
}
|
|
14744
|
+
/**
|
|
14745
|
+
* Takes an ended additive track out of the table of its entity, and the entity with its last one.
|
|
14746
|
+
*
|
|
14747
|
+
* @param actx - Domain context of the anim plugin.
|
|
14748
|
+
* @param track - The track that ended.
|
|
14749
|
+
*/
|
|
14750
|
+
function unfileAdditive(actx, track) {
|
|
14751
|
+
const tracks = actx.state.additive.get(track.entity);
|
|
14752
|
+
if (tracks?.delete(track) === true && tracks.size === 0) actx.state.additive.delete(track.entity);
|
|
14753
|
+
}
|
|
14754
|
+
/**
|
|
14357
14755
|
* Reads the start values of a track, once, when its delay has ended. The base of a field another
|
|
14358
14756
|
* track already drives is used, so an additive offset is never folded into a start value.
|
|
14359
14757
|
*
|
|
@@ -14424,6 +14822,53 @@ function fieldValue$1(track, field, start, target, fraction) {
|
|
|
14424
14822
|
return walkValue(track.segments, track.ease, field, start, fraction);
|
|
14425
14823
|
}
|
|
14426
14824
|
/**
|
|
14825
|
+
* Tells whether another writer owns a field at this write, so the track leaves it alone. An
|
|
14826
|
+
* additive track remembers that it skipped the field: it has to find the value under it again
|
|
14827
|
+
* once the field is given back.
|
|
14828
|
+
*
|
|
14829
|
+
* @param track - The track that is about to write.
|
|
14830
|
+
* @param owned - The fields another writer owns now.
|
|
14831
|
+
* @param field - The field.
|
|
14832
|
+
* @returns True when the track must not write the field.
|
|
14833
|
+
*/
|
|
14834
|
+
function skipsOwned(track, owned, field) {
|
|
14835
|
+
if (!owned.has(field)) return false;
|
|
14836
|
+
if (track.additive) track.masked.add(field);
|
|
14837
|
+
return true;
|
|
14838
|
+
}
|
|
14839
|
+
/**
|
|
14840
|
+
* How far a field may sit from the value the table last wrote into it and still count as
|
|
14841
|
+
* untouched by another writer. The same size as the rest tolerance of `world`.
|
|
14842
|
+
*/
|
|
14843
|
+
const UNTOUCHED = 1e-6;
|
|
14844
|
+
/**
|
|
14845
|
+
* Books what an additive track adds to one field in this frame. On its first write after another
|
|
14846
|
+
* writer gave the field back it also moves the base of the field, when that writer moved the
|
|
14847
|
+
* field. While the field was owned, the track did not write it and the other writer put it where
|
|
14848
|
+
* it wanted, so the base the table kept is stale: a hand dragged a bobbing view away. The value
|
|
14849
|
+
* that stands in the field becomes what the base and the offsets add up to. So this write leaves
|
|
14850
|
+
* the field where it is, the track runs on from there, and a track that starts now to bring the
|
|
14851
|
+
* field home reads that value as its start. Without it the field would jump back to the old base
|
|
14852
|
+
* in one frame.
|
|
14853
|
+
*
|
|
14854
|
+
* A writer that only held the field left in it what the table last wrote: the base plus the
|
|
14855
|
+
* offsets as they stood. Then nothing moved the value under the track, and the base stays.
|
|
14856
|
+
*
|
|
14857
|
+
* @param actx - Domain context of the anim plugin.
|
|
14858
|
+
* @param track - The additive track.
|
|
14859
|
+
* @param field - The field.
|
|
14860
|
+
* @param delta - What the track adds to the field now.
|
|
14861
|
+
* @param left - The value that stands in the field before this write.
|
|
14862
|
+
*/
|
|
14863
|
+
function addOffset(actx, track, field, delta, left) {
|
|
14864
|
+
const key = keyOf$1(track, field);
|
|
14865
|
+
const written = (actx.state.bases.get(key) ?? 0) + sumOffsets(actx, key);
|
|
14866
|
+
offsetsOf(actx, key).set(track.id, delta);
|
|
14867
|
+
if (!track.masked.delete(field) || typeof left !== "number") return;
|
|
14868
|
+
if (Math.abs(left - written) <= UNTOUCHED) return;
|
|
14869
|
+
actx.state.bases.set(key, left - sumOffsets(actx, key));
|
|
14870
|
+
}
|
|
14871
|
+
/**
|
|
14427
14872
|
* Writes one frame of a track: the eased value for an absolute track, the contributed delta for
|
|
14428
14873
|
* an additive one, and in both cases the base plus every offset of the field. A component the
|
|
14429
14874
|
* entity no longer carries ends the track silently.
|
|
@@ -14432,6 +14877,9 @@ function fieldValue$1(track, field, start, target, fraction) {
|
|
|
14432
14877
|
* delta it adds over the base, so a loop keeps its shape whatever pose it started on. A straight
|
|
14433
14878
|
* additive track still adds the way from its start value to its target.
|
|
14434
14879
|
*
|
|
14880
|
+
* A field another writer owns is left alone. An additive track remembers that it skipped the
|
|
14881
|
+
* field, and adds to the value it finds there once the field is given back.
|
|
14882
|
+
*
|
|
14435
14883
|
* @param actx - Domain context of the anim plugin.
|
|
14436
14884
|
* @param track - The track to write.
|
|
14437
14885
|
* @param fraction - Normalised time of the track.
|
|
@@ -14449,12 +14897,12 @@ function writeFields(actx, track, fraction, final) {
|
|
|
14449
14897
|
const patch = {};
|
|
14450
14898
|
const walksOffsets = track.additive && track.segments !== void 0;
|
|
14451
14899
|
for (const [field, target] of Object.entries(track.to)) {
|
|
14452
|
-
if (owned
|
|
14900
|
+
if (skipsOwned(track, owned, field)) continue;
|
|
14453
14901
|
const key = keyOf$1(track, field);
|
|
14454
14902
|
const start = from[field] ?? target;
|
|
14455
14903
|
const origin = walksOffsets ? 0 : start;
|
|
14456
14904
|
const value = final ? target : fieldValue$1(track, field, origin, target, fraction);
|
|
14457
|
-
if (track.additive)
|
|
14905
|
+
if (track.additive) addOffset(actx, track, field, value - origin, stored[field]);
|
|
14458
14906
|
else actx.state.bases.set(key, value);
|
|
14459
14907
|
patch[field] = (actx.state.bases.get(key) ?? start) + sumOffsets(actx, key);
|
|
14460
14908
|
}
|
|
@@ -14496,6 +14944,7 @@ function endTrack(actx, track) {
|
|
|
14496
14944
|
if (track.ended) return;
|
|
14497
14945
|
track.ended = true;
|
|
14498
14946
|
actx.state.tracks.delete(track.id);
|
|
14947
|
+
if (track.additive) unfileAdditive(actx, track);
|
|
14499
14948
|
const clean = releaseFields(actx, track);
|
|
14500
14949
|
const ecs = actx.deps.world.ecs;
|
|
14501
14950
|
if (Object.keys(clean).length > 0 && ecs.get(track.entity, track.component) !== void 0) ecs.set(track.entity, track.component, clean);
|
|
@@ -14563,6 +15012,15 @@ function beginFrame(actx) {
|
|
|
14563
15012
|
actx.state.frame += 1;
|
|
14564
15013
|
}
|
|
14565
15014
|
/**
|
|
15015
|
+
* The empty set of the fields a new track skipped. It lives in its own function because lint
|
|
15016
|
+
* rule L5 refuses a collection built inside an exported declaration.
|
|
15017
|
+
*
|
|
15018
|
+
* @returns An empty set of field names.
|
|
15019
|
+
*/
|
|
15020
|
+
function noMaskedFields() {
|
|
15021
|
+
return /* @__PURE__ */ new Set();
|
|
15022
|
+
}
|
|
15023
|
+
/**
|
|
14566
15024
|
* The target of a new track: the fields the caller passed, plus every field a keyframe segment
|
|
14567
15025
|
* names at the last value a segment gives it, so `claimFields` books the whole walk at once.
|
|
14568
15026
|
*
|
|
@@ -14630,11 +15088,13 @@ function startTrack$1(actx, entity, component, to, options, muted, driven = fals
|
|
|
14630
15088
|
ended: Object.keys(target).length === 0,
|
|
14631
15089
|
segments: options.segments,
|
|
14632
15090
|
repeatsLeft: instant ? 0 : repeats,
|
|
14633
|
-
held: false
|
|
15091
|
+
held: false,
|
|
15092
|
+
masked: noMaskedFields()
|
|
14634
15093
|
};
|
|
14635
15094
|
actx.state.nextId += 1;
|
|
14636
15095
|
if (track.ended) return track;
|
|
14637
|
-
if (
|
|
15096
|
+
if (track.additive) fileAdditive(actx, track);
|
|
15097
|
+
else claimFields(actx, track);
|
|
14638
15098
|
actx.state.tracks.set(track.id, track);
|
|
14639
15099
|
countPlaying(actx.deps.world.ecs, entity, 1);
|
|
14640
15100
|
actx.deps.time.wake();
|
|
@@ -14763,12 +15223,19 @@ function advanceTracks(actx, deltaMs) {
|
|
|
14763
15223
|
for (const track of open) if (!track.additive) advanceTrack(actx, track, deltaMs);
|
|
14764
15224
|
}
|
|
14765
15225
|
/**
|
|
14766
|
-
* Finishes every track of the table, in table order.
|
|
15226
|
+
* Finishes every track of the table, in table order. With `keepLoops` a loop is left alone: it
|
|
15227
|
+
* has no end to finish on, so it stays in the table and stands where it is. The frame step of a
|
|
15228
|
+
* fast world asks for that, because nothing would start the loop again: a fast walk of the flow
|
|
15229
|
+
* that commits nothing flushes no view, so no view would owe its loop.
|
|
14767
15230
|
*
|
|
14768
15231
|
* @param actx - Domain context of the anim plugin.
|
|
15232
|
+
* @param keepLoops - True to leave every track that repeats forever running.
|
|
14769
15233
|
*/
|
|
14770
|
-
function finishAllTracks(actx) {
|
|
14771
|
-
for (const track of [...actx.state.tracks.values()])
|
|
15234
|
+
function finishAllTracks(actx, keepLoops = false) {
|
|
15235
|
+
for (const track of [...actx.state.tracks.values()]) {
|
|
15236
|
+
if (keepLoops && track.repeatsLeft === Number.POSITIVE_INFINITY) continue;
|
|
15237
|
+
finishTrack(actx, track);
|
|
15238
|
+
}
|
|
14772
15239
|
}
|
|
14773
15240
|
/**
|
|
14774
15241
|
* Ends every track of one entity, absolute and additive, and removes its `Animation`.
|
|
@@ -14793,7 +15260,9 @@ function noFields() {
|
|
|
14793
15260
|
}
|
|
14794
15261
|
/**
|
|
14795
15262
|
* Creates the tween engine `world` runs `ViewHandle.tween`, `toRest` and `all` on. Installed with
|
|
14796
|
-
* `world.projection.setDriver` in `onStart`; the remover puts the instant writes back.
|
|
15263
|
+
* `world.projection.setDriver` in `onStart`; the remover puts the instant writes back. It also
|
|
15264
|
+
* reports what its additive tracks add to a component right now, so `world` can compare the
|
|
15265
|
+
* value under a loop with the rest pose.
|
|
14797
15266
|
*
|
|
14798
15267
|
* @param actx - Domain context of the anim plugin.
|
|
14799
15268
|
* @returns The driver.
|
|
@@ -14801,7 +15270,8 @@ function noFields() {
|
|
|
14801
15270
|
function createDriver(actx) {
|
|
14802
15271
|
return {
|
|
14803
15272
|
track: (entity, component, to, options, muted) => handleOf$1(actx, startTrack$1(actx, entity, component, to, options, muted)),
|
|
14804
|
-
cancelAll: (entity) => cancelTracksOf(actx, entity)
|
|
15273
|
+
cancelAll: (entity) => cancelTracksOf(actx, entity),
|
|
15274
|
+
offsetsOf: (entity, component) => addedOffsets(actx, entity, component)
|
|
14805
15275
|
};
|
|
14806
15276
|
}
|
|
14807
15277
|
/**
|
|
@@ -14927,7 +15397,9 @@ function createRuntime(actx) {
|
|
|
14927
15397
|
}
|
|
14928
15398
|
/**
|
|
14929
15399
|
* Ends everything that moves: every timeline at its own end, then every track on its exact
|
|
14930
|
-
* target
|
|
15400
|
+
* target, a loop too. The API exposes it and the teardown runs it last. It owes no loop by
|
|
15401
|
+
* itself; the next flush of `world` (a load, a restore, a commit in a paused or fast world) owes
|
|
15402
|
+
* the loop of a view again.
|
|
14931
15403
|
*
|
|
14932
15404
|
* @param actx - Domain context of the anim plugin.
|
|
14933
15405
|
* @param rt - The timeline runtime.
|
|
@@ -14939,8 +15411,10 @@ function finishAllAnim(actx, rt) {
|
|
|
14939
15411
|
/**
|
|
14940
15412
|
* The one frame step: the timelines consume the delta first, so a track a step just started is
|
|
14941
15413
|
* advanced by the remainder at once, then every other track advances, then every `Frames` loop.
|
|
14942
|
-
* A paused world moves nothing
|
|
14943
|
-
* stands where it is
|
|
15414
|
+
* A paused world moves nothing. A fast world ends every timeline and every track that has an
|
|
15415
|
+
* end; a loop, which has none, stays and stands where it is, so it runs on when the world is live
|
|
15416
|
+
* again. That matters for a fast walk of the flow that commits nothing: no view is flushed then,
|
|
15417
|
+
* so no view would get its loop back.
|
|
14944
15418
|
*
|
|
14945
15419
|
* @param actx - Domain context of the anim plugin.
|
|
14946
15420
|
* @param rt - The timeline runtime.
|
|
@@ -14950,7 +15424,8 @@ function stepAnim(actx, rt, time) {
|
|
|
14950
15424
|
const mode = actx.deps.world.ecs.mode();
|
|
14951
15425
|
if (mode === "paused") return;
|
|
14952
15426
|
if (mode === "fast") {
|
|
14953
|
-
|
|
15427
|
+
finishAllTimelines(actx, rt);
|
|
15428
|
+
finishAllTracks(actx, true);
|
|
14954
15429
|
return;
|
|
14955
15430
|
}
|
|
14956
15431
|
beginFrame(actx);
|
|
@@ -15114,6 +15589,7 @@ function stopAnim(state) {
|
|
|
15114
15589
|
state.owner.clear();
|
|
15115
15590
|
state.offsets.clear();
|
|
15116
15591
|
state.bases.clear();
|
|
15592
|
+
state.additive.clear();
|
|
15117
15593
|
state.timelines.clear();
|
|
15118
15594
|
state.registry.clear();
|
|
15119
15595
|
state.markListeners.clear();
|
|
@@ -15191,6 +15667,7 @@ function createAnimState(ctx) {
|
|
|
15191
15667
|
owner: emptyMap$5(),
|
|
15192
15668
|
offsets: emptyMap$5(),
|
|
15193
15669
|
bases: emptyMap$5(),
|
|
15670
|
+
additive: emptyMap$5(),
|
|
15194
15671
|
timelines: emptyMap$5(),
|
|
15195
15672
|
registry: emptyMap$5(),
|
|
15196
15673
|
markListeners: emptySet$3(),
|
|
@@ -35779,9 +36256,9 @@ function changeHooks(names, transition) {
|
|
|
35779
36256
|
* transition is resolved and every name is checked here, at definition time, so every track it
|
|
35780
36257
|
* starts carries a concrete duration and a wrong name fails where it is written. A `loop` builds
|
|
35781
36258
|
* the `loop` hook: `world` and `ui` play it wherever they play `enter`, so a projection view loops
|
|
35782
|
-
*
|
|
35783
|
-
* prop of an element changes. One cycle of the loop is `loop.ms`, or `transition.ms`
|
|
35784
|
-
* left out.
|
|
36259
|
+
* from its enter, or from the first live frame after a direct reconcile, and `ui` swaps it when
|
|
36260
|
+
* the motion prop of an element changes. One cycle of the loop is `loop.ms`, or `transition.ms`
|
|
36261
|
+
* when it is left out.
|
|
35785
36262
|
*
|
|
35786
36263
|
* @param spec - The named poses, the keyframe tracks, the transition, the loop and the hooks to
|
|
35787
36264
|
* build.
|