@moku-labs/game 0.15.3 → 0.15.4
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-31d2BEwc.d.mts → commands-CvWBaoEi.d.mts} +5 -5
- package/dist/control.d.mts +4 -4
- package/dist/{define-DYJwRXB_.d.mts → define-DSC-Q0yU.d.mts} +2 -2
- package/dist/{headless-D0U5AXiK.d.mts → headless-oykCMFBV.d.mts} +1 -1
- package/dist/{index-ByvWj9OX.d.mts → index-Cv5r0M9Y.d.mts} +5 -7
- 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-biKbodaT.d.mts → runtime-BVVlgcMv.d.mts} +1 -1
- package/dist/{src-xChMXzwH.mjs → src-tkKl-oCn.mjs} +709 -807
- package/dist/testing.d.mts +4 -4
- package/dist/testing.mjs +1 -1
- package/dist/{types-yLdzHyTl.d.mts → types-B6YgPkY8.d.mts} +2 -10
- package/dist/{types-CsMLBPf-.d.mts → types-CiTr21gu.d.mts} +2 -2
- package/dist/{types-B0bmoyRT.d.mts → types-DY_wOvUF.d.mts} +2 -2
- package/dist/{types-BApuIqwq.d.mts → types-VNoG7shY.d.mts} +4 -4
- package/dist/{types-DlC3Ytbj.d.mts → types-vjWgmPqS.d.mts} +106 -128
- package/dist/{visual-G6F0Mi9H.d.mts → visual-B8ve4Qll.d.mts} +6 -6
- package/dist/visual.d.mts +1 -1
- package/llms.txt +2 -3
- package/package.json +1 -1
|
@@ -3753,7 +3753,7 @@ function featureProblems(flows, features) {
|
|
|
3753
3753
|
* sceneIdOf("board"); // undefined: a scene is an object with an id
|
|
3754
3754
|
* ```
|
|
3755
3755
|
*/
|
|
3756
|
-
function sceneIdOf(value) {
|
|
3756
|
+
function sceneIdOf$1(value) {
|
|
3757
3757
|
if (typeof value !== "object" || value === null || !("id" in value)) return void 0;
|
|
3758
3758
|
return typeof value.id === "string" ? value.id : void 0;
|
|
3759
3759
|
}
|
|
@@ -3772,7 +3772,7 @@ function declaredScenes(features) {
|
|
|
3772
3772
|
for (const list of lists) {
|
|
3773
3773
|
if (!Array.isArray(list)) continue;
|
|
3774
3774
|
for (const scene of list) {
|
|
3775
|
-
const id = sceneIdOf(scene);
|
|
3775
|
+
const id = sceneIdOf$1(scene);
|
|
3776
3776
|
if (id !== void 0) ids.add(id);
|
|
3777
3777
|
}
|
|
3778
3778
|
}
|
|
@@ -8374,6 +8374,32 @@ const NO_ROWS = {
|
|
|
8374
8374
|
next: () => DONE
|
|
8375
8375
|
};
|
|
8376
8376
|
/**
|
|
8377
|
+
* Tells whether a system's query may match, before its run: every term has a store and none of them
|
|
8378
|
+
* is empty, which is the smallest store among the terms not being empty. A store met for the first
|
|
8379
|
+
* time is kept in the entry, so later checks only read sizes. An empty query may always match. An
|
|
8380
|
+
* entity that carries one term and not the others still passes: the walk finds nothing, as before.
|
|
8381
|
+
*
|
|
8382
|
+
* @param stores - Every component store of the world, by name.
|
|
8383
|
+
* @param entry - The registered system.
|
|
8384
|
+
* @returns False when a term has no entity: the system is skipped.
|
|
8385
|
+
*/
|
|
8386
|
+
function mayMatch(stores, entry) {
|
|
8387
|
+
const names = entry.plan.names;
|
|
8388
|
+
const kept = entry.stores;
|
|
8389
|
+
const count = names.length;
|
|
8390
|
+
for (let index = 0; index < count; index += 1) {
|
|
8391
|
+
let store = kept[index];
|
|
8392
|
+
if (store === void 0) {
|
|
8393
|
+
const name = names[index];
|
|
8394
|
+
store = name === void 0 ? void 0 : stores.get(name);
|
|
8395
|
+
if (store === void 0) return false;
|
|
8396
|
+
kept[index] = store;
|
|
8397
|
+
}
|
|
8398
|
+
if (store.size === 0) return false;
|
|
8399
|
+
}
|
|
8400
|
+
return true;
|
|
8401
|
+
}
|
|
8402
|
+
/**
|
|
8377
8403
|
* The rows the runner hands a system's `run`: its walk, reset; none for an empty query. A system
|
|
8378
8404
|
* entry made without a walk gets one here, once.
|
|
8379
8405
|
*
|
|
@@ -8467,6 +8493,113 @@ function rememberPlan(ctx, key, terms) {
|
|
|
8467
8493
|
return plan;
|
|
8468
8494
|
}
|
|
8469
8495
|
//#endregion
|
|
8496
|
+
//#region src/plugins/world/ecs/scenes.ts
|
|
8497
|
+
/**
|
|
8498
|
+
* Creates the set of declared scene ids. Its own function: a collection is built outside the
|
|
8499
|
+
* exported declarations.
|
|
8500
|
+
*
|
|
8501
|
+
* @returns An empty set.
|
|
8502
|
+
*/
|
|
8503
|
+
function emptyIds() {
|
|
8504
|
+
return /* @__PURE__ */ new Set();
|
|
8505
|
+
}
|
|
8506
|
+
/**
|
|
8507
|
+
* Reads the id of one entry of a feature's `scenes` list, without trusting the entry.
|
|
8508
|
+
*
|
|
8509
|
+
* @param entry - What the feature put in the list.
|
|
8510
|
+
* @returns The id, or `undefined` when the entry carries none.
|
|
8511
|
+
* @example
|
|
8512
|
+
* ```ts
|
|
8513
|
+
* sceneIdOf({ id: "board", layers: {} }); // "board"
|
|
8514
|
+
* ```
|
|
8515
|
+
*/
|
|
8516
|
+
function sceneIdOf(entry) {
|
|
8517
|
+
if (typeof entry !== "object" || entry === null) return void 0;
|
|
8518
|
+
const id = entry.id;
|
|
8519
|
+
return typeof id === "string" ? id : void 0;
|
|
8520
|
+
}
|
|
8521
|
+
/**
|
|
8522
|
+
* Records the scene ids every feature declares, so the systems registered from now on have their
|
|
8523
|
+
* `scenes` checked. Called once at start, before the features' systems register.
|
|
8524
|
+
*
|
|
8525
|
+
* @param scene - The scene state.
|
|
8526
|
+
* @param features - The registered features.
|
|
8527
|
+
*/
|
|
8528
|
+
function declareScenes(scene, features) {
|
|
8529
|
+
const declared = emptyIds();
|
|
8530
|
+
for (const feature of features) {
|
|
8531
|
+
const list = feature.description.scenes;
|
|
8532
|
+
if (!Array.isArray(list)) continue;
|
|
8533
|
+
for (const entry of list) {
|
|
8534
|
+
const id = sceneIdOf(entry);
|
|
8535
|
+
if (id !== void 0) declared.add(id);
|
|
8536
|
+
}
|
|
8537
|
+
}
|
|
8538
|
+
scene.declared = declared;
|
|
8539
|
+
}
|
|
8540
|
+
/**
|
|
8541
|
+
* Throws for a name in a system's `scenes` that no feature declares as a scene: such a system
|
|
8542
|
+
* would never run. Nothing is checked before start, when the declared scenes are not known yet.
|
|
8543
|
+
* The caller guards it for dev builds.
|
|
8544
|
+
*
|
|
8545
|
+
* @param scene - The scene state.
|
|
8546
|
+
* @param definition - The system being registered.
|
|
8547
|
+
* @throws {Error} When a name in `scenes` is not a declared scene.
|
|
8548
|
+
*/
|
|
8549
|
+
function checkScenes(scene, definition) {
|
|
8550
|
+
const declared = scene.declared;
|
|
8551
|
+
if (declared === void 0 || definition.scenes === void 0) return;
|
|
8552
|
+
for (const name of definition.scenes) {
|
|
8553
|
+
if (declared.has(name)) continue;
|
|
8554
|
+
const known = [...declared].join(", ") || "none";
|
|
8555
|
+
throw new Error(`[game] System "${definition.name}" runs on the scene "${name}", which no feature declares.\n Name a scene a feature lists in its scenes (declared: ${known}).`);
|
|
8556
|
+
}
|
|
8557
|
+
}
|
|
8558
|
+
/**
|
|
8559
|
+
* Mounts a scene for the systems: the scene mounted before starts leaving. The same scene again
|
|
8560
|
+
* changes nothing.
|
|
8561
|
+
*
|
|
8562
|
+
* @param scene - The scene state.
|
|
8563
|
+
* @param id - The id of the scene now mounted.
|
|
8564
|
+
*/
|
|
8565
|
+
function mountScene(scene, id) {
|
|
8566
|
+
const before = scene.mounted;
|
|
8567
|
+
if (before === id) return;
|
|
8568
|
+
if (before !== void 0 && !scene.leaving.includes(before)) scene.leaving.push(before);
|
|
8569
|
+
scene.mounted = id;
|
|
8570
|
+
}
|
|
8571
|
+
/**
|
|
8572
|
+
* Ends the leave of every leaving scene once nothing carries `Exiting`: the elements of the JSX
|
|
8573
|
+
* views of the scenes before played their exit. Called at the start of a phase while a scene leaves.
|
|
8574
|
+
*
|
|
8575
|
+
* @param state - The ecs branch of the plugin state.
|
|
8576
|
+
*/
|
|
8577
|
+
function endLeaving(state) {
|
|
8578
|
+
const exiting = state.stores.get(Exiting.componentName);
|
|
8579
|
+
if (exiting !== void 0 && exiting.size > 0) return;
|
|
8580
|
+
const leaving = state.scene.leaving;
|
|
8581
|
+
while (leaving.length > 0) leaving.pop();
|
|
8582
|
+
}
|
|
8583
|
+
/**
|
|
8584
|
+
* Tells whether one of a system's scenes is mounted or leaving.
|
|
8585
|
+
*
|
|
8586
|
+
* @param scene - The scene state.
|
|
8587
|
+
* @param names - The system's `scenes`.
|
|
8588
|
+
* @returns True when the system may run.
|
|
8589
|
+
*/
|
|
8590
|
+
function onScene(scene, names) {
|
|
8591
|
+
const { mounted, leaving } = scene;
|
|
8592
|
+
const count = names.length;
|
|
8593
|
+
const left = leaving.length;
|
|
8594
|
+
for (let index = 0; index < count; index += 1) {
|
|
8595
|
+
const name = names[index];
|
|
8596
|
+
if (name === void 0) continue;
|
|
8597
|
+
if (name === mounted) return true;
|
|
8598
|
+
for (let at = 0; at < left; at += 1) if (leaving[at] === name) return true;
|
|
8599
|
+
}
|
|
8600
|
+
return false;
|
|
8601
|
+
}
|
|
8602
|
+
//#endregion
|
|
8470
8603
|
//#region src/plugins/world/ecs/schema.ts
|
|
8471
8604
|
const notJson = null;
|
|
8472
8605
|
/**
|
|
@@ -8546,294 +8679,6 @@ function schemaOf(types) {
|
|
|
8546
8679
|
return [...types].map((type) => schemaOfType(type)).toSorted((left, right) => left.name.localeCompare(right.name));
|
|
8547
8680
|
}
|
|
8548
8681
|
//#endregion
|
|
8549
|
-
//#region src/plugins/world/ecs/scope.ts
|
|
8550
|
-
/** The keys of a description whose entries can make a feature active. */
|
|
8551
|
-
const SOURCES = [
|
|
8552
|
-
"nodes",
|
|
8553
|
-
"flows",
|
|
8554
|
-
"scenes",
|
|
8555
|
-
"projections",
|
|
8556
|
-
"ui"
|
|
8557
|
-
];
|
|
8558
|
-
/** The release of a hold that holds nothing. */
|
|
8559
|
-
const RELEASE_NOTHING = () => {};
|
|
8560
|
-
/**
|
|
8561
|
-
* Creates an empty table of owners. Its own function: a collection is built outside the exported
|
|
8562
|
-
* declarations.
|
|
8563
|
-
*
|
|
8564
|
-
* @returns An empty map.
|
|
8565
|
-
*/
|
|
8566
|
-
function emptyOwners$2() {
|
|
8567
|
-
return /* @__PURE__ */ new Map();
|
|
8568
|
-
}
|
|
8569
|
-
/**
|
|
8570
|
-
* Sizes the scope to the features of the game, in registration order: the only allocation of the
|
|
8571
|
-
* scope, once per start. Whatever an earlier start left is dropped first.
|
|
8572
|
-
*
|
|
8573
|
-
* @param ctx - Domain context of the world plugin.
|
|
8574
|
-
*/
|
|
8575
|
-
function openScope(ctx) {
|
|
8576
|
-
const scope = ctx.state.ecs.scope;
|
|
8577
|
-
const features = ctx.deps.flow.features.all();
|
|
8578
|
-
const count = features.length;
|
|
8579
|
-
resetScope(scope);
|
|
8580
|
-
for (const [at, feature] of features.entries()) {
|
|
8581
|
-
scope.names.push(feature.name);
|
|
8582
|
-
scope.index.set(feature.name, at);
|
|
8583
|
-
}
|
|
8584
|
-
scope.onStack = new Uint8Array(count);
|
|
8585
|
-
scope.active = new Uint8Array(count);
|
|
8586
|
-
scope.holds = new Int32Array(count);
|
|
8587
|
-
scope.views = new Int32Array(count);
|
|
8588
|
-
scope.dirty = true;
|
|
8589
|
-
}
|
|
8590
|
-
/**
|
|
8591
|
-
* Empties the scope in place, as a world that never started has it, and moves its generation on:
|
|
8592
|
-
* a release kept past this changes nothing, also once the world started again.
|
|
8593
|
-
*
|
|
8594
|
-
* @param scope - The scope.
|
|
8595
|
-
*/
|
|
8596
|
-
function resetScope(scope) {
|
|
8597
|
-
scope.generation += 1;
|
|
8598
|
-
scope.names.length = 0;
|
|
8599
|
-
scope.index.clear();
|
|
8600
|
-
scope.projections.clear();
|
|
8601
|
-
scope.owners = void 0;
|
|
8602
|
-
scope.ownersFailed = false;
|
|
8603
|
-
scope.flow = void 0;
|
|
8604
|
-
scope.onStack = /* @__PURE__ */ new Uint8Array(0);
|
|
8605
|
-
scope.active = /* @__PURE__ */ new Uint8Array(0);
|
|
8606
|
-
scope.holds = /* @__PURE__ */ new Int32Array(0);
|
|
8607
|
-
scope.views = /* @__PURE__ */ new Int32Array(0);
|
|
8608
|
-
scope.dirty = false;
|
|
8609
|
-
scope.scoped = false;
|
|
8610
|
-
}
|
|
8611
|
-
/**
|
|
8612
|
-
* The scope the systems of one feature register with: its index for `systemsRun: "active"`, -1
|
|
8613
|
-
* otherwise. A value outside the type (a hand-written `features.register`) is reported and its
|
|
8614
|
-
* systems always run.
|
|
8615
|
-
*
|
|
8616
|
-
* @param ctx - Domain context of the world plugin.
|
|
8617
|
-
* @param at - The feature's index.
|
|
8618
|
-
* @param feature - The feature and what it registered.
|
|
8619
|
-
* @param feature.name - Feature name, for the log entry.
|
|
8620
|
-
* @param feature.description - What the feature brought.
|
|
8621
|
-
* @returns The scope of its systems.
|
|
8622
|
-
*/
|
|
8623
|
-
function scopeOf(ctx, at, feature) {
|
|
8624
|
-
const run = feature.description.systemsRun;
|
|
8625
|
-
if (run === "active") return at;
|
|
8626
|
-
if (run !== void 0 && run !== "always") ctx.log.warn("world:bad-feature-entry", {
|
|
8627
|
-
feature: feature.name,
|
|
8628
|
-
key: "systemsRun"
|
|
8629
|
-
});
|
|
8630
|
-
return -1;
|
|
8631
|
-
}
|
|
8632
|
-
/**
|
|
8633
|
-
* Warns about a feature whose systems wait for it to be active while nothing can make it active:
|
|
8634
|
-
* it has systems and `systemsRun: "active"`, and no node, flow, scene, projection or ui component.
|
|
8635
|
-
* The caller guards it for dev builds.
|
|
8636
|
-
*
|
|
8637
|
-
* @param ctx - Domain context of the world plugin.
|
|
8638
|
-
* @param feature - The feature and what it registered.
|
|
8639
|
-
* @param feature.name - Feature name, for the warning.
|
|
8640
|
-
* @param feature.description - What the feature brought.
|
|
8641
|
-
*/
|
|
8642
|
-
function reportNeverActive(ctx, feature) {
|
|
8643
|
-
const { name, description } = feature;
|
|
8644
|
-
const systems = description.systems;
|
|
8645
|
-
if (description.systemsRun !== "active") return;
|
|
8646
|
-
if (!Array.isArray(systems) || systems.length === 0) return;
|
|
8647
|
-
for (const key of SOURCES) {
|
|
8648
|
-
const list = description[key];
|
|
8649
|
-
if (Array.isArray(list) && list.length > 0) return;
|
|
8650
|
-
}
|
|
8651
|
-
ctx.log.warn("world:feature-never-active", {
|
|
8652
|
-
feature: name,
|
|
8653
|
-
message: `Feature "${name}" runs its systems only while it is active, but it has no node, flow, scene, projection or ui component, so nothing makes it active and its systems never run. Remove systemsRun: "active" from it.`
|
|
8654
|
-
});
|
|
8655
|
-
}
|
|
8656
|
-
/**
|
|
8657
|
-
* Builds the owner table of rule N from one `describe()`: flow id to node name to the index of the
|
|
8658
|
-
* feature that owns the node. A node without an owner, or owned by a name that is not a feature,
|
|
8659
|
-
* is left out.
|
|
8660
|
-
*
|
|
8661
|
-
* @param graph - The described graph.
|
|
8662
|
-
* @param index - Feature name to index.
|
|
8663
|
-
* @returns The owner table.
|
|
8664
|
-
*/
|
|
8665
|
-
function ownersOf$1(graph, index) {
|
|
8666
|
-
const owners = emptyOwners$2();
|
|
8667
|
-
for (const [id, flow] of Object.entries(graph.flows)) {
|
|
8668
|
-
const nodes = emptyOwners$2();
|
|
8669
|
-
for (const [node, info] of Object.entries(flow.nodes)) {
|
|
8670
|
-
const at = info.owner === void 0 ? void 0 : index.get(info.owner);
|
|
8671
|
-
if (at !== void 0) nodes.set(node, at);
|
|
8672
|
-
}
|
|
8673
|
-
if (nodes.size > 0) owners.set(id, nodes);
|
|
8674
|
-
}
|
|
8675
|
-
return owners;
|
|
8676
|
-
}
|
|
8677
|
-
/**
|
|
8678
|
-
* Builds the owner table once, at the first stack that is not empty: the graph is sealed by
|
|
8679
|
-
* `flow.run()`. A `describe()` that throws is logged once and makes every feature count as on the
|
|
8680
|
-
* stack (fail-safe).
|
|
8681
|
-
*
|
|
8682
|
-
* @param ctx - Domain context of the world plugin.
|
|
8683
|
-
* @param scope - The scope.
|
|
8684
|
-
*/
|
|
8685
|
-
function buildOwners(ctx, scope) {
|
|
8686
|
-
try {
|
|
8687
|
-
scope.owners = ownersOf$1(ctx.deps.flow.describe(), scope.index);
|
|
8688
|
-
} catch (error) {
|
|
8689
|
-
scope.ownersFailed = true;
|
|
8690
|
-
ctx.log.error("world:scope-owners-failed", {}, asError$3(error));
|
|
8691
|
-
}
|
|
8692
|
-
}
|
|
8693
|
-
/**
|
|
8694
|
-
* Sets the stack flags of rule N from the frames of one flow state, every frame counted, the
|
|
8695
|
-
* outer frames of sub-flows included.
|
|
8696
|
-
*
|
|
8697
|
-
* @param ctx - Domain context of the world plugin.
|
|
8698
|
-
* @param scope - The scope.
|
|
8699
|
-
* @param stack - The frames of the flow state.
|
|
8700
|
-
*/
|
|
8701
|
-
function readStack$1(ctx, scope, stack) {
|
|
8702
|
-
const onStack = scope.onStack;
|
|
8703
|
-
const depth = stack.length;
|
|
8704
|
-
onStack.fill(0);
|
|
8705
|
-
if (depth === 0) return;
|
|
8706
|
-
if (scope.owners === void 0 && !scope.ownersFailed) buildOwners(ctx, scope);
|
|
8707
|
-
const owners = scope.owners;
|
|
8708
|
-
if (owners === void 0) {
|
|
8709
|
-
onStack.fill(1);
|
|
8710
|
-
return;
|
|
8711
|
-
}
|
|
8712
|
-
for (let index = 0; index < depth; index += 1) {
|
|
8713
|
-
const frame = stack[index];
|
|
8714
|
-
const at = frame === void 0 ? void 0 : owners.get(frame.flow)?.get(frame.node);
|
|
8715
|
-
if (at !== void 0) onStack[at] = 1;
|
|
8716
|
-
}
|
|
8717
|
-
}
|
|
8718
|
-
/**
|
|
8719
|
-
* Reads the flow stack again when `flow.state()` answers a new object. The state is memoised, so a
|
|
8720
|
-
* frame in which the graph did not move costs one call and one comparison.
|
|
8721
|
-
*
|
|
8722
|
-
* @param ctx - Domain context of the world plugin.
|
|
8723
|
-
* @param scope - The scope.
|
|
8724
|
-
*/
|
|
8725
|
-
function readSources(ctx, scope) {
|
|
8726
|
-
const flowState = ctx.deps.flow.state();
|
|
8727
|
-
if (flowState === scope.flow) return;
|
|
8728
|
-
scope.flow = flowState;
|
|
8729
|
-
readStack$1(ctx, scope, flowState.stack);
|
|
8730
|
-
scope.dirty = true;
|
|
8731
|
-
}
|
|
8732
|
-
/**
|
|
8733
|
-
* Evaluates the active set at the start of a phase that runs systems: a feature is active while it
|
|
8734
|
-
* is on the stack, held, or has a view. Nothing moved: one call, one comparison and one flag test.
|
|
8735
|
-
*
|
|
8736
|
-
* @param ctx - Domain context of the world plugin.
|
|
8737
|
-
*/
|
|
8738
|
-
function refreshScope(ctx) {
|
|
8739
|
-
const scope = ctx.state.ecs.scope;
|
|
8740
|
-
readSources(ctx, scope);
|
|
8741
|
-
if (!scope.dirty) return;
|
|
8742
|
-
const { onStack, holds, views, active } = scope;
|
|
8743
|
-
const count = scope.names.length;
|
|
8744
|
-
for (let at = 0; at < count; at += 1) active[at] = onStack[at] === 1 || (holds[at] ?? 0) > 0 || (views[at] ?? 0) > 0 ? 1 : 0;
|
|
8745
|
-
scope.dirty = false;
|
|
8746
|
-
}
|
|
8747
|
-
/**
|
|
8748
|
-
* The release of one hold: it takes the hold back once, never below 0, and only in the run it was
|
|
8749
|
-
* taken in. A release kept across a stop and a start finds a newer generation and changes nothing
|
|
8750
|
-
* (spec 28 W7.1).
|
|
8751
|
-
*
|
|
8752
|
-
* @param scope - The scope.
|
|
8753
|
-
* @param at - The held feature's index.
|
|
8754
|
-
* @returns The idempotent release.
|
|
8755
|
-
*/
|
|
8756
|
-
function releaseOf(scope, at) {
|
|
8757
|
-
const hold = {
|
|
8758
|
-
released: false,
|
|
8759
|
-
generation: scope.generation
|
|
8760
|
-
};
|
|
8761
|
-
return () => {
|
|
8762
|
-
if (hold.released || hold.generation !== scope.generation) return;
|
|
8763
|
-
hold.released = true;
|
|
8764
|
-
scope.holds[at] = Math.max(0, (scope.holds[at] ?? 0) - 1);
|
|
8765
|
-
scope.dirty = true;
|
|
8766
|
-
};
|
|
8767
|
-
}
|
|
8768
|
-
/**
|
|
8769
|
-
* Takes one hold on a feature. A name that is not a feature holds nothing; a dev build warns.
|
|
8770
|
-
*
|
|
8771
|
-
* @param ctx - Domain context of the world plugin.
|
|
8772
|
-
* @param feature - The feature to keep active.
|
|
8773
|
-
* @returns The release.
|
|
8774
|
-
*/
|
|
8775
|
-
function holdFeature(ctx, feature) {
|
|
8776
|
-
const scope = ctx.state.ecs.scope;
|
|
8777
|
-
const at = scope.index.get(feature);
|
|
8778
|
-
if (at === void 0) {
|
|
8779
|
-
if (typeof __MOKU_GAME_DEV__ !== "undefined" && __MOKU_GAME_DEV__) ctx.log.warn("world:hold-unknown-feature", { feature });
|
|
8780
|
-
return RELEASE_NOTHING;
|
|
8781
|
-
}
|
|
8782
|
-
scope.holds[at] = (scope.holds[at] ?? 0) + 1;
|
|
8783
|
-
scope.dirty = true;
|
|
8784
|
-
return releaseOf(scope, at);
|
|
8785
|
-
}
|
|
8786
|
-
/**
|
|
8787
|
-
* Whether a feature is active now, from the sources as they are at this moment. It leaves the
|
|
8788
|
-
* active set of the running phase alone: a source that moves inside a phase counts for the
|
|
8789
|
-
* systems from the next phase.
|
|
8790
|
-
*
|
|
8791
|
-
* @param ctx - Domain context of the world plugin.
|
|
8792
|
-
* @param feature - The feature name.
|
|
8793
|
-
* @returns True while it is on the stack, held or has a view; false for a name that is not a feature.
|
|
8794
|
-
*/
|
|
8795
|
-
function isFeatureActive(ctx, feature) {
|
|
8796
|
-
const scope = ctx.state.ecs.scope;
|
|
8797
|
-
const at = scope.index.get(feature);
|
|
8798
|
-
if (at === void 0) return false;
|
|
8799
|
-
readSources(ctx, scope);
|
|
8800
|
-
return scope.onStack[at] === 1 || (scope.holds[at] ?? 0) > 0 || (scope.views[at] ?? 0) > 0;
|
|
8801
|
-
}
|
|
8802
|
-
/**
|
|
8803
|
-
* Counts a new view of a projection for the feature that registered it. A projection no feature
|
|
8804
|
-
* registered counts for nobody.
|
|
8805
|
-
*
|
|
8806
|
-
* @param scope - The scope.
|
|
8807
|
-
* @param projection - The projection name.
|
|
8808
|
-
*/
|
|
8809
|
-
function viewCame(scope, projection) {
|
|
8810
|
-
const at = scope.projections.get(projection);
|
|
8811
|
-
if (at === void 0) return;
|
|
8812
|
-
scope.views[at] = (scope.views[at] ?? 0) + 1;
|
|
8813
|
-
scope.dirty = true;
|
|
8814
|
-
}
|
|
8815
|
-
/**
|
|
8816
|
-
* Takes back the count of a despawned view, never below 0.
|
|
8817
|
-
*
|
|
8818
|
-
* @param scope - The scope.
|
|
8819
|
-
* @param projection - The projection name.
|
|
8820
|
-
*/
|
|
8821
|
-
function viewLeft(scope, projection) {
|
|
8822
|
-
const at = scope.projections.get(projection);
|
|
8823
|
-
if (at === void 0) return;
|
|
8824
|
-
scope.views[at] = Math.max(0, (scope.views[at] ?? 0) - 1);
|
|
8825
|
-
scope.dirty = true;
|
|
8826
|
-
}
|
|
8827
|
-
/**
|
|
8828
|
-
* Drops every view count: the projections were cleared.
|
|
8829
|
-
*
|
|
8830
|
-
* @param scope - The scope.
|
|
8831
|
-
*/
|
|
8832
|
-
function viewsCleared(scope) {
|
|
8833
|
-
scope.views.fill(0);
|
|
8834
|
-
scope.dirty = true;
|
|
8835
|
-
}
|
|
8836
|
-
//#endregion
|
|
8837
8682
|
//#region src/plugins/world/ecs/systems.ts
|
|
8838
8683
|
/** The one phase that runs in every mode, because `renderer.sync` lives in it. */
|
|
8839
8684
|
const ALWAYS = "sync";
|
|
@@ -8923,18 +8768,29 @@ function frameSnapshot(ctx, time) {
|
|
|
8923
8768
|
return snapshot;
|
|
8924
8769
|
}
|
|
8925
8770
|
/**
|
|
8926
|
-
*
|
|
8927
|
-
*
|
|
8928
|
-
*
|
|
8929
|
-
*
|
|
8771
|
+
* An empty slot for each term of a plan: the stores its check keeps, filled with `push`, so the
|
|
8772
|
+
* list is a packed array of its exact length from the start.
|
|
8773
|
+
*
|
|
8774
|
+
* @param count - The number of terms.
|
|
8775
|
+
* @returns The slots, all `undefined`.
|
|
8776
|
+
*/
|
|
8777
|
+
function storeSlots(count) {
|
|
8778
|
+
const slots = [];
|
|
8779
|
+
for (let index = 0; index < count; index += 1) slots.push(void 0);
|
|
8780
|
+
return slots;
|
|
8781
|
+
}
|
|
8782
|
+
/**
|
|
8783
|
+
* Registers a system at the end of its phase list and returns the remover. Its query and its
|
|
8784
|
+
* `scenes` are read here, once: a later change to the definition is not seen. A dev build checks
|
|
8785
|
+
* the `scenes` against the scenes the features declare, once the world started.
|
|
8930
8786
|
*
|
|
8931
8787
|
* @param ctx - Domain context of the world plugin.
|
|
8932
8788
|
* @param definition - The system.
|
|
8933
|
-
* @param scope - The index of its feature in the scope, or -1 for a system that always runs.
|
|
8934
8789
|
* @returns The remover; calling it twice is a no-op.
|
|
8935
|
-
* @throws {Error} When a system of that name is already registered
|
|
8790
|
+
* @throws {Error} When a system of that name is already registered, and in a dev build for a
|
|
8791
|
+
* name in `scenes` that no feature declares.
|
|
8936
8792
|
*/
|
|
8937
|
-
function registerSystem(ctx, definition
|
|
8793
|
+
function registerSystem(ctx, definition) {
|
|
8938
8794
|
const state = ctx.state.ecs;
|
|
8939
8795
|
for (const phase of [
|
|
8940
8796
|
"input",
|
|
@@ -8942,18 +8798,19 @@ function registerSystem(ctx, definition, scope = -1) {
|
|
|
8942
8798
|
"layout",
|
|
8943
8799
|
"sync"
|
|
8944
8800
|
]) if (state.systems[phase].some((entry) => entry.definition.name === definition.name)) throw new Error(`[game] System "${definition.name}" is registered twice.\n Give the second system another name, or remove the first one.`);
|
|
8801
|
+
if (typeof __MOKU_GAME_DEV__ !== "undefined" && __MOKU_GAME_DEV__) checkScenes(state.scene, definition);
|
|
8945
8802
|
const plan = planOf(ctx, definition.query);
|
|
8946
8803
|
const entry = {
|
|
8947
8804
|
definition,
|
|
8948
8805
|
frame: ctx.deps.time.snapshot().frame,
|
|
8949
8806
|
plan,
|
|
8950
|
-
|
|
8807
|
+
stores: storeSlots(plan.names.length),
|
|
8808
|
+
scenes: definition.scenes === void 0 ? void 0 : [...definition.scenes],
|
|
8951
8809
|
skipped: false,
|
|
8952
8810
|
walk: plan.names.length === 0 ? void 0 : new SystemWalk(ctx, plan)
|
|
8953
8811
|
};
|
|
8954
8812
|
const list = state.systems[definition.phase];
|
|
8955
8813
|
list.push(entry);
|
|
8956
|
-
if (scope !== -1) state.scope.scoped = true;
|
|
8957
8814
|
if (state.timing !== void 0) giveRow(state, state.timing, definition.name);
|
|
8958
8815
|
return () => {
|
|
8959
8816
|
const at = list.indexOf(entry);
|
|
@@ -9002,19 +8859,19 @@ function phaseContextOf(ctx, world, time) {
|
|
|
9002
8859
|
return known;
|
|
9003
8860
|
}
|
|
9004
8861
|
/**
|
|
9005
|
-
* Tells whether a system
|
|
9006
|
-
*
|
|
9007
|
-
* skipped. No walk, no mark and no clock read happen for a
|
|
9008
|
-
*
|
|
9009
|
-
* reads false without a write of its own.
|
|
8862
|
+
* Tells whether a system runs in this phase: one of its `scenes` is mounted or leaving, and no
|
|
8863
|
+
* term of its query has an empty store. A skipped system remembers it; one that runs is told in
|
|
8864
|
+
* `resumed` whether its last chance was skipped. No walk, no mark and no clock read happen for a
|
|
8865
|
+
* skipped system: the check comes before all three.
|
|
9010
8866
|
*
|
|
9011
|
-
* @param
|
|
9012
|
-
* @param entry - The system
|
|
8867
|
+
* @param state - The ecs branch of the plugin state.
|
|
8868
|
+
* @param entry - The system.
|
|
9013
8869
|
* @param context - What its `run` receives; `resumed` is written here.
|
|
9014
8870
|
* @returns True when the system runs.
|
|
9015
8871
|
*/
|
|
9016
|
-
function wakes(
|
|
9017
|
-
|
|
8872
|
+
function wakes(state, entry, context) {
|
|
8873
|
+
const scenes = entry.scenes;
|
|
8874
|
+
if (scenes !== void 0 && !onScene(state.scene, scenes) || !mayMatch(state.stores, entry)) {
|
|
9018
8875
|
entry.skipped = true;
|
|
9019
8876
|
return false;
|
|
9020
8877
|
}
|
|
@@ -9023,21 +8880,6 @@ function wakes(active, entry, context) {
|
|
|
9023
8880
|
return true;
|
|
9024
8881
|
}
|
|
9025
8882
|
/**
|
|
9026
|
-
* Runs one system of a scoped feature when its feature is active, and leaves `resumed` false for
|
|
9027
|
-
* the systems after it.
|
|
9028
|
-
*
|
|
9029
|
-
* @param ctx - Domain context of the world plugin.
|
|
9030
|
-
* @param active - The active flags of the features.
|
|
9031
|
-
* @param entry - The system.
|
|
9032
|
-
* @param context - What its `run` receives.
|
|
9033
|
-
* @param phase - The phase, for the report of a throw.
|
|
9034
|
-
*/
|
|
9035
|
-
function runScoped(ctx, active, entry, context, phase) {
|
|
9036
|
-
if (!wakes(active, entry, context)) return;
|
|
9037
|
-
guardedRun(ctx, entry, context, phase);
|
|
9038
|
-
context.resumed = false;
|
|
9039
|
-
}
|
|
9040
|
-
/**
|
|
9041
8883
|
* Runs one system, reporting a throw: the try lives here, so the loop of a phase holds none.
|
|
9042
8884
|
*
|
|
9043
8885
|
* @param ctx - Domain context of the world plugin.
|
|
@@ -9057,9 +8899,9 @@ function guardedRun(ctx, entry, context, phase) {
|
|
|
9057
8899
|
}
|
|
9058
8900
|
/**
|
|
9059
8901
|
* Runs the systems of one phase and flushes the commands they queued. A throwing system is
|
|
9060
|
-
* reported and skipped for this frame; the frame goes on.
|
|
9061
|
-
*
|
|
9062
|
-
*
|
|
8902
|
+
* reported and skipped for this frame; the frame goes on. A system whose query has no entity, or
|
|
8903
|
+
* none of whose `scenes` is mounted or leaving, is skipped in its slot. While a scene leaves, the
|
|
8904
|
+
* phase first ends the leave when nothing carries `Exiting` any more.
|
|
9063
8905
|
*
|
|
9064
8906
|
* @param ctx - Domain context of the world plugin.
|
|
9065
8907
|
* @param world - The ecs API a system's `run` receives.
|
|
@@ -9069,16 +8911,14 @@ function guardedRun(ctx, entry, context, phase) {
|
|
|
9069
8911
|
function runPhase(ctx, world, phase, time) {
|
|
9070
8912
|
const state = ctx.state.ecs;
|
|
9071
8913
|
if (phase !== ALWAYS && effectiveMode(ctx) !== "live") return;
|
|
9072
|
-
if (state.
|
|
8914
|
+
if (state.scene.leaving.length > 0) endLeaving(state);
|
|
9073
8915
|
const context = phaseContextOf(ctx, world, time);
|
|
9074
8916
|
const entries = state.systems[phase];
|
|
9075
|
-
const active = state.scope.active;
|
|
9076
8917
|
state.running = phase;
|
|
9077
8918
|
if (state.timing === void 0) for (let index = 0; index < entries.length; index += 1) {
|
|
9078
8919
|
const entry = entries[index];
|
|
9079
8920
|
if (entry === void 0 || entry.frame >= time.frame) continue;
|
|
9080
|
-
if (entry
|
|
9081
|
-
else runScoped(ctx, active, entry, context, phase);
|
|
8921
|
+
if (wakes(state, entry, context)) guardedRun(ctx, entry, context, phase);
|
|
9082
8922
|
}
|
|
9083
8923
|
else runTimed(ctx, state.timing, entries, context, phase, time);
|
|
9084
8924
|
state.running = void 0;
|
|
@@ -9098,26 +8938,24 @@ function runPhase(ctx, world, phase, time) {
|
|
|
9098
8938
|
* @param time - The frame's `Time`.
|
|
9099
8939
|
*/
|
|
9100
8940
|
function runTimed(ctx, timing, entries, context, phase, time) {
|
|
8941
|
+
const state = ctx.state.ecs;
|
|
9101
8942
|
const count = entries.length;
|
|
9102
|
-
const active = ctx.state.ecs.scope.active;
|
|
9103
8943
|
for (let index = 0; index < count; index += 1) {
|
|
9104
8944
|
const entry = entries[index];
|
|
9105
8945
|
if (entry === void 0 || entry.frame >= time.frame) continue;
|
|
9106
|
-
if (
|
|
9107
|
-
const row = rowOf(
|
|
8946
|
+
if (!wakes(state, entry, context)) continue;
|
|
8947
|
+
const row = rowOf(state, entry.definition.name);
|
|
9108
8948
|
const started = timing.clock();
|
|
9109
8949
|
guardedRun(ctx, entry, context, phase);
|
|
9110
8950
|
timing.ms[row] = (timing.ms[row] ?? 0) + timing.clock() - started;
|
|
9111
|
-
context.resumed = false;
|
|
9112
8951
|
}
|
|
9113
8952
|
}
|
|
9114
8953
|
//#endregion
|
|
9115
8954
|
//#region src/plugins/world/ecs/api.ts
|
|
9116
8955
|
/**
|
|
9117
|
-
* Empties the world in place: entities, components, resources, systems, hooks
|
|
9118
|
-
*
|
|
9119
|
-
*
|
|
9120
|
-
* every view on it stays valid.
|
|
8956
|
+
* Empties the world in place: entities, components, resources, systems, hooks and the frame
|
|
8957
|
+
* history. The mounted scene stays: `scenes` still shows it. The state object keeps its identity,
|
|
8958
|
+
* so every view on it stays valid.
|
|
9121
8959
|
*
|
|
9122
8960
|
* @param ctx - Domain context of the world plugin.
|
|
9123
8961
|
*/
|
|
@@ -9148,7 +8986,6 @@ function clearWorldState(ctx) {
|
|
|
9148
8986
|
state.mode = "live";
|
|
9149
8987
|
state.frameSnapshot = void 0;
|
|
9150
8988
|
resetHistory(state);
|
|
9151
|
-
viewsCleared(state.scope);
|
|
9152
8989
|
}
|
|
9153
8990
|
/**
|
|
9154
8991
|
* Creates the ecs module: the public API a game and the plugins above call, plus the four methods
|
|
@@ -9161,8 +8998,6 @@ function createEcsApi(ctx) {
|
|
|
9161
8998
|
const state = ctx.state.ecs;
|
|
9162
8999
|
const api = {
|
|
9163
9000
|
system: (definition) => registerSystem(ctx, definition),
|
|
9164
|
-
hold: (feature) => holdFeature(ctx, feature),
|
|
9165
|
-
isActive: (feature) => isFeatureActive(ctx, feature),
|
|
9166
9001
|
spawn: (owner, components) => {
|
|
9167
9002
|
const entity = reserveEntity(state, owner);
|
|
9168
9003
|
queueOrApply(ctx, {
|
|
@@ -9242,6 +9077,9 @@ function createEcsApi(ctx) {
|
|
|
9242
9077
|
setMode: (mode) => {
|
|
9243
9078
|
state.mode = mode;
|
|
9244
9079
|
},
|
|
9080
|
+
setScene: (scene) => {
|
|
9081
|
+
mountScene(state.scene, scene);
|
|
9082
|
+
},
|
|
9245
9083
|
typeOf: (name) => asComponent$2(state.types.get(name)),
|
|
9246
9084
|
snapshot: () => snapshotWorld(state, effectiveMode(ctx)),
|
|
9247
9085
|
diff: (from, to) => diffHistory(state.history, from, to),
|
|
@@ -9266,15 +9104,6 @@ function createEcsApi(ctx) {
|
|
|
9266
9104
|
},
|
|
9267
9105
|
clear: () => {
|
|
9268
9106
|
clearWorldState(ctx);
|
|
9269
|
-
},
|
|
9270
|
-
viewCame: (projection) => {
|
|
9271
|
-
viewCame(state.scope, projection);
|
|
9272
|
-
},
|
|
9273
|
-
viewLeft: (projection) => {
|
|
9274
|
-
viewLeft(state.scope, projection);
|
|
9275
|
-
},
|
|
9276
|
-
viewsCleared: () => {
|
|
9277
|
-
viewsCleared(state.scope);
|
|
9278
9107
|
}
|
|
9279
9108
|
};
|
|
9280
9109
|
return api;
|
|
@@ -9388,29 +9217,24 @@ function registerEach(ctx, feature, key, accepts, register) {
|
|
|
9388
9217
|
}
|
|
9389
9218
|
/**
|
|
9390
9219
|
* Registers what every feature brought, in feature order: components first, so their schema is
|
|
9391
|
-
* known, then systems, then projections.
|
|
9392
|
-
*
|
|
9393
|
-
* for it (rule V). A dev build warns about a feature nothing can make active.
|
|
9220
|
+
* known, then systems, then projections. The scene ids of every feature are read first, so a dev
|
|
9221
|
+
* build checks the `scenes` of each system as it registers.
|
|
9394
9222
|
*
|
|
9395
9223
|
* @param ctx - Domain context of the world plugin.
|
|
9224
|
+
* @param ecs - The ecs module.
|
|
9396
9225
|
* @param projection - The projection module.
|
|
9397
9226
|
*/
|
|
9398
|
-
function registerFeatures(ctx, projection) {
|
|
9399
|
-
const
|
|
9400
|
-
|
|
9401
|
-
|
|
9227
|
+
function registerFeatures(ctx, ecs, projection) {
|
|
9228
|
+
const features = ctx.deps.flow.features.all();
|
|
9229
|
+
declareScenes(ctx.state.ecs.scene, features);
|
|
9230
|
+
for (const feature of features) {
|
|
9402
9231
|
registerEach(ctx, feature, "components", isComponentEntry, (entry) => registerType(ctx, entry));
|
|
9403
|
-
registerEach(ctx, feature, "systems", isSystemEntry, (entry) =>
|
|
9404
|
-
registerEach(ctx, feature, "projections", isProjectionEntry, (entry) =>
|
|
9405
|
-
projection.register(entry);
|
|
9406
|
-
projections.set(entry.name, at);
|
|
9407
|
-
});
|
|
9408
|
-
if (typeof __MOKU_GAME_DEV__ !== "undefined" && __MOKU_GAME_DEV__) reportNeverActive(ctx, feature);
|
|
9232
|
+
registerEach(ctx, feature, "systems", isSystemEntry, (entry) => ecs.system(entry));
|
|
9233
|
+
registerEach(ctx, feature, "projections", isProjectionEntry, (entry) => projection.register(entry));
|
|
9409
9234
|
}
|
|
9410
9235
|
}
|
|
9411
9236
|
/**
|
|
9412
|
-
* Connects the world in `onStart`: it
|
|
9413
|
-
* description, registers the five frame
|
|
9237
|
+
* Connects the world in `onStart`: it reads every feature description, registers the five frame
|
|
9414
9238
|
* callbacks of the phases and listens to the released hints. In a dev build the `signals`
|
|
9415
9239
|
* callback records the frame history before it clears the change sets; in an agent build the
|
|
9416
9240
|
* timing table starts with the page clock of `time` and the `input` callback zeroes it each
|
|
@@ -9431,8 +9255,7 @@ function connectWorld(ctx) {
|
|
|
9431
9255
|
const clock = time.agentClock();
|
|
9432
9256
|
if (clock !== void 0) startTiming(worldCtx.state.ecs, clock);
|
|
9433
9257
|
}
|
|
9434
|
-
|
|
9435
|
-
registerFeatures(worldCtx, projection);
|
|
9258
|
+
registerFeatures(worldCtx, ecs, projection);
|
|
9436
9259
|
ecs.resource(Now).now = clock.now();
|
|
9437
9260
|
worldCtx.state.ecs.offFrame.push(ecs.onOwnerLeft((owner) => projection.ownerLeft(owner)), time.onFrame("input", (frame) => {
|
|
9438
9261
|
ecs.resource(Now).now = clock.now();
|
|
@@ -9450,8 +9273,8 @@ function connectWorld(ctx) {
|
|
|
9450
9273
|
}
|
|
9451
9274
|
/**
|
|
9452
9275
|
* Empties the world in `onStop`: the frame callbacks and the hint listener are removed, then
|
|
9453
|
-
* every track, view, queued view, entity and resource is dropped, the
|
|
9454
|
-
* are reset.
|
|
9276
|
+
* every track, view, queued view, entity and resource is dropped, and the scenes and the frame
|
|
9277
|
+
* history are reset.
|
|
9455
9278
|
* No `onRemoved` fires, because `renderer` stopped earlier and destroyed its own objects.
|
|
9456
9279
|
*
|
|
9457
9280
|
* @param state - The plugin state, the only thing a teardown context carries.
|
|
@@ -9502,7 +9325,9 @@ function clearWorld(state) {
|
|
|
9502
9325
|
state.ecs.mode = "live";
|
|
9503
9326
|
state.ecs.timing = void 0;
|
|
9504
9327
|
state.ecs.timingSlots.clear();
|
|
9505
|
-
|
|
9328
|
+
state.ecs.scene.mounted = void 0;
|
|
9329
|
+
state.ecs.scene.leaving.length = 0;
|
|
9330
|
+
state.ecs.scene.declared = void 0;
|
|
9506
9331
|
resetHistory(state.ecs);
|
|
9507
9332
|
}
|
|
9508
9333
|
//#endregion
|
|
@@ -10415,7 +10240,7 @@ function despawnView(pctx, mounted, view) {
|
|
|
10415
10240
|
view.handles = [];
|
|
10416
10241
|
cancelTracks(pctx, view.entity);
|
|
10417
10242
|
const state = pctx.ctx.state.projection;
|
|
10418
|
-
|
|
10243
|
+
state.byEntity.delete(view.entity);
|
|
10419
10244
|
state.mutes.delete(view.entity);
|
|
10420
10245
|
state.rests.delete(view.entity);
|
|
10421
10246
|
state.leaving.set(view.entity, view.cause);
|
|
@@ -10791,7 +10616,6 @@ function enterEntry(pctx, spec, mounted, entry) {
|
|
|
10791
10616
|
const view = createViewRecord(entity, spec.name, entry.key, entry.item, values, entry.direct ? "mount" : "change");
|
|
10792
10617
|
mounted.live.set(entry.key, view);
|
|
10793
10618
|
pctx.ctx.state.projection.byEntity.set(entity, view);
|
|
10794
|
-
pctx.deps.views.came(spec.name);
|
|
10795
10619
|
pctx.ctx.state.projection.membershipVersion += 1;
|
|
10796
10620
|
const hook = spec.motion?.enter;
|
|
10797
10621
|
const loop = spec.motion?.loop;
|
|
@@ -11604,7 +11428,6 @@ function createProjectionApi(ctx, deps) {
|
|
|
11604
11428
|
state.mounted.clear();
|
|
11605
11429
|
state.specs.clear();
|
|
11606
11430
|
state.byEntity.clear();
|
|
11607
|
-
pctx.deps.views.cleared();
|
|
11608
11431
|
state.mutes.clear();
|
|
11609
11432
|
state.rests.clear();
|
|
11610
11433
|
state.keys.clear();
|
|
@@ -11644,12 +11467,7 @@ function createModules$2(ctx) {
|
|
|
11644
11467
|
ecs,
|
|
11645
11468
|
projection: createProjectionApi(ctx, {
|
|
11646
11469
|
ecs,
|
|
11647
|
-
components: WORLD_COMPONENTS
|
|
11648
|
-
views: {
|
|
11649
|
-
came: ecs.viewCame,
|
|
11650
|
-
left: ecs.viewLeft,
|
|
11651
|
-
cleared: ecs.viewsCleared
|
|
11652
|
-
}
|
|
11470
|
+
components: WORLD_COMPONENTS
|
|
11653
11471
|
})
|
|
11654
11472
|
};
|
|
11655
11473
|
}
|
|
@@ -11684,8 +11502,6 @@ function withKeys(diff, ecs, projection) {
|
|
|
11684
11502
|
function exposeEcs(ecs, projection) {
|
|
11685
11503
|
return {
|
|
11686
11504
|
system: ecs.system,
|
|
11687
|
-
hold: ecs.hold,
|
|
11688
|
-
isActive: ecs.isActive,
|
|
11689
11505
|
spawn: ecs.spawn,
|
|
11690
11506
|
despawn: ecs.despawn,
|
|
11691
11507
|
despawnOwnedBy: ecs.despawnOwnedBy,
|
|
@@ -11709,6 +11525,7 @@ function exposeEcs(ecs, projection) {
|
|
|
11709
11525
|
ecs.setMode(mode);
|
|
11710
11526
|
if (ecs.mode() === "fast") projection.flushAll();
|
|
11711
11527
|
},
|
|
11528
|
+
setScene: ecs.setScene,
|
|
11712
11529
|
snapshot: ecs.snapshot,
|
|
11713
11530
|
diff: (from, to) => withKeys(ecs.diff(from, to), ecs, projection),
|
|
11714
11531
|
schema: ecs.schema
|
|
@@ -11807,26 +11624,16 @@ function emptyMap$10() {
|
|
|
11807
11624
|
return /* @__PURE__ */ new Map();
|
|
11808
11625
|
}
|
|
11809
11626
|
/**
|
|
11810
|
-
* Creates the
|
|
11811
|
-
*
|
|
11627
|
+
* Creates the scene state of a world no scene was mounted in: nothing mounted, nothing leaving, the
|
|
11628
|
+
* declared scenes unknown until start.
|
|
11812
11629
|
*
|
|
11813
|
-
* @returns An empty
|
|
11630
|
+
* @returns An empty scene state.
|
|
11814
11631
|
*/
|
|
11815
|
-
function
|
|
11632
|
+
function emptyScene() {
|
|
11816
11633
|
return {
|
|
11817
|
-
|
|
11818
|
-
|
|
11819
|
-
|
|
11820
|
-
owners: void 0,
|
|
11821
|
-
ownersFailed: false,
|
|
11822
|
-
flow: void 0,
|
|
11823
|
-
onStack: /* @__PURE__ */ new Uint8Array(0),
|
|
11824
|
-
holds: /* @__PURE__ */ new Int32Array(0),
|
|
11825
|
-
views: /* @__PURE__ */ new Int32Array(0),
|
|
11826
|
-
active: /* @__PURE__ */ new Uint8Array(0),
|
|
11827
|
-
dirty: false,
|
|
11828
|
-
scoped: false,
|
|
11829
|
-
generation: 0
|
|
11634
|
+
mounted: void 0,
|
|
11635
|
+
leaving: [],
|
|
11636
|
+
declared: void 0
|
|
11830
11637
|
};
|
|
11831
11638
|
}
|
|
11832
11639
|
/**
|
|
@@ -11872,7 +11679,7 @@ function createEcsState() {
|
|
|
11872
11679
|
queryPlans: emptyMap$10(),
|
|
11873
11680
|
walkLists: [],
|
|
11874
11681
|
despawns: 0,
|
|
11875
|
-
|
|
11682
|
+
scene: emptyScene()
|
|
11876
11683
|
};
|
|
11877
11684
|
}
|
|
11878
11685
|
//#endregion
|
|
@@ -13012,336 +12819,486 @@ function watchPixelRatio(ctx) {
|
|
|
13012
12819
|
state.cleanups.push(() => query?.removeEventListener("change", follow));
|
|
13013
12820
|
}
|
|
13014
12821
|
//#endregion
|
|
13015
|
-
//#region src/plugins/renderer/host/sdf-
|
|
12822
|
+
//#region src/plugins/renderer/host/sdf-batcher.ts
|
|
12823
|
+
/** The key of the glyph record on the proxy graphics of every bitmap text the renderer's pipe lays out. */
|
|
12824
|
+
const SDF = Symbol("moku-sdf-glyphs");
|
|
12825
|
+
/** The name the renderer's batcher of distance-field glyphs registers under, and the element's `batcherName`. */
|
|
12826
|
+
const SDF_BATCHER = "moku-sdf";
|
|
12827
|
+
/** The name of Pixi's default batcher, which an element goes back to. */
|
|
12828
|
+
const DEFAULT_BATCHER = "default";
|
|
12829
|
+
/** Floats of one vertex: position 2, uv 2, colour 1 (unorm8x4), texture id and rounding 1 (uint16x2), distance 1. */
|
|
12830
|
+
const VERTEX_SIZE = 7;
|
|
12831
|
+
/** Bytes of one vertex. */
|
|
12832
|
+
const STRIDE = 28;
|
|
12833
|
+
/** The name of the renderer's batched SDF program, on both backends. */
|
|
12834
|
+
const PROGRAM_NAME = "moku-sdf-batch";
|
|
12835
|
+
/**
|
|
12836
|
+
* The renderer's piece of the high shader: the glyph's edge width from the vertex, at the scale of the render group,
|
|
12837
|
+
* and the coverage with the alpha applied once. The template's `outColor * vColor` applies the colour and every alpha.
|
|
12838
|
+
*/
|
|
12839
|
+
const SDF_BIT = {
|
|
12840
|
+
name: "moku-sdf-batch-bit",
|
|
12841
|
+
vertex: {
|
|
12842
|
+
header: `
|
|
12843
|
+
@in aSdfDistance: f32;
|
|
12844
|
+
@out @interpolate(flat) vSdfDistance: f32;
|
|
12845
|
+
`,
|
|
12846
|
+
main: `
|
|
12847
|
+
let sdfWorld = globalUniforms.uWorldTransformMatrix;
|
|
12848
|
+
vSdfDistance = aSdfDistance * (length(sdfWorld[0].xy) + length(sdfWorld[1].xy)) * 0.5;
|
|
12849
|
+
`
|
|
12850
|
+
},
|
|
12851
|
+
fragment: {
|
|
12852
|
+
header: `
|
|
12853
|
+
@in @interpolate(flat) vSdfDistance: f32;
|
|
12854
|
+
`,
|
|
12855
|
+
main: `
|
|
12856
|
+
outColor = vec4<f32>(calculateMSDFAlpha(outColor, vec4<f32>(vColor.rgb / max(vColor.a, 1e-4), 1.0), vSdfDistance));
|
|
12857
|
+
`
|
|
12858
|
+
}
|
|
12859
|
+
};
|
|
13016
12860
|
/**
|
|
13017
|
-
* The
|
|
13018
|
-
*
|
|
13019
|
-
*
|
|
13020
|
-
*/
|
|
13021
|
-
const
|
|
13022
|
-
|
|
13023
|
-
|
|
13024
|
-
|
|
13025
|
-
|
|
13026
|
-
|
|
13027
|
-
|
|
13028
|
-
|
|
13029
|
-
|
|
13030
|
-
|
|
13031
|
-
|
|
12861
|
+
* The same piece in GLSL, for the WebGL program. Pixi compiles its batch programs as GLSL ES 1.00 (its `in` and `out`
|
|
12862
|
+
* are macros), where `flat` is a reserved word: the distance is a plain varying, the same at the three vertices of a
|
|
12863
|
+
* glyph's triangle, as Pixi's own texture id is.
|
|
12864
|
+
*/
|
|
12865
|
+
const SDF_BIT_GL = {
|
|
12866
|
+
name: "moku-sdf-batch-bit",
|
|
12867
|
+
vertex: {
|
|
12868
|
+
header: `
|
|
12869
|
+
in float aSdfDistance;
|
|
12870
|
+
out float vSdfDistance;
|
|
12871
|
+
`,
|
|
12872
|
+
main: `
|
|
12873
|
+
vSdfDistance = aSdfDistance * (length(uWorldTransformMatrix[0].xy) + length(uWorldTransformMatrix[1].xy)) * 0.5;
|
|
12874
|
+
`
|
|
12875
|
+
},
|
|
12876
|
+
fragment: {
|
|
12877
|
+
header: `
|
|
12878
|
+
in float vSdfDistance;
|
|
12879
|
+
`,
|
|
12880
|
+
main: `
|
|
12881
|
+
outColor = vec4(calculateMSDFAlpha(outColor, vec4(vColor.rgb / max(vColor.a, 1e-4), 1.0), vSdfDistance));
|
|
12882
|
+
`
|
|
12883
|
+
}
|
|
12884
|
+
};
|
|
13032
12885
|
/**
|
|
13033
|
-
* The
|
|
12886
|
+
* The edge width one vertex of an element carries: Pixi's distance at the element's scale in its render group, times
|
|
12887
|
+
* the resolution. A text's group transform is what Pixi scales by; the render group's own scale is the shader's.
|
|
13034
12888
|
*
|
|
13035
|
-
* @param
|
|
13036
|
-
* @
|
|
13037
|
-
* @
|
|
13038
|
-
*
|
|
13039
|
-
*
|
|
13040
|
-
*
|
|
12889
|
+
* @param element - The element.
|
|
12890
|
+
* @param a - `a` of its group transform.
|
|
12891
|
+
* @param b - `b` of its group transform.
|
|
12892
|
+
* @param c - `c` of its group transform.
|
|
12893
|
+
* @param d - `d` of its group transform.
|
|
12894
|
+
* @param density - Device pixels per CSS pixel.
|
|
12895
|
+
* @returns The distance, device px at the render group's scale 1; 0 for an element without glyphs.
|
|
13041
12896
|
*/
|
|
13042
|
-
function
|
|
13043
|
-
const
|
|
13044
|
-
|
|
12897
|
+
function distanceOf(element, a, b, c, d, density) {
|
|
12898
|
+
const glyphs = element.renderable?.[SDF];
|
|
12899
|
+
if (glyphs === void 0) return 0;
|
|
12900
|
+
return glyphs.perScale * (Math.sqrt(a * a + b * b) + Math.sqrt(c * c + d * d)) * density / 2;
|
|
13045
12901
|
}
|
|
13046
12902
|
/**
|
|
13047
|
-
*
|
|
13048
|
-
*
|
|
13049
|
-
*/
|
|
13050
|
-
const DISTANCE = Symbol("sdf-text-distance");
|
|
13051
|
-
/**
|
|
13052
|
-
* Pixi's read of `uDistance`, at every upload: the distance per unit of scale at the text's world scale, or Pixi's last
|
|
13053
|
-
* write without one, times the renderer's resolution.
|
|
12903
|
+
* Keeps one shader per batch texture limit: made the first time a limit is asked for, shared by every batcher after.
|
|
12904
|
+
* Its own function, because lint rule L5 refuses a collection built inside an exported declaration.
|
|
13054
12905
|
*
|
|
13055
|
-
* @param
|
|
13056
|
-
* @returns The
|
|
12906
|
+
* @param make - Makes the shader of one texture limit.
|
|
12907
|
+
* @returns The shader for a texture limit.
|
|
13057
12908
|
*/
|
|
13058
|
-
function
|
|
13059
|
-
const
|
|
13060
|
-
|
|
13061
|
-
|
|
13062
|
-
|
|
13063
|
-
|
|
13064
|
-
|
|
13065
|
-
|
|
13066
|
-
|
|
13067
|
-
|
|
13068
|
-
}
|
|
13069
|
-
/**
|
|
13070
|
-
* Pixi's write of `uDistance`, at a layout: the CSS distance and, while the pipe names the text, the distance per unit
|
|
13071
|
-
* of its group scale.
|
|
13072
|
-
*
|
|
13073
|
-
* @param this - The text's uniforms.
|
|
13074
|
-
* @param value - Pixi's distance, CSS px.
|
|
13075
|
-
*/
|
|
13076
|
-
function writeDistance(value) {
|
|
13077
|
-
const state = this[DISTANCE];
|
|
13078
|
-
const text = state.writing.text;
|
|
13079
|
-
const scale = text === void 0 ? 0 : scaleOf$1(text.groupTransform);
|
|
13080
|
-
state.cell[0] = value;
|
|
13081
|
-
state.cell[1] = scale > 0 ? value / scale : 0;
|
|
13082
|
-
state.text = text;
|
|
13083
|
-
}
|
|
13084
|
-
/**
|
|
13085
|
-
* The `uDistance` of every text's uniforms: one pair of functions, so every uniforms object has the same hidden class
|
|
13086
|
-
* and Pixi's generated sync function reads it monomorphic. A pair made per text, or an accessor laid over Pixi's data
|
|
13087
|
-
* property, sends each object to dictionary mode. The getter hands a still text's answer back boxed once (`answer`):
|
|
13088
|
-
* Pixi's sync may not inline it.
|
|
13089
|
-
*/
|
|
13090
|
-
const DISTANCE_ACCESSOR = {
|
|
13091
|
-
enumerable: true,
|
|
13092
|
-
configurable: true,
|
|
13093
|
-
get: readDistance,
|
|
13094
|
-
set: writeDistance
|
|
13095
|
-
};
|
|
12909
|
+
function cachedBy(make) {
|
|
12910
|
+
const shaders = /* @__PURE__ */ new Map();
|
|
12911
|
+
return (maxTextures) => {
|
|
12912
|
+
const known = shaders.get(maxTextures);
|
|
12913
|
+
if (known !== void 0) return known;
|
|
12914
|
+
const shader = make(maxTextures);
|
|
12915
|
+
shaders.set(maxTextures, shader);
|
|
12916
|
+
return shader;
|
|
12917
|
+
};
|
|
12918
|
+
}
|
|
13096
12919
|
/**
|
|
13097
|
-
* The
|
|
13098
|
-
*
|
|
12920
|
+
* The shaders of the batcher: Pixi's five SDF bits on both backends with the local uniforms replaced by the
|
|
12921
|
+
* renderer's piece, and Pixi's batch sampler slots for WebGL, as Pixi's own `DefaultShader` carries them.
|
|
13099
12922
|
*
|
|
13100
|
-
*
|
|
13101
|
-
*
|
|
13102
|
-
|
|
13103
|
-
|
|
13104
|
-
|
|
13105
|
-
|
|
13106
|
-
|
|
13107
|
-
|
|
13108
|
-
|
|
13109
|
-
|
|
13110
|
-
|
|
13111
|
-
|
|
13112
|
-
|
|
13113
|
-
|
|
13114
|
-
|
|
13115
|
-
|
|
13116
|
-
|
|
13117
|
-
|
|
12923
|
+
* @param pixi - The loaded Pixi module.
|
|
12924
|
+
* @returns The shader for a texture limit.
|
|
12925
|
+
*/
|
|
12926
|
+
function sdfShaders(pixi) {
|
|
12927
|
+
return cachedBy((maxTextures) => new pixi.Shader({
|
|
12928
|
+
gpuProgram: pixi.compileHighShaderGpuProgram({
|
|
12929
|
+
name: PROGRAM_NAME,
|
|
12930
|
+
bits: [
|
|
12931
|
+
pixi.colorBit,
|
|
12932
|
+
pixi.generateTextureBatchBit(maxTextures),
|
|
12933
|
+
SDF_BIT,
|
|
12934
|
+
pixi.mSDFBit,
|
|
12935
|
+
pixi.roundPixelsBit
|
|
12936
|
+
]
|
|
12937
|
+
}),
|
|
12938
|
+
glProgram: pixi.compileHighShaderGlProgram({
|
|
12939
|
+
name: PROGRAM_NAME,
|
|
12940
|
+
bits: [
|
|
12941
|
+
pixi.colorBitGl,
|
|
12942
|
+
pixi.generateTextureBatchBitGl(maxTextures),
|
|
12943
|
+
SDF_BIT_GL,
|
|
12944
|
+
pixi.mSDFBitGl,
|
|
12945
|
+
pixi.roundPixelsBitGl
|
|
12946
|
+
]
|
|
12947
|
+
}),
|
|
12948
|
+
resources: { batchSamplers: pixi.getBatchSamplersUniformGroup(maxTextures) }
|
|
12949
|
+
}));
|
|
12950
|
+
}
|
|
12951
|
+
/**
|
|
12952
|
+
* The geometry of one batcher: Pixi's batch geometry with the distance after the texture id, one buffer of 7 floats a
|
|
12953
|
+
* vertex.
|
|
13118
12954
|
*
|
|
13119
12955
|
* @param pixi - The loaded Pixi module.
|
|
13120
|
-
* @
|
|
13121
|
-
|
|
13122
|
-
|
|
13123
|
-
|
|
13124
|
-
*/
|
|
13125
|
-
|
|
13126
|
-
|
|
13127
|
-
|
|
13128
|
-
|
|
13129
|
-
|
|
13130
|
-
|
|
13131
|
-
|
|
13132
|
-
|
|
13133
|
-
|
|
13134
|
-
|
|
13135
|
-
|
|
13136
|
-
|
|
13137
|
-
|
|
13138
|
-
|
|
13139
|
-
|
|
13140
|
-
|
|
13141
|
-
|
|
13142
|
-
|
|
12956
|
+
* @returns A new geometry.
|
|
12957
|
+
*/
|
|
12958
|
+
function sdfGeometry(pixi) {
|
|
12959
|
+
const buffer = new pixi.Buffer({
|
|
12960
|
+
data: /* @__PURE__ */ new Float32Array(1),
|
|
12961
|
+
label: "attribute-sdf-batch-buffer",
|
|
12962
|
+
usage: pixi.BufferUsage.VERTEX | pixi.BufferUsage.COPY_DST,
|
|
12963
|
+
shrinkToFit: false
|
|
12964
|
+
});
|
|
12965
|
+
const indexBuffer = new pixi.Buffer({
|
|
12966
|
+
data: /* @__PURE__ */ new Uint32Array(1),
|
|
12967
|
+
label: "index-sdf-batch-buffer",
|
|
12968
|
+
usage: pixi.BufferUsage.INDEX | pixi.BufferUsage.COPY_DST,
|
|
12969
|
+
shrinkToFit: false
|
|
12970
|
+
});
|
|
12971
|
+
return new pixi.Geometry({
|
|
12972
|
+
attributes: {
|
|
12973
|
+
aPosition: {
|
|
12974
|
+
buffer,
|
|
12975
|
+
format: "float32x2",
|
|
12976
|
+
stride: STRIDE,
|
|
12977
|
+
offset: 0
|
|
12978
|
+
},
|
|
12979
|
+
aUV: {
|
|
12980
|
+
buffer,
|
|
12981
|
+
format: "float32x2",
|
|
12982
|
+
stride: STRIDE,
|
|
12983
|
+
offset: 8
|
|
12984
|
+
},
|
|
12985
|
+
aColor: {
|
|
12986
|
+
buffer,
|
|
12987
|
+
format: "unorm8x4",
|
|
12988
|
+
stride: STRIDE,
|
|
12989
|
+
offset: 16
|
|
12990
|
+
},
|
|
12991
|
+
aTextureIdAndRound: {
|
|
12992
|
+
buffer,
|
|
12993
|
+
format: "uint16x2",
|
|
12994
|
+
stride: STRIDE,
|
|
12995
|
+
offset: 20
|
|
12996
|
+
},
|
|
12997
|
+
aSdfDistance: {
|
|
12998
|
+
buffer,
|
|
12999
|
+
format: "float32",
|
|
13000
|
+
stride: STRIDE,
|
|
13001
|
+
offset: 24
|
|
13002
|
+
}
|
|
13143
13003
|
},
|
|
13144
|
-
|
|
13145
|
-
value: 0,
|
|
13146
|
-
type: "f32"
|
|
13147
|
-
}
|
|
13004
|
+
indexBuffer
|
|
13148
13005
|
});
|
|
13149
|
-
const values = group.uniforms;
|
|
13150
|
-
const state = {
|
|
13151
|
-
cell: Float64Array.of(4, 0, NaN),
|
|
13152
|
-
text: void 0,
|
|
13153
|
-
writing,
|
|
13154
|
-
density,
|
|
13155
|
-
answer: void 0
|
|
13156
|
-
};
|
|
13157
|
-
const uniforms = {
|
|
13158
|
-
uColor: values.uColor,
|
|
13159
|
-
uTransformMatrix: values.uTransformMatrix,
|
|
13160
|
-
uRound: values.uRound
|
|
13161
|
-
};
|
|
13162
|
-
Object.defineProperty(uniforms, "uDistance", DISTANCE_ACCESSOR);
|
|
13163
|
-
Object.defineProperty(uniforms, DISTANCE, { value: state });
|
|
13164
|
-
group.uniforms = uniforms;
|
|
13165
|
-
return group;
|
|
13166
13006
|
}
|
|
13167
13007
|
/**
|
|
13168
|
-
*
|
|
13008
|
+
* Builds the batcher of distance-field glyphs from the module object, at call time, because Pixi's `Batcher` arrives
|
|
13009
|
+
* with the lazily loaded module. Its packing is Pixi's `DefaultBatcher`'s loop with the distance written after the
|
|
13010
|
+
* texture id; the distances are packed at the resolution the batcher pipe last drew it at (`packAt`).
|
|
13169
13011
|
*
|
|
13170
|
-
* @param
|
|
13171
|
-
* @
|
|
13172
|
-
|
|
13173
|
-
|
|
13174
|
-
|
|
13175
|
-
|
|
13012
|
+
* @param pixi - The loaded Pixi module.
|
|
13013
|
+
* @returns The batcher class, with the extension Pixi's registry files it under.
|
|
13014
|
+
*/
|
|
13015
|
+
function createSdfBatcher(pixi) {
|
|
13016
|
+
const shaderFor = sdfShaders(pixi);
|
|
13017
|
+
/** Pixi's batcher, with a vertex that carries the edge width of its glyph. */
|
|
13018
|
+
class SdfBatcher extends pixi.Batcher {
|
|
13019
|
+
static extension = {
|
|
13020
|
+
type: [pixi.ExtensionType.Batcher],
|
|
13021
|
+
name: SDF_BATCHER
|
|
13022
|
+
};
|
|
13023
|
+
name = SDF_BATCHER;
|
|
13024
|
+
geometry = sdfGeometry(pixi);
|
|
13025
|
+
shader = shaderFor(this.maxTextures);
|
|
13026
|
+
/** Device pixels per CSS pixel the distances are packed at; a double from the start, so writes box nothing. */
|
|
13027
|
+
density = NaN;
|
|
13028
|
+
vertexSize = VERTEX_SIZE;
|
|
13029
|
+
/**
|
|
13030
|
+
* Packs the vertices of a mesh element: Pixi's default vertex and the distance.
|
|
13031
|
+
*
|
|
13032
|
+
* @param element - The element.
|
|
13033
|
+
* @param float32View - The attribute buffer as floats.
|
|
13034
|
+
* @param uint32View - The attribute buffer as unsigned integers.
|
|
13035
|
+
* @param index - The first float of the element.
|
|
13036
|
+
* @param textureId - The element's slot in its batch's textures.
|
|
13037
|
+
*/
|
|
13038
|
+
packAttributes(element, float32View, uint32View, index, textureId) {
|
|
13039
|
+
const textureIdAndRound = textureId << 16 | element.roundPixels & 65535;
|
|
13040
|
+
const { a, b, c, d, tx, ty } = element.transform;
|
|
13041
|
+
const distance = distanceOf(element, a, b, c, d, this.density);
|
|
13042
|
+
const positions = element.positions;
|
|
13043
|
+
const uvs = element.uvs;
|
|
13044
|
+
const argb = element.color;
|
|
13045
|
+
const end = element.attributeOffset + element.attributeSize;
|
|
13046
|
+
let at = index;
|
|
13047
|
+
for (let vertex = element.attributeOffset; vertex < end; vertex += 1) {
|
|
13048
|
+
const x = positions[vertex * 2] ?? 0;
|
|
13049
|
+
const y = positions[vertex * 2 + 1] ?? 0;
|
|
13050
|
+
float32View[at] = a * x + c * y + tx;
|
|
13051
|
+
float32View[at + 1] = d * y + b * x + ty;
|
|
13052
|
+
float32View[at + 2] = uvs[vertex * 2] ?? 0;
|
|
13053
|
+
float32View[at + 3] = uvs[vertex * 2 + 1] ?? 0;
|
|
13054
|
+
uint32View[at + 4] = argb;
|
|
13055
|
+
uint32View[at + 5] = textureIdAndRound;
|
|
13056
|
+
float32View[at + 6] = distance;
|
|
13057
|
+
at += VERTEX_SIZE;
|
|
13058
|
+
}
|
|
13059
|
+
}
|
|
13060
|
+
/**
|
|
13061
|
+
* Packs the four vertices of a quad element: Pixi's default quad and the distance.
|
|
13062
|
+
*
|
|
13063
|
+
* @param element - The element.
|
|
13064
|
+
* @param float32View - The attribute buffer as floats.
|
|
13065
|
+
* @param uint32View - The attribute buffer as unsigned integers.
|
|
13066
|
+
* @param index - The first float of the element.
|
|
13067
|
+
* @param textureId - The element's slot in its batch's textures.
|
|
13068
|
+
*/
|
|
13069
|
+
packQuadAttributes(element, float32View, uint32View, index, textureId) {
|
|
13070
|
+
const { a, b, c, d, tx, ty } = element.transform;
|
|
13071
|
+
const { minX, minY, maxX, maxY } = element.bounds;
|
|
13072
|
+
const uvs = element.texture.uvs;
|
|
13073
|
+
const argb = element.color;
|
|
13074
|
+
const textureIdAndRound = textureId << 16 | element.roundPixels & 65535;
|
|
13075
|
+
const distance = distanceOf(element, a, b, c, d, this.density);
|
|
13076
|
+
float32View[index] = a * minX + c * minY + tx;
|
|
13077
|
+
float32View[index + 1] = d * minY + b * minX + ty;
|
|
13078
|
+
float32View[index + 2] = uvs.x0;
|
|
13079
|
+
float32View[index + 3] = uvs.y0;
|
|
13080
|
+
uint32View[index + 4] = argb;
|
|
13081
|
+
uint32View[index + 5] = textureIdAndRound;
|
|
13082
|
+
float32View[index + 6] = distance;
|
|
13083
|
+
float32View[index + 7] = a * maxX + c * minY + tx;
|
|
13084
|
+
float32View[index + 8] = d * minY + b * maxX + ty;
|
|
13085
|
+
float32View[index + 9] = uvs.x1;
|
|
13086
|
+
float32View[index + 10] = uvs.y1;
|
|
13087
|
+
uint32View[index + 11] = argb;
|
|
13088
|
+
uint32View[index + 12] = textureIdAndRound;
|
|
13089
|
+
float32View[index + 13] = distance;
|
|
13090
|
+
float32View[index + 14] = a * maxX + c * maxY + tx;
|
|
13091
|
+
float32View[index + 15] = d * maxY + b * maxX + ty;
|
|
13092
|
+
float32View[index + 16] = uvs.x2;
|
|
13093
|
+
float32View[index + 17] = uvs.y2;
|
|
13094
|
+
uint32View[index + 18] = argb;
|
|
13095
|
+
uint32View[index + 19] = textureIdAndRound;
|
|
13096
|
+
float32View[index + 20] = distance;
|
|
13097
|
+
float32View[index + 21] = a * minX + c * maxY + tx;
|
|
13098
|
+
float32View[index + 22] = d * maxY + b * minX + ty;
|
|
13099
|
+
float32View[index + 23] = uvs.x3;
|
|
13100
|
+
float32View[index + 24] = uvs.y3;
|
|
13101
|
+
uint32View[index + 25] = argb;
|
|
13102
|
+
uint32View[index + 26] = textureIdAndRound;
|
|
13103
|
+
float32View[index + 27] = distance;
|
|
13104
|
+
}
|
|
13105
|
+
/**
|
|
13106
|
+
* Packs every element of the drawn batches again at a resolution and hands the buffer to the GPU.
|
|
13107
|
+
*
|
|
13108
|
+
* @param density - Device pixels per CSS pixel.
|
|
13109
|
+
*/
|
|
13110
|
+
packAt(density) {
|
|
13111
|
+
this.density = density;
|
|
13112
|
+
for (let batch = 0; batch < this.batchIndex; batch += 1) {
|
|
13113
|
+
const elements = this.batches[batch]?.elements;
|
|
13114
|
+
if (elements === void 0) continue;
|
|
13115
|
+
for (let at = 0; at < elements.length; at += 1) {
|
|
13116
|
+
const element = elements[at];
|
|
13117
|
+
if (element !== void 0) this.updateElement(element);
|
|
13118
|
+
}
|
|
13119
|
+
}
|
|
13120
|
+
this.dirty = false;
|
|
13121
|
+
this.geometry.buffers[0]?.update(this.attributeSize * 4);
|
|
13122
|
+
}
|
|
13123
|
+
}
|
|
13124
|
+
return SdfBatcher;
|
|
13176
13125
|
}
|
|
13177
13126
|
/**
|
|
13178
|
-
*
|
|
13179
|
-
* for, shared by every text after. Its own function, because lint rule L5 refuses a collection
|
|
13180
|
-
* built inside an exported declaration.
|
|
13127
|
+
* Tells whether a batch's batcher is the renderer's batcher of distance-field glyphs.
|
|
13181
13128
|
*
|
|
13182
|
-
* @param
|
|
13183
|
-
* @returns
|
|
13129
|
+
* @param batcher - The batcher of a batch.
|
|
13130
|
+
* @returns True for the SDF batcher.
|
|
13184
13131
|
*/
|
|
13185
|
-
function
|
|
13186
|
-
|
|
13187
|
-
return (maxTextures) => {
|
|
13188
|
-
const known = programs.get(maxTextures);
|
|
13189
|
-
if (known !== void 0) return known;
|
|
13190
|
-
const program = compile(maxTextures);
|
|
13191
|
-
programs.set(maxTextures, program);
|
|
13192
|
-
return program;
|
|
13193
|
-
};
|
|
13132
|
+
function isSdfBatcher(batcher) {
|
|
13133
|
+
return batcher.name === SDF_BATCHER;
|
|
13194
13134
|
}
|
|
13195
13135
|
/**
|
|
13196
|
-
*
|
|
13197
|
-
*
|
|
13136
|
+
* Builds the renderer's batcher pipe from the module object, at call time. It keeps the base's `static extension`
|
|
13137
|
+
* object, so the registry files it under the same types and name, and Pixi's batcher registry, which `getBatcher`
|
|
13138
|
+
* reads. `addToBatch` names the batcher of an element before Pixi adds it: the glyphs of a distance-field text go to
|
|
13139
|
+
* `SDF_BATCHER`; a pooled element that carried that name before, now another graphics' or a plain font's, goes back to
|
|
13140
|
+
* Pixi's default. `execute` packs the distances of a batcher again when the renderer's resolution changed since it
|
|
13141
|
+
* last drew, before Pixi binds its geometry. The monitor's counting pipe of a dev build extends this pipe.
|
|
13198
13142
|
*
|
|
13199
13143
|
* @param pixi - The loaded Pixi module.
|
|
13200
|
-
* @returns The
|
|
13144
|
+
* @returns The pipe class.
|
|
13201
13145
|
*/
|
|
13202
|
-
function
|
|
13203
|
-
|
|
13204
|
-
|
|
13205
|
-
|
|
13206
|
-
|
|
13207
|
-
|
|
13208
|
-
|
|
13209
|
-
|
|
13210
|
-
|
|
13211
|
-
|
|
13212
|
-
|
|
13213
|
-
|
|
13214
|
-
|
|
13215
|
-
|
|
13216
|
-
|
|
13217
|
-
|
|
13218
|
-
|
|
13219
|
-
|
|
13220
|
-
|
|
13221
|
-
|
|
13146
|
+
function createSdfBatcherPipe(pixi) {
|
|
13147
|
+
/** Pixi's batcher pipe, which routes the glyphs of distance-field text to the renderer's batcher. */
|
|
13148
|
+
class SdfRoutingBatcherPipe extends pixi.BatcherPipe {
|
|
13149
|
+
static extension = pixi.BatcherPipe.extension;
|
|
13150
|
+
/**
|
|
13151
|
+
* Names the element's batcher, then adds it as Pixi does.
|
|
13152
|
+
*
|
|
13153
|
+
* @param element - The element.
|
|
13154
|
+
* @param instructionSet - The instructions of its render group.
|
|
13155
|
+
*/
|
|
13156
|
+
addToBatch(element, instructionSet) {
|
|
13157
|
+
routeElement(element);
|
|
13158
|
+
super.addToBatch(element, instructionSet);
|
|
13159
|
+
}
|
|
13160
|
+
/**
|
|
13161
|
+
* Draws a batch as Pixi does; the first batch of an SDF batcher's run first packs the distances again when the
|
|
13162
|
+
* resolution moved.
|
|
13163
|
+
*
|
|
13164
|
+
* @param batch - The batch.
|
|
13165
|
+
*/
|
|
13166
|
+
execute(batch) {
|
|
13167
|
+
const batcher = batch.batcher;
|
|
13168
|
+
if (batch.action === "startBatch" && isSdfBatcher(batcher)) {
|
|
13169
|
+
const resolution = this.renderer.resolution;
|
|
13170
|
+
if (batcher.density !== resolution) batcher.packAt(resolution);
|
|
13171
|
+
}
|
|
13172
|
+
super.execute(batch);
|
|
13173
|
+
}
|
|
13174
|
+
}
|
|
13175
|
+
return SdfRoutingBatcherPipe;
|
|
13222
13176
|
}
|
|
13223
13177
|
/**
|
|
13224
|
-
*
|
|
13225
|
-
*
|
|
13178
|
+
* Names the batcher of one element: `SDF_BATCHER` for a glyph of a distance-field text, Pixi's default again for an
|
|
13179
|
+
* element that carried that name from an earlier owner, as it was otherwise.
|
|
13226
13180
|
*
|
|
13227
|
-
* @param
|
|
13228
|
-
* @returns The WebGL program for a texture limit.
|
|
13181
|
+
* @param element - The element.
|
|
13229
13182
|
*/
|
|
13230
|
-
function
|
|
13231
|
-
const
|
|
13232
|
-
|
|
13233
|
-
|
|
13234
|
-
name: TEXT_BIT_NAME,
|
|
13235
|
-
fragment: {
|
|
13236
|
-
...base.fragment,
|
|
13237
|
-
main: SINGLE_ALPHA_STEP_GL
|
|
13238
|
-
}
|
|
13239
|
-
};
|
|
13240
|
-
return cachedBy((maxTextures) => pixi.compileHighShaderGlProgram({
|
|
13241
|
-
name: PROGRAM_NAME,
|
|
13242
|
-
bits: [
|
|
13243
|
-
pixi.colorBitGl,
|
|
13244
|
-
pixi.generateTextureBatchBitGl(maxTextures),
|
|
13245
|
-
textBit,
|
|
13246
|
-
pixi.mSDFBitGl,
|
|
13247
|
-
pixi.roundPixelsBitGl
|
|
13248
|
-
]
|
|
13249
|
-
}));
|
|
13183
|
+
function routeElement(element) {
|
|
13184
|
+
const glyphs = element.renderable?.[SDF];
|
|
13185
|
+
if (glyphs !== void 0 && glyphs.perScale > 0) element.batcherName = SDF_BATCHER;
|
|
13186
|
+
else if (element.batcherName === "moku-sdf") element.batcherName = DEFAULT_BATCHER;
|
|
13250
13187
|
}
|
|
13188
|
+
//#endregion
|
|
13189
|
+
//#region src/plugins/renderer/host/sdf-text.ts
|
|
13190
|
+
/** The suffix of a bitmap font's key in Pixi's cache, the one `_updateDistanceField` reads. */
|
|
13191
|
+
const CACHE_SUFFIX$1 = "-bitmap";
|
|
13251
13192
|
/**
|
|
13252
|
-
*
|
|
13253
|
-
* Pixi's
|
|
13254
|
-
*
|
|
13255
|
-
*
|
|
13256
|
-
*
|
|
13257
|
-
*
|
|
13258
|
-
|
|
13259
|
-
|
|
13193
|
+
* Measures the edge of a text's glyphs again when its font family or size changed since: Pixi's distance at scale 1
|
|
13194
|
+
* from the font in Pixi's cache, 0 for a font without a distance field or not in the cache.
|
|
13195
|
+
*
|
|
13196
|
+
* @param pixi - The loaded Pixi module.
|
|
13197
|
+
* @param glyphs - The text's glyph record.
|
|
13198
|
+
* @param style - The text's style.
|
|
13199
|
+
*/
|
|
13200
|
+
function measureGlyphs(pixi, glyphs, style) {
|
|
13201
|
+
const family = style.fontFamily;
|
|
13202
|
+
const size = style.fontSize;
|
|
13203
|
+
if (family === glyphs.family && size === glyphs.size) return;
|
|
13204
|
+
const key = `${String(family)}${CACHE_SUFFIX$1}`;
|
|
13205
|
+
const font = pixi.Cache.has(key) ? pixi.Cache.get(key) : void 0;
|
|
13206
|
+
const field = font?.distanceField;
|
|
13207
|
+
glyphs.family = family;
|
|
13208
|
+
glyphs.size = size;
|
|
13209
|
+
glyphs.perScale = font === void 0 || field === void 0 || field.type === "none" ? 0 : field.range * (1 / (font.baseRenderedFontSize / size));
|
|
13210
|
+
}
|
|
13211
|
+
/**
|
|
13212
|
+
* Builds the renderer's bitmap text pipe from the module object, at call time, because Pixi's pipe arrives with the
|
|
13213
|
+
* lazily loaded module. It keeps the base's `static extension` object, so the registry files it under the same types
|
|
13214
|
+
* and name. Every text's proxy graphics gets a batchable context and its glyph record when Pixi makes it; the record is
|
|
13215
|
+
* brought up to date before Pixi adds or updates the text, so the batcher packs the edge of the text's font.
|
|
13260
13216
|
*
|
|
13261
13217
|
* @param pixi - The loaded Pixi module.
|
|
13262
13218
|
* @returns The pipe class.
|
|
13263
13219
|
*/
|
|
13264
13220
|
function createSdfTextPipe(pixi) {
|
|
13265
|
-
|
|
13266
|
-
|
|
13267
|
-
/** Pixi's bitmap text pipe, with an SDF shader that applies the alpha once. */
|
|
13268
|
-
class SingleAlphaBitmapTextPipe extends pixi.BitmapTextPipe {
|
|
13221
|
+
/** Pixi's bitmap text pipe, with distance-field text drawn by the renderer's batcher. */
|
|
13222
|
+
class SdfTextPipe extends pixi.BitmapTextPipe {
|
|
13269
13223
|
static extension = pixi.BitmapTextPipe.extension;
|
|
13270
|
-
/** The text whose distance Pixi writes now, read by the uniforms of every text of this pipe. */
|
|
13271
|
-
writing = { text: void 0 };
|
|
13272
13224
|
/**
|
|
13273
|
-
*
|
|
13274
|
-
*
|
|
13275
|
-
*
|
|
13276
|
-
*
|
|
13225
|
+
* Makes a text's proxy graphics as Pixi does, batchable at any length, with the edge of its glyphs.
|
|
13226
|
+
*
|
|
13227
|
+
* @param text - The text.
|
|
13228
|
+
* @returns The proxy.
|
|
13277
13229
|
*/
|
|
13278
|
-
|
|
13230
|
+
initGpuText(text) {
|
|
13231
|
+
const proxy = super.initGpuText(text);
|
|
13232
|
+
const glyphs = {
|
|
13233
|
+
perScale: NaN,
|
|
13234
|
+
family: void 0,
|
|
13235
|
+
size: NaN
|
|
13236
|
+
};
|
|
13237
|
+
proxy.context.batchMode = "batch";
|
|
13238
|
+
proxy[SDF] = glyphs;
|
|
13239
|
+
measureGlyphs(pixi, glyphs, text._style);
|
|
13240
|
+
return proxy;
|
|
13241
|
+
}
|
|
13279
13242
|
/**
|
|
13280
|
-
* Adds a text to the instructions as Pixi does,
|
|
13243
|
+
* Adds a text to the instructions as Pixi does, its glyph edge measured first.
|
|
13281
13244
|
*
|
|
13282
13245
|
* @param text - The text.
|
|
13283
13246
|
* @param instructionSet - The instructions of its render group.
|
|
13284
13247
|
*/
|
|
13285
13248
|
addRenderable(text, instructionSet) {
|
|
13286
|
-
this.
|
|
13287
|
-
this.laying = text;
|
|
13249
|
+
this.measure(text);
|
|
13288
13250
|
super.addRenderable(text, instructionSet);
|
|
13289
|
-
this.laying = void 0;
|
|
13290
|
-
this.writing.text = void 0;
|
|
13291
13251
|
}
|
|
13292
13252
|
/**
|
|
13293
|
-
* Updates a text as Pixi does,
|
|
13253
|
+
* Updates a text as Pixi does, its glyph edge measured first.
|
|
13294
13254
|
*
|
|
13295
13255
|
* @param text - The text.
|
|
13296
13256
|
*/
|
|
13297
13257
|
updateRenderable(text) {
|
|
13298
|
-
this.
|
|
13258
|
+
this.measure(text);
|
|
13299
13259
|
super.updateRenderable(text);
|
|
13300
|
-
this.writing.text = void 0;
|
|
13301
13260
|
}
|
|
13302
13261
|
/**
|
|
13303
|
-
*
|
|
13304
|
-
* else a new one.
|
|
13262
|
+
* No shader: Pixi then leaves the text's context without one, and batchable.
|
|
13305
13263
|
*
|
|
13306
|
-
* @returns
|
|
13264
|
+
* @returns `null`.
|
|
13307
13265
|
*/
|
|
13308
13266
|
getSdfShader() {
|
|
13309
|
-
|
|
13310
|
-
|
|
13311
|
-
|
|
13312
|
-
|
|
13313
|
-
|
|
13314
|
-
|
|
13315
|
-
|
|
13316
|
-
|
|
13317
|
-
|
|
13318
|
-
|
|
13319
|
-
resources: {
|
|
13320
|
-
localUniforms: createLocalUniforms(pixi, density, this.writing),
|
|
13321
|
-
batchSamplers: pixi.getBatchSamplersUniformGroup(maxTextures)
|
|
13322
|
-
}
|
|
13323
|
-
});
|
|
13267
|
+
return null;
|
|
13268
|
+
}
|
|
13269
|
+
/**
|
|
13270
|
+
* Measures the glyph edge of a text Pixi made a proxy for already; a new text's proxy measures its own.
|
|
13271
|
+
*
|
|
13272
|
+
* @param text - The text.
|
|
13273
|
+
*/
|
|
13274
|
+
measure(text) {
|
|
13275
|
+
const proxy = text._gpuData[this._renderer.uid];
|
|
13276
|
+
if (proxy !== void 0) measureGlyphs(pixi, proxy[SDF], text._style);
|
|
13324
13277
|
}
|
|
13325
13278
|
}
|
|
13326
|
-
return
|
|
13279
|
+
return SdfTextPipe;
|
|
13327
13280
|
}
|
|
13328
13281
|
/**
|
|
13329
|
-
*
|
|
13330
|
-
*
|
|
13331
|
-
* afterwards draws
|
|
13332
|
-
*
|
|
13282
|
+
* Installs the three pieces of batched distance-field text in the module's extension registry: the batcher under its
|
|
13283
|
+
* own name, and the bitmap text pipe and the batch pipe under Pixi's, on both backends. Every application made
|
|
13284
|
+
* afterwards draws distance-field text with them. Every class is built before the registry is touched, so a module
|
|
13285
|
+
* without the pieces throws with the registry as it was. The registry is global to the module object, so the remover
|
|
13286
|
+
* matters: it swaps Pixi's own pipes back and leaves the batcher, which a pooled element may still name. A counting batch pipe of the monitor, which extends the routing pipe, is
|
|
13287
|
+
* installed after this one, in its place.
|
|
13333
13288
|
*
|
|
13334
13289
|
* @param pixi - The loaded Pixi module.
|
|
13335
13290
|
* @returns The remover.
|
|
13336
|
-
* @throws {TypeError} When the module has no `BitmapTextPipe` to subclass.
|
|
13337
|
-
*/
|
|
13338
|
-
function
|
|
13339
|
-
const
|
|
13340
|
-
|
|
13341
|
-
pixi
|
|
13291
|
+
* @throws {TypeError} When the module has no `Batcher`, `BitmapTextPipe` or `BatcherPipe` to subclass.
|
|
13292
|
+
*/
|
|
13293
|
+
function installSdfText(pixi) {
|
|
13294
|
+
const batcher = createSdfBatcher(pixi);
|
|
13295
|
+
const textPipe = createSdfTextPipe(pixi);
|
|
13296
|
+
const batchPipe = createSdfBatcherPipe(pixi);
|
|
13297
|
+
pixi.extensions.remove(pixi.BitmapTextPipe, pixi.BatcherPipe);
|
|
13298
|
+
pixi.extensions.add(batcher, textPipe, batchPipe);
|
|
13342
13299
|
return () => {
|
|
13343
|
-
pixi.extensions.remove(
|
|
13344
|
-
pixi.extensions.add(pixi.BitmapTextPipe);
|
|
13300
|
+
pixi.extensions.remove(textPipe, batchPipe);
|
|
13301
|
+
pixi.extensions.add(pixi.BitmapTextPipe, pixi.BatcherPipe);
|
|
13345
13302
|
};
|
|
13346
13303
|
}
|
|
13347
13304
|
//#endregion
|
|
@@ -13478,26 +13435,27 @@ function installCounting$1(ctx, pixi, deps) {
|
|
|
13478
13435
|
} else installPasses(ctx, pixi, deps.installPasses);
|
|
13479
13436
|
}
|
|
13480
13437
|
/**
|
|
13481
|
-
*
|
|
13482
|
-
*
|
|
13483
|
-
*
|
|
13484
|
-
* Every build installs it. A Pixi without the pieces draws
|
|
13438
|
+
* Installs the renderer's batched distance-field text: its batcher, its bitmap text pipe and its batch pipe, which draw
|
|
13439
|
+
* consecutive labels in one draw with the alpha applied once. It runs before `new Application()`, because Pixi builds
|
|
13440
|
+
* its pipes inside `init`, and before the draw counter, whose batch pipe extends the routing one and takes its place;
|
|
13441
|
+
* once per host state: a restore reuses the installed pieces. Every build installs it. A Pixi without the pieces draws
|
|
13442
|
+
* on with its own pipes, with a warning.
|
|
13485
13443
|
*
|
|
13486
13444
|
* @param ctx - Domain context of the renderer plugin.
|
|
13487
13445
|
* @param pixi - The module the application is built from.
|
|
13488
13446
|
*/
|
|
13489
|
-
function
|
|
13447
|
+
function installTextBatch(ctx, pixi) {
|
|
13490
13448
|
const state = ctx.state.host;
|
|
13491
13449
|
if (state.uninstallSdfText !== void 0) return;
|
|
13492
13450
|
try {
|
|
13493
|
-
state.uninstallSdfText =
|
|
13451
|
+
state.uninstallSdfText = installSdfText(pixi);
|
|
13494
13452
|
} catch (error) {
|
|
13495
13453
|
ctx.log.warn("renderer: distance-field text applies its alpha twice", { error });
|
|
13496
13454
|
}
|
|
13497
13455
|
}
|
|
13498
13456
|
/**
|
|
13499
13457
|
* Swaps Pixi's render-group pipe for the renderer's, which binds a filter's input again after a group cached as a
|
|
13500
|
-
* texture was refreshed inside the filter (F10). Like the
|
|
13458
|
+
* texture was refreshed inside the filter (F10). Like the batched text it runs before `new Application()`, once per
|
|
13501
13459
|
* host state, in every build. A Pixi without the pieces draws on with its own pipe, with a warning.
|
|
13502
13460
|
*
|
|
13503
13461
|
* @param ctx - Domain context of the renderer plugin.
|
|
@@ -13531,8 +13489,8 @@ async function createApplication(ctx, mount, deps) {
|
|
|
13531
13489
|
const pixi = loaded ?? await ctx.config.loadPixi();
|
|
13532
13490
|
if (typeof __MOKU_GAME_AGENT__ !== "undefined" && __MOKU_GAME_AGENT__ && loaded === void 0) startupMark("moku:pixi-end");
|
|
13533
13491
|
state.pixi = pixi;
|
|
13492
|
+
installTextBatch(ctx, pixi);
|
|
13534
13493
|
installCounting$1(ctx, pixi, deps);
|
|
13535
|
-
installSdfText(ctx, pixi);
|
|
13536
13494
|
installRenderGroup(ctx, pixi);
|
|
13537
13495
|
state.resolution.dpr = devicePixels();
|
|
13538
13496
|
state.resolution.cap = ctx.config.maxResolution;
|
|
@@ -13577,7 +13535,7 @@ function destroyApplication(state) {
|
|
|
13577
13535
|
/**
|
|
13578
13536
|
* Undoes everything `init` created. Works on the state alone, because `onStop` has no context.
|
|
13579
13537
|
* `ready` drops first, so a device that goes away during the teardown is ignored. The draw
|
|
13580
|
-
* counter, the
|
|
13538
|
+
* counter, the batched text and the render-group pipe swap Pixi's own classes back, so the next application on
|
|
13581
13539
|
* the page starts from them. The pool trim starts over and its listeners go; Pixi's pool itself is left.
|
|
13582
13540
|
*
|
|
13583
13541
|
* @param state - The host branch of the plugin state.
|
|
@@ -26837,19 +26795,22 @@ function countUpload(sinks, source) {
|
|
|
26837
26795
|
/**
|
|
26838
26796
|
* Builds the seven counting subclasses from the module object. They are made at call time,
|
|
26839
26797
|
* because the base classes arrive with the lazily loaded module. Each keeps the base's `static
|
|
26840
|
-
* extension` object as it is, so the registry files it under the same type and name.
|
|
26798
|
+
* extension` object as it is, so the registry files it under the same type and name. The batch pipe extends the one
|
|
26799
|
+
* it is given: the routing pipe of the renderer's batched text, which the plugin root injects, so the counting pipe
|
|
26800
|
+
* that takes its place routes the same.
|
|
26841
26801
|
*
|
|
26842
26802
|
* @param pixi - The loaded Pixi module.
|
|
26843
26803
|
* @param sinks - What every instance counts into.
|
|
26804
|
+
* @param batcherPipe - The batch pipe the counting one extends; Pixi's own when none is given.
|
|
26844
26805
|
* @returns The seven counting classes (BatcherPipe among them).
|
|
26845
26806
|
*/
|
|
26846
|
-
function createCountingClasses(pixi, sinks) {
|
|
26807
|
+
function createCountingClasses(pixi, sinks, batcherPipe = pixi.BatcherPipe) {
|
|
26847
26808
|
const counter = sinks.draws;
|
|
26848
26809
|
/**
|
|
26849
26810
|
* Counts every rebuild of a render group's instruction list: Pixi's render group system calls `buildStart` once per
|
|
26850
26811
|
* rebuild and nowhere else. It inherits the static batcher registry, so batchers resolve as before.
|
|
26851
26812
|
*/
|
|
26852
|
-
class CountingBatcherPipe extends
|
|
26813
|
+
class CountingBatcherPipe extends batcherPipe {
|
|
26853
26814
|
static extension = pixi.BatcherPipe.extension;
|
|
26854
26815
|
/**
|
|
26855
26816
|
* Counts the rebuild, in the frame's counter only inside a frame, then begins it.
|
|
@@ -27056,10 +27017,11 @@ function createCountingClasses(pixi, sinks) {
|
|
|
27056
27017
|
*
|
|
27057
27018
|
* @param pixi - The loaded Pixi module.
|
|
27058
27019
|
* @param sinks - The sinks of the renderer that installs it.
|
|
27020
|
+
* @param batcherPipe - The batch pipe the counting one extends: the routing pipe of the batched text.
|
|
27059
27021
|
* @returns The remover.
|
|
27060
27022
|
*/
|
|
27061
|
-
function installCounting(pixi, sinks) {
|
|
27062
|
-
const counting = createCountingClasses(pixi, sinks);
|
|
27023
|
+
function installCounting(pixi, sinks, batcherPipe = pixi.BatcherPipe) {
|
|
27024
|
+
const counting = createCountingClasses(pixi, sinks, batcherPipe);
|
|
27063
27025
|
const bases = [
|
|
27064
27026
|
pixi.BatcherPipe,
|
|
27065
27027
|
pixi.GpuBatchAdaptor,
|
|
@@ -29427,14 +29389,14 @@ function createViewportApi(ctx, deps) {
|
|
|
29427
29389
|
/**
|
|
29428
29390
|
* The counting `host` installs before `new Application()`: the seven counting classes (BatcherPipe among them) of
|
|
29429
29391
|
* `monitor` in a dev build and in a debug build, bound to the sinks in the monitor branch of the
|
|
29430
|
-
* state, none in production
|
|
29431
|
-
* module leaves the bundle.
|
|
29392
|
+
* state, none in production; the counting batch pipe extends the routing pipe of the batched text. A production
|
|
29393
|
+
* `define` folds the dev or agent guard, so the counting module leaves the bundle.
|
|
29432
29394
|
*
|
|
29433
29395
|
* @param ctx - Domain context of the renderer plugin.
|
|
29434
29396
|
* @returns The bound installer, or `undefined` without the dev flag and the agent flag.
|
|
29435
29397
|
*/
|
|
29436
29398
|
function countingOf(ctx) {
|
|
29437
|
-
return typeof __MOKU_GAME_DEV__ !== "undefined" && __MOKU_GAME_DEV__ || typeof __MOKU_GAME_AGENT__ !== "undefined" && __MOKU_GAME_AGENT__ ? (pixi) => installCounting(pixi, ctx.state.monitor.sinks) : void 0;
|
|
29399
|
+
return typeof __MOKU_GAME_DEV__ !== "undefined" && __MOKU_GAME_DEV__ || typeof __MOKU_GAME_AGENT__ !== "undefined" && __MOKU_GAME_AGENT__ ? (pixi) => installCounting(pixi, ctx.state.monitor.sinks, createSdfBatcherPipe(pixi)) : void 0;
|
|
29438
29400
|
}
|
|
29439
29401
|
/**
|
|
29440
29402
|
* The writers of `sync` the kept cache draws through: the one owner of `renderable`, the one writer of a view's
|
|
@@ -35770,24 +35732,9 @@ function overTargetOf(ctx, node, run) {
|
|
|
35770
35732
|
return takePending(ctx);
|
|
35771
35733
|
}
|
|
35772
35734
|
/**
|
|
35773
|
-
* Holds the feature that declares the scene now mounted, then releases the hold of the scene
|
|
35774
|
-
* before: the new hold first, so no phase sees the moment with neither. A scene no feature
|
|
35775
|
-
* declared holds nothing.
|
|
35776
|
-
*
|
|
35777
|
-
* @param ctx - Domain context of the plugin.
|
|
35778
|
-
* @param scene - The id of the scene now mounted.
|
|
35779
|
-
*/
|
|
35780
|
-
function holdOwner(ctx, scene) {
|
|
35781
|
-
const state = ctx.state;
|
|
35782
|
-
const release = state.held;
|
|
35783
|
-
const owner = state.owners.get(scene);
|
|
35784
|
-
state.held = owner === void 0 ? void 0 : ctx.deps.world.ecs.hold(owner);
|
|
35785
|
-
release?.();
|
|
35786
|
-
}
|
|
35787
|
-
/**
|
|
35788
35735
|
* Builds the scene in one synchronous block: the layers, then the projections of the old scene
|
|
35789
|
-
* out, then the ones of the new scene in, the
|
|
35790
|
-
* to `assets`, then the frame loop out of its idle cap, and only then the event.
|
|
35736
|
+
* out, then the ones of the new scene in, the new scene to the world's systems, then the list the
|
|
35737
|
+
* scene names to `assets`, then the frame loop out of its idle cap, and only then the event.
|
|
35791
35738
|
*
|
|
35792
35739
|
* @param ctx - Domain context of the plugin.
|
|
35793
35740
|
* @param scene - The scene whose bundles are there.
|
|
@@ -35802,7 +35749,7 @@ function apply(ctx, scene, named) {
|
|
|
35802
35749
|
if (previous !== void 0) projection.unmount(previous.projections);
|
|
35803
35750
|
projection.mount(scene.projections, state.owner);
|
|
35804
35751
|
state.current = scene.id;
|
|
35805
|
-
|
|
35752
|
+
ctx.deps.world.ecs.setScene(scene.id);
|
|
35806
35753
|
state.named = named;
|
|
35807
35754
|
ctx.deps.assets.sceneBundles(named);
|
|
35808
35755
|
ctx.deps.time.wake();
|
|
@@ -35916,16 +35863,22 @@ function withDeps$5(ctx) {
|
|
|
35916
35863
|
};
|
|
35917
35864
|
}
|
|
35918
35865
|
/**
|
|
35919
|
-
*
|
|
35920
|
-
*
|
|
35921
|
-
*
|
|
35922
|
-
|
|
35866
|
+
* Records which feature declared which scene, so a duplicate id can name both of them.
|
|
35867
|
+
*
|
|
35868
|
+
* @returns An empty table of scene id to feature name.
|
|
35869
|
+
*/
|
|
35870
|
+
function emptyOwners$1() {
|
|
35871
|
+
return /* @__PURE__ */ new Map();
|
|
35872
|
+
}
|
|
35873
|
+
/**
|
|
35874
|
+
* Reads the `scenes` key of every registered feature into the registry. Features register in
|
|
35875
|
+
* their own `onInit`, which runs before this `onStart`, so every scene of the game is here.
|
|
35923
35876
|
*
|
|
35924
35877
|
* @param ctx - Domain context of the plugin.
|
|
35925
35878
|
* @throws {Error} When two features declare the same scene id.
|
|
35926
35879
|
*/
|
|
35927
35880
|
function readScenes(ctx) {
|
|
35928
|
-
const owners =
|
|
35881
|
+
const owners = emptyOwners$1();
|
|
35929
35882
|
for (const feature of ctx.deps.flow.features.all()) {
|
|
35930
35883
|
const list = feature.description.scenes;
|
|
35931
35884
|
if (!Array.isArray(list)) continue;
|
|
@@ -35956,9 +35909,9 @@ function startScenes(ctx) {
|
|
|
35956
35909
|
scenes.state.teardown = scenes.deps.flow.onEnter("scene", (node, run) => enterScene(scenes, node, run));
|
|
35957
35910
|
}
|
|
35958
35911
|
/**
|
|
35959
|
-
* Removes the `onEnter` callback in `onStop` and forgets every scene
|
|
35960
|
-
* one named
|
|
35961
|
-
* context may not call another plugin, and `world.onStop` clears every entity
|
|
35912
|
+
* Removes the `onEnter` callback in `onStop` and forgets every scene and the bundles the mounted
|
|
35913
|
+
* one named. Nothing is unmounted: a
|
|
35914
|
+
* teardown context may not call another plugin, and `world.onStop` clears every entity anyway.
|
|
35962
35915
|
*
|
|
35963
35916
|
* @param teardown - What the teardown context carries.
|
|
35964
35917
|
* @param teardown.state - The plugin state.
|
|
@@ -35973,8 +35926,6 @@ function stopScenes(teardown) {
|
|
|
35973
35926
|
state.teardown?.();
|
|
35974
35927
|
state.teardown = void 0;
|
|
35975
35928
|
state.scenes.clear();
|
|
35976
|
-
state.owners.clear();
|
|
35977
|
-
state.held = void 0;
|
|
35978
35929
|
state.current = void 0;
|
|
35979
35930
|
state.pending = void 0;
|
|
35980
35931
|
state.named = [];
|
|
@@ -35991,15 +35942,6 @@ function emptyRegistry$1() {
|
|
|
35991
35942
|
return /* @__PURE__ */ new Map();
|
|
35992
35943
|
}
|
|
35993
35944
|
/**
|
|
35994
|
-
* Creates the table of which feature declared which scene, for the same reason as
|
|
35995
|
-
* `emptyRegistry`.
|
|
35996
|
-
*
|
|
35997
|
-
* @returns An empty table of scene id to feature name.
|
|
35998
|
-
*/
|
|
35999
|
-
function emptyOwners$1() {
|
|
36000
|
-
return /* @__PURE__ */ new Map();
|
|
36001
|
-
}
|
|
36002
|
-
/**
|
|
36003
35945
|
* Creates the one owner every mount of this plugin carries. One is enough: a single scene is
|
|
36004
35946
|
* mounted at a time and `unmount` is what flushes.
|
|
36005
35947
|
*
|
|
@@ -36012,8 +35954,8 @@ function sceneOwner() {
|
|
|
36012
35954
|
};
|
|
36013
35955
|
}
|
|
36014
35956
|
/**
|
|
36015
|
-
* Creates the initial scenes state: nothing declared, nothing mounted, nothing pending
|
|
36016
|
-
*
|
|
35957
|
+
* Creates the initial scenes state: nothing declared, nothing mounted, nothing pending. The
|
|
35958
|
+
* registry is filled in `onStart` from the feature descriptions.
|
|
36017
35959
|
*
|
|
36018
35960
|
* @returns A fresh state, owned by one app.
|
|
36019
35961
|
*/
|
|
@@ -36024,9 +35966,7 @@ function createScenesState() {
|
|
|
36024
35966
|
pending: void 0,
|
|
36025
35967
|
named: [],
|
|
36026
35968
|
owner: sceneOwner(),
|
|
36027
|
-
teardown: void 0
|
|
36028
|
-
owners: emptyOwners$1(),
|
|
36029
|
-
held: void 0
|
|
35969
|
+
teardown: void 0
|
|
36030
35970
|
};
|
|
36031
35971
|
}
|
|
36032
35972
|
//#endregion
|
|
@@ -42258,8 +42198,9 @@ function countParticles(ectx) {
|
|
|
42258
42198
|
state.over.particles = above;
|
|
42259
42199
|
}
|
|
42260
42200
|
/**
|
|
42261
|
-
* Builds the `animate` system of the
|
|
42262
|
-
*
|
|
42201
|
+
* Builds the `animate` system of the emitters. It runs while an entity carries `Emitter` (the world
|
|
42202
|
+
* skips a system whose query has no entity) and returns at once while the renderer does not draw.
|
|
42203
|
+
* Time is the frame's `delta` only, so `time.step(dt)` drives every test.
|
|
42263
42204
|
*
|
|
42264
42205
|
* @param ectx - Domain context of the effects plugin.
|
|
42265
42206
|
* @returns The system `world.ecs.system` takes.
|
|
@@ -42275,6 +42216,26 @@ function createParticleSystem(ectx) {
|
|
|
42275
42216
|
if (pixi === void 0) return;
|
|
42276
42217
|
const reducedMotion = ectx.deps.anim.reducedMotion();
|
|
42277
42218
|
for (const [entity, emitter] of rows) runEmitter(ectx, pixi, entity, emitter, context.time, reducedMotion);
|
|
42219
|
+
}
|
|
42220
|
+
});
|
|
42221
|
+
}
|
|
42222
|
+
/**
|
|
42223
|
+
* Builds the `animate` system of the orphans, registered right after the emitters: the particles
|
|
42224
|
+
* of an emitter whose host left fly on after the last `Emitter` is gone, so this system has no
|
|
42225
|
+
* query and runs every frame. It returns at once while the renderer does not draw, then steps the
|
|
42226
|
+
* orphans and keeps the particle budget.
|
|
42227
|
+
*
|
|
42228
|
+
* @param ectx - Domain context of the effects plugin.
|
|
42229
|
+
* @returns The system `world.ecs.system` takes.
|
|
42230
|
+
*/
|
|
42231
|
+
function createOrphanSystem(ectx) {
|
|
42232
|
+
return system({
|
|
42233
|
+
name: "effects:orphans",
|
|
42234
|
+
phase: "animate",
|
|
42235
|
+
query: [],
|
|
42236
|
+
run: (_rows, context) => {
|
|
42237
|
+
const host = ectx.deps.renderer.host;
|
|
42238
|
+
if (!host.ready() || host.pixi() === void 0) return;
|
|
42278
42239
|
runOrphans(ectx, context.time);
|
|
42279
42240
|
countParticles(ectx);
|
|
42280
42241
|
}
|
|
@@ -42602,7 +42563,7 @@ function warnScreenReads(ectx, entries) {
|
|
|
42602
42563
|
});
|
|
42603
42564
|
}
|
|
42604
42565
|
/**
|
|
42605
|
-
* Opens the world side: the
|
|
42566
|
+
* Opens the world side: the three systems, the hook that retires an emitter's instance, and the two
|
|
42606
42567
|
* hooks per filter kind; then records the filter components that are on entities already.
|
|
42607
42568
|
*
|
|
42608
42569
|
* @param ectx - Domain context of the effects plugin.
|
|
@@ -42610,7 +42571,7 @@ function warnScreenReads(ectx, entries) {
|
|
|
42610
42571
|
*/
|
|
42611
42572
|
function openWorld(ectx, kinds) {
|
|
42612
42573
|
const ecs = ectx.deps.world.ecs;
|
|
42613
|
-
ectx.state.removers.push(ecs.system(createParticleSystem(ectx)), ecs.system(createFilterSystem(ectx)), ecs.onRemoved(Emitter, (entity) => forgetEmitter(ectx, entity)), ...kinds.map((kind) => ecs.onRemoved(kind.component, (entity) => forgetFilter(ectx, kind, entity))), ...kinds.map((kind) => ecs.onAdded(kind.component, (entity) => trackFilter(ectx, kind, entity))));
|
|
42574
|
+
ectx.state.removers.push(ecs.system(createParticleSystem(ectx)), ecs.system(createOrphanSystem(ectx)), ecs.system(createFilterSystem(ectx)), ecs.onRemoved(Emitter, (entity) => forgetEmitter(ectx, entity)), ...kinds.map((kind) => ecs.onRemoved(kind.component, (entity) => forgetFilter(ectx, kind, entity))), ...kinds.map((kind) => ecs.onAdded(kind.component, (entity) => trackFilter(ectx, kind, entity))));
|
|
42614
42575
|
for (const kind of kinds) for (const [entity] of ecs.query(kind.component)) trackFilter(ectx, kind, entity);
|
|
42615
42576
|
}
|
|
42616
42577
|
/**
|
|
@@ -53470,16 +53431,6 @@ function entitySet(entities) {
|
|
|
53470
53431
|
return new Set(entities);
|
|
53471
53432
|
}
|
|
53472
53433
|
/**
|
|
53473
|
-
* Takes the hold of the feature a root shows, when the root belongs to a feature.
|
|
53474
|
-
*
|
|
53475
|
-
* @param ctx - Domain context of the ui plugin.
|
|
53476
|
-
* @param feature - The feature of the root's component or projection, if any.
|
|
53477
|
-
* @returns The release, or `undefined` for a root of no feature.
|
|
53478
|
-
*/
|
|
53479
|
-
function holdFor(ctx, feature) {
|
|
53480
|
-
return feature === void 0 ? void 0 : ctx.deps.world.ecs.hold(feature);
|
|
53481
|
-
}
|
|
53482
|
-
/**
|
|
53483
53434
|
* Builds the reconcile half of the jsx module: the two systems of the frame and the root
|
|
53484
53435
|
* bookkeeping around them.
|
|
53485
53436
|
*
|
|
@@ -54084,7 +54035,6 @@ function createReconciler(ctx, modules, fields) {
|
|
|
54084
54035
|
if (root === void 0) continue;
|
|
54085
54036
|
if (root.element !== void 0 && state.elements.has(root.element)) continue;
|
|
54086
54037
|
state.removing.delete(entity);
|
|
54087
|
-
root.held?.();
|
|
54088
54038
|
state.roots.delete(entity);
|
|
54089
54039
|
state.byKey.delete(entity);
|
|
54090
54040
|
ecs.despawn(entity);
|
|
@@ -54502,21 +54452,15 @@ function createReconciler(ctx, modules, fields) {
|
|
|
54502
54452
|
if (element?.live === true && root.slot === void 0) ecs.set(element.entity, Order, { value: order });
|
|
54503
54453
|
}
|
|
54504
54454
|
/**
|
|
54505
|
-
* Reconciles one dirty root: reads its current tree and projection name, then diffs it.
|
|
54506
|
-
*
|
|
54507
|
-
* names its projection: its `Tree` hook fires inside the spawn, before the world knows the view.
|
|
54508
|
-
* The root stays dirty while the layout engine is not loaded, and a failure is logged for this
|
|
54509
|
-
* root only.
|
|
54455
|
+
* Reconciles one dirty root: reads its current tree and projection name, then diffs it. The root
|
|
54456
|
+
* stays dirty while the layout engine is not loaded, and a failure is logged for this root only.
|
|
54510
54457
|
*
|
|
54511
54458
|
* @param root - A root with work this frame.
|
|
54512
54459
|
*/
|
|
54513
54460
|
function reconcileDirtyRoot(root) {
|
|
54514
54461
|
const tree = ecs.get(root.entity, Tree);
|
|
54515
54462
|
const place = ctx.deps.world.projection.keyOf(root.entity);
|
|
54516
|
-
if (place !== void 0)
|
|
54517
|
-
root.name = place.projection;
|
|
54518
|
-
if (root.popup === void 0) root.held ??= holdFor(ctx, state.featureOfProjection.get(place.projection));
|
|
54519
|
-
}
|
|
54463
|
+
if (place !== void 0) root.name = place.projection;
|
|
54520
54464
|
if (tree !== void 0) root.tree = tree.node;
|
|
54521
54465
|
refreshRootOrder(root);
|
|
54522
54466
|
root.dirty = !modules.layout.loaded();
|
|
@@ -54593,10 +54537,7 @@ function createReconciler(ctx, modules, fields) {
|
|
|
54593
54537
|
/**
|
|
54594
54538
|
* Records a root: a projection view that returned a tree, or a popup this plugin spawned. The
|
|
54595
54539
|
* `Tree` hook and the popup handler both call it, in either order, so a second call fills in
|
|
54596
|
-
* what the first one could not know.
|
|
54597
|
-
* first recorded by the `Tree` hook takes that hold when the popup handler names the component,
|
|
54598
|
-
* and a reclaimed root keeps the hold it has. A projection root takes its projection's hold at
|
|
54599
|
-
* its first reconcile.
|
|
54540
|
+
* what the first one could not know.
|
|
54600
54541
|
*
|
|
54601
54542
|
* @param entity - The entity that carries `Tree`.
|
|
54602
54543
|
* @param name - The projection name, or the component name of a popup.
|
|
@@ -54612,7 +54553,6 @@ function createReconciler(ctx, modules, fields) {
|
|
|
54612
54553
|
known.popup = popup;
|
|
54613
54554
|
known.name = name;
|
|
54614
54555
|
known.cause = "change";
|
|
54615
|
-
known.held ??= holdFor(ctx, state.featureOfComponent.get(name));
|
|
54616
54556
|
}
|
|
54617
54557
|
return;
|
|
54618
54558
|
}
|
|
@@ -54636,8 +54576,7 @@ function createReconciler(ctx, modules, fields) {
|
|
|
54636
54576
|
cause: popup === void 0 ? void 0 : "change",
|
|
54637
54577
|
slot: void 0,
|
|
54638
54578
|
slotRect: void 0,
|
|
54639
|
-
pending: false
|
|
54640
|
-
held: popup === void 0 ? void 0 : holdFor(ctx, state.featureOfComponent.get(name))
|
|
54579
|
+
pending: false
|
|
54641
54580
|
});
|
|
54642
54581
|
}
|
|
54643
54582
|
/**
|
|
@@ -56872,45 +56811,14 @@ function isComponent(entry) {
|
|
|
56872
56811
|
return typeof entry === "function" && entry.isUiComponent === true;
|
|
56873
56812
|
}
|
|
56874
56813
|
/**
|
|
56875
|
-
*
|
|
56876
|
-
*
|
|
56877
|
-
* @param entry - What the feature put under its `projections` key.
|
|
56878
|
-
* @returns The name, or `undefined` when the entry carries none.
|
|
56879
|
-
* @example
|
|
56880
|
-
* ```ts
|
|
56881
|
-
* projectionName({ name: "shop.sheet", layer: "ui" }); // "shop.sheet"
|
|
56882
|
-
* ```
|
|
56883
|
-
*/
|
|
56884
|
-
function projectionName(entry) {
|
|
56885
|
-
if (typeof entry !== "object" || entry === null) return void 0;
|
|
56886
|
-
const name = entry.name;
|
|
56887
|
-
return typeof name === "string" ? name : void 0;
|
|
56888
|
-
}
|
|
56889
|
-
/**
|
|
56890
|
-
* Fills the component registry from the `ui` key of every feature, in feature order, and records
|
|
56891
|
-
* which feature each component and each projection belongs to: a root holds that feature while it
|
|
56892
|
-
* is shown, so the feature's systems run with `systemsRun: "active"`. Two tables, because a
|
|
56893
|
-
* component and a projection may share a name.
|
|
56814
|
+
* Fills the component registry from the `ui` key of every feature, in feature order.
|
|
56894
56815
|
*
|
|
56895
56816
|
* @param ctx - Domain context of the ui plugin.
|
|
56896
56817
|
* @param jsx - The jsx module, which owns the registry.
|
|
56897
56818
|
* @throws {Error} When two features define the same component name.
|
|
56898
56819
|
*/
|
|
56899
56820
|
function registerComponents(ctx, jsx) {
|
|
56900
|
-
const
|
|
56901
|
-
for (const feature of ctx.deps.flow.features.all()) {
|
|
56902
|
-
for (const entry of feature.description.ui ?? []) {
|
|
56903
|
-
if (!isComponent(entry)) continue;
|
|
56904
|
-
jsx.register(entry);
|
|
56905
|
-
featureOfComponent.set(entry.name, feature.name);
|
|
56906
|
-
}
|
|
56907
|
-
const projections = feature.description.projections;
|
|
56908
|
-
if (!Array.isArray(projections)) continue;
|
|
56909
|
-
for (const entry of projections) {
|
|
56910
|
-
const name = projectionName(entry);
|
|
56911
|
-
if (name !== void 0) featureOfProjection.set(name, feature.name);
|
|
56912
|
-
}
|
|
56913
|
-
}
|
|
56821
|
+
for (const feature of ctx.deps.flow.features.all()) for (const entry of feature.description.ui ?? []) if (isComponent(entry)) jsx.register(entry);
|
|
56914
56822
|
}
|
|
56915
56823
|
/**
|
|
56916
56824
|
* Opens the two systems of phase `layout`, the seven world hooks of `Tree`, `Box`, `Pressed` and
|
|
@@ -56990,8 +56898,6 @@ function stopUi(state) {
|
|
|
56990
56898
|
state.layout.scrolling = void 0;
|
|
56991
56899
|
state.layout.iconLine = void 0;
|
|
56992
56900
|
state.jsx.components.clear();
|
|
56993
|
-
state.jsx.featureOfComponent.clear();
|
|
56994
|
-
state.jsx.featureOfProjection.clear();
|
|
56995
56901
|
state.jsx.roots.clear();
|
|
56996
56902
|
state.jsx.elements.clear();
|
|
56997
56903
|
state.jsx.byIdentity.clear();
|
|
@@ -57387,9 +57293,7 @@ function createJsxState() {
|
|
|
57387
57293
|
written: 0,
|
|
57388
57294
|
debugTaps: [],
|
|
57389
57295
|
debugWarned: false,
|
|
57390
|
-
overlays: emptySet()
|
|
57391
|
-
featureOfComponent: emptyMap$1(),
|
|
57392
|
-
featureOfProjection: emptyMap$1()
|
|
57296
|
+
overlays: emptySet()
|
|
57393
57297
|
};
|
|
57394
57298
|
}
|
|
57395
57299
|
//#endregion
|
|
@@ -58135,18 +58039,6 @@ function assertFeatures(name, children) {
|
|
|
58135
58039
|
for (const [index, child] of children.entries()) if (typeof child?.logicOnly !== "object" || !Array.isArray(child.features)) throw notAFeature(`Feature "${name}"`, `features[${index}]`);
|
|
58136
58040
|
}
|
|
58137
58041
|
/**
|
|
58138
|
-
* Throws for a `systemsRun` outside its type. The type already says so; a game in JavaScript, or
|
|
58139
|
-
* a factory that passes a string through, is caught here, before the world reads it.
|
|
58140
|
-
*
|
|
58141
|
-
* @param name - The feature that declares it.
|
|
58142
|
-
* @param value - What the description holds under `systemsRun`.
|
|
58143
|
-
* @throws {Error} When the value is present and neither `"always"` nor `"active"`.
|
|
58144
|
-
*/
|
|
58145
|
-
function assertSystemsRun(name, value) {
|
|
58146
|
-
if (value === void 0 || value === "always" || value === "active") return;
|
|
58147
|
-
throw new Error(`[game] Feature "${name}" has systemsRun "${String(value)}".\n Use "always" (the default) or "active".`);
|
|
58148
|
-
}
|
|
58149
|
-
/**
|
|
58150
58042
|
* Builds the plugin of one feature: it depends on `flow`, checks in `onInit` that the app composes
|
|
58151
58043
|
* every declared sub-feature, then registers its description, before any plugin above reads the
|
|
58152
58044
|
* registry in `onStart`.
|
|
@@ -58185,14 +58077,12 @@ function featurePluginOf(name, description, children) {
|
|
|
58185
58077
|
* same plugin built from the V1 keys of the description, for headless tests. A feature that
|
|
58186
58078
|
* declares `features` is a parent: its `onInit` throws when one of them is not composed. The
|
|
58187
58079
|
* plugin carries them in `features`, its `logicOnly` carries their `logicOnly`.
|
|
58188
|
-
* `systemsRun: "active"` runs the feature's systems only while it is active (see `SystemsRun`).
|
|
58189
58080
|
*
|
|
58190
58081
|
* @param name - Feature name. Shares the namespace with plugin names.
|
|
58191
58082
|
* @param description - What the feature brings: nodes, flows, slot contributions, sub-features.
|
|
58192
58083
|
* @returns The feature plugin, with its `logicOnly` twin.
|
|
58193
|
-
* @throws {Error} When the name is reserved or belongs to an engine plugin, for an entry of
|
|
58194
|
-
* `features` that `defineFeature` did not make
|
|
58195
|
-
* `"always"` nor `"active"`.
|
|
58084
|
+
* @throws {Error} When the name is reserved or belongs to an engine plugin, and for an entry of
|
|
58085
|
+
* `features` that `defineFeature` did not make.
|
|
58196
58086
|
* @example
|
|
58197
58087
|
* ```ts
|
|
58198
58088
|
* // The reward popup is a feature: the main flow only declares slot("afterOrder").
|
|
@@ -58207,7 +58097,7 @@ function featurePluginOf(name, description, children) {
|
|
|
58207
58097
|
*/
|
|
58208
58098
|
function defineFeature(name, description) {
|
|
58209
58099
|
if (takenNames().has(name)) throw new Error(`[game] Feature name "${name}" is reserved or already a plugin.\n Choose another feature name.`);
|
|
58210
|
-
|
|
58100
|
+
assertNoSystemsRun(name, description);
|
|
58211
58101
|
const children = Object.freeze([...description.features ?? []]);
|
|
58212
58102
|
assertFeatures(name, children);
|
|
58213
58103
|
const logicOnly = Object.assign(featurePluginOf(name, logicKeysOf(description), children), { features: Object.freeze(children.map((child) => child.logicOnly)) });
|
|
@@ -58217,6 +58107,18 @@ function defineFeature(name, description) {
|
|
|
58217
58107
|
});
|
|
58218
58108
|
}
|
|
58219
58109
|
/**
|
|
58110
|
+
* Throws for a feature that still writes `systemsRun`, removed after 0.15.3: the type refuses it,
|
|
58111
|
+
* this catches a game in JavaScript or a factory that passes the key through.
|
|
58112
|
+
*
|
|
58113
|
+
* @param name - Feature name.
|
|
58114
|
+
* @param description - What the feature brings.
|
|
58115
|
+
* @throws {Error} When the description carries `systemsRun`.
|
|
58116
|
+
*/
|
|
58117
|
+
function assertNoSystemsRun(name, description) {
|
|
58118
|
+
if (!("systemsRun" in description)) return;
|
|
58119
|
+
throw new Error(`[game] Feature "${name}" sets systemsRun, which the engine no longer has.\n Delete it: a system whose query has no entity is not run (world README, "When a system runs").`);
|
|
58120
|
+
}
|
|
58121
|
+
/**
|
|
58220
58122
|
* Tells whether an entry of a feature tree carries its `features` list, as every plugin of
|
|
58221
58123
|
* `defineFeature` does. A plain plugin, a cast object or a hole in the array does not.
|
|
58222
58124
|
*
|