@moku-labs/game 0.15.2 → 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-C_GccOFq.d.mts → commands-CvWBaoEi.d.mts} +5 -5
- package/dist/control.d.mts +4 -4
- package/dist/{define-DnO7ZLGl.d.mts → define-DSC-Q0yU.d.mts} +2 -2
- package/dist/{headless-D6BrEwCf.d.mts → headless-oykCMFBV.d.mts} +1 -1
- package/dist/{index-w2uyRKkj.d.mts → index-Cv5r0M9Y.d.mts} +38 -4
- package/dist/index.d.mts +5 -5
- package/dist/index.mjs +2 -2
- 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-kOH8InAs.d.mts → runtime-BVVlgcMv.d.mts} +1 -1
- package/dist/{src-Ca8W8Dvv.mjs → src-tkKl-oCn.mjs} +1121 -315
- package/dist/testing.d.mts +4 -4
- package/dist/testing.mjs +1 -1
- package/dist/{types-Bm7DVtA_.d.mts → types-B6YgPkY8.d.mts} +1 -1
- package/dist/{types-fpFKYm6E.d.mts → types-CiTr21gu.d.mts} +13 -5
- package/dist/{types-jiWJhLPE.d.mts → types-DY_wOvUF.d.mts} +2 -2
- package/dist/{types-DEYQF_tT.d.mts → types-VNoG7shY.d.mts} +4 -4
- package/dist/{types-BHgJ1F6f.d.mts → types-vjWgmPqS.d.mts} +139 -27
- package/dist/{visual-BvkKJOSz.d.mts → visual-B8ve4Qll.d.mts} +6 -6
- package/dist/visual.d.mts +1 -1
- package/llms.txt +2 -0
- 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
|
/**
|
|
@@ -8635,13 +8768,27 @@ function frameSnapshot(ctx, time) {
|
|
|
8635
8768
|
return snapshot;
|
|
8636
8769
|
}
|
|
8637
8770
|
/**
|
|
8638
|
-
*
|
|
8639
|
-
*
|
|
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.
|
|
8640
8786
|
*
|
|
8641
8787
|
* @param ctx - Domain context of the world plugin.
|
|
8642
8788
|
* @param definition - The system.
|
|
8643
8789
|
* @returns The remover; calling it twice is a no-op.
|
|
8644
|
-
* @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.
|
|
8645
8792
|
*/
|
|
8646
8793
|
function registerSystem(ctx, definition) {
|
|
8647
8794
|
const state = ctx.state.ecs;
|
|
@@ -8651,11 +8798,15 @@ function registerSystem(ctx, definition) {
|
|
|
8651
8798
|
"layout",
|
|
8652
8799
|
"sync"
|
|
8653
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);
|
|
8654
8802
|
const plan = planOf(ctx, definition.query);
|
|
8655
8803
|
const entry = {
|
|
8656
8804
|
definition,
|
|
8657
8805
|
frame: ctx.deps.time.snapshot().frame,
|
|
8658
8806
|
plan,
|
|
8807
|
+
stores: storeSlots(plan.names.length),
|
|
8808
|
+
scenes: definition.scenes === void 0 ? void 0 : [...definition.scenes],
|
|
8809
|
+
skipped: false,
|
|
8659
8810
|
walk: plan.names.length === 0 ? void 0 : new SystemWalk(ctx, plan)
|
|
8660
8811
|
};
|
|
8661
8812
|
const list = state.systems[definition.phase];
|
|
@@ -8682,7 +8833,8 @@ function createPhaseContext(state, world, snapshot, time) {
|
|
|
8682
8833
|
world,
|
|
8683
8834
|
res: (resourceType) => resourceValue(state, resourceType),
|
|
8684
8835
|
snapshot,
|
|
8685
|
-
time
|
|
8836
|
+
time,
|
|
8837
|
+
resumed: false
|
|
8686
8838
|
};
|
|
8687
8839
|
state.phaseContext = context;
|
|
8688
8840
|
return context;
|
|
@@ -8707,6 +8859,27 @@ function phaseContextOf(ctx, world, time) {
|
|
|
8707
8859
|
return known;
|
|
8708
8860
|
}
|
|
8709
8861
|
/**
|
|
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.
|
|
8866
|
+
*
|
|
8867
|
+
* @param state - The ecs branch of the plugin state.
|
|
8868
|
+
* @param entry - The system.
|
|
8869
|
+
* @param context - What its `run` receives; `resumed` is written here.
|
|
8870
|
+
* @returns True when the system runs.
|
|
8871
|
+
*/
|
|
8872
|
+
function wakes(state, entry, context) {
|
|
8873
|
+
const scenes = entry.scenes;
|
|
8874
|
+
if (scenes !== void 0 && !onScene(state.scene, scenes) || !mayMatch(state.stores, entry)) {
|
|
8875
|
+
entry.skipped = true;
|
|
8876
|
+
return false;
|
|
8877
|
+
}
|
|
8878
|
+
context.resumed = entry.skipped;
|
|
8879
|
+
entry.skipped = false;
|
|
8880
|
+
return true;
|
|
8881
|
+
}
|
|
8882
|
+
/**
|
|
8710
8883
|
* Runs one system, reporting a throw: the try lives here, so the loop of a phase holds none.
|
|
8711
8884
|
*
|
|
8712
8885
|
* @param ctx - Domain context of the world plugin.
|
|
@@ -8726,7 +8899,9 @@ function guardedRun(ctx, entry, context, phase) {
|
|
|
8726
8899
|
}
|
|
8727
8900
|
/**
|
|
8728
8901
|
* Runs the systems of one phase and flushes the commands they queued. A throwing system is
|
|
8729
|
-
* reported and skipped for this frame; the frame goes on.
|
|
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.
|
|
8730
8905
|
*
|
|
8731
8906
|
* @param ctx - Domain context of the world plugin.
|
|
8732
8907
|
* @param world - The ecs API a system's `run` receives.
|
|
@@ -8736,21 +8911,24 @@ function guardedRun(ctx, entry, context, phase) {
|
|
|
8736
8911
|
function runPhase(ctx, world, phase, time) {
|
|
8737
8912
|
const state = ctx.state.ecs;
|
|
8738
8913
|
if (phase !== ALWAYS && effectiveMode(ctx) !== "live") return;
|
|
8914
|
+
if (state.scene.leaving.length > 0) endLeaving(state);
|
|
8739
8915
|
const context = phaseContextOf(ctx, world, time);
|
|
8740
8916
|
const entries = state.systems[phase];
|
|
8741
8917
|
state.running = phase;
|
|
8742
8918
|
if (state.timing === void 0) for (let index = 0; index < entries.length; index += 1) {
|
|
8743
8919
|
const entry = entries[index];
|
|
8744
|
-
if (entry
|
|
8920
|
+
if (entry === void 0 || entry.frame >= time.frame) continue;
|
|
8921
|
+
if (wakes(state, entry, context)) guardedRun(ctx, entry, context, phase);
|
|
8745
8922
|
}
|
|
8746
8923
|
else runTimed(ctx, state.timing, entries, context, phase, time);
|
|
8747
8924
|
state.running = void 0;
|
|
8748
8925
|
flushCommands(ctx);
|
|
8749
8926
|
}
|
|
8750
8927
|
/**
|
|
8751
|
-
* Runs the systems of one phase as `runPhase` does, same order, same error isolation,
|
|
8752
|
-
* each run's time to its row of the timing table: two clock reads per run, an indexed
|
|
8753
|
-
* closure. The rows were zeroed at the start of the frame, so after the frame they hold
|
|
8928
|
+
* Runs the systems of one phase as `runPhase` does, same order, same error isolation, same skip,
|
|
8929
|
+
* and adds each run's time to its row of the timing table: two clock reads per run, an indexed
|
|
8930
|
+
* loop, no closure. The rows were zeroed at the start of the frame, so after the frame they hold
|
|
8931
|
+
* its time; a skipped system's row stays 0.
|
|
8754
8932
|
*
|
|
8755
8933
|
* @param ctx - Domain context of the world plugin.
|
|
8756
8934
|
* @param timing - The timing table of the agent build.
|
|
@@ -8760,11 +8938,13 @@ function runPhase(ctx, world, phase, time) {
|
|
|
8760
8938
|
* @param time - The frame's `Time`.
|
|
8761
8939
|
*/
|
|
8762
8940
|
function runTimed(ctx, timing, entries, context, phase, time) {
|
|
8941
|
+
const state = ctx.state.ecs;
|
|
8763
8942
|
const count = entries.length;
|
|
8764
8943
|
for (let index = 0; index < count; index += 1) {
|
|
8765
8944
|
const entry = entries[index];
|
|
8766
8945
|
if (entry === void 0 || entry.frame >= time.frame) continue;
|
|
8767
|
-
|
|
8946
|
+
if (!wakes(state, entry, context)) continue;
|
|
8947
|
+
const row = rowOf(state, entry.definition.name);
|
|
8768
8948
|
const started = timing.clock();
|
|
8769
8949
|
guardedRun(ctx, entry, context, phase);
|
|
8770
8950
|
timing.ms[row] = (timing.ms[row] ?? 0) + timing.clock() - started;
|
|
@@ -8774,7 +8954,8 @@ function runTimed(ctx, timing, entries, context, phase, time) {
|
|
|
8774
8954
|
//#region src/plugins/world/ecs/api.ts
|
|
8775
8955
|
/**
|
|
8776
8956
|
* Empties the world in place: entities, components, resources, systems, hooks and the frame
|
|
8777
|
-
* history. The
|
|
8957
|
+
* history. The mounted scene stays: `scenes` still shows it. The state object keeps its identity,
|
|
8958
|
+
* so every view on it stays valid.
|
|
8778
8959
|
*
|
|
8779
8960
|
* @param ctx - Domain context of the world plugin.
|
|
8780
8961
|
*/
|
|
@@ -8896,6 +9077,9 @@ function createEcsApi(ctx) {
|
|
|
8896
9077
|
setMode: (mode) => {
|
|
8897
9078
|
state.mode = mode;
|
|
8898
9079
|
},
|
|
9080
|
+
setScene: (scene) => {
|
|
9081
|
+
mountScene(state.scene, scene);
|
|
9082
|
+
},
|
|
8899
9083
|
typeOf: (name) => asComponent$2(state.types.get(name)),
|
|
8900
9084
|
snapshot: () => snapshotWorld(state, effectiveMode(ctx)),
|
|
8901
9085
|
diff: (from, to) => diffHistory(state.history, from, to),
|
|
@@ -9033,14 +9217,17 @@ function registerEach(ctx, feature, key, accepts, register) {
|
|
|
9033
9217
|
}
|
|
9034
9218
|
/**
|
|
9035
9219
|
* Registers what every feature brought, in feature order: components first, so their schema is
|
|
9036
|
-
* known, then systems, then projections.
|
|
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.
|
|
9037
9222
|
*
|
|
9038
9223
|
* @param ctx - Domain context of the world plugin.
|
|
9039
9224
|
* @param ecs - The ecs module.
|
|
9040
9225
|
* @param projection - The projection module.
|
|
9041
9226
|
*/
|
|
9042
9227
|
function registerFeatures(ctx, ecs, projection) {
|
|
9043
|
-
|
|
9228
|
+
const features = ctx.deps.flow.features.all();
|
|
9229
|
+
declareScenes(ctx.state.ecs.scene, features);
|
|
9230
|
+
for (const feature of features) {
|
|
9044
9231
|
registerEach(ctx, feature, "components", isComponentEntry, (entry) => registerType(ctx, entry));
|
|
9045
9232
|
registerEach(ctx, feature, "systems", isSystemEntry, (entry) => ecs.system(entry));
|
|
9046
9233
|
registerEach(ctx, feature, "projections", isProjectionEntry, (entry) => projection.register(entry));
|
|
@@ -9086,7 +9273,8 @@ function connectWorld(ctx) {
|
|
|
9086
9273
|
}
|
|
9087
9274
|
/**
|
|
9088
9275
|
* Empties the world in `onStop`: the frame callbacks and the hint listener are removed, then
|
|
9089
|
-
* every track, view, queued view, entity and resource is dropped and the
|
|
9276
|
+
* every track, view, queued view, entity and resource is dropped, and the scenes and the frame
|
|
9277
|
+
* history are reset.
|
|
9090
9278
|
* No `onRemoved` fires, because `renderer` stopped earlier and destroyed its own objects.
|
|
9091
9279
|
*
|
|
9092
9280
|
* @param state - The plugin state, the only thing a teardown context carries.
|
|
@@ -9137,6 +9325,9 @@ function clearWorld(state) {
|
|
|
9137
9325
|
state.ecs.mode = "live";
|
|
9138
9326
|
state.ecs.timing = void 0;
|
|
9139
9327
|
state.ecs.timingSlots.clear();
|
|
9328
|
+
state.ecs.scene.mounted = void 0;
|
|
9329
|
+
state.ecs.scene.leaving.length = 0;
|
|
9330
|
+
state.ecs.scene.declared = void 0;
|
|
9140
9331
|
resetHistory(state.ecs);
|
|
9141
9332
|
}
|
|
9142
9333
|
//#endregion
|
|
@@ -11334,6 +11525,7 @@ function exposeEcs(ecs, projection) {
|
|
|
11334
11525
|
ecs.setMode(mode);
|
|
11335
11526
|
if (ecs.mode() === "fast") projection.flushAll();
|
|
11336
11527
|
},
|
|
11528
|
+
setScene: ecs.setScene,
|
|
11337
11529
|
snapshot: ecs.snapshot,
|
|
11338
11530
|
diff: (from, to) => withKeys(ecs.diff(from, to), ecs, projection),
|
|
11339
11531
|
schema: ecs.schema
|
|
@@ -11432,6 +11624,19 @@ function emptyMap$10() {
|
|
|
11432
11624
|
return /* @__PURE__ */ new Map();
|
|
11433
11625
|
}
|
|
11434
11626
|
/**
|
|
11627
|
+
* Creates the scene state of a world no scene was mounted in: nothing mounted, nothing leaving, the
|
|
11628
|
+
* declared scenes unknown until start.
|
|
11629
|
+
*
|
|
11630
|
+
* @returns An empty scene state.
|
|
11631
|
+
*/
|
|
11632
|
+
function emptyScene() {
|
|
11633
|
+
return {
|
|
11634
|
+
mounted: void 0,
|
|
11635
|
+
leaving: [],
|
|
11636
|
+
declared: void 0
|
|
11637
|
+
};
|
|
11638
|
+
}
|
|
11639
|
+
/**
|
|
11435
11640
|
* Creates the initial ecs state: no entity, no type, no system, no resource and no frame history:
|
|
11436
11641
|
* a dev build starts one on its first frame. Every collection is its own object, so two apps in
|
|
11437
11642
|
* one process never share a world.
|
|
@@ -11473,7 +11678,8 @@ function createEcsState() {
|
|
|
11473
11678
|
walkReported: false,
|
|
11474
11679
|
queryPlans: emptyMap$10(),
|
|
11475
11680
|
walkLists: [],
|
|
11476
|
-
despawns: 0
|
|
11681
|
+
despawns: 0,
|
|
11682
|
+
scene: emptyScene()
|
|
11477
11683
|
};
|
|
11478
11684
|
}
|
|
11479
11685
|
//#endregion
|
|
@@ -11585,7 +11791,7 @@ const worldPlugin = /*#__PURE__*/ createPlugin$1("world", {
|
|
|
11585
11791
|
*/
|
|
11586
11792
|
function slotsPadding(slots) {
|
|
11587
11793
|
let pad = 0;
|
|
11588
|
-
for (
|
|
11794
|
+
for (let index = 0; index < slots.length; index += 1) pad += slots[index]?.filter.padding ?? 0;
|
|
11589
11795
|
return pad;
|
|
11590
11796
|
}
|
|
11591
11797
|
/**
|
|
@@ -11745,8 +11951,9 @@ function checkMissedMarks(ctx, state, stage, views, shown) {
|
|
|
11745
11951
|
//#endregion
|
|
11746
11952
|
//#region src/plugins/renderer/demand/shown.ts
|
|
11747
11953
|
/**
|
|
11748
|
-
* The paddings of every filter a node holds
|
|
11749
|
-
*
|
|
11954
|
+
* The paddings of every filter a node holds: what they could spill onto the screen. Pixi holds the enabled filters of
|
|
11955
|
+
* a view (E3), and a filter switched on reaches Pixi before the decision judges the marks again. Pixi answers
|
|
11956
|
+
* `undefined` before the first write and `null` after a clear.
|
|
11750
11957
|
*
|
|
11751
11958
|
* @param node - A display object on the chain of a view.
|
|
11752
11959
|
* @returns The sum of the paddings; 0 without a filter.
|
|
@@ -11755,7 +11962,7 @@ function paddingOf$2(node) {
|
|
|
11755
11962
|
const filters = node.filters;
|
|
11756
11963
|
if (filters === null || filters === void 0) return 0;
|
|
11757
11964
|
let pad = 0;
|
|
11758
|
-
for (
|
|
11965
|
+
for (let index = 0; index < filters.length; index += 1) pad += filters[index]?.padding ?? 0;
|
|
11759
11966
|
return pad;
|
|
11760
11967
|
}
|
|
11761
11968
|
/** What `chainPadding` answers for a chain that is not drawn. */
|
|
@@ -11777,6 +11984,15 @@ function noAlpha() {
|
|
|
11777
11984
|
return false;
|
|
11778
11985
|
}
|
|
11779
11986
|
/**
|
|
11987
|
+
* The reader without the engine's records: Pixi's own `alpha`.
|
|
11988
|
+
*
|
|
11989
|
+
* @param node - A node on the chain of a view.
|
|
11990
|
+
* @returns True at alpha 0 or below.
|
|
11991
|
+
*/
|
|
11992
|
+
function alphaZero(node) {
|
|
11993
|
+
return node.alpha <= 0;
|
|
11994
|
+
}
|
|
11995
|
+
/**
|
|
11780
11996
|
* Walks from a node up to the stage: every node on the way is visible, renderable (or a kept top its quad draws) and
|
|
11781
11997
|
* not fully transparent, and the chain reaches the stage. The paddings of the filters met on the way are summed; no
|
|
11782
11998
|
* object is made.
|
|
@@ -11784,14 +12000,15 @@ function noAlpha() {
|
|
|
11784
12000
|
* @param from - The first node of the walk.
|
|
11785
12001
|
* @param stage - The stage of the application.
|
|
11786
12002
|
* @param through - The kept reader: the padding of a kept top drawn through its quad, or `HIDDEN`.
|
|
12003
|
+
* @param transparent - Whether a node is drawn at alpha 0.
|
|
11787
12004
|
* @param stop - Where the walk notes a visible node at alpha 0 it stopped at; left as it is for any other stop.
|
|
11788
12005
|
* @returns The sum of the paddings of the chain, or `HIDDEN` when the chain is not drawn.
|
|
11789
12006
|
*/
|
|
11790
|
-
function chainPadding(from, stage, through, stop) {
|
|
12007
|
+
function chainPadding(from, stage, through, transparent, stop) {
|
|
11791
12008
|
let pad = 0;
|
|
11792
12009
|
for (let node = from; node !== stage; node = node.parent) {
|
|
11793
12010
|
if (node === null || !node.visible) return HIDDEN;
|
|
11794
|
-
if (node
|
|
12011
|
+
if (transparent(node)) {
|
|
11795
12012
|
if (stop !== void 0) stop.node = node;
|
|
11796
12013
|
return HIDDEN;
|
|
11797
12014
|
}
|
|
@@ -11861,15 +12078,16 @@ function alphaStop(node, top, alphaHidden) {
|
|
|
11861
12078
|
* @param entity - The entity.
|
|
11862
12079
|
* @param through - The kept reader: the padding of a kept top drawn through its quad, or `HIDDEN`; none by default.
|
|
11863
12080
|
* @param alphaHidden - Sync's reader of the `"alpha"` reason; none by default.
|
|
12081
|
+
* @param transparent - Whether a node is drawn at alpha 0; Pixi's `alpha` by default.
|
|
11864
12082
|
* @returns Where the walk stopped.
|
|
11865
12083
|
*/
|
|
11866
|
-
function unseenBy(state, host, views, entity, through = noneThrough, alphaHidden = noAlpha) {
|
|
12084
|
+
function unseenBy(state, host, views, entity, through = noneThrough, alphaHidden = noAlpha, transparent = alphaZero) {
|
|
11867
12085
|
const view = views.get(entity);
|
|
11868
12086
|
const stage = host.stage();
|
|
11869
12087
|
if (view === void 0 || stage === void 0) return "other";
|
|
11870
12088
|
const top = view.wrapper ?? view.object;
|
|
11871
12089
|
state.stop.node = void 0;
|
|
11872
|
-
const pad = chainPadding(top, stage, through, state.stop);
|
|
12090
|
+
const pad = chainPadding(top, stage, through, transparent, state.stop);
|
|
11873
12091
|
if (pad === HIDDEN) return alphaStop(state.stop.node, top, alphaHidden);
|
|
11874
12092
|
if (view.wrapper !== void 0 || top.children.length > 0) return "shown";
|
|
11875
12093
|
return onScreen(state, host, top, pad) ? "shown" : "off-screen";
|
|
@@ -11882,10 +12100,11 @@ function unseenBy(state, host, views, entity, through = noneThrough, alphaHidden
|
|
|
11882
12100
|
* @param views - The views by entity.
|
|
11883
12101
|
* @param entity - The marked entity.
|
|
11884
12102
|
* @param through - The kept reader: the padding of a kept top drawn through its quad, or `HIDDEN`; none by default.
|
|
12103
|
+
* @param transparent - Whether a node is drawn at alpha 0; Pixi's `alpha` by default.
|
|
11885
12104
|
* @returns True when the view is drawn.
|
|
11886
12105
|
*/
|
|
11887
|
-
function isShown$2(state, host, views, entity, through = noneThrough) {
|
|
11888
|
-
return unseenBy(state, host, views, entity, through) === "shown";
|
|
12106
|
+
function isShown$2(state, host, views, entity, through = noneThrough, transparent = alphaZero) {
|
|
12107
|
+
return unseenBy(state, host, views, entity, through, noAlpha, transparent) === "shown";
|
|
11889
12108
|
}
|
|
11890
12109
|
/**
|
|
11891
12110
|
* The ancestor rule of loose marks: the walk of `isShown` from the parent of the view's top node, without bounds. A
|
|
@@ -11896,13 +12115,14 @@ function isShown$2(state, host, views, entity, through = noneThrough) {
|
|
|
11896
12115
|
* @param views - The views by entity.
|
|
11897
12116
|
* @param entity - The entity of a loose mark.
|
|
11898
12117
|
* @param through - The kept reader: the padding of a kept top drawn through its quad, or `HIDDEN`; none by default.
|
|
12118
|
+
* @param transparent - Whether a node is drawn at alpha 0; Pixi's `alpha` by default.
|
|
11899
12119
|
* @returns True when the ancestors of the view draw it.
|
|
11900
12120
|
*/
|
|
11901
|
-
function ancestorsShown(host, views, entity, through = noneThrough) {
|
|
12121
|
+
function ancestorsShown(host, views, entity, through = noneThrough, transparent = alphaZero) {
|
|
11902
12122
|
const view = views.get(entity);
|
|
11903
12123
|
const stage = host.stage();
|
|
11904
12124
|
if (view === void 0 || stage === void 0) return false;
|
|
11905
|
-
return chainPadding((view.wrapper ?? view.object).parent, stage, through) !== HIDDEN;
|
|
12125
|
+
return chainPadding((view.wrapper ?? view.object).parent, stage, through, transparent) !== HIDDEN;
|
|
11906
12126
|
}
|
|
11907
12127
|
//#endregion
|
|
11908
12128
|
//#region src/plugins/renderer/pixels.ts
|
|
@@ -12025,6 +12245,15 @@ function firstWhole(wholes) {
|
|
|
12025
12245
|
return wholes.size === 0 ? void 0 : wholes.values().next().value;
|
|
12026
12246
|
}
|
|
12027
12247
|
/**
|
|
12248
|
+
* Whether a node is drawn at alpha 0, read from Pixi: the reader of a demand module built without sync's records.
|
|
12249
|
+
*
|
|
12250
|
+
* @param node - A node on the chain of a view.
|
|
12251
|
+
* @returns True at alpha 0 or below.
|
|
12252
|
+
*/
|
|
12253
|
+
function pixiTransparent(node) {
|
|
12254
|
+
return node.alpha <= 0;
|
|
12255
|
+
}
|
|
12256
|
+
/**
|
|
12028
12257
|
* Tells whether one entity of a mark list passes a test, walked by index: no iterator.
|
|
12029
12258
|
*
|
|
12030
12259
|
* @param entities - The marks.
|
|
@@ -12067,11 +12296,12 @@ function clearMarks(state) {
|
|
|
12067
12296
|
* @param deps.host - The host module.
|
|
12068
12297
|
* @param deps.views - The views by entity: `ctx.state.sync.views`, whose identity sync keeps.
|
|
12069
12298
|
* @param deps.hiddenBy - Sync's `hiddenBy`: whether one reason hides a view (spec 26 R1.7.1).
|
|
12299
|
+
* @param deps.transparent - Sync's reader of a node drawn at alpha 0 from its records (E4); Pixi's `alpha` without it.
|
|
12070
12300
|
* @returns The demand module.
|
|
12071
12301
|
*/
|
|
12072
12302
|
function createDemandApi(ctx, deps) {
|
|
12073
12303
|
const state = ctx.state.demand;
|
|
12074
|
-
const { host, views, hiddenBy } = deps;
|
|
12304
|
+
const { host, views, hiddenBy, transparent = pixiTransparent } = deps;
|
|
12075
12305
|
/**
|
|
12076
12306
|
* Whether the render phase decides this frame: `onDemand` is on, or the pixel oracle runs in shadow.
|
|
12077
12307
|
*
|
|
@@ -12084,7 +12314,7 @@ function createDemandApi(ctx, deps) {
|
|
|
12084
12314
|
* @param entity - The entity.
|
|
12085
12315
|
* @returns True when its view is drawn as the frame stands.
|
|
12086
12316
|
*/
|
|
12087
|
-
const shown = (entity) => isShown$2(state, host, views, entity, through);
|
|
12317
|
+
const shown = (entity) => isShown$2(state, host, views, entity, through, transparent);
|
|
12088
12318
|
/**
|
|
12089
12319
|
* Whether a node is the top or the visual of a view hidden by `"alpha"` (spec 26 R1.7.1), read from sync's state.
|
|
12090
12320
|
*
|
|
@@ -12110,7 +12340,7 @@ function createDemandApi(ctx, deps) {
|
|
|
12110
12340
|
* @param entity - The entity of a loose mark.
|
|
12111
12341
|
* @returns True when it counts.
|
|
12112
12342
|
*/
|
|
12113
|
-
const looseShown = (entity) => ancestorsShown(host, views, entity, through);
|
|
12343
|
+
const looseShown = (entity) => ancestorsShown(host, views, entity, through, transparent);
|
|
12114
12344
|
/**
|
|
12115
12345
|
* Notes an entity in the record since the last drawn frame, in a dev or an agent build.
|
|
12116
12346
|
*
|
|
@@ -12226,7 +12456,7 @@ function createDemandApi(ctx, deps) {
|
|
|
12226
12456
|
settle,
|
|
12227
12457
|
frame: () => state.frame,
|
|
12228
12458
|
shown,
|
|
12229
|
-
unseenBy: (entity) => unseenBy(state, host, views, entity, through, alphaHidden),
|
|
12459
|
+
unseenBy: (entity) => unseenBy(state, host, views, entity, through, alphaHidden, transparent),
|
|
12230
12460
|
marked: () => state.marked,
|
|
12231
12461
|
loose: () => state.loose,
|
|
12232
12462
|
visual: () => state.visual,
|
|
@@ -12589,281 +12819,486 @@ function watchPixelRatio(ctx) {
|
|
|
12589
12819
|
state.cleanups.push(() => query?.removeEventListener("change", follow));
|
|
12590
12820
|
}
|
|
12591
12821
|
//#endregion
|
|
12592
|
-
//#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
|
+
};
|
|
12593
12860
|
/**
|
|
12594
|
-
* The
|
|
12595
|
-
*
|
|
12596
|
-
*
|
|
12597
|
-
*/
|
|
12598
|
-
const
|
|
12599
|
-
|
|
12600
|
-
|
|
12601
|
-
|
|
12602
|
-
|
|
12603
|
-
|
|
12604
|
-
|
|
12605
|
-
|
|
12606
|
-
|
|
12607
|
-
|
|
12608
|
-
|
|
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
|
+
};
|
|
12609
12885
|
/**
|
|
12610
|
-
* 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.
|
|
12611
12888
|
*
|
|
12612
|
-
* @param
|
|
12613
|
-
* @
|
|
12614
|
-
* @
|
|
12615
|
-
*
|
|
12616
|
-
*
|
|
12617
|
-
*
|
|
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.
|
|
12618
12896
|
*/
|
|
12619
|
-
function
|
|
12620
|
-
|
|
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;
|
|
12621
12901
|
}
|
|
12622
12902
|
/**
|
|
12623
|
-
*
|
|
12624
|
-
*
|
|
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.
|
|
12625
12905
|
*
|
|
12626
|
-
*
|
|
12627
|
-
*
|
|
12628
|
-
|
|
12629
|
-
|
|
12630
|
-
|
|
12631
|
-
|
|
12632
|
-
|
|
12906
|
+
* @param make - Makes the shader of one texture limit.
|
|
12907
|
+
* @returns The shader for a texture limit.
|
|
12908
|
+
*/
|
|
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
|
+
}
|
|
12919
|
+
/**
|
|
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.
|
|
12633
12922
|
*
|
|
12634
|
-
*
|
|
12635
|
-
*
|
|
12636
|
-
|
|
12637
|
-
|
|
12638
|
-
|
|
12639
|
-
|
|
12640
|
-
|
|
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.
|
|
12641
12954
|
*
|
|
12642
12955
|
* @param pixi - The loaded Pixi module.
|
|
12643
|
-
* @
|
|
12644
|
-
|
|
12645
|
-
|
|
12646
|
-
|
|
12647
|
-
*/
|
|
12648
|
-
|
|
12649
|
-
|
|
12650
|
-
|
|
12651
|
-
value: new Float32Array([
|
|
12652
|
-
1,
|
|
12653
|
-
1,
|
|
12654
|
-
1,
|
|
12655
|
-
1
|
|
12656
|
-
]),
|
|
12657
|
-
type: "vec4<f32>"
|
|
12658
|
-
},
|
|
12659
|
-
uTransformMatrix: {
|
|
12660
|
-
value: new pixi.Matrix(),
|
|
12661
|
-
type: "mat3x3<f32>"
|
|
12662
|
-
},
|
|
12663
|
-
uDistance: {
|
|
12664
|
-
value: 4,
|
|
12665
|
-
type: "f32"
|
|
12666
|
-
},
|
|
12667
|
-
uRound: {
|
|
12668
|
-
value: 0,
|
|
12669
|
-
type: "f32"
|
|
12670
|
-
}
|
|
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
|
|
12671
12964
|
});
|
|
12672
|
-
|
|
12673
|
-
|
|
12674
|
-
|
|
12675
|
-
|
|
12676
|
-
|
|
12677
|
-
|
|
12678
|
-
|
|
12679
|
-
|
|
12680
|
-
|
|
12681
|
-
|
|
12682
|
-
|
|
12683
|
-
|
|
12684
|
-
|
|
12685
|
-
|
|
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
|
+
}
|
|
13003
|
+
},
|
|
13004
|
+
indexBuffer
|
|
12686
13005
|
});
|
|
12687
|
-
return group;
|
|
12688
13006
|
}
|
|
12689
13007
|
/**
|
|
12690
|
-
*
|
|
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`).
|
|
12691
13011
|
*
|
|
12692
|
-
* @param
|
|
12693
|
-
* @
|
|
12694
|
-
|
|
12695
|
-
|
|
12696
|
-
|
|
12697
|
-
|
|
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;
|
|
12698
13125
|
}
|
|
12699
13126
|
/**
|
|
12700
|
-
*
|
|
12701
|
-
* for, shared by every text after. Its own function, because lint rule L5 refuses a collection
|
|
12702
|
-
* built inside an exported declaration.
|
|
13127
|
+
* Tells whether a batch's batcher is the renderer's batcher of distance-field glyphs.
|
|
12703
13128
|
*
|
|
12704
|
-
* @param
|
|
12705
|
-
* @returns
|
|
13129
|
+
* @param batcher - The batcher of a batch.
|
|
13130
|
+
* @returns True for the SDF batcher.
|
|
12706
13131
|
*/
|
|
12707
|
-
function
|
|
12708
|
-
|
|
12709
|
-
return (maxTextures) => {
|
|
12710
|
-
const known = programs.get(maxTextures);
|
|
12711
|
-
if (known !== void 0) return known;
|
|
12712
|
-
const program = compile(maxTextures);
|
|
12713
|
-
programs.set(maxTextures, program);
|
|
12714
|
-
return program;
|
|
12715
|
-
};
|
|
13132
|
+
function isSdfBatcher(batcher) {
|
|
13133
|
+
return batcher.name === SDF_BATCHER;
|
|
12716
13134
|
}
|
|
12717
13135
|
/**
|
|
12718
|
-
*
|
|
12719
|
-
*
|
|
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.
|
|
12720
13142
|
*
|
|
12721
13143
|
* @param pixi - The loaded Pixi module.
|
|
12722
|
-
* @returns The
|
|
13144
|
+
* @returns The pipe class.
|
|
12723
13145
|
*/
|
|
12724
|
-
function
|
|
12725
|
-
|
|
12726
|
-
|
|
12727
|
-
|
|
12728
|
-
|
|
12729
|
-
|
|
12730
|
-
|
|
12731
|
-
|
|
12732
|
-
|
|
12733
|
-
|
|
12734
|
-
|
|
12735
|
-
|
|
12736
|
-
|
|
12737
|
-
|
|
12738
|
-
|
|
12739
|
-
|
|
12740
|
-
|
|
12741
|
-
|
|
12742
|
-
|
|
12743
|
-
|
|
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;
|
|
12744
13176
|
}
|
|
12745
13177
|
/**
|
|
12746
|
-
*
|
|
12747
|
-
*
|
|
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.
|
|
12748
13180
|
*
|
|
12749
|
-
* @param
|
|
12750
|
-
* @returns The WebGL program for a texture limit.
|
|
13181
|
+
* @param element - The element.
|
|
12751
13182
|
*/
|
|
12752
|
-
function
|
|
12753
|
-
const
|
|
12754
|
-
|
|
12755
|
-
|
|
12756
|
-
name: TEXT_BIT_NAME,
|
|
12757
|
-
fragment: {
|
|
12758
|
-
...base.fragment,
|
|
12759
|
-
main: SINGLE_ALPHA_STEP_GL
|
|
12760
|
-
}
|
|
12761
|
-
};
|
|
12762
|
-
return cachedBy((maxTextures) => pixi.compileHighShaderGlProgram({
|
|
12763
|
-
name: PROGRAM_NAME,
|
|
12764
|
-
bits: [
|
|
12765
|
-
pixi.colorBitGl,
|
|
12766
|
-
pixi.generateTextureBatchBitGl(maxTextures),
|
|
12767
|
-
textBit,
|
|
12768
|
-
pixi.mSDFBitGl,
|
|
12769
|
-
pixi.roundPixelsBitGl
|
|
12770
|
-
]
|
|
12771
|
-
}));
|
|
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;
|
|
12772
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";
|
|
12773
13192
|
/**
|
|
12774
|
-
*
|
|
12775
|
-
* Pixi's
|
|
12776
|
-
*
|
|
12777
|
-
*
|
|
12778
|
-
*
|
|
12779
|
-
*
|
|
12780
|
-
|
|
12781
|
-
|
|
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.
|
|
12782
13216
|
*
|
|
12783
13217
|
* @param pixi - The loaded Pixi module.
|
|
12784
13218
|
* @returns The pipe class.
|
|
12785
13219
|
*/
|
|
12786
13220
|
function createSdfTextPipe(pixi) {
|
|
12787
|
-
|
|
12788
|
-
|
|
12789
|
-
/** Pixi's bitmap text pipe, with an SDF shader that applies the alpha once. */
|
|
12790
|
-
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 {
|
|
12791
13223
|
static extension = pixi.BitmapTextPipe.extension;
|
|
12792
|
-
/** The text whose distance Pixi writes now, read by the uniforms of every text of this pipe. */
|
|
12793
|
-
writing = { text: void 0 };
|
|
12794
13224
|
/**
|
|
12795
|
-
*
|
|
12796
|
-
*
|
|
12797
|
-
*
|
|
12798
|
-
*
|
|
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.
|
|
12799
13229
|
*/
|
|
12800
|
-
|
|
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
|
+
}
|
|
12801
13242
|
/**
|
|
12802
|
-
* Adds a text to the instructions as Pixi does,
|
|
13243
|
+
* Adds a text to the instructions as Pixi does, its glyph edge measured first.
|
|
12803
13244
|
*
|
|
12804
13245
|
* @param text - The text.
|
|
12805
13246
|
* @param instructionSet - The instructions of its render group.
|
|
12806
13247
|
*/
|
|
12807
13248
|
addRenderable(text, instructionSet) {
|
|
12808
|
-
this.
|
|
12809
|
-
this.laying = text;
|
|
13249
|
+
this.measure(text);
|
|
12810
13250
|
super.addRenderable(text, instructionSet);
|
|
12811
|
-
this.laying = void 0;
|
|
12812
|
-
this.writing.text = void 0;
|
|
12813
13251
|
}
|
|
12814
13252
|
/**
|
|
12815
|
-
* Updates a text as Pixi does,
|
|
13253
|
+
* Updates a text as Pixi does, its glyph edge measured first.
|
|
12816
13254
|
*
|
|
12817
13255
|
* @param text - The text.
|
|
12818
13256
|
*/
|
|
12819
13257
|
updateRenderable(text) {
|
|
12820
|
-
this.
|
|
13258
|
+
this.measure(text);
|
|
12821
13259
|
super.updateRenderable(text);
|
|
12822
|
-
this.writing.text = void 0;
|
|
12823
13260
|
}
|
|
12824
13261
|
/**
|
|
12825
|
-
*
|
|
12826
|
-
* else a new one.
|
|
13262
|
+
* No shader: Pixi then leaves the text's context without one, and batchable.
|
|
12827
13263
|
*
|
|
12828
|
-
* @returns
|
|
13264
|
+
* @returns `null`.
|
|
12829
13265
|
*/
|
|
12830
13266
|
getSdfShader() {
|
|
12831
|
-
|
|
12832
|
-
|
|
12833
|
-
|
|
12834
|
-
|
|
12835
|
-
|
|
12836
|
-
|
|
12837
|
-
|
|
12838
|
-
|
|
12839
|
-
|
|
12840
|
-
|
|
12841
|
-
resources: {
|
|
12842
|
-
localUniforms: createLocalUniforms(pixi, density, this.writing),
|
|
12843
|
-
batchSamplers: pixi.getBatchSamplersUniformGroup(maxTextures)
|
|
12844
|
-
}
|
|
12845
|
-
});
|
|
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);
|
|
12846
13277
|
}
|
|
12847
13278
|
}
|
|
12848
|
-
return
|
|
13279
|
+
return SdfTextPipe;
|
|
12849
13280
|
}
|
|
12850
13281
|
/**
|
|
12851
|
-
*
|
|
12852
|
-
*
|
|
12853
|
-
* afterwards draws
|
|
12854
|
-
*
|
|
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.
|
|
12855
13288
|
*
|
|
12856
13289
|
* @param pixi - The loaded Pixi module.
|
|
12857
13290
|
* @returns The remover.
|
|
12858
|
-
* @throws {TypeError} When the module has no `BitmapTextPipe` to subclass.
|
|
12859
|
-
*/
|
|
12860
|
-
function
|
|
12861
|
-
const
|
|
12862
|
-
|
|
12863
|
-
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);
|
|
12864
13299
|
return () => {
|
|
12865
|
-
pixi.extensions.remove(
|
|
12866
|
-
pixi.extensions.add(pixi.BitmapTextPipe);
|
|
13300
|
+
pixi.extensions.remove(textPipe, batchPipe);
|
|
13301
|
+
pixi.extensions.add(pixi.BitmapTextPipe, pixi.BatcherPipe);
|
|
12867
13302
|
};
|
|
12868
13303
|
}
|
|
12869
13304
|
//#endregion
|
|
@@ -13000,26 +13435,27 @@ function installCounting$1(ctx, pixi, deps) {
|
|
|
13000
13435
|
} else installPasses(ctx, pixi, deps.installPasses);
|
|
13001
13436
|
}
|
|
13002
13437
|
/**
|
|
13003
|
-
*
|
|
13004
|
-
*
|
|
13005
|
-
*
|
|
13006
|
-
* 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.
|
|
13007
13443
|
*
|
|
13008
13444
|
* @param ctx - Domain context of the renderer plugin.
|
|
13009
13445
|
* @param pixi - The module the application is built from.
|
|
13010
13446
|
*/
|
|
13011
|
-
function
|
|
13447
|
+
function installTextBatch(ctx, pixi) {
|
|
13012
13448
|
const state = ctx.state.host;
|
|
13013
13449
|
if (state.uninstallSdfText !== void 0) return;
|
|
13014
13450
|
try {
|
|
13015
|
-
state.uninstallSdfText =
|
|
13451
|
+
state.uninstallSdfText = installSdfText(pixi);
|
|
13016
13452
|
} catch (error) {
|
|
13017
13453
|
ctx.log.warn("renderer: distance-field text applies its alpha twice", { error });
|
|
13018
13454
|
}
|
|
13019
13455
|
}
|
|
13020
13456
|
/**
|
|
13021
13457
|
* Swaps Pixi's render-group pipe for the renderer's, which binds a filter's input again after a group cached as a
|
|
13022
|
-
* 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
|
|
13023
13459
|
* host state, in every build. A Pixi without the pieces draws on with its own pipe, with a warning.
|
|
13024
13460
|
*
|
|
13025
13461
|
* @param ctx - Domain context of the renderer plugin.
|
|
@@ -13053,8 +13489,8 @@ async function createApplication(ctx, mount, deps) {
|
|
|
13053
13489
|
const pixi = loaded ?? await ctx.config.loadPixi();
|
|
13054
13490
|
if (typeof __MOKU_GAME_AGENT__ !== "undefined" && __MOKU_GAME_AGENT__ && loaded === void 0) startupMark("moku:pixi-end");
|
|
13055
13491
|
state.pixi = pixi;
|
|
13492
|
+
installTextBatch(ctx, pixi);
|
|
13056
13493
|
installCounting$1(ctx, pixi, deps);
|
|
13057
|
-
installSdfText(ctx, pixi);
|
|
13058
13494
|
installRenderGroup(ctx, pixi);
|
|
13059
13495
|
state.resolution.dpr = devicePixels();
|
|
13060
13496
|
state.resolution.cap = ctx.config.maxResolution;
|
|
@@ -13099,7 +13535,7 @@ function destroyApplication(state) {
|
|
|
13099
13535
|
/**
|
|
13100
13536
|
* Undoes everything `init` created. Works on the state alone, because `onStop` has no context.
|
|
13101
13537
|
* `ready` drops first, so a device that goes away during the teardown is ignored. The draw
|
|
13102
|
-
* counter, the
|
|
13538
|
+
* counter, the batched text and the render-group pipe swap Pixi's own classes back, so the next application on
|
|
13103
13539
|
* the page starts from them. The pool trim starts over and its listeners go; Pixi's pool itself is left.
|
|
13104
13540
|
*
|
|
13105
13541
|
* @param state - The host branch of the plugin state.
|
|
@@ -14018,8 +14454,9 @@ function endLoan(inputs) {
|
|
|
14018
14454
|
* @returns The slot and its inputs, or `undefined`.
|
|
14019
14455
|
*/
|
|
14020
14456
|
function firstInputs(slots) {
|
|
14021
|
-
for (
|
|
14022
|
-
|
|
14457
|
+
for (let index = 0; index < slots.length; index += 1) {
|
|
14458
|
+
const slot = slots[index];
|
|
14459
|
+
if (slot === void 0 || !slot.filter.enabled) continue;
|
|
14023
14460
|
return slot.inputs === void 0 ? void 0 : {
|
|
14024
14461
|
slot,
|
|
14025
14462
|
inputs: slot.inputs
|
|
@@ -14256,7 +14693,11 @@ function renderStage3(kctx, pixi, matrices, record, now) {
|
|
|
14256
14693
|
key[KEY.s3Tx] = now.tx;
|
|
14257
14694
|
key[KEY.s3Ty] = now.ty;
|
|
14258
14695
|
let passes = 1;
|
|
14259
|
-
|
|
14696
|
+
const slots = record.slots;
|
|
14697
|
+
for (let index = 0; index < slots.length; index += 1) {
|
|
14698
|
+
const slot = slots[index];
|
|
14699
|
+
if (slot?.filter.enabled === true) passes += slot.passes;
|
|
14700
|
+
}
|
|
14260
14701
|
kctx.state.flushPasses += passes;
|
|
14261
14702
|
return true;
|
|
14262
14703
|
}
|
|
@@ -14662,7 +15103,8 @@ function createKeptState(config) {
|
|
|
14662
15103
|
warnedBudget: false,
|
|
14663
15104
|
verifySkipped: 0,
|
|
14664
15105
|
bypassed: false,
|
|
14665
|
-
flushPasses: 0
|
|
15106
|
+
flushPasses: 0,
|
|
15107
|
+
calm: false
|
|
14666
15108
|
};
|
|
14667
15109
|
}
|
|
14668
15110
|
/**
|
|
@@ -14798,11 +15240,12 @@ function readStack(slots, resolution, into) {
|
|
|
14798
15240
|
let enabled = 0;
|
|
14799
15241
|
let rs = Number.POSITIVE_INFINITY;
|
|
14800
15242
|
let padding = 0;
|
|
14801
|
-
for (
|
|
14802
|
-
|
|
15243
|
+
for (let index = 0; index < slots.length; index += 1) {
|
|
15244
|
+
const filter = slots[index]?.filter;
|
|
15245
|
+
if (filter === void 0 || !filter.enabled) continue;
|
|
14803
15246
|
enabled += 1;
|
|
14804
|
-
rs = Math.min(rs, resolutionOf(
|
|
14805
|
-
padding +=
|
|
15247
|
+
rs = Math.min(rs, resolutionOf(filter, resolution));
|
|
15248
|
+
padding += filter.padding;
|
|
14806
15249
|
}
|
|
14807
15250
|
into.enabled = enabled;
|
|
14808
15251
|
into.rs = enabled === 0 ? resolution : rs;
|
|
@@ -14834,8 +15277,9 @@ function holdsFilters(node) {
|
|
|
14834
15277
|
* @returns True when they do.
|
|
14835
15278
|
*/
|
|
14836
15279
|
function plainFilters(slots) {
|
|
14837
|
-
for (
|
|
14838
|
-
const
|
|
15280
|
+
for (let index = 0; index < slots.length; index += 1) {
|
|
15281
|
+
const filter = slots[index]?.filter;
|
|
15282
|
+
if (filter === void 0) continue;
|
|
14839
15283
|
if (filter.enabled && (filter.blendRequired || !plainBlend(filter.blendMode))) return false;
|
|
14840
15284
|
}
|
|
14841
15285
|
return true;
|
|
@@ -15126,16 +15570,18 @@ function neededBytes(record, now, box) {
|
|
|
15126
15570
|
return bytes;
|
|
15127
15571
|
}
|
|
15128
15572
|
/**
|
|
15129
|
-
* Hands the filters of a live record from its top to its stand-in, before stage 1 renders the top without them
|
|
15573
|
+
* Hands the filters of a live record from its top to its stand-in, before stage 1 renders the top without them: the
|
|
15574
|
+
* enabled ones, as `sync` hangs them (E3).
|
|
15130
15575
|
*
|
|
15131
15576
|
* @param kctx - The kept context.
|
|
15132
15577
|
* @param pixi - The module that draws.
|
|
15133
15578
|
* @param record - The record.
|
|
15579
|
+
* @param view - Its view.
|
|
15134
15580
|
*/
|
|
15135
|
-
function handFilters(kctx, pixi, record) {
|
|
15581
|
+
function handFilters(kctx, pixi, record, view) {
|
|
15136
15582
|
const standIn = standInOf(pixi, record);
|
|
15137
15583
|
kctx.sync.clearFilters(record.top);
|
|
15138
|
-
|
|
15584
|
+
kctx.sync.applyFilters(view, filtersOf$1(kctx.ctx.state.sync, record.entity), noMark$1, standIn);
|
|
15139
15585
|
}
|
|
15140
15586
|
/**
|
|
15141
15587
|
* Writes the take keys of a record: the linear part, the stack resolution and the translation of this frame, and the
|
|
@@ -15240,7 +15686,7 @@ function isEmptyBounds(bounds) {
|
|
|
15240
15686
|
*/
|
|
15241
15687
|
function renderStages(kctx, pixi, matrices, record, view, now) {
|
|
15242
15688
|
const live = record.mode === "live";
|
|
15243
|
-
if (live) handFilters(kctx, pixi, record);
|
|
15689
|
+
if (live) handFilters(kctx, pixi, record, view);
|
|
15244
15690
|
const first = firstInputs(record.slots);
|
|
15245
15691
|
let ok = renderStage1(kctx, pixi, matrices, record);
|
|
15246
15692
|
if (ok && first !== void 0) renderStage2(kctx, pixi, matrices, record, {
|
|
@@ -15538,6 +15984,23 @@ function drawingChanged(kctx, record, before) {
|
|
|
15538
15984
|
return before.mode !== record.mode || before.passes !== kctx.state.flushPasses || !Object.is(before.qtx, record.key[KEY.qtx]) || !Object.is(before.qty, record.key[KEY.qty]) || before.shown !== quadShown(record);
|
|
15539
15985
|
}
|
|
15540
15986
|
/**
|
|
15987
|
+
* The frame of a kept record in a calm frame (no mark, no whole-frame reason) that owes nothing: no dirty flag, no
|
|
15988
|
+
* settle retake, no churn, not bypassed. Its step would find every key as it stands and its quad where it is, so it is
|
|
15989
|
+
* counted as drawn from its picture and left alone (E5).
|
|
15990
|
+
*
|
|
15991
|
+
* @param kctx - The kept context.
|
|
15992
|
+
* @param record - The record.
|
|
15993
|
+
* @returns True when the record was drawn as it stands; false when it is to be stepped.
|
|
15994
|
+
*/
|
|
15995
|
+
function stepCalm(kctx, record) {
|
|
15996
|
+
const { state } = kctx;
|
|
15997
|
+
if (!state.calm || state.bypassed || record.mode !== "kept" || record.dirtyS1 || record.dirtyS2 || record.dirtyS3 || record.settle || record.churn !== 0) return false;
|
|
15998
|
+
record.lastDrawn = state.frame;
|
|
15999
|
+
state.drawnPictures += 1;
|
|
16000
|
+
record.quiet += 1;
|
|
16001
|
+
return true;
|
|
16002
|
+
}
|
|
16003
|
+
/**
|
|
15541
16004
|
* One drawn frame of a record, inside the flush: eligibility, the keys of this frame, the policy of its life, the quad
|
|
15542
16005
|
* of a kept one; the dirty flags are cleared.
|
|
15543
16006
|
*
|
|
@@ -15639,7 +16102,7 @@ function bypassRecord(kctx, record, view, live) {
|
|
|
15639
16102
|
return;
|
|
15640
16103
|
}
|
|
15641
16104
|
kctx.sync.clearFilters(record.top);
|
|
15642
|
-
if (record.standIn !== void 0)
|
|
16105
|
+
if (record.standIn !== void 0) kctx.sync.applyFilters(view, filters, noMark$1, record.standIn);
|
|
15643
16106
|
kctx.sync.swapHidden(kctx.hidden, view, "kept", true);
|
|
15644
16107
|
if (record.quad !== void 0) record.quad.visible = record.top.visible && !kctx.sync.hiddenBy(view, "alpha") && !kctx.sync.hiddenBy(view, "cover");
|
|
15645
16108
|
}
|
|
@@ -15849,7 +16312,7 @@ function stepRecords(kctx, pixi, matrices) {
|
|
|
15849
16312
|
continue;
|
|
15850
16313
|
}
|
|
15851
16314
|
const view = kctx.ctx.state.sync.views.get(record.entity);
|
|
15852
|
-
if (view !== void 0) stepRecord(kctx, pixi, matrices, record, view);
|
|
16315
|
+
if (view !== void 0 && !stepCalm(kctx, record)) stepRecord(kctx, pixi, matrices, record, view);
|
|
15853
16316
|
}
|
|
15854
16317
|
}
|
|
15855
16318
|
/**
|
|
@@ -15902,9 +16365,10 @@ function createKeptApi(ctx, deps) {
|
|
|
15902
16365
|
const bypass = (on) => {
|
|
15903
16366
|
if (state.bypassed === on) return;
|
|
15904
16367
|
state.bypassed = on;
|
|
15905
|
-
for (
|
|
15906
|
-
const
|
|
15907
|
-
|
|
16368
|
+
for (let index = 0; index < state.records.length; index += 1) {
|
|
16369
|
+
const record = state.records[index];
|
|
16370
|
+
const view = record === void 0 ? void 0 : viewOf(record);
|
|
16371
|
+
if (record?.mode === "kept" && view !== void 0) bypassRecord(kctx, record, view, on);
|
|
15908
16372
|
}
|
|
15909
16373
|
};
|
|
15910
16374
|
/**
|
|
@@ -15913,7 +16377,9 @@ function createKeptApi(ctx, deps) {
|
|
|
15913
16377
|
*/
|
|
15914
16378
|
const sortRecords = () => {
|
|
15915
16379
|
let changed = state.unsorted;
|
|
15916
|
-
for (
|
|
16380
|
+
for (let index = 0; index < state.records.length; index += 1) {
|
|
16381
|
+
const record = state.records[index];
|
|
16382
|
+
if (record === void 0) continue;
|
|
15917
16383
|
const depth = depthOf(record.top);
|
|
15918
16384
|
if (depth !== record.depth) {
|
|
15919
16385
|
record.depth = depth;
|
|
@@ -15982,6 +16448,7 @@ function createKeptApi(ctx, deps) {
|
|
|
15982
16448
|
if (wholes.has("debug") || wholes.has("restore")) for (const record of state.records) record.dirtyS1 = true;
|
|
15983
16449
|
const marked = deps.demand.marked();
|
|
15984
16450
|
const loose = deps.demand.loose();
|
|
16451
|
+
state.calm = wholes.size === 0 && marked.size === 0 && loose.size === 0;
|
|
15985
16452
|
for (let index = 0; index < marked.size; index += 1) collectOne(marked.at(index));
|
|
15986
16453
|
for (let index = 0; index < loose.size; index += 1) collectOne(loose.at(index));
|
|
15987
16454
|
},
|
|
@@ -15995,6 +16462,7 @@ function createKeptApi(ctx, deps) {
|
|
|
15995
16462
|
if (state.on) {
|
|
15996
16463
|
sortRecords();
|
|
15997
16464
|
stepRecords(kctx, pixi, matrices);
|
|
16465
|
+
state.calm = false;
|
|
15998
16466
|
} else for (const record of state.records) if (record.mode === "kept") live(record);
|
|
15999
16467
|
flushRanges(kctx);
|
|
16000
16468
|
},
|
|
@@ -16379,7 +16847,7 @@ const ALL_SECTIONS = Object.freeze({
|
|
|
16379
16847
|
* @param next - The switches asked for; `undefined` for none.
|
|
16380
16848
|
* @returns The sections, frozen; `base` itself when nothing differs.
|
|
16381
16849
|
*/
|
|
16382
|
-
function switched(base, next) {
|
|
16850
|
+
function switched$1(base, next) {
|
|
16383
16851
|
if (next === void 0) return base;
|
|
16384
16852
|
const sections = { ...base };
|
|
16385
16853
|
let changed = false;
|
|
@@ -18211,7 +18679,7 @@ function createSinks(draws) {
|
|
|
18211
18679
|
*/
|
|
18212
18680
|
function createPerfState(config, sinks) {
|
|
18213
18681
|
const events = createEventRing();
|
|
18214
|
-
const sections = switched(ALL_SECTIONS, config.sections);
|
|
18682
|
+
const sections = switched$1(ALL_SECTIONS, config.sections);
|
|
18215
18683
|
sinks.sections = bitsOf(sections);
|
|
18216
18684
|
sinks.sectionsNext = sinks.sections;
|
|
18217
18685
|
return {
|
|
@@ -20959,7 +21427,7 @@ function stopPerf(perf, sinks) {
|
|
|
20959
21427
|
*/
|
|
20960
21428
|
function switchSections(pc, next) {
|
|
20961
21429
|
const { perf, sinks } = pc;
|
|
20962
|
-
const sections = switched(perf.sections, next);
|
|
21430
|
+
const sections = switched$1(perf.sections, next);
|
|
20963
21431
|
if (sections === perf.sections) return sections;
|
|
20964
21432
|
perf.sections = sections;
|
|
20965
21433
|
sinks.sectionsNext = bitsOf(sections);
|
|
@@ -20980,7 +21448,7 @@ const READ_ONLY_SECTIONS = "[game] monitor.sections switches the sections only o
|
|
|
20980
21448
|
*/
|
|
20981
21449
|
function refuseSections(pc, next) {
|
|
20982
21450
|
const { sections } = pc.perf;
|
|
20983
|
-
if (switched(sections, next) === sections) return sections;
|
|
21451
|
+
if (switched$1(sections, next) === sections) return sections;
|
|
20984
21452
|
throw Object.assign(/* @__PURE__ */ new Error(READ_ONLY_SECTIONS), { reason: "read-only" });
|
|
20985
21453
|
}
|
|
20986
21454
|
//#endregion
|
|
@@ -21545,6 +22013,23 @@ function hiddenBy(view, reason) {
|
|
|
21545
22013
|
return (view[maskOf(view, reason)] & BITS[reason]) !== 0;
|
|
21546
22014
|
}
|
|
21547
22015
|
/**
|
|
22016
|
+
* Whether a node on the chain of a view is drawn at alpha 0, read from the engine's records (E4): Pixi's `alpha` getter
|
|
22017
|
+
* hands its double back boxed at every read of a walk of `shown`. The top of a built-in visual, its wrapper or its
|
|
22018
|
+
* object, draws at alpha 0 exactly when the `"alpha"` reason hides it, and so does an adapter's own object. A node of no
|
|
22019
|
+
* view on a chain is the engine's (a clip container, a layer, the root), never faded. A game's object and the wrapper
|
|
22020
|
+
* of an adapter carry an alpha the engine does not hold: Pixi's is read.
|
|
22021
|
+
*
|
|
22022
|
+
* @param state - The sync state.
|
|
22023
|
+
* @param node - A node on the chain of a view, below the stage.
|
|
22024
|
+
* @returns True at alpha 0.
|
|
22025
|
+
*/
|
|
22026
|
+
function transparentNode(state, node) {
|
|
22027
|
+
const view = state.viewOfTop.get(node);
|
|
22028
|
+
if (view === void 0) return false;
|
|
22029
|
+
if (view.kind === "Display" || view.kind === "adapter" && node === view.wrapper) return node.alpha <= 0;
|
|
22030
|
+
return hiddenBy(view, "alpha");
|
|
22031
|
+
}
|
|
22032
|
+
/**
|
|
21548
22033
|
* Tells whether a node is hidden, and by these reasons alone: spec 23's eligibility of a picture whose ancestor is in
|
|
21549
22034
|
* an overlay's range (`"freeze"`) or is itself a kept top drawn through its quad (`"kept"`).
|
|
21550
22035
|
*
|
|
@@ -21678,6 +22163,8 @@ function clearAdapters(state) {
|
|
|
21678
22163
|
}
|
|
21679
22164
|
//#endregion
|
|
21680
22165
|
//#region src/plugins/renderer/sync/filters.ts
|
|
22166
|
+
/** What a display object without filters holds: one shared list, plain, so a still frame's compare makes none. */
|
|
22167
|
+
const NO_FILTERS = [];
|
|
21681
22168
|
/**
|
|
21682
22169
|
* The filters a display object holds now. Pixi answers `undefined` before the first write and
|
|
21683
22170
|
* `null` after a clear, which its getter type does not say.
|
|
@@ -21686,10 +22173,11 @@ function clearAdapters(state) {
|
|
|
21686
22173
|
* @returns The filters, empty when there are none.
|
|
21687
22174
|
*/
|
|
21688
22175
|
function heldFilters(target) {
|
|
21689
|
-
return target.filters ??
|
|
22176
|
+
return target.filters ?? NO_FILTERS;
|
|
21690
22177
|
}
|
|
21691
22178
|
/**
|
|
21692
|
-
* Tells whether a display object holds exactly these
|
|
22179
|
+
* Tells whether a display object holds exactly the enabled filters of these slots, in their order (E3): Pixi is handed
|
|
22180
|
+
* no disabled filter, which draws nothing but costs a filter push and pop, two batch breaks, every frame.
|
|
21693
22181
|
*
|
|
21694
22182
|
* @param target - The display object.
|
|
21695
22183
|
* @param slots - The slots to compare with.
|
|
@@ -21697,7 +22185,37 @@ function heldFilters(target) {
|
|
|
21697
22185
|
*/
|
|
21698
22186
|
function holds(target, slots) {
|
|
21699
22187
|
const held = heldFilters(target);
|
|
21700
|
-
|
|
22188
|
+
let at = 0;
|
|
22189
|
+
for (let index = 0; index < slots.length; index += 1) {
|
|
22190
|
+
const filter = slots[index]?.filter;
|
|
22191
|
+
if (filter === void 0 || !filter.enabled) continue;
|
|
22192
|
+
if (held[at] !== filter) return false;
|
|
22193
|
+
at += 1;
|
|
22194
|
+
}
|
|
22195
|
+
return at === held.length;
|
|
22196
|
+
}
|
|
22197
|
+
/**
|
|
22198
|
+
* The enabled filters of slots, in their order: what Pixi is handed.
|
|
22199
|
+
*
|
|
22200
|
+
* @param slots - The slots.
|
|
22201
|
+
* @returns A new list, or `undefined` when no filter is enabled.
|
|
22202
|
+
*/
|
|
22203
|
+
function enabledFilters(slots) {
|
|
22204
|
+
let enabled;
|
|
22205
|
+
for (const slot of slots) if (slot.filter.enabled) {
|
|
22206
|
+
enabled ??= [];
|
|
22207
|
+
enabled.push(slot.filter);
|
|
22208
|
+
}
|
|
22209
|
+
return enabled;
|
|
22210
|
+
}
|
|
22211
|
+
/**
|
|
22212
|
+
* Writes the enabled filters of slots onto a display object, or clears them when none is enabled.
|
|
22213
|
+
*
|
|
22214
|
+
* @param target - The display object.
|
|
22215
|
+
* @param slots - The slots.
|
|
22216
|
+
*/
|
|
22217
|
+
function writeFilters(target, slots) {
|
|
22218
|
+
target.filters = enabledFilters(slots) ?? null;
|
|
21701
22219
|
}
|
|
21702
22220
|
/**
|
|
21703
22221
|
* Takes every filter off a display object. Nothing is written when it holds none: Pixi copies the
|
|
@@ -21711,8 +22229,10 @@ function clearFilters(target) {
|
|
|
21711
22229
|
/**
|
|
21712
22230
|
* Hangs the filters on a view: on its wrapper when it has one, so they cover the entity's whole
|
|
21713
22231
|
* subtree, otherwise on the object; on the stand-in of its kept picture while it has one
|
|
21714
|
-
* (`kept.filterTarget`, spec 23 P9.1).
|
|
21715
|
-
*
|
|
22232
|
+
* (`kept.filterTarget`, spec 23 P9.1). Only the enabled ones: a disabled filter draws nothing, and Pixi would push and
|
|
22233
|
+
* pop it around the view every frame (E3); a view whose filters are all disabled holds none. Written only when they
|
|
22234
|
+
* differ from what the target holds, and `before` runs once just before that write: the caller marks the entity there.
|
|
22235
|
+
* A filter switched on or off later is written by `applySwitched` in the frame it was switched.
|
|
21716
22236
|
*
|
|
21717
22237
|
* @param view - The view.
|
|
21718
22238
|
* @param slots - The filters in draw order; empty clears them.
|
|
@@ -21722,7 +22242,7 @@ function clearFilters(target) {
|
|
|
21722
22242
|
function applyFilters(view, slots, before, target = view.wrapper ?? view.object) {
|
|
21723
22243
|
if (holds(target, slots)) return;
|
|
21724
22244
|
before();
|
|
21725
|
-
target
|
|
22245
|
+
writeFilters(target, slots);
|
|
21726
22246
|
}
|
|
21727
22247
|
/**
|
|
21728
22248
|
* The filters kept for an entity.
|
|
@@ -21815,6 +22335,45 @@ function moveFilters(state, entity, view, before, kept) {
|
|
|
21815
22335
|
clearFilters(view.object);
|
|
21816
22336
|
applyFilters(view, slots, before);
|
|
21817
22337
|
}
|
|
22338
|
+
/** What the walk of the switched filters reads, set before the walk: one at a time, no closure per frame. */
|
|
22339
|
+
const switched = {
|
|
22340
|
+
sync: void 0,
|
|
22341
|
+
kept: void 0,
|
|
22342
|
+
wrote: false
|
|
22343
|
+
};
|
|
22344
|
+
/**
|
|
22345
|
+
* Writes the enabled filters of one view again when one of them was switched on or off since they were handed to Pixi.
|
|
22346
|
+
*
|
|
22347
|
+
* @param slots - The filters of the view.
|
|
22348
|
+
* @param entity - The entity.
|
|
22349
|
+
*/
|
|
22350
|
+
function applySwitchedOne(slots, entity) {
|
|
22351
|
+
const { sync, kept } = switched;
|
|
22352
|
+
const view = sync?.views.get(entity);
|
|
22353
|
+
if (view === void 0 || kept === void 0) return;
|
|
22354
|
+
const target = kept.filterTarget(view);
|
|
22355
|
+
if (holds(target, slots)) return;
|
|
22356
|
+
writeFilters(target, slots);
|
|
22357
|
+
switched.wrote = true;
|
|
22358
|
+
}
|
|
22359
|
+
/**
|
|
22360
|
+
* Hands Pixi the filters of every view whose filters were switched on or off since they were written (E3), before the
|
|
22361
|
+
* frame is drawn, so a switch is drawn in the frame it was made. No mark: the writer of a switch tells the renderer
|
|
22362
|
+
* with `invalidate` first. A write moves `passesVersion` on: the paddings the walks of `shown` read changed (E4).
|
|
22363
|
+
*
|
|
22364
|
+
* @param state - The sync branch of the plugin state.
|
|
22365
|
+
* @param kept - The kept module: where each view's filters hang.
|
|
22366
|
+
*/
|
|
22367
|
+
function applySwitched(state, kept) {
|
|
22368
|
+
if (state.filters.size === 0) return;
|
|
22369
|
+
switched.sync = state;
|
|
22370
|
+
switched.kept = kept;
|
|
22371
|
+
switched.wrote = false;
|
|
22372
|
+
state.filters.forEach(applySwitchedOne);
|
|
22373
|
+
if (switched.wrote) state.passesVersion += 1;
|
|
22374
|
+
switched.sync = void 0;
|
|
22375
|
+
switched.kept = void 0;
|
|
22376
|
+
}
|
|
21818
22377
|
/**
|
|
21819
22378
|
* Forgets the filters and the filter area of an entity whose view left, only when the entity itself is gone. A live
|
|
21820
22379
|
* entity keeps them for its next view, in this frame, as a `ui` element that swaps its shape for a
|
|
@@ -25057,6 +25616,7 @@ function applyHud(sctx, entity, view, factor) {
|
|
|
25057
25616
|
if (view.hud === factor) return;
|
|
25058
25617
|
markView(sctx, entity);
|
|
25059
25618
|
view.hud = factor;
|
|
25619
|
+
sctx.ctx.state.sync.passesVersion += 1;
|
|
25060
25620
|
if (ownsAlpha(view)) {
|
|
25061
25621
|
applyAlpha(sctx, entity, view, componentAlpha(sctx, entity, view));
|
|
25062
25622
|
return;
|
|
@@ -25812,6 +26372,7 @@ function dropView(sctx, entity) {
|
|
|
25812
26372
|
state.views.delete(entity);
|
|
25813
26373
|
if (state.filters.has(entity) || state.areas.has(entity)) state.viewlessEntries = true;
|
|
25814
26374
|
state.orphans.delete(entity);
|
|
26375
|
+
state.held.delete(entity);
|
|
25815
26376
|
state.entityOf.delete(view.object);
|
|
25816
26377
|
state.viewOfTop.delete(view.wrapper ?? view.object);
|
|
25817
26378
|
untrackKey(state, entity, view.textureKey);
|
|
@@ -26034,9 +26595,11 @@ function renderFrame(ctx, modules) {
|
|
|
26034
26595
|
if (modules.viewport.applyPending()) {
|
|
26035
26596
|
const root = modules.sync.root();
|
|
26036
26597
|
modules.monitor.changed("resize");
|
|
26598
|
+
ctx.state.sync.passesVersion += 1;
|
|
26037
26599
|
if (root !== void 0) modules.viewport.apply(root);
|
|
26038
26600
|
modules.demand.whole("resize");
|
|
26039
26601
|
}
|
|
26602
|
+
modules.sync.stepFilters();
|
|
26040
26603
|
modules.sync.stepOverlays();
|
|
26041
26604
|
modules.sync.drainOverlayEvents(modules.monitor);
|
|
26042
26605
|
modules.kept.collect();
|
|
@@ -26232,19 +26795,22 @@ function countUpload(sinks, source) {
|
|
|
26232
26795
|
/**
|
|
26233
26796
|
* Builds the seven counting subclasses from the module object. They are made at call time,
|
|
26234
26797
|
* because the base classes arrive with the lazily loaded module. Each keeps the base's `static
|
|
26235
|
-
* 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.
|
|
26236
26801
|
*
|
|
26237
26802
|
* @param pixi - The loaded Pixi module.
|
|
26238
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.
|
|
26239
26805
|
* @returns The seven counting classes (BatcherPipe among them).
|
|
26240
26806
|
*/
|
|
26241
|
-
function createCountingClasses(pixi, sinks) {
|
|
26807
|
+
function createCountingClasses(pixi, sinks, batcherPipe = pixi.BatcherPipe) {
|
|
26242
26808
|
const counter = sinks.draws;
|
|
26243
26809
|
/**
|
|
26244
26810
|
* Counts every rebuild of a render group's instruction list: Pixi's render group system calls `buildStart` once per
|
|
26245
26811
|
* rebuild and nowhere else. It inherits the static batcher registry, so batchers resolve as before.
|
|
26246
26812
|
*/
|
|
26247
|
-
class CountingBatcherPipe extends
|
|
26813
|
+
class CountingBatcherPipe extends batcherPipe {
|
|
26248
26814
|
static extension = pixi.BatcherPipe.extension;
|
|
26249
26815
|
/**
|
|
26250
26816
|
* Counts the rebuild, in the frame's counter only inside a frame, then begins it.
|
|
@@ -26451,10 +27017,11 @@ function createCountingClasses(pixi, sinks) {
|
|
|
26451
27017
|
*
|
|
26452
27018
|
* @param pixi - The loaded Pixi module.
|
|
26453
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.
|
|
26454
27021
|
* @returns The remover.
|
|
26455
27022
|
*/
|
|
26456
|
-
function installCounting(pixi, sinks) {
|
|
26457
|
-
const counting = createCountingClasses(pixi, sinks);
|
|
27023
|
+
function installCounting(pixi, sinks, batcherPipe = pixi.BatcherPipe) {
|
|
27024
|
+
const counting = createCountingClasses(pixi, sinks, batcherPipe);
|
|
26458
27025
|
const bases = [
|
|
26459
27026
|
pixi.BatcherPipe,
|
|
26460
27027
|
pixi.GpuBatchAdaptor,
|
|
@@ -27796,8 +28363,9 @@ function emptyEntitySet$1() {
|
|
|
27796
28363
|
return /* @__PURE__ */ new Set();
|
|
27797
28364
|
}
|
|
27798
28365
|
/**
|
|
27799
|
-
* Where the walk of `shown` stopped for an entity's view, walked
|
|
27800
|
-
* endless loops
|
|
28366
|
+
* Where the walk of `shown` stopped for an entity's view, walked again only when `passesVersion` moved since (E4): an
|
|
28367
|
+
* entity with several endless loops is walked once, and the loops of a still board are answered from their records
|
|
28368
|
+
* (spec 26 R1.7.3). A record is made at an entity's first ask and written in place after.
|
|
27801
28369
|
*
|
|
27802
28370
|
* @param sctx - Domain context of the sync module.
|
|
27803
28371
|
* @param entity - The entity a loop is about to write.
|
|
@@ -27807,9 +28375,16 @@ function unseenOf(sctx, entity) {
|
|
|
27807
28375
|
const state = sctx.ctx.state.sync;
|
|
27808
28376
|
if (state.root === void 0 || !state.views.has(entity)) return "other";
|
|
27809
28377
|
const known = state.held.get(entity);
|
|
27810
|
-
if (known !== void 0) return known;
|
|
28378
|
+
if (known !== void 0 && known.at === state.passesVersion) return known.answer;
|
|
27811
28379
|
const answer = sctx.deps.demand.unseenBy(entity);
|
|
27812
|
-
state.held.set(entity,
|
|
28380
|
+
if (known === void 0) state.held.set(entity, {
|
|
28381
|
+
answer,
|
|
28382
|
+
at: state.passesVersion
|
|
28383
|
+
});
|
|
28384
|
+
else {
|
|
28385
|
+
known.answer = answer;
|
|
28386
|
+
known.at = state.passesVersion;
|
|
28387
|
+
}
|
|
27813
28388
|
return answer;
|
|
27814
28389
|
}
|
|
27815
28390
|
/**
|
|
@@ -28200,7 +28775,6 @@ function createSyncApi(ctx, deps) {
|
|
|
28200
28775
|
* view. A still frame (nothing changed, nothing waits) returns before any work and makes nothing.
|
|
28201
28776
|
*/
|
|
28202
28777
|
const pass = () => {
|
|
28203
|
-
if (state.held.size > 0) state.held.clear();
|
|
28204
28778
|
if (still()) return;
|
|
28205
28779
|
state.passesVersion += 1;
|
|
28206
28780
|
if (state.removed.size > 0) {
|
|
@@ -28347,6 +28921,7 @@ function createSyncApi(ctx, deps) {
|
|
|
28347
28921
|
layerContainer: (name) => layerContainer(state, name),
|
|
28348
28922
|
layerOf: (entity) => layerOf(sctx, entity),
|
|
28349
28923
|
stepOverlays: () => stepOverlays(sctx),
|
|
28924
|
+
stepFilters: () => applySwitched(state, deps.kept),
|
|
28350
28925
|
drainOverlayEvents: (monitor) => drainOverlayEvents(sctx, monitor),
|
|
28351
28926
|
warmStep: () => warmStep(sctx),
|
|
28352
28927
|
onWarm: (fn) => {
|
|
@@ -28814,14 +29389,14 @@ function createViewportApi(ctx, deps) {
|
|
|
28814
29389
|
/**
|
|
28815
29390
|
* The counting `host` installs before `new Application()`: the seven counting classes (BatcherPipe among them) of
|
|
28816
29391
|
* `monitor` in a dev build and in a debug build, bound to the sinks in the monitor branch of the
|
|
28817
|
-
* state, none in production
|
|
28818
|
-
* 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.
|
|
28819
29394
|
*
|
|
28820
29395
|
* @param ctx - Domain context of the renderer plugin.
|
|
28821
29396
|
* @returns The bound installer, or `undefined` without the dev flag and the agent flag.
|
|
28822
29397
|
*/
|
|
28823
29398
|
function countingOf(ctx) {
|
|
28824
|
-
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;
|
|
28825
29400
|
}
|
|
28826
29401
|
/**
|
|
28827
29402
|
* The writers of `sync` the kept cache draws through: the one owner of `renderable`, the one writer of a view's
|
|
@@ -28861,7 +29436,8 @@ function createModules$1(ctx) {
|
|
|
28861
29436
|
const demand = createDemandApi(ctx, {
|
|
28862
29437
|
host,
|
|
28863
29438
|
views: ctx.state.sync.views,
|
|
28864
|
-
hiddenBy
|
|
29439
|
+
hiddenBy,
|
|
29440
|
+
transparent: (node) => transparentNode(ctx.state.sync, node)
|
|
28865
29441
|
});
|
|
28866
29442
|
const built = {};
|
|
28867
29443
|
const kept = createKeptApi(ctx, {
|
|
@@ -30994,7 +31570,8 @@ function same$1(stored, next) {
|
|
|
30994
31570
|
* every frame all the same.
|
|
30995
31571
|
*
|
|
30996
31572
|
* A frame that moves every field writes the track's own patch, filled in place. A frame that
|
|
30997
|
-
* leaves a field alone
|
|
31573
|
+
* leaves a field alone writes the track's patch of the fields it moves, kept for the next such
|
|
31574
|
+
* frame and built again only when the moved fields change, so no other field is written.
|
|
30998
31575
|
*
|
|
30999
31576
|
* @param actx - Domain context of the anim plugin.
|
|
31000
31577
|
* @param track - The track to write, its normalised time in `clock[1]`.
|
|
@@ -31009,7 +31586,7 @@ function writeFields(actx, track, final) {
|
|
|
31009
31586
|
}
|
|
31010
31587
|
beginTrack(actx, track, stored);
|
|
31011
31588
|
const owned = track.muted();
|
|
31012
|
-
const { names, live, moved, values, seen
|
|
31589
|
+
const { names, live, moved, values, seen } = track;
|
|
31013
31590
|
let fields = 0;
|
|
31014
31591
|
let count = 0;
|
|
31015
31592
|
for (let slot = 0; slot < names.length; slot++) {
|
|
@@ -31029,27 +31606,57 @@ function writeFields(actx, track, final) {
|
|
|
31029
31606
|
continue;
|
|
31030
31607
|
}
|
|
31031
31608
|
seen[slot] = next;
|
|
31032
|
-
patch[field] = next;
|
|
31033
31609
|
moved[slot] = 1;
|
|
31034
31610
|
count++;
|
|
31035
31611
|
}
|
|
31036
31612
|
if (count === 0) return;
|
|
31037
|
-
|
|
31613
|
+
const written = count === fields ? track.patch : partialPatch(track);
|
|
31614
|
+
fillPatch(track, written);
|
|
31615
|
+
ecs.set(track.entity, track.component, written);
|
|
31038
31616
|
}
|
|
31039
31617
|
/**
|
|
31040
|
-
*
|
|
31041
|
-
*
|
|
31618
|
+
* Puts the values of the moved slots into a patch of the track: each moved field gets what the
|
|
31619
|
+
* frame left in `values` for it.
|
|
31042
31620
|
*
|
|
31043
31621
|
* @param track - The track that writes.
|
|
31044
|
-
* @
|
|
31622
|
+
* @param written - Its full patch, or its patch of the moved fields.
|
|
31045
31623
|
*/
|
|
31046
|
-
function
|
|
31047
|
-
const
|
|
31624
|
+
function fillPatch(track, written) {
|
|
31625
|
+
const { names, moved, values } = track;
|
|
31626
|
+
for (let slot = 0; slot < names.length; slot++) {
|
|
31627
|
+
const field = names[slot];
|
|
31628
|
+
if (moved[slot] === 1 && field !== void 0) written[field] = values[slot] ?? 0;
|
|
31629
|
+
}
|
|
31630
|
+
}
|
|
31631
|
+
/**
|
|
31632
|
+
* The patch of a frame that moved only some of the fields of a track: the one the track keeps,
|
|
31633
|
+
* when the frame moved the same fields as the frame it was built for, otherwise a new one.
|
|
31634
|
+
*
|
|
31635
|
+
* @param track - The track that writes, its `moved` flags set by this frame.
|
|
31636
|
+
* @returns The patch of exactly the moved fields; their values are filled after.
|
|
31637
|
+
*/
|
|
31638
|
+
function partialPatch(track) {
|
|
31639
|
+
const { moved, partialMoved, partial } = track;
|
|
31640
|
+
if (partial === void 0) return keepPartial(track);
|
|
31641
|
+
for (let slot = 0; slot < moved.length; slot++) if (moved[slot] !== partialMoved[slot]) return keepPartial(track);
|
|
31642
|
+
return partial;
|
|
31643
|
+
}
|
|
31644
|
+
/**
|
|
31645
|
+
* Builds the patch of the fields the frame moved and keeps it on the track, with the `moved`
|
|
31646
|
+
* flags it was built for. Runs only when the moved fields change.
|
|
31647
|
+
*
|
|
31648
|
+
* @param track - The track that writes.
|
|
31649
|
+
* @returns The new patch, its values still to fill.
|
|
31650
|
+
*/
|
|
31651
|
+
function keepPartial(track) {
|
|
31652
|
+
const built = {};
|
|
31048
31653
|
for (let slot = 0; slot < track.names.length; slot++) {
|
|
31049
31654
|
const field = track.names[slot];
|
|
31050
|
-
if (track.moved[slot] === 1 && field !== void 0)
|
|
31655
|
+
if (track.moved[slot] === 1 && field !== void 0) built[field] = 0;
|
|
31051
31656
|
}
|
|
31052
|
-
|
|
31657
|
+
track.partialMoved.set(track.moved);
|
|
31658
|
+
track.partial = built;
|
|
31659
|
+
return built;
|
|
31053
31660
|
}
|
|
31054
31661
|
/**
|
|
31055
31662
|
* Releases the field bookkeeping of a track that is ending: the fields it owned, its own offsets
|
|
@@ -31289,7 +31896,9 @@ function startTrack$1(actx, entity, component, to, options, muted, driven = fals
|
|
|
31289
31896
|
masked: noMaskedFields(),
|
|
31290
31897
|
keep,
|
|
31291
31898
|
reveals: revealsOf(component, target),
|
|
31292
|
-
patch: { ...target }
|
|
31899
|
+
patch: { ...target },
|
|
31900
|
+
partial: void 0,
|
|
31901
|
+
partialMoved: new Uint8Array(names.length)
|
|
31293
31902
|
};
|
|
31294
31903
|
actx.state.nextId += 1;
|
|
31295
31904
|
if (track.ended) return track;
|
|
@@ -35124,8 +35733,8 @@ function overTargetOf(ctx, node, run) {
|
|
|
35124
35733
|
}
|
|
35125
35734
|
/**
|
|
35126
35735
|
* Builds the scene in one synchronous block: the layers, then the projections of the old scene
|
|
35127
|
-
* out, then the ones of the new scene in,
|
|
35128
|
-
* 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.
|
|
35129
35738
|
*
|
|
35130
35739
|
* @param ctx - Domain context of the plugin.
|
|
35131
35740
|
* @param scene - The scene whose bundles are there.
|
|
@@ -35140,6 +35749,7 @@ function apply(ctx, scene, named) {
|
|
|
35140
35749
|
if (previous !== void 0) projection.unmount(previous.projections);
|
|
35141
35750
|
projection.mount(scene.projections, state.owner);
|
|
35142
35751
|
state.current = scene.id;
|
|
35752
|
+
ctx.deps.world.ecs.setScene(scene.id);
|
|
35143
35753
|
state.named = named;
|
|
35144
35754
|
ctx.deps.assets.sceneBundles(named);
|
|
35145
35755
|
ctx.deps.time.wake();
|
|
@@ -41588,8 +42198,9 @@ function countParticles(ectx) {
|
|
|
41588
42198
|
state.over.particles = above;
|
|
41589
42199
|
}
|
|
41590
42200
|
/**
|
|
41591
|
-
* Builds the `animate` system of the
|
|
41592
|
-
*
|
|
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.
|
|
41593
42204
|
*
|
|
41594
42205
|
* @param ectx - Domain context of the effects plugin.
|
|
41595
42206
|
* @returns The system `world.ecs.system` takes.
|
|
@@ -41605,6 +42216,26 @@ function createParticleSystem(ectx) {
|
|
|
41605
42216
|
if (pixi === void 0) return;
|
|
41606
42217
|
const reducedMotion = ectx.deps.anim.reducedMotion();
|
|
41607
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;
|
|
41608
42239
|
runOrphans(ectx, context.time);
|
|
41609
42240
|
countParticles(ectx);
|
|
41610
42241
|
}
|
|
@@ -41932,7 +42563,7 @@ function warnScreenReads(ectx, entries) {
|
|
|
41932
42563
|
});
|
|
41933
42564
|
}
|
|
41934
42565
|
/**
|
|
41935
|
-
* Opens the world side: the
|
|
42566
|
+
* Opens the world side: the three systems, the hook that retires an emitter's instance, and the two
|
|
41936
42567
|
* hooks per filter kind; then records the filter components that are on entities already.
|
|
41937
42568
|
*
|
|
41938
42569
|
* @param ectx - Domain context of the effects plugin.
|
|
@@ -41940,7 +42571,7 @@ function warnScreenReads(ectx, entries) {
|
|
|
41940
42571
|
*/
|
|
41941
42572
|
function openWorld(ectx, kinds) {
|
|
41942
42573
|
const ecs = ectx.deps.world.ecs;
|
|
41943
|
-
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))));
|
|
41944
42575
|
for (const kind of kinds) for (const [entity] of ecs.query(kind.component)) trackFilter(ectx, kind, entity);
|
|
41945
42576
|
}
|
|
41946
42577
|
/**
|
|
@@ -57466,6 +58097,7 @@ function featurePluginOf(name, description, children) {
|
|
|
57466
58097
|
*/
|
|
57467
58098
|
function defineFeature(name, description) {
|
|
57468
58099
|
if (takenNames().has(name)) throw new Error(`[game] Feature name "${name}" is reserved or already a plugin.\n Choose another feature name.`);
|
|
58100
|
+
assertNoSystemsRun(name, description);
|
|
57469
58101
|
const children = Object.freeze([...description.features ?? []]);
|
|
57470
58102
|
assertFeatures(name, children);
|
|
57471
58103
|
const logicOnly = Object.assign(featurePluginOf(name, logicKeysOf(description), children), { features: Object.freeze(children.map((child) => child.logicOnly)) });
|
|
@@ -57475,6 +58107,18 @@ function defineFeature(name, description) {
|
|
|
57475
58107
|
});
|
|
57476
58108
|
}
|
|
57477
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
|
+
/**
|
|
57478
58122
|
* Tells whether an entry of a feature tree carries its `features` list, as every plugin of
|
|
57479
58123
|
* `defineFeature` does. A plain plugin, a cast object or a hole in the array does not.
|
|
57480
58124
|
*
|
|
@@ -57947,6 +58591,168 @@ function defineEffect(kind, options = {}) {
|
|
|
57947
58591
|
});
|
|
57948
58592
|
}
|
|
57949
58593
|
//#endregion
|
|
58594
|
+
//#region src/plugins/anim/tween/bezier.ts
|
|
58595
|
+
/**
|
|
58596
|
+
* @file anim/tween — the CSS timing function `cubic-bezier(x1, y1, x2, y2)` as an easing. The
|
|
58597
|
+
* curve is solved once, into a table of its progress at 512 even steps of time; a frame reads two
|
|
58598
|
+
* cells and draws a line between them. Where a line strays from the curve (the curve leaves an end
|
|
58599
|
+
* vertically), that step is solved again at each call, inside the bracket the table keeps. Either
|
|
58600
|
+
* way a call allocates nothing.
|
|
58601
|
+
*/
|
|
58602
|
+
/**
|
|
58603
|
+
* How many even steps of time the table splits 0…1 into. With 512, a line strays at most 1e-5
|
|
58604
|
+
* from any curve that keeps a finite slope (the CSS keywords, an overshoot both ways), so only a
|
|
58605
|
+
* curve that leaves an end vertically solves any step at a call; 8 KB per curve.
|
|
58606
|
+
*/
|
|
58607
|
+
const STEPS = 512;
|
|
58608
|
+
/**
|
|
58609
|
+
* How far the line of a step may stray from the curve at the step's middle before that step is
|
|
58610
|
+
* solved at each call instead: a fifth of the 1e-4 the factory promises, so the rest of the step,
|
|
58611
|
+
* where the line may stray a little more, stays inside the promise too (2e-5 measured).
|
|
58612
|
+
*/
|
|
58613
|
+
const STRAY = 2e-5;
|
|
58614
|
+
/** Halvings of a solve: 2^-48 of the bracket, far below what a double of progress shows. */
|
|
58615
|
+
const HALVINGS = 48;
|
|
58616
|
+
/**
|
|
58617
|
+
* One coordinate of a cubic curve from 0 to 1 with the two inner control values. Pure.
|
|
58618
|
+
*
|
|
58619
|
+
* @param along - The curve parameter, 0 to 1.
|
|
58620
|
+
* @param first - The first control value.
|
|
58621
|
+
* @param second - The second control value.
|
|
58622
|
+
* @returns The coordinate at `along`.
|
|
58623
|
+
* @example
|
|
58624
|
+
* ```ts
|
|
58625
|
+
* coordinate(0.5, 1 / 3, 2 / 3); // 0.5: the middle of a straight line
|
|
58626
|
+
* ```
|
|
58627
|
+
*/
|
|
58628
|
+
function coordinate(along, first, second) {
|
|
58629
|
+
const back = 1 - along;
|
|
58630
|
+
return 3 * back * back * along * first + 3 * back * along * along * second + along * along * along;
|
|
58631
|
+
}
|
|
58632
|
+
/**
|
|
58633
|
+
* The curve parameter whose x is `time`, found by halving a bracket of parameters. The x of the
|
|
58634
|
+
* curve never falls while x1 and x2 lie in 0…1, so halving always finds it. Pure.
|
|
58635
|
+
*
|
|
58636
|
+
* @param time - The x to reach.
|
|
58637
|
+
* @param x1 - The x of the first control point.
|
|
58638
|
+
* @param x2 - The x of the second control point.
|
|
58639
|
+
* @param low - A parameter whose x is at most `time`.
|
|
58640
|
+
* @param high - A parameter whose x is at least `time`.
|
|
58641
|
+
* @returns The parameter.
|
|
58642
|
+
* @example
|
|
58643
|
+
* ```ts
|
|
58644
|
+
* parameterAt(0.25, 0, 1, 0, 1); // 0.25: on a straight line the parameter is the time
|
|
58645
|
+
* ```
|
|
58646
|
+
*/
|
|
58647
|
+
function parameterAt(time, x1, x2, low, high) {
|
|
58648
|
+
let from = low;
|
|
58649
|
+
let to = high;
|
|
58650
|
+
for (let step = 0; step < HALVINGS; step++) {
|
|
58651
|
+
const middle = (from + to) / 2;
|
|
58652
|
+
if (coordinate(middle, x1, x2) < time) from = middle;
|
|
58653
|
+
else to = middle;
|
|
58654
|
+
}
|
|
58655
|
+
return (from + to) / 2;
|
|
58656
|
+
}
|
|
58657
|
+
/**
|
|
58658
|
+
* Solves a curve into its table: the parameter and the progress at every even step of time, and
|
|
58659
|
+
* the steps whose line strays from the curve at their middle.
|
|
58660
|
+
*
|
|
58661
|
+
* @param points - `[x1, y1, x2, y2]`, x1 and x2 in 0…1.
|
|
58662
|
+
* @returns The table.
|
|
58663
|
+
*/
|
|
58664
|
+
function solveTable(points) {
|
|
58665
|
+
const [x1 = 0, y1 = 0, x2 = 0, y2 = 0] = points;
|
|
58666
|
+
const params = /* @__PURE__ */ new Float64Array(513);
|
|
58667
|
+
const progress = /* @__PURE__ */ new Float64Array(513);
|
|
58668
|
+
const solved = new Uint8Array(STEPS);
|
|
58669
|
+
for (let index = 1; index < STEPS; index++) {
|
|
58670
|
+
params[index] = parameterAt(index / STEPS, x1, x2, 0, 1);
|
|
58671
|
+
progress[index] = coordinate(params[index] ?? 0, y1, y2);
|
|
58672
|
+
}
|
|
58673
|
+
params[STEPS] = 1;
|
|
58674
|
+
progress[STEPS] = 1;
|
|
58675
|
+
for (let index = 0; index < STEPS; index++) {
|
|
58676
|
+
const middle = parameterAt((index + .5) / STEPS, x1, x2, params[index] ?? 0, params[index + 1] ?? 1);
|
|
58677
|
+
const line = ((progress[index] ?? 0) + (progress[index + 1] ?? 0)) / 2;
|
|
58678
|
+
if (Math.abs(coordinate(middle, y1, y2) - line) > STRAY) solved[index] = 1;
|
|
58679
|
+
}
|
|
58680
|
+
return {
|
|
58681
|
+
points,
|
|
58682
|
+
params,
|
|
58683
|
+
progress,
|
|
58684
|
+
solved
|
|
58685
|
+
};
|
|
58686
|
+
}
|
|
58687
|
+
/**
|
|
58688
|
+
* The cell a solved step passes its time in and its progress out: a double handed to or returned
|
|
58689
|
+
* from a call V8 does not inline is boxed, a cell is not.
|
|
58690
|
+
*/
|
|
58691
|
+
const SOLVED = /* @__PURE__ */ new Float64Array(1);
|
|
58692
|
+
/**
|
|
58693
|
+
* The progress of a curve at one time: exactly 0 up to the start and 1 from the end on, a line
|
|
58694
|
+
* between two cells of the table in between, or the curve itself on a step whose line strays.
|
|
58695
|
+
*
|
|
58696
|
+
* @param table - The curve's table.
|
|
58697
|
+
* @param time - The time.
|
|
58698
|
+
* @returns The progress.
|
|
58699
|
+
*/
|
|
58700
|
+
function progressAt(table, time) {
|
|
58701
|
+
if (time <= 0) return 0;
|
|
58702
|
+
if (time >= 1) return 1;
|
|
58703
|
+
const at = time * STEPS;
|
|
58704
|
+
const index = Math.floor(at);
|
|
58705
|
+
if (table.solved[index] === 1) {
|
|
58706
|
+
SOLVED[0] = time;
|
|
58707
|
+
solveStep(table, index);
|
|
58708
|
+
return SOLVED[0] ?? 0;
|
|
58709
|
+
}
|
|
58710
|
+
const low = table.progress[index] ?? 0;
|
|
58711
|
+
return low + ((table.progress[index + 1] ?? 1) - low) * (at - index);
|
|
58712
|
+
}
|
|
58713
|
+
/**
|
|
58714
|
+
* The progress on a step whose line strays from the curve: the curve solved inside the bracket of
|
|
58715
|
+
* parameters the table keeps for that step. It reads the time from `SOLVED[0]` and leaves the
|
|
58716
|
+
* progress there. Kept apart so the common call stays small.
|
|
58717
|
+
*
|
|
58718
|
+
* @param table - The curve's table.
|
|
58719
|
+
* @param index - The step.
|
|
58720
|
+
*/
|
|
58721
|
+
function solveStep(table, index) {
|
|
58722
|
+
const points = table.points;
|
|
58723
|
+
const along = parameterAt(SOLVED[0] ?? 0, points[0] ?? 0, points[2] ?? 0, table.params[index] ?? 0, table.params[index + 1] ?? 1);
|
|
58724
|
+
SOLVED[0] = coordinate(along, points[1] ?? 0, points[3] ?? 0);
|
|
58725
|
+
}
|
|
58726
|
+
/**
|
|
58727
|
+
* The CSS timing function `cubic-bezier(x1, y1, x2, y2)` as an easing a tween, a keyframe segment
|
|
58728
|
+
* or a motion takes for `ease`. The curve is solved once, here, into a table; a frame reads it, so
|
|
58729
|
+
* an eased time costs a few reads and allocates nothing, and the progress stays within 1e-4 of the
|
|
58730
|
+
* exact curve. It is exactly 0 up to the start and exactly 1 from the end on, and may leave 0…1 on
|
|
58731
|
+
* the way (an overshoot). Make each curve once, at module level, and share it.
|
|
58732
|
+
*
|
|
58733
|
+
* @param x1 - The x of the first control point, 0 to 1.
|
|
58734
|
+
* @param y1 - The y of the first control point; below 0 or above 1 overshoots.
|
|
58735
|
+
* @param x2 - The x of the second control point, 0 to 1.
|
|
58736
|
+
* @param y2 - The y of the second control point; below 0 or above 1 overshoots.
|
|
58737
|
+
* @returns The curve: normalised time in, eased progress out.
|
|
58738
|
+
* @throws {Error} When x1 or x2 lies outside 0…1: the curve would be no function of time.
|
|
58739
|
+
* @example
|
|
58740
|
+
* ```ts
|
|
58741
|
+
* // The design's arrival curve, made once and named by every tween that arrives.
|
|
58742
|
+
* const arrive = cubicBezier(0.22, 0.61, 0.36, 1);
|
|
58743
|
+
* arrive(0.5); // 0.873 (to 1e-4)
|
|
58744
|
+
* const drop = defineAnimation("card.drop", {
|
|
58745
|
+
* slots: { card: type<Target>() },
|
|
58746
|
+
* build: ({ card }) => sequence(tween(card, Transform, { y: 40 }, { ms: 300, ease: arrive }))
|
|
58747
|
+
* });
|
|
58748
|
+
* ```
|
|
58749
|
+
*/
|
|
58750
|
+
function cubicBezier(x1, y1, x2, y2) {
|
|
58751
|
+
if (!(x1 >= 0 && x1 <= 1 && x2 >= 0 && x2 <= 1)) throw new Error(`[game] cubicBezier needs x1 and x2 between 0 and 1, got ${x1} and ${x2}.\n The x of a control point is a time; y1 and y2 may leave 0…1 for an overshoot.`);
|
|
58752
|
+
const table = solveTable(Float64Array.of(x1, y1, x2, y2));
|
|
58753
|
+
return (t) => progressAt(table, t);
|
|
58754
|
+
}
|
|
58755
|
+
//#endregion
|
|
57950
58756
|
//#region src/index.ts
|
|
57951
58757
|
/**
|
|
57952
58758
|
* The `@moku-labs/game` package — 2D puzzle game engine on Moku Core.
|
|
@@ -58155,4 +58961,4 @@ const screen = [
|
|
|
58155
58961
|
uiPlugin
|
|
58156
58962
|
];
|
|
58157
58963
|
//#endregion
|
|
58158
|
-
export {
|
|
58964
|
+
export { mark as $, PROBLEM_RULES as $t, types_exports$11 as A, Alpha as At, Box as B, scenesPlugin as Bt, types_exports$4 as C, Traceable as Ct, types_exports$8 as D, messageDuration as Dt, types_exports$7 as E, messageArgument as Et, types_exports$16 as F, Noise as Ft, defineStyle as G, Frames as Gt, LocalWrite as H, animPlugin as Ht, types_exports$17 as I, Outline as It, HAPTIC_KINDS as J, frameAt as Jt, textPlugin as K, rendererPlugin as Kt, uiPlugin as L, defineFilter as Lt, types_exports$13 as M, ColorMatrix as Mt, types_exports$14 as N, Displacement as Nt, types_exports$9 as O, effectsPlugin as Ot, types_exports$15 as P, Glow as Pt, haptic as Q, PERF_LOG_VERSION as Qt, resolve as R, debugPlugin as Rt, types_exports$3 as S, Touchable as St, types_exports$6 as T, i18nPlugin as Tt, popup as U, AnimPlayer as Ut, DebugTrigger as V, assetsPlugin as Vt, defineTokens as W, Animation as Wt, external as X, PERF_CAUSES as Xt, defineAnimation as Y, MONITOR_SECTIONS as Yt, frames as Z, PERF_GAP_BUCKETS as Zt, defineBundles as _, PointerOver as _t, cubicBezier as a, exit as an, spawn as at, types_exports$1 as b, Swipeable as bt, hint as c, type as cn, tween as ct, defineScene as d, lifecyclePlugin as dn, inputPlugin as dt, SHADER_WHATS as en, parallel as et, tr as f, timePlugin as fn, Draggable as ft, music as g, Pointer as gt, defineEmitter as h, Hovered as ht, screen as i, refuseStart as in, sfx as it, types_exports$12 as j, Blur as jt, types_exports$10 as k, Emitter as kt, schedule as l, modelPlugin as ln, use as lt, flattenFeatures as m, Held as mt, createPlugin as n, worldPlugin as nn, sequence as nt, defineEffect as o, slot as on, spawned as ot, defineFeature as p, clockPlugin as pn, DropTarget as pt, platformPlugin as q, PASS_KINDS as qt, defineGame as r, flowPlugin as rn, set as rt, guide as s, to as sn, stagger as st, createApp as t, DEMAND_REASONS as tn, play$1 as tt, projection as u, SaveUnreadableError as un, wait as ut, load as v, Pressable as vt, types_exports$5 as w, Traced as wt, types_exports$2 as x, Tappable as xt, types_exports as y, Pressed as yt, defineMotion as z, audioPlugin as zt };
|