@moku-labs/game 0.15.27 → 0.15.29
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 +3 -3
- package/dist/app.mjs +1 -1
- package/dist/assets.d.mts +1 -1
- package/dist/{commands-BTAIwlyn.d.mts → commands-BOxMqhVe.d.mts} +5 -5
- package/dist/control.d.mts +4 -4
- package/dist/{define-CIg6SOO0.d.mts → define-CE1uxbe9.d.mts} +2 -2
- package/dist/{headless-DDymHw9o.d.mts → headless-f31cEZ7f.d.mts} +1 -1
- package/dist/{index-CURQhlzb.d.mts → index-Cl5zwodk.d.mts} +25 -22
- package/dist/index.d.mts +4 -4
- package/dist/index.mjs +1 -1
- package/dist/inspect.d.mts +6 -6
- package/dist/jsx-dev-runtime.d.mts +1 -1
- package/dist/jsx-runtime.d.mts +1 -1
- package/dist/{runtime-jtZ4eVu7.d.mts → runtime-HwTBjkIL.d.mts} +1 -1
- package/dist/{src-DLs4b_Vs.mjs → src-wyJYr12n.mjs} +1724 -601
- package/dist/testing.d.mts +4 -4
- package/dist/testing.mjs +1 -1
- package/dist/{types--1Zq5_6Q.d.mts → types-BAp9HdR4.d.mts} +10 -2
- package/dist/{types-WoDZEVQI.d.mts → types-CgE5L_7f.d.mts} +2 -2
- package/dist/{types-DHxDVC5b.d.mts → types-Cyu_jKop.d.mts} +4 -4
- package/dist/{types-DFROr3sM.d.mts → types-D27I4Q7b.d.mts} +317 -22
- package/dist/{types-BbuRDHDK.d.mts → types-DjQkfbfZ.d.mts} +2 -2
- package/dist/{visual-Dj1tlgUk.d.mts → visual-DhNLNtl8.d.mts} +6 -6
- package/dist/visual.d.mts +1 -1
- package/package.json +1 -1
|
@@ -5872,7 +5872,7 @@ function restorePosition(ctx, _modules, bookmark) {
|
|
|
5872
5872
|
* @param view - The last view.
|
|
5873
5873
|
* @returns True while nothing the state shows moved.
|
|
5874
5874
|
*/
|
|
5875
|
-
function isCurrent$
|
|
5875
|
+
function isCurrent$2(ctx, view) {
|
|
5876
5876
|
const runner = ctx.state.runner;
|
|
5877
5877
|
return view.running === (runner.running !== void 0) && view.depth === runner.stack.length && view.top === runner.stack.at(-1) && view.open === ctx.state.gate.open && view.mode === ctx.state.fx.mode && view.journalIndex === runner.journalIndex;
|
|
5878
5878
|
}
|
|
@@ -5913,7 +5913,7 @@ function viewOf(ctx) {
|
|
|
5913
5913
|
function readState(ctx) {
|
|
5914
5914
|
const runner = ctx.state.runner;
|
|
5915
5915
|
const view = runner.view;
|
|
5916
|
-
if (view !== void 0 && isCurrent$
|
|
5916
|
+
if (view !== void 0 && isCurrent$2(ctx, view)) return view.state;
|
|
5917
5917
|
const next = viewOf(ctx);
|
|
5918
5918
|
runner.view = next;
|
|
5919
5919
|
return next.state;
|
|
@@ -9467,7 +9467,8 @@ function registerFeatures(ctx, ecs, projection) {
|
|
|
9467
9467
|
* callback records the frame history before it clears the change sets; in an agent build the
|
|
9468
9468
|
* timing table starts with the page clock of `time` and the `input` callback zeroes it each
|
|
9469
9469
|
* frame. The resource `Now` gets `clock.now()` once here and first thing in every `input`
|
|
9470
|
-
* callback, so every system of a frame reads one moment
|
|
9470
|
+
* callback, so every system of a frame reads one moment; a running scene build counts its frame
|
|
9471
|
+
* budget from that moment too. `onStart` has no access to the
|
|
9471
9472
|
* plugin's own API, so it builds its own module objects — they are views on `ctx.state`, which is
|
|
9472
9473
|
* the one place the modules keep their data.
|
|
9473
9474
|
*
|
|
@@ -9486,7 +9487,9 @@ function connectWorld(ctx) {
|
|
|
9486
9487
|
registerFeatures(worldCtx, ecs, projection);
|
|
9487
9488
|
ecs.resource(Now).now = clock.now();
|
|
9488
9489
|
worldCtx.state.ecs.offFrame.push(ecs.onOwnerLeft((owner) => projection.ownerLeft(owner)), time.onFrame("input", (frame) => {
|
|
9489
|
-
|
|
9490
|
+
const now = clock.now();
|
|
9491
|
+
ecs.resource(Now).now = now;
|
|
9492
|
+
projection.startFrame(now);
|
|
9490
9493
|
worldCtx.state.ecs.timing?.ms.fill(0);
|
|
9491
9494
|
projection.reconcileIfDirty();
|
|
9492
9495
|
ecs.runPhase("input", frame);
|
|
@@ -9501,8 +9504,8 @@ function connectWorld(ctx) {
|
|
|
9501
9504
|
}
|
|
9502
9505
|
/**
|
|
9503
9506
|
* Empties the world in `onStop`: the frame callbacks and the hint listener are removed, then
|
|
9504
|
-
* every track, view, queued view, entity and resource is dropped,
|
|
9505
|
-
* history are reset.
|
|
9507
|
+
* every track, view, queued view, entity and resource is dropped, a running scene build and the
|
|
9508
|
+
* `onBuild` checks with them, and the scenes and the frame history are reset.
|
|
9506
9509
|
* No `onRemoved` fires, because `renderer` stopped earlier and destroyed its own objects.
|
|
9507
9510
|
*
|
|
9508
9511
|
* @param state - The plugin state, the only thing a teardown context carries.
|
|
@@ -9528,6 +9531,8 @@ function clearWorld(state) {
|
|
|
9528
9531
|
state.projection.membershipVersion += 1;
|
|
9529
9532
|
state.projection.moving = false;
|
|
9530
9533
|
state.projection.owedLoops = 0;
|
|
9534
|
+
delete state.projection.building;
|
|
9535
|
+
state.projection.buildChecks.length = 0;
|
|
9531
9536
|
state.ecs.generations.length = 0;
|
|
9532
9537
|
state.ecs.free.length = 0;
|
|
9533
9538
|
state.ecs.owners.clear();
|
|
@@ -9920,9 +9925,10 @@ function isNode(output) {
|
|
|
9920
9925
|
/**
|
|
9921
9926
|
* Runs `view(item)` and collects its output by component name. A view that describes a screen
|
|
9922
9927
|
* returns one node, which becomes the `Tree` of the entity, and `ui` reconciles it from there;
|
|
9923
|
-
* the root draws at the `Order` of its place in the mounted list
|
|
9924
|
-
*
|
|
9925
|
-
* view's rest, so a new node compares it by value
|
|
9928
|
+
* the root draws at the `Order` of its place in the mounted list (or the list of the running build,
|
|
9929
|
+
* which mounts it aside), written before `Tree` because the ecs fires `onAdded` in list order and the
|
|
9930
|
+
* `Tree` hook of `ui` reads it. `Order` is part of the view's rest, so a new node compares it by value
|
|
9931
|
+
* and writes nothing.
|
|
9926
9932
|
* `Layer` is the projection's own component: a view that returns it is reported and ignored.
|
|
9927
9933
|
*
|
|
9928
9934
|
* @param pctx - Domain context of the projection module.
|
|
@@ -9935,7 +9941,8 @@ function collectView(pctx, spec, item) {
|
|
|
9935
9941
|
const layerName = pctx.deps.components.Layer.componentName;
|
|
9936
9942
|
const output = spec.view(item);
|
|
9937
9943
|
const { Order, Tree } = pctx.deps.components;
|
|
9938
|
-
const
|
|
9944
|
+
const state = pctx.ctx.state.projection;
|
|
9945
|
+
const order = (state.mounted.get(spec.name) ?? state.building?.mounted.get(spec.name))?.order ?? 0;
|
|
9939
9946
|
const produced = isNode(output) ? [Order({ value: order }), Tree({ node: output })] : output;
|
|
9940
9947
|
for (const value of produced) {
|
|
9941
9948
|
if (value.type.componentName === layerName) {
|
|
@@ -10862,6 +10869,8 @@ function hintFor$1(pctx, projection, key, used) {
|
|
|
10862
10869
|
* play the `enter` hook and the `loop` hook when the reconcile plays motions. The loop is not
|
|
10863
10870
|
* kept with the view's handles: a change cancels those, and the loop runs until the view dies.
|
|
10864
10871
|
* A direct reconcile plays neither hook. The view then owes its loop, which the sweep starts.
|
|
10872
|
+
* A view of a projection the running build mounts is flagged building right after its spawn: the
|
|
10873
|
+
* spawn fires `onAdded` inside the call, so no flag can come before it.
|
|
10865
10874
|
*
|
|
10866
10875
|
* @param pctx - Domain context of the projection module.
|
|
10867
10876
|
* @param spec - The projection spec.
|
|
@@ -10880,14 +10889,17 @@ function enterEntry(pctx, spec, mounted, entry) {
|
|
|
10880
10889
|
name: spec.name
|
|
10881
10890
|
}, [pctx.deps.components.Layer({ name: spec.layer }), ...values.values()]);
|
|
10882
10891
|
const view = createViewRecord(entity, spec.name, entry.key, entry.item, values, entry.direct ? "mount" : "change");
|
|
10892
|
+
const state = pctx.ctx.state.projection;
|
|
10893
|
+
const building = state.building;
|
|
10883
10894
|
mounted.live.set(entry.key, view);
|
|
10884
|
-
|
|
10885
|
-
|
|
10895
|
+
state.byEntity.set(entity, view);
|
|
10896
|
+
state.membershipVersion += 1;
|
|
10897
|
+
if (building?.mounted.get(spec.name) === mounted) building.entities.add(entity);
|
|
10886
10898
|
const hook = spec.motion?.enter;
|
|
10887
10899
|
const loop = spec.motion?.loop;
|
|
10888
10900
|
if (entry.direct) {
|
|
10889
10901
|
view.loopOwed = true;
|
|
10890
|
-
|
|
10902
|
+
state.owedLoops += 1;
|
|
10891
10903
|
return;
|
|
10892
10904
|
}
|
|
10893
10905
|
if (hook === void 0 && loop === void 0) return;
|
|
@@ -11258,10 +11270,12 @@ function reconcileOne(pctx, spec, mounted, snapshot, options, counts, used) {
|
|
|
11258
11270
|
}, counts);
|
|
11259
11271
|
exitGone(pctx, spec, mounted, next, entry, counts);
|
|
11260
11272
|
} catch (error) {
|
|
11273
|
+
if (options.strict === true) throw error;
|
|
11261
11274
|
pctx.ctx.log.error("world:projection-failed", { projection: spec.name }, asError$2(error));
|
|
11262
11275
|
return;
|
|
11263
11276
|
}
|
|
11264
11277
|
mounted.items = next;
|
|
11278
|
+
mounted.seen = snapshot;
|
|
11265
11279
|
if (options.direct) flushMounted(pctx, mounted);
|
|
11266
11280
|
if (options.converge) convergeMounted(pctx, mounted);
|
|
11267
11281
|
else {
|
|
@@ -11271,8 +11285,60 @@ function reconcileOne(pctx, spec, mounted, snapshot, options, counts, used) {
|
|
|
11271
11285
|
counts.queued += mounted.queue.length;
|
|
11272
11286
|
}
|
|
11273
11287
|
/**
|
|
11274
|
-
*
|
|
11275
|
-
*
|
|
11288
|
+
* Tells whether a play reconcile would change nothing in a projection: its last reconcile read the same `player` and
|
|
11289
|
+
* `session` trees, and nothing forces, converges or settles it. A scene mounts its projections direct after the commit
|
|
11290
|
+
* of its node; the next frame's reconcile of that commit then skips them (≈2 ms a switch on a Galaxy A26, F61).
|
|
11291
|
+
*
|
|
11292
|
+
* @param mounted - The mounted projection.
|
|
11293
|
+
* @param snapshot - The state of this reconcile.
|
|
11294
|
+
* @param options - How this reconcile runs.
|
|
11295
|
+
* @returns True when the projection is passed over.
|
|
11296
|
+
*/
|
|
11297
|
+
function isCurrent$1(mounted, snapshot, options) {
|
|
11298
|
+
const seen = mounted.seen;
|
|
11299
|
+
return seen !== void 0 && !options.direct && !options.force && !options.converge && !options.settle && seen.player === snapshot.player && seen.session === snapshot.session;
|
|
11300
|
+
}
|
|
11301
|
+
/**
|
|
11302
|
+
* Reconciles the projections of one table against the snapshot: every one, or the named ones only.
|
|
11303
|
+
* A projection whose last reconcile read the same trees is passed over as `skipBy` decides; `runWith`
|
|
11304
|
+
* is how the others run.
|
|
11305
|
+
*
|
|
11306
|
+
* @param pctx - Domain context of the projection module.
|
|
11307
|
+
* @param table - The mounted projections, or the ones a build mounts aside.
|
|
11308
|
+
* @param snapshot - The model snapshot of this reconcile.
|
|
11309
|
+
* @param skipBy - The options of the reconcile that was asked for.
|
|
11310
|
+
* @param runWith - The options each projection of this table is reconciled with.
|
|
11311
|
+
* @param counts - The counters of this reconcile.
|
|
11312
|
+
* @param used - The hints that reached an entry.
|
|
11313
|
+
*/
|
|
11314
|
+
function reconcileTable(pctx, table, snapshot, skipBy, runWith, counts, used) {
|
|
11315
|
+
const specs = pctx.ctx.state.projection.specs;
|
|
11316
|
+
for (const [name, mounted] of table) {
|
|
11317
|
+
if (skipBy.names !== void 0 && !skipBy.names.includes(name)) continue;
|
|
11318
|
+
const spec = specs.get(name);
|
|
11319
|
+
if (spec === void 0 || isCurrent$1(mounted, snapshot, skipBy)) continue;
|
|
11320
|
+
counts.projections += 1;
|
|
11321
|
+
reconcileOne(pctx, spec, mounted, snapshot, runWith, counts, used);
|
|
11322
|
+
}
|
|
11323
|
+
}
|
|
11324
|
+
/**
|
|
11325
|
+
* Tells whether a mounted projection is not on a list: one a reconcile limited to the list passes over.
|
|
11326
|
+
*
|
|
11327
|
+
* @param mounted - The mounted projections.
|
|
11328
|
+
* @param names - The list, the coming scene's.
|
|
11329
|
+
* @returns True when one is left out.
|
|
11330
|
+
*/
|
|
11331
|
+
function passesOver(mounted, names) {
|
|
11332
|
+
for (const name of mounted.keys()) if (!names.includes(name)) return true;
|
|
11333
|
+
return false;
|
|
11334
|
+
}
|
|
11335
|
+
/**
|
|
11336
|
+
* Runs one reconcile over every mounted projection, or over the named ones only, or over the table
|
|
11337
|
+
* the options name. A reconcile of every mounted projection reaches the projections a running build
|
|
11338
|
+
* mounts aside too, direct, so a store change during the build reaches the coming scene and none of
|
|
11339
|
+
* its views plays a motion unseen; the mounted projections the coming scene drops are passed over
|
|
11340
|
+
* until the build ends. A converging one then brings the elements other plugins own home and tells
|
|
11341
|
+
* their owners.
|
|
11276
11342
|
*
|
|
11277
11343
|
* @param pctx - Domain context of the projection module.
|
|
11278
11344
|
* @param options - Which projections, play or direct, whether `view` is forced, whether every
|
|
@@ -11284,12 +11350,20 @@ function reconcile(pctx, options) {
|
|
|
11284
11350
|
const snapshot = pctx.ctx.deps.model.store.snapshot();
|
|
11285
11351
|
const counts = emptyCounts();
|
|
11286
11352
|
const used = emptyHintSet();
|
|
11287
|
-
|
|
11288
|
-
|
|
11289
|
-
|
|
11290
|
-
|
|
11291
|
-
|
|
11292
|
-
|
|
11353
|
+
const building = state.building;
|
|
11354
|
+
const whole = options.table === void 0 && options.names === void 0;
|
|
11355
|
+
const skipBy = whole && building !== void 0 ? {
|
|
11356
|
+
...options,
|
|
11357
|
+
names: building.names
|
|
11358
|
+
} : options;
|
|
11359
|
+
if (whole && building !== void 0 && passesOver(state.mounted, building.names)) building.held = options.force || building.held === "rerun" ? "rerun" : "change";
|
|
11360
|
+
reconcileTable(pctx, options.table ?? state.mounted, snapshot, skipBy, options, counts, used);
|
|
11361
|
+
if (whole && building !== void 0) {
|
|
11362
|
+
const aside = {
|
|
11363
|
+
...options,
|
|
11364
|
+
direct: true
|
|
11365
|
+
};
|
|
11366
|
+
reconcileTable(pctx, building.mounted, snapshot, options, aside, counts, used);
|
|
11293
11367
|
}
|
|
11294
11368
|
if (options.converge) {
|
|
11295
11369
|
convergeElements(pctx);
|
|
@@ -11305,9 +11379,7 @@ function reconcile(pctx, options) {
|
|
|
11305
11379
|
//#endregion
|
|
11306
11380
|
//#region src/plugins/world/projection/mount.ts
|
|
11307
11381
|
/**
|
|
11308
|
-
* Creates the empty view tables of a freshly mounted projection.
|
|
11309
|
-
* non-exported functions because lint rule L5 refuses a collection built inside an exported
|
|
11310
|
-
* declaration.
|
|
11382
|
+
* Creates the empty view tables of a freshly mounted projection.
|
|
11311
11383
|
*
|
|
11312
11384
|
* @param owner - Who owns the entities of this projection.
|
|
11313
11385
|
* @param order - The `Order` its screen roots draw at.
|
|
@@ -11325,14 +11397,29 @@ function emptyMounted(owner, order, layers) {
|
|
|
11325
11397
|
};
|
|
11326
11398
|
}
|
|
11327
11399
|
/**
|
|
11328
|
-
*
|
|
11400
|
+
* The `Order` a screen root of a projection draws at: its place in the list of its scene minus the
|
|
11401
|
+
* length of the list, so the roots of one scene take −n … −1 (spec 16 U1.6).
|
|
11402
|
+
*
|
|
11403
|
+
* @param names - The projection list of the scene.
|
|
11404
|
+
* @param name - One projection of it.
|
|
11405
|
+
* @returns The order of its screen roots.
|
|
11406
|
+
* @example
|
|
11407
|
+
* ```ts
|
|
11408
|
+
* orderIn(["board.items", "hud"], "hud"); // -1
|
|
11409
|
+
* ```
|
|
11410
|
+
*/
|
|
11411
|
+
function orderIn(names, name) {
|
|
11412
|
+
return names.indexOf(name) - names.length;
|
|
11413
|
+
}
|
|
11414
|
+
/**
|
|
11415
|
+
* Checks that every layer a projection names is declared by a layer list.
|
|
11329
11416
|
*
|
|
11330
|
-
* @param pctx - Domain context of the projection module.
|
|
11331
11417
|
* @param spec - The projection spec.
|
|
11418
|
+
* @param layers - The layer list of the scene: the current one, or the one a build comes with.
|
|
11332
11419
|
* @throws {Error} When `layer` or `lift` is not in the layer list.
|
|
11333
11420
|
*/
|
|
11334
|
-
function checkLayers(
|
|
11335
|
-
const declared = new Set(
|
|
11421
|
+
function checkLayers(spec, layers) {
|
|
11422
|
+
const declared = new Set(layers.map((layer) => layer.name));
|
|
11336
11423
|
for (const name of [spec.layer, spec.lift]) {
|
|
11337
11424
|
if (name === void 0 || declared.has(name)) continue;
|
|
11338
11425
|
throw new Error(`[game] Projection "${spec.name}" names layer "${name}", which the scene does not declare.\n Add it to the layers of the scene.`);
|
|
@@ -11389,16 +11476,17 @@ function unmountNames(pctx, names) {
|
|
|
11389
11476
|
}
|
|
11390
11477
|
}
|
|
11391
11478
|
/**
|
|
11392
|
-
* The first pass of a mount, which touches nothing: every name is checked
|
|
11393
|
-
*
|
|
11394
|
-
* come and the ones a scene before mounted that have to go and come back.
|
|
11479
|
+
* The first pass of a mount, which touches nothing: every name is checked against a layer list, a
|
|
11480
|
+
* projection mounted under that very list already is reported and passed over, and the rest is
|
|
11481
|
+
* sorted into the ones that come and the ones a scene before mounted that have to go and come back.
|
|
11395
11482
|
*
|
|
11396
11483
|
* @param pctx - Domain context of the projection module.
|
|
11397
11484
|
* @param names - Names of the projections to mount.
|
|
11485
|
+
* @param layers - The layer list they are mounted under: the current one, or the one a build comes with.
|
|
11398
11486
|
* @returns The specs to mount and reconcile, and the names to unmount first.
|
|
11399
|
-
* @throws {Error} For an unknown name, or a `layer` or `lift` the
|
|
11487
|
+
* @throws {Error} For an unknown name, or a `layer` or `lift` the list does not declare.
|
|
11400
11488
|
*/
|
|
11401
|
-
function sortMount(pctx, names) {
|
|
11489
|
+
function sortMount(pctx, names, layers) {
|
|
11402
11490
|
const state = pctx.ctx.state.projection;
|
|
11403
11491
|
const fresh = [];
|
|
11404
11492
|
const again = [];
|
|
@@ -11406,12 +11494,12 @@ function sortMount(pctx, names) {
|
|
|
11406
11494
|
const spec = state.specs.get(name);
|
|
11407
11495
|
if (spec === void 0) throw new Error(`[game] Projection "${name}" is not registered.\n Register it before a scene mounts it.`);
|
|
11408
11496
|
const mounted = state.mounted.get(name);
|
|
11409
|
-
if (mounted?.layers ===
|
|
11497
|
+
if (mounted?.layers === layers) {
|
|
11410
11498
|
pctx.ctx.log.warn("world:already-mounted", { projection: name });
|
|
11411
11499
|
continue;
|
|
11412
11500
|
}
|
|
11413
|
-
checkLayers(
|
|
11414
|
-
if (mounted !== void 0 && drawsAlike(mounted.layers,
|
|
11501
|
+
checkLayers(spec, layers);
|
|
11502
|
+
if (mounted !== void 0 && drawsAlike(mounted.layers, layers, spec)) continue;
|
|
11415
11503
|
if (mounted !== void 0) again.push(name);
|
|
11416
11504
|
fresh.push(spec);
|
|
11417
11505
|
}
|
|
@@ -11433,18 +11521,40 @@ function sortMount(pctx, names) {
|
|
|
11433
11521
|
* @throws {Error} For an unknown name, or a `layer` or `lift` the scene does not declare.
|
|
11434
11522
|
*/
|
|
11435
11523
|
function mountNames(pctx, names, owner) {
|
|
11436
|
-
const
|
|
11437
|
-
const { fresh, again } = sortMount(pctx, names);
|
|
11524
|
+
const { fresh, again } = sortMount(pctx, names, pctx.ctx.state.projection.layers);
|
|
11438
11525
|
unmountNames(pctx, again);
|
|
11526
|
+
placeNames(pctx, names, owner);
|
|
11527
|
+
reconcileFresh(pctx, fresh.map((spec) => spec.name));
|
|
11528
|
+
}
|
|
11529
|
+
/**
|
|
11530
|
+
* Gives every projection of a list its place under the current layer list: a missing one gets an
|
|
11531
|
+
* empty record at the `Order` of its place, a kept one mounted under another list takes its new
|
|
11532
|
+
* place (`placeMounted`). A screen root draws at its place in the list (spec 16 U1.6).
|
|
11533
|
+
*
|
|
11534
|
+
* @param pctx - Domain context of the projection module.
|
|
11535
|
+
* @param names - The projection list of the scene.
|
|
11536
|
+
* @param owner - Who owns the entities of a missing one.
|
|
11537
|
+
*/
|
|
11538
|
+
function placeNames(pctx, names, owner) {
|
|
11539
|
+
const state = pctx.ctx.state.projection;
|
|
11439
11540
|
for (const name of names) {
|
|
11440
|
-
const order = names
|
|
11541
|
+
const order = orderIn(names, name);
|
|
11441
11542
|
const mounted = state.mounted.get(name);
|
|
11442
11543
|
if (mounted === void 0) state.mounted.set(name, emptyMounted(owner, order, state.layers));
|
|
11443
11544
|
else if (mounted.layers !== state.layers) placeMounted(pctx, mounted, order);
|
|
11444
11545
|
}
|
|
11445
|
-
|
|
11546
|
+
}
|
|
11547
|
+
/**
|
|
11548
|
+
* Reconciles freshly placed projections at once, direct, as a mount does. Nothing runs for an
|
|
11549
|
+
* empty list.
|
|
11550
|
+
*
|
|
11551
|
+
* @param pctx - Domain context of the projection module.
|
|
11552
|
+
* @param names - The projections that came.
|
|
11553
|
+
*/
|
|
11554
|
+
function reconcileFresh(pctx, names) {
|
|
11555
|
+
if (names.length === 0) return;
|
|
11446
11556
|
reconcile(pctx, {
|
|
11447
|
-
names
|
|
11557
|
+
names,
|
|
11448
11558
|
direct: true,
|
|
11449
11559
|
force: true,
|
|
11450
11560
|
converge: false,
|
|
@@ -11452,6 +11562,148 @@ function mountNames(pctx, names, owner) {
|
|
|
11452
11562
|
});
|
|
11453
11563
|
}
|
|
11454
11564
|
//#endregion
|
|
11565
|
+
//#region src/plugins/world/projection/build.ts
|
|
11566
|
+
/**
|
|
11567
|
+
* Creates the empty table of a build: nothing mounted aside and nothing spawned yet.
|
|
11568
|
+
*
|
|
11569
|
+
* @param names - The projection list of the coming scene.
|
|
11570
|
+
* @param layers - The layer list of the coming scene.
|
|
11571
|
+
* @param owner - Who owns the entities of the projections it mounts.
|
|
11572
|
+
* @param timing - The frame budget and the moment the build starts.
|
|
11573
|
+
* @param timing.budgetMs - Milliseconds per frame the building work may take.
|
|
11574
|
+
* @param timing.now - The game clock at the build start.
|
|
11575
|
+
* @returns The table, its frame starting at the build start.
|
|
11576
|
+
*/
|
|
11577
|
+
function emptyBuilding(names, layers, owner, timing) {
|
|
11578
|
+
return {
|
|
11579
|
+
names: [...names],
|
|
11580
|
+
layers,
|
|
11581
|
+
owner,
|
|
11582
|
+
mounted: /* @__PURE__ */ new Map(),
|
|
11583
|
+
entities: /* @__PURE__ */ new Set(),
|
|
11584
|
+
frameStart: timing.now,
|
|
11585
|
+
budgetMs: timing.budgetMs
|
|
11586
|
+
};
|
|
11587
|
+
}
|
|
11588
|
+
/**
|
|
11589
|
+
* Creates the empty set of dirty roots. It lives in its own non-exported function because lint
|
|
11590
|
+
* rule L5 refuses a collection built inside an exported declaration.
|
|
11591
|
+
*
|
|
11592
|
+
* @returns An empty set of roots.
|
|
11593
|
+
*/
|
|
11594
|
+
function emptyRoots() {
|
|
11595
|
+
return /* @__PURE__ */ new Set();
|
|
11596
|
+
}
|
|
11597
|
+
/**
|
|
11598
|
+
* Marks the world dirty at the end of a build that passed mounted projections over (`held`), so the
|
|
11599
|
+
* next frame's reconcile reaches the ones still mounted: their last reconcile read an older state,
|
|
11600
|
+
* every other projection read this one and is passed over. A build that held a forced reconcile
|
|
11601
|
+
* forces the next one too. A build that held nothing marks nothing.
|
|
11602
|
+
*
|
|
11603
|
+
* @param pctx - Domain context of the projection module.
|
|
11604
|
+
* @param building - The build that ends.
|
|
11605
|
+
*/
|
|
11606
|
+
function handBack(pctx, building) {
|
|
11607
|
+
const state = pctx.ctx.state.projection;
|
|
11608
|
+
const held = building.held;
|
|
11609
|
+
if (held === void 0) return;
|
|
11610
|
+
const dirty = state.dirty ?? {
|
|
11611
|
+
causes: [],
|
|
11612
|
+
roots: emptyRoots(),
|
|
11613
|
+
force: false
|
|
11614
|
+
};
|
|
11615
|
+
dirty.causes.push(held === "rerun" ? "rerun" : "edge");
|
|
11616
|
+
dirty.roots.add("player");
|
|
11617
|
+
dirty.roots.add("session");
|
|
11618
|
+
dirty.force ||= held === "rerun";
|
|
11619
|
+
state.dirty = dirty;
|
|
11620
|
+
}
|
|
11621
|
+
/**
|
|
11622
|
+
* Starts the build of a scene's list under its layer list. A projection mounted now under other
|
|
11623
|
+
* layer rules makes it answer false before anything is touched. The rest of the list is checked as
|
|
11624
|
+
* a mount checks it, against the coming layers; a projection the current scene mounts and the
|
|
11625
|
+
* coming one declares alike stays as it is, and every other one is mounted aside, direct. A view
|
|
11626
|
+
* that throws leaves nothing behind: what the build spawned is despawned and the throw goes on.
|
|
11627
|
+
*
|
|
11628
|
+
* @param pctx - Domain context of the projection module.
|
|
11629
|
+
* @param names - The projection list of the coming scene.
|
|
11630
|
+
* @param owner - Who owns the entities of the projections it mounts.
|
|
11631
|
+
* @param layers - The layer list of the coming scene.
|
|
11632
|
+
* @param budgetMs - Milliseconds per frame the building work may take.
|
|
11633
|
+
* @returns True when the build runs, false on a projection mounted under other layer rules.
|
|
11634
|
+
* @throws {Error} When a build already runs, for an unknown name, for a `layer` or `lift` the
|
|
11635
|
+
* coming list does not declare, and whatever a `view` threw.
|
|
11636
|
+
*/
|
|
11637
|
+
function buildNames(pctx, names, owner, layers, budgetMs) {
|
|
11638
|
+
const state = pctx.ctx.state.projection;
|
|
11639
|
+
if (state.building !== void 0) throw new Error("[game] A scene is already being built.\n Commit or discard the running build before the next one starts.");
|
|
11640
|
+
const { fresh, again } = sortMount(pctx, names, layers);
|
|
11641
|
+
if (again.length > 0) return false;
|
|
11642
|
+
const building = emptyBuilding(names, layers, owner, {
|
|
11643
|
+
budgetMs,
|
|
11644
|
+
now: pctx.ctx.deps.clock.now()
|
|
11645
|
+
});
|
|
11646
|
+
for (const spec of fresh) building.mounted.set(spec.name, emptyMounted(owner, orderIn(names, spec.name), layers));
|
|
11647
|
+
state.building = building;
|
|
11648
|
+
if (fresh.length === 0) return true;
|
|
11649
|
+
try {
|
|
11650
|
+
reconcile(pctx, {
|
|
11651
|
+
table: building.mounted,
|
|
11652
|
+
direct: true,
|
|
11653
|
+
force: true,
|
|
11654
|
+
converge: false,
|
|
11655
|
+
settle: false,
|
|
11656
|
+
strict: true
|
|
11657
|
+
});
|
|
11658
|
+
} catch (error) {
|
|
11659
|
+
discardBuild(pctx);
|
|
11660
|
+
throw error;
|
|
11661
|
+
}
|
|
11662
|
+
return true;
|
|
11663
|
+
}
|
|
11664
|
+
/**
|
|
11665
|
+
* The swap of the running build. Its projections move into the mounted table, and the list is
|
|
11666
|
+
* placed as a mount places it: every moved and kept projection takes the layer list `setLayers`
|
|
11667
|
+
* stored just before (`placeMounted`), so the next scene compares it by identity as after a mount;
|
|
11668
|
+
* a kept one takes its new place, and one the build kept that left before the commit is mounted
|
|
11669
|
+
* now, direct. The views that moved owe their loop, also when nothing left with the commit, and the
|
|
11670
|
+
* hosts of `ui` look at the views again.
|
|
11671
|
+
*
|
|
11672
|
+
* @param pctx - Domain context of the projection module.
|
|
11673
|
+
*/
|
|
11674
|
+
function commitBuild(pctx) {
|
|
11675
|
+
const state = pctx.ctx.state.projection;
|
|
11676
|
+
const building = state.building;
|
|
11677
|
+
if (building === void 0) return;
|
|
11678
|
+
delete state.building;
|
|
11679
|
+
for (const [name, mounted] of building.mounted) state.mounted.set(name, mounted);
|
|
11680
|
+
const missing = building.names.filter((name) => !state.mounted.has(name));
|
|
11681
|
+
placeNames(pctx, building.names, building.owner);
|
|
11682
|
+
state.owedLoops += 1;
|
|
11683
|
+
state.membershipVersion += 1;
|
|
11684
|
+
reconcileFresh(pctx, missing);
|
|
11685
|
+
handBack(pctx, building);
|
|
11686
|
+
}
|
|
11687
|
+
/**
|
|
11688
|
+
* Drops the running build: every view it spawned leaves with the cause `"discard"` and no motion,
|
|
11689
|
+
* then every entity `adopt` marked that is still there is despawned too, and the table goes. The
|
|
11690
|
+
* removal hooks still see the build while they run.
|
|
11691
|
+
*
|
|
11692
|
+
* @param pctx - Domain context of the projection module.
|
|
11693
|
+
*/
|
|
11694
|
+
function discardBuild(pctx) {
|
|
11695
|
+
const state = pctx.ctx.state.projection;
|
|
11696
|
+
const building = state.building;
|
|
11697
|
+
if (building === void 0) return;
|
|
11698
|
+
for (const mounted of building.mounted.values()) for (const view of [...mounted.live.values()]) {
|
|
11699
|
+
view.cause = "discard";
|
|
11700
|
+
despawnView(pctx, mounted, view);
|
|
11701
|
+
}
|
|
11702
|
+
for (const entity of building.entities) if (pctx.deps.ecs.ownerOf(entity) !== void 0) pctx.deps.ecs.despawn(entity);
|
|
11703
|
+
delete state.building;
|
|
11704
|
+
handBack(pctx, building);
|
|
11705
|
+
}
|
|
11706
|
+
//#endregion
|
|
11455
11707
|
//#region src/plugins/world/projection/api.ts
|
|
11456
11708
|
/**
|
|
11457
11709
|
* Tells whether a cause sets the picture instead of playing it.
|
|
@@ -11626,7 +11878,7 @@ function createProjectionApi(ctx, deps) {
|
|
|
11626
11878
|
replace: (spec) => {
|
|
11627
11879
|
if (!state.specs.has(spec.name)) throw new Error(`[game] Projection "${spec.name}" is not registered.\n Register it before replacing it.`);
|
|
11628
11880
|
const mounted = state.mounted.has(spec.name);
|
|
11629
|
-
if (mounted) checkLayers(
|
|
11881
|
+
if (mounted) checkLayers(spec, state.layers);
|
|
11630
11882
|
state.specs.set(spec.name, spec);
|
|
11631
11883
|
if (mounted) forceRerun();
|
|
11632
11884
|
},
|
|
@@ -11701,7 +11953,7 @@ function createProjectionApi(ctx, deps) {
|
|
|
11701
11953
|
motionsOf: (entity) => runningComponents(state.tracks, entity),
|
|
11702
11954
|
entityOf: (projection, key) => state.mounted.get(projection)?.live.get(key)?.entity ?? state.keys.get(projection)?.get(key),
|
|
11703
11955
|
entitiesOf: (name) => {
|
|
11704
|
-
const mounted = state.mounted.get(name);
|
|
11956
|
+
const mounted = state.mounted.get(name) ?? state.building?.mounted.get(name);
|
|
11705
11957
|
return mounted === void 0 ? [] : liveEntities(mounted);
|
|
11706
11958
|
},
|
|
11707
11959
|
setDriver: (driver) => {
|
|
@@ -11747,6 +11999,38 @@ function createProjectionApi(ctx, deps) {
|
|
|
11747
11999
|
};
|
|
11748
12000
|
},
|
|
11749
12001
|
rerunAll: forceRerun,
|
|
12002
|
+
build: (names, owner, layers, budgetMs) => buildNames(pctx, names, owner, layers, budgetMs),
|
|
12003
|
+
built: () => {
|
|
12004
|
+
if (state.building === void 0) return false;
|
|
12005
|
+
for (const check of state.buildChecks) if (!check()) return false;
|
|
12006
|
+
return true;
|
|
12007
|
+
},
|
|
12008
|
+
commit: () => {
|
|
12009
|
+
commitBuild(pctx);
|
|
12010
|
+
},
|
|
12011
|
+
discard: () => {
|
|
12012
|
+
discardBuild(pctx);
|
|
12013
|
+
},
|
|
12014
|
+
left: () => {
|
|
12015
|
+
const building = state.building;
|
|
12016
|
+
if (building === void 0) return Number.POSITIVE_INFINITY;
|
|
12017
|
+
return building.budgetMs - (ctx.deps.clock.now() - building.frameStart);
|
|
12018
|
+
},
|
|
12019
|
+
isBuilding: (entity) => state.building?.entities.has(entity) === true,
|
|
12020
|
+
onBuild: (check) => {
|
|
12021
|
+
state.buildChecks.push(check);
|
|
12022
|
+
return () => {
|
|
12023
|
+
const at = state.buildChecks.indexOf(check);
|
|
12024
|
+
if (at !== -1) state.buildChecks.splice(at, 1);
|
|
12025
|
+
};
|
|
12026
|
+
},
|
|
12027
|
+
adopt: (entity) => {
|
|
12028
|
+
state.building?.entities.add(entity);
|
|
12029
|
+
},
|
|
12030
|
+
startFrame: (now) => {
|
|
12031
|
+
const building = state.building;
|
|
12032
|
+
if (building !== void 0) building.frameStart = now;
|
|
12033
|
+
},
|
|
11750
12034
|
markDirty: (roots, cause) => {
|
|
11751
12035
|
const dirty = state.dirty ?? emptyDirty();
|
|
11752
12036
|
dirty.causes.push(cause);
|
|
@@ -11785,6 +12069,7 @@ function createProjectionApi(ctx, deps) {
|
|
|
11785
12069
|
if (mounted !== void 0) clearMounted(pctx, mounted);
|
|
11786
12070
|
},
|
|
11787
12071
|
clear: () => {
|
|
12072
|
+
discardBuild(pctx);
|
|
11788
12073
|
for (const mounted of [...state.mounted.values()]) clearMounted(pctx, mounted);
|
|
11789
12074
|
state.mounted.clear();
|
|
11790
12075
|
state.specs.clear();
|
|
@@ -11893,8 +12178,8 @@ function exposeEcs(ecs, projection) {
|
|
|
11893
12178
|
};
|
|
11894
12179
|
}
|
|
11895
12180
|
/**
|
|
11896
|
-
* Reduces the projection module to what `scenes`, `input`, `i18n` and the
|
|
11897
|
-
* The frame methods and the dirty flag stay with the plugin root.
|
|
12181
|
+
* Reduces the projection module to what `scenes`, `input`, `i18n`, `ui`, `renderer` and the
|
|
12182
|
+
* `/inspect` door call. The frame methods and the dirty flag stay with the plugin root.
|
|
11898
12183
|
*
|
|
11899
12184
|
* @param projection - The full projection module.
|
|
11900
12185
|
* @returns The public projection API.
|
|
@@ -11922,7 +12207,15 @@ function exposeProjection(projection) {
|
|
|
11922
12207
|
restOf: projection.restOf,
|
|
11923
12208
|
registerKey: projection.registerKey,
|
|
11924
12209
|
onConverged: projection.onConverged,
|
|
11925
|
-
rerunAll: projection.rerunAll
|
|
12210
|
+
rerunAll: projection.rerunAll,
|
|
12211
|
+
build: projection.build,
|
|
12212
|
+
built: projection.built,
|
|
12213
|
+
commit: projection.commit,
|
|
12214
|
+
discard: projection.discard,
|
|
12215
|
+
left: projection.left,
|
|
12216
|
+
isBuilding: projection.isBuilding,
|
|
12217
|
+
onBuild: projection.onBuild,
|
|
12218
|
+
adopt: projection.adopt
|
|
11926
12219
|
};
|
|
11927
12220
|
}
|
|
11928
12221
|
/**
|
|
@@ -12065,7 +12358,8 @@ function emptyWeakMap$2() {
|
|
|
12065
12358
|
}
|
|
12066
12359
|
/**
|
|
12067
12360
|
* Creates the initial projection state: nothing registered, nothing mounted, no layer list, no
|
|
12068
|
-
* running track and no driver, so every motion is instant until `anim` installs one.
|
|
12361
|
+
* running track and no driver, so every motion is instant until `anim` installs one. No build
|
|
12362
|
+
* runs: `building` is absent.
|
|
12069
12363
|
*
|
|
12070
12364
|
* @returns The projection branch of the plugin state.
|
|
12071
12365
|
*/
|
|
@@ -12091,7 +12385,8 @@ function createProjectionState() {
|
|
|
12091
12385
|
membershipVersion: 0,
|
|
12092
12386
|
moving: false,
|
|
12093
12387
|
owedLoops: 0,
|
|
12094
|
-
sweptDespawns: 0
|
|
12388
|
+
sweptDespawns: 0,
|
|
12389
|
+
buildChecks: []
|
|
12095
12390
|
};
|
|
12096
12391
|
}
|
|
12097
12392
|
//#endregion
|
|
@@ -23548,7 +23843,7 @@ function heldFilters(target) {
|
|
|
23548
23843
|
* @param slots - The slots to compare with.
|
|
23549
23844
|
* @returns True when nothing has to be written.
|
|
23550
23845
|
*/
|
|
23551
|
-
function holds(target, slots) {
|
|
23846
|
+
function holds$1(target, slots) {
|
|
23552
23847
|
const held = heldFilters(target);
|
|
23553
23848
|
let at = 0;
|
|
23554
23849
|
for (let index = 0; index < slots.length; index += 1) {
|
|
@@ -23605,7 +23900,7 @@ function clearFilters(target) {
|
|
|
23605
23900
|
* @param target - Where the filters hang: the view's top unless the kept cache says otherwise.
|
|
23606
23901
|
*/
|
|
23607
23902
|
function applyFilters(view, slots, before, target = view.wrapper ?? view.object) {
|
|
23608
|
-
if (holds(target, slots)) return;
|
|
23903
|
+
if (holds$1(target, slots)) return;
|
|
23609
23904
|
before();
|
|
23610
23905
|
writeFilters(target, slots);
|
|
23611
23906
|
}
|
|
@@ -23717,7 +24012,7 @@ function applySwitchedOne(slots, entity) {
|
|
|
23717
24012
|
const view = sync?.views.get(entity);
|
|
23718
24013
|
if (view === void 0 || kept === void 0) return;
|
|
23719
24014
|
const target = kept.filterTarget(view);
|
|
23720
|
-
if (holds(target, slots)) return;
|
|
24015
|
+
if (holds$1(target, slots)) return;
|
|
23721
24016
|
writeFilters(target, slots);
|
|
23722
24017
|
switched.wrote = true;
|
|
23723
24018
|
}
|
|
@@ -24628,6 +24923,348 @@ function clearFonts(state) {
|
|
|
24628
24923
|
state.fontCache = void 0;
|
|
24629
24924
|
}
|
|
24630
24925
|
//#endregion
|
|
24926
|
+
//#region src/plugins/renderer/sync/pools.ts
|
|
24927
|
+
/**
|
|
24928
|
+
* Tells whether a display object is a sprite. A `NineSliceSprite` carries the slice widths and
|
|
24929
|
+
* is recognised by them.
|
|
24930
|
+
*
|
|
24931
|
+
* @param object - The display object.
|
|
24932
|
+
* @returns True for a plain sprite.
|
|
24933
|
+
*/
|
|
24934
|
+
function isSprite(object) {
|
|
24935
|
+
return "texture" in object && !("leftWidth" in object);
|
|
24936
|
+
}
|
|
24937
|
+
/**
|
|
24938
|
+
* Tells whether a display object draws paths, so a `Shape` can be written onto it.
|
|
24939
|
+
*
|
|
24940
|
+
* @param object - The display object.
|
|
24941
|
+
* @returns True for a graphics object.
|
|
24942
|
+
*/
|
|
24943
|
+
function isGraphics(object) {
|
|
24944
|
+
return "roundRect" in object;
|
|
24945
|
+
}
|
|
24946
|
+
/**
|
|
24947
|
+
* Tells whether a display object draws a nine-slice: Pixi's `NineSliceSprite`, or the engine's rim-only mesh of a
|
|
24948
|
+
* `hollow` one. Both carry the slice widths.
|
|
24949
|
+
*
|
|
24950
|
+
* @param object - The display object.
|
|
24951
|
+
* @returns True for either nine-slice object.
|
|
24952
|
+
*/
|
|
24953
|
+
function isNineSlice(object) {
|
|
24954
|
+
return "leftWidth" in object;
|
|
24955
|
+
}
|
|
24956
|
+
/**
|
|
24957
|
+
* Tells whether a display object is the engine's rim-only nine-slice (F59).
|
|
24958
|
+
*
|
|
24959
|
+
* @param object - The display object.
|
|
24960
|
+
* @returns True for the hollow mesh, false for Pixi's `NineSliceSprite` and every other object.
|
|
24961
|
+
*/
|
|
24962
|
+
function isHollow(object) {
|
|
24963
|
+
return "hollow" in object;
|
|
24964
|
+
}
|
|
24965
|
+
/**
|
|
24966
|
+
* Takes a display object out of the Pixi tree. Pixi containers are not DOM nodes, so the DOM
|
|
24967
|
+
* helper the linter suggests does not exist on them.
|
|
24968
|
+
*
|
|
24969
|
+
* @param object - The display object.
|
|
24970
|
+
*/
|
|
24971
|
+
function detach$1(object) {
|
|
24972
|
+
object.parent?.removeChild(object);
|
|
24973
|
+
}
|
|
24974
|
+
/**
|
|
24975
|
+
* Takes the masked container off a view: its children go back to the wrapper, unmasked and in
|
|
24976
|
+
* their order, and the container is freed.
|
|
24977
|
+
*
|
|
24978
|
+
* @param view - The view of a shape that clips, or of anything that does not.
|
|
24979
|
+
*/
|
|
24980
|
+
function dropClipped(view) {
|
|
24981
|
+
const clipped = view.clipped;
|
|
24982
|
+
if (clipped === void 0) return;
|
|
24983
|
+
clipped.mask = null;
|
|
24984
|
+
const wrapper = view.wrapper;
|
|
24985
|
+
if (wrapper !== void 0) for (const child of clipped.children) wrapper.addChild(child);
|
|
24986
|
+
detach$1(clipped);
|
|
24987
|
+
clipped.destroy();
|
|
24988
|
+
view.clipped = void 0;
|
|
24989
|
+
}
|
|
24990
|
+
/**
|
|
24991
|
+
* Takes the clip rectangle off a view: the children stop being masked and the graphics goes.
|
|
24992
|
+
*
|
|
24993
|
+
* @param view - The view of a shape that clips, or of anything that does not.
|
|
24994
|
+
*/
|
|
24995
|
+
function dropMask(view) {
|
|
24996
|
+
dropClipped(view);
|
|
24997
|
+
const mask = view.mask;
|
|
24998
|
+
if (mask === void 0) return;
|
|
24999
|
+
detach$1(mask);
|
|
25000
|
+
mask.destroy();
|
|
25001
|
+
view.mask = void 0;
|
|
25002
|
+
}
|
|
25003
|
+
/**
|
|
25004
|
+
* Takes the debug outline off a view: the graphics leaves the wrapper and is freed.
|
|
25005
|
+
*
|
|
25006
|
+
* @param view - The view of a nine-slice that was outlined, or of anything that was not.
|
|
25007
|
+
*/
|
|
25008
|
+
function dropOutline(view) {
|
|
25009
|
+
const outline = view.outline;
|
|
25010
|
+
if (outline === void 0) return;
|
|
25011
|
+
detach$1(outline);
|
|
25012
|
+
outline.destroy();
|
|
25013
|
+
view.outline = void 0;
|
|
25014
|
+
}
|
|
25015
|
+
/**
|
|
25016
|
+
* The pool an object belongs to: its class and the texture it draws. A rim-only nine-slice has a pool of its own, so
|
|
25017
|
+
* a filled panel never gets a hollow object back, nor the other way round.
|
|
25018
|
+
*
|
|
25019
|
+
* @param kind - Which component gave the entity its display object.
|
|
25020
|
+
* @param textureKey - The asset key.
|
|
25021
|
+
* @param hollow - True for the rim-only object of a `NineSlice` with `hollow: true`.
|
|
25022
|
+
* @returns The pool key.
|
|
25023
|
+
* @example
|
|
25024
|
+
* ```ts
|
|
25025
|
+
* poolKeyOf("NineSlice", "glass.frost", true); // "NineSlice.hollow:glass.frost"
|
|
25026
|
+
* ```
|
|
25027
|
+
*/
|
|
25028
|
+
function poolKeyOf(kind, textureKey, hollow) {
|
|
25029
|
+
return hollow ? `${kind}.hollow:${textureKey}` : `${kind}:${textureKey}`;
|
|
25030
|
+
}
|
|
25031
|
+
/**
|
|
25032
|
+
* Brings the object of a view that left back to the state a fresh one has: detached, drawn, opaque, unmoved, blending
|
|
25033
|
+
* normally, white.
|
|
25034
|
+
* Its hidden reasons go with `clearHidden`, so a pooled object never brings the reason of the view it belonged to; a
|
|
25035
|
+
* sprite and a graphics lose their tint (a fill-only shape is coloured by it, spec 26 R2.1).
|
|
25036
|
+
*
|
|
25037
|
+
* @param view - The view whose object goes back to its pool; its wrapper is gone already.
|
|
25038
|
+
*/
|
|
25039
|
+
function resetObject(view) {
|
|
25040
|
+
detach$1(view.object);
|
|
25041
|
+
clearHidden(view);
|
|
25042
|
+
resetNode(view.object);
|
|
25043
|
+
}
|
|
25044
|
+
/**
|
|
25045
|
+
* Brings a detached object back to a fresh one's pose and look: drawn, opaque, unmoved, blending as its parent (no
|
|
25046
|
+
* glow of the view it drew for), white for a sprite and a graphics, and a graphics without a path.
|
|
25047
|
+
*
|
|
25048
|
+
* @param object - The object, out of the tree.
|
|
25049
|
+
*/
|
|
25050
|
+
function resetNode(object) {
|
|
25051
|
+
object.visible = true;
|
|
25052
|
+
object.alpha = 1;
|
|
25053
|
+
object.blendMode = "inherit";
|
|
25054
|
+
object.rotation = 0;
|
|
25055
|
+
object.zIndex = 0;
|
|
25056
|
+
object.position.set(0, 0);
|
|
25057
|
+
object.pivot.set(0, 0);
|
|
25058
|
+
object.scale.set(1);
|
|
25059
|
+
if (isSprite(object)) object.tint = 16777215;
|
|
25060
|
+
if (isGraphics(object)) {
|
|
25061
|
+
object.tint = 16777215;
|
|
25062
|
+
object.clear();
|
|
25063
|
+
}
|
|
25064
|
+
}
|
|
25065
|
+
/**
|
|
25066
|
+
* Takes an object out of its pool, if one is waiting.
|
|
25067
|
+
*
|
|
25068
|
+
* @param state - The sync branch of the plugin state.
|
|
25069
|
+
* @param poolKey - Class and texture key.
|
|
25070
|
+
* @returns A reusable object, or `undefined`.
|
|
25071
|
+
*/
|
|
25072
|
+
function acquire(state, poolKey) {
|
|
25073
|
+
const object = state.pools.get(poolKey)?.pop();
|
|
25074
|
+
if (object !== void 0) state.pooled -= 1;
|
|
25075
|
+
return object;
|
|
25076
|
+
}
|
|
25077
|
+
/**
|
|
25078
|
+
* Creates the array a pool keeps its objects in. Lint rule L5 refuses a collection built inside
|
|
25079
|
+
* an exported declaration, so it lives here.
|
|
25080
|
+
*
|
|
25081
|
+
* @returns An empty pool.
|
|
25082
|
+
*/
|
|
25083
|
+
function emptyPool() {
|
|
25084
|
+
return [];
|
|
25085
|
+
}
|
|
25086
|
+
/**
|
|
25087
|
+
* The pool that holds the object that has waited longest.
|
|
25088
|
+
*
|
|
25089
|
+
* @param state - The sync branch of the plugin state.
|
|
25090
|
+
* @returns A non-empty pool, or `undefined` when every pool is empty.
|
|
25091
|
+
*/
|
|
25092
|
+
function oldestPool(state) {
|
|
25093
|
+
for (const pool of state.pools.values()) if (pool.length > 0) return pool;
|
|
25094
|
+
}
|
|
25095
|
+
/**
|
|
25096
|
+
* Puts a view's object back into its pool and evicts the oldest one when the pool grew past the
|
|
25097
|
+
* configured limit. A `Display` object belongs to the game and is only detached. The filters and the
|
|
25098
|
+
* filter area the renderer hung on the object or its wrapper come off first, so a pooled object never
|
|
25099
|
+
* carries a filter into its next life; the filter instances themselves belong to `effects`.
|
|
25100
|
+
*
|
|
25101
|
+
* @param sctx - Domain context of the sync module.
|
|
25102
|
+
* @param view - The view that left.
|
|
25103
|
+
*/
|
|
25104
|
+
function release$1(sctx, view) {
|
|
25105
|
+
dropMask(view);
|
|
25106
|
+
dropOutline(view);
|
|
25107
|
+
clearFilters(view.object);
|
|
25108
|
+
clearArea(view.object);
|
|
25109
|
+
if (view.wrapper !== void 0) {
|
|
25110
|
+
clearFilters(view.wrapper);
|
|
25111
|
+
clearArea(view.wrapper);
|
|
25112
|
+
detach$1(view.wrapper);
|
|
25113
|
+
view.wrapper.destroy({ children: false });
|
|
25114
|
+
view.wrapper = void 0;
|
|
25115
|
+
}
|
|
25116
|
+
if (view.kind === "Display") {
|
|
25117
|
+
detach$1(view.object);
|
|
25118
|
+
clearHidden(view);
|
|
25119
|
+
return;
|
|
25120
|
+
}
|
|
25121
|
+
if (view.kind === "adapter") {
|
|
25122
|
+
detach$1(view.object);
|
|
25123
|
+
clearHidden(view);
|
|
25124
|
+
view.display?.adapter.destroy(view.object);
|
|
25125
|
+
return;
|
|
25126
|
+
}
|
|
25127
|
+
dropGroup(sctx, view.object);
|
|
25128
|
+
resetObject(view);
|
|
25129
|
+
store$1(sctx, view.object, view.poolKey);
|
|
25130
|
+
}
|
|
25131
|
+
/**
|
|
25132
|
+
* Puts an object a view no longer draws with back into a pool, while the view goes on with another one: the
|
|
25133
|
+
* nine-slice whose `hollow` flipped (F59). Reset as `release` resets a view's object; its view keeps its hidden
|
|
25134
|
+
* reasons, which hang on the view's wrapper.
|
|
25135
|
+
*
|
|
25136
|
+
* @param sctx - Domain context of the sync module.
|
|
25137
|
+
* @param object - The object, still in the view's wrapper.
|
|
25138
|
+
* @param poolKey - The pool it belongs to.
|
|
25139
|
+
*/
|
|
25140
|
+
function releaseObject(sctx, object, poolKey) {
|
|
25141
|
+
dropGroup(sctx, object);
|
|
25142
|
+
detach$1(object);
|
|
25143
|
+
resetNode(object);
|
|
25144
|
+
store$1(sctx, object, poolKey);
|
|
25145
|
+
}
|
|
25146
|
+
/**
|
|
25147
|
+
* Takes away the render group the kept cache made of an object (spec 23 P9.3), before it is pooled.
|
|
25148
|
+
*
|
|
25149
|
+
* @param sctx - Domain context of the sync module.
|
|
25150
|
+
* @param object - The object.
|
|
25151
|
+
*/
|
|
25152
|
+
function dropGroup(sctx, object) {
|
|
25153
|
+
const groups = sctx.ctx.state.kept.madeGroups;
|
|
25154
|
+
if (!groups.has(object)) return;
|
|
25155
|
+
object.disableRenderGroup();
|
|
25156
|
+
groups.delete(object);
|
|
25157
|
+
}
|
|
25158
|
+
/**
|
|
25159
|
+
* Pushes a reset object into its pool and evicts the one that waited longest while the pools hold more than
|
|
25160
|
+
* `poolLimit`.
|
|
25161
|
+
*
|
|
25162
|
+
* @param sctx - Domain context of the sync module.
|
|
25163
|
+
* @param object - The reset object.
|
|
25164
|
+
* @param poolKey - Its pool.
|
|
25165
|
+
*/
|
|
25166
|
+
function store$1(sctx, object, poolKey) {
|
|
25167
|
+
const state = sctx.ctx.state.sync;
|
|
25168
|
+
const pool = state.pools.get(poolKey) ?? emptyPool();
|
|
25169
|
+
pool.push(object);
|
|
25170
|
+
state.pools.set(poolKey, pool);
|
|
25171
|
+
state.pooled += 1;
|
|
25172
|
+
while (state.pooled > sctx.ctx.config.poolLimit) {
|
|
25173
|
+
const oldest = oldestPool(state);
|
|
25174
|
+
if (oldest === void 0) return;
|
|
25175
|
+
oldest.shift()?.destroy({ texture: false });
|
|
25176
|
+
state.pooled -= 1;
|
|
25177
|
+
}
|
|
25178
|
+
}
|
|
25179
|
+
/**
|
|
25180
|
+
* Destroys the pooled objects that draw one of these keys, so a bundle that left frees its GPU
|
|
25181
|
+
* memory instead of waiting for a reuse that resolves nothing: sprites, nine-slices and rim-only nine-slices.
|
|
25182
|
+
*
|
|
25183
|
+
* @param state - The sync branch of the plugin state.
|
|
25184
|
+
* @param keys - The asset keys that went stale.
|
|
25185
|
+
*/
|
|
25186
|
+
function dropPooled(state, keys) {
|
|
25187
|
+
for (const key of keys) {
|
|
25188
|
+
dropPool(state, poolKeyOf("Sprite", key, false));
|
|
25189
|
+
dropPool(state, poolKeyOf("NineSlice", key, false));
|
|
25190
|
+
dropPool(state, poolKeyOf("NineSlice", key, true));
|
|
25191
|
+
}
|
|
25192
|
+
}
|
|
25193
|
+
/**
|
|
25194
|
+
* Destroys one pool and everything in it.
|
|
25195
|
+
*
|
|
25196
|
+
* @param state - The sync branch of the plugin state.
|
|
25197
|
+
* @param poolKey - The pool.
|
|
25198
|
+
*/
|
|
25199
|
+
function dropPool(state, poolKey) {
|
|
25200
|
+
const pool = state.pools.get(poolKey);
|
|
25201
|
+
if (pool === void 0) return;
|
|
25202
|
+
for (const object of pool) object.destroy({ texture: false });
|
|
25203
|
+
state.pooled -= pool.length;
|
|
25204
|
+
state.pools.delete(poolKey);
|
|
25205
|
+
}
|
|
25206
|
+
/**
|
|
25207
|
+
* Destroys everything the pools hold. Works on the state alone, because `onStop` has no context.
|
|
25208
|
+
*
|
|
25209
|
+
* @param state - The sync branch of the plugin state.
|
|
25210
|
+
*/
|
|
25211
|
+
function destroyPools(state) {
|
|
25212
|
+
for (const pool of state.pools.values()) for (const object of pool) object.destroy({ texture: false });
|
|
25213
|
+
state.pools.clear();
|
|
25214
|
+
state.pooled = 0;
|
|
25215
|
+
}
|
|
25216
|
+
//#endregion
|
|
25217
|
+
//#region src/plugins/renderer/sync/held.ts
|
|
25218
|
+
/**
|
|
25219
|
+
* Tells whether a view is held: its entity belongs to the running build, and it has no parent or its parent is live.
|
|
25220
|
+
* A building view under a building parent hangs in the parent's tree, which is held as a whole. One set lookup for a
|
|
25221
|
+
* live entity.
|
|
25222
|
+
*
|
|
25223
|
+
* @param sctx - Domain context of the sync module.
|
|
25224
|
+
* @param entity - The entity of the view.
|
|
25225
|
+
* @param parent - The entity its `Parent` names, 0 without one.
|
|
25226
|
+
* @returns True when the view must hang nowhere until the commit.
|
|
25227
|
+
*/
|
|
25228
|
+
function holds(sctx, entity, parent) {
|
|
25229
|
+
const projection = sctx.ctx.deps.world.projection;
|
|
25230
|
+
return projection.isBuilding(entity) && (parent === 0 || !projection.isBuilding(parent));
|
|
25231
|
+
}
|
|
25232
|
+
/**
|
|
25233
|
+
* Holds a view of the build: out of the tree, no orphan, listed once in `heldViews`. A top keeps the name of the layer
|
|
25234
|
+
* it will hang in, which the coming scene may declare only after the commit.
|
|
25235
|
+
*
|
|
25236
|
+
* @param sctx - Domain context of the sync module.
|
|
25237
|
+
* @param entity - The entity.
|
|
25238
|
+
* @param view - Its view.
|
|
25239
|
+
* @param layer - The layer it names; `""` for a view whose parent is live.
|
|
25240
|
+
*/
|
|
25241
|
+
function holdView(sctx, entity, view, layer) {
|
|
25242
|
+
const state = sctx.ctx.state.sync;
|
|
25243
|
+
const held = state.heldViews;
|
|
25244
|
+
state.orphans.delete(entity);
|
|
25245
|
+
view.layer = layer;
|
|
25246
|
+
detach$1(view.wrapper ?? view.object);
|
|
25247
|
+
if (view.held) return;
|
|
25248
|
+
view.held = true;
|
|
25249
|
+
held.entities[held.count] = entity;
|
|
25250
|
+
held.count += 1;
|
|
25251
|
+
}
|
|
25252
|
+
/**
|
|
25253
|
+
* Forgets the queues of a scene build with the tree, on a device loss, a rebuild or a stop: the views they name are
|
|
25254
|
+
* gone, and a rebuild holds every building view again. Works on the state alone.
|
|
25255
|
+
*
|
|
25256
|
+
* @param state - The sync branch of the plugin state.
|
|
25257
|
+
*/
|
|
25258
|
+
function forgetBuild(state) {
|
|
25259
|
+
const { aside, heldViews } = state;
|
|
25260
|
+
aside.count = 0;
|
|
25261
|
+
aside.head = 0;
|
|
25262
|
+
aside.made = 0;
|
|
25263
|
+
aside.waiting.clear();
|
|
25264
|
+
heldViews.count = 0;
|
|
25265
|
+
heldViews.reported = false;
|
|
25266
|
+
}
|
|
25267
|
+
//#endregion
|
|
24631
25268
|
//#region src/plugins/renderer/sync/outline.ts
|
|
24632
25269
|
/** Above every child of the wrapper, whatever `Order` a child carries. */
|
|
24633
25270
|
const OUTLINE_Z = Number.MAX_SAFE_INTEGER;
|
|
@@ -25056,293 +25693,6 @@ function labelOf$1(sctx, name, entity) {
|
|
|
25056
25693
|
return key === void 0 ? base : `${base} ${key.projection}:${key.key}`;
|
|
25057
25694
|
}
|
|
25058
25695
|
//#endregion
|
|
25059
|
-
//#region src/plugins/renderer/sync/pools.ts
|
|
25060
|
-
/**
|
|
25061
|
-
* Tells whether a display object is a sprite. A `NineSliceSprite` carries the slice widths and
|
|
25062
|
-
* is recognised by them.
|
|
25063
|
-
*
|
|
25064
|
-
* @param object - The display object.
|
|
25065
|
-
* @returns True for a plain sprite.
|
|
25066
|
-
*/
|
|
25067
|
-
function isSprite(object) {
|
|
25068
|
-
return "texture" in object && !("leftWidth" in object);
|
|
25069
|
-
}
|
|
25070
|
-
/**
|
|
25071
|
-
* Tells whether a display object draws paths, so a `Shape` can be written onto it.
|
|
25072
|
-
*
|
|
25073
|
-
* @param object - The display object.
|
|
25074
|
-
* @returns True for a graphics object.
|
|
25075
|
-
*/
|
|
25076
|
-
function isGraphics(object) {
|
|
25077
|
-
return "roundRect" in object;
|
|
25078
|
-
}
|
|
25079
|
-
/**
|
|
25080
|
-
* Tells whether a display object draws a nine-slice: Pixi's `NineSliceSprite`, or the engine's rim-only mesh of a
|
|
25081
|
-
* `hollow` one. Both carry the slice widths.
|
|
25082
|
-
*
|
|
25083
|
-
* @param object - The display object.
|
|
25084
|
-
* @returns True for either nine-slice object.
|
|
25085
|
-
*/
|
|
25086
|
-
function isNineSlice(object) {
|
|
25087
|
-
return "leftWidth" in object;
|
|
25088
|
-
}
|
|
25089
|
-
/**
|
|
25090
|
-
* Tells whether a display object is the engine's rim-only nine-slice (F59).
|
|
25091
|
-
*
|
|
25092
|
-
* @param object - The display object.
|
|
25093
|
-
* @returns True for the hollow mesh, false for Pixi's `NineSliceSprite` and every other object.
|
|
25094
|
-
*/
|
|
25095
|
-
function isHollow(object) {
|
|
25096
|
-
return "hollow" in object;
|
|
25097
|
-
}
|
|
25098
|
-
/**
|
|
25099
|
-
* Takes a display object out of the Pixi tree. Pixi containers are not DOM nodes, so the DOM
|
|
25100
|
-
* helper the linter suggests does not exist on them.
|
|
25101
|
-
*
|
|
25102
|
-
* @param object - The display object.
|
|
25103
|
-
*/
|
|
25104
|
-
function detach$1(object) {
|
|
25105
|
-
object.parent?.removeChild(object);
|
|
25106
|
-
}
|
|
25107
|
-
/**
|
|
25108
|
-
* Takes the masked container off a view: its children go back to the wrapper, unmasked and in
|
|
25109
|
-
* their order, and the container is freed.
|
|
25110
|
-
*
|
|
25111
|
-
* @param view - The view of a shape that clips, or of anything that does not.
|
|
25112
|
-
*/
|
|
25113
|
-
function dropClipped(view) {
|
|
25114
|
-
const clipped = view.clipped;
|
|
25115
|
-
if (clipped === void 0) return;
|
|
25116
|
-
clipped.mask = null;
|
|
25117
|
-
const wrapper = view.wrapper;
|
|
25118
|
-
if (wrapper !== void 0) for (const child of clipped.children) wrapper.addChild(child);
|
|
25119
|
-
detach$1(clipped);
|
|
25120
|
-
clipped.destroy();
|
|
25121
|
-
view.clipped = void 0;
|
|
25122
|
-
}
|
|
25123
|
-
/**
|
|
25124
|
-
* Takes the clip rectangle off a view: the children stop being masked and the graphics goes.
|
|
25125
|
-
*
|
|
25126
|
-
* @param view - The view of a shape that clips, or of anything that does not.
|
|
25127
|
-
*/
|
|
25128
|
-
function dropMask(view) {
|
|
25129
|
-
dropClipped(view);
|
|
25130
|
-
const mask = view.mask;
|
|
25131
|
-
if (mask === void 0) return;
|
|
25132
|
-
detach$1(mask);
|
|
25133
|
-
mask.destroy();
|
|
25134
|
-
view.mask = void 0;
|
|
25135
|
-
}
|
|
25136
|
-
/**
|
|
25137
|
-
* Takes the debug outline off a view: the graphics leaves the wrapper and is freed.
|
|
25138
|
-
*
|
|
25139
|
-
* @param view - The view of a nine-slice that was outlined, or of anything that was not.
|
|
25140
|
-
*/
|
|
25141
|
-
function dropOutline(view) {
|
|
25142
|
-
const outline = view.outline;
|
|
25143
|
-
if (outline === void 0) return;
|
|
25144
|
-
detach$1(outline);
|
|
25145
|
-
outline.destroy();
|
|
25146
|
-
view.outline = void 0;
|
|
25147
|
-
}
|
|
25148
|
-
/**
|
|
25149
|
-
* The pool an object belongs to: its class and the texture it draws. A rim-only nine-slice has a pool of its own, so
|
|
25150
|
-
* a filled panel never gets a hollow object back, nor the other way round.
|
|
25151
|
-
*
|
|
25152
|
-
* @param kind - Which component gave the entity its display object.
|
|
25153
|
-
* @param textureKey - The asset key.
|
|
25154
|
-
* @param hollow - True for the rim-only object of a `NineSlice` with `hollow: true`.
|
|
25155
|
-
* @returns The pool key.
|
|
25156
|
-
* @example
|
|
25157
|
-
* ```ts
|
|
25158
|
-
* poolKeyOf("NineSlice", "glass.frost", true); // "NineSlice.hollow:glass.frost"
|
|
25159
|
-
* ```
|
|
25160
|
-
*/
|
|
25161
|
-
function poolKeyOf(kind, textureKey, hollow) {
|
|
25162
|
-
return hollow ? `${kind}.hollow:${textureKey}` : `${kind}:${textureKey}`;
|
|
25163
|
-
}
|
|
25164
|
-
/**
|
|
25165
|
-
* Brings the object of a view that left back to the state a fresh one has: detached, drawn, opaque, unmoved, blending
|
|
25166
|
-
* normally, white.
|
|
25167
|
-
* Its hidden reasons go with `clearHidden`, so a pooled object never brings the reason of the view it belonged to; a
|
|
25168
|
-
* sprite and a graphics lose their tint (a fill-only shape is coloured by it, spec 26 R2.1).
|
|
25169
|
-
*
|
|
25170
|
-
* @param view - The view whose object goes back to its pool; its wrapper is gone already.
|
|
25171
|
-
*/
|
|
25172
|
-
function resetObject(view) {
|
|
25173
|
-
detach$1(view.object);
|
|
25174
|
-
clearHidden(view);
|
|
25175
|
-
resetNode(view.object);
|
|
25176
|
-
}
|
|
25177
|
-
/**
|
|
25178
|
-
* Brings a detached object back to a fresh one's pose and look: drawn, opaque, unmoved, blending as its parent (no
|
|
25179
|
-
* glow of the view it drew for), white for a sprite and a graphics.
|
|
25180
|
-
*
|
|
25181
|
-
* @param object - The object, out of the tree.
|
|
25182
|
-
*/
|
|
25183
|
-
function resetNode(object) {
|
|
25184
|
-
object.visible = true;
|
|
25185
|
-
object.alpha = 1;
|
|
25186
|
-
object.blendMode = "inherit";
|
|
25187
|
-
object.rotation = 0;
|
|
25188
|
-
object.zIndex = 0;
|
|
25189
|
-
object.position.set(0, 0);
|
|
25190
|
-
object.pivot.set(0, 0);
|
|
25191
|
-
object.scale.set(1);
|
|
25192
|
-
if (isSprite(object) || isGraphics(object)) object.tint = 16777215;
|
|
25193
|
-
}
|
|
25194
|
-
/**
|
|
25195
|
-
* Takes an object out of its pool, if one is waiting.
|
|
25196
|
-
*
|
|
25197
|
-
* @param state - The sync branch of the plugin state.
|
|
25198
|
-
* @param poolKey - Class and texture key.
|
|
25199
|
-
* @returns A reusable object, or `undefined`.
|
|
25200
|
-
*/
|
|
25201
|
-
function acquire(state, poolKey) {
|
|
25202
|
-
const object = state.pools.get(poolKey)?.pop();
|
|
25203
|
-
if (object !== void 0) state.pooled -= 1;
|
|
25204
|
-
return object;
|
|
25205
|
-
}
|
|
25206
|
-
/**
|
|
25207
|
-
* Creates the array a pool keeps its objects in. Lint rule L5 refuses a collection built inside
|
|
25208
|
-
* an exported declaration, so it lives here.
|
|
25209
|
-
*
|
|
25210
|
-
* @returns An empty pool.
|
|
25211
|
-
*/
|
|
25212
|
-
function emptyPool() {
|
|
25213
|
-
return [];
|
|
25214
|
-
}
|
|
25215
|
-
/**
|
|
25216
|
-
* The pool that holds the object that has waited longest.
|
|
25217
|
-
*
|
|
25218
|
-
* @param state - The sync branch of the plugin state.
|
|
25219
|
-
* @returns A non-empty pool, or `undefined` when every pool is empty.
|
|
25220
|
-
*/
|
|
25221
|
-
function oldestPool(state) {
|
|
25222
|
-
for (const pool of state.pools.values()) if (pool.length > 0) return pool;
|
|
25223
|
-
}
|
|
25224
|
-
/**
|
|
25225
|
-
* Puts a view's object back into its pool and evicts the oldest one when the pool grew past the
|
|
25226
|
-
* configured limit. A `Display` object belongs to the game and is only detached. The filters and the
|
|
25227
|
-
* filter area the renderer hung on the object or its wrapper come off first, so a pooled object never
|
|
25228
|
-
* carries a filter into its next life; the filter instances themselves belong to `effects`.
|
|
25229
|
-
*
|
|
25230
|
-
* @param sctx - Domain context of the sync module.
|
|
25231
|
-
* @param view - The view that left.
|
|
25232
|
-
*/
|
|
25233
|
-
function release$1(sctx, view) {
|
|
25234
|
-
dropMask(view);
|
|
25235
|
-
dropOutline(view);
|
|
25236
|
-
clearFilters(view.object);
|
|
25237
|
-
clearArea(view.object);
|
|
25238
|
-
if (view.wrapper !== void 0) {
|
|
25239
|
-
clearFilters(view.wrapper);
|
|
25240
|
-
clearArea(view.wrapper);
|
|
25241
|
-
detach$1(view.wrapper);
|
|
25242
|
-
view.wrapper.destroy({ children: false });
|
|
25243
|
-
view.wrapper = void 0;
|
|
25244
|
-
}
|
|
25245
|
-
if (view.kind === "Display") {
|
|
25246
|
-
detach$1(view.object);
|
|
25247
|
-
clearHidden(view);
|
|
25248
|
-
return;
|
|
25249
|
-
}
|
|
25250
|
-
if (view.kind === "adapter") {
|
|
25251
|
-
detach$1(view.object);
|
|
25252
|
-
clearHidden(view);
|
|
25253
|
-
view.display?.adapter.destroy(view.object);
|
|
25254
|
-
return;
|
|
25255
|
-
}
|
|
25256
|
-
dropGroup(sctx, view.object);
|
|
25257
|
-
resetObject(view);
|
|
25258
|
-
store$1(sctx, view.object, view.poolKey);
|
|
25259
|
-
}
|
|
25260
|
-
/**
|
|
25261
|
-
* Puts an object a view no longer draws with back into a pool, while the view goes on with another one: the
|
|
25262
|
-
* nine-slice whose `hollow` flipped (F59). Reset as `release` resets a view's object; its view keeps its hidden
|
|
25263
|
-
* reasons, which hang on the view's wrapper.
|
|
25264
|
-
*
|
|
25265
|
-
* @param sctx - Domain context of the sync module.
|
|
25266
|
-
* @param object - The object, still in the view's wrapper.
|
|
25267
|
-
* @param poolKey - The pool it belongs to.
|
|
25268
|
-
*/
|
|
25269
|
-
function releaseObject(sctx, object, poolKey) {
|
|
25270
|
-
dropGroup(sctx, object);
|
|
25271
|
-
detach$1(object);
|
|
25272
|
-
resetNode(object);
|
|
25273
|
-
store$1(sctx, object, poolKey);
|
|
25274
|
-
}
|
|
25275
|
-
/**
|
|
25276
|
-
* Takes away the render group the kept cache made of an object (spec 23 P9.3), before it is pooled.
|
|
25277
|
-
*
|
|
25278
|
-
* @param sctx - Domain context of the sync module.
|
|
25279
|
-
* @param object - The object.
|
|
25280
|
-
*/
|
|
25281
|
-
function dropGroup(sctx, object) {
|
|
25282
|
-
const groups = sctx.ctx.state.kept.madeGroups;
|
|
25283
|
-
if (!groups.has(object)) return;
|
|
25284
|
-
object.disableRenderGroup();
|
|
25285
|
-
groups.delete(object);
|
|
25286
|
-
}
|
|
25287
|
-
/**
|
|
25288
|
-
* Pushes a reset object into its pool and evicts the one that waited longest while the pools hold more than
|
|
25289
|
-
* `poolLimit`.
|
|
25290
|
-
*
|
|
25291
|
-
* @param sctx - Domain context of the sync module.
|
|
25292
|
-
* @param object - The reset object.
|
|
25293
|
-
* @param poolKey - Its pool.
|
|
25294
|
-
*/
|
|
25295
|
-
function store$1(sctx, object, poolKey) {
|
|
25296
|
-
const state = sctx.ctx.state.sync;
|
|
25297
|
-
const pool = state.pools.get(poolKey) ?? emptyPool();
|
|
25298
|
-
pool.push(object);
|
|
25299
|
-
state.pools.set(poolKey, pool);
|
|
25300
|
-
state.pooled += 1;
|
|
25301
|
-
while (state.pooled > sctx.ctx.config.poolLimit) {
|
|
25302
|
-
const oldest = oldestPool(state);
|
|
25303
|
-
if (oldest === void 0) return;
|
|
25304
|
-
oldest.shift()?.destroy({ texture: false });
|
|
25305
|
-
state.pooled -= 1;
|
|
25306
|
-
}
|
|
25307
|
-
}
|
|
25308
|
-
/**
|
|
25309
|
-
* Destroys the pooled objects that draw one of these keys, so a bundle that left frees its GPU
|
|
25310
|
-
* memory instead of waiting for a reuse that resolves nothing: sprites, nine-slices and rim-only nine-slices.
|
|
25311
|
-
*
|
|
25312
|
-
* @param state - The sync branch of the plugin state.
|
|
25313
|
-
* @param keys - The asset keys that went stale.
|
|
25314
|
-
*/
|
|
25315
|
-
function dropPooled(state, keys) {
|
|
25316
|
-
for (const key of keys) {
|
|
25317
|
-
dropPool(state, poolKeyOf("Sprite", key, false));
|
|
25318
|
-
dropPool(state, poolKeyOf("NineSlice", key, false));
|
|
25319
|
-
dropPool(state, poolKeyOf("NineSlice", key, true));
|
|
25320
|
-
}
|
|
25321
|
-
}
|
|
25322
|
-
/**
|
|
25323
|
-
* Destroys one pool and everything in it.
|
|
25324
|
-
*
|
|
25325
|
-
* @param state - The sync branch of the plugin state.
|
|
25326
|
-
* @param poolKey - The pool.
|
|
25327
|
-
*/
|
|
25328
|
-
function dropPool(state, poolKey) {
|
|
25329
|
-
const pool = state.pools.get(poolKey);
|
|
25330
|
-
if (pool === void 0) return;
|
|
25331
|
-
for (const object of pool) object.destroy({ texture: false });
|
|
25332
|
-
state.pooled -= pool.length;
|
|
25333
|
-
state.pools.delete(poolKey);
|
|
25334
|
-
}
|
|
25335
|
-
/**
|
|
25336
|
-
* Destroys everything the pools hold. Works on the state alone, because `onStop` has no context.
|
|
25337
|
-
*
|
|
25338
|
-
* @param state - The sync branch of the plugin state.
|
|
25339
|
-
*/
|
|
25340
|
-
function destroyPools(state) {
|
|
25341
|
-
for (const pool of state.pools.values()) for (const object of pool) object.destroy({ texture: false });
|
|
25342
|
-
state.pools.clear();
|
|
25343
|
-
state.pooled = 0;
|
|
25344
|
-
}
|
|
25345
|
-
//#endregion
|
|
25346
25696
|
//#region src/plugins/renderer/sync/layers.ts
|
|
25347
25697
|
/**
|
|
25348
25698
|
* @file renderer/sync — the layers. `scenes` declares them through `world.projection.setLayers`;
|
|
@@ -28047,7 +28397,9 @@ function keepOrphan(sctx, entity, view, parentEntity) {
|
|
|
28047
28397
|
/**
|
|
28048
28398
|
* Hangs a view where it belongs: under its parent, or in the container of the layer it names.
|
|
28049
28399
|
* An entity with no layer and no parent is not drawn. A view whose parent has no view yet waits
|
|
28050
|
-
* as an orphan, detached, so it is never drawn in a layer while its parent waits.
|
|
28400
|
+
* as an orphan, detached, so it is never drawn in a layer while its parent waits. A view of a scene
|
|
28401
|
+
* build with no parent, or with a live parent, is held: it hangs nowhere until the commit (spec 31),
|
|
28402
|
+
* and no wrapper is made on the live parent before it.
|
|
28051
28403
|
*
|
|
28052
28404
|
* @param sctx - Domain context of the sync module.
|
|
28053
28405
|
* @param entity - The entity.
|
|
@@ -28058,6 +28410,11 @@ function attach$1(sctx, entity, view) {
|
|
|
28058
28410
|
const parentEntity = parentOf$1(sctx.ctx.deps.world.ecs, entity);
|
|
28059
28411
|
markView(sctx, entity);
|
|
28060
28412
|
noteLeaving(state, view);
|
|
28413
|
+
if (holds(sctx, entity, parentEntity)) {
|
|
28414
|
+
holdView(sctx, entity, view, parentEntity === 0 ? layerNameOf(sctx, entity) : "");
|
|
28415
|
+
return;
|
|
28416
|
+
}
|
|
28417
|
+
if (view.held) view.held = false;
|
|
28061
28418
|
if (parentEntity !== 0) {
|
|
28062
28419
|
const host = childHostOf(sctx, parentEntity);
|
|
28063
28420
|
view.layer = "";
|
|
@@ -28185,7 +28542,8 @@ function createView(sctx, entity) {
|
|
|
28185
28542
|
clipDrawn: void 0,
|
|
28186
28543
|
hiddenSelf: 0,
|
|
28187
28544
|
hiddenTop: 0,
|
|
28188
|
-
hud: 1
|
|
28545
|
+
hud: 1,
|
|
28546
|
+
held: false
|
|
28189
28547
|
};
|
|
28190
28548
|
object.label = labelOf$1(sctx, nameOf$1(visual), entity);
|
|
28191
28549
|
state.views.set(entity, view);
|
|
@@ -28286,10 +28644,10 @@ function dropView(sctx, entity) {
|
|
|
28286
28644
|
}
|
|
28287
28645
|
/**
|
|
28288
28646
|
* Drops every view, every layer, every pool, every filter slot and filter area, the warm-up (its waiting promises
|
|
28289
|
-
* answer false, its probe tree is destroyed)
|
|
28290
|
-
* alone,
|
|
28291
|
-
*
|
|
28292
|
-
*
|
|
28647
|
+
* answer false, its probe tree is destroyed), the upload queue (its waiting calls answer false) and the queues of a
|
|
28648
|
+
* scene build. Works on the state alone, because `onStop` has no context. A `Display` object belongs to the game: it
|
|
28649
|
+
* is detached, never destroyed, and leaves without the filters, the filter area and the hidden reasons the renderer
|
|
28650
|
+
* gave it.
|
|
28293
28651
|
*
|
|
28294
28652
|
* @param state - The sync branch of the plugin state.
|
|
28295
28653
|
*/
|
|
@@ -28335,6 +28693,7 @@ function stopSync(state) {
|
|
|
28335
28693
|
stopWarm(state);
|
|
28336
28694
|
stopUploads(state);
|
|
28337
28695
|
state.held.clear();
|
|
28696
|
+
forgetBuild(state);
|
|
28338
28697
|
state.redraws = 0;
|
|
28339
28698
|
state.overdraw.listeners.length = 0;
|
|
28340
28699
|
clearLayers(state);
|
|
@@ -28478,8 +28837,7 @@ function withDeps$8(ctx) {
|
|
|
28478
28837
|
* overlay step (spec 22), then `kept` reads the marks before `demand` decides whether the frame is
|
|
28479
28838
|
* drawn and clears them; a drawn frame draws the probes of the warm-up onto the canvas first (F4: the
|
|
28480
28839
|
* stage pass clears them, in the same task, so they never show), then uploads the queued texture sources (F60), then
|
|
28481
|
-
* the kept pictures
|
|
28482
|
-
* (`kept.flush`, so the canvas stays one pass, E7), then the stage, then the pixel leg of the kept
|
|
28840
|
+
* the kept pictures (`kept.flush`, so the canvas stays one pass, E7), then the stage, then the pixel leg of the kept
|
|
28483
28841
|
* oracle; `demand` settles the
|
|
28484
28842
|
* frame (the dev check, the pixel oracle), and `monitor` closes it and hands a drawn one to a
|
|
28485
28843
|
* waiting capture; last, `host` trims Pixi's texture pool when a rest or a scene change is due. The display objects
|
|
@@ -28490,7 +28848,8 @@ function withDeps$8(ctx) {
|
|
|
28490
28848
|
* last picture (WebGPU presents a canvas texture only when one is rendered; a WebGL canvas keeps
|
|
28491
28849
|
* what it composited until the next draw). A drawn frame is reported to `time` for the adaptive
|
|
28492
28850
|
* frame rate, and only when Pixi drew it: an inert or lost host reports none. A frame that also drew
|
|
28493
|
-
* probes is reported as probed: it
|
|
28851
|
+
* probes, uploaded sources or made views of the next scene aside is reported as probed: it did load work, so it is
|
|
28852
|
+
* never a sample of the rate.
|
|
28494
28853
|
*
|
|
28495
28854
|
* @param ctx - Domain context of the renderer plugin.
|
|
28496
28855
|
* @param modules - The six modules.
|
|
@@ -28514,10 +28873,11 @@ function renderFrame(ctx, modules) {
|
|
|
28514
28873
|
if (drawn) {
|
|
28515
28874
|
const probed = modules.sync.warmStep();
|
|
28516
28875
|
const uploaded = modules.sync.uploadStep();
|
|
28876
|
+
const built = modules.sync.takeBuildWork();
|
|
28517
28877
|
modules.kept.flush();
|
|
28518
28878
|
if (overdraw ? modules.sync.drawOverdraw(modules.host.render) : modules.host.render()) {
|
|
28519
28879
|
ctx.deps.time.frameDrawn();
|
|
28520
|
-
if (probed || uploaded) ctx.deps.time.frameProbed();
|
|
28880
|
+
if (probed || uploaded || built) ctx.deps.time.frameProbed();
|
|
28521
28881
|
}
|
|
28522
28882
|
modules.kept.after();
|
|
28523
28883
|
}
|
|
@@ -29320,6 +29680,233 @@ function layerOf(sctx, entity) {
|
|
|
29320
29680
|
return ecs.get(top, Layer)?.name;
|
|
29321
29681
|
}
|
|
29322
29682
|
//#endregion
|
|
29683
|
+
//#region src/plugins/renderer/sync/build.ts
|
|
29684
|
+
/** Views made between two reads of the clock: a read costs more than a small view. */
|
|
29685
|
+
const CLOCK_EVERY = 8;
|
|
29686
|
+
/**
|
|
29687
|
+
* Tells whether a scene is being built: `left()` answers a finite number only while a build runs.
|
|
29688
|
+
*
|
|
29689
|
+
* @param sctx - Domain context of the sync module.
|
|
29690
|
+
* @returns True while a build runs.
|
|
29691
|
+
*/
|
|
29692
|
+
function buildRuns(sctx) {
|
|
29693
|
+
return sctx.ctx.deps.world.projection.left() !== Number.POSITIVE_INFINITY;
|
|
29694
|
+
}
|
|
29695
|
+
/**
|
|
29696
|
+
* Queues one building entity for its view, once.
|
|
29697
|
+
*
|
|
29698
|
+
* @param aside - The aside queue.
|
|
29699
|
+
* @param entity - The entity.
|
|
29700
|
+
*/
|
|
29701
|
+
function pushAside(aside, entity) {
|
|
29702
|
+
if (aside.waiting.has(entity)) return;
|
|
29703
|
+
aside.waiting.add(entity);
|
|
29704
|
+
aside.entities[aside.count] = entity;
|
|
29705
|
+
aside.count += 1;
|
|
29706
|
+
}
|
|
29707
|
+
/**
|
|
29708
|
+
* Moves the building entities of the pass's added set into the aside queue, in their order: the live pass builds the
|
|
29709
|
+
* rest. One set lookup per added entity; called only while a build runs.
|
|
29710
|
+
*
|
|
29711
|
+
* @param sctx - Domain context of the sync module.
|
|
29712
|
+
* @param taken - The entities the pass took out of `added`; the building ones leave it.
|
|
29713
|
+
*/
|
|
29714
|
+
function sortAside(sctx, taken) {
|
|
29715
|
+
const projection = sctx.ctx.deps.world.projection;
|
|
29716
|
+
const aside = sctx.ctx.state.sync.aside;
|
|
29717
|
+
for (const entity of taken) {
|
|
29718
|
+
if (!projection.isBuilding(entity)) continue;
|
|
29719
|
+
taken.delete(entity);
|
|
29720
|
+
pushAside(aside, entity);
|
|
29721
|
+
}
|
|
29722
|
+
}
|
|
29723
|
+
/**
|
|
29724
|
+
* Forgets a queued entity that left before its view was made: the queue passes over it.
|
|
29725
|
+
*
|
|
29726
|
+
* @param state - The sync branch of the plugin state.
|
|
29727
|
+
* @param entity - The entity that left.
|
|
29728
|
+
*/
|
|
29729
|
+
function dropAside(state, entity) {
|
|
29730
|
+
if (state.aside.count > 0) state.aside.waiting.delete(entity);
|
|
29731
|
+
}
|
|
29732
|
+
/**
|
|
29733
|
+
* Makes the view of one queued entity, its queued parent first (spec 16 U2.3: a child made before its parent finds no
|
|
29734
|
+
* parent view and is detached for good). The entity leaves the waiting set before its parent is made, so a cycle of
|
|
29735
|
+
* parents ends. A throw costs that entity.
|
|
29736
|
+
*
|
|
29737
|
+
* @param sctx - Domain context of the sync module.
|
|
29738
|
+
* @param entity - The entity.
|
|
29739
|
+
* @param report - Logs a throw of one entity's work.
|
|
29740
|
+
* @returns The views made.
|
|
29741
|
+
*/
|
|
29742
|
+
function makeParentFirst(sctx, entity, report) {
|
|
29743
|
+
const waiting = sctx.ctx.state.sync.aside.waiting;
|
|
29744
|
+
if (!waiting.delete(entity)) return 0;
|
|
29745
|
+
const parent = parentOf$1(sctx.ctx.deps.world.ecs, entity);
|
|
29746
|
+
let made = parent !== 0 && waiting.has(parent) ? makeParentFirst(sctx, parent, report) : 0;
|
|
29747
|
+
try {
|
|
29748
|
+
if (createView(sctx, entity)) made += 1;
|
|
29749
|
+
} catch (error) {
|
|
29750
|
+
report(entity, error);
|
|
29751
|
+
}
|
|
29752
|
+
return made;
|
|
29753
|
+
}
|
|
29754
|
+
/**
|
|
29755
|
+
* Makes the views of the aside queue while the frame has budget: at least one, the clock read after the first and then
|
|
29756
|
+
* every `CLOCK_EVERY` views. An entity that is no longer building but still alive (its build ended before its view was
|
|
29757
|
+
* made) goes back to `added`, for the live pass. The made views hang nowhere in the stage, so the version the live
|
|
29758
|
+
* tree is counted at stays as it was.
|
|
29759
|
+
*
|
|
29760
|
+
* @param sctx - Domain context of the sync module.
|
|
29761
|
+
* @param report - Logs a throw of one entity's work.
|
|
29762
|
+
* @returns The views made.
|
|
29763
|
+
*/
|
|
29764
|
+
function makeAside(sctx, report) {
|
|
29765
|
+
const state = sctx.ctx.state.sync;
|
|
29766
|
+
const { aside } = state;
|
|
29767
|
+
const { projection, ecs } = sctx.ctx.deps.world;
|
|
29768
|
+
const version = state.passesVersion;
|
|
29769
|
+
let made = 0;
|
|
29770
|
+
let check = 1;
|
|
29771
|
+
while (aside.head < aside.count) {
|
|
29772
|
+
const entity = aside.entities[aside.head] ?? 0;
|
|
29773
|
+
aside.head += 1;
|
|
29774
|
+
if (!aside.waiting.has(entity)) continue;
|
|
29775
|
+
if (!projection.isBuilding(entity)) {
|
|
29776
|
+
aside.waiting.delete(entity);
|
|
29777
|
+
if (ecs.ownerOf(entity) !== void 0) state.added.add(entity);
|
|
29778
|
+
continue;
|
|
29779
|
+
}
|
|
29780
|
+
made += makeParentFirst(sctx, entity, report);
|
|
29781
|
+
if (made >= check) {
|
|
29782
|
+
if (projection.left() <= 0) break;
|
|
29783
|
+
check = made + CLOCK_EVERY;
|
|
29784
|
+
}
|
|
29785
|
+
}
|
|
29786
|
+
if (aside.head >= aside.count) {
|
|
29787
|
+
aside.head = 0;
|
|
29788
|
+
aside.count = 0;
|
|
29789
|
+
}
|
|
29790
|
+
state.passesVersion = version;
|
|
29791
|
+
return made;
|
|
29792
|
+
}
|
|
29793
|
+
/**
|
|
29794
|
+
* Tells whether a node hangs under the stage.
|
|
29795
|
+
*
|
|
29796
|
+
* @param node - The node.
|
|
29797
|
+
* @param stage - The stage.
|
|
29798
|
+
* @returns True when the stage is one of its ancestors.
|
|
29799
|
+
*/
|
|
29800
|
+
function underStage(node, stage) {
|
|
29801
|
+
for (let parent = node.parent; parent !== null; parent = parent.parent) if (parent === stage) return true;
|
|
29802
|
+
return false;
|
|
29803
|
+
}
|
|
29804
|
+
/**
|
|
29805
|
+
* The dev check of a build frame: no view of a building entity hangs under the stage, or it would be drawn and hit
|
|
29806
|
+
* before the commit. Named once per build, as `renderer:building-drawn` with the entities. Dev and agent builds only.
|
|
29807
|
+
*
|
|
29808
|
+
* @param sctx - Domain context of the sync module.
|
|
29809
|
+
*/
|
|
29810
|
+
function checkDrawn(sctx) {
|
|
29811
|
+
const state = sctx.ctx.state.sync;
|
|
29812
|
+
const stage = sctx.deps.host.stage();
|
|
29813
|
+
if (state.heldViews.reported || stage === void 0) return;
|
|
29814
|
+
const projection = sctx.ctx.deps.world.projection;
|
|
29815
|
+
const drawn = [];
|
|
29816
|
+
for (const [entity, view] of state.views) if (projection.isBuilding(entity) && underStage(view.wrapper ?? view.object, stage)) drawn.push(entity);
|
|
29817
|
+
if (drawn.length === 0) return;
|
|
29818
|
+
state.heldViews.reported = true;
|
|
29819
|
+
sctx.ctx.log.warn("renderer:building-drawn", { entities: drawn });
|
|
29820
|
+
}
|
|
29821
|
+
/**
|
|
29822
|
+
* The building step of a frame, after the live pass: the views of the aside queue are made under the budget, and while
|
|
29823
|
+
* views wait to be made or attached, the next frame is asked for. A few field reads and no allocation with no build.
|
|
29824
|
+
*
|
|
29825
|
+
* @param sctx - Domain context of the sync module.
|
|
29826
|
+
* @param report - Logs a throw of one entity's work.
|
|
29827
|
+
*/
|
|
29828
|
+
function stepAside(sctx, report) {
|
|
29829
|
+
const state = sctx.ctx.state.sync;
|
|
29830
|
+
const { aside } = state;
|
|
29831
|
+
if (aside.made !== 0) aside.made = 0;
|
|
29832
|
+
if (aside.head >= aside.count && state.heldViews.count === 0) return;
|
|
29833
|
+
if (aside.head < aside.count) aside.made = makeAside(sctx, report);
|
|
29834
|
+
sctx.deps.demand.request();
|
|
29835
|
+
if (typeof __MOKU_GAME_DEV__ !== "undefined" && __MOKU_GAME_DEV__ || typeof __MOKU_GAME_AGENT__ !== "undefined" && __MOKU_GAME_AGENT__) checkDrawn(sctx);
|
|
29836
|
+
}
|
|
29837
|
+
/**
|
|
29838
|
+
* Tells whether held views wait for the pass that attaches them: the build their first slot belongs to has ended (a
|
|
29839
|
+
* commit, or a discard whose views the pass drops). Read by `still()`: a field read with no build, one set lookup
|
|
29840
|
+
* during one.
|
|
29841
|
+
*
|
|
29842
|
+
* @param sctx - Domain context of the sync module.
|
|
29843
|
+
* @returns True when the pass has held views to attach or forget.
|
|
29844
|
+
*/
|
|
29845
|
+
function heldWaiting(sctx) {
|
|
29846
|
+
const held = sctx.ctx.state.sync.heldViews;
|
|
29847
|
+
return held.count > 0 && !sctx.ctx.deps.world.projection.isBuilding(held.entities[0] ?? 0);
|
|
29848
|
+
}
|
|
29849
|
+
/**
|
|
29850
|
+
* The swap: once the build of the held views ended, every held top is attached in one block, in the order it was made
|
|
29851
|
+
* (parents first), and sorted by the rule of the layer list the pass has just applied. A slot whose view went (a
|
|
29852
|
+
* discard, a view that left) attaches nothing. The list holds the views of one build only: a build that starts after
|
|
29853
|
+
* the commit makes its views after this step. Called by the pass right after `syncLayers`.
|
|
29854
|
+
*
|
|
29855
|
+
* @param sctx - Domain context of the sync module.
|
|
29856
|
+
*/
|
|
29857
|
+
function attachHeld(sctx) {
|
|
29858
|
+
if (!heldWaiting(sctx)) return;
|
|
29859
|
+
const state = sctx.ctx.state.sync;
|
|
29860
|
+
const held = state.heldViews;
|
|
29861
|
+
for (let index = 0; index < held.count; index += 1) {
|
|
29862
|
+
const entity = held.entities[index] ?? 0;
|
|
29863
|
+
const view = state.views.get(entity);
|
|
29864
|
+
if (view === void 0 || !view.held) continue;
|
|
29865
|
+
view.held = false;
|
|
29866
|
+
attach$1(sctx, entity, view);
|
|
29867
|
+
resort(sctx, entity, view);
|
|
29868
|
+
}
|
|
29869
|
+
held.count = 0;
|
|
29870
|
+
held.reported = false;
|
|
29871
|
+
}
|
|
29872
|
+
/**
|
|
29873
|
+
* Tells whether an entity of a set belongs to the running build.
|
|
29874
|
+
*
|
|
29875
|
+
* @param sctx - Domain context of the sync module.
|
|
29876
|
+
* @param entities - The set.
|
|
29877
|
+
* @returns True when one does.
|
|
29878
|
+
*/
|
|
29879
|
+
function holdsBuilding(sctx, entities) {
|
|
29880
|
+
const projection = sctx.ctx.deps.world.projection;
|
|
29881
|
+
for (const entity of entities) if (projection.isBuilding(entity)) return true;
|
|
29882
|
+
return false;
|
|
29883
|
+
}
|
|
29884
|
+
/**
|
|
29885
|
+
* The renderer's part of `world.projection.built()`, registered with `onBuild`: no building entity waits in `added`
|
|
29886
|
+
* or in the aside queue for its view. True while the renderer draws nothing.
|
|
29887
|
+
*
|
|
29888
|
+
* @param sctx - Domain context of the sync module.
|
|
29889
|
+
* @returns True when the renderer holds the swap back no longer.
|
|
29890
|
+
*/
|
|
29891
|
+
function buildReady(sctx) {
|
|
29892
|
+
const state = sctx.ctx.state.sync;
|
|
29893
|
+
if (state.root === void 0) return true;
|
|
29894
|
+
if (state.aside.head < state.aside.count) return false;
|
|
29895
|
+
return state.added.size === 0 || !holdsBuilding(sctx, state.added);
|
|
29896
|
+
}
|
|
29897
|
+
/**
|
|
29898
|
+
* The render phase's half of a scene build (the body of `SyncInternal.takeBuildWork`): reads and clears the count of the
|
|
29899
|
+
* views the pass made aside in this frame. A field read and no allocation.
|
|
29900
|
+
*
|
|
29901
|
+
* @param aside - The aside queue.
|
|
29902
|
+
* @returns True when the pass made views of the build: the frame did load work.
|
|
29903
|
+
*/
|
|
29904
|
+
function takeBuildWork(aside) {
|
|
29905
|
+
const made = aside.made > 0;
|
|
29906
|
+
aside.made = 0;
|
|
29907
|
+
return made;
|
|
29908
|
+
}
|
|
29909
|
+
//#endregion
|
|
29323
29910
|
//#region src/plugins/renderer/sync/each.ts
|
|
29324
29911
|
/**
|
|
29325
29912
|
* Tells whether a change list is an array.
|
|
@@ -30212,7 +30799,7 @@ function expectedHostOf(sctx, entity, parent) {
|
|
|
30212
30799
|
}
|
|
30213
30800
|
/**
|
|
30214
30801
|
* Checks where the views of the given entities hang and reports every one that hangs elsewhere than the ECS says,
|
|
30215
|
-
* once per entity. An orphan
|
|
30802
|
+
* once per entity. An orphan and a held view of a scene build (spec 31) hang nowhere on purpose and are skipped.
|
|
30216
30803
|
*
|
|
30217
30804
|
* @param sctx - Domain context of the sync module.
|
|
30218
30805
|
* @param entities - The entities a pass built, hung again or took out of the orphans.
|
|
@@ -30222,7 +30809,7 @@ function checkTree(sctx, entities) {
|
|
|
30222
30809
|
const ecs = sctx.ctx.deps.world.ecs;
|
|
30223
30810
|
for (const entity of entities) {
|
|
30224
30811
|
const view = state.views.get(entity);
|
|
30225
|
-
if (view === void 0 || state.orphans.has(entity) || state.mismatched.has(entity)) continue;
|
|
30812
|
+
if (view === void 0 || view.held || state.orphans.has(entity) || state.mismatched.has(entity)) continue;
|
|
30226
30813
|
const parent = parentOf$1(ecs, entity);
|
|
30227
30814
|
const host = (view.wrapper ?? view.object).parent;
|
|
30228
30815
|
const expected = expectedHostOf(sctx, entity, parent);
|
|
@@ -30497,12 +31084,13 @@ function createSyncApi(ctx, deps) {
|
|
|
30497
31084
|
const onParent = (entity) => stepOn(STEP.parent, entity);
|
|
30498
31085
|
const onAdapter = (entity) => stepOn(STEP.adapter, entity);
|
|
30499
31086
|
/**
|
|
30500
|
-
* A new `Display` object of the game replaces the old view outright, unless this pass built it
|
|
31087
|
+
* A new `Display` object of the game replaces the old view outright, unless this pass built it, or it waits aside
|
|
31088
|
+
* for its view (spec 31): the chunk that makes it reads the new object.
|
|
30501
31089
|
*
|
|
30502
31090
|
* @param entity - The entity.
|
|
30503
31091
|
*/
|
|
30504
31092
|
const onDisplay = (entity) => {
|
|
30505
|
-
if (built.has(entity)) return;
|
|
31093
|
+
if (built.has(entity) || state.aside.waiting.has(entity)) return;
|
|
30506
31094
|
guard(entity, () => {
|
|
30507
31095
|
dropView(sctx, entity);
|
|
30508
31096
|
createView(sctx, entity);
|
|
@@ -30585,12 +31173,13 @@ function createSyncApi(ctx, deps) {
|
|
|
30585
31173
|
};
|
|
30586
31174
|
/**
|
|
30587
31175
|
* Tells whether the pass has nothing to do: nothing waits to be built, dropped, hung or resolved again, the layer list
|
|
30588
|
-
* is the one built, and no watched component of any entity changed. A still frame
|
|
31176
|
+
* is the one built, no held view waits for its swap, and no watched component of any entity changed. A still frame
|
|
31177
|
+
* returns on this, before any work. The aside queue of a scene build is not read: its work is the build step's.
|
|
30589
31178
|
*
|
|
30590
31179
|
* @returns True when the pass would change nothing.
|
|
30591
31180
|
*/
|
|
30592
31181
|
const still = () => {
|
|
30593
|
-
if (state.removed.size > 0 || state.added.size > 0 || state.reparented.size > 0 || state.orphans.size > 0 || state.invalidated.size > 0 || state.viewlessEntries || state.outlinesStale || ctx.deps.world.projection.layers() !== state.layerList) return false;
|
|
31182
|
+
if (state.removed.size > 0 || state.added.size > 0 || state.reparented.size > 0 || state.orphans.size > 0 || state.invalidated.size > 0 || state.viewlessEntries || state.outlinesStale || ctx.deps.world.projection.layers() !== state.layerList || heldWaiting(sctx)) return false;
|
|
30594
31183
|
const ecs = ctx.deps.world.ecs;
|
|
30595
31184
|
if (!isEmptyList(ecs.changed(Transform)) || !isEmptyList(ecs.changed(Sprite)) || !isEmptyList(ecs.changed(NineSlice)) || !isEmptyList(ecs.changed(Shape)) || !isEmptyList(ecs.changed(Order)) || !isEmptyList(ecs.changed(Layer)) || !isEmptyList(ecs.changed(Parent)) || !isEmptyList(ecs.changed(Display))) return false;
|
|
30596
31185
|
for (let index = 0; index < state.adapters.length; index += 1) {
|
|
@@ -30625,13 +31214,16 @@ function createSyncApi(ctx, deps) {
|
|
|
30625
31214
|
for (const [entity, view] of state.views) if (view.kind === "NineSlice") guard(entity, () => applyNineSlice(sctx, entity, view));
|
|
30626
31215
|
};
|
|
30627
31216
|
/**
|
|
30628
|
-
* Takes the entities that are waiting to be built out of the change set. Called only when one waits.
|
|
31217
|
+
* Takes the entities that are waiting to be built out of the change set. Called only when one waits. While a scene is
|
|
31218
|
+
* built aside, its entities go to the aside queue instead (spec 31): world flags them right after their spawn, so
|
|
31219
|
+
* they are sorted here, not when their components came.
|
|
30629
31220
|
*
|
|
30630
31221
|
* @returns The entities of this pass, so the change step skips what the build just wrote.
|
|
30631
31222
|
*/
|
|
30632
31223
|
const takeAdded = () => {
|
|
30633
31224
|
const built = copyOf(state.added);
|
|
30634
31225
|
state.added.clear();
|
|
31226
|
+
if (buildRuns(sctx)) sortAside(sctx, built);
|
|
30635
31227
|
return built;
|
|
30636
31228
|
};
|
|
30637
31229
|
/**
|
|
@@ -30684,17 +31276,17 @@ function createSyncApi(ctx, deps) {
|
|
|
30684
31276
|
return viewless;
|
|
30685
31277
|
};
|
|
30686
31278
|
/**
|
|
30687
|
-
*
|
|
30688
|
-
* slots with it, also when its view left in an earlier pass; a live
|
|
30689
|
-
*
|
|
31279
|
+
* The live half of a pass: removed, layers, the held views of a build that ended, added, changed, orphans,
|
|
31280
|
+
* invalidated. A despawned entity takes its filter slots with it, also when its view left in an earlier pass; a live
|
|
31281
|
+
* one keeps them for its next view.
|
|
30690
31282
|
*/
|
|
30691
|
-
const
|
|
30692
|
-
if (still()) return;
|
|
31283
|
+
const livePass = () => {
|
|
30693
31284
|
state.passesVersion += 1;
|
|
30694
31285
|
if (state.removed.size > 0) {
|
|
30695
31286
|
for (const entity of state.removed) {
|
|
30696
31287
|
guard(entity, () => dropView(sctx, entity));
|
|
30697
31288
|
forgetLeft(state, ctx.deps.world.ecs, entity);
|
|
31289
|
+
dropAside(state, entity);
|
|
30698
31290
|
}
|
|
30699
31291
|
state.removed.clear();
|
|
30700
31292
|
}
|
|
@@ -30703,6 +31295,7 @@ function createSyncApi(ctx, deps) {
|
|
|
30703
31295
|
deps.demand.whole("layers");
|
|
30704
31296
|
deps.host.sceneChanged();
|
|
30705
31297
|
}
|
|
31298
|
+
attachHeld(sctx);
|
|
30706
31299
|
built = state.added.size === 0 ? nothingBuilt : buildParentFirst(takeAdded());
|
|
30707
31300
|
const touched = typeof __MOKU_GAME_DEV__ !== "undefined" && __MOKU_GAME_DEV__ || typeof __MOKU_GAME_AGENT__ !== "undefined" && __MOKU_GAME_AGENT__ ? touchedBy() : void 0;
|
|
30708
31301
|
applyChanges();
|
|
@@ -30715,6 +31308,14 @@ function createSyncApi(ctx, deps) {
|
|
|
30715
31308
|
if (touched !== void 0) checkTree(sctx, touched);
|
|
30716
31309
|
};
|
|
30717
31310
|
/**
|
|
31311
|
+
* One pass: the live half unless the frame is still, then the build step of a scene built aside (spec 31). A still
|
|
31312
|
+
* frame with no build (nothing changed, nothing waits) does no work and makes nothing.
|
|
31313
|
+
*/
|
|
31314
|
+
const pass = () => {
|
|
31315
|
+
if (!still()) livePass();
|
|
31316
|
+
stepAside(sctx, report);
|
|
31317
|
+
};
|
|
31318
|
+
/**
|
|
30718
31319
|
* Forgets every view, layer and pooled object without touching Pixi. Used before a rebuild,
|
|
30719
31320
|
* where the old application and everything in it are already gone.
|
|
30720
31321
|
*/
|
|
@@ -30739,6 +31340,7 @@ function createSyncApi(ctx, deps) {
|
|
|
30739
31340
|
clearFrames(state);
|
|
30740
31341
|
destroyPools(state);
|
|
30741
31342
|
clearLayers(state);
|
|
31343
|
+
forgetBuild(state);
|
|
30742
31344
|
state.root = void 0;
|
|
30743
31345
|
state.passesVersion += 1;
|
|
30744
31346
|
};
|
|
@@ -30840,6 +31442,7 @@ function createSyncApi(ctx, deps) {
|
|
|
30840
31442
|
stepFilters: () => applySwitched(state, deps.kept),
|
|
30841
31443
|
drainOverlayEvents: (monitor) => drainOverlayEvents(sctx, monitor),
|
|
30842
31444
|
warmStep: () => warmStep(sctx),
|
|
31445
|
+
takeBuildWork: () => takeBuildWork(state.aside),
|
|
30843
31446
|
uploadStep: () => uploadStep(sctx),
|
|
30844
31447
|
onWarm: (fn) => {
|
|
30845
31448
|
state.warm.listeners.push(fn);
|
|
@@ -30868,7 +31471,7 @@ function createSyncApi(ctx, deps) {
|
|
|
30868
31471
|
start: () => {
|
|
30869
31472
|
if (deps.host.stage() === void 0) return;
|
|
30870
31473
|
const ecs = ctx.deps.world.ecs;
|
|
30871
|
-
state.cleanups.push(ecs.onAdded(Sprite, markAdded), ecs.onAdded(NineSlice, markAdded), ecs.onAdded(Shape, markAdded), ecs.onAdded(Display, markAdded), ecs.onRemoved(Sprite, markRemoved), ecs.onRemoved(NineSlice, markRemoved), ecs.onRemoved(Shape, markRemoved), ecs.onRemoved(Display, markRemoved), ecs.onRemoved(Parent, markReparented), ...watchOverlays(sctx), ecs.system(createSyncSystem(pass)));
|
|
31474
|
+
state.cleanups.push(ecs.onAdded(Sprite, markAdded), ecs.onAdded(NineSlice, markAdded), ecs.onAdded(Shape, markAdded), ecs.onAdded(Display, markAdded), ecs.onRemoved(Sprite, markRemoved), ecs.onRemoved(NineSlice, markRemoved), ecs.onRemoved(Shape, markRemoved), ecs.onRemoved(Display, markRemoved), ecs.onRemoved(Parent, markReparented), ...watchOverlays(sctx), ecs.system(createSyncSystem(pass)), ctx.deps.world.projection.onBuild(() => buildReady(sctx)));
|
|
30872
31475
|
rebuildAll();
|
|
30873
31476
|
}
|
|
30874
31477
|
};
|
|
@@ -31649,8 +32252,24 @@ function createUploadState() {
|
|
|
31649
32252
|
};
|
|
31650
32253
|
}
|
|
31651
32254
|
/**
|
|
32255
|
+
* Creates the empty aside queue of a scene build: nothing waits for a view. Its own non-exported function, as
|
|
32256
|
+
* `emptyMap`.
|
|
32257
|
+
*
|
|
32258
|
+
* @returns The aside branch of the sync state.
|
|
32259
|
+
*/
|
|
32260
|
+
function createAsideQueue() {
|
|
32261
|
+
return {
|
|
32262
|
+
entities: [],
|
|
32263
|
+
count: 0,
|
|
32264
|
+
head: 0,
|
|
32265
|
+
waiting: emptySet$4(),
|
|
32266
|
+
made: 0
|
|
32267
|
+
};
|
|
32268
|
+
}
|
|
32269
|
+
/**
|
|
31652
32270
|
* Creates the initial sync state: no root, no layers, no view, no orphan, no pool, no provider, no filter, no
|
|
31653
|
-
* filter area, no slice, no hidden mask, no overlay, no warm-up, no upload, no redraw counted
|
|
32271
|
+
* filter area, no slice, no hidden mask, no overlay, no warm-up, no upload, no redraw counted, no unseen walk kept and
|
|
32272
|
+
* no scene build.
|
|
31654
32273
|
*
|
|
31655
32274
|
* @param debug - The debug switches of the config; copied, so the frozen config stays untouched.
|
|
31656
32275
|
* @returns The sync branch of the plugin state.
|
|
@@ -31709,6 +32328,12 @@ function createSyncState(debug) {
|
|
|
31709
32328
|
enabled: []
|
|
31710
32329
|
},
|
|
31711
32330
|
hudLayer: void 0,
|
|
32331
|
+
aside: createAsideQueue(),
|
|
32332
|
+
heldViews: {
|
|
32333
|
+
entities: [],
|
|
32334
|
+
count: 0,
|
|
32335
|
+
reported: false
|
|
32336
|
+
},
|
|
31712
32337
|
cleanups: []
|
|
31713
32338
|
};
|
|
31714
32339
|
}
|
|
@@ -31816,7 +32441,7 @@ function checkAdaptiveScale(adaptiveScale) {
|
|
|
31816
32441
|
* number from 64 to 16384, `keptMb` is not a finite number of at least 0, `warm` is not a
|
|
31817
32442
|
* whole number from 0 to 16, or `uploads` is not a whole number from 0 to 8.
|
|
31818
32443
|
*/
|
|
31819
|
-
function checkConfig(config) {
|
|
32444
|
+
function checkConfig$1(config) {
|
|
31820
32445
|
const { maxResolution, keptMb } = config;
|
|
31821
32446
|
const { budgetMs, frames } = config.monitor;
|
|
31822
32447
|
if (!Number.isFinite(maxResolution) || maxResolution <= 0) throw new Error(`[game] renderer.maxResolution is ${maxResolution}.\n Give a number above 0.`);
|
|
@@ -31841,7 +32466,7 @@ function checkConfig(config) {
|
|
|
31841
32466
|
* @throws {Error} When the config fails a check of `checkConfig`.
|
|
31842
32467
|
*/
|
|
31843
32468
|
function createRendererState(ctx) {
|
|
31844
|
-
checkConfig(ctx.config);
|
|
32469
|
+
checkConfig$1(ctx.config);
|
|
31845
32470
|
return {
|
|
31846
32471
|
host: createHostState(),
|
|
31847
32472
|
viewport: createViewportState(),
|
|
@@ -35799,7 +36424,7 @@ function usedMb(state) {
|
|
|
35799
36424
|
}
|
|
35800
36425
|
/**
|
|
35801
36426
|
* Picks the bundle that leaves next: the loaded one with the smallest use counter that is not
|
|
35802
|
-
* pinned, not named by the mounted scene, not permanent and not in the preload queue. Its
|
|
36427
|
+
* pinned, not named by the mounted, a leaving or the coming scene, not permanent and not in the preload queue. Its
|
|
35803
36428
|
* `release` is not read: a `"leave"` bundle is a candidate like any other.
|
|
35804
36429
|
*
|
|
35805
36430
|
* @param state - The plugin state.
|
|
@@ -35813,6 +36438,7 @@ function pickVictim(state) {
|
|
|
35813
36438
|
if (record.status !== "loaded" || entry === void 0) continue;
|
|
35814
36439
|
if (state.pinned.has(name) || isPermanent(entry.tier)) continue;
|
|
35815
36440
|
if (state.sceneNamed.includes(name) || state.sceneLeaving.includes(name)) continue;
|
|
36441
|
+
if (state.sceneComing.includes(name)) continue;
|
|
35816
36442
|
if (state.queue?.bundles.includes(name) === true) continue;
|
|
35817
36443
|
if (record.lastUsed >= smallest) continue;
|
|
35818
36444
|
smallest = record.lastUsed;
|
|
@@ -35968,9 +36594,9 @@ function featuresOnStack(ctx) {
|
|
|
35968
36594
|
}
|
|
35969
36595
|
/**
|
|
35970
36596
|
* Tells whether a bundle must stay where the game stands now: it is pinned (a bundle of the node
|
|
35971
|
-
* entered last), the mounted scene names it (the last list `sceneBundles` received),
|
|
35972
|
-
*
|
|
35973
|
-
* is permanent.
|
|
36597
|
+
* entered last), the mounted scene names it (the last list `sceneBundles` received), the scene
|
|
36598
|
+
* being built names it (the last list `sceneComing` received), a leaving scene named it and its
|
|
36599
|
+
* elements still play their exit, its feature is in `onStack`, or its tier is permanent.
|
|
35974
36600
|
*
|
|
35975
36601
|
* @param ctx - Domain context of the plugin.
|
|
35976
36602
|
* @param name - Name of the bundle.
|
|
@@ -35978,9 +36604,9 @@ function featuresOnStack(ctx) {
|
|
|
35978
36604
|
* @param onStack - The features on the stack, from `featuresOnStack`.
|
|
35979
36605
|
* @returns True when the bundle is held.
|
|
35980
36606
|
*/
|
|
35981
|
-
function isHeld(ctx, name, entry, onStack) {
|
|
36607
|
+
function isHeld$1(ctx, name, entry, onStack) {
|
|
35982
36608
|
const state = ctx.state;
|
|
35983
|
-
return state.pinned.has(name) || state.sceneNamed.includes(name) || state.sceneLeaving.includes(name) || onStack.has(entry.feature) || isPermanent(entry.tier);
|
|
36609
|
+
return state.pinned.has(name) || state.sceneNamed.includes(name) || state.sceneLeaving.includes(name) || state.sceneComing.includes(name) || onStack.has(entry.feature) || isPermanent(entry.tier);
|
|
35984
36610
|
}
|
|
35985
36611
|
/**
|
|
35986
36612
|
* Unloads every loaded `"leave"` bundle nobody holds any more, with the reason `"leave"`. A
|
|
@@ -35999,7 +36625,7 @@ function releaseLeftBundles(ctx) {
|
|
|
35999
36625
|
/**
|
|
36000
36626
|
* Takes the list the mounted scene names now. A bundle the list named before and names no more
|
|
36001
36627
|
* stays held as a leaving scene's, until nothing carries `Exiting` (F51); a name the list takes
|
|
36002
|
-
* again is the mounted scene's once more.
|
|
36628
|
+
* again is the mounted scene's once more. The coming list empties: its scene is the mounted one.
|
|
36003
36629
|
*
|
|
36004
36630
|
* @param state - The plugin state.
|
|
36005
36631
|
* @param named - The bundles the mounted scene names now.
|
|
@@ -36009,6 +36635,17 @@ function nameSceneBundles(state, named) {
|
|
|
36009
36635
|
for (const name of state.sceneNamed) if (!named.includes(name) && !leaving.includes(name)) leaving.push(name);
|
|
36010
36636
|
state.sceneLeaving = leaving;
|
|
36011
36637
|
state.sceneNamed = [...named];
|
|
36638
|
+
state.sceneComing = [];
|
|
36639
|
+
}
|
|
36640
|
+
/**
|
|
36641
|
+
* Takes the list the scene being built aside names: each call replaces the one before, and `[]`
|
|
36642
|
+
* ends the hold of a discarded build. `nameSceneBundles` empties it at the swap (F61 S2).
|
|
36643
|
+
*
|
|
36644
|
+
* @param state - The plugin state.
|
|
36645
|
+
* @param named - The bundles the coming scene names.
|
|
36646
|
+
*/
|
|
36647
|
+
function nameComingBundles(state, named) {
|
|
36648
|
+
state.sceneComing = [...named];
|
|
36012
36649
|
}
|
|
36013
36650
|
/**
|
|
36014
36651
|
* Lets the bundles a leaving scene named go once nothing carries `Exiting` any more: each loaded
|
|
@@ -36018,14 +36655,17 @@ function nameSceneBundles(state, named) {
|
|
|
36018
36655
|
* its feature shows (the splash's 7.5 MiB under the ward). A bundle of another release stays, as
|
|
36019
36656
|
* it would have. The system `assets.leave` calls it in every phase `sync`: after the reconcile of
|
|
36020
36657
|
* `layout`, which tags the leaving elements `Exiting` in the frame after the switch, so the hold
|
|
36021
|
-
* never ends before the leave begins (F51b).
|
|
36022
|
-
*
|
|
36658
|
+
* never ends before the leave begins (F51b). While a scene is built aside (the coming list is
|
|
36659
|
+
* set; a scene names its own bundle, so the list of a build is never empty) the leave has not
|
|
36660
|
+
* begun: the world holds the scene on screen as it stood until the swap, and what it draws still
|
|
36661
|
+
* needs its bundles. It costs two length reads while no scene leaves, and a count read while one
|
|
36662
|
+
* does.
|
|
36023
36663
|
*
|
|
36024
36664
|
* @param ctx - Domain context of the plugin.
|
|
36025
36665
|
*/
|
|
36026
36666
|
function releaseSceneLeft(ctx) {
|
|
36027
36667
|
const state = ctx.state;
|
|
36028
|
-
if (state.sceneLeaving.length === 0 || state.exiting > 0) return;
|
|
36668
|
+
if (state.sceneLeaving.length === 0 || state.exiting > 0 || state.sceneComing.length > 0) return;
|
|
36029
36669
|
const names = state.sceneLeaving;
|
|
36030
36670
|
state.sceneLeaving = [];
|
|
36031
36671
|
if (state.io === void 0) return;
|
|
@@ -36045,7 +36685,7 @@ function releaseSceneLeft(ctx) {
|
|
|
36045
36685
|
function unloadIfLeft(ctx, name, onStack) {
|
|
36046
36686
|
const entry = ctx.state.manifest.bundles[name];
|
|
36047
36687
|
if (ctx.state.records.get(name)?.status !== "loaded" || entry?.release !== "leave") return;
|
|
36048
|
-
if (isHeld(ctx, name, entry, onStack)) return;
|
|
36688
|
+
if (isHeld$1(ctx, name, entry, onStack)) return;
|
|
36049
36689
|
unloadBundle(ctx, name, "leave");
|
|
36050
36690
|
}
|
|
36051
36691
|
//#endregion
|
|
@@ -36438,7 +37078,7 @@ function affordable(ctx, distances) {
|
|
|
36438
37078
|
for (const name of ordered) {
|
|
36439
37079
|
const entry = ctx.state.manifest.bundles[name];
|
|
36440
37080
|
if (entry === void 0 || entry.tier === "lazy") continue;
|
|
36441
|
-
if (entry.release === "leave" && !isHeld(ctx, name, entry, onStack)) continue;
|
|
37081
|
+
if (entry.release === "leave" && !isHeld$1(ctx, name, entry, onStack)) continue;
|
|
36442
37082
|
if (!isStillWanted(ctx.state, name)) continue;
|
|
36443
37083
|
if (running + entry.mb > ctx.config.textureBudgetMb) break;
|
|
36444
37084
|
running += entry.mb;
|
|
@@ -36874,6 +37514,7 @@ function releaseAll(state) {
|
|
|
36874
37514
|
state.sceneId = void 0;
|
|
36875
37515
|
state.sceneNamed = [];
|
|
36876
37516
|
state.sceneLeaving = [];
|
|
37517
|
+
state.sceneComing = [];
|
|
36877
37518
|
state.exiting = 0;
|
|
36878
37519
|
state.restPending = false;
|
|
36879
37520
|
state.stamps = void 0;
|
|
@@ -37023,6 +37664,7 @@ function createAssetsApi(ctx) {
|
|
|
37023
37664
|
audio: (key) => loadedRecord(assets, key)?.audio.get(key),
|
|
37024
37665
|
usage: () => usage(assets),
|
|
37025
37666
|
sceneBundles: (named) => nameSceneBundles(ctx.state, named),
|
|
37667
|
+
sceneComing: (named) => nameComingBundles(ctx.state, named),
|
|
37026
37668
|
has: (bundle) => Object.hasOwn(ctx.state.manifest.bundles, bundle),
|
|
37027
37669
|
companions: { add: (companion) => addCompanion(ctx.state, companion) }
|
|
37028
37670
|
};
|
|
@@ -37691,6 +38333,7 @@ function createAssetsState() {
|
|
|
37691
38333
|
swapping: void 0,
|
|
37692
38334
|
sceneNamed: [],
|
|
37693
38335
|
sceneLeaving: [],
|
|
38336
|
+
sceneComing: [],
|
|
37694
38337
|
exiting: 0,
|
|
37695
38338
|
companions: [],
|
|
37696
38339
|
restPending: false,
|
|
@@ -37759,161 +38402,7 @@ function createScenesApi(ctx) {
|
|
|
37759
38402
|
};
|
|
37760
38403
|
}
|
|
37761
38404
|
//#endregion
|
|
37762
|
-
//#region src/plugins/scenes/
|
|
37763
|
-
/**
|
|
37764
|
-
* A promise that resolves when the node was aborted. Already aborted resolves at once.
|
|
37765
|
-
*
|
|
37766
|
-
* @param signal - The abort signal of the node being entered.
|
|
37767
|
-
* @returns A promise that resolves on abort and never rejects.
|
|
37768
|
-
*/
|
|
37769
|
-
function whenAborted$1(signal) {
|
|
37770
|
-
return new Promise((resolve) => {
|
|
37771
|
-
if (signal.aborted) {
|
|
37772
|
-
resolve();
|
|
37773
|
-
return;
|
|
37774
|
-
}
|
|
37775
|
-
signal.addEventListener("abort", () => resolve(), { once: true });
|
|
37776
|
-
});
|
|
37777
|
-
}
|
|
37778
|
-
/**
|
|
37779
|
-
* Waits for the textures of loaded bundles to reach the GPU, or for the abort. It never rejects:
|
|
37780
|
-
* a warm that fails (an unload during it) is logged and the switch goes on, its first draw
|
|
37781
|
-
* uploads as before. Headless, without a device or with `uploads: 0` the warms answer at once,
|
|
37782
|
-
* and textures already on the GPU are not waited for.
|
|
37783
|
-
*
|
|
37784
|
-
* @param ctx - Domain context of the plugin.
|
|
37785
|
-
* @param bundles - The bundles the scene being entered names, all of them loaded.
|
|
37786
|
-
* @param signal - The abort signal of the node.
|
|
37787
|
-
* @returns A promise that resolves when every warm settled or the node was aborted.
|
|
37788
|
-
*/
|
|
37789
|
-
async function untilWarmed(ctx, bundles, signal) {
|
|
37790
|
-
const warms = bundles.map((bundle) => ctx.deps.assets.warm(bundle).catch((error) => {
|
|
37791
|
-
ctx.log.warn("scenes: a bundle's textures were not uploaded before the switch", {
|
|
37792
|
-
bundle,
|
|
37793
|
-
error
|
|
37794
|
-
});
|
|
37795
|
-
}));
|
|
37796
|
-
await Promise.race([Promise.all(warms), whenAborted$1(signal)]);
|
|
37797
|
-
}
|
|
37798
|
-
/**
|
|
37799
|
-
* Loads every bundle the scene names at once and returns as soon as all loads settled or the node
|
|
37800
|
-
* is aborted. The first load that fails while the node still runs is rethrown, so `flow` rolls
|
|
37801
|
-
* the node back; one that fails after the abort is logged, because nobody waits for it any more.
|
|
37802
|
-
*
|
|
37803
|
-
* @param ctx - Domain context of the plugin.
|
|
37804
|
-
* @param bundles - The bundles the scene being entered names, its own bundle first.
|
|
37805
|
-
* @param signal - The abort signal of the node.
|
|
37806
|
-
* @returns A promise that resolves when the loads settled or the node was aborted.
|
|
37807
|
-
* @throws {Error} Whatever the first failed load rejected with, while the node was not aborted.
|
|
37808
|
-
*/
|
|
37809
|
-
async function untilAborted(ctx, bundles, signal) {
|
|
37810
|
-
const load = {
|
|
37811
|
-
failed: false,
|
|
37812
|
-
error: void 0
|
|
37813
|
-
};
|
|
37814
|
-
const loads = bundles.map((bundle) => ctx.deps.assets.load(bundle).catch((error) => {
|
|
37815
|
-
if (!load.failed) {
|
|
37816
|
-
load.failed = true;
|
|
37817
|
-
load.error = error;
|
|
37818
|
-
}
|
|
37819
|
-
if (signal.aborted) ctx.log.warn("scenes: a cancelled bundle load failed", {
|
|
37820
|
-
bundle,
|
|
37821
|
-
error
|
|
37822
|
-
});
|
|
37823
|
-
}));
|
|
37824
|
-
await Promise.race([Promise.all(loads), whenAborted$1(signal)]);
|
|
37825
|
-
if (load.failed && !signal.aborted) throw load.error;
|
|
37826
|
-
}
|
|
37827
|
-
/**
|
|
37828
|
-
* Reads the committed state a scene's `bundles` gets: the frozen trees of the store, in a frozen
|
|
37829
|
-
* wrapper. During the scene stage no node writes, so it is the state the node is entered with.
|
|
37830
|
-
*
|
|
37831
|
-
* @param ctx - Domain context of the plugin.
|
|
37832
|
-
* @returns The committed `player` and `session`.
|
|
37833
|
-
*/
|
|
37834
|
-
function committedState(ctx) {
|
|
37835
|
-
const { player, session } = ctx.deps.model.store.snapshot();
|
|
37836
|
-
return Object.freeze({
|
|
37837
|
-
player,
|
|
37838
|
-
session
|
|
37839
|
-
});
|
|
37840
|
-
}
|
|
37841
|
-
/**
|
|
37842
|
-
* Tells whether two lists name the same bundles in the same order.
|
|
37843
|
-
*
|
|
37844
|
-
* @param left - One list.
|
|
37845
|
-
* @param right - The other list.
|
|
37846
|
-
* @returns True when both have the same names at the same places.
|
|
37847
|
-
* @example
|
|
37848
|
-
* ```ts
|
|
37849
|
-
* sameNames(["location", "ep01"], ["location", "ep02"]); // false
|
|
37850
|
-
* ```
|
|
37851
|
-
*/
|
|
37852
|
-
function sameNames(left, right) {
|
|
37853
|
-
return left.length === right.length && left.every((name, index) => name === right[index]);
|
|
37854
|
-
}
|
|
37855
|
-
/**
|
|
37856
|
-
* Takes the expected scene and clears the record, so one `expect` mounts one scene.
|
|
37857
|
-
*
|
|
37858
|
-
* @param ctx - Domain context of the plugin.
|
|
37859
|
-
* @returns The scene id that was recorded or expected, or `undefined` when there was none.
|
|
37860
|
-
*/
|
|
37861
|
-
function takePending(ctx) {
|
|
37862
|
-
const target = ctx.state.pending;
|
|
37863
|
-
ctx.state.pending = void 0;
|
|
37864
|
-
return target;
|
|
37865
|
-
}
|
|
37866
|
-
/**
|
|
37867
|
-
* Tells whether the node is a transit node of a fast walk: the walk passes through it and builds
|
|
37868
|
-
* no scene there.
|
|
37869
|
-
*
|
|
37870
|
-
* @param node - The node being entered.
|
|
37871
|
-
* @param run - The runner's mode and signal.
|
|
37872
|
-
* @returns True for a node without `rest` entered in fast mode.
|
|
37873
|
-
*/
|
|
37874
|
-
function isFastTransit(node, run) {
|
|
37875
|
-
return run.mode === "fast" && !node.rest;
|
|
37876
|
-
}
|
|
37877
|
-
/**
|
|
37878
|
-
* Reads the scene the node asks for. In fast mode a transit node only records it: the walk passes
|
|
37879
|
-
* through many of them and the picture is built once, at the rest point. In live mode a transit
|
|
37880
|
-
* node without a scene of its own takes the expected one, so a restore at a popup that waits for
|
|
37881
|
-
* its answer builds the scene under it; one with its own scene leaves the record alone.
|
|
37882
|
-
*
|
|
37883
|
-
* @param ctx - Domain context of the plugin.
|
|
37884
|
-
* @param node - The node being entered.
|
|
37885
|
-
* @param run - The runner's mode and signal.
|
|
37886
|
-
* @returns The scene id to switch to, or `undefined` when nothing has to happen.
|
|
37887
|
-
*/
|
|
37888
|
-
function targetOf(ctx, node, run) {
|
|
37889
|
-
const state = ctx.state;
|
|
37890
|
-
if (isFastTransit(node, run)) {
|
|
37891
|
-
state.pending = node.scene ?? state.pending;
|
|
37892
|
-
return;
|
|
37893
|
-
}
|
|
37894
|
-
if (!node.rest) return node.scene ?? takePending(ctx);
|
|
37895
|
-
const target = node.scene ?? state.pending;
|
|
37896
|
-
state.pending = void 0;
|
|
37897
|
-
if (target === void 0 && state.current === void 0) ctx.log.warn("scenes: a rest node was entered with no scene", { path: node.path });
|
|
37898
|
-
return target;
|
|
37899
|
-
}
|
|
37900
|
-
/**
|
|
37901
|
-
* Reads the scene an `over` node asks for. An over node takes the expected scene and clears the
|
|
37902
|
-
* record, whatever is mounted, so a restore at a popup builds the scene under it, whether the
|
|
37903
|
-
* popup rests or waits for its answer. With nothing expected it switches nothing. In fast mode an
|
|
37904
|
-
* over transit node takes nothing and leaves the record alone: a walk builds no scene on the way.
|
|
37905
|
-
* A scene the over node names itself is only warned about.
|
|
37906
|
-
*
|
|
37907
|
-
* @param ctx - Domain context of the plugin.
|
|
37908
|
-
* @param node - The over node being entered.
|
|
37909
|
-
* @param run - The runner's mode and signal.
|
|
37910
|
-
* @returns The expected scene id, or `undefined` when nothing has to happen.
|
|
37911
|
-
*/
|
|
37912
|
-
function overTargetOf(ctx, node, run) {
|
|
37913
|
-
if (node.scene !== void 0) ctx.log.warn("scenes: an over node cannot name a scene", { path: node.path });
|
|
37914
|
-
if (isFastTransit(node, run)) return void 0;
|
|
37915
|
-
return takePending(ctx);
|
|
37916
|
-
}
|
|
38405
|
+
//#region src/plugins/scenes/swap.ts
|
|
37917
38406
|
/**
|
|
37918
38407
|
* The projections of the scene that leaves which the scene that comes does not list. A projection
|
|
37919
38408
|
* both list stays mounted: the layers of a projection are the ones its spec names, and both scenes
|
|
@@ -37927,23 +38416,55 @@ function leavingOf(previous, scene) {
|
|
|
37927
38416
|
return previous.projections.filter((name) => !scene.projections.includes(name));
|
|
37928
38417
|
}
|
|
37929
38418
|
/**
|
|
37930
|
-
*
|
|
37931
|
-
*
|
|
37932
|
-
*
|
|
37933
|
-
*
|
|
38419
|
+
* Names the switch in the performance log of an agent build: the scenes it went between and,
|
|
38420
|
+
* for a scene built aside, the frames and the milliseconds of page clock the build took, and the
|
|
38421
|
+
* budget of a frame it ran with.
|
|
38422
|
+
*
|
|
38423
|
+
* @param ctx - Domain context of the plugin.
|
|
38424
|
+
* @param from - The scene that left, `undefined` for the first one.
|
|
38425
|
+
* @param to - The scene that is mounted now.
|
|
38426
|
+
* @param build - The build that was committed; absent for a one-block switch.
|
|
38427
|
+
*/
|
|
38428
|
+
function logSwitch(ctx, from, to, build) {
|
|
38429
|
+
const scenes = {
|
|
38430
|
+
from: from ?? null,
|
|
38431
|
+
to
|
|
38432
|
+
};
|
|
38433
|
+
if (build === void 0) {
|
|
38434
|
+
ctx.deps.renderer.monitor.event("scene", scenes);
|
|
38435
|
+
return;
|
|
38436
|
+
}
|
|
38437
|
+
const clock = ctx.deps.time.agentClock();
|
|
38438
|
+
const buildMs = clock === void 0 ? 0 : clock() - build.startedAt;
|
|
38439
|
+
ctx.deps.renderer.monitor.event("scene", {
|
|
38440
|
+
...scenes,
|
|
38441
|
+
buildFrames: build.frames,
|
|
38442
|
+
buildMs,
|
|
38443
|
+
budgetMs: build.budgetMs
|
|
38444
|
+
});
|
|
38445
|
+
}
|
|
38446
|
+
/**
|
|
38447
|
+
* Makes a scene the mounted one in one synchronous block: the layers, then the projections only
|
|
38448
|
+
* the old scene lists out, then the new scene in (a `mount` of its list, or the commit of the
|
|
38449
|
+
* build that made it aside; a projection both list stays and takes its new place), the new scene
|
|
38450
|
+
* to the world's systems, then the list the scene names to `assets`, then the frame loop out of
|
|
38451
|
+
* its idle cap and the adaptive frame rate told of the new screen, and only then the event.
|
|
37934
38452
|
*
|
|
37935
38453
|
* @param ctx - Domain context of the plugin.
|
|
37936
38454
|
* @param scene - The scene whose bundles are there.
|
|
37937
38455
|
* @param named - The bundles the scene names, as they were awaited.
|
|
38456
|
+
* @param build - The build to commit in the mount's place; absent for a one-block switch.
|
|
38457
|
+
* @throws {Error} Whatever the mount threw: a `view` or an unknown projection.
|
|
37938
38458
|
*/
|
|
37939
|
-
function
|
|
38459
|
+
function swapIn(ctx, scene, named, build) {
|
|
37940
38460
|
const state = ctx.state;
|
|
37941
38461
|
const projection = ctx.deps.world.projection;
|
|
37942
|
-
const from = state.current;
|
|
38462
|
+
const from = build === void 0 ? state.current : build.from;
|
|
37943
38463
|
const previous = from === void 0 ? void 0 : state.scenes.get(from);
|
|
37944
38464
|
projection.setLayers(scene.layers);
|
|
37945
38465
|
if (previous !== void 0) projection.unmount(leavingOf(previous, scene));
|
|
37946
|
-
projection.mount(scene.projections, state.owner);
|
|
38466
|
+
if (build === void 0) projection.mount(scene.projections, state.owner);
|
|
38467
|
+
else projection.commit();
|
|
37947
38468
|
state.current = scene.id;
|
|
37948
38469
|
ctx.deps.world.ecs.setScene(scene.id);
|
|
37949
38470
|
state.named = named;
|
|
@@ -37955,16 +38476,424 @@ function apply(ctx, scene, named) {
|
|
|
37955
38476
|
to: scene.id,
|
|
37956
38477
|
music: scene.music
|
|
37957
38478
|
});
|
|
37958
|
-
if (typeof __MOKU_GAME_AGENT__ !== "undefined" && __MOKU_GAME_AGENT__) ctx.
|
|
37959
|
-
|
|
37960
|
-
|
|
38479
|
+
if (typeof __MOKU_GAME_AGENT__ !== "undefined" && __MOKU_GAME_AGENT__) logSwitch(ctx, from, scene.id, build);
|
|
38480
|
+
}
|
|
38481
|
+
//#endregion
|
|
38482
|
+
//#region src/plugins/scenes/build.ts
|
|
38483
|
+
/**
|
|
38484
|
+
* @file scenes plugin — the next scene built aside on a live switch (spec 31, F61 S2). The world
|
|
38485
|
+
* mounts the coming projections into a table of its own while the old scene stays current, drawn
|
|
38486
|
+
* and alive; `ui` and the renderer do their part under the budget of a frame (`buildMs`); the frame
|
|
38487
|
+
* `world.projection.built()` first answers true commits it with the swap of a one-block switch. An
|
|
38488
|
+
* abort of the node discards the build and leaves the world as it stood.
|
|
38489
|
+
*/
|
|
38490
|
+
/** Half a second in ms: half of a frame interval is this over the frame rate. */
|
|
38491
|
+
const HALF_SECOND_MS = 500;
|
|
38492
|
+
/** The budget of a frame when `time` answers no frame rate to halve: half a 60 Hz frame, ms. */
|
|
38493
|
+
const FALLBACK_BUDGET_MS = HALF_SECOND_MS / 60;
|
|
38494
|
+
/**
|
|
38495
|
+
* Tells whether a value is a number a frame can count with: finite and above 0. Pure.
|
|
38496
|
+
*
|
|
38497
|
+
* @param value - The value.
|
|
38498
|
+
* @returns True for a finite number above 0.
|
|
38499
|
+
* @example
|
|
38500
|
+
* ```ts
|
|
38501
|
+
* isPositiveFinite(Number.NaN); // false
|
|
38502
|
+
* ```
|
|
38503
|
+
*/
|
|
38504
|
+
function isPositiveFinite(value) {
|
|
38505
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0;
|
|
38506
|
+
}
|
|
38507
|
+
/**
|
|
38508
|
+
* Does nothing: the listeners of a build that commits at once, before it registered any.
|
|
38509
|
+
*/
|
|
38510
|
+
function noListeners() {}
|
|
38511
|
+
/**
|
|
38512
|
+
* Reads the page clock of an agent build, for the milliseconds a build took.
|
|
38513
|
+
*
|
|
38514
|
+
* @param ctx - Domain context of the plugin.
|
|
38515
|
+
* @returns The page clock in ms, or 0 in a build without the flag.
|
|
38516
|
+
*/
|
|
38517
|
+
function agentNow(ctx) {
|
|
38518
|
+
const clock = ctx.deps.time.agentClock();
|
|
38519
|
+
return clock === void 0 ? 0 : clock();
|
|
38520
|
+
}
|
|
38521
|
+
/**
|
|
38522
|
+
* The building budget of a frame for a build that starts now: the number a game configured, or
|
|
38523
|
+
* for `"auto"` half of the frame interval the loop runs at, `500 / time.rate().fps`, read once
|
|
38524
|
+
* here. A rate that is not a finite number above 0 gives half a 60 Hz frame.
|
|
38525
|
+
*
|
|
38526
|
+
* @param ctx - Domain context of the plugin.
|
|
38527
|
+
* @returns Milliseconds per frame the building work may take.
|
|
38528
|
+
*/
|
|
38529
|
+
function budgetOf(ctx) {
|
|
38530
|
+
const { buildMs } = ctx.config;
|
|
38531
|
+
if (buildMs !== "auto") return buildMs;
|
|
38532
|
+
const { fps } = ctx.deps.time.rate();
|
|
38533
|
+
return isPositiveFinite(fps) ? HALF_SECOND_MS / fps : FALLBACK_BUDGET_MS;
|
|
38534
|
+
}
|
|
38535
|
+
/**
|
|
38536
|
+
* Ends the build with the swap: its listeners go and the record is dropped, then the swap of a
|
|
38537
|
+
* one-block switch runs, with the commit in the mount's place, or, for `"mount"`, after the build
|
|
38538
|
+
* was discarded, with the mount itself. A swap that throws rejects the switch, so `flow` rolls the
|
|
38539
|
+
* node back; the build is discarded and the coming bundles are held no more first, so the next
|
|
38540
|
+
* switch can build again.
|
|
38541
|
+
*
|
|
38542
|
+
* @param ctx - Domain context of the plugin.
|
|
38543
|
+
* @param build - The running build.
|
|
38544
|
+
* @param how - `"commit"` to commit the build, `"mount"` to discard it and mount in one block.
|
|
38545
|
+
*/
|
|
38546
|
+
function endBuild(ctx, build, how) {
|
|
38547
|
+
build.off();
|
|
38548
|
+
delete ctx.state.build;
|
|
38549
|
+
if (how === "mount") ctx.deps.world.projection.discard();
|
|
38550
|
+
try {
|
|
38551
|
+
swapIn(ctx, build.scene, build.named, how === "commit" ? build : void 0);
|
|
38552
|
+
} catch (error) {
|
|
38553
|
+
ctx.deps.world.projection.discard();
|
|
38554
|
+
ctx.deps.assets.sceneComing([]);
|
|
38555
|
+
build.reject(error);
|
|
38556
|
+
return;
|
|
38557
|
+
}
|
|
38558
|
+
build.resolve();
|
|
38559
|
+
}
|
|
38560
|
+
/**
|
|
38561
|
+
* The `signals` callback of a running build: counts the frame and commits the build the frame
|
|
38562
|
+
* every part of it is done. A world that stopped running live during the build (a walk that went
|
|
38563
|
+
* fast, as the restore of a repro walks its route, or a pause) skips its `layout` phase, so `ui`
|
|
38564
|
+
* would never lay the coming scene out: the build is given up and the scene mounted in one block,
|
|
38565
|
+
* as a switch in that mode is. While the build is not done, a counter step and two calls, no
|
|
38566
|
+
* allocation.
|
|
38567
|
+
*
|
|
38568
|
+
* @param ctx - Domain context of the plugin.
|
|
38569
|
+
* @param build - The running build.
|
|
38570
|
+
*/
|
|
38571
|
+
function stepBuild(ctx, build) {
|
|
38572
|
+
build.frames += 1;
|
|
38573
|
+
const world = ctx.deps.world;
|
|
38574
|
+
if (world.ecs.mode() !== "live") {
|
|
38575
|
+
endBuild(ctx, build, "mount");
|
|
38576
|
+
return;
|
|
38577
|
+
}
|
|
38578
|
+
if (world.projection.built()) endBuild(ctx, build, "commit");
|
|
38579
|
+
}
|
|
38580
|
+
/**
|
|
38581
|
+
* Drops the running build on an abort of its node (a restore, the inbox, a stop of the runner):
|
|
38582
|
+
* the world despawns what the build made, the coming bundles are held no more, and the switch
|
|
38583
|
+
* returns with the old scene current. The layers, the scene of the world, the named bundles and
|
|
38584
|
+
* the mounted projections were never touched. The commit and a stop remove this listener first.
|
|
38585
|
+
*
|
|
38586
|
+
* @param ctx - Domain context of the plugin.
|
|
38587
|
+
* @param build - The build of the aborted node.
|
|
38588
|
+
*/
|
|
38589
|
+
function abortBuild(ctx, build) {
|
|
38590
|
+
build.off();
|
|
38591
|
+
delete ctx.state.build;
|
|
38592
|
+
ctx.deps.world.projection.discard();
|
|
38593
|
+
ctx.deps.assets.sceneComing([]);
|
|
38594
|
+
build.resolve();
|
|
38595
|
+
}
|
|
38596
|
+
/**
|
|
38597
|
+
* Creates the record of a build that starts now, its listeners not registered yet.
|
|
38598
|
+
*
|
|
38599
|
+
* @param ctx - Domain context of the plugin.
|
|
38600
|
+
* @param scene - The scene being built.
|
|
38601
|
+
* @param named - The bundles it names, as they were awaited.
|
|
38602
|
+
* @param budgetMs - The building budget of a frame the world was given.
|
|
38603
|
+
* @param settle - The callbacks of the switch's promise.
|
|
38604
|
+
* @param settle.resolve - Settles the switch.
|
|
38605
|
+
* @param settle.reject - Rejects the switch.
|
|
38606
|
+
* @returns The record, at frame 0, with no listeners to remove.
|
|
38607
|
+
*/
|
|
38608
|
+
function startBuildRecord(ctx, scene, named, budgetMs, settle) {
|
|
38609
|
+
return {
|
|
38610
|
+
scene,
|
|
38611
|
+
named,
|
|
38612
|
+
from: ctx.state.current,
|
|
38613
|
+
frames: 0,
|
|
38614
|
+
budgetMs,
|
|
38615
|
+
startedAt: agentNow(ctx),
|
|
38616
|
+
off: noListeners,
|
|
38617
|
+
resolve: settle.resolve,
|
|
38618
|
+
reject: settle.reject
|
|
38619
|
+
};
|
|
38620
|
+
}
|
|
38621
|
+
/**
|
|
38622
|
+
* Registers the listeners of a running build: the `signals` callback that asks every frame
|
|
38623
|
+
* whether it is done, and the abort of its node, which discards it. `build.off` removes both; the
|
|
38624
|
+
* build becomes the plugin's running one.
|
|
38625
|
+
*
|
|
38626
|
+
* @param ctx - Domain context of the plugin.
|
|
38627
|
+
* @param build - The build that starts waiting for its frames.
|
|
38628
|
+
* @param signal - The abort signal of the node, not aborted.
|
|
38629
|
+
*/
|
|
38630
|
+
function watchBuild(ctx, build, signal) {
|
|
38631
|
+
const onAbort = () => abortBuild(ctx, build);
|
|
38632
|
+
const offFrame = ctx.deps.time.onFrame("signals", () => stepBuild(ctx, build));
|
|
38633
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
38634
|
+
build.off = () => {
|
|
38635
|
+
offFrame();
|
|
38636
|
+
signal.removeEventListener("abort", onAbort);
|
|
38637
|
+
};
|
|
38638
|
+
ctx.state.build = build;
|
|
38639
|
+
}
|
|
38640
|
+
/**
|
|
38641
|
+
* Starts the build of a scene aside and answers the promise of the switch. The world mounts what
|
|
38642
|
+
* only the coming scene lists, against the committed state, and gets the budget of a frame
|
|
38643
|
+
* (`budgetOf`); the coming bundles are held from now.
|
|
38644
|
+
* When nothing is left for `ui` and the renderer the commit runs at once, with no frame between.
|
|
38645
|
+
* Otherwise a `signals` callback asks every frame whether the build is done, and an abort of the
|
|
38646
|
+
* node discards it.
|
|
38647
|
+
*
|
|
38648
|
+
* @param ctx - Domain context of the plugin.
|
|
38649
|
+
* @param scene - The scene whose bundles are there.
|
|
38650
|
+
* @param named - The bundles it names, as they were awaited.
|
|
38651
|
+
* @param signal - The abort signal of the node, not aborted.
|
|
38652
|
+
* @returns The promise of the switch, settled at the commit or the abort; `undefined` when the
|
|
38653
|
+
* world refused to build (a listed projection is mounted under other layer rules), so the
|
|
38654
|
+
* caller switches in one block.
|
|
38655
|
+
* @throws {Error} Whatever `world.projection.build` threw: a `view`, an unknown projection or an
|
|
38656
|
+
* undeclared layer. Nothing is built then.
|
|
38657
|
+
*/
|
|
38658
|
+
function buildAside(ctx, scene, named, signal) {
|
|
38659
|
+
const projection = ctx.deps.world.projection;
|
|
38660
|
+
const budgetMs = budgetOf(ctx);
|
|
38661
|
+
if (!projection.build(scene.projections, ctx.state.owner, scene.layers, budgetMs)) return void 0;
|
|
38662
|
+
ctx.deps.assets.sceneComing(named);
|
|
38663
|
+
if (typeof __MOKU_GAME_AGENT__ !== "undefined" && __MOKU_GAME_AGENT__) {
|
|
38664
|
+
const from = ctx.state.current;
|
|
38665
|
+
ctx.deps.renderer.monitor.event("scene:build", {
|
|
38666
|
+
from: from ?? null,
|
|
38667
|
+
to: scene.id
|
|
38668
|
+
});
|
|
38669
|
+
}
|
|
38670
|
+
return new Promise((resolve, reject) => {
|
|
38671
|
+
const build = startBuildRecord(ctx, scene, named, budgetMs, {
|
|
38672
|
+
resolve,
|
|
38673
|
+
reject
|
|
38674
|
+
});
|
|
38675
|
+
if (projection.built()) {
|
|
38676
|
+
endBuild(ctx, build, "commit");
|
|
38677
|
+
return;
|
|
38678
|
+
}
|
|
38679
|
+
watchBuild(ctx, build, signal);
|
|
38680
|
+
});
|
|
38681
|
+
}
|
|
38682
|
+
/**
|
|
38683
|
+
* Drops a running build when the plugin stops: its frame callback and its abort listener go and
|
|
38684
|
+
* the switch returns. Nothing else is called: a teardown context may not call another plugin, and
|
|
38685
|
+
* `world.onStop` drops the build table with every entity.
|
|
38686
|
+
*
|
|
38687
|
+
* @param build - The running build, if any.
|
|
38688
|
+
*/
|
|
38689
|
+
function dropBuild(build) {
|
|
38690
|
+
if (build === void 0) return;
|
|
38691
|
+
build.off();
|
|
38692
|
+
build.resolve();
|
|
38693
|
+
}
|
|
38694
|
+
//#endregion
|
|
38695
|
+
//#region src/plugins/scenes/switch.ts
|
|
38696
|
+
/**
|
|
38697
|
+
* A promise that resolves when the node was aborted. Already aborted resolves at once.
|
|
38698
|
+
*
|
|
38699
|
+
* @param signal - The abort signal of the node being entered.
|
|
38700
|
+
* @returns A promise that resolves on abort and never rejects.
|
|
38701
|
+
*/
|
|
38702
|
+
function whenAborted$1(signal) {
|
|
38703
|
+
return new Promise((resolve) => {
|
|
38704
|
+
if (signal.aborted) {
|
|
38705
|
+
resolve();
|
|
38706
|
+
return;
|
|
38707
|
+
}
|
|
38708
|
+
signal.addEventListener("abort", () => resolve(), { once: true });
|
|
37961
38709
|
});
|
|
37962
38710
|
}
|
|
37963
38711
|
/**
|
|
38712
|
+
* Waits for the textures of loaded bundles to reach the GPU, or for the abort. It never rejects:
|
|
38713
|
+
* a warm that fails (an unload during it) is logged and the switch goes on, its first draw
|
|
38714
|
+
* uploads as before. Headless, without a device or with `uploads: 0` the warms answer at once,
|
|
38715
|
+
* and textures already on the GPU are not waited for.
|
|
38716
|
+
*
|
|
38717
|
+
* @param ctx - Domain context of the plugin.
|
|
38718
|
+
* @param bundles - The bundles the scene being entered names, all of them loaded.
|
|
38719
|
+
* @param signal - The abort signal of the node.
|
|
38720
|
+
* @returns A promise that resolves when every warm settled or the node was aborted.
|
|
38721
|
+
*/
|
|
38722
|
+
async function untilWarmed(ctx, bundles, signal) {
|
|
38723
|
+
const warms = bundles.map((bundle) => ctx.deps.assets.warm(bundle).catch((error) => {
|
|
38724
|
+
ctx.log.warn("scenes: a bundle's textures were not uploaded before the switch", {
|
|
38725
|
+
bundle,
|
|
38726
|
+
error
|
|
38727
|
+
});
|
|
38728
|
+
}));
|
|
38729
|
+
await Promise.race([Promise.all(warms), whenAborted$1(signal)]);
|
|
38730
|
+
}
|
|
38731
|
+
/**
|
|
38732
|
+
* Loads every bundle the scene names at once and returns as soon as all loads settled or the node
|
|
38733
|
+
* is aborted. The first load that fails while the node still runs is rethrown, so `flow` rolls
|
|
38734
|
+
* the node back; one that fails after the abort is logged, because nobody waits for it any more.
|
|
38735
|
+
*
|
|
38736
|
+
* @param ctx - Domain context of the plugin.
|
|
38737
|
+
* @param bundles - The bundles the scene being entered names, its own bundle first.
|
|
38738
|
+
* @param signal - The abort signal of the node.
|
|
38739
|
+
* @returns A promise that resolves when the loads settled or the node was aborted.
|
|
38740
|
+
* @throws {Error} Whatever the first failed load rejected with, while the node was not aborted.
|
|
38741
|
+
*/
|
|
38742
|
+
async function untilAborted(ctx, bundles, signal) {
|
|
38743
|
+
const load = {
|
|
38744
|
+
failed: false,
|
|
38745
|
+
error: void 0
|
|
38746
|
+
};
|
|
38747
|
+
const loads = bundles.map((bundle) => ctx.deps.assets.load(bundle).catch((error) => {
|
|
38748
|
+
if (!load.failed) {
|
|
38749
|
+
load.failed = true;
|
|
38750
|
+
load.error = error;
|
|
38751
|
+
}
|
|
38752
|
+
if (signal.aborted) ctx.log.warn("scenes: a cancelled bundle load failed", {
|
|
38753
|
+
bundle,
|
|
38754
|
+
error
|
|
38755
|
+
});
|
|
38756
|
+
}));
|
|
38757
|
+
await Promise.race([Promise.all(loads), whenAborted$1(signal)]);
|
|
38758
|
+
if (load.failed && !signal.aborted) throw load.error;
|
|
38759
|
+
}
|
|
38760
|
+
/**
|
|
38761
|
+
* Reads the committed state a scene's `bundles` gets: the frozen trees of the store, in a frozen
|
|
38762
|
+
* wrapper. During the scene stage no node writes, so it is the state the node is entered with.
|
|
38763
|
+
*
|
|
38764
|
+
* @param ctx - Domain context of the plugin.
|
|
38765
|
+
* @returns The committed `player` and `session`.
|
|
38766
|
+
*/
|
|
38767
|
+
function committedState(ctx) {
|
|
38768
|
+
const { player, session } = ctx.deps.model.store.snapshot();
|
|
38769
|
+
return Object.freeze({
|
|
38770
|
+
player,
|
|
38771
|
+
session
|
|
38772
|
+
});
|
|
38773
|
+
}
|
|
38774
|
+
/**
|
|
38775
|
+
* Tells whether two lists name the same bundles in the same order.
|
|
38776
|
+
*
|
|
38777
|
+
* @param left - One list.
|
|
38778
|
+
* @param right - The other list.
|
|
38779
|
+
* @returns True when both have the same names at the same places.
|
|
38780
|
+
* @example
|
|
38781
|
+
* ```ts
|
|
38782
|
+
* sameNames(["location", "ep01"], ["location", "ep02"]); // false
|
|
38783
|
+
* ```
|
|
38784
|
+
*/
|
|
38785
|
+
function sameNames(left, right) {
|
|
38786
|
+
return left.length === right.length && left.every((name, index) => name === right[index]);
|
|
38787
|
+
}
|
|
38788
|
+
/**
|
|
38789
|
+
* Takes the expected scene and clears the record, so one `expect` mounts one scene.
|
|
38790
|
+
*
|
|
38791
|
+
* @param ctx - Domain context of the plugin.
|
|
38792
|
+
* @returns The scene id that was recorded or expected, or `undefined` when there was none.
|
|
38793
|
+
*/
|
|
38794
|
+
function takePending(ctx) {
|
|
38795
|
+
const target = ctx.state.pending;
|
|
38796
|
+
ctx.state.pending = void 0;
|
|
38797
|
+
return target;
|
|
38798
|
+
}
|
|
38799
|
+
/**
|
|
38800
|
+
* Tells whether the node is a transit node of a fast walk: the walk passes through it and builds
|
|
38801
|
+
* no scene there.
|
|
38802
|
+
*
|
|
38803
|
+
* @param node - The node being entered.
|
|
38804
|
+
* @param run - The runner's mode and signal.
|
|
38805
|
+
* @returns True for a node without `rest` entered in fast mode.
|
|
38806
|
+
*/
|
|
38807
|
+
function isFastTransit(node, run) {
|
|
38808
|
+
return run.mode === "fast" && !node.rest;
|
|
38809
|
+
}
|
|
38810
|
+
/**
|
|
38811
|
+
* Reads the scene the node asks for. In fast mode a transit node only records it: the walk passes
|
|
38812
|
+
* through many of them and the picture is built once, at the rest point. In live mode a transit
|
|
38813
|
+
* node without a scene of its own takes the expected one, so a restore at a popup that waits for
|
|
38814
|
+
* its answer builds the scene under it; one with its own scene leaves the record alone.
|
|
38815
|
+
*
|
|
38816
|
+
* @param ctx - Domain context of the plugin.
|
|
38817
|
+
* @param node - The node being entered.
|
|
38818
|
+
* @param run - The runner's mode and signal.
|
|
38819
|
+
* @returns The scene id to switch to, or `undefined` when nothing has to happen.
|
|
38820
|
+
*/
|
|
38821
|
+
function targetOf(ctx, node, run) {
|
|
38822
|
+
const state = ctx.state;
|
|
38823
|
+
if (isFastTransit(node, run)) {
|
|
38824
|
+
state.pending = node.scene ?? state.pending;
|
|
38825
|
+
return;
|
|
38826
|
+
}
|
|
38827
|
+
if (!node.rest) return node.scene ?? takePending(ctx);
|
|
38828
|
+
const target = node.scene ?? state.pending;
|
|
38829
|
+
state.pending = void 0;
|
|
38830
|
+
if (target === void 0 && state.current === void 0) ctx.log.warn("scenes: a rest node was entered with no scene", { path: node.path });
|
|
38831
|
+
return target;
|
|
38832
|
+
}
|
|
38833
|
+
/**
|
|
38834
|
+
* Reads the scene an `over` node asks for. An over node takes the expected scene and clears the
|
|
38835
|
+
* record, whatever is mounted, so a restore at a popup builds the scene under it, whether the
|
|
38836
|
+
* popup rests or waits for its answer. With nothing expected it switches nothing. In fast mode an
|
|
38837
|
+
* over transit node takes nothing and leaves the record alone: a walk builds no scene on the way.
|
|
38838
|
+
* A scene the over node names itself is only warned about.
|
|
38839
|
+
*
|
|
38840
|
+
* @param ctx - Domain context of the plugin.
|
|
38841
|
+
* @param node - The over node being entered.
|
|
38842
|
+
* @param run - The runner's mode and signal.
|
|
38843
|
+
* @returns The expected scene id, or `undefined` when nothing has to happen.
|
|
38844
|
+
*/
|
|
38845
|
+
function overTargetOf(ctx, node, run) {
|
|
38846
|
+
if (node.scene !== void 0) ctx.log.warn("scenes: an over node cannot name a scene", { path: node.path });
|
|
38847
|
+
if (isFastTransit(node, run)) return void 0;
|
|
38848
|
+
return takePending(ctx);
|
|
38849
|
+
}
|
|
38850
|
+
/**
|
|
38851
|
+
* Tells whether a switch builds the next scene aside: a live switch while the world runs live and
|
|
38852
|
+
* the renderer draws. A fast walk (a restore, a test walk) and a renderer without a device build
|
|
38853
|
+
* in one block, as a walk or a headless app sees no frame between. So does a node entered live
|
|
38854
|
+
* while the world runs fast or paused by now: a restore of a repro enters its node live and walks
|
|
38855
|
+
* fast from there, and a world that skips its `layout` phase never lays the coming scene out.
|
|
38856
|
+
*
|
|
38857
|
+
* @param ctx - Domain context of the plugin.
|
|
38858
|
+
* @param run - The runner's mode and signal.
|
|
38859
|
+
* @returns True when the switch builds aside.
|
|
38860
|
+
*/
|
|
38861
|
+
function buildsAside(ctx, run) {
|
|
38862
|
+
return run.mode === "live" && ctx.deps.world.ecs.mode() === "live" && ctx.deps.renderer.host.ready();
|
|
38863
|
+
}
|
|
38864
|
+
/**
|
|
38865
|
+
* Builds the scene: aside on a live switch with a drawing renderer, resolving at the commit or
|
|
38866
|
+
* the abort; in one block otherwise, and when the world refuses to build it aside because a
|
|
38867
|
+
* projection both scenes list is declared on other layer rules (named in a dev build).
|
|
38868
|
+
*
|
|
38869
|
+
* @param ctx - Domain context of the plugin.
|
|
38870
|
+
* @param scene - The scene whose bundles are there.
|
|
38871
|
+
* @param named - The bundles the scene names, as they were awaited.
|
|
38872
|
+
* @param run - The runner's mode and signal, not aborted.
|
|
38873
|
+
* @returns A promise that resolves once the scene stands, or the node was aborted.
|
|
38874
|
+
* @throws {Error} Whatever the mount or the build threw: a `view` or an unknown projection.
|
|
38875
|
+
*/
|
|
38876
|
+
async function standUp(ctx, scene, named, run) {
|
|
38877
|
+
if (buildsAside(ctx, run)) {
|
|
38878
|
+
const building = buildAside(ctx, scene, named, run.signal);
|
|
38879
|
+
if (building !== void 0) {
|
|
38880
|
+
await building;
|
|
38881
|
+
return;
|
|
38882
|
+
}
|
|
38883
|
+
if (typeof __MOKU_GAME_DEV__ !== "undefined" && __MOKU_GAME_DEV__) ctx.log.warn("scenes:build-skipped", {
|
|
38884
|
+
scene: scene.id,
|
|
38885
|
+
from: ctx.state.current,
|
|
38886
|
+
projections: scene.projections
|
|
38887
|
+
});
|
|
38888
|
+
}
|
|
38889
|
+
swapIn(ctx, scene, named);
|
|
38890
|
+
}
|
|
38891
|
+
/**
|
|
37964
38892
|
* Switches to another scene: it reads what the scene names from the committed state, awaits all
|
|
37965
38893
|
* of it and only then builds the scene. In live mode it waits between the two for the textures of
|
|
37966
38894
|
* those bundles to reach the GPU; a fast walk does not, so tests and restores keep their pace. An
|
|
37967
|
-
* aborted node, or a plugin stopped during the wait, returns without touching anything.
|
|
38895
|
+
* aborted node, or a plugin stopped during the wait, returns without touching anything. A live
|
|
38896
|
+
* switch with a drawing renderer resolves at the commit of the scene built aside, frames later.
|
|
37968
38897
|
*
|
|
37969
38898
|
* @param ctx - Domain context of the plugin.
|
|
37970
38899
|
* @param target - Id of the scene to mount.
|
|
@@ -37980,7 +38909,7 @@ async function switchTo(ctx, target, run) {
|
|
|
37980
38909
|
if (run.mode === "live" && !run.signal.aborted) await untilWarmed(ctx, named, run.signal);
|
|
37981
38910
|
const stopped = ctx.state.teardown === void 0;
|
|
37982
38911
|
if (run.signal.aborted || stopped) return;
|
|
37983
|
-
|
|
38912
|
+
await standUp(ctx, scene, named, run);
|
|
37984
38913
|
}
|
|
37985
38914
|
/**
|
|
37986
38915
|
* Reads again what the mounted scene names, at a node that keeps it: a bundle it names now and
|
|
@@ -38016,8 +38945,8 @@ async function reread(ctx, run) {
|
|
|
38016
38945
|
* @param run - The runner's mode and signal.
|
|
38017
38946
|
* @returns A promise that resolves once the scene stands, or at once when nothing changed.
|
|
38018
38947
|
* @throws {Error} For a scene id no feature declared, and for a `bundles` that throws or returns
|
|
38019
|
-
* something that is not a list of names. A failed load
|
|
38020
|
-
* `flow` rolls the node back with the old scene still mounted.
|
|
38948
|
+
* something that is not a list of names. A failed load, a failed `mount` and a build whose
|
|
38949
|
+
* `view` throws also reject, so `flow` rolls the node back with the old scene still mounted.
|
|
38021
38950
|
*/
|
|
38022
38951
|
async function enterScene(ctx, node, run) {
|
|
38023
38952
|
const target = node.over ? overTargetOf(ctx, node, run) : targetOf(ctx, node, run);
|
|
@@ -38031,8 +38960,9 @@ async function enterScene(ctx, node, run) {
|
|
|
38031
38960
|
//#endregion
|
|
38032
38961
|
//#region src/plugins/scenes/lifecycle.ts
|
|
38033
38962
|
/**
|
|
38034
|
-
* @file scenes plugin — lifecycle: the scenes read out of the
|
|
38035
|
-
* the one `onEnter` registration, and the teardown that removes
|
|
38963
|
+
* @file scenes plugin — lifecycle: the config checked in `onInit`, the scenes read out of the
|
|
38964
|
+
* feature descriptions in `onStart`, the one `onEnter` registration, and the teardown that removes
|
|
38965
|
+
* exactly that registration.
|
|
38036
38966
|
*/
|
|
38037
38967
|
/**
|
|
38038
38968
|
* Resolves the dependency APIs `flow`, `world`, `assets`, `time`, `model` and `renderer` with
|
|
@@ -38093,6 +39023,20 @@ function readScenes(ctx) {
|
|
|
38093
39023
|
}
|
|
38094
39024
|
}
|
|
38095
39025
|
/**
|
|
39026
|
+
* Refuses a building budget a frame cannot count down, in `onInit`, so `createApp` throws: neither
|
|
39027
|
+
* `"auto"` nor a finite number above 0. A game written in plain JS passes anything, so the config
|
|
39028
|
+
* is a boundary.
|
|
39029
|
+
*
|
|
39030
|
+
* @param config - The resolved config of the plugin.
|
|
39031
|
+
* @throws {Error} When `buildMs` is neither `"auto"` nor a finite number above 0.
|
|
39032
|
+
*/
|
|
39033
|
+
function checkConfig(config) {
|
|
39034
|
+
const value = config.buildMs;
|
|
39035
|
+
if (value === "auto") return;
|
|
39036
|
+
if (isPositiveFinite(value)) return;
|
|
39037
|
+
throw new Error(`[game] scenes.buildMs is ${String(value)}.\n Give "auto" or the milliseconds a frame may spend on the next scene, a finite number above 0 such as 4.`);
|
|
39038
|
+
}
|
|
39039
|
+
/**
|
|
38096
39040
|
* Starts the plugin in `onStart`: it collects the scenes of the features and registers the one
|
|
38097
39041
|
* callback that switches them, on the `scene` stage of entering a node.
|
|
38098
39042
|
*
|
|
@@ -38111,8 +39055,9 @@ function startScenes(ctx) {
|
|
|
38111
39055
|
}
|
|
38112
39056
|
/**
|
|
38113
39057
|
* Removes the `onEnter` callback in `onStop` and forgets every scene and the bundles the mounted
|
|
38114
|
-
* one named. Nothing is unmounted
|
|
38115
|
-
* teardown context may not call another plugin, and `world.onStop` clears every
|
|
39058
|
+
* one named. A running build loses its frame callback and its switch returns. Nothing is unmounted
|
|
39059
|
+
* or discarded: a teardown context may not call another plugin, and `world.onStop` clears every
|
|
39060
|
+
* entity and the build table anyway.
|
|
38116
39061
|
*
|
|
38117
39062
|
* @param teardown - What the teardown context carries.
|
|
38118
39063
|
* @param teardown.state - The plugin state.
|
|
@@ -38126,6 +39071,8 @@ function stopScenes(teardown) {
|
|
|
38126
39071
|
const state = teardown.state;
|
|
38127
39072
|
state.teardown?.();
|
|
38128
39073
|
state.teardown = void 0;
|
|
39074
|
+
dropBuild(state.build);
|
|
39075
|
+
delete state.build;
|
|
38129
39076
|
state.scenes.clear();
|
|
38130
39077
|
state.current = void 0;
|
|
38131
39078
|
state.pending = void 0;
|
|
@@ -38170,8 +39117,6 @@ function createScenesState() {
|
|
|
38170
39117
|
teardown: void 0
|
|
38171
39118
|
};
|
|
38172
39119
|
}
|
|
38173
|
-
//#endregion
|
|
38174
|
-
//#region src/plugins/scenes/index.ts
|
|
38175
39120
|
/**
|
|
38176
39121
|
* Scenes plugin: `app.scenes.current()`, and `defineScene` for the game.
|
|
38177
39122
|
*
|
|
@@ -38193,6 +39138,9 @@ function createScenesState() {
|
|
|
38193
39138
|
* layers: { room: {} },
|
|
38194
39139
|
* projections: [wardRoom]
|
|
38195
39140
|
* });
|
|
39141
|
+
*
|
|
39142
|
+
* // A game gives the next scene a fixed 3 ms of each frame (half of it by default) while the old one stays on the screen.
|
|
39143
|
+
* createApp({ plugins: [...screen, boardFeature], pluginConfigs: { scenes: { buildMs: 3 } } });
|
|
38196
39144
|
* ```
|
|
38197
39145
|
*/
|
|
38198
39146
|
const scenesPlugin = /*#__PURE__*/ createPlugin$1("scenes", {
|
|
@@ -38204,9 +39152,11 @@ const scenesPlugin = /*#__PURE__*/ createPlugin$1("scenes", {
|
|
|
38204
39152
|
modelPlugin,
|
|
38205
39153
|
rendererPlugin
|
|
38206
39154
|
],
|
|
39155
|
+
config: { buildMs: "auto" },
|
|
38207
39156
|
events: (register) => register.map({ "scenes:changed": "The mounted scene changed" }),
|
|
38208
39157
|
createState: createScenesState,
|
|
38209
39158
|
api: createScenesApi,
|
|
39159
|
+
onInit: ({ config }) => checkConfig(config),
|
|
38210
39160
|
onStart: startScenes,
|
|
38211
39161
|
onStop: ({ state }) => stopScenes({ state })
|
|
38212
39162
|
});
|
|
@@ -52473,6 +53423,9 @@ function ownerOf(state, name) {
|
|
|
52473
53423
|
/**
|
|
52474
53424
|
* Gives the host as parent to every view of one projection that has no parent and is not held
|
|
52475
53425
|
* by a drag. The parent is queued while the phase runs, so ownership is decided by name first.
|
|
53426
|
+
* A host of the scene `world` builds aside takes only the views of that build, and a live host
|
|
53427
|
+
* none of them, until the swap (spec 31): a live view under a hidden host would vanish from the
|
|
53428
|
+
* screen, a coming one under a live host would be drawn before its scene.
|
|
52476
53429
|
*
|
|
52477
53430
|
* @param ctx - Domain context of the ui plugin.
|
|
52478
53431
|
* @param state - The jsx state.
|
|
@@ -52481,8 +53434,10 @@ function ownerOf(state, name) {
|
|
|
52481
53434
|
*/
|
|
52482
53435
|
function parentViews(ctx, state, name, host) {
|
|
52483
53436
|
const ecs = ctx.deps.world.ecs;
|
|
52484
|
-
|
|
52485
|
-
|
|
53437
|
+
const projection = ctx.deps.world.projection;
|
|
53438
|
+
const aside = projection.isBuilding(host);
|
|
53439
|
+
for (const view of projection.entitiesOf(name)) {
|
|
53440
|
+
if (ecs.has(view, Held) || projection.isBuilding(view) !== aside) continue;
|
|
52486
53441
|
if (ecs.has(view, Parent) && !state.released.has(view)) continue;
|
|
52487
53442
|
ecs.add(view, Parent({ entity: host }));
|
|
52488
53443
|
state.hosted.set(view, host);
|
|
@@ -52604,17 +53559,21 @@ function keptSlot(state, root) {
|
|
|
52604
53559
|
}
|
|
52605
53560
|
/**
|
|
52606
53561
|
* Tells whether a host element may take a root as its slot: it names the root's projection, it is
|
|
52607
|
-
* not leaving,
|
|
53562
|
+
* not leaving, it is not an element of that root itself, and both stand on the same side of a
|
|
53563
|
+
* scene build (spec 31): a root built aside never takes a slot in a live root of the old scene,
|
|
53564
|
+
* and a live root none in a root built aside. A kept host takes its coming guest after the swap.
|
|
52608
53565
|
*
|
|
52609
53566
|
* @param state - The jsx state.
|
|
52610
53567
|
* @param root - The root.
|
|
52611
53568
|
* @param host - An entity of the host set.
|
|
53569
|
+
* @param isAside - Whether an entity belongs to the scene `world` builds aside.
|
|
52612
53570
|
* @returns The host element, or `undefined` when it cannot take the root.
|
|
52613
53571
|
*/
|
|
52614
|
-
function slotFor(state, root, host) {
|
|
53572
|
+
function slotFor(state, root, host, isAside) {
|
|
52615
53573
|
const element = state.elements.get(host);
|
|
52616
53574
|
if (element === void 0 || state.exiting.has(host) || element.root === root.entity) return;
|
|
52617
|
-
|
|
53575
|
+
if (!hostsOf(element.node).includes(root.name)) return void 0;
|
|
53576
|
+
return isAside(element.root) === isAside(root.entity) ? element : void 0;
|
|
52618
53577
|
}
|
|
52619
53578
|
/**
|
|
52620
53579
|
* The host element a root is laid out in (spec 16 U3.2). A popup root never is. A root keeps its
|
|
@@ -52623,13 +53582,14 @@ function slotFor(state, root, host) {
|
|
|
52623
53582
|
*
|
|
52624
53583
|
* @param state - The jsx state.
|
|
52625
53584
|
* @param root - The root.
|
|
53585
|
+
* @param isAside - Whether an entity belongs to the scene `world` builds aside.
|
|
52626
53586
|
* @returns The slot, or `undefined` for a root laid out on the viewport.
|
|
52627
53587
|
*/
|
|
52628
|
-
function slotOf$1(state, root) {
|
|
53588
|
+
function slotOf$1(state, root, isAside) {
|
|
52629
53589
|
if (root.popup !== void 0) return void 0;
|
|
52630
53590
|
const kept = keptSlot(state, root);
|
|
52631
53591
|
if (kept !== void 0) return kept;
|
|
52632
|
-
for (const host of state.hosts) if (slotFor(state, root, host) !== void 0) return host;
|
|
53592
|
+
for (const host of state.hosts) if (slotFor(state, root, host, isAside) !== void 0) return host;
|
|
52633
53593
|
}
|
|
52634
53594
|
/**
|
|
52635
53595
|
* Where an element is drawn at rest, in root coordinates: its drawn rect (`visualRectOf`), and for
|
|
@@ -53059,7 +54019,8 @@ function createFields(ctx, links) {
|
|
|
53059
54019
|
}
|
|
53060
54020
|
/**
|
|
53061
54021
|
* Spawns the four parts of a field, each a child of the field: its pose, its `Parent`, its
|
|
53062
|
-
* visual, in that order, as an element is spawned.
|
|
54022
|
+
* visual, in that order, as an element is spawned. The parts of a field of a scene built aside
|
|
54023
|
+
* are the build's too, as the field is: held with it and dropped by a discard (spec 31).
|
|
53063
54024
|
*
|
|
53064
54025
|
* @param entity - The field entity.
|
|
53065
54026
|
* @param values - What the parts draw.
|
|
@@ -53067,15 +54028,22 @@ function createFields(ctx, links) {
|
|
|
53067
54028
|
*/
|
|
53068
54029
|
function spawnParts(entity, values) {
|
|
53069
54030
|
const ecs = ctx.deps.world.ecs;
|
|
54031
|
+
const projection = ctx.deps.world.projection;
|
|
53070
54032
|
const parent = Parent({ entity });
|
|
53071
|
-
const
|
|
54033
|
+
const aside = projection.isBuilding(entity);
|
|
54034
|
+
const spawn = (components) => {
|
|
54035
|
+
const part = ecs.spawn(UI_OWNER, components);
|
|
54036
|
+
if (aside) projection.adopt(part);
|
|
54037
|
+
return part;
|
|
54038
|
+
};
|
|
54039
|
+
const box = (part) => spawn([
|
|
53072
54040
|
Transform(part.pose),
|
|
53073
54041
|
parent,
|
|
53074
54042
|
Shape(part.shape)
|
|
53075
54043
|
]);
|
|
53076
54044
|
return {
|
|
53077
54045
|
selection: box(values.selection),
|
|
53078
|
-
text:
|
|
54046
|
+
text: spawn([
|
|
53079
54047
|
Transform(values.text.pose),
|
|
53080
54048
|
parent,
|
|
53081
54049
|
Text({
|
|
@@ -53806,18 +54774,20 @@ function nodeOf(state, element, resolvedOf, place) {
|
|
|
53806
54774
|
/**
|
|
53807
54775
|
* Every mounted root in the order a reader meets them: the popup roots first, the highest `Order`
|
|
53808
54776
|
* of them first, then the projection roots in the layer order of the scene, and by their `Order`
|
|
53809
|
-
* inside one layer, bottom first, as they draw.
|
|
54777
|
+
* inside one layer, bottom first, as they draw. A root of the scene `world` builds aside is left
|
|
54778
|
+
* out until the swap: nothing of it is on the screen yet (spec 31).
|
|
53810
54779
|
*
|
|
53811
54780
|
* @param state - The jsx state.
|
|
53812
54781
|
* @param layers - The layer names of the scene, in draw order.
|
|
54782
|
+
* @param isAside - Whether an entity belongs to the scene built aside; none leaves no root out.
|
|
53813
54783
|
* @returns The roots, sorted.
|
|
53814
54784
|
* @example
|
|
53815
54785
|
* ```ts
|
|
53816
54786
|
* sortedRoots({ roots: new Map() } as unknown as JsxState, ["ui"]); // []
|
|
53817
54787
|
* ```
|
|
53818
54788
|
*/
|
|
53819
|
-
function sortedRoots(state, layers) {
|
|
53820
|
-
const roots = [...state.roots.values()];
|
|
54789
|
+
function sortedRoots(state, layers, isAside) {
|
|
54790
|
+
const roots = [...state.roots.values()].filter((root) => isAside === void 0 || !isAside(root.entity));
|
|
53821
54791
|
const popups = roots.filter((root) => root.popup !== void 0);
|
|
53822
54792
|
const screens = roots.filter((root) => root.popup === void 0);
|
|
53823
54793
|
const indexOf = (root) => {
|
|
@@ -53842,22 +54812,24 @@ function placeOf(root, slotPose) {
|
|
|
53842
54812
|
return (rect) => drawnRect(pose, rect);
|
|
53843
54813
|
}
|
|
53844
54814
|
/**
|
|
53845
|
-
* Reads every mounted root, popups first, then the projection roots in layer order
|
|
53846
|
-
* root laid out in a slot are reported in root
|
|
54815
|
+
* Reads every mounted root, popups first, then the projection roots in layer order, and none of the
|
|
54816
|
+
* scene `world` builds aside. The rects of a root laid out in a slot are reported in root
|
|
54817
|
+
* coordinates, through the root pose of its slot.
|
|
53847
54818
|
*
|
|
53848
54819
|
* @param state - The jsx state.
|
|
53849
54820
|
* @param layers - The layer names of the scene, in draw order.
|
|
53850
54821
|
* @param resolvedOf - Reads the string `text` resolved for an entity; none in a read without a world.
|
|
53851
54822
|
* @param slotPose - The root pose of a slot, `rootPoseOf` of `renderer`; none in a read without a world.
|
|
54823
|
+
* @param isAside - Whether an entity belongs to the scene `world` builds aside: such a root is not read.
|
|
53852
54824
|
* @returns One root node, or every root under a `screen` node.
|
|
53853
54825
|
* @example
|
|
53854
54826
|
* ```ts
|
|
53855
54827
|
* readTree({ roots: new Map(), elements: new Map() } as unknown as JsxState).type; // "screen"
|
|
53856
54828
|
* ```
|
|
53857
54829
|
*/
|
|
53858
|
-
function readTree(state, layers = [], resolvedOf, slotPose) {
|
|
54830
|
+
function readTree(state, layers = [], resolvedOf, slotPose, isAside) {
|
|
53859
54831
|
const nodes = [];
|
|
53860
|
-
for (const root of sortedRoots(state, layers)) {
|
|
54832
|
+
for (const root of sortedRoots(state, layers, isAside)) {
|
|
53861
54833
|
const element = root.element === void 0 ? void 0 : state.elements.get(root.element);
|
|
53862
54834
|
if (element !== void 0) nodes.push(nodeOf(state, element, resolvedOf, placeOf(root, slotPose)));
|
|
53863
54835
|
}
|
|
@@ -53900,15 +54872,17 @@ function readingOrder(first, second) {
|
|
|
53900
54872
|
/**
|
|
53901
54873
|
* The root the keyboard works in: the first uncovered popup in reader order, else the last screen
|
|
53902
54874
|
* root, the one in the top layer. A root of the HUD layer never takes it: the HUD draws above every
|
|
53903
|
-
* overlay, so the top sheet under it does. A root on its way out does not count
|
|
54875
|
+
* overlay, so the top sheet under it does. A root on its way out does not count, nor one of the
|
|
54876
|
+
* scene `world` builds aside: nothing of it is on the screen before the swap.
|
|
53904
54877
|
*
|
|
53905
54878
|
* @param state - The jsx state.
|
|
53906
54879
|
* @param layers - The layer names of the scene, in draw order.
|
|
53907
54880
|
* @param hud - The name of the scene's HUD layer (`above: "overlays"`), none without one.
|
|
54881
|
+
* @param isAside - Whether an entity belongs to the scene built aside; none leaves no root out.
|
|
53908
54882
|
* @returns The root, or `undefined` when nothing is mounted.
|
|
53909
54883
|
*/
|
|
53910
|
-
function focusRoot(state, layers, hud) {
|
|
53911
|
-
const roots = sortedRoots(state, layers).filter((root) => root.element !== void 0 && !state.removing.has(root.entity));
|
|
54884
|
+
function focusRoot(state, layers, hud, isAside) {
|
|
54885
|
+
const roots = sortedRoots(state, layers, isAside).filter((root) => root.element !== void 0 && !state.removing.has(root.entity));
|
|
53912
54886
|
return roots.find((root) => root.popup !== void 0 && !root.covered) ?? roots.findLast((root) => root.popup === void 0 && root.layer !== hud);
|
|
53913
54887
|
}
|
|
53914
54888
|
/**
|
|
@@ -54368,7 +55342,8 @@ function zIndexOnRoot(element) {
|
|
|
54368
55342
|
};
|
|
54369
55343
|
}
|
|
54370
55344
|
/**
|
|
54371
|
-
* Runs the six rules over every live element
|
|
55345
|
+
* Runs the six rules over every live element on the screen: an element of a root `world` builds
|
|
55346
|
+
* aside is not on it before the swap (spec 31).
|
|
54372
55347
|
*
|
|
54373
55348
|
* @param ctx - Domain context of the ui plugin.
|
|
54374
55349
|
* @returns One finding per rule and element, in element order.
|
|
@@ -54377,7 +55352,7 @@ function runLint(ctx) {
|
|
|
54377
55352
|
const findings = [];
|
|
54378
55353
|
const scale = ctx.deps.renderer.viewport.size().scale;
|
|
54379
55354
|
for (const element of ctx.state.jsx.elements.values()) {
|
|
54380
|
-
if (!element.live) continue;
|
|
55355
|
+
if (!element.live || ctx.deps.world.projection.isBuilding(element.root)) continue;
|
|
54381
55356
|
for (const finding of [
|
|
54382
55357
|
tapTarget(ctx, element, scale),
|
|
54383
55358
|
textOverflow(ctx, element),
|
|
@@ -56542,6 +57517,8 @@ function createReconciler(ctx, modules, fields) {
|
|
|
56542
57517
|
let slotsRoots = -1;
|
|
56543
57518
|
let placedUnder;
|
|
56544
57519
|
let rendered = 0;
|
|
57520
|
+
const isAside = (entity) => ctx.deps.world.projection.isBuilding(entity);
|
|
57521
|
+
let released = false;
|
|
56545
57522
|
/**
|
|
56546
57523
|
* Registers the key of an element under its root, so `entityOf` and `find` answer for it. A key
|
|
56547
57524
|
* `ui` gave a node of its own, a spacer's, is never registered.
|
|
@@ -56570,7 +57547,9 @@ function createReconciler(ctx, modules, fields) {
|
|
|
56570
57547
|
return range.rowStyle(parent, node, modules.styles.resolveElement(styleOf(node), is));
|
|
56571
57548
|
}
|
|
56572
57549
|
/**
|
|
56573
|
-
* Creates one element and its subtree.
|
|
57550
|
+
* Creates one element and its subtree. An element of a root built aside is marked as the build's
|
|
57551
|
+
* right after its spawn, before `applyFirst` gives it a visual: the renderer holds its view, and a
|
|
57552
|
+
* discard takes it with the rest (spec 31).
|
|
56574
57553
|
*
|
|
56575
57554
|
* @param root - The root being reconciled.
|
|
56576
57555
|
* @param parent - The parent element, or nothing for a root element.
|
|
@@ -56583,6 +57562,7 @@ function createReconciler(ctx, modules, fields) {
|
|
|
56583
57562
|
function enter(root, parent, node, identity, instance, index) {
|
|
56584
57563
|
checkTag(node);
|
|
56585
57564
|
const entity = ecs.spawn(UI_OWNER, []);
|
|
57565
|
+
if (isAside(root.entity)) ctx.deps.world.projection.adopt(entity);
|
|
56586
57566
|
const is = isFlagsOf(node, {
|
|
56587
57567
|
pressed: false,
|
|
56588
57568
|
hover: false,
|
|
@@ -57032,13 +58012,18 @@ function createReconciler(ctx, modules, fields) {
|
|
|
57032
58012
|
root.needsSolve = true;
|
|
57033
58013
|
}
|
|
57034
58014
|
/**
|
|
57035
|
-
* Starts the exit of an element and of everything under it.
|
|
58015
|
+
* Starts the exit of an element and of everything under it. An element of a root built aside was
|
|
58016
|
+
* never seen: it goes at once, with no exit (spec 31).
|
|
57036
58017
|
*
|
|
57037
58018
|
* @param element - The element that left the description.
|
|
57038
58019
|
* @param cause - Why it leaves: `"change"` for a commit, `"mount"` when it goes with its screen.
|
|
57039
58020
|
* @param leads - The element is the top of a root that goes with its screen.
|
|
57040
58021
|
*/
|
|
57041
58022
|
function exitElement(element, cause, leads = false) {
|
|
58023
|
+
if (isAside(element.root)) {
|
|
58024
|
+
dropElement(element);
|
|
58025
|
+
return;
|
|
58026
|
+
}
|
|
57042
58027
|
forgetSubtree(element);
|
|
57043
58028
|
forgetInstances(state, element.identity);
|
|
57044
58029
|
state.exiting.add(element.entity);
|
|
@@ -57128,6 +58113,17 @@ function createReconciler(ctx, modules, fields) {
|
|
|
57128
58113
|
ecs.despawn(element.entity);
|
|
57129
58114
|
}
|
|
57130
58115
|
/**
|
|
58116
|
+
* Drops an element and everything under it at once, with no exit: its identity, its key and the
|
|
58117
|
+
* local state of its components are forgotten, its nodes freed and its entities despawned.
|
|
58118
|
+
*
|
|
58119
|
+
* @param element - An element of a root built aside that leaves before the swap.
|
|
58120
|
+
*/
|
|
58121
|
+
function dropElement(element) {
|
|
58122
|
+
forgetSubtree(element);
|
|
58123
|
+
forgetInstances(state, element.identity);
|
|
58124
|
+
despawnTree(element);
|
|
58125
|
+
}
|
|
58126
|
+
/**
|
|
57131
58127
|
* Despawns every exiting element whose motions finished, and closes a popup whose root is gone.
|
|
57132
58128
|
*/
|
|
57133
58129
|
function sweep() {
|
|
@@ -57291,7 +58287,7 @@ function createReconciler(ctx, modules, fields) {
|
|
|
57291
58287
|
*/
|
|
57292
58288
|
function warnTaken(root, slot) {
|
|
57293
58289
|
for (const host of state.hosts) {
|
|
57294
|
-
const element = host === slot ? void 0 : slotFor(state, root, host);
|
|
58290
|
+
const element = host === slot ? void 0 : slotFor(state, root, host, isAside);
|
|
57295
58291
|
const once = `slot-taken:${root.name}`;
|
|
57296
58292
|
if (element === void 0 || element.warned.has(once)) continue;
|
|
57297
58293
|
element.warned.add(once);
|
|
@@ -57323,7 +58319,7 @@ function createReconciler(ctx, modules, fields) {
|
|
|
57323
58319
|
slotsRoots = state.roots.size;
|
|
57324
58320
|
for (const root of state.roots.values()) {
|
|
57325
58321
|
if (state.removing.has(root.entity)) continue;
|
|
57326
|
-
const slot = isCyclic(root) ? void 0 : slotOf$1(state, root);
|
|
58322
|
+
const slot = isCyclic(root) ? void 0 : slotOf$1(state, root, isAside);
|
|
57327
58323
|
if (typeof __MOKU_GAME_DEV__ !== "undefined" && __MOKU_GAME_DEV__ && slot !== void 0) warnTaken(root, slot);
|
|
57328
58324
|
if (slot === root.slot) continue;
|
|
57329
58325
|
root.slot = slot;
|
|
@@ -57458,7 +58454,9 @@ function createReconciler(ctx, modules, fields) {
|
|
|
57458
58454
|
* Keeps an element whose spawn has just been applied: the world calls it from the `Box` hook, at
|
|
57459
58455
|
* the flush of phase `layout`. Its enter hook waits for `playEntered`, so the game's systems see
|
|
57460
58456
|
* the entity first (F48). The hook itself plays at once, so they see the start pose it writes (F49);
|
|
57461
|
-
* `ui.enter` plays it again when one of them changed what it read.
|
|
58457
|
+
* `ui.enter` plays it again when one of them changed what it read. An element of a scene built
|
|
58458
|
+
* aside plays nothing here: its enter waits for the swap, and `poseReleased` plays it at the same
|
|
58459
|
+
* point of the swap frame (spec 31).
|
|
57462
58460
|
*
|
|
57463
58461
|
* @param entity - The entity the `Box` was attached to.
|
|
57464
58462
|
*/
|
|
@@ -57468,21 +58466,45 @@ function createReconciler(ctx, modules, fields) {
|
|
|
57468
58466
|
else entering.list.push(entity);
|
|
57469
58467
|
entering.count += 1;
|
|
57470
58468
|
const element = state.elements.get(entity);
|
|
57471
|
-
if (arrives(element)) modules.layout.pose(element, overlayOf(element) === void 0 ? void 0 : createSpan());
|
|
58469
|
+
if (arrives(element) && !isAside(entity)) modules.layout.pose(element, overlayOf(element) === void 0 ? void 0 : createSpan());
|
|
58470
|
+
}
|
|
58471
|
+
/**
|
|
58472
|
+
* Plays the enter hooks of the elements a committed build released, at the end of the swap
|
|
58473
|
+
* frame's solve, the point of the frame where a `Box` lands: the game's systems of `sync` see the
|
|
58474
|
+
* start pose they write, as after a mount (F49), and `ui.enter` keeps the play or plays it again
|
|
58475
|
+
* (F48). An element of a build still running, one that played already and a row a range change
|
|
58476
|
+
* scrolled in play nothing here.
|
|
58477
|
+
*/
|
|
58478
|
+
function poseReleased() {
|
|
58479
|
+
const entering = state.entering;
|
|
58480
|
+
for (let index = 0; index < entering.count; index += 1) {
|
|
58481
|
+
const entity = entering.list[index];
|
|
58482
|
+
const element = entity === void 0 ? void 0 : state.elements.get(entity);
|
|
58483
|
+
if (!arrives(element) || element.posed !== void 0 || isAside(element.entity)) continue;
|
|
58484
|
+
modules.layout.pose(element, overlayOf(element) === void 0 ? void 0 : createSpan());
|
|
58485
|
+
}
|
|
57472
58486
|
}
|
|
57473
58487
|
/**
|
|
57474
58488
|
* Plays the enter hooks queued since the last call, in the order the elements came. The `ui.enter`
|
|
57475
58489
|
* system calls it in phase `sync`: after the game's systems, which may have written what a hook
|
|
57476
58490
|
* reads (a list's scroll), and before `renderer.sync`, so an element is never drawn at its rest
|
|
57477
|
-
* pose by mistake. An element despawned in between plays nothing.
|
|
58491
|
+
* pose by mistake. An element despawned in between plays nothing. An element of a scene built
|
|
58492
|
+
* aside stays queued, in its order, and plays in the first call after the swap (spec 31): its
|
|
58493
|
+
* enter, posed by `poseReleased` in that frame's solve, its loop and the level of an overlay
|
|
58494
|
+
* start in the frame the scene is first drawn.
|
|
57478
58495
|
*/
|
|
57479
58496
|
function playEntered() {
|
|
57480
58497
|
const entering = state.entering;
|
|
58498
|
+
let held = 0;
|
|
57481
58499
|
for (let index = 0; index < entering.count; index += 1) {
|
|
57482
58500
|
const entity = entering.list[index];
|
|
57483
|
-
if (entity
|
|
58501
|
+
if (entity === void 0) continue;
|
|
58502
|
+
if (isAside(entity)) {
|
|
58503
|
+
entering.list[held] = entity;
|
|
58504
|
+
held += 1;
|
|
58505
|
+
} else playEnter(entity);
|
|
57484
58506
|
}
|
|
57485
|
-
entering.count =
|
|
58507
|
+
entering.count = held;
|
|
57486
58508
|
}
|
|
57487
58509
|
/**
|
|
57488
58510
|
* Plays the enter hook of an element whose spawn has been applied, once. A row a range change
|
|
@@ -57623,13 +58645,22 @@ function createReconciler(ctx, modules, fields) {
|
|
|
57623
58645
|
/**
|
|
57624
58646
|
* Reconciles one dirty root: reads its current tree and projection name, then diffs it. The root
|
|
57625
58647
|
* stays dirty while the layout engine is not loaded, and a failure is logged for this root only.
|
|
58648
|
+
* A root of the scene `world` builds aside is reconciled like any other and counted, so the
|
|
58649
|
+
* reconcile after the swap finds it (spec 31).
|
|
57626
58650
|
*
|
|
57627
58651
|
* @param root - A root with work this frame.
|
|
57628
58652
|
*/
|
|
57629
58653
|
function reconcileDirtyRoot(root) {
|
|
58654
|
+
if (root.aside === void 0 && isAside(root.entity)) {
|
|
58655
|
+
root.aside = true;
|
|
58656
|
+
state.asideRoots += 1;
|
|
58657
|
+
}
|
|
57630
58658
|
const tree = ecs.get(root.entity, Tree);
|
|
57631
58659
|
const place = ctx.deps.world.projection.keyOf(root.entity);
|
|
57632
|
-
if (place !== void 0
|
|
58660
|
+
if (place !== void 0 && place.projection !== root.name) {
|
|
58661
|
+
root.name = place.projection;
|
|
58662
|
+
hostsMoved(state);
|
|
58663
|
+
}
|
|
57633
58664
|
if (tree !== void 0) root.tree = tree.node;
|
|
57634
58665
|
refreshRootOrder(root);
|
|
57635
58666
|
root.dirty = !modules.layout.loaded();
|
|
@@ -57641,16 +58672,55 @@ function createReconciler(ctx, modules, fields) {
|
|
|
57641
58672
|
}
|
|
57642
58673
|
}
|
|
57643
58674
|
/**
|
|
57644
|
-
*
|
|
57645
|
-
*
|
|
57646
|
-
*
|
|
57647
|
-
*
|
|
58675
|
+
* Finds the roots the last scene build committed: a root counted aside that `world` builds no more
|
|
58676
|
+
* loses its mark, the slots are given out again, so a kept host takes its coming guest in the
|
|
58677
|
+
* swap frame, and this frame's solve poses the held enters (spec 31). A root a discard dropped is
|
|
58678
|
+
* gone from the registry and is not counted.
|
|
58679
|
+
*/
|
|
58680
|
+
function settleAside() {
|
|
58681
|
+
if (state.asideRoots === 0) return;
|
|
58682
|
+
let left = 0;
|
|
58683
|
+
let committed = false;
|
|
58684
|
+
for (const root of state.roots.values()) {
|
|
58685
|
+
if (root.aside === void 0) continue;
|
|
58686
|
+
if (isAside(root.entity)) left += 1;
|
|
58687
|
+
else {
|
|
58688
|
+
delete root.aside;
|
|
58689
|
+
committed = true;
|
|
58690
|
+
}
|
|
58691
|
+
}
|
|
58692
|
+
state.asideRoots = left;
|
|
58693
|
+
if (!committed) return;
|
|
58694
|
+
hostsMoved(state);
|
|
58695
|
+
released = true;
|
|
58696
|
+
}
|
|
58697
|
+
/**
|
|
58698
|
+
* The part of a scene build `ui` does, which `world` asks through `onBuild` (spec 31): done when no
|
|
58699
|
+
* root of the build is dirty (its tree not diffed yet, or the layout engine not loaded) and none
|
|
58700
|
+
* waits for its solve. A root whose first diff threw has no element to solve and does not hold
|
|
58701
|
+
* the swap.
|
|
58702
|
+
*
|
|
58703
|
+
* @returns True when every root built aside is reconciled and solved.
|
|
58704
|
+
*/
|
|
58705
|
+
function asideBuilt() {
|
|
58706
|
+
for (const root of state.roots.values()) {
|
|
58707
|
+
const solves = root.needsSolve && root.element !== void 0 && lookup(root.element) !== void 0;
|
|
58708
|
+
if ((root.dirty || solves) && isAside(root.entity)) return false;
|
|
58709
|
+
}
|
|
58710
|
+
return true;
|
|
58711
|
+
}
|
|
58712
|
+
/**
|
|
58713
|
+
* The first of the two systems of phase `layout`: the sweep, the roots a scene build committed, the
|
|
58714
|
+
* roots a restore asked to build again, the roots a scene placed again, the popups that may leave,
|
|
58715
|
+
* the pull of the text being typed, the scroll offsets and the windowed ranges, the bound texts
|
|
58716
|
+
* that show a string of another size, the viewport, the dirty roots and the hosted views.
|
|
57648
58717
|
*
|
|
57649
58718
|
* @param time - The frame's `Time`: the scrolls move by its `delta`.
|
|
57650
58719
|
*/
|
|
57651
58720
|
function reconcile(time = NO_TIME) {
|
|
57652
58721
|
sweep();
|
|
57653
58722
|
state.reconciles += 1;
|
|
58723
|
+
settleAside();
|
|
57654
58724
|
if (state.remount) remountRoots();
|
|
57655
58725
|
reorderRoots();
|
|
57656
58726
|
settlePopups(ctx, state, unmountRoot);
|
|
@@ -57690,7 +58760,8 @@ function createReconciler(ctx, modules, fields) {
|
|
|
57690
58760
|
* The second system of phase `layout`: every root takes its slot, then one solve per marked
|
|
57691
58761
|
* root, a root in a slot after the root of its slot and in its content box, then the rects, the
|
|
57692
58762
|
* rest poses and the hooks, the scrolls settled on what the solve and the render gave them, the
|
|
57693
|
-
* hosting of the hosts that came,
|
|
58763
|
+
* hosting of the hosts that came, a sweep of what left with nothing to wait for, and, in the swap
|
|
58764
|
+
* frame of a scene build, the start pose of the enters it held. The hints released since the last frame step are dropped
|
|
57694
58765
|
* at its end, so the `change.Box` and `change.Transform` hooks the solve plays get the hint the
|
|
57695
58766
|
* reconcile routed, and the next frame starts with none.
|
|
57696
58767
|
*
|
|
@@ -57711,6 +58782,10 @@ function createReconciler(ctx, modules, fields) {
|
|
|
57711
58782
|
state.risen.clear();
|
|
57712
58783
|
}
|
|
57713
58784
|
if (state.exiting.size > 0 || state.removing.size > 0) sweep();
|
|
58785
|
+
if (released) {
|
|
58786
|
+
released = false;
|
|
58787
|
+
poseReleased();
|
|
58788
|
+
}
|
|
57714
58789
|
state.hints.length = 0;
|
|
57715
58790
|
writeCounters();
|
|
57716
58791
|
}
|
|
@@ -57764,7 +58839,9 @@ function createReconciler(ctx, modules, fields) {
|
|
|
57764
58839
|
* nodes and despawns the root entity. A popup leaves by an event; a projection root leaves the
|
|
57765
58840
|
* way its view did, which the world still answers in its removal hook: `"mount"` with its scene,
|
|
57766
58841
|
* a load or a restore. A root that goes with its screen ends with the exit of its top element
|
|
57767
|
-
* when that plays one, so a longer exit under it does not hold it and its bundles (F55).
|
|
58842
|
+
* when that plays one, so a longer exit under it does not hold it and its bundles (F55). A root of
|
|
58843
|
+
* a scene built aside was never seen: a discard (`"discard"`), or its key leaving before the swap,
|
|
58844
|
+
* drops it at once with no exit (spec 31).
|
|
57768
58845
|
*
|
|
57769
58846
|
* @param entity - The root entity.
|
|
57770
58847
|
*/
|
|
@@ -57773,6 +58850,10 @@ function createReconciler(ctx, modules, fields) {
|
|
|
57773
58850
|
if (root === void 0 || state.removing.has(entity)) return;
|
|
57774
58851
|
const element = root.element === void 0 ? void 0 : lookup(root.element);
|
|
57775
58852
|
const cause = root.popup === void 0 ? ctx.deps.world.projection.causeOf(entity) ?? "change" : "change";
|
|
58853
|
+
if (cause === "discard" || isAside(entity)) {
|
|
58854
|
+
dropRoot(root, element);
|
|
58855
|
+
return;
|
|
58856
|
+
}
|
|
57776
58857
|
state.removing.add(entity);
|
|
57777
58858
|
hostsMoved(state);
|
|
57778
58859
|
if (element !== void 0) {
|
|
@@ -57782,6 +58863,20 @@ function createReconciler(ctx, modules, fields) {
|
|
|
57782
58863
|
ctx.deps.time.wake();
|
|
57783
58864
|
}
|
|
57784
58865
|
/**
|
|
58866
|
+
* Drops a root of a scene built aside at once: its elements are forgotten and despawned with no
|
|
58867
|
+
* exit, and the root leaves the registry. The world despawns the root entity itself: it is its
|
|
58868
|
+
* view, on its way out.
|
|
58869
|
+
*
|
|
58870
|
+
* @param root - The root.
|
|
58871
|
+
* @param element - Its root element, if it has one.
|
|
58872
|
+
*/
|
|
58873
|
+
function dropRoot(root, element) {
|
|
58874
|
+
if (element !== void 0) dropElement(element);
|
|
58875
|
+
state.roots.delete(root.entity);
|
|
58876
|
+
state.byKey.delete(root.entity);
|
|
58877
|
+
hostsMoved(state);
|
|
58878
|
+
}
|
|
58879
|
+
/**
|
|
57785
58880
|
* Re-resolves the style of one element whose pointer or focus flag changed: a press, a release,
|
|
57786
58881
|
* the mouse coming over it or leaving it, the keyboard focus arriving or leaving. A new rect
|
|
57787
58882
|
* waits for the solve; a new look and a new rest pose apply now. Only this element is restyled:
|
|
@@ -57818,7 +58913,8 @@ function createReconciler(ctx, modules, fields) {
|
|
|
57818
58913
|
queueEnter,
|
|
57819
58914
|
playEntered,
|
|
57820
58915
|
sweep,
|
|
57821
|
-
converged
|
|
58916
|
+
converged,
|
|
58917
|
+
asideBuilt
|
|
57822
58918
|
};
|
|
57823
58919
|
}
|
|
57824
58920
|
//#endregion
|
|
@@ -57894,9 +58990,10 @@ function createJsxApi(ctx, modules) {
|
|
|
57894
58990
|
const state = ctx.state.jsx;
|
|
57895
58991
|
const sceneLayers = sceneLayersOf(ctx);
|
|
57896
58992
|
const layerNames = () => sceneLayers().names;
|
|
58993
|
+
const isAside = (entity) => ctx.deps.world.projection.isBuilding(entity);
|
|
57897
58994
|
const topRoot = () => {
|
|
57898
58995
|
const scene = sceneLayers();
|
|
57899
|
-
return focusRoot(state, scene.names, scene.hud);
|
|
58996
|
+
return focusRoot(state, scene.names, scene.hud, isAside);
|
|
57900
58997
|
};
|
|
57901
58998
|
const fields = createFields(ctx, {
|
|
57902
58999
|
layout: modules.layout,
|
|
@@ -57907,13 +59004,13 @@ function createJsxApi(ctx, modules) {
|
|
|
57907
59004
|
const resolvedOf = (entity) => ctx.deps.world.ecs.get(entity, Text)?.resolved;
|
|
57908
59005
|
const lookup = (entity) => state.elements.get(entity);
|
|
57909
59006
|
const find = (key) => {
|
|
57910
|
-
for (const root of sortedRoots(state, layerNames())) {
|
|
59007
|
+
for (const root of sortedRoots(state, layerNames(), isAside)) {
|
|
57911
59008
|
const entity = state.byKey.get(root.entity)?.get(key);
|
|
57912
59009
|
if (entity !== void 0 && !state.exiting.has(entity)) return entity;
|
|
57913
59010
|
}
|
|
57914
59011
|
};
|
|
57915
59012
|
return {
|
|
57916
|
-
tree: () => readTree(state, layerNames(), resolvedOf, (slot) => rootPoseOf(ctx.deps.world.ecs, slot)),
|
|
59013
|
+
tree: () => readTree(state, layerNames(), resolvedOf, (slot) => rootPoseOf(ctx.deps.world.ecs, slot), isAside),
|
|
57917
59014
|
find,
|
|
57918
59015
|
lint: () => runLint(ctx),
|
|
57919
59016
|
fill: (key, value) => {
|
|
@@ -57974,6 +59071,7 @@ function createJsxApi(ctx, modules) {
|
|
|
57974
59071
|
queueEnter: frame.queueEnter,
|
|
57975
59072
|
playEntered: frame.playEntered,
|
|
57976
59073
|
converged: frame.converged,
|
|
59074
|
+
asideBuilt: frame.asideBuilt,
|
|
57977
59075
|
key: (input) => focus.key(input),
|
|
57978
59076
|
blur: focus.blur
|
|
57979
59077
|
};
|
|
@@ -58262,6 +59360,18 @@ function writeRest(ctx, element, parent) {
|
|
|
58262
59360
|
if (visual !== void 0) projection.setRest(element.entity, visual.component, visual.value);
|
|
58263
59361
|
}
|
|
58264
59362
|
/**
|
|
59363
|
+
* Tells whether an element waits for the swap of a scene `world` builds aside (spec 31): it has not
|
|
59364
|
+
* entered, and it belongs to the build. Its hooks wait for its enter at the swap; a change before
|
|
59365
|
+
* it plays no motion and is written at once, so the enter starts from where it stands.
|
|
59366
|
+
*
|
|
59367
|
+
* @param ctx - Domain context of the ui plugin.
|
|
59368
|
+
* @param element - The element a hook would play on.
|
|
59369
|
+
* @returns True while its motions are held.
|
|
59370
|
+
*/
|
|
59371
|
+
function isHeld(ctx, element) {
|
|
59372
|
+
return !element.entered && ctx.deps.world.projection.isBuilding(element.entity);
|
|
59373
|
+
}
|
|
59374
|
+
/**
|
|
58265
59375
|
* Asks the projection for the handle of a ui-owned entity. A foreign owner is an error in the
|
|
58266
59376
|
* log and the caller writes the value directly.
|
|
58267
59377
|
*
|
|
@@ -58569,6 +59679,7 @@ function noteEndless(element, motion) {
|
|
|
58569
59679
|
* Plays the `change` hook of one extra component whose fields changed. The motion the last change
|
|
58570
59680
|
* of that name started is cancelled first, so its value stays where it is and the new motion
|
|
58571
59681
|
* starts from there; the new motion is kept with the element's handles, so an exit waits for it.
|
|
59682
|
+
* An element held for the swap of a scene build plays none: the caller writes the fields.
|
|
58572
59683
|
*
|
|
58573
59684
|
* @param ctx - Domain context of the ui plugin.
|
|
58574
59685
|
* @param element - The element that carries the extra.
|
|
@@ -58587,7 +59698,7 @@ function playExtraChange(ctx, element, name, values, hint) {
|
|
|
58587
59698
|
element.extraHandles.delete(name);
|
|
58588
59699
|
}
|
|
58589
59700
|
const hook = element.motion?.change?.[name];
|
|
58590
|
-
if (hook === void 0) return false;
|
|
59701
|
+
if (hook === void 0 || isHeld(ctx, element)) return false;
|
|
58591
59702
|
const motion = play(ctx, element, (view) => hook(view, values.previous, values.next, hint));
|
|
58592
59703
|
if (motion === void 0) return false;
|
|
58593
59704
|
element.extraHandles.set(name, motion);
|
|
@@ -58638,8 +59749,9 @@ function still(element) {
|
|
|
58638
59749
|
* 0 ms rest track. That track writes the pose as it starts, so the pivot and the position land in
|
|
58639
59750
|
* the same frame, and ends at the next frame step, where an additive loop re-bases on the new
|
|
58640
59751
|
* rest. An element that has not spawned yet only records it, and so does one whose
|
|
58641
|
-
* `change.Box` motion already played.
|
|
58642
|
-
*
|
|
59752
|
+
* `change.Box` motion already played. An element held for the swap of a scene build takes the
|
|
59753
|
+
* pose at once, with no hook and no track (spec 31). The content of a scroll keeps its `Transform`
|
|
59754
|
+
* and records no rest with the world: the scroll step owns it.
|
|
58643
59755
|
*
|
|
58644
59756
|
* @param ctx - Domain context of the ui plugin.
|
|
58645
59757
|
* @param element - The element whose rect, style or fit may have changed.
|
|
@@ -58655,6 +59767,13 @@ function repose(ctx, element, parent, hooked, hint) {
|
|
|
58655
59767
|
if (element.type === "content") return;
|
|
58656
59768
|
ctx.deps.world.projection.setRest(element.entity, Transform, next);
|
|
58657
59769
|
if (!element.live || hooked) return;
|
|
59770
|
+
if (isHeld(ctx, element)) {
|
|
59771
|
+
ctx.deps.world.ecs.set(element.entity, Transform, {
|
|
59772
|
+
...next,
|
|
59773
|
+
pivot: { ...next.pivot }
|
|
59774
|
+
});
|
|
59775
|
+
return;
|
|
59776
|
+
}
|
|
58658
59777
|
const hook = element.motion?.change?.Transform;
|
|
58659
59778
|
if (hook !== void 0) {
|
|
58660
59779
|
play(ctx, element, (view) => hook(view, previous, next, hint));
|
|
@@ -60195,7 +61314,7 @@ function createLayoutApi(ctx) {
|
|
|
60195
61314
|
loop: (element) => startLoop(ctx, element),
|
|
60196
61315
|
change: (element, previous, hint) => {
|
|
60197
61316
|
const hook = element.motion?.change?.Box;
|
|
60198
|
-
if (hook === void 0) return false;
|
|
61317
|
+
if (hook === void 0 || isHeld(ctx, element)) return false;
|
|
60199
61318
|
play(ctx, element, (view) => hook(view, previous, element.rect, hint));
|
|
60200
61319
|
return true;
|
|
60201
61320
|
},
|
|
@@ -60771,8 +61890,9 @@ function registerComponents(ctx, jsx) {
|
|
|
60771
61890
|
* step, the three tap listeners (the `LocalWrite` patch, the pointer tap that clears the keyboard
|
|
60772
61891
|
* focus, and the tap that starts or ends the editing of a text field), the pointer door that opens
|
|
60773
61892
|
* the keyboard inside the DOM listener, the key listener of the focus and `LocalWrite` as an input
|
|
60774
|
-
* control, so the cursor shows a hand over a local-state button
|
|
60775
|
-
*
|
|
61893
|
+
* control, so the cursor shows a hand over a local-state button, and `ui`'s part of a scene build
|
|
61894
|
+
* (`onBuild`: the roots built aside are reconciled and solved, spec 31). On an agent page also the
|
|
61895
|
+
* tap counter of `DebugTrigger` and its check of a view that takes no press.
|
|
60776
61896
|
*
|
|
60777
61897
|
* The `Box` hook plays an element's enter hook and queues it, so the game's systems see its start
|
|
60778
61898
|
* pose (F49); `ui.enter` starts its loop after them and before the frame is drawn, and plays the
|
|
@@ -60804,7 +61924,7 @@ function openRegistrations(ctx, jsx, layout) {
|
|
|
60804
61924
|
})), ecs.onAdded(Tree, (entity) => {
|
|
60805
61925
|
const place = projection.keyOf(entity);
|
|
60806
61926
|
jsx.mountRoot(entity, place?.projection ?? "ui", ecs.get(entity, Layer)?.name ?? "ui");
|
|
60807
|
-
}), ecs.onRemoved(Tree, (entity) => jsx.unmountRoot(entity)), ecs.onAdded(Box, (entity) => jsx.queueEnter(entity)), projection.onConverged(jsx.converged), ecs.onAdded(Pressed, (entity) => jsx.markPointer(entity, "pressed", true)), ecs.onRemoved(Pressed, (entity) => jsx.markPointer(entity, "pressed", false)), ecs.onAdded(PointerOver, (entity) => jsx.markPointer(entity, "hover", true)), ecs.onRemoved(PointerOver, (entity) => jsx.markPointer(entity, "hover", false)), ecs.onRemoved(Parent, () => hostsMoved(ctx.state.jsx)), ecs.onRemoved(Held, () => hostsMoved(ctx.state.jsx)), ctx.deps.flow.fx.handle("popup", layout.popupHandler(jsx)), ctx.deps.flow.fx.handle("guide", layout.guideHandler()), ctx.deps.flow.fx.onHint((hint) => {
|
|
61927
|
+
}), ecs.onRemoved(Tree, (entity) => jsx.unmountRoot(entity)), ecs.onAdded(Box, (entity) => jsx.queueEnter(entity)), projection.onConverged(jsx.converged), projection.onBuild(jsx.asideBuilt), ecs.onAdded(Pressed, (entity) => jsx.markPointer(entity, "pressed", true)), ecs.onRemoved(Pressed, (entity) => jsx.markPointer(entity, "pressed", false)), ecs.onAdded(PointerOver, (entity) => jsx.markPointer(entity, "hover", true)), ecs.onRemoved(PointerOver, (entity) => jsx.markPointer(entity, "hover", false)), ecs.onRemoved(Parent, () => hostsMoved(ctx.state.jsx)), ecs.onRemoved(Held, () => hostsMoved(ctx.state.jsx)), ctx.deps.flow.fx.handle("popup", layout.popupHandler(jsx)), ctx.deps.flow.fx.handle("guide", layout.guideHandler()), ctx.deps.flow.fx.onHint((hint) => {
|
|
60808
61928
|
ctx.state.jsx.hints.push(hint);
|
|
60809
61929
|
}), ctx.deps.input.onTap(jsx.applyTap), ctx.deps.input.onTap(() => jsx.blur()), ctx.deps.input.onTap(jsx.tapped), ctx.deps.input.onPointer(jsx.pointer), ctx.deps.input.onKey(jsx.key), ctx.deps.input.controls.add(LocalWrite), () => jsx.releaseHosted(), () => ecs.despawnOwnedBy(UI_OWNER));
|
|
60810
61930
|
if (typeof __MOKU_GAME_AGENT__ !== "undefined" && __MOKU_GAME_AGENT__) ctx.state.layout.cleanups.push(ctx.deps.input.onTap((entity) => debugTap(ctx, entity)), ecs.onAdded(DebugTrigger, (entity) => checkTrigger(ctx, entity)));
|
|
@@ -60863,6 +61983,7 @@ function stopUi(state) {
|
|
|
60863
61983
|
state.jsx.released.clear();
|
|
60864
61984
|
state.jsx.scrolls.clear();
|
|
60865
61985
|
state.jsx.remount = false;
|
|
61986
|
+
state.jsx.asideRoots = 0;
|
|
60866
61987
|
state.jsx.hints.length = 0;
|
|
60867
61988
|
state.jsx.entering.list.length = 0;
|
|
60868
61989
|
state.jsx.entering.count = 0;
|
|
@@ -61253,7 +62374,8 @@ function createJsxState() {
|
|
|
61253
62374
|
written: 0,
|
|
61254
62375
|
debugTaps: [],
|
|
61255
62376
|
debugWarned: false,
|
|
61256
|
-
overlays: emptySet()
|
|
62377
|
+
overlays: emptySet(),
|
|
62378
|
+
asideRoots: 0
|
|
61257
62379
|
};
|
|
61258
62380
|
}
|
|
61259
62381
|
//#endregion
|
|
@@ -62838,6 +63960,7 @@ function cubicBezier(x1, y1, x2, y2) {
|
|
|
62838
63960
|
* | assets | preloadDepth | 2 |
|
|
62839
63961
|
* | assets | baseUrl | undefined |
|
|
62840
63962
|
* | assets | io | undefined, the browser fetch and the renderer textures |
|
|
63963
|
+
* | scenes | buildMs | "auto", half of a frame (500 / time.rate().fps), read at each build start; a number fixes it |
|
|
62841
63964
|
* | anim | maxTracks | 2000 |
|
|
62842
63965
|
* | anim | reducedMotion | false |
|
|
62843
63966
|
* | i18n | locale | "en" |
|