@moku-labs/game 0.15.28 → 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-BJ4gG5WZ.d.mts → commands-BOxMqhVe.d.mts} +5 -5
- package/dist/control.d.mts +4 -4
- package/dist/{define-CVYadV_I.d.mts → define-CE1uxbe9.d.mts} +2 -2
- package/dist/{headless-DBYmnluy.d.mts → headless-f31cEZ7f.d.mts} +1 -1
- package/dist/{index-Dn_v0Fqs.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-DnjJ4Zo9.d.mts → runtime-HwTBjkIL.d.mts} +1 -1
- package/dist/{src-DPvSTM8O.mjs → src-wyJYr12n.mjs} +1703 -602
- package/dist/testing.d.mts +4 -4
- package/dist/testing.mjs +1 -1
- package/dist/{types-bEJEga5m.d.mts → types-BAp9HdR4.d.mts} +10 -2
- package/dist/{types-DTetCcBC.d.mts → types-CgE5L_7f.d.mts} +2 -2
- package/dist/{types-Bsa1nFyU.d.mts → types-Cyu_jKop.d.mts} +4 -4
- package/dist/{types-C_nC_M4p.d.mts → types-D27I4Q7b.d.mts} +312 -22
- package/dist/{types-C76NQm-h.d.mts → types-DjQkfbfZ.d.mts} +2 -2
- package/dist/{visual-BMRHt4IB.d.mts → visual-DhNLNtl8.d.mts} +6 -6
- package/dist/visual.d.mts +1 -1
- package/package.json +1 -1
|
@@ -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,6 +11270,7 @@ 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
|
}
|
|
@@ -11286,8 +11299,46 @@ function isCurrent$1(mounted, snapshot, options) {
|
|
|
11286
11299
|
return seen !== void 0 && !options.direct && !options.force && !options.converge && !options.settle && seen.player === snapshot.player && seen.session === snapshot.session;
|
|
11287
11300
|
}
|
|
11288
11301
|
/**
|
|
11289
|
-
*
|
|
11290
|
-
*
|
|
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.
|
|
11291
11342
|
*
|
|
11292
11343
|
* @param pctx - Domain context of the projection module.
|
|
11293
11344
|
* @param options - Which projections, play or direct, whether `view` is forced, whether every
|
|
@@ -11299,12 +11350,20 @@ function reconcile(pctx, options) {
|
|
|
11299
11350
|
const snapshot = pctx.ctx.deps.model.store.snapshot();
|
|
11300
11351
|
const counts = emptyCounts();
|
|
11301
11352
|
const used = emptyHintSet();
|
|
11302
|
-
|
|
11303
|
-
|
|
11304
|
-
|
|
11305
|
-
|
|
11306
|
-
|
|
11307
|
-
|
|
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);
|
|
11308
11367
|
}
|
|
11309
11368
|
if (options.converge) {
|
|
11310
11369
|
convergeElements(pctx);
|
|
@@ -11320,9 +11379,7 @@ function reconcile(pctx, options) {
|
|
|
11320
11379
|
//#endregion
|
|
11321
11380
|
//#region src/plugins/world/projection/mount.ts
|
|
11322
11381
|
/**
|
|
11323
|
-
* Creates the empty view tables of a freshly mounted projection.
|
|
11324
|
-
* non-exported functions because lint rule L5 refuses a collection built inside an exported
|
|
11325
|
-
* declaration.
|
|
11382
|
+
* Creates the empty view tables of a freshly mounted projection.
|
|
11326
11383
|
*
|
|
11327
11384
|
* @param owner - Who owns the entities of this projection.
|
|
11328
11385
|
* @param order - The `Order` its screen roots draw at.
|
|
@@ -11340,14 +11397,29 @@ function emptyMounted(owner, order, layers) {
|
|
|
11340
11397
|
};
|
|
11341
11398
|
}
|
|
11342
11399
|
/**
|
|
11343
|
-
*
|
|
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.
|
|
11344
11416
|
*
|
|
11345
|
-
* @param pctx - Domain context of the projection module.
|
|
11346
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.
|
|
11347
11419
|
* @throws {Error} When `layer` or `lift` is not in the layer list.
|
|
11348
11420
|
*/
|
|
11349
|
-
function checkLayers(
|
|
11350
|
-
const declared = new Set(
|
|
11421
|
+
function checkLayers(spec, layers) {
|
|
11422
|
+
const declared = new Set(layers.map((layer) => layer.name));
|
|
11351
11423
|
for (const name of [spec.layer, spec.lift]) {
|
|
11352
11424
|
if (name === void 0 || declared.has(name)) continue;
|
|
11353
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.`);
|
|
@@ -11404,16 +11476,17 @@ function unmountNames(pctx, names) {
|
|
|
11404
11476
|
}
|
|
11405
11477
|
}
|
|
11406
11478
|
/**
|
|
11407
|
-
* The first pass of a mount, which touches nothing: every name is checked
|
|
11408
|
-
*
|
|
11409
|
-
* 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.
|
|
11410
11482
|
*
|
|
11411
11483
|
* @param pctx - Domain context of the projection module.
|
|
11412
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.
|
|
11413
11486
|
* @returns The specs to mount and reconcile, and the names to unmount first.
|
|
11414
|
-
* @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.
|
|
11415
11488
|
*/
|
|
11416
|
-
function sortMount(pctx, names) {
|
|
11489
|
+
function sortMount(pctx, names, layers) {
|
|
11417
11490
|
const state = pctx.ctx.state.projection;
|
|
11418
11491
|
const fresh = [];
|
|
11419
11492
|
const again = [];
|
|
@@ -11421,12 +11494,12 @@ function sortMount(pctx, names) {
|
|
|
11421
11494
|
const spec = state.specs.get(name);
|
|
11422
11495
|
if (spec === void 0) throw new Error(`[game] Projection "${name}" is not registered.\n Register it before a scene mounts it.`);
|
|
11423
11496
|
const mounted = state.mounted.get(name);
|
|
11424
|
-
if (mounted?.layers ===
|
|
11497
|
+
if (mounted?.layers === layers) {
|
|
11425
11498
|
pctx.ctx.log.warn("world:already-mounted", { projection: name });
|
|
11426
11499
|
continue;
|
|
11427
11500
|
}
|
|
11428
|
-
checkLayers(
|
|
11429
|
-
if (mounted !== void 0 && drawsAlike(mounted.layers,
|
|
11501
|
+
checkLayers(spec, layers);
|
|
11502
|
+
if (mounted !== void 0 && drawsAlike(mounted.layers, layers, spec)) continue;
|
|
11430
11503
|
if (mounted !== void 0) again.push(name);
|
|
11431
11504
|
fresh.push(spec);
|
|
11432
11505
|
}
|
|
@@ -11448,18 +11521,40 @@ function sortMount(pctx, names) {
|
|
|
11448
11521
|
* @throws {Error} For an unknown name, or a `layer` or `lift` the scene does not declare.
|
|
11449
11522
|
*/
|
|
11450
11523
|
function mountNames(pctx, names, owner) {
|
|
11451
|
-
const
|
|
11452
|
-
const { fresh, again } = sortMount(pctx, names);
|
|
11524
|
+
const { fresh, again } = sortMount(pctx, names, pctx.ctx.state.projection.layers);
|
|
11453
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;
|
|
11454
11540
|
for (const name of names) {
|
|
11455
|
-
const order = names
|
|
11541
|
+
const order = orderIn(names, name);
|
|
11456
11542
|
const mounted = state.mounted.get(name);
|
|
11457
11543
|
if (mounted === void 0) state.mounted.set(name, emptyMounted(owner, order, state.layers));
|
|
11458
11544
|
else if (mounted.layers !== state.layers) placeMounted(pctx, mounted, order);
|
|
11459
11545
|
}
|
|
11460
|
-
|
|
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;
|
|
11461
11556
|
reconcile(pctx, {
|
|
11462
|
-
names
|
|
11557
|
+
names,
|
|
11463
11558
|
direct: true,
|
|
11464
11559
|
force: true,
|
|
11465
11560
|
converge: false,
|
|
@@ -11467,6 +11562,148 @@ function mountNames(pctx, names, owner) {
|
|
|
11467
11562
|
});
|
|
11468
11563
|
}
|
|
11469
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
|
|
11470
11707
|
//#region src/plugins/world/projection/api.ts
|
|
11471
11708
|
/**
|
|
11472
11709
|
* Tells whether a cause sets the picture instead of playing it.
|
|
@@ -11641,7 +11878,7 @@ function createProjectionApi(ctx, deps) {
|
|
|
11641
11878
|
replace: (spec) => {
|
|
11642
11879
|
if (!state.specs.has(spec.name)) throw new Error(`[game] Projection "${spec.name}" is not registered.\n Register it before replacing it.`);
|
|
11643
11880
|
const mounted = state.mounted.has(spec.name);
|
|
11644
|
-
if (mounted) checkLayers(
|
|
11881
|
+
if (mounted) checkLayers(spec, state.layers);
|
|
11645
11882
|
state.specs.set(spec.name, spec);
|
|
11646
11883
|
if (mounted) forceRerun();
|
|
11647
11884
|
},
|
|
@@ -11716,7 +11953,7 @@ function createProjectionApi(ctx, deps) {
|
|
|
11716
11953
|
motionsOf: (entity) => runningComponents(state.tracks, entity),
|
|
11717
11954
|
entityOf: (projection, key) => state.mounted.get(projection)?.live.get(key)?.entity ?? state.keys.get(projection)?.get(key),
|
|
11718
11955
|
entitiesOf: (name) => {
|
|
11719
|
-
const mounted = state.mounted.get(name);
|
|
11956
|
+
const mounted = state.mounted.get(name) ?? state.building?.mounted.get(name);
|
|
11720
11957
|
return mounted === void 0 ? [] : liveEntities(mounted);
|
|
11721
11958
|
},
|
|
11722
11959
|
setDriver: (driver) => {
|
|
@@ -11762,6 +11999,38 @@ function createProjectionApi(ctx, deps) {
|
|
|
11762
11999
|
};
|
|
11763
12000
|
},
|
|
11764
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
|
+
},
|
|
11765
12034
|
markDirty: (roots, cause) => {
|
|
11766
12035
|
const dirty = state.dirty ?? emptyDirty();
|
|
11767
12036
|
dirty.causes.push(cause);
|
|
@@ -11800,6 +12069,7 @@ function createProjectionApi(ctx, deps) {
|
|
|
11800
12069
|
if (mounted !== void 0) clearMounted(pctx, mounted);
|
|
11801
12070
|
},
|
|
11802
12071
|
clear: () => {
|
|
12072
|
+
discardBuild(pctx);
|
|
11803
12073
|
for (const mounted of [...state.mounted.values()]) clearMounted(pctx, mounted);
|
|
11804
12074
|
state.mounted.clear();
|
|
11805
12075
|
state.specs.clear();
|
|
@@ -11908,8 +12178,8 @@ function exposeEcs(ecs, projection) {
|
|
|
11908
12178
|
};
|
|
11909
12179
|
}
|
|
11910
12180
|
/**
|
|
11911
|
-
* Reduces the projection module to what `scenes`, `input`, `i18n` and the
|
|
11912
|
-
* 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.
|
|
11913
12183
|
*
|
|
11914
12184
|
* @param projection - The full projection module.
|
|
11915
12185
|
* @returns The public projection API.
|
|
@@ -11937,7 +12207,15 @@ function exposeProjection(projection) {
|
|
|
11937
12207
|
restOf: projection.restOf,
|
|
11938
12208
|
registerKey: projection.registerKey,
|
|
11939
12209
|
onConverged: projection.onConverged,
|
|
11940
|
-
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
|
|
11941
12219
|
};
|
|
11942
12220
|
}
|
|
11943
12221
|
/**
|
|
@@ -12080,7 +12358,8 @@ function emptyWeakMap$2() {
|
|
|
12080
12358
|
}
|
|
12081
12359
|
/**
|
|
12082
12360
|
* Creates the initial projection state: nothing registered, nothing mounted, no layer list, no
|
|
12083
|
-
* 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.
|
|
12084
12363
|
*
|
|
12085
12364
|
* @returns The projection branch of the plugin state.
|
|
12086
12365
|
*/
|
|
@@ -12106,7 +12385,8 @@ function createProjectionState() {
|
|
|
12106
12385
|
membershipVersion: 0,
|
|
12107
12386
|
moving: false,
|
|
12108
12387
|
owedLoops: 0,
|
|
12109
|
-
sweptDespawns: 0
|
|
12388
|
+
sweptDespawns: 0,
|
|
12389
|
+
buildChecks: []
|
|
12110
12390
|
};
|
|
12111
12391
|
}
|
|
12112
12392
|
//#endregion
|
|
@@ -23563,7 +23843,7 @@ function heldFilters(target) {
|
|
|
23563
23843
|
* @param slots - The slots to compare with.
|
|
23564
23844
|
* @returns True when nothing has to be written.
|
|
23565
23845
|
*/
|
|
23566
|
-
function holds(target, slots) {
|
|
23846
|
+
function holds$1(target, slots) {
|
|
23567
23847
|
const held = heldFilters(target);
|
|
23568
23848
|
let at = 0;
|
|
23569
23849
|
for (let index = 0; index < slots.length; index += 1) {
|
|
@@ -23620,7 +23900,7 @@ function clearFilters(target) {
|
|
|
23620
23900
|
* @param target - Where the filters hang: the view's top unless the kept cache says otherwise.
|
|
23621
23901
|
*/
|
|
23622
23902
|
function applyFilters(view, slots, before, target = view.wrapper ?? view.object) {
|
|
23623
|
-
if (holds(target, slots)) return;
|
|
23903
|
+
if (holds$1(target, slots)) return;
|
|
23624
23904
|
before();
|
|
23625
23905
|
writeFilters(target, slots);
|
|
23626
23906
|
}
|
|
@@ -23732,7 +24012,7 @@ function applySwitchedOne(slots, entity) {
|
|
|
23732
24012
|
const view = sync?.views.get(entity);
|
|
23733
24013
|
if (view === void 0 || kept === void 0) return;
|
|
23734
24014
|
const target = kept.filterTarget(view);
|
|
23735
|
-
if (holds(target, slots)) return;
|
|
24015
|
+
if (holds$1(target, slots)) return;
|
|
23736
24016
|
writeFilters(target, slots);
|
|
23737
24017
|
switched.wrote = true;
|
|
23738
24018
|
}
|
|
@@ -24643,6 +24923,348 @@ function clearFonts(state) {
|
|
|
24643
24923
|
state.fontCache = void 0;
|
|
24644
24924
|
}
|
|
24645
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
|
|
24646
25268
|
//#region src/plugins/renderer/sync/outline.ts
|
|
24647
25269
|
/** Above every child of the wrapper, whatever `Order` a child carries. */
|
|
24648
25270
|
const OUTLINE_Z = Number.MAX_SAFE_INTEGER;
|
|
@@ -25071,297 +25693,6 @@ function labelOf$1(sctx, name, entity) {
|
|
|
25071
25693
|
return key === void 0 ? base : `${base} ${key.projection}:${key.key}`;
|
|
25072
25694
|
}
|
|
25073
25695
|
//#endregion
|
|
25074
|
-
//#region src/plugins/renderer/sync/pools.ts
|
|
25075
|
-
/**
|
|
25076
|
-
* Tells whether a display object is a sprite. A `NineSliceSprite` carries the slice widths and
|
|
25077
|
-
* is recognised by them.
|
|
25078
|
-
*
|
|
25079
|
-
* @param object - The display object.
|
|
25080
|
-
* @returns True for a plain sprite.
|
|
25081
|
-
*/
|
|
25082
|
-
function isSprite(object) {
|
|
25083
|
-
return "texture" in object && !("leftWidth" in object);
|
|
25084
|
-
}
|
|
25085
|
-
/**
|
|
25086
|
-
* Tells whether a display object draws paths, so a `Shape` can be written onto it.
|
|
25087
|
-
*
|
|
25088
|
-
* @param object - The display object.
|
|
25089
|
-
* @returns True for a graphics object.
|
|
25090
|
-
*/
|
|
25091
|
-
function isGraphics(object) {
|
|
25092
|
-
return "roundRect" in object;
|
|
25093
|
-
}
|
|
25094
|
-
/**
|
|
25095
|
-
* Tells whether a display object draws a nine-slice: Pixi's `NineSliceSprite`, or the engine's rim-only mesh of a
|
|
25096
|
-
* `hollow` one. Both carry the slice widths.
|
|
25097
|
-
*
|
|
25098
|
-
* @param object - The display object.
|
|
25099
|
-
* @returns True for either nine-slice object.
|
|
25100
|
-
*/
|
|
25101
|
-
function isNineSlice(object) {
|
|
25102
|
-
return "leftWidth" in object;
|
|
25103
|
-
}
|
|
25104
|
-
/**
|
|
25105
|
-
* Tells whether a display object is the engine's rim-only nine-slice (F59).
|
|
25106
|
-
*
|
|
25107
|
-
* @param object - The display object.
|
|
25108
|
-
* @returns True for the hollow mesh, false for Pixi's `NineSliceSprite` and every other object.
|
|
25109
|
-
*/
|
|
25110
|
-
function isHollow(object) {
|
|
25111
|
-
return "hollow" in object;
|
|
25112
|
-
}
|
|
25113
|
-
/**
|
|
25114
|
-
* Takes a display object out of the Pixi tree. Pixi containers are not DOM nodes, so the DOM
|
|
25115
|
-
* helper the linter suggests does not exist on them.
|
|
25116
|
-
*
|
|
25117
|
-
* @param object - The display object.
|
|
25118
|
-
*/
|
|
25119
|
-
function detach$1(object) {
|
|
25120
|
-
object.parent?.removeChild(object);
|
|
25121
|
-
}
|
|
25122
|
-
/**
|
|
25123
|
-
* Takes the masked container off a view: its children go back to the wrapper, unmasked and in
|
|
25124
|
-
* their order, and the container is freed.
|
|
25125
|
-
*
|
|
25126
|
-
* @param view - The view of a shape that clips, or of anything that does not.
|
|
25127
|
-
*/
|
|
25128
|
-
function dropClipped(view) {
|
|
25129
|
-
const clipped = view.clipped;
|
|
25130
|
-
if (clipped === void 0) return;
|
|
25131
|
-
clipped.mask = null;
|
|
25132
|
-
const wrapper = view.wrapper;
|
|
25133
|
-
if (wrapper !== void 0) for (const child of clipped.children) wrapper.addChild(child);
|
|
25134
|
-
detach$1(clipped);
|
|
25135
|
-
clipped.destroy();
|
|
25136
|
-
view.clipped = void 0;
|
|
25137
|
-
}
|
|
25138
|
-
/**
|
|
25139
|
-
* Takes the clip rectangle off a view: the children stop being masked and the graphics goes.
|
|
25140
|
-
*
|
|
25141
|
-
* @param view - The view of a shape that clips, or of anything that does not.
|
|
25142
|
-
*/
|
|
25143
|
-
function dropMask(view) {
|
|
25144
|
-
dropClipped(view);
|
|
25145
|
-
const mask = view.mask;
|
|
25146
|
-
if (mask === void 0) return;
|
|
25147
|
-
detach$1(mask);
|
|
25148
|
-
mask.destroy();
|
|
25149
|
-
view.mask = void 0;
|
|
25150
|
-
}
|
|
25151
|
-
/**
|
|
25152
|
-
* Takes the debug outline off a view: the graphics leaves the wrapper and is freed.
|
|
25153
|
-
*
|
|
25154
|
-
* @param view - The view of a nine-slice that was outlined, or of anything that was not.
|
|
25155
|
-
*/
|
|
25156
|
-
function dropOutline(view) {
|
|
25157
|
-
const outline = view.outline;
|
|
25158
|
-
if (outline === void 0) return;
|
|
25159
|
-
detach$1(outline);
|
|
25160
|
-
outline.destroy();
|
|
25161
|
-
view.outline = void 0;
|
|
25162
|
-
}
|
|
25163
|
-
/**
|
|
25164
|
-
* 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
|
|
25165
|
-
* a filled panel never gets a hollow object back, nor the other way round.
|
|
25166
|
-
*
|
|
25167
|
-
* @param kind - Which component gave the entity its display object.
|
|
25168
|
-
* @param textureKey - The asset key.
|
|
25169
|
-
* @param hollow - True for the rim-only object of a `NineSlice` with `hollow: true`.
|
|
25170
|
-
* @returns The pool key.
|
|
25171
|
-
* @example
|
|
25172
|
-
* ```ts
|
|
25173
|
-
* poolKeyOf("NineSlice", "glass.frost", true); // "NineSlice.hollow:glass.frost"
|
|
25174
|
-
* ```
|
|
25175
|
-
*/
|
|
25176
|
-
function poolKeyOf(kind, textureKey, hollow) {
|
|
25177
|
-
return hollow ? `${kind}.hollow:${textureKey}` : `${kind}:${textureKey}`;
|
|
25178
|
-
}
|
|
25179
|
-
/**
|
|
25180
|
-
* Brings the object of a view that left back to the state a fresh one has: detached, drawn, opaque, unmoved, blending
|
|
25181
|
-
* normally, white.
|
|
25182
|
-
* Its hidden reasons go with `clearHidden`, so a pooled object never brings the reason of the view it belonged to; a
|
|
25183
|
-
* sprite and a graphics lose their tint (a fill-only shape is coloured by it, spec 26 R2.1).
|
|
25184
|
-
*
|
|
25185
|
-
* @param view - The view whose object goes back to its pool; its wrapper is gone already.
|
|
25186
|
-
*/
|
|
25187
|
-
function resetObject(view) {
|
|
25188
|
-
detach$1(view.object);
|
|
25189
|
-
clearHidden(view);
|
|
25190
|
-
resetNode(view.object);
|
|
25191
|
-
}
|
|
25192
|
-
/**
|
|
25193
|
-
* Brings a detached object back to a fresh one's pose and look: drawn, opaque, unmoved, blending as its parent (no
|
|
25194
|
-
* glow of the view it drew for), white for a sprite and a graphics, and a graphics without a path.
|
|
25195
|
-
*
|
|
25196
|
-
* @param object - The object, out of the tree.
|
|
25197
|
-
*/
|
|
25198
|
-
function resetNode(object) {
|
|
25199
|
-
object.visible = true;
|
|
25200
|
-
object.alpha = 1;
|
|
25201
|
-
object.blendMode = "inherit";
|
|
25202
|
-
object.rotation = 0;
|
|
25203
|
-
object.zIndex = 0;
|
|
25204
|
-
object.position.set(0, 0);
|
|
25205
|
-
object.pivot.set(0, 0);
|
|
25206
|
-
object.scale.set(1);
|
|
25207
|
-
if (isSprite(object)) object.tint = 16777215;
|
|
25208
|
-
if (isGraphics(object)) {
|
|
25209
|
-
object.tint = 16777215;
|
|
25210
|
-
object.clear();
|
|
25211
|
-
}
|
|
25212
|
-
}
|
|
25213
|
-
/**
|
|
25214
|
-
* Takes an object out of its pool, if one is waiting.
|
|
25215
|
-
*
|
|
25216
|
-
* @param state - The sync branch of the plugin state.
|
|
25217
|
-
* @param poolKey - Class and texture key.
|
|
25218
|
-
* @returns A reusable object, or `undefined`.
|
|
25219
|
-
*/
|
|
25220
|
-
function acquire(state, poolKey) {
|
|
25221
|
-
const object = state.pools.get(poolKey)?.pop();
|
|
25222
|
-
if (object !== void 0) state.pooled -= 1;
|
|
25223
|
-
return object;
|
|
25224
|
-
}
|
|
25225
|
-
/**
|
|
25226
|
-
* Creates the array a pool keeps its objects in. Lint rule L5 refuses a collection built inside
|
|
25227
|
-
* an exported declaration, so it lives here.
|
|
25228
|
-
*
|
|
25229
|
-
* @returns An empty pool.
|
|
25230
|
-
*/
|
|
25231
|
-
function emptyPool() {
|
|
25232
|
-
return [];
|
|
25233
|
-
}
|
|
25234
|
-
/**
|
|
25235
|
-
* The pool that holds the object that has waited longest.
|
|
25236
|
-
*
|
|
25237
|
-
* @param state - The sync branch of the plugin state.
|
|
25238
|
-
* @returns A non-empty pool, or `undefined` when every pool is empty.
|
|
25239
|
-
*/
|
|
25240
|
-
function oldestPool(state) {
|
|
25241
|
-
for (const pool of state.pools.values()) if (pool.length > 0) return pool;
|
|
25242
|
-
}
|
|
25243
|
-
/**
|
|
25244
|
-
* Puts a view's object back into its pool and evicts the oldest one when the pool grew past the
|
|
25245
|
-
* configured limit. A `Display` object belongs to the game and is only detached. The filters and the
|
|
25246
|
-
* filter area the renderer hung on the object or its wrapper come off first, so a pooled object never
|
|
25247
|
-
* carries a filter into its next life; the filter instances themselves belong to `effects`.
|
|
25248
|
-
*
|
|
25249
|
-
* @param sctx - Domain context of the sync module.
|
|
25250
|
-
* @param view - The view that left.
|
|
25251
|
-
*/
|
|
25252
|
-
function release$1(sctx, view) {
|
|
25253
|
-
dropMask(view);
|
|
25254
|
-
dropOutline(view);
|
|
25255
|
-
clearFilters(view.object);
|
|
25256
|
-
clearArea(view.object);
|
|
25257
|
-
if (view.wrapper !== void 0) {
|
|
25258
|
-
clearFilters(view.wrapper);
|
|
25259
|
-
clearArea(view.wrapper);
|
|
25260
|
-
detach$1(view.wrapper);
|
|
25261
|
-
view.wrapper.destroy({ children: false });
|
|
25262
|
-
view.wrapper = void 0;
|
|
25263
|
-
}
|
|
25264
|
-
if (view.kind === "Display") {
|
|
25265
|
-
detach$1(view.object);
|
|
25266
|
-
clearHidden(view);
|
|
25267
|
-
return;
|
|
25268
|
-
}
|
|
25269
|
-
if (view.kind === "adapter") {
|
|
25270
|
-
detach$1(view.object);
|
|
25271
|
-
clearHidden(view);
|
|
25272
|
-
view.display?.adapter.destroy(view.object);
|
|
25273
|
-
return;
|
|
25274
|
-
}
|
|
25275
|
-
dropGroup(sctx, view.object);
|
|
25276
|
-
resetObject(view);
|
|
25277
|
-
store$1(sctx, view.object, view.poolKey);
|
|
25278
|
-
}
|
|
25279
|
-
/**
|
|
25280
|
-
* Puts an object a view no longer draws with back into a pool, while the view goes on with another one: the
|
|
25281
|
-
* nine-slice whose `hollow` flipped (F59). Reset as `release` resets a view's object; its view keeps its hidden
|
|
25282
|
-
* reasons, which hang on the view's wrapper.
|
|
25283
|
-
*
|
|
25284
|
-
* @param sctx - Domain context of the sync module.
|
|
25285
|
-
* @param object - The object, still in the view's wrapper.
|
|
25286
|
-
* @param poolKey - The pool it belongs to.
|
|
25287
|
-
*/
|
|
25288
|
-
function releaseObject(sctx, object, poolKey) {
|
|
25289
|
-
dropGroup(sctx, object);
|
|
25290
|
-
detach$1(object);
|
|
25291
|
-
resetNode(object);
|
|
25292
|
-
store$1(sctx, object, poolKey);
|
|
25293
|
-
}
|
|
25294
|
-
/**
|
|
25295
|
-
* Takes away the render group the kept cache made of an object (spec 23 P9.3), before it is pooled.
|
|
25296
|
-
*
|
|
25297
|
-
* @param sctx - Domain context of the sync module.
|
|
25298
|
-
* @param object - The object.
|
|
25299
|
-
*/
|
|
25300
|
-
function dropGroup(sctx, object) {
|
|
25301
|
-
const groups = sctx.ctx.state.kept.madeGroups;
|
|
25302
|
-
if (!groups.has(object)) return;
|
|
25303
|
-
object.disableRenderGroup();
|
|
25304
|
-
groups.delete(object);
|
|
25305
|
-
}
|
|
25306
|
-
/**
|
|
25307
|
-
* Pushes a reset object into its pool and evicts the one that waited longest while the pools hold more than
|
|
25308
|
-
* `poolLimit`.
|
|
25309
|
-
*
|
|
25310
|
-
* @param sctx - Domain context of the sync module.
|
|
25311
|
-
* @param object - The reset object.
|
|
25312
|
-
* @param poolKey - Its pool.
|
|
25313
|
-
*/
|
|
25314
|
-
function store$1(sctx, object, poolKey) {
|
|
25315
|
-
const state = sctx.ctx.state.sync;
|
|
25316
|
-
const pool = state.pools.get(poolKey) ?? emptyPool();
|
|
25317
|
-
pool.push(object);
|
|
25318
|
-
state.pools.set(poolKey, pool);
|
|
25319
|
-
state.pooled += 1;
|
|
25320
|
-
while (state.pooled > sctx.ctx.config.poolLimit) {
|
|
25321
|
-
const oldest = oldestPool(state);
|
|
25322
|
-
if (oldest === void 0) return;
|
|
25323
|
-
oldest.shift()?.destroy({ texture: false });
|
|
25324
|
-
state.pooled -= 1;
|
|
25325
|
-
}
|
|
25326
|
-
}
|
|
25327
|
-
/**
|
|
25328
|
-
* Destroys the pooled objects that draw one of these keys, so a bundle that left frees its GPU
|
|
25329
|
-
* memory instead of waiting for a reuse that resolves nothing: sprites, nine-slices and rim-only nine-slices.
|
|
25330
|
-
*
|
|
25331
|
-
* @param state - The sync branch of the plugin state.
|
|
25332
|
-
* @param keys - The asset keys that went stale.
|
|
25333
|
-
*/
|
|
25334
|
-
function dropPooled(state, keys) {
|
|
25335
|
-
for (const key of keys) {
|
|
25336
|
-
dropPool(state, poolKeyOf("Sprite", key, false));
|
|
25337
|
-
dropPool(state, poolKeyOf("NineSlice", key, false));
|
|
25338
|
-
dropPool(state, poolKeyOf("NineSlice", key, true));
|
|
25339
|
-
}
|
|
25340
|
-
}
|
|
25341
|
-
/**
|
|
25342
|
-
* Destroys one pool and everything in it.
|
|
25343
|
-
*
|
|
25344
|
-
* @param state - The sync branch of the plugin state.
|
|
25345
|
-
* @param poolKey - The pool.
|
|
25346
|
-
*/
|
|
25347
|
-
function dropPool(state, poolKey) {
|
|
25348
|
-
const pool = state.pools.get(poolKey);
|
|
25349
|
-
if (pool === void 0) return;
|
|
25350
|
-
for (const object of pool) object.destroy({ texture: false });
|
|
25351
|
-
state.pooled -= pool.length;
|
|
25352
|
-
state.pools.delete(poolKey);
|
|
25353
|
-
}
|
|
25354
|
-
/**
|
|
25355
|
-
* Destroys everything the pools hold. Works on the state alone, because `onStop` has no context.
|
|
25356
|
-
*
|
|
25357
|
-
* @param state - The sync branch of the plugin state.
|
|
25358
|
-
*/
|
|
25359
|
-
function destroyPools(state) {
|
|
25360
|
-
for (const pool of state.pools.values()) for (const object of pool) object.destroy({ texture: false });
|
|
25361
|
-
state.pools.clear();
|
|
25362
|
-
state.pooled = 0;
|
|
25363
|
-
}
|
|
25364
|
-
//#endregion
|
|
25365
25696
|
//#region src/plugins/renderer/sync/layers.ts
|
|
25366
25697
|
/**
|
|
25367
25698
|
* @file renderer/sync — the layers. `scenes` declares them through `world.projection.setLayers`;
|
|
@@ -28066,7 +28397,9 @@ function keepOrphan(sctx, entity, view, parentEntity) {
|
|
|
28066
28397
|
/**
|
|
28067
28398
|
* Hangs a view where it belongs: under its parent, or in the container of the layer it names.
|
|
28068
28399
|
* An entity with no layer and no parent is not drawn. A view whose parent has no view yet waits
|
|
28069
|
-
* 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.
|
|
28070
28403
|
*
|
|
28071
28404
|
* @param sctx - Domain context of the sync module.
|
|
28072
28405
|
* @param entity - The entity.
|
|
@@ -28077,6 +28410,11 @@ function attach$1(sctx, entity, view) {
|
|
|
28077
28410
|
const parentEntity = parentOf$1(sctx.ctx.deps.world.ecs, entity);
|
|
28078
28411
|
markView(sctx, entity);
|
|
28079
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;
|
|
28080
28418
|
if (parentEntity !== 0) {
|
|
28081
28419
|
const host = childHostOf(sctx, parentEntity);
|
|
28082
28420
|
view.layer = "";
|
|
@@ -28204,7 +28542,8 @@ function createView(sctx, entity) {
|
|
|
28204
28542
|
clipDrawn: void 0,
|
|
28205
28543
|
hiddenSelf: 0,
|
|
28206
28544
|
hiddenTop: 0,
|
|
28207
|
-
hud: 1
|
|
28545
|
+
hud: 1,
|
|
28546
|
+
held: false
|
|
28208
28547
|
};
|
|
28209
28548
|
object.label = labelOf$1(sctx, nameOf$1(visual), entity);
|
|
28210
28549
|
state.views.set(entity, view);
|
|
@@ -28305,10 +28644,10 @@ function dropView(sctx, entity) {
|
|
|
28305
28644
|
}
|
|
28306
28645
|
/**
|
|
28307
28646
|
* Drops every view, every layer, every pool, every filter slot and filter area, the warm-up (its waiting promises
|
|
28308
|
-
* answer false, its probe tree is destroyed)
|
|
28309
|
-
* alone,
|
|
28310
|
-
*
|
|
28311
|
-
*
|
|
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.
|
|
28312
28651
|
*
|
|
28313
28652
|
* @param state - The sync branch of the plugin state.
|
|
28314
28653
|
*/
|
|
@@ -28354,6 +28693,7 @@ function stopSync(state) {
|
|
|
28354
28693
|
stopWarm(state);
|
|
28355
28694
|
stopUploads(state);
|
|
28356
28695
|
state.held.clear();
|
|
28696
|
+
forgetBuild(state);
|
|
28357
28697
|
state.redraws = 0;
|
|
28358
28698
|
state.overdraw.listeners.length = 0;
|
|
28359
28699
|
clearLayers(state);
|
|
@@ -28497,8 +28837,7 @@ function withDeps$8(ctx) {
|
|
|
28497
28837
|
* overlay step (spec 22), then `kept` reads the marks before `demand` decides whether the frame is
|
|
28498
28838
|
* drawn and clears them; a drawn frame draws the probes of the warm-up onto the canvas first (F4: the
|
|
28499
28839
|
* stage pass clears them, in the same task, so they never show), then uploads the queued texture sources (F60), then
|
|
28500
|
-
* the kept pictures
|
|
28501
|
-
* (`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
|
|
28502
28841
|
* oracle; `demand` settles the
|
|
28503
28842
|
* frame (the dev check, the pixel oracle), and `monitor` closes it and hands a drawn one to a
|
|
28504
28843
|
* waiting capture; last, `host` trims Pixi's texture pool when a rest or a scene change is due. The display objects
|
|
@@ -28509,7 +28848,8 @@ function withDeps$8(ctx) {
|
|
|
28509
28848
|
* last picture (WebGPU presents a canvas texture only when one is rendered; a WebGL canvas keeps
|
|
28510
28849
|
* what it composited until the next draw). A drawn frame is reported to `time` for the adaptive
|
|
28511
28850
|
* frame rate, and only when Pixi drew it: an inert or lost host reports none. A frame that also drew
|
|
28512
|
-
* 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.
|
|
28513
28853
|
*
|
|
28514
28854
|
* @param ctx - Domain context of the renderer plugin.
|
|
28515
28855
|
* @param modules - The six modules.
|
|
@@ -28533,10 +28873,11 @@ function renderFrame(ctx, modules) {
|
|
|
28533
28873
|
if (drawn) {
|
|
28534
28874
|
const probed = modules.sync.warmStep();
|
|
28535
28875
|
const uploaded = modules.sync.uploadStep();
|
|
28876
|
+
const built = modules.sync.takeBuildWork();
|
|
28536
28877
|
modules.kept.flush();
|
|
28537
28878
|
if (overdraw ? modules.sync.drawOverdraw(modules.host.render) : modules.host.render()) {
|
|
28538
28879
|
ctx.deps.time.frameDrawn();
|
|
28539
|
-
if (probed || uploaded) ctx.deps.time.frameProbed();
|
|
28880
|
+
if (probed || uploaded || built) ctx.deps.time.frameProbed();
|
|
28540
28881
|
}
|
|
28541
28882
|
modules.kept.after();
|
|
28542
28883
|
}
|
|
@@ -29339,6 +29680,233 @@ function layerOf(sctx, entity) {
|
|
|
29339
29680
|
return ecs.get(top, Layer)?.name;
|
|
29340
29681
|
}
|
|
29341
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
|
|
29342
29910
|
//#region src/plugins/renderer/sync/each.ts
|
|
29343
29911
|
/**
|
|
29344
29912
|
* Tells whether a change list is an array.
|
|
@@ -30231,7 +30799,7 @@ function expectedHostOf(sctx, entity, parent) {
|
|
|
30231
30799
|
}
|
|
30232
30800
|
/**
|
|
30233
30801
|
* Checks where the views of the given entities hang and reports every one that hangs elsewhere than the ECS says,
|
|
30234
|
-
* 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.
|
|
30235
30803
|
*
|
|
30236
30804
|
* @param sctx - Domain context of the sync module.
|
|
30237
30805
|
* @param entities - The entities a pass built, hung again or took out of the orphans.
|
|
@@ -30241,7 +30809,7 @@ function checkTree(sctx, entities) {
|
|
|
30241
30809
|
const ecs = sctx.ctx.deps.world.ecs;
|
|
30242
30810
|
for (const entity of entities) {
|
|
30243
30811
|
const view = state.views.get(entity);
|
|
30244
|
-
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;
|
|
30245
30813
|
const parent = parentOf$1(ecs, entity);
|
|
30246
30814
|
const host = (view.wrapper ?? view.object).parent;
|
|
30247
30815
|
const expected = expectedHostOf(sctx, entity, parent);
|
|
@@ -30516,12 +31084,13 @@ function createSyncApi(ctx, deps) {
|
|
|
30516
31084
|
const onParent = (entity) => stepOn(STEP.parent, entity);
|
|
30517
31085
|
const onAdapter = (entity) => stepOn(STEP.adapter, entity);
|
|
30518
31086
|
/**
|
|
30519
|
-
* 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.
|
|
30520
31089
|
*
|
|
30521
31090
|
* @param entity - The entity.
|
|
30522
31091
|
*/
|
|
30523
31092
|
const onDisplay = (entity) => {
|
|
30524
|
-
if (built.has(entity)) return;
|
|
31093
|
+
if (built.has(entity) || state.aside.waiting.has(entity)) return;
|
|
30525
31094
|
guard(entity, () => {
|
|
30526
31095
|
dropView(sctx, entity);
|
|
30527
31096
|
createView(sctx, entity);
|
|
@@ -30604,12 +31173,13 @@ function createSyncApi(ctx, deps) {
|
|
|
30604
31173
|
};
|
|
30605
31174
|
/**
|
|
30606
31175
|
* Tells whether the pass has nothing to do: nothing waits to be built, dropped, hung or resolved again, the layer list
|
|
30607
|
-
* 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.
|
|
30608
31178
|
*
|
|
30609
31179
|
* @returns True when the pass would change nothing.
|
|
30610
31180
|
*/
|
|
30611
31181
|
const still = () => {
|
|
30612
|
-
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;
|
|
30613
31183
|
const ecs = ctx.deps.world.ecs;
|
|
30614
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;
|
|
30615
31185
|
for (let index = 0; index < state.adapters.length; index += 1) {
|
|
@@ -30644,13 +31214,16 @@ function createSyncApi(ctx, deps) {
|
|
|
30644
31214
|
for (const [entity, view] of state.views) if (view.kind === "NineSlice") guard(entity, () => applyNineSlice(sctx, entity, view));
|
|
30645
31215
|
};
|
|
30646
31216
|
/**
|
|
30647
|
-
* 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.
|
|
30648
31220
|
*
|
|
30649
31221
|
* @returns The entities of this pass, so the change step skips what the build just wrote.
|
|
30650
31222
|
*/
|
|
30651
31223
|
const takeAdded = () => {
|
|
30652
31224
|
const built = copyOf(state.added);
|
|
30653
31225
|
state.added.clear();
|
|
31226
|
+
if (buildRuns(sctx)) sortAside(sctx, built);
|
|
30654
31227
|
return built;
|
|
30655
31228
|
};
|
|
30656
31229
|
/**
|
|
@@ -30703,17 +31276,17 @@ function createSyncApi(ctx, deps) {
|
|
|
30703
31276
|
return viewless;
|
|
30704
31277
|
};
|
|
30705
31278
|
/**
|
|
30706
|
-
*
|
|
30707
|
-
* slots with it, also when its view left in an earlier pass; a live
|
|
30708
|
-
*
|
|
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.
|
|
30709
31282
|
*/
|
|
30710
|
-
const
|
|
30711
|
-
if (still()) return;
|
|
31283
|
+
const livePass = () => {
|
|
30712
31284
|
state.passesVersion += 1;
|
|
30713
31285
|
if (state.removed.size > 0) {
|
|
30714
31286
|
for (const entity of state.removed) {
|
|
30715
31287
|
guard(entity, () => dropView(sctx, entity));
|
|
30716
31288
|
forgetLeft(state, ctx.deps.world.ecs, entity);
|
|
31289
|
+
dropAside(state, entity);
|
|
30717
31290
|
}
|
|
30718
31291
|
state.removed.clear();
|
|
30719
31292
|
}
|
|
@@ -30722,6 +31295,7 @@ function createSyncApi(ctx, deps) {
|
|
|
30722
31295
|
deps.demand.whole("layers");
|
|
30723
31296
|
deps.host.sceneChanged();
|
|
30724
31297
|
}
|
|
31298
|
+
attachHeld(sctx);
|
|
30725
31299
|
built = state.added.size === 0 ? nothingBuilt : buildParentFirst(takeAdded());
|
|
30726
31300
|
const touched = typeof __MOKU_GAME_DEV__ !== "undefined" && __MOKU_GAME_DEV__ || typeof __MOKU_GAME_AGENT__ !== "undefined" && __MOKU_GAME_AGENT__ ? touchedBy() : void 0;
|
|
30727
31301
|
applyChanges();
|
|
@@ -30734,6 +31308,14 @@ function createSyncApi(ctx, deps) {
|
|
|
30734
31308
|
if (touched !== void 0) checkTree(sctx, touched);
|
|
30735
31309
|
};
|
|
30736
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
|
+
/**
|
|
30737
31319
|
* Forgets every view, layer and pooled object without touching Pixi. Used before a rebuild,
|
|
30738
31320
|
* where the old application and everything in it are already gone.
|
|
30739
31321
|
*/
|
|
@@ -30758,6 +31340,7 @@ function createSyncApi(ctx, deps) {
|
|
|
30758
31340
|
clearFrames(state);
|
|
30759
31341
|
destroyPools(state);
|
|
30760
31342
|
clearLayers(state);
|
|
31343
|
+
forgetBuild(state);
|
|
30761
31344
|
state.root = void 0;
|
|
30762
31345
|
state.passesVersion += 1;
|
|
30763
31346
|
};
|
|
@@ -30859,6 +31442,7 @@ function createSyncApi(ctx, deps) {
|
|
|
30859
31442
|
stepFilters: () => applySwitched(state, deps.kept),
|
|
30860
31443
|
drainOverlayEvents: (monitor) => drainOverlayEvents(sctx, monitor),
|
|
30861
31444
|
warmStep: () => warmStep(sctx),
|
|
31445
|
+
takeBuildWork: () => takeBuildWork(state.aside),
|
|
30862
31446
|
uploadStep: () => uploadStep(sctx),
|
|
30863
31447
|
onWarm: (fn) => {
|
|
30864
31448
|
state.warm.listeners.push(fn);
|
|
@@ -30887,7 +31471,7 @@ function createSyncApi(ctx, deps) {
|
|
|
30887
31471
|
start: () => {
|
|
30888
31472
|
if (deps.host.stage() === void 0) return;
|
|
30889
31473
|
const ecs = ctx.deps.world.ecs;
|
|
30890
|
-
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)));
|
|
30891
31475
|
rebuildAll();
|
|
30892
31476
|
}
|
|
30893
31477
|
};
|
|
@@ -31668,8 +32252,24 @@ function createUploadState() {
|
|
|
31668
32252
|
};
|
|
31669
32253
|
}
|
|
31670
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
|
+
/**
|
|
31671
32270
|
* Creates the initial sync state: no root, no layers, no view, no orphan, no pool, no provider, no filter, no
|
|
31672
|
-
* 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.
|
|
31673
32273
|
*
|
|
31674
32274
|
* @param debug - The debug switches of the config; copied, so the frozen config stays untouched.
|
|
31675
32275
|
* @returns The sync branch of the plugin state.
|
|
@@ -31728,6 +32328,12 @@ function createSyncState(debug) {
|
|
|
31728
32328
|
enabled: []
|
|
31729
32329
|
},
|
|
31730
32330
|
hudLayer: void 0,
|
|
32331
|
+
aside: createAsideQueue(),
|
|
32332
|
+
heldViews: {
|
|
32333
|
+
entities: [],
|
|
32334
|
+
count: 0,
|
|
32335
|
+
reported: false
|
|
32336
|
+
},
|
|
31731
32337
|
cleanups: []
|
|
31732
32338
|
};
|
|
31733
32339
|
}
|
|
@@ -31835,7 +32441,7 @@ function checkAdaptiveScale(adaptiveScale) {
|
|
|
31835
32441
|
* number from 64 to 16384, `keptMb` is not a finite number of at least 0, `warm` is not a
|
|
31836
32442
|
* whole number from 0 to 16, or `uploads` is not a whole number from 0 to 8.
|
|
31837
32443
|
*/
|
|
31838
|
-
function checkConfig(config) {
|
|
32444
|
+
function checkConfig$1(config) {
|
|
31839
32445
|
const { maxResolution, keptMb } = config;
|
|
31840
32446
|
const { budgetMs, frames } = config.monitor;
|
|
31841
32447
|
if (!Number.isFinite(maxResolution) || maxResolution <= 0) throw new Error(`[game] renderer.maxResolution is ${maxResolution}.\n Give a number above 0.`);
|
|
@@ -31860,7 +32466,7 @@ function checkConfig(config) {
|
|
|
31860
32466
|
* @throws {Error} When the config fails a check of `checkConfig`.
|
|
31861
32467
|
*/
|
|
31862
32468
|
function createRendererState(ctx) {
|
|
31863
|
-
checkConfig(ctx.config);
|
|
32469
|
+
checkConfig$1(ctx.config);
|
|
31864
32470
|
return {
|
|
31865
32471
|
host: createHostState(),
|
|
31866
32472
|
viewport: createViewportState(),
|
|
@@ -35818,7 +36424,7 @@ function usedMb(state) {
|
|
|
35818
36424
|
}
|
|
35819
36425
|
/**
|
|
35820
36426
|
* Picks the bundle that leaves next: the loaded one with the smallest use counter that is not
|
|
35821
|
-
* 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
|
|
35822
36428
|
* `release` is not read: a `"leave"` bundle is a candidate like any other.
|
|
35823
36429
|
*
|
|
35824
36430
|
* @param state - The plugin state.
|
|
@@ -35832,6 +36438,7 @@ function pickVictim(state) {
|
|
|
35832
36438
|
if (record.status !== "loaded" || entry === void 0) continue;
|
|
35833
36439
|
if (state.pinned.has(name) || isPermanent(entry.tier)) continue;
|
|
35834
36440
|
if (state.sceneNamed.includes(name) || state.sceneLeaving.includes(name)) continue;
|
|
36441
|
+
if (state.sceneComing.includes(name)) continue;
|
|
35835
36442
|
if (state.queue?.bundles.includes(name) === true) continue;
|
|
35836
36443
|
if (record.lastUsed >= smallest) continue;
|
|
35837
36444
|
smallest = record.lastUsed;
|
|
@@ -35987,9 +36594,9 @@ function featuresOnStack(ctx) {
|
|
|
35987
36594
|
}
|
|
35988
36595
|
/**
|
|
35989
36596
|
* Tells whether a bundle must stay where the game stands now: it is pinned (a bundle of the node
|
|
35990
|
-
* entered last), the mounted scene names it (the last list `sceneBundles` received),
|
|
35991
|
-
*
|
|
35992
|
-
* 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.
|
|
35993
36600
|
*
|
|
35994
36601
|
* @param ctx - Domain context of the plugin.
|
|
35995
36602
|
* @param name - Name of the bundle.
|
|
@@ -35997,9 +36604,9 @@ function featuresOnStack(ctx) {
|
|
|
35997
36604
|
* @param onStack - The features on the stack, from `featuresOnStack`.
|
|
35998
36605
|
* @returns True when the bundle is held.
|
|
35999
36606
|
*/
|
|
36000
|
-
function isHeld(ctx, name, entry, onStack) {
|
|
36607
|
+
function isHeld$1(ctx, name, entry, onStack) {
|
|
36001
36608
|
const state = ctx.state;
|
|
36002
|
-
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);
|
|
36003
36610
|
}
|
|
36004
36611
|
/**
|
|
36005
36612
|
* Unloads every loaded `"leave"` bundle nobody holds any more, with the reason `"leave"`. A
|
|
@@ -36018,7 +36625,7 @@ function releaseLeftBundles(ctx) {
|
|
|
36018
36625
|
/**
|
|
36019
36626
|
* Takes the list the mounted scene names now. A bundle the list named before and names no more
|
|
36020
36627
|
* stays held as a leaving scene's, until nothing carries `Exiting` (F51); a name the list takes
|
|
36021
|
-
* 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.
|
|
36022
36629
|
*
|
|
36023
36630
|
* @param state - The plugin state.
|
|
36024
36631
|
* @param named - The bundles the mounted scene names now.
|
|
@@ -36028,6 +36635,17 @@ function nameSceneBundles(state, named) {
|
|
|
36028
36635
|
for (const name of state.sceneNamed) if (!named.includes(name) && !leaving.includes(name)) leaving.push(name);
|
|
36029
36636
|
state.sceneLeaving = leaving;
|
|
36030
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];
|
|
36031
36649
|
}
|
|
36032
36650
|
/**
|
|
36033
36651
|
* Lets the bundles a leaving scene named go once nothing carries `Exiting` any more: each loaded
|
|
@@ -36037,14 +36655,17 @@ function nameSceneBundles(state, named) {
|
|
|
36037
36655
|
* its feature shows (the splash's 7.5 MiB under the ward). A bundle of another release stays, as
|
|
36038
36656
|
* it would have. The system `assets.leave` calls it in every phase `sync`: after the reconcile of
|
|
36039
36657
|
* `layout`, which tags the leaving elements `Exiting` in the frame after the switch, so the hold
|
|
36040
|
-
* never ends before the leave begins (F51b).
|
|
36041
|
-
*
|
|
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.
|
|
36042
36663
|
*
|
|
36043
36664
|
* @param ctx - Domain context of the plugin.
|
|
36044
36665
|
*/
|
|
36045
36666
|
function releaseSceneLeft(ctx) {
|
|
36046
36667
|
const state = ctx.state;
|
|
36047
|
-
if (state.sceneLeaving.length === 0 || state.exiting > 0) return;
|
|
36668
|
+
if (state.sceneLeaving.length === 0 || state.exiting > 0 || state.sceneComing.length > 0) return;
|
|
36048
36669
|
const names = state.sceneLeaving;
|
|
36049
36670
|
state.sceneLeaving = [];
|
|
36050
36671
|
if (state.io === void 0) return;
|
|
@@ -36064,7 +36685,7 @@ function releaseSceneLeft(ctx) {
|
|
|
36064
36685
|
function unloadIfLeft(ctx, name, onStack) {
|
|
36065
36686
|
const entry = ctx.state.manifest.bundles[name];
|
|
36066
36687
|
if (ctx.state.records.get(name)?.status !== "loaded" || entry?.release !== "leave") return;
|
|
36067
|
-
if (isHeld(ctx, name, entry, onStack)) return;
|
|
36688
|
+
if (isHeld$1(ctx, name, entry, onStack)) return;
|
|
36068
36689
|
unloadBundle(ctx, name, "leave");
|
|
36069
36690
|
}
|
|
36070
36691
|
//#endregion
|
|
@@ -36457,7 +37078,7 @@ function affordable(ctx, distances) {
|
|
|
36457
37078
|
for (const name of ordered) {
|
|
36458
37079
|
const entry = ctx.state.manifest.bundles[name];
|
|
36459
37080
|
if (entry === void 0 || entry.tier === "lazy") continue;
|
|
36460
|
-
if (entry.release === "leave" && !isHeld(ctx, name, entry, onStack)) continue;
|
|
37081
|
+
if (entry.release === "leave" && !isHeld$1(ctx, name, entry, onStack)) continue;
|
|
36461
37082
|
if (!isStillWanted(ctx.state, name)) continue;
|
|
36462
37083
|
if (running + entry.mb > ctx.config.textureBudgetMb) break;
|
|
36463
37084
|
running += entry.mb;
|
|
@@ -36893,6 +37514,7 @@ function releaseAll(state) {
|
|
|
36893
37514
|
state.sceneId = void 0;
|
|
36894
37515
|
state.sceneNamed = [];
|
|
36895
37516
|
state.sceneLeaving = [];
|
|
37517
|
+
state.sceneComing = [];
|
|
36896
37518
|
state.exiting = 0;
|
|
36897
37519
|
state.restPending = false;
|
|
36898
37520
|
state.stamps = void 0;
|
|
@@ -37042,6 +37664,7 @@ function createAssetsApi(ctx) {
|
|
|
37042
37664
|
audio: (key) => loadedRecord(assets, key)?.audio.get(key),
|
|
37043
37665
|
usage: () => usage(assets),
|
|
37044
37666
|
sceneBundles: (named) => nameSceneBundles(ctx.state, named),
|
|
37667
|
+
sceneComing: (named) => nameComingBundles(ctx.state, named),
|
|
37045
37668
|
has: (bundle) => Object.hasOwn(ctx.state.manifest.bundles, bundle),
|
|
37046
37669
|
companions: { add: (companion) => addCompanion(ctx.state, companion) }
|
|
37047
37670
|
};
|
|
@@ -37710,6 +38333,7 @@ function createAssetsState() {
|
|
|
37710
38333
|
swapping: void 0,
|
|
37711
38334
|
sceneNamed: [],
|
|
37712
38335
|
sceneLeaving: [],
|
|
38336
|
+
sceneComing: [],
|
|
37713
38337
|
exiting: 0,
|
|
37714
38338
|
companions: [],
|
|
37715
38339
|
restPending: false,
|
|
@@ -37778,161 +38402,7 @@ function createScenesApi(ctx) {
|
|
|
37778
38402
|
};
|
|
37779
38403
|
}
|
|
37780
38404
|
//#endregion
|
|
37781
|
-
//#region src/plugins/scenes/
|
|
37782
|
-
/**
|
|
37783
|
-
* A promise that resolves when the node was aborted. Already aborted resolves at once.
|
|
37784
|
-
*
|
|
37785
|
-
* @param signal - The abort signal of the node being entered.
|
|
37786
|
-
* @returns A promise that resolves on abort and never rejects.
|
|
37787
|
-
*/
|
|
37788
|
-
function whenAborted$1(signal) {
|
|
37789
|
-
return new Promise((resolve) => {
|
|
37790
|
-
if (signal.aborted) {
|
|
37791
|
-
resolve();
|
|
37792
|
-
return;
|
|
37793
|
-
}
|
|
37794
|
-
signal.addEventListener("abort", () => resolve(), { once: true });
|
|
37795
|
-
});
|
|
37796
|
-
}
|
|
37797
|
-
/**
|
|
37798
|
-
* Waits for the textures of loaded bundles to reach the GPU, or for the abort. It never rejects:
|
|
37799
|
-
* a warm that fails (an unload during it) is logged and the switch goes on, its first draw
|
|
37800
|
-
* uploads as before. Headless, without a device or with `uploads: 0` the warms answer at once,
|
|
37801
|
-
* and textures already on the GPU are not waited for.
|
|
37802
|
-
*
|
|
37803
|
-
* @param ctx - Domain context of the plugin.
|
|
37804
|
-
* @param bundles - The bundles the scene being entered names, all of them loaded.
|
|
37805
|
-
* @param signal - The abort signal of the node.
|
|
37806
|
-
* @returns A promise that resolves when every warm settled or the node was aborted.
|
|
37807
|
-
*/
|
|
37808
|
-
async function untilWarmed(ctx, bundles, signal) {
|
|
37809
|
-
const warms = bundles.map((bundle) => ctx.deps.assets.warm(bundle).catch((error) => {
|
|
37810
|
-
ctx.log.warn("scenes: a bundle's textures were not uploaded before the switch", {
|
|
37811
|
-
bundle,
|
|
37812
|
-
error
|
|
37813
|
-
});
|
|
37814
|
-
}));
|
|
37815
|
-
await Promise.race([Promise.all(warms), whenAborted$1(signal)]);
|
|
37816
|
-
}
|
|
37817
|
-
/**
|
|
37818
|
-
* Loads every bundle the scene names at once and returns as soon as all loads settled or the node
|
|
37819
|
-
* is aborted. The first load that fails while the node still runs is rethrown, so `flow` rolls
|
|
37820
|
-
* the node back; one that fails after the abort is logged, because nobody waits for it any more.
|
|
37821
|
-
*
|
|
37822
|
-
* @param ctx - Domain context of the plugin.
|
|
37823
|
-
* @param bundles - The bundles the scene being entered names, its own bundle first.
|
|
37824
|
-
* @param signal - The abort signal of the node.
|
|
37825
|
-
* @returns A promise that resolves when the loads settled or the node was aborted.
|
|
37826
|
-
* @throws {Error} Whatever the first failed load rejected with, while the node was not aborted.
|
|
37827
|
-
*/
|
|
37828
|
-
async function untilAborted(ctx, bundles, signal) {
|
|
37829
|
-
const load = {
|
|
37830
|
-
failed: false,
|
|
37831
|
-
error: void 0
|
|
37832
|
-
};
|
|
37833
|
-
const loads = bundles.map((bundle) => ctx.deps.assets.load(bundle).catch((error) => {
|
|
37834
|
-
if (!load.failed) {
|
|
37835
|
-
load.failed = true;
|
|
37836
|
-
load.error = error;
|
|
37837
|
-
}
|
|
37838
|
-
if (signal.aborted) ctx.log.warn("scenes: a cancelled bundle load failed", {
|
|
37839
|
-
bundle,
|
|
37840
|
-
error
|
|
37841
|
-
});
|
|
37842
|
-
}));
|
|
37843
|
-
await Promise.race([Promise.all(loads), whenAborted$1(signal)]);
|
|
37844
|
-
if (load.failed && !signal.aborted) throw load.error;
|
|
37845
|
-
}
|
|
37846
|
-
/**
|
|
37847
|
-
* Reads the committed state a scene's `bundles` gets: the frozen trees of the store, in a frozen
|
|
37848
|
-
* wrapper. During the scene stage no node writes, so it is the state the node is entered with.
|
|
37849
|
-
*
|
|
37850
|
-
* @param ctx - Domain context of the plugin.
|
|
37851
|
-
* @returns The committed `player` and `session`.
|
|
37852
|
-
*/
|
|
37853
|
-
function committedState(ctx) {
|
|
37854
|
-
const { player, session } = ctx.deps.model.store.snapshot();
|
|
37855
|
-
return Object.freeze({
|
|
37856
|
-
player,
|
|
37857
|
-
session
|
|
37858
|
-
});
|
|
37859
|
-
}
|
|
37860
|
-
/**
|
|
37861
|
-
* Tells whether two lists name the same bundles in the same order.
|
|
37862
|
-
*
|
|
37863
|
-
* @param left - One list.
|
|
37864
|
-
* @param right - The other list.
|
|
37865
|
-
* @returns True when both have the same names at the same places.
|
|
37866
|
-
* @example
|
|
37867
|
-
* ```ts
|
|
37868
|
-
* sameNames(["location", "ep01"], ["location", "ep02"]); // false
|
|
37869
|
-
* ```
|
|
37870
|
-
*/
|
|
37871
|
-
function sameNames(left, right) {
|
|
37872
|
-
return left.length === right.length && left.every((name, index) => name === right[index]);
|
|
37873
|
-
}
|
|
37874
|
-
/**
|
|
37875
|
-
* Takes the expected scene and clears the record, so one `expect` mounts one scene.
|
|
37876
|
-
*
|
|
37877
|
-
* @param ctx - Domain context of the plugin.
|
|
37878
|
-
* @returns The scene id that was recorded or expected, or `undefined` when there was none.
|
|
37879
|
-
*/
|
|
37880
|
-
function takePending(ctx) {
|
|
37881
|
-
const target = ctx.state.pending;
|
|
37882
|
-
ctx.state.pending = void 0;
|
|
37883
|
-
return target;
|
|
37884
|
-
}
|
|
37885
|
-
/**
|
|
37886
|
-
* Tells whether the node is a transit node of a fast walk: the walk passes through it and builds
|
|
37887
|
-
* no scene there.
|
|
37888
|
-
*
|
|
37889
|
-
* @param node - The node being entered.
|
|
37890
|
-
* @param run - The runner's mode and signal.
|
|
37891
|
-
* @returns True for a node without `rest` entered in fast mode.
|
|
37892
|
-
*/
|
|
37893
|
-
function isFastTransit(node, run) {
|
|
37894
|
-
return run.mode === "fast" && !node.rest;
|
|
37895
|
-
}
|
|
37896
|
-
/**
|
|
37897
|
-
* Reads the scene the node asks for. In fast mode a transit node only records it: the walk passes
|
|
37898
|
-
* through many of them and the picture is built once, at the rest point. In live mode a transit
|
|
37899
|
-
* node without a scene of its own takes the expected one, so a restore at a popup that waits for
|
|
37900
|
-
* its answer builds the scene under it; one with its own scene leaves the record alone.
|
|
37901
|
-
*
|
|
37902
|
-
* @param ctx - Domain context of the plugin.
|
|
37903
|
-
* @param node - The node being entered.
|
|
37904
|
-
* @param run - The runner's mode and signal.
|
|
37905
|
-
* @returns The scene id to switch to, or `undefined` when nothing has to happen.
|
|
37906
|
-
*/
|
|
37907
|
-
function targetOf(ctx, node, run) {
|
|
37908
|
-
const state = ctx.state;
|
|
37909
|
-
if (isFastTransit(node, run)) {
|
|
37910
|
-
state.pending = node.scene ?? state.pending;
|
|
37911
|
-
return;
|
|
37912
|
-
}
|
|
37913
|
-
if (!node.rest) return node.scene ?? takePending(ctx);
|
|
37914
|
-
const target = node.scene ?? state.pending;
|
|
37915
|
-
state.pending = void 0;
|
|
37916
|
-
if (target === void 0 && state.current === void 0) ctx.log.warn("scenes: a rest node was entered with no scene", { path: node.path });
|
|
37917
|
-
return target;
|
|
37918
|
-
}
|
|
37919
|
-
/**
|
|
37920
|
-
* Reads the scene an `over` node asks for. An over node takes the expected scene and clears the
|
|
37921
|
-
* record, whatever is mounted, so a restore at a popup builds the scene under it, whether the
|
|
37922
|
-
* popup rests or waits for its answer. With nothing expected it switches nothing. In fast mode an
|
|
37923
|
-
* over transit node takes nothing and leaves the record alone: a walk builds no scene on the way.
|
|
37924
|
-
* A scene the over node names itself is only warned about.
|
|
37925
|
-
*
|
|
37926
|
-
* @param ctx - Domain context of the plugin.
|
|
37927
|
-
* @param node - The over node being entered.
|
|
37928
|
-
* @param run - The runner's mode and signal.
|
|
37929
|
-
* @returns The expected scene id, or `undefined` when nothing has to happen.
|
|
37930
|
-
*/
|
|
37931
|
-
function overTargetOf(ctx, node, run) {
|
|
37932
|
-
if (node.scene !== void 0) ctx.log.warn("scenes: an over node cannot name a scene", { path: node.path });
|
|
37933
|
-
if (isFastTransit(node, run)) return void 0;
|
|
37934
|
-
return takePending(ctx);
|
|
37935
|
-
}
|
|
38405
|
+
//#region src/plugins/scenes/swap.ts
|
|
37936
38406
|
/**
|
|
37937
38407
|
* The projections of the scene that leaves which the scene that comes does not list. A projection
|
|
37938
38408
|
* both list stays mounted: the layers of a projection are the ones its spec names, and both scenes
|
|
@@ -37946,23 +38416,55 @@ function leavingOf(previous, scene) {
|
|
|
37946
38416
|
return previous.projections.filter((name) => !scene.projections.includes(name));
|
|
37947
38417
|
}
|
|
37948
38418
|
/**
|
|
37949
|
-
*
|
|
37950
|
-
*
|
|
37951
|
-
*
|
|
37952
|
-
*
|
|
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.
|
|
37953
38452
|
*
|
|
37954
38453
|
* @param ctx - Domain context of the plugin.
|
|
37955
38454
|
* @param scene - The scene whose bundles are there.
|
|
37956
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.
|
|
37957
38458
|
*/
|
|
37958
|
-
function
|
|
38459
|
+
function swapIn(ctx, scene, named, build) {
|
|
37959
38460
|
const state = ctx.state;
|
|
37960
38461
|
const projection = ctx.deps.world.projection;
|
|
37961
|
-
const from = state.current;
|
|
38462
|
+
const from = build === void 0 ? state.current : build.from;
|
|
37962
38463
|
const previous = from === void 0 ? void 0 : state.scenes.get(from);
|
|
37963
38464
|
projection.setLayers(scene.layers);
|
|
37964
38465
|
if (previous !== void 0) projection.unmount(leavingOf(previous, scene));
|
|
37965
|
-
projection.mount(scene.projections, state.owner);
|
|
38466
|
+
if (build === void 0) projection.mount(scene.projections, state.owner);
|
|
38467
|
+
else projection.commit();
|
|
37966
38468
|
state.current = scene.id;
|
|
37967
38469
|
ctx.deps.world.ecs.setScene(scene.id);
|
|
37968
38470
|
state.named = named;
|
|
@@ -37974,16 +38476,424 @@ function apply(ctx, scene, named) {
|
|
|
37974
38476
|
to: scene.id,
|
|
37975
38477
|
music: scene.music
|
|
37976
38478
|
});
|
|
37977
|
-
if (typeof __MOKU_GAME_AGENT__ !== "undefined" && __MOKU_GAME_AGENT__) ctx.
|
|
37978
|
-
|
|
37979
|
-
|
|
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 });
|
|
38709
|
+
});
|
|
38710
|
+
}
|
|
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
|
|
37980
38772
|
});
|
|
37981
38773
|
}
|
|
37982
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
|
+
/**
|
|
37983
38892
|
* Switches to another scene: it reads what the scene names from the committed state, awaits all
|
|
37984
38893
|
* of it and only then builds the scene. In live mode it waits between the two for the textures of
|
|
37985
38894
|
* those bundles to reach the GPU; a fast walk does not, so tests and restores keep their pace. An
|
|
37986
|
-
* 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.
|
|
37987
38897
|
*
|
|
37988
38898
|
* @param ctx - Domain context of the plugin.
|
|
37989
38899
|
* @param target - Id of the scene to mount.
|
|
@@ -37999,7 +38909,7 @@ async function switchTo(ctx, target, run) {
|
|
|
37999
38909
|
if (run.mode === "live" && !run.signal.aborted) await untilWarmed(ctx, named, run.signal);
|
|
38000
38910
|
const stopped = ctx.state.teardown === void 0;
|
|
38001
38911
|
if (run.signal.aborted || stopped) return;
|
|
38002
|
-
|
|
38912
|
+
await standUp(ctx, scene, named, run);
|
|
38003
38913
|
}
|
|
38004
38914
|
/**
|
|
38005
38915
|
* Reads again what the mounted scene names, at a node that keeps it: a bundle it names now and
|
|
@@ -38035,8 +38945,8 @@ async function reread(ctx, run) {
|
|
|
38035
38945
|
* @param run - The runner's mode and signal.
|
|
38036
38946
|
* @returns A promise that resolves once the scene stands, or at once when nothing changed.
|
|
38037
38947
|
* @throws {Error} For a scene id no feature declared, and for a `bundles` that throws or returns
|
|
38038
|
-
* something that is not a list of names. A failed load
|
|
38039
|
-
* `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.
|
|
38040
38950
|
*/
|
|
38041
38951
|
async function enterScene(ctx, node, run) {
|
|
38042
38952
|
const target = node.over ? overTargetOf(ctx, node, run) : targetOf(ctx, node, run);
|
|
@@ -38050,8 +38960,9 @@ async function enterScene(ctx, node, run) {
|
|
|
38050
38960
|
//#endregion
|
|
38051
38961
|
//#region src/plugins/scenes/lifecycle.ts
|
|
38052
38962
|
/**
|
|
38053
|
-
* @file scenes plugin — lifecycle: the scenes read out of the
|
|
38054
|
-
* 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.
|
|
38055
38966
|
*/
|
|
38056
38967
|
/**
|
|
38057
38968
|
* Resolves the dependency APIs `flow`, `world`, `assets`, `time`, `model` and `renderer` with
|
|
@@ -38112,6 +39023,20 @@ function readScenes(ctx) {
|
|
|
38112
39023
|
}
|
|
38113
39024
|
}
|
|
38114
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
|
+
/**
|
|
38115
39040
|
* Starts the plugin in `onStart`: it collects the scenes of the features and registers the one
|
|
38116
39041
|
* callback that switches them, on the `scene` stage of entering a node.
|
|
38117
39042
|
*
|
|
@@ -38130,8 +39055,9 @@ function startScenes(ctx) {
|
|
|
38130
39055
|
}
|
|
38131
39056
|
/**
|
|
38132
39057
|
* Removes the `onEnter` callback in `onStop` and forgets every scene and the bundles the mounted
|
|
38133
|
-
* one named. Nothing is unmounted
|
|
38134
|
-
* 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.
|
|
38135
39061
|
*
|
|
38136
39062
|
* @param teardown - What the teardown context carries.
|
|
38137
39063
|
* @param teardown.state - The plugin state.
|
|
@@ -38145,6 +39071,8 @@ function stopScenes(teardown) {
|
|
|
38145
39071
|
const state = teardown.state;
|
|
38146
39072
|
state.teardown?.();
|
|
38147
39073
|
state.teardown = void 0;
|
|
39074
|
+
dropBuild(state.build);
|
|
39075
|
+
delete state.build;
|
|
38148
39076
|
state.scenes.clear();
|
|
38149
39077
|
state.current = void 0;
|
|
38150
39078
|
state.pending = void 0;
|
|
@@ -38189,8 +39117,6 @@ function createScenesState() {
|
|
|
38189
39117
|
teardown: void 0
|
|
38190
39118
|
};
|
|
38191
39119
|
}
|
|
38192
|
-
//#endregion
|
|
38193
|
-
//#region src/plugins/scenes/index.ts
|
|
38194
39120
|
/**
|
|
38195
39121
|
* Scenes plugin: `app.scenes.current()`, and `defineScene` for the game.
|
|
38196
39122
|
*
|
|
@@ -38212,6 +39138,9 @@ function createScenesState() {
|
|
|
38212
39138
|
* layers: { room: {} },
|
|
38213
39139
|
* projections: [wardRoom]
|
|
38214
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 } } });
|
|
38215
39144
|
* ```
|
|
38216
39145
|
*/
|
|
38217
39146
|
const scenesPlugin = /*#__PURE__*/ createPlugin$1("scenes", {
|
|
@@ -38223,9 +39152,11 @@ const scenesPlugin = /*#__PURE__*/ createPlugin$1("scenes", {
|
|
|
38223
39152
|
modelPlugin,
|
|
38224
39153
|
rendererPlugin
|
|
38225
39154
|
],
|
|
39155
|
+
config: { buildMs: "auto" },
|
|
38226
39156
|
events: (register) => register.map({ "scenes:changed": "The mounted scene changed" }),
|
|
38227
39157
|
createState: createScenesState,
|
|
38228
39158
|
api: createScenesApi,
|
|
39159
|
+
onInit: ({ config }) => checkConfig(config),
|
|
38229
39160
|
onStart: startScenes,
|
|
38230
39161
|
onStop: ({ state }) => stopScenes({ state })
|
|
38231
39162
|
});
|
|
@@ -52492,6 +53423,9 @@ function ownerOf(state, name) {
|
|
|
52492
53423
|
/**
|
|
52493
53424
|
* Gives the host as parent to every view of one projection that has no parent and is not held
|
|
52494
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.
|
|
52495
53429
|
*
|
|
52496
53430
|
* @param ctx - Domain context of the ui plugin.
|
|
52497
53431
|
* @param state - The jsx state.
|
|
@@ -52500,8 +53434,10 @@ function ownerOf(state, name) {
|
|
|
52500
53434
|
*/
|
|
52501
53435
|
function parentViews(ctx, state, name, host) {
|
|
52502
53436
|
const ecs = ctx.deps.world.ecs;
|
|
52503
|
-
|
|
52504
|
-
|
|
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;
|
|
52505
53441
|
if (ecs.has(view, Parent) && !state.released.has(view)) continue;
|
|
52506
53442
|
ecs.add(view, Parent({ entity: host }));
|
|
52507
53443
|
state.hosted.set(view, host);
|
|
@@ -52623,17 +53559,21 @@ function keptSlot(state, root) {
|
|
|
52623
53559
|
}
|
|
52624
53560
|
/**
|
|
52625
53561
|
* Tells whether a host element may take a root as its slot: it names the root's projection, it is
|
|
52626
|
-
* 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.
|
|
52627
53565
|
*
|
|
52628
53566
|
* @param state - The jsx state.
|
|
52629
53567
|
* @param root - The root.
|
|
52630
53568
|
* @param host - An entity of the host set.
|
|
53569
|
+
* @param isAside - Whether an entity belongs to the scene `world` builds aside.
|
|
52631
53570
|
* @returns The host element, or `undefined` when it cannot take the root.
|
|
52632
53571
|
*/
|
|
52633
|
-
function slotFor(state, root, host) {
|
|
53572
|
+
function slotFor(state, root, host, isAside) {
|
|
52634
53573
|
const element = state.elements.get(host);
|
|
52635
53574
|
if (element === void 0 || state.exiting.has(host) || element.root === root.entity) return;
|
|
52636
|
-
|
|
53575
|
+
if (!hostsOf(element.node).includes(root.name)) return void 0;
|
|
53576
|
+
return isAside(element.root) === isAside(root.entity) ? element : void 0;
|
|
52637
53577
|
}
|
|
52638
53578
|
/**
|
|
52639
53579
|
* The host element a root is laid out in (spec 16 U3.2). A popup root never is. A root keeps its
|
|
@@ -52642,13 +53582,14 @@ function slotFor(state, root, host) {
|
|
|
52642
53582
|
*
|
|
52643
53583
|
* @param state - The jsx state.
|
|
52644
53584
|
* @param root - The root.
|
|
53585
|
+
* @param isAside - Whether an entity belongs to the scene `world` builds aside.
|
|
52645
53586
|
* @returns The slot, or `undefined` for a root laid out on the viewport.
|
|
52646
53587
|
*/
|
|
52647
|
-
function slotOf$1(state, root) {
|
|
53588
|
+
function slotOf$1(state, root, isAside) {
|
|
52648
53589
|
if (root.popup !== void 0) return void 0;
|
|
52649
53590
|
const kept = keptSlot(state, root);
|
|
52650
53591
|
if (kept !== void 0) return kept;
|
|
52651
|
-
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;
|
|
52652
53593
|
}
|
|
52653
53594
|
/**
|
|
52654
53595
|
* Where an element is drawn at rest, in root coordinates: its drawn rect (`visualRectOf`), and for
|
|
@@ -53078,7 +54019,8 @@ function createFields(ctx, links) {
|
|
|
53078
54019
|
}
|
|
53079
54020
|
/**
|
|
53080
54021
|
* Spawns the four parts of a field, each a child of the field: its pose, its `Parent`, its
|
|
53081
|
-
* 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).
|
|
53082
54024
|
*
|
|
53083
54025
|
* @param entity - The field entity.
|
|
53084
54026
|
* @param values - What the parts draw.
|
|
@@ -53086,15 +54028,22 @@ function createFields(ctx, links) {
|
|
|
53086
54028
|
*/
|
|
53087
54029
|
function spawnParts(entity, values) {
|
|
53088
54030
|
const ecs = ctx.deps.world.ecs;
|
|
54031
|
+
const projection = ctx.deps.world.projection;
|
|
53089
54032
|
const parent = Parent({ entity });
|
|
53090
|
-
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([
|
|
53091
54040
|
Transform(part.pose),
|
|
53092
54041
|
parent,
|
|
53093
54042
|
Shape(part.shape)
|
|
53094
54043
|
]);
|
|
53095
54044
|
return {
|
|
53096
54045
|
selection: box(values.selection),
|
|
53097
|
-
text:
|
|
54046
|
+
text: spawn([
|
|
53098
54047
|
Transform(values.text.pose),
|
|
53099
54048
|
parent,
|
|
53100
54049
|
Text({
|
|
@@ -53825,18 +54774,20 @@ function nodeOf(state, element, resolvedOf, place) {
|
|
|
53825
54774
|
/**
|
|
53826
54775
|
* Every mounted root in the order a reader meets them: the popup roots first, the highest `Order`
|
|
53827
54776
|
* of them first, then the projection roots in the layer order of the scene, and by their `Order`
|
|
53828
|
-
* 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).
|
|
53829
54779
|
*
|
|
53830
54780
|
* @param state - The jsx state.
|
|
53831
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.
|
|
53832
54783
|
* @returns The roots, sorted.
|
|
53833
54784
|
* @example
|
|
53834
54785
|
* ```ts
|
|
53835
54786
|
* sortedRoots({ roots: new Map() } as unknown as JsxState, ["ui"]); // []
|
|
53836
54787
|
* ```
|
|
53837
54788
|
*/
|
|
53838
|
-
function sortedRoots(state, layers) {
|
|
53839
|
-
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));
|
|
53840
54791
|
const popups = roots.filter((root) => root.popup !== void 0);
|
|
53841
54792
|
const screens = roots.filter((root) => root.popup === void 0);
|
|
53842
54793
|
const indexOf = (root) => {
|
|
@@ -53861,22 +54812,24 @@ function placeOf(root, slotPose) {
|
|
|
53861
54812
|
return (rect) => drawnRect(pose, rect);
|
|
53862
54813
|
}
|
|
53863
54814
|
/**
|
|
53864
|
-
* Reads every mounted root, popups first, then the projection roots in layer order
|
|
53865
|
-
* 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.
|
|
53866
54818
|
*
|
|
53867
54819
|
* @param state - The jsx state.
|
|
53868
54820
|
* @param layers - The layer names of the scene, in draw order.
|
|
53869
54821
|
* @param resolvedOf - Reads the string `text` resolved for an entity; none in a read without a world.
|
|
53870
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.
|
|
53871
54824
|
* @returns One root node, or every root under a `screen` node.
|
|
53872
54825
|
* @example
|
|
53873
54826
|
* ```ts
|
|
53874
54827
|
* readTree({ roots: new Map(), elements: new Map() } as unknown as JsxState).type; // "screen"
|
|
53875
54828
|
* ```
|
|
53876
54829
|
*/
|
|
53877
|
-
function readTree(state, layers = [], resolvedOf, slotPose) {
|
|
54830
|
+
function readTree(state, layers = [], resolvedOf, slotPose, isAside) {
|
|
53878
54831
|
const nodes = [];
|
|
53879
|
-
for (const root of sortedRoots(state, layers)) {
|
|
54832
|
+
for (const root of sortedRoots(state, layers, isAside)) {
|
|
53880
54833
|
const element = root.element === void 0 ? void 0 : state.elements.get(root.element);
|
|
53881
54834
|
if (element !== void 0) nodes.push(nodeOf(state, element, resolvedOf, placeOf(root, slotPose)));
|
|
53882
54835
|
}
|
|
@@ -53919,15 +54872,17 @@ function readingOrder(first, second) {
|
|
|
53919
54872
|
/**
|
|
53920
54873
|
* The root the keyboard works in: the first uncovered popup in reader order, else the last screen
|
|
53921
54874
|
* root, the one in the top layer. A root of the HUD layer never takes it: the HUD draws above every
|
|
53922
|
-
* 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.
|
|
53923
54877
|
*
|
|
53924
54878
|
* @param state - The jsx state.
|
|
53925
54879
|
* @param layers - The layer names of the scene, in draw order.
|
|
53926
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.
|
|
53927
54882
|
* @returns The root, or `undefined` when nothing is mounted.
|
|
53928
54883
|
*/
|
|
53929
|
-
function focusRoot(state, layers, hud) {
|
|
53930
|
-
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));
|
|
53931
54886
|
return roots.find((root) => root.popup !== void 0 && !root.covered) ?? roots.findLast((root) => root.popup === void 0 && root.layer !== hud);
|
|
53932
54887
|
}
|
|
53933
54888
|
/**
|
|
@@ -54387,7 +55342,8 @@ function zIndexOnRoot(element) {
|
|
|
54387
55342
|
};
|
|
54388
55343
|
}
|
|
54389
55344
|
/**
|
|
54390
|
-
* 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).
|
|
54391
55347
|
*
|
|
54392
55348
|
* @param ctx - Domain context of the ui plugin.
|
|
54393
55349
|
* @returns One finding per rule and element, in element order.
|
|
@@ -54396,7 +55352,7 @@ function runLint(ctx) {
|
|
|
54396
55352
|
const findings = [];
|
|
54397
55353
|
const scale = ctx.deps.renderer.viewport.size().scale;
|
|
54398
55354
|
for (const element of ctx.state.jsx.elements.values()) {
|
|
54399
|
-
if (!element.live) continue;
|
|
55355
|
+
if (!element.live || ctx.deps.world.projection.isBuilding(element.root)) continue;
|
|
54400
55356
|
for (const finding of [
|
|
54401
55357
|
tapTarget(ctx, element, scale),
|
|
54402
55358
|
textOverflow(ctx, element),
|
|
@@ -56561,6 +57517,8 @@ function createReconciler(ctx, modules, fields) {
|
|
|
56561
57517
|
let slotsRoots = -1;
|
|
56562
57518
|
let placedUnder;
|
|
56563
57519
|
let rendered = 0;
|
|
57520
|
+
const isAside = (entity) => ctx.deps.world.projection.isBuilding(entity);
|
|
57521
|
+
let released = false;
|
|
56564
57522
|
/**
|
|
56565
57523
|
* Registers the key of an element under its root, so `entityOf` and `find` answer for it. A key
|
|
56566
57524
|
* `ui` gave a node of its own, a spacer's, is never registered.
|
|
@@ -56589,7 +57547,9 @@ function createReconciler(ctx, modules, fields) {
|
|
|
56589
57547
|
return range.rowStyle(parent, node, modules.styles.resolveElement(styleOf(node), is));
|
|
56590
57548
|
}
|
|
56591
57549
|
/**
|
|
56592
|
-
* 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).
|
|
56593
57553
|
*
|
|
56594
57554
|
* @param root - The root being reconciled.
|
|
56595
57555
|
* @param parent - The parent element, or nothing for a root element.
|
|
@@ -56602,6 +57562,7 @@ function createReconciler(ctx, modules, fields) {
|
|
|
56602
57562
|
function enter(root, parent, node, identity, instance, index) {
|
|
56603
57563
|
checkTag(node);
|
|
56604
57564
|
const entity = ecs.spawn(UI_OWNER, []);
|
|
57565
|
+
if (isAside(root.entity)) ctx.deps.world.projection.adopt(entity);
|
|
56605
57566
|
const is = isFlagsOf(node, {
|
|
56606
57567
|
pressed: false,
|
|
56607
57568
|
hover: false,
|
|
@@ -57051,13 +58012,18 @@ function createReconciler(ctx, modules, fields) {
|
|
|
57051
58012
|
root.needsSolve = true;
|
|
57052
58013
|
}
|
|
57053
58014
|
/**
|
|
57054
|
-
* 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).
|
|
57055
58017
|
*
|
|
57056
58018
|
* @param element - The element that left the description.
|
|
57057
58019
|
* @param cause - Why it leaves: `"change"` for a commit, `"mount"` when it goes with its screen.
|
|
57058
58020
|
* @param leads - The element is the top of a root that goes with its screen.
|
|
57059
58021
|
*/
|
|
57060
58022
|
function exitElement(element, cause, leads = false) {
|
|
58023
|
+
if (isAside(element.root)) {
|
|
58024
|
+
dropElement(element);
|
|
58025
|
+
return;
|
|
58026
|
+
}
|
|
57061
58027
|
forgetSubtree(element);
|
|
57062
58028
|
forgetInstances(state, element.identity);
|
|
57063
58029
|
state.exiting.add(element.entity);
|
|
@@ -57147,6 +58113,17 @@ function createReconciler(ctx, modules, fields) {
|
|
|
57147
58113
|
ecs.despawn(element.entity);
|
|
57148
58114
|
}
|
|
57149
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
|
+
/**
|
|
57150
58127
|
* Despawns every exiting element whose motions finished, and closes a popup whose root is gone.
|
|
57151
58128
|
*/
|
|
57152
58129
|
function sweep() {
|
|
@@ -57310,7 +58287,7 @@ function createReconciler(ctx, modules, fields) {
|
|
|
57310
58287
|
*/
|
|
57311
58288
|
function warnTaken(root, slot) {
|
|
57312
58289
|
for (const host of state.hosts) {
|
|
57313
|
-
const element = host === slot ? void 0 : slotFor(state, root, host);
|
|
58290
|
+
const element = host === slot ? void 0 : slotFor(state, root, host, isAside);
|
|
57314
58291
|
const once = `slot-taken:${root.name}`;
|
|
57315
58292
|
if (element === void 0 || element.warned.has(once)) continue;
|
|
57316
58293
|
element.warned.add(once);
|
|
@@ -57342,7 +58319,7 @@ function createReconciler(ctx, modules, fields) {
|
|
|
57342
58319
|
slotsRoots = state.roots.size;
|
|
57343
58320
|
for (const root of state.roots.values()) {
|
|
57344
58321
|
if (state.removing.has(root.entity)) continue;
|
|
57345
|
-
const slot = isCyclic(root) ? void 0 : slotOf$1(state, root);
|
|
58322
|
+
const slot = isCyclic(root) ? void 0 : slotOf$1(state, root, isAside);
|
|
57346
58323
|
if (typeof __MOKU_GAME_DEV__ !== "undefined" && __MOKU_GAME_DEV__ && slot !== void 0) warnTaken(root, slot);
|
|
57347
58324
|
if (slot === root.slot) continue;
|
|
57348
58325
|
root.slot = slot;
|
|
@@ -57477,7 +58454,9 @@ function createReconciler(ctx, modules, fields) {
|
|
|
57477
58454
|
* Keeps an element whose spawn has just been applied: the world calls it from the `Box` hook, at
|
|
57478
58455
|
* the flush of phase `layout`. Its enter hook waits for `playEntered`, so the game's systems see
|
|
57479
58456
|
* the entity first (F48). The hook itself plays at once, so they see the start pose it writes (F49);
|
|
57480
|
-
* `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).
|
|
57481
58460
|
*
|
|
57482
58461
|
* @param entity - The entity the `Box` was attached to.
|
|
57483
58462
|
*/
|
|
@@ -57487,21 +58466,45 @@ function createReconciler(ctx, modules, fields) {
|
|
|
57487
58466
|
else entering.list.push(entity);
|
|
57488
58467
|
entering.count += 1;
|
|
57489
58468
|
const element = state.elements.get(entity);
|
|
57490
|
-
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
|
+
}
|
|
57491
58486
|
}
|
|
57492
58487
|
/**
|
|
57493
58488
|
* Plays the enter hooks queued since the last call, in the order the elements came. The `ui.enter`
|
|
57494
58489
|
* system calls it in phase `sync`: after the game's systems, which may have written what a hook
|
|
57495
58490
|
* reads (a list's scroll), and before `renderer.sync`, so an element is never drawn at its rest
|
|
57496
|
-
* 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.
|
|
57497
58495
|
*/
|
|
57498
58496
|
function playEntered() {
|
|
57499
58497
|
const entering = state.entering;
|
|
58498
|
+
let held = 0;
|
|
57500
58499
|
for (let index = 0; index < entering.count; index += 1) {
|
|
57501
58500
|
const entity = entering.list[index];
|
|
57502
|
-
if (entity
|
|
58501
|
+
if (entity === void 0) continue;
|
|
58502
|
+
if (isAside(entity)) {
|
|
58503
|
+
entering.list[held] = entity;
|
|
58504
|
+
held += 1;
|
|
58505
|
+
} else playEnter(entity);
|
|
57503
58506
|
}
|
|
57504
|
-
entering.count =
|
|
58507
|
+
entering.count = held;
|
|
57505
58508
|
}
|
|
57506
58509
|
/**
|
|
57507
58510
|
* Plays the enter hook of an element whose spawn has been applied, once. A row a range change
|
|
@@ -57642,10 +58645,16 @@ function createReconciler(ctx, modules, fields) {
|
|
|
57642
58645
|
/**
|
|
57643
58646
|
* Reconciles one dirty root: reads its current tree and projection name, then diffs it. The root
|
|
57644
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).
|
|
57645
58650
|
*
|
|
57646
58651
|
* @param root - A root with work this frame.
|
|
57647
58652
|
*/
|
|
57648
58653
|
function reconcileDirtyRoot(root) {
|
|
58654
|
+
if (root.aside === void 0 && isAside(root.entity)) {
|
|
58655
|
+
root.aside = true;
|
|
58656
|
+
state.asideRoots += 1;
|
|
58657
|
+
}
|
|
57649
58658
|
const tree = ecs.get(root.entity, Tree);
|
|
57650
58659
|
const place = ctx.deps.world.projection.keyOf(root.entity);
|
|
57651
58660
|
if (place !== void 0 && place.projection !== root.name) {
|
|
@@ -57663,16 +58672,55 @@ function createReconciler(ctx, modules, fields) {
|
|
|
57663
58672
|
}
|
|
57664
58673
|
}
|
|
57665
58674
|
/**
|
|
57666
|
-
*
|
|
57667
|
-
*
|
|
57668
|
-
*
|
|
57669
|
-
*
|
|
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.
|
|
57670
58717
|
*
|
|
57671
58718
|
* @param time - The frame's `Time`: the scrolls move by its `delta`.
|
|
57672
58719
|
*/
|
|
57673
58720
|
function reconcile(time = NO_TIME) {
|
|
57674
58721
|
sweep();
|
|
57675
58722
|
state.reconciles += 1;
|
|
58723
|
+
settleAside();
|
|
57676
58724
|
if (state.remount) remountRoots();
|
|
57677
58725
|
reorderRoots();
|
|
57678
58726
|
settlePopups(ctx, state, unmountRoot);
|
|
@@ -57712,7 +58760,8 @@ function createReconciler(ctx, modules, fields) {
|
|
|
57712
58760
|
* The second system of phase `layout`: every root takes its slot, then one solve per marked
|
|
57713
58761
|
* root, a root in a slot after the root of its slot and in its content box, then the rects, the
|
|
57714
58762
|
* rest poses and the hooks, the scrolls settled on what the solve and the render gave them, the
|
|
57715
|
-
* 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
|
|
57716
58765
|
* at its end, so the `change.Box` and `change.Transform` hooks the solve plays get the hint the
|
|
57717
58766
|
* reconcile routed, and the next frame starts with none.
|
|
57718
58767
|
*
|
|
@@ -57733,6 +58782,10 @@ function createReconciler(ctx, modules, fields) {
|
|
|
57733
58782
|
state.risen.clear();
|
|
57734
58783
|
}
|
|
57735
58784
|
if (state.exiting.size > 0 || state.removing.size > 0) sweep();
|
|
58785
|
+
if (released) {
|
|
58786
|
+
released = false;
|
|
58787
|
+
poseReleased();
|
|
58788
|
+
}
|
|
57736
58789
|
state.hints.length = 0;
|
|
57737
58790
|
writeCounters();
|
|
57738
58791
|
}
|
|
@@ -57786,7 +58839,9 @@ function createReconciler(ctx, modules, fields) {
|
|
|
57786
58839
|
* nodes and despawns the root entity. A popup leaves by an event; a projection root leaves the
|
|
57787
58840
|
* way its view did, which the world still answers in its removal hook: `"mount"` with its scene,
|
|
57788
58841
|
* a load or a restore. A root that goes with its screen ends with the exit of its top element
|
|
57789
|
-
* 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).
|
|
57790
58845
|
*
|
|
57791
58846
|
* @param entity - The root entity.
|
|
57792
58847
|
*/
|
|
@@ -57795,6 +58850,10 @@ function createReconciler(ctx, modules, fields) {
|
|
|
57795
58850
|
if (root === void 0 || state.removing.has(entity)) return;
|
|
57796
58851
|
const element = root.element === void 0 ? void 0 : lookup(root.element);
|
|
57797
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
|
+
}
|
|
57798
58857
|
state.removing.add(entity);
|
|
57799
58858
|
hostsMoved(state);
|
|
57800
58859
|
if (element !== void 0) {
|
|
@@ -57804,6 +58863,20 @@ function createReconciler(ctx, modules, fields) {
|
|
|
57804
58863
|
ctx.deps.time.wake();
|
|
57805
58864
|
}
|
|
57806
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
|
+
/**
|
|
57807
58880
|
* Re-resolves the style of one element whose pointer or focus flag changed: a press, a release,
|
|
57808
58881
|
* the mouse coming over it or leaving it, the keyboard focus arriving or leaving. A new rect
|
|
57809
58882
|
* waits for the solve; a new look and a new rest pose apply now. Only this element is restyled:
|
|
@@ -57840,7 +58913,8 @@ function createReconciler(ctx, modules, fields) {
|
|
|
57840
58913
|
queueEnter,
|
|
57841
58914
|
playEntered,
|
|
57842
58915
|
sweep,
|
|
57843
|
-
converged
|
|
58916
|
+
converged,
|
|
58917
|
+
asideBuilt
|
|
57844
58918
|
};
|
|
57845
58919
|
}
|
|
57846
58920
|
//#endregion
|
|
@@ -57916,9 +58990,10 @@ function createJsxApi(ctx, modules) {
|
|
|
57916
58990
|
const state = ctx.state.jsx;
|
|
57917
58991
|
const sceneLayers = sceneLayersOf(ctx);
|
|
57918
58992
|
const layerNames = () => sceneLayers().names;
|
|
58993
|
+
const isAside = (entity) => ctx.deps.world.projection.isBuilding(entity);
|
|
57919
58994
|
const topRoot = () => {
|
|
57920
58995
|
const scene = sceneLayers();
|
|
57921
|
-
return focusRoot(state, scene.names, scene.hud);
|
|
58996
|
+
return focusRoot(state, scene.names, scene.hud, isAside);
|
|
57922
58997
|
};
|
|
57923
58998
|
const fields = createFields(ctx, {
|
|
57924
58999
|
layout: modules.layout,
|
|
@@ -57929,13 +59004,13 @@ function createJsxApi(ctx, modules) {
|
|
|
57929
59004
|
const resolvedOf = (entity) => ctx.deps.world.ecs.get(entity, Text)?.resolved;
|
|
57930
59005
|
const lookup = (entity) => state.elements.get(entity);
|
|
57931
59006
|
const find = (key) => {
|
|
57932
|
-
for (const root of sortedRoots(state, layerNames())) {
|
|
59007
|
+
for (const root of sortedRoots(state, layerNames(), isAside)) {
|
|
57933
59008
|
const entity = state.byKey.get(root.entity)?.get(key);
|
|
57934
59009
|
if (entity !== void 0 && !state.exiting.has(entity)) return entity;
|
|
57935
59010
|
}
|
|
57936
59011
|
};
|
|
57937
59012
|
return {
|
|
57938
|
-
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),
|
|
57939
59014
|
find,
|
|
57940
59015
|
lint: () => runLint(ctx),
|
|
57941
59016
|
fill: (key, value) => {
|
|
@@ -57996,6 +59071,7 @@ function createJsxApi(ctx, modules) {
|
|
|
57996
59071
|
queueEnter: frame.queueEnter,
|
|
57997
59072
|
playEntered: frame.playEntered,
|
|
57998
59073
|
converged: frame.converged,
|
|
59074
|
+
asideBuilt: frame.asideBuilt,
|
|
57999
59075
|
key: (input) => focus.key(input),
|
|
58000
59076
|
blur: focus.blur
|
|
58001
59077
|
};
|
|
@@ -58284,6 +59360,18 @@ function writeRest(ctx, element, parent) {
|
|
|
58284
59360
|
if (visual !== void 0) projection.setRest(element.entity, visual.component, visual.value);
|
|
58285
59361
|
}
|
|
58286
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
|
+
/**
|
|
58287
59375
|
* Asks the projection for the handle of a ui-owned entity. A foreign owner is an error in the
|
|
58288
59376
|
* log and the caller writes the value directly.
|
|
58289
59377
|
*
|
|
@@ -58591,6 +59679,7 @@ function noteEndless(element, motion) {
|
|
|
58591
59679
|
* Plays the `change` hook of one extra component whose fields changed. The motion the last change
|
|
58592
59680
|
* of that name started is cancelled first, so its value stays where it is and the new motion
|
|
58593
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.
|
|
58594
59683
|
*
|
|
58595
59684
|
* @param ctx - Domain context of the ui plugin.
|
|
58596
59685
|
* @param element - The element that carries the extra.
|
|
@@ -58609,7 +59698,7 @@ function playExtraChange(ctx, element, name, values, hint) {
|
|
|
58609
59698
|
element.extraHandles.delete(name);
|
|
58610
59699
|
}
|
|
58611
59700
|
const hook = element.motion?.change?.[name];
|
|
58612
|
-
if (hook === void 0) return false;
|
|
59701
|
+
if (hook === void 0 || isHeld(ctx, element)) return false;
|
|
58613
59702
|
const motion = play(ctx, element, (view) => hook(view, values.previous, values.next, hint));
|
|
58614
59703
|
if (motion === void 0) return false;
|
|
58615
59704
|
element.extraHandles.set(name, motion);
|
|
@@ -58660,8 +59749,9 @@ function still(element) {
|
|
|
58660
59749
|
* 0 ms rest track. That track writes the pose as it starts, so the pivot and the position land in
|
|
58661
59750
|
* the same frame, and ends at the next frame step, where an additive loop re-bases on the new
|
|
58662
59751
|
* rest. An element that has not spawned yet only records it, and so does one whose
|
|
58663
|
-
* `change.Box` motion already played.
|
|
58664
|
-
*
|
|
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.
|
|
58665
59755
|
*
|
|
58666
59756
|
* @param ctx - Domain context of the ui plugin.
|
|
58667
59757
|
* @param element - The element whose rect, style or fit may have changed.
|
|
@@ -58677,6 +59767,13 @@ function repose(ctx, element, parent, hooked, hint) {
|
|
|
58677
59767
|
if (element.type === "content") return;
|
|
58678
59768
|
ctx.deps.world.projection.setRest(element.entity, Transform, next);
|
|
58679
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
|
+
}
|
|
58680
59777
|
const hook = element.motion?.change?.Transform;
|
|
58681
59778
|
if (hook !== void 0) {
|
|
58682
59779
|
play(ctx, element, (view) => hook(view, previous, next, hint));
|
|
@@ -60217,7 +61314,7 @@ function createLayoutApi(ctx) {
|
|
|
60217
61314
|
loop: (element) => startLoop(ctx, element),
|
|
60218
61315
|
change: (element, previous, hint) => {
|
|
60219
61316
|
const hook = element.motion?.change?.Box;
|
|
60220
|
-
if (hook === void 0) return false;
|
|
61317
|
+
if (hook === void 0 || isHeld(ctx, element)) return false;
|
|
60221
61318
|
play(ctx, element, (view) => hook(view, previous, element.rect, hint));
|
|
60222
61319
|
return true;
|
|
60223
61320
|
},
|
|
@@ -60793,8 +61890,9 @@ function registerComponents(ctx, jsx) {
|
|
|
60793
61890
|
* step, the three tap listeners (the `LocalWrite` patch, the pointer tap that clears the keyboard
|
|
60794
61891
|
* focus, and the tap that starts or ends the editing of a text field), the pointer door that opens
|
|
60795
61892
|
* the keyboard inside the DOM listener, the key listener of the focus and `LocalWrite` as an input
|
|
60796
|
-
* control, so the cursor shows a hand over a local-state button
|
|
60797
|
-
*
|
|
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.
|
|
60798
61896
|
*
|
|
60799
61897
|
* The `Box` hook plays an element's enter hook and queues it, so the game's systems see its start
|
|
60800
61898
|
* pose (F49); `ui.enter` starts its loop after them and before the frame is drawn, and plays the
|
|
@@ -60826,7 +61924,7 @@ function openRegistrations(ctx, jsx, layout) {
|
|
|
60826
61924
|
})), ecs.onAdded(Tree, (entity) => {
|
|
60827
61925
|
const place = projection.keyOf(entity);
|
|
60828
61926
|
jsx.mountRoot(entity, place?.projection ?? "ui", ecs.get(entity, Layer)?.name ?? "ui");
|
|
60829
|
-
}), 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) => {
|
|
60830
61928
|
ctx.state.jsx.hints.push(hint);
|
|
60831
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));
|
|
60832
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)));
|
|
@@ -60885,6 +61983,7 @@ function stopUi(state) {
|
|
|
60885
61983
|
state.jsx.released.clear();
|
|
60886
61984
|
state.jsx.scrolls.clear();
|
|
60887
61985
|
state.jsx.remount = false;
|
|
61986
|
+
state.jsx.asideRoots = 0;
|
|
60888
61987
|
state.jsx.hints.length = 0;
|
|
60889
61988
|
state.jsx.entering.list.length = 0;
|
|
60890
61989
|
state.jsx.entering.count = 0;
|
|
@@ -61275,7 +62374,8 @@ function createJsxState() {
|
|
|
61275
62374
|
written: 0,
|
|
61276
62375
|
debugTaps: [],
|
|
61277
62376
|
debugWarned: false,
|
|
61278
|
-
overlays: emptySet()
|
|
62377
|
+
overlays: emptySet(),
|
|
62378
|
+
asideRoots: 0
|
|
61279
62379
|
};
|
|
61280
62380
|
}
|
|
61281
62381
|
//#endregion
|
|
@@ -62860,6 +63960,7 @@ function cubicBezier(x1, y1, x2, y2) {
|
|
|
62860
63960
|
* | assets | preloadDepth | 2 |
|
|
62861
63961
|
* | assets | baseUrl | undefined |
|
|
62862
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 |
|
|
62863
63964
|
* | anim | maxTracks | 2000 |
|
|
62864
63965
|
* | anim | reducedMotion | false |
|
|
62865
63966
|
* | i18n | locale | "en" |
|