@moku-labs/game 0.15.2 → 0.15.3
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-31d2BEwc.d.mts} +5 -5
- package/dist/control.d.mts +4 -4
- package/dist/{define-DnO7ZLGl.d.mts → define-DYJwRXB_.d.mts} +2 -2
- package/dist/{headless-D6BrEwCf.d.mts → headless-D0U5AXiK.d.mts} +1 -1
- package/dist/{index-w2uyRKkj.d.mts → index-ByvWj9OX.d.mts} +42 -6
- 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-biKbodaT.d.mts} +1 -1
- package/dist/{src-Ca8W8Dvv.mjs → src-xChMXzwH.mjs} +1036 -132
- package/dist/testing.d.mts +4 -4
- package/dist/testing.mjs +1 -1
- package/dist/{types-jiWJhLPE.d.mts → types-B0bmoyRT.d.mts} +2 -2
- package/dist/{types-DEYQF_tT.d.mts → types-BApuIqwq.d.mts} +4 -4
- package/dist/{types-fpFKYm6E.d.mts → types-CsMLBPf-.d.mts} +13 -5
- package/dist/{types-BHgJ1F6f.d.mts → types-DlC3Ytbj.d.mts} +149 -15
- package/dist/{types-Bm7DVtA_.d.mts → types-yLdzHyTl.d.mts} +10 -2
- package/dist/{visual-BvkKJOSz.d.mts → visual-G6F0Mi9H.d.mts} +6 -6
- package/dist/visual.d.mts +1 -1
- package/llms.txt +3 -0
- package/package.json +1 -1
|
@@ -8546,6 +8546,294 @@ function schemaOf(types) {
|
|
|
8546
8546
|
return [...types].map((type) => schemaOfType(type)).toSorted((left, right) => left.name.localeCompare(right.name));
|
|
8547
8547
|
}
|
|
8548
8548
|
//#endregion
|
|
8549
|
+
//#region src/plugins/world/ecs/scope.ts
|
|
8550
|
+
/** The keys of a description whose entries can make a feature active. */
|
|
8551
|
+
const SOURCES = [
|
|
8552
|
+
"nodes",
|
|
8553
|
+
"flows",
|
|
8554
|
+
"scenes",
|
|
8555
|
+
"projections",
|
|
8556
|
+
"ui"
|
|
8557
|
+
];
|
|
8558
|
+
/** The release of a hold that holds nothing. */
|
|
8559
|
+
const RELEASE_NOTHING = () => {};
|
|
8560
|
+
/**
|
|
8561
|
+
* Creates an empty table of owners. Its own function: a collection is built outside the exported
|
|
8562
|
+
* declarations.
|
|
8563
|
+
*
|
|
8564
|
+
* @returns An empty map.
|
|
8565
|
+
*/
|
|
8566
|
+
function emptyOwners$2() {
|
|
8567
|
+
return /* @__PURE__ */ new Map();
|
|
8568
|
+
}
|
|
8569
|
+
/**
|
|
8570
|
+
* Sizes the scope to the features of the game, in registration order: the only allocation of the
|
|
8571
|
+
* scope, once per start. Whatever an earlier start left is dropped first.
|
|
8572
|
+
*
|
|
8573
|
+
* @param ctx - Domain context of the world plugin.
|
|
8574
|
+
*/
|
|
8575
|
+
function openScope(ctx) {
|
|
8576
|
+
const scope = ctx.state.ecs.scope;
|
|
8577
|
+
const features = ctx.deps.flow.features.all();
|
|
8578
|
+
const count = features.length;
|
|
8579
|
+
resetScope(scope);
|
|
8580
|
+
for (const [at, feature] of features.entries()) {
|
|
8581
|
+
scope.names.push(feature.name);
|
|
8582
|
+
scope.index.set(feature.name, at);
|
|
8583
|
+
}
|
|
8584
|
+
scope.onStack = new Uint8Array(count);
|
|
8585
|
+
scope.active = new Uint8Array(count);
|
|
8586
|
+
scope.holds = new Int32Array(count);
|
|
8587
|
+
scope.views = new Int32Array(count);
|
|
8588
|
+
scope.dirty = true;
|
|
8589
|
+
}
|
|
8590
|
+
/**
|
|
8591
|
+
* Empties the scope in place, as a world that never started has it, and moves its generation on:
|
|
8592
|
+
* a release kept past this changes nothing, also once the world started again.
|
|
8593
|
+
*
|
|
8594
|
+
* @param scope - The scope.
|
|
8595
|
+
*/
|
|
8596
|
+
function resetScope(scope) {
|
|
8597
|
+
scope.generation += 1;
|
|
8598
|
+
scope.names.length = 0;
|
|
8599
|
+
scope.index.clear();
|
|
8600
|
+
scope.projections.clear();
|
|
8601
|
+
scope.owners = void 0;
|
|
8602
|
+
scope.ownersFailed = false;
|
|
8603
|
+
scope.flow = void 0;
|
|
8604
|
+
scope.onStack = /* @__PURE__ */ new Uint8Array(0);
|
|
8605
|
+
scope.active = /* @__PURE__ */ new Uint8Array(0);
|
|
8606
|
+
scope.holds = /* @__PURE__ */ new Int32Array(0);
|
|
8607
|
+
scope.views = /* @__PURE__ */ new Int32Array(0);
|
|
8608
|
+
scope.dirty = false;
|
|
8609
|
+
scope.scoped = false;
|
|
8610
|
+
}
|
|
8611
|
+
/**
|
|
8612
|
+
* The scope the systems of one feature register with: its index for `systemsRun: "active"`, -1
|
|
8613
|
+
* otherwise. A value outside the type (a hand-written `features.register`) is reported and its
|
|
8614
|
+
* systems always run.
|
|
8615
|
+
*
|
|
8616
|
+
* @param ctx - Domain context of the world plugin.
|
|
8617
|
+
* @param at - The feature's index.
|
|
8618
|
+
* @param feature - The feature and what it registered.
|
|
8619
|
+
* @param feature.name - Feature name, for the log entry.
|
|
8620
|
+
* @param feature.description - What the feature brought.
|
|
8621
|
+
* @returns The scope of its systems.
|
|
8622
|
+
*/
|
|
8623
|
+
function scopeOf(ctx, at, feature) {
|
|
8624
|
+
const run = feature.description.systemsRun;
|
|
8625
|
+
if (run === "active") return at;
|
|
8626
|
+
if (run !== void 0 && run !== "always") ctx.log.warn("world:bad-feature-entry", {
|
|
8627
|
+
feature: feature.name,
|
|
8628
|
+
key: "systemsRun"
|
|
8629
|
+
});
|
|
8630
|
+
return -1;
|
|
8631
|
+
}
|
|
8632
|
+
/**
|
|
8633
|
+
* Warns about a feature whose systems wait for it to be active while nothing can make it active:
|
|
8634
|
+
* it has systems and `systemsRun: "active"`, and no node, flow, scene, projection or ui component.
|
|
8635
|
+
* The caller guards it for dev builds.
|
|
8636
|
+
*
|
|
8637
|
+
* @param ctx - Domain context of the world plugin.
|
|
8638
|
+
* @param feature - The feature and what it registered.
|
|
8639
|
+
* @param feature.name - Feature name, for the warning.
|
|
8640
|
+
* @param feature.description - What the feature brought.
|
|
8641
|
+
*/
|
|
8642
|
+
function reportNeverActive(ctx, feature) {
|
|
8643
|
+
const { name, description } = feature;
|
|
8644
|
+
const systems = description.systems;
|
|
8645
|
+
if (description.systemsRun !== "active") return;
|
|
8646
|
+
if (!Array.isArray(systems) || systems.length === 0) return;
|
|
8647
|
+
for (const key of SOURCES) {
|
|
8648
|
+
const list = description[key];
|
|
8649
|
+
if (Array.isArray(list) && list.length > 0) return;
|
|
8650
|
+
}
|
|
8651
|
+
ctx.log.warn("world:feature-never-active", {
|
|
8652
|
+
feature: name,
|
|
8653
|
+
message: `Feature "${name}" runs its systems only while it is active, but it has no node, flow, scene, projection or ui component, so nothing makes it active and its systems never run. Remove systemsRun: "active" from it.`
|
|
8654
|
+
});
|
|
8655
|
+
}
|
|
8656
|
+
/**
|
|
8657
|
+
* Builds the owner table of rule N from one `describe()`: flow id to node name to the index of the
|
|
8658
|
+
* feature that owns the node. A node without an owner, or owned by a name that is not a feature,
|
|
8659
|
+
* is left out.
|
|
8660
|
+
*
|
|
8661
|
+
* @param graph - The described graph.
|
|
8662
|
+
* @param index - Feature name to index.
|
|
8663
|
+
* @returns The owner table.
|
|
8664
|
+
*/
|
|
8665
|
+
function ownersOf$1(graph, index) {
|
|
8666
|
+
const owners = emptyOwners$2();
|
|
8667
|
+
for (const [id, flow] of Object.entries(graph.flows)) {
|
|
8668
|
+
const nodes = emptyOwners$2();
|
|
8669
|
+
for (const [node, info] of Object.entries(flow.nodes)) {
|
|
8670
|
+
const at = info.owner === void 0 ? void 0 : index.get(info.owner);
|
|
8671
|
+
if (at !== void 0) nodes.set(node, at);
|
|
8672
|
+
}
|
|
8673
|
+
if (nodes.size > 0) owners.set(id, nodes);
|
|
8674
|
+
}
|
|
8675
|
+
return owners;
|
|
8676
|
+
}
|
|
8677
|
+
/**
|
|
8678
|
+
* Builds the owner table once, at the first stack that is not empty: the graph is sealed by
|
|
8679
|
+
* `flow.run()`. A `describe()` that throws is logged once and makes every feature count as on the
|
|
8680
|
+
* stack (fail-safe).
|
|
8681
|
+
*
|
|
8682
|
+
* @param ctx - Domain context of the world plugin.
|
|
8683
|
+
* @param scope - The scope.
|
|
8684
|
+
*/
|
|
8685
|
+
function buildOwners(ctx, scope) {
|
|
8686
|
+
try {
|
|
8687
|
+
scope.owners = ownersOf$1(ctx.deps.flow.describe(), scope.index);
|
|
8688
|
+
} catch (error) {
|
|
8689
|
+
scope.ownersFailed = true;
|
|
8690
|
+
ctx.log.error("world:scope-owners-failed", {}, asError$3(error));
|
|
8691
|
+
}
|
|
8692
|
+
}
|
|
8693
|
+
/**
|
|
8694
|
+
* Sets the stack flags of rule N from the frames of one flow state, every frame counted, the
|
|
8695
|
+
* outer frames of sub-flows included.
|
|
8696
|
+
*
|
|
8697
|
+
* @param ctx - Domain context of the world plugin.
|
|
8698
|
+
* @param scope - The scope.
|
|
8699
|
+
* @param stack - The frames of the flow state.
|
|
8700
|
+
*/
|
|
8701
|
+
function readStack$1(ctx, scope, stack) {
|
|
8702
|
+
const onStack = scope.onStack;
|
|
8703
|
+
const depth = stack.length;
|
|
8704
|
+
onStack.fill(0);
|
|
8705
|
+
if (depth === 0) return;
|
|
8706
|
+
if (scope.owners === void 0 && !scope.ownersFailed) buildOwners(ctx, scope);
|
|
8707
|
+
const owners = scope.owners;
|
|
8708
|
+
if (owners === void 0) {
|
|
8709
|
+
onStack.fill(1);
|
|
8710
|
+
return;
|
|
8711
|
+
}
|
|
8712
|
+
for (let index = 0; index < depth; index += 1) {
|
|
8713
|
+
const frame = stack[index];
|
|
8714
|
+
const at = frame === void 0 ? void 0 : owners.get(frame.flow)?.get(frame.node);
|
|
8715
|
+
if (at !== void 0) onStack[at] = 1;
|
|
8716
|
+
}
|
|
8717
|
+
}
|
|
8718
|
+
/**
|
|
8719
|
+
* Reads the flow stack again when `flow.state()` answers a new object. The state is memoised, so a
|
|
8720
|
+
* frame in which the graph did not move costs one call and one comparison.
|
|
8721
|
+
*
|
|
8722
|
+
* @param ctx - Domain context of the world plugin.
|
|
8723
|
+
* @param scope - The scope.
|
|
8724
|
+
*/
|
|
8725
|
+
function readSources(ctx, scope) {
|
|
8726
|
+
const flowState = ctx.deps.flow.state();
|
|
8727
|
+
if (flowState === scope.flow) return;
|
|
8728
|
+
scope.flow = flowState;
|
|
8729
|
+
readStack$1(ctx, scope, flowState.stack);
|
|
8730
|
+
scope.dirty = true;
|
|
8731
|
+
}
|
|
8732
|
+
/**
|
|
8733
|
+
* Evaluates the active set at the start of a phase that runs systems: a feature is active while it
|
|
8734
|
+
* is on the stack, held, or has a view. Nothing moved: one call, one comparison and one flag test.
|
|
8735
|
+
*
|
|
8736
|
+
* @param ctx - Domain context of the world plugin.
|
|
8737
|
+
*/
|
|
8738
|
+
function refreshScope(ctx) {
|
|
8739
|
+
const scope = ctx.state.ecs.scope;
|
|
8740
|
+
readSources(ctx, scope);
|
|
8741
|
+
if (!scope.dirty) return;
|
|
8742
|
+
const { onStack, holds, views, active } = scope;
|
|
8743
|
+
const count = scope.names.length;
|
|
8744
|
+
for (let at = 0; at < count; at += 1) active[at] = onStack[at] === 1 || (holds[at] ?? 0) > 0 || (views[at] ?? 0) > 0 ? 1 : 0;
|
|
8745
|
+
scope.dirty = false;
|
|
8746
|
+
}
|
|
8747
|
+
/**
|
|
8748
|
+
* The release of one hold: it takes the hold back once, never below 0, and only in the run it was
|
|
8749
|
+
* taken in. A release kept across a stop and a start finds a newer generation and changes nothing
|
|
8750
|
+
* (spec 28 W7.1).
|
|
8751
|
+
*
|
|
8752
|
+
* @param scope - The scope.
|
|
8753
|
+
* @param at - The held feature's index.
|
|
8754
|
+
* @returns The idempotent release.
|
|
8755
|
+
*/
|
|
8756
|
+
function releaseOf(scope, at) {
|
|
8757
|
+
const hold = {
|
|
8758
|
+
released: false,
|
|
8759
|
+
generation: scope.generation
|
|
8760
|
+
};
|
|
8761
|
+
return () => {
|
|
8762
|
+
if (hold.released || hold.generation !== scope.generation) return;
|
|
8763
|
+
hold.released = true;
|
|
8764
|
+
scope.holds[at] = Math.max(0, (scope.holds[at] ?? 0) - 1);
|
|
8765
|
+
scope.dirty = true;
|
|
8766
|
+
};
|
|
8767
|
+
}
|
|
8768
|
+
/**
|
|
8769
|
+
* Takes one hold on a feature. A name that is not a feature holds nothing; a dev build warns.
|
|
8770
|
+
*
|
|
8771
|
+
* @param ctx - Domain context of the world plugin.
|
|
8772
|
+
* @param feature - The feature to keep active.
|
|
8773
|
+
* @returns The release.
|
|
8774
|
+
*/
|
|
8775
|
+
function holdFeature(ctx, feature) {
|
|
8776
|
+
const scope = ctx.state.ecs.scope;
|
|
8777
|
+
const at = scope.index.get(feature);
|
|
8778
|
+
if (at === void 0) {
|
|
8779
|
+
if (typeof __MOKU_GAME_DEV__ !== "undefined" && __MOKU_GAME_DEV__) ctx.log.warn("world:hold-unknown-feature", { feature });
|
|
8780
|
+
return RELEASE_NOTHING;
|
|
8781
|
+
}
|
|
8782
|
+
scope.holds[at] = (scope.holds[at] ?? 0) + 1;
|
|
8783
|
+
scope.dirty = true;
|
|
8784
|
+
return releaseOf(scope, at);
|
|
8785
|
+
}
|
|
8786
|
+
/**
|
|
8787
|
+
* Whether a feature is active now, from the sources as they are at this moment. It leaves the
|
|
8788
|
+
* active set of the running phase alone: a source that moves inside a phase counts for the
|
|
8789
|
+
* systems from the next phase.
|
|
8790
|
+
*
|
|
8791
|
+
* @param ctx - Domain context of the world plugin.
|
|
8792
|
+
* @param feature - The feature name.
|
|
8793
|
+
* @returns True while it is on the stack, held or has a view; false for a name that is not a feature.
|
|
8794
|
+
*/
|
|
8795
|
+
function isFeatureActive(ctx, feature) {
|
|
8796
|
+
const scope = ctx.state.ecs.scope;
|
|
8797
|
+
const at = scope.index.get(feature);
|
|
8798
|
+
if (at === void 0) return false;
|
|
8799
|
+
readSources(ctx, scope);
|
|
8800
|
+
return scope.onStack[at] === 1 || (scope.holds[at] ?? 0) > 0 || (scope.views[at] ?? 0) > 0;
|
|
8801
|
+
}
|
|
8802
|
+
/**
|
|
8803
|
+
* Counts a new view of a projection for the feature that registered it. A projection no feature
|
|
8804
|
+
* registered counts for nobody.
|
|
8805
|
+
*
|
|
8806
|
+
* @param scope - The scope.
|
|
8807
|
+
* @param projection - The projection name.
|
|
8808
|
+
*/
|
|
8809
|
+
function viewCame(scope, projection) {
|
|
8810
|
+
const at = scope.projections.get(projection);
|
|
8811
|
+
if (at === void 0) return;
|
|
8812
|
+
scope.views[at] = (scope.views[at] ?? 0) + 1;
|
|
8813
|
+
scope.dirty = true;
|
|
8814
|
+
}
|
|
8815
|
+
/**
|
|
8816
|
+
* Takes back the count of a despawned view, never below 0.
|
|
8817
|
+
*
|
|
8818
|
+
* @param scope - The scope.
|
|
8819
|
+
* @param projection - The projection name.
|
|
8820
|
+
*/
|
|
8821
|
+
function viewLeft(scope, projection) {
|
|
8822
|
+
const at = scope.projections.get(projection);
|
|
8823
|
+
if (at === void 0) return;
|
|
8824
|
+
scope.views[at] = Math.max(0, (scope.views[at] ?? 0) - 1);
|
|
8825
|
+
scope.dirty = true;
|
|
8826
|
+
}
|
|
8827
|
+
/**
|
|
8828
|
+
* Drops every view count: the projections were cleared.
|
|
8829
|
+
*
|
|
8830
|
+
* @param scope - The scope.
|
|
8831
|
+
*/
|
|
8832
|
+
function viewsCleared(scope) {
|
|
8833
|
+
scope.views.fill(0);
|
|
8834
|
+
scope.dirty = true;
|
|
8835
|
+
}
|
|
8836
|
+
//#endregion
|
|
8549
8837
|
//#region src/plugins/world/ecs/systems.ts
|
|
8550
8838
|
/** The one phase that runs in every mode, because `renderer.sync` lives in it. */
|
|
8551
8839
|
const ALWAYS = "sync";
|
|
@@ -8636,14 +8924,17 @@ function frameSnapshot(ctx, time) {
|
|
|
8636
8924
|
}
|
|
8637
8925
|
/**
|
|
8638
8926
|
* Registers a system at the end of its phase list and returns the remover. Its query is resolved
|
|
8639
|
-
* here, once: a later change to the definition's `query` array is not seen.
|
|
8927
|
+
* here, once: a later change to the definition's `query` array is not seen. A system of a feature
|
|
8928
|
+
* with `systemsRun: "active"` carries the feature's index as its scope and runs only while the
|
|
8929
|
+
* feature is active; `ecs.system` passes none, so a system registered by hand always runs.
|
|
8640
8930
|
*
|
|
8641
8931
|
* @param ctx - Domain context of the world plugin.
|
|
8642
8932
|
* @param definition - The system.
|
|
8933
|
+
* @param scope - The index of its feature in the scope, or -1 for a system that always runs.
|
|
8643
8934
|
* @returns The remover; calling it twice is a no-op.
|
|
8644
8935
|
* @throws {Error} When a system of that name is already registered.
|
|
8645
8936
|
*/
|
|
8646
|
-
function registerSystem(ctx, definition) {
|
|
8937
|
+
function registerSystem(ctx, definition, scope = -1) {
|
|
8647
8938
|
const state = ctx.state.ecs;
|
|
8648
8939
|
for (const phase of [
|
|
8649
8940
|
"input",
|
|
@@ -8656,10 +8947,13 @@ function registerSystem(ctx, definition) {
|
|
|
8656
8947
|
definition,
|
|
8657
8948
|
frame: ctx.deps.time.snapshot().frame,
|
|
8658
8949
|
plan,
|
|
8950
|
+
scope,
|
|
8951
|
+
skipped: false,
|
|
8659
8952
|
walk: plan.names.length === 0 ? void 0 : new SystemWalk(ctx, plan)
|
|
8660
8953
|
};
|
|
8661
8954
|
const list = state.systems[definition.phase];
|
|
8662
8955
|
list.push(entry);
|
|
8956
|
+
if (scope !== -1) state.scope.scoped = true;
|
|
8663
8957
|
if (state.timing !== void 0) giveRow(state, state.timing, definition.name);
|
|
8664
8958
|
return () => {
|
|
8665
8959
|
const at = list.indexOf(entry);
|
|
@@ -8682,7 +8976,8 @@ function createPhaseContext(state, world, snapshot, time) {
|
|
|
8682
8976
|
world,
|
|
8683
8977
|
res: (resourceType) => resourceValue(state, resourceType),
|
|
8684
8978
|
snapshot,
|
|
8685
|
-
time
|
|
8979
|
+
time,
|
|
8980
|
+
resumed: false
|
|
8686
8981
|
};
|
|
8687
8982
|
state.phaseContext = context;
|
|
8688
8983
|
return context;
|
|
@@ -8707,6 +9002,42 @@ function phaseContextOf(ctx, world, time) {
|
|
|
8707
9002
|
return known;
|
|
8708
9003
|
}
|
|
8709
9004
|
/**
|
|
9005
|
+
* Tells whether a system of a scoped feature runs in this phase. One whose feature is not active is
|
|
9006
|
+
* skipped in its slot and remembers it; one that runs is told whether its runs before were
|
|
9007
|
+
* skipped. No walk, no mark and no clock read happen for a skipped system: the check comes before
|
|
9008
|
+
* all three. The caller sets `resumed` back to false after the run, so a system that always runs
|
|
9009
|
+
* reads false without a write of its own.
|
|
9010
|
+
*
|
|
9011
|
+
* @param active - The active flags of the features, evaluated at the start of the phase.
|
|
9012
|
+
* @param entry - The system, whose scope is a feature index.
|
|
9013
|
+
* @param context - What its `run` receives; `resumed` is written here.
|
|
9014
|
+
* @returns True when the system runs.
|
|
9015
|
+
*/
|
|
9016
|
+
function wakes(active, entry, context) {
|
|
9017
|
+
if (active[entry.scope] === 0) {
|
|
9018
|
+
entry.skipped = true;
|
|
9019
|
+
return false;
|
|
9020
|
+
}
|
|
9021
|
+
context.resumed = entry.skipped;
|
|
9022
|
+
entry.skipped = false;
|
|
9023
|
+
return true;
|
|
9024
|
+
}
|
|
9025
|
+
/**
|
|
9026
|
+
* Runs one system of a scoped feature when its feature is active, and leaves `resumed` false for
|
|
9027
|
+
* the systems after it.
|
|
9028
|
+
*
|
|
9029
|
+
* @param ctx - Domain context of the world plugin.
|
|
9030
|
+
* @param active - The active flags of the features.
|
|
9031
|
+
* @param entry - The system.
|
|
9032
|
+
* @param context - What its `run` receives.
|
|
9033
|
+
* @param phase - The phase, for the report of a throw.
|
|
9034
|
+
*/
|
|
9035
|
+
function runScoped(ctx, active, entry, context, phase) {
|
|
9036
|
+
if (!wakes(active, entry, context)) return;
|
|
9037
|
+
guardedRun(ctx, entry, context, phase);
|
|
9038
|
+
context.resumed = false;
|
|
9039
|
+
}
|
|
9040
|
+
/**
|
|
8710
9041
|
* Runs one system, reporting a throw: the try lives here, so the loop of a phase holds none.
|
|
8711
9042
|
*
|
|
8712
9043
|
* @param ctx - Domain context of the world plugin.
|
|
@@ -8726,7 +9057,9 @@ function guardedRun(ctx, entry, context, phase) {
|
|
|
8726
9057
|
}
|
|
8727
9058
|
/**
|
|
8728
9059
|
* 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.
|
|
9060
|
+
* reported and skipped for this frame; the frame goes on. When a feature scopes its systems, the
|
|
9061
|
+
* active set is evaluated first, from the sources as they stand at the start of the phase; a
|
|
9062
|
+
* system of a feature that is not active is skipped in its slot.
|
|
8730
9063
|
*
|
|
8731
9064
|
* @param ctx - Domain context of the world plugin.
|
|
8732
9065
|
* @param world - The ecs API a system's `run` receives.
|
|
@@ -8736,21 +9069,26 @@ function guardedRun(ctx, entry, context, phase) {
|
|
|
8736
9069
|
function runPhase(ctx, world, phase, time) {
|
|
8737
9070
|
const state = ctx.state.ecs;
|
|
8738
9071
|
if (phase !== ALWAYS && effectiveMode(ctx) !== "live") return;
|
|
9072
|
+
if (state.scope.scoped) refreshScope(ctx);
|
|
8739
9073
|
const context = phaseContextOf(ctx, world, time);
|
|
8740
9074
|
const entries = state.systems[phase];
|
|
9075
|
+
const active = state.scope.active;
|
|
8741
9076
|
state.running = phase;
|
|
8742
9077
|
if (state.timing === void 0) for (let index = 0; index < entries.length; index += 1) {
|
|
8743
9078
|
const entry = entries[index];
|
|
8744
|
-
if (entry
|
|
9079
|
+
if (entry === void 0 || entry.frame >= time.frame) continue;
|
|
9080
|
+
if (entry.scope === -1) guardedRun(ctx, entry, context, phase);
|
|
9081
|
+
else runScoped(ctx, active, entry, context, phase);
|
|
8745
9082
|
}
|
|
8746
9083
|
else runTimed(ctx, state.timing, entries, context, phase, time);
|
|
8747
9084
|
state.running = void 0;
|
|
8748
9085
|
flushCommands(ctx);
|
|
8749
9086
|
}
|
|
8750
9087
|
/**
|
|
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
|
|
9088
|
+
* Runs the systems of one phase as `runPhase` does, same order, same error isolation, same skip,
|
|
9089
|
+
* and adds each run's time to its row of the timing table: two clock reads per run, an indexed
|
|
9090
|
+
* loop, no closure. The rows were zeroed at the start of the frame, so after the frame they hold
|
|
9091
|
+
* its time; a skipped system's row stays 0.
|
|
8754
9092
|
*
|
|
8755
9093
|
* @param ctx - Domain context of the world plugin.
|
|
8756
9094
|
* @param timing - The timing table of the agent build.
|
|
@@ -8761,20 +9099,25 @@ function runPhase(ctx, world, phase, time) {
|
|
|
8761
9099
|
*/
|
|
8762
9100
|
function runTimed(ctx, timing, entries, context, phase, time) {
|
|
8763
9101
|
const count = entries.length;
|
|
9102
|
+
const active = ctx.state.ecs.scope.active;
|
|
8764
9103
|
for (let index = 0; index < count; index += 1) {
|
|
8765
9104
|
const entry = entries[index];
|
|
8766
9105
|
if (entry === void 0 || entry.frame >= time.frame) continue;
|
|
9106
|
+
if (entry.scope !== -1 && !wakes(active, entry, context)) continue;
|
|
8767
9107
|
const row = rowOf(ctx.state.ecs, entry.definition.name);
|
|
8768
9108
|
const started = timing.clock();
|
|
8769
9109
|
guardedRun(ctx, entry, context, phase);
|
|
8770
9110
|
timing.ms[row] = (timing.ms[row] ?? 0) + timing.clock() - started;
|
|
9111
|
+
context.resumed = false;
|
|
8771
9112
|
}
|
|
8772
9113
|
}
|
|
8773
9114
|
//#endregion
|
|
8774
9115
|
//#region src/plugins/world/ecs/api.ts
|
|
8775
9116
|
/**
|
|
8776
|
-
* Empties the world in place: entities, components, resources, systems, hooks
|
|
8777
|
-
*
|
|
9117
|
+
* Empties the world in place: entities, components, resources, systems, hooks, the frame history
|
|
9118
|
+
* and the view counts of the scope. The holds stay: the plugins above still show their scene or
|
|
9119
|
+
* root. The owner table stays: the graph did not change. The state object keeps its identity, so
|
|
9120
|
+
* every view on it stays valid.
|
|
8778
9121
|
*
|
|
8779
9122
|
* @param ctx - Domain context of the world plugin.
|
|
8780
9123
|
*/
|
|
@@ -8805,6 +9148,7 @@ function clearWorldState(ctx) {
|
|
|
8805
9148
|
state.mode = "live";
|
|
8806
9149
|
state.frameSnapshot = void 0;
|
|
8807
9150
|
resetHistory(state);
|
|
9151
|
+
viewsCleared(state.scope);
|
|
8808
9152
|
}
|
|
8809
9153
|
/**
|
|
8810
9154
|
* Creates the ecs module: the public API a game and the plugins above call, plus the four methods
|
|
@@ -8817,6 +9161,8 @@ function createEcsApi(ctx) {
|
|
|
8817
9161
|
const state = ctx.state.ecs;
|
|
8818
9162
|
const api = {
|
|
8819
9163
|
system: (definition) => registerSystem(ctx, definition),
|
|
9164
|
+
hold: (feature) => holdFeature(ctx, feature),
|
|
9165
|
+
isActive: (feature) => isFeatureActive(ctx, feature),
|
|
8820
9166
|
spawn: (owner, components) => {
|
|
8821
9167
|
const entity = reserveEntity(state, owner);
|
|
8822
9168
|
queueOrApply(ctx, {
|
|
@@ -8920,6 +9266,15 @@ function createEcsApi(ctx) {
|
|
|
8920
9266
|
},
|
|
8921
9267
|
clear: () => {
|
|
8922
9268
|
clearWorldState(ctx);
|
|
9269
|
+
},
|
|
9270
|
+
viewCame: (projection) => {
|
|
9271
|
+
viewCame(state.scope, projection);
|
|
9272
|
+
},
|
|
9273
|
+
viewLeft: (projection) => {
|
|
9274
|
+
viewLeft(state.scope, projection);
|
|
9275
|
+
},
|
|
9276
|
+
viewsCleared: () => {
|
|
9277
|
+
viewsCleared(state.scope);
|
|
8923
9278
|
}
|
|
8924
9279
|
};
|
|
8925
9280
|
return api;
|
|
@@ -9033,21 +9388,29 @@ function registerEach(ctx, feature, key, accepts, register) {
|
|
|
9033
9388
|
}
|
|
9034
9389
|
/**
|
|
9035
9390
|
* Registers what every feature brought, in feature order: components first, so their schema is
|
|
9036
|
-
* known, then systems, then projections.
|
|
9391
|
+
* known, then systems, then projections. A feature's index is its place in the list: the systems
|
|
9392
|
+
* of a feature with `systemsRun: "active"` carry it as their scope, and each projection is counted
|
|
9393
|
+
* for it (rule V). A dev build warns about a feature nothing can make active.
|
|
9037
9394
|
*
|
|
9038
9395
|
* @param ctx - Domain context of the world plugin.
|
|
9039
|
-
* @param ecs - The ecs module.
|
|
9040
9396
|
* @param projection - The projection module.
|
|
9041
9397
|
*/
|
|
9042
|
-
function registerFeatures(ctx,
|
|
9043
|
-
|
|
9398
|
+
function registerFeatures(ctx, projection) {
|
|
9399
|
+
const projections = ctx.state.ecs.scope.projections;
|
|
9400
|
+
for (const [at, feature] of ctx.deps.flow.features.all().entries()) {
|
|
9401
|
+
const scope = scopeOf(ctx, at, feature);
|
|
9044
9402
|
registerEach(ctx, feature, "components", isComponentEntry, (entry) => registerType(ctx, entry));
|
|
9045
|
-
registerEach(ctx, feature, "systems", isSystemEntry, (entry) =>
|
|
9046
|
-
registerEach(ctx, feature, "projections", isProjectionEntry, (entry) =>
|
|
9403
|
+
registerEach(ctx, feature, "systems", isSystemEntry, (entry) => registerSystem(ctx, entry, scope));
|
|
9404
|
+
registerEach(ctx, feature, "projections", isProjectionEntry, (entry) => {
|
|
9405
|
+
projection.register(entry);
|
|
9406
|
+
projections.set(entry.name, at);
|
|
9407
|
+
});
|
|
9408
|
+
if (typeof __MOKU_GAME_DEV__ !== "undefined" && __MOKU_GAME_DEV__) reportNeverActive(ctx, feature);
|
|
9047
9409
|
}
|
|
9048
9410
|
}
|
|
9049
9411
|
/**
|
|
9050
|
-
* Connects the world in `onStart`: it
|
|
9412
|
+
* Connects the world in `onStart`: it sizes the scope to the features, reads every feature
|
|
9413
|
+
* description, registers the five frame
|
|
9051
9414
|
* callbacks of the phases and listens to the released hints. In a dev build the `signals`
|
|
9052
9415
|
* callback records the frame history before it clears the change sets; in an agent build the
|
|
9053
9416
|
* timing table starts with the page clock of `time` and the `input` callback zeroes it each
|
|
@@ -9068,7 +9431,8 @@ function connectWorld(ctx) {
|
|
|
9068
9431
|
const clock = time.agentClock();
|
|
9069
9432
|
if (clock !== void 0) startTiming(worldCtx.state.ecs, clock);
|
|
9070
9433
|
}
|
|
9071
|
-
|
|
9434
|
+
openScope(worldCtx);
|
|
9435
|
+
registerFeatures(worldCtx, projection);
|
|
9072
9436
|
ecs.resource(Now).now = clock.now();
|
|
9073
9437
|
worldCtx.state.ecs.offFrame.push(ecs.onOwnerLeft((owner) => projection.ownerLeft(owner)), time.onFrame("input", (frame) => {
|
|
9074
9438
|
ecs.resource(Now).now = clock.now();
|
|
@@ -9086,7 +9450,8 @@ function connectWorld(ctx) {
|
|
|
9086
9450
|
}
|
|
9087
9451
|
/**
|
|
9088
9452
|
* 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 frame history
|
|
9453
|
+
* every track, view, queued view, entity and resource is dropped, the scope and the frame history
|
|
9454
|
+
* are reset.
|
|
9090
9455
|
* No `onRemoved` fires, because `renderer` stopped earlier and destroyed its own objects.
|
|
9091
9456
|
*
|
|
9092
9457
|
* @param state - The plugin state, the only thing a teardown context carries.
|
|
@@ -9137,6 +9502,7 @@ function clearWorld(state) {
|
|
|
9137
9502
|
state.ecs.mode = "live";
|
|
9138
9503
|
state.ecs.timing = void 0;
|
|
9139
9504
|
state.ecs.timingSlots.clear();
|
|
9505
|
+
resetScope(state.ecs.scope);
|
|
9140
9506
|
resetHistory(state.ecs);
|
|
9141
9507
|
}
|
|
9142
9508
|
//#endregion
|
|
@@ -10049,7 +10415,7 @@ function despawnView(pctx, mounted, view) {
|
|
|
10049
10415
|
view.handles = [];
|
|
10050
10416
|
cancelTracks(pctx, view.entity);
|
|
10051
10417
|
const state = pctx.ctx.state.projection;
|
|
10052
|
-
state.byEntity.delete(view.entity);
|
|
10418
|
+
if (state.byEntity.delete(view.entity)) pctx.deps.views.left(view.projection);
|
|
10053
10419
|
state.mutes.delete(view.entity);
|
|
10054
10420
|
state.rests.delete(view.entity);
|
|
10055
10421
|
state.leaving.set(view.entity, view.cause);
|
|
@@ -10425,6 +10791,7 @@ function enterEntry(pctx, spec, mounted, entry) {
|
|
|
10425
10791
|
const view = createViewRecord(entity, spec.name, entry.key, entry.item, values, entry.direct ? "mount" : "change");
|
|
10426
10792
|
mounted.live.set(entry.key, view);
|
|
10427
10793
|
pctx.ctx.state.projection.byEntity.set(entity, view);
|
|
10794
|
+
pctx.deps.views.came(spec.name);
|
|
10428
10795
|
pctx.ctx.state.projection.membershipVersion += 1;
|
|
10429
10796
|
const hook = spec.motion?.enter;
|
|
10430
10797
|
const loop = spec.motion?.loop;
|
|
@@ -11237,6 +11604,7 @@ function createProjectionApi(ctx, deps) {
|
|
|
11237
11604
|
state.mounted.clear();
|
|
11238
11605
|
state.specs.clear();
|
|
11239
11606
|
state.byEntity.clear();
|
|
11607
|
+
pctx.deps.views.cleared();
|
|
11240
11608
|
state.mutes.clear();
|
|
11241
11609
|
state.rests.clear();
|
|
11242
11610
|
state.keys.clear();
|
|
@@ -11276,7 +11644,12 @@ function createModules$2(ctx) {
|
|
|
11276
11644
|
ecs,
|
|
11277
11645
|
projection: createProjectionApi(ctx, {
|
|
11278
11646
|
ecs,
|
|
11279
|
-
components: WORLD_COMPONENTS
|
|
11647
|
+
components: WORLD_COMPONENTS,
|
|
11648
|
+
views: {
|
|
11649
|
+
came: ecs.viewCame,
|
|
11650
|
+
left: ecs.viewLeft,
|
|
11651
|
+
cleared: ecs.viewsCleared
|
|
11652
|
+
}
|
|
11280
11653
|
})
|
|
11281
11654
|
};
|
|
11282
11655
|
}
|
|
@@ -11311,6 +11684,8 @@ function withKeys(diff, ecs, projection) {
|
|
|
11311
11684
|
function exposeEcs(ecs, projection) {
|
|
11312
11685
|
return {
|
|
11313
11686
|
system: ecs.system,
|
|
11687
|
+
hold: ecs.hold,
|
|
11688
|
+
isActive: ecs.isActive,
|
|
11314
11689
|
spawn: ecs.spawn,
|
|
11315
11690
|
despawn: ecs.despawn,
|
|
11316
11691
|
despawnOwnedBy: ecs.despawnOwnedBy,
|
|
@@ -11432,6 +11807,29 @@ function emptyMap$10() {
|
|
|
11432
11807
|
return /* @__PURE__ */ new Map();
|
|
11433
11808
|
}
|
|
11434
11809
|
/**
|
|
11810
|
+
* Creates the scope of a world that has not started: no feature, no table of owners, typed arrays
|
|
11811
|
+
* of length 0. `onStart` sizes it once to the feature count.
|
|
11812
|
+
*
|
|
11813
|
+
* @returns An empty scope.
|
|
11814
|
+
*/
|
|
11815
|
+
function emptyScope() {
|
|
11816
|
+
return {
|
|
11817
|
+
names: [],
|
|
11818
|
+
index: /* @__PURE__ */ new Map(),
|
|
11819
|
+
projections: /* @__PURE__ */ new Map(),
|
|
11820
|
+
owners: void 0,
|
|
11821
|
+
ownersFailed: false,
|
|
11822
|
+
flow: void 0,
|
|
11823
|
+
onStack: /* @__PURE__ */ new Uint8Array(0),
|
|
11824
|
+
holds: /* @__PURE__ */ new Int32Array(0),
|
|
11825
|
+
views: /* @__PURE__ */ new Int32Array(0),
|
|
11826
|
+
active: /* @__PURE__ */ new Uint8Array(0),
|
|
11827
|
+
dirty: false,
|
|
11828
|
+
scoped: false,
|
|
11829
|
+
generation: 0
|
|
11830
|
+
};
|
|
11831
|
+
}
|
|
11832
|
+
/**
|
|
11435
11833
|
* Creates the initial ecs state: no entity, no type, no system, no resource and no frame history:
|
|
11436
11834
|
* a dev build starts one on its first frame. Every collection is its own object, so two apps in
|
|
11437
11835
|
* one process never share a world.
|
|
@@ -11473,7 +11871,8 @@ function createEcsState() {
|
|
|
11473
11871
|
walkReported: false,
|
|
11474
11872
|
queryPlans: emptyMap$10(),
|
|
11475
11873
|
walkLists: [],
|
|
11476
|
-
despawns: 0
|
|
11874
|
+
despawns: 0,
|
|
11875
|
+
scope: emptyScope()
|
|
11477
11876
|
};
|
|
11478
11877
|
}
|
|
11479
11878
|
//#endregion
|
|
@@ -11585,7 +11984,7 @@ const worldPlugin = /*#__PURE__*/ createPlugin$1("world", {
|
|
|
11585
11984
|
*/
|
|
11586
11985
|
function slotsPadding(slots) {
|
|
11587
11986
|
let pad = 0;
|
|
11588
|
-
for (
|
|
11987
|
+
for (let index = 0; index < slots.length; index += 1) pad += slots[index]?.filter.padding ?? 0;
|
|
11589
11988
|
return pad;
|
|
11590
11989
|
}
|
|
11591
11990
|
/**
|
|
@@ -11745,8 +12144,9 @@ function checkMissedMarks(ctx, state, stage, views, shown) {
|
|
|
11745
12144
|
//#endregion
|
|
11746
12145
|
//#region src/plugins/renderer/demand/shown.ts
|
|
11747
12146
|
/**
|
|
11748
|
-
* The paddings of every filter a node holds
|
|
11749
|
-
*
|
|
12147
|
+
* The paddings of every filter a node holds: what they could spill onto the screen. Pixi holds the enabled filters of
|
|
12148
|
+
* a view (E3), and a filter switched on reaches Pixi before the decision judges the marks again. Pixi answers
|
|
12149
|
+
* `undefined` before the first write and `null` after a clear.
|
|
11750
12150
|
*
|
|
11751
12151
|
* @param node - A display object on the chain of a view.
|
|
11752
12152
|
* @returns The sum of the paddings; 0 without a filter.
|
|
@@ -11755,7 +12155,7 @@ function paddingOf$2(node) {
|
|
|
11755
12155
|
const filters = node.filters;
|
|
11756
12156
|
if (filters === null || filters === void 0) return 0;
|
|
11757
12157
|
let pad = 0;
|
|
11758
|
-
for (
|
|
12158
|
+
for (let index = 0; index < filters.length; index += 1) pad += filters[index]?.padding ?? 0;
|
|
11759
12159
|
return pad;
|
|
11760
12160
|
}
|
|
11761
12161
|
/** What `chainPadding` answers for a chain that is not drawn. */
|
|
@@ -11777,6 +12177,15 @@ function noAlpha() {
|
|
|
11777
12177
|
return false;
|
|
11778
12178
|
}
|
|
11779
12179
|
/**
|
|
12180
|
+
* The reader without the engine's records: Pixi's own `alpha`.
|
|
12181
|
+
*
|
|
12182
|
+
* @param node - A node on the chain of a view.
|
|
12183
|
+
* @returns True at alpha 0 or below.
|
|
12184
|
+
*/
|
|
12185
|
+
function alphaZero(node) {
|
|
12186
|
+
return node.alpha <= 0;
|
|
12187
|
+
}
|
|
12188
|
+
/**
|
|
11780
12189
|
* 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
12190
|
* not fully transparent, and the chain reaches the stage. The paddings of the filters met on the way are summed; no
|
|
11782
12191
|
* object is made.
|
|
@@ -11784,14 +12193,15 @@ function noAlpha() {
|
|
|
11784
12193
|
* @param from - The first node of the walk.
|
|
11785
12194
|
* @param stage - The stage of the application.
|
|
11786
12195
|
* @param through - The kept reader: the padding of a kept top drawn through its quad, or `HIDDEN`.
|
|
12196
|
+
* @param transparent - Whether a node is drawn at alpha 0.
|
|
11787
12197
|
* @param stop - Where the walk notes a visible node at alpha 0 it stopped at; left as it is for any other stop.
|
|
11788
12198
|
* @returns The sum of the paddings of the chain, or `HIDDEN` when the chain is not drawn.
|
|
11789
12199
|
*/
|
|
11790
|
-
function chainPadding(from, stage, through, stop) {
|
|
12200
|
+
function chainPadding(from, stage, through, transparent, stop) {
|
|
11791
12201
|
let pad = 0;
|
|
11792
12202
|
for (let node = from; node !== stage; node = node.parent) {
|
|
11793
12203
|
if (node === null || !node.visible) return HIDDEN;
|
|
11794
|
-
if (node
|
|
12204
|
+
if (transparent(node)) {
|
|
11795
12205
|
if (stop !== void 0) stop.node = node;
|
|
11796
12206
|
return HIDDEN;
|
|
11797
12207
|
}
|
|
@@ -11861,15 +12271,16 @@ function alphaStop(node, top, alphaHidden) {
|
|
|
11861
12271
|
* @param entity - The entity.
|
|
11862
12272
|
* @param through - The kept reader: the padding of a kept top drawn through its quad, or `HIDDEN`; none by default.
|
|
11863
12273
|
* @param alphaHidden - Sync's reader of the `"alpha"` reason; none by default.
|
|
12274
|
+
* @param transparent - Whether a node is drawn at alpha 0; Pixi's `alpha` by default.
|
|
11864
12275
|
* @returns Where the walk stopped.
|
|
11865
12276
|
*/
|
|
11866
|
-
function unseenBy(state, host, views, entity, through = noneThrough, alphaHidden = noAlpha) {
|
|
12277
|
+
function unseenBy(state, host, views, entity, through = noneThrough, alphaHidden = noAlpha, transparent = alphaZero) {
|
|
11867
12278
|
const view = views.get(entity);
|
|
11868
12279
|
const stage = host.stage();
|
|
11869
12280
|
if (view === void 0 || stage === void 0) return "other";
|
|
11870
12281
|
const top = view.wrapper ?? view.object;
|
|
11871
12282
|
state.stop.node = void 0;
|
|
11872
|
-
const pad = chainPadding(top, stage, through, state.stop);
|
|
12283
|
+
const pad = chainPadding(top, stage, through, transparent, state.stop);
|
|
11873
12284
|
if (pad === HIDDEN) return alphaStop(state.stop.node, top, alphaHidden);
|
|
11874
12285
|
if (view.wrapper !== void 0 || top.children.length > 0) return "shown";
|
|
11875
12286
|
return onScreen(state, host, top, pad) ? "shown" : "off-screen";
|
|
@@ -11882,10 +12293,11 @@ function unseenBy(state, host, views, entity, through = noneThrough, alphaHidden
|
|
|
11882
12293
|
* @param views - The views by entity.
|
|
11883
12294
|
* @param entity - The marked entity.
|
|
11884
12295
|
* @param through - The kept reader: the padding of a kept top drawn through its quad, or `HIDDEN`; none by default.
|
|
12296
|
+
* @param transparent - Whether a node is drawn at alpha 0; Pixi's `alpha` by default.
|
|
11885
12297
|
* @returns True when the view is drawn.
|
|
11886
12298
|
*/
|
|
11887
|
-
function isShown$2(state, host, views, entity, through = noneThrough) {
|
|
11888
|
-
return unseenBy(state, host, views, entity, through) === "shown";
|
|
12299
|
+
function isShown$2(state, host, views, entity, through = noneThrough, transparent = alphaZero) {
|
|
12300
|
+
return unseenBy(state, host, views, entity, through, noAlpha, transparent) === "shown";
|
|
11889
12301
|
}
|
|
11890
12302
|
/**
|
|
11891
12303
|
* The ancestor rule of loose marks: the walk of `isShown` from the parent of the view's top node, without bounds. A
|
|
@@ -11896,13 +12308,14 @@ function isShown$2(state, host, views, entity, through = noneThrough) {
|
|
|
11896
12308
|
* @param views - The views by entity.
|
|
11897
12309
|
* @param entity - The entity of a loose mark.
|
|
11898
12310
|
* @param through - The kept reader: the padding of a kept top drawn through its quad, or `HIDDEN`; none by default.
|
|
12311
|
+
* @param transparent - Whether a node is drawn at alpha 0; Pixi's `alpha` by default.
|
|
11899
12312
|
* @returns True when the ancestors of the view draw it.
|
|
11900
12313
|
*/
|
|
11901
|
-
function ancestorsShown(host, views, entity, through = noneThrough) {
|
|
12314
|
+
function ancestorsShown(host, views, entity, through = noneThrough, transparent = alphaZero) {
|
|
11902
12315
|
const view = views.get(entity);
|
|
11903
12316
|
const stage = host.stage();
|
|
11904
12317
|
if (view === void 0 || stage === void 0) return false;
|
|
11905
|
-
return chainPadding((view.wrapper ?? view.object).parent, stage, through) !== HIDDEN;
|
|
12318
|
+
return chainPadding((view.wrapper ?? view.object).parent, stage, through, transparent) !== HIDDEN;
|
|
11906
12319
|
}
|
|
11907
12320
|
//#endregion
|
|
11908
12321
|
//#region src/plugins/renderer/pixels.ts
|
|
@@ -12025,6 +12438,15 @@ function firstWhole(wholes) {
|
|
|
12025
12438
|
return wholes.size === 0 ? void 0 : wholes.values().next().value;
|
|
12026
12439
|
}
|
|
12027
12440
|
/**
|
|
12441
|
+
* Whether a node is drawn at alpha 0, read from Pixi: the reader of a demand module built without sync's records.
|
|
12442
|
+
*
|
|
12443
|
+
* @param node - A node on the chain of a view.
|
|
12444
|
+
* @returns True at alpha 0 or below.
|
|
12445
|
+
*/
|
|
12446
|
+
function pixiTransparent(node) {
|
|
12447
|
+
return node.alpha <= 0;
|
|
12448
|
+
}
|
|
12449
|
+
/**
|
|
12028
12450
|
* Tells whether one entity of a mark list passes a test, walked by index: no iterator.
|
|
12029
12451
|
*
|
|
12030
12452
|
* @param entities - The marks.
|
|
@@ -12067,11 +12489,12 @@ function clearMarks(state) {
|
|
|
12067
12489
|
* @param deps.host - The host module.
|
|
12068
12490
|
* @param deps.views - The views by entity: `ctx.state.sync.views`, whose identity sync keeps.
|
|
12069
12491
|
* @param deps.hiddenBy - Sync's `hiddenBy`: whether one reason hides a view (spec 26 R1.7.1).
|
|
12492
|
+
* @param deps.transparent - Sync's reader of a node drawn at alpha 0 from its records (E4); Pixi's `alpha` without it.
|
|
12070
12493
|
* @returns The demand module.
|
|
12071
12494
|
*/
|
|
12072
12495
|
function createDemandApi(ctx, deps) {
|
|
12073
12496
|
const state = ctx.state.demand;
|
|
12074
|
-
const { host, views, hiddenBy } = deps;
|
|
12497
|
+
const { host, views, hiddenBy, transparent = pixiTransparent } = deps;
|
|
12075
12498
|
/**
|
|
12076
12499
|
* Whether the render phase decides this frame: `onDemand` is on, or the pixel oracle runs in shadow.
|
|
12077
12500
|
*
|
|
@@ -12084,7 +12507,7 @@ function createDemandApi(ctx, deps) {
|
|
|
12084
12507
|
* @param entity - The entity.
|
|
12085
12508
|
* @returns True when its view is drawn as the frame stands.
|
|
12086
12509
|
*/
|
|
12087
|
-
const shown = (entity) => isShown$2(state, host, views, entity, through);
|
|
12510
|
+
const shown = (entity) => isShown$2(state, host, views, entity, through, transparent);
|
|
12088
12511
|
/**
|
|
12089
12512
|
* 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
12513
|
*
|
|
@@ -12110,7 +12533,7 @@ function createDemandApi(ctx, deps) {
|
|
|
12110
12533
|
* @param entity - The entity of a loose mark.
|
|
12111
12534
|
* @returns True when it counts.
|
|
12112
12535
|
*/
|
|
12113
|
-
const looseShown = (entity) => ancestorsShown(host, views, entity, through);
|
|
12536
|
+
const looseShown = (entity) => ancestorsShown(host, views, entity, through, transparent);
|
|
12114
12537
|
/**
|
|
12115
12538
|
* Notes an entity in the record since the last drawn frame, in a dev or an agent build.
|
|
12116
12539
|
*
|
|
@@ -12226,7 +12649,7 @@ function createDemandApi(ctx, deps) {
|
|
|
12226
12649
|
settle,
|
|
12227
12650
|
frame: () => state.frame,
|
|
12228
12651
|
shown,
|
|
12229
|
-
unseenBy: (entity) => unseenBy(state, host, views, entity, through, alphaHidden),
|
|
12652
|
+
unseenBy: (entity) => unseenBy(state, host, views, entity, through, alphaHidden, transparent),
|
|
12230
12653
|
marked: () => state.marked,
|
|
12231
12654
|
loose: () => state.loose,
|
|
12232
12655
|
visual: () => state.visual,
|
|
@@ -12617,9 +13040,60 @@ const PROGRAM_NAME = "sdf-shader-single-alpha";
|
|
|
12617
13040
|
* ```
|
|
12618
13041
|
*/
|
|
12619
13042
|
function scaleOf$1(linear) {
|
|
12620
|
-
|
|
13043
|
+
const { a, b, c, d } = linear;
|
|
13044
|
+
return (Math.sqrt(a * a + b * b) + Math.sqrt(c * c + d * d)) / 2;
|
|
12621
13045
|
}
|
|
12622
13046
|
/**
|
|
13047
|
+
* The key the state of a text's `uDistance` hangs under on its uniforms: a symbol, not enumerable, so Pixi's walk of
|
|
13048
|
+
* the uniforms (`for…in` on WebGL) and a deep compare see the four uniforms alone.
|
|
13049
|
+
*/
|
|
13050
|
+
const DISTANCE = Symbol("sdf-text-distance");
|
|
13051
|
+
/**
|
|
13052
|
+
* Pixi's read of `uDistance`, at every upload: the distance per unit of scale at the text's world scale, or Pixi's last
|
|
13053
|
+
* write without one, times the renderer's resolution.
|
|
13054
|
+
*
|
|
13055
|
+
* @param this - The text's uniforms.
|
|
13056
|
+
* @returns The distance in device pixels.
|
|
13057
|
+
*/
|
|
13058
|
+
function readDistance() {
|
|
13059
|
+
const state = this[DISTANCE];
|
|
13060
|
+
const perScale = state.cell[1] ?? 0;
|
|
13061
|
+
const text = state.text;
|
|
13062
|
+
const distance = (text === void 0 || perScale === 0 ? state.cell[0] ?? 0 : perScale * scaleOf$1(text.worldTransform)) * state.density.resolution;
|
|
13063
|
+
const answer = state.answer;
|
|
13064
|
+
if (answer !== void 0 && distance === state.cell[2]) return answer;
|
|
13065
|
+
state.cell[2] = distance;
|
|
13066
|
+
state.answer = distance;
|
|
13067
|
+
return distance;
|
|
13068
|
+
}
|
|
13069
|
+
/**
|
|
13070
|
+
* Pixi's write of `uDistance`, at a layout: the CSS distance and, while the pipe names the text, the distance per unit
|
|
13071
|
+
* of its group scale.
|
|
13072
|
+
*
|
|
13073
|
+
* @param this - The text's uniforms.
|
|
13074
|
+
* @param value - Pixi's distance, CSS px.
|
|
13075
|
+
*/
|
|
13076
|
+
function writeDistance(value) {
|
|
13077
|
+
const state = this[DISTANCE];
|
|
13078
|
+
const text = state.writing.text;
|
|
13079
|
+
const scale = text === void 0 ? 0 : scaleOf$1(text.groupTransform);
|
|
13080
|
+
state.cell[0] = value;
|
|
13081
|
+
state.cell[1] = scale > 0 ? value / scale : 0;
|
|
13082
|
+
state.text = text;
|
|
13083
|
+
}
|
|
13084
|
+
/**
|
|
13085
|
+
* The `uDistance` of every text's uniforms: one pair of functions, so every uniforms object has the same hidden class
|
|
13086
|
+
* and Pixi's generated sync function reads it monomorphic. A pair made per text, or an accessor laid over Pixi's data
|
|
13087
|
+
* property, sends each object to dictionary mode. The getter hands a still text's answer back boxed once (`answer`):
|
|
13088
|
+
* Pixi's sync may not inline it.
|
|
13089
|
+
*/
|
|
13090
|
+
const DISTANCE_ACCESSOR = {
|
|
13091
|
+
enumerable: true,
|
|
13092
|
+
configurable: true,
|
|
13093
|
+
get: readDistance,
|
|
13094
|
+
set: writeDistance
|
|
13095
|
+
};
|
|
13096
|
+
/**
|
|
12623
13097
|
* The local uniforms of one text, in the order of the WGSL struct, at Pixi's starting values. The
|
|
12624
13098
|
* pipe writes `uDistance`, the graphics adaptor the rest, before every draw.
|
|
12625
13099
|
*
|
|
@@ -12639,6 +13113,9 @@ function scaleOf$1(linear) {
|
|
|
12639
13113
|
* keeps the distance per unit of the group scale it was made with; a read answers that times the
|
|
12640
13114
|
* text's world scale. A write without a text, or at a group scale of 0, is answered as Pixi wrote it.
|
|
12641
13115
|
*
|
|
13116
|
+
* The uniforms object is the renderer's own, made in one shape for every text with the one accessor pair
|
|
13117
|
+
* `DISTANCE_ACCESSOR`, in place of the one Pixi's group made.
|
|
13118
|
+
*
|
|
12642
13119
|
* @param pixi - The loaded Pixi module.
|
|
12643
13120
|
* @param density - What the device pixels per CSS pixel are read from at each draw: the renderer.
|
|
12644
13121
|
* @param density.resolution - The renderer's resolution, read at each upload.
|
|
@@ -12669,21 +13146,22 @@ function createLocalUniforms(pixi, density, writing) {
|
|
|
12669
13146
|
type: "f32"
|
|
12670
13147
|
}
|
|
12671
13148
|
});
|
|
12672
|
-
|
|
12673
|
-
|
|
12674
|
-
|
|
12675
|
-
|
|
12676
|
-
|
|
12677
|
-
|
|
12678
|
-
|
|
12679
|
-
|
|
12680
|
-
|
|
12681
|
-
|
|
12682
|
-
|
|
12683
|
-
|
|
12684
|
-
|
|
12685
|
-
|
|
12686
|
-
});
|
|
13149
|
+
const values = group.uniforms;
|
|
13150
|
+
const state = {
|
|
13151
|
+
cell: Float64Array.of(4, 0, NaN),
|
|
13152
|
+
text: void 0,
|
|
13153
|
+
writing,
|
|
13154
|
+
density,
|
|
13155
|
+
answer: void 0
|
|
13156
|
+
};
|
|
13157
|
+
const uniforms = {
|
|
13158
|
+
uColor: values.uColor,
|
|
13159
|
+
uTransformMatrix: values.uTransformMatrix,
|
|
13160
|
+
uRound: values.uRound
|
|
13161
|
+
};
|
|
13162
|
+
Object.defineProperty(uniforms, "uDistance", DISTANCE_ACCESSOR);
|
|
13163
|
+
Object.defineProperty(uniforms, DISTANCE, { value: state });
|
|
13164
|
+
group.uniforms = uniforms;
|
|
12687
13165
|
return group;
|
|
12688
13166
|
}
|
|
12689
13167
|
/**
|
|
@@ -14018,8 +14496,9 @@ function endLoan(inputs) {
|
|
|
14018
14496
|
* @returns The slot and its inputs, or `undefined`.
|
|
14019
14497
|
*/
|
|
14020
14498
|
function firstInputs(slots) {
|
|
14021
|
-
for (
|
|
14022
|
-
|
|
14499
|
+
for (let index = 0; index < slots.length; index += 1) {
|
|
14500
|
+
const slot = slots[index];
|
|
14501
|
+
if (slot === void 0 || !slot.filter.enabled) continue;
|
|
14023
14502
|
return slot.inputs === void 0 ? void 0 : {
|
|
14024
14503
|
slot,
|
|
14025
14504
|
inputs: slot.inputs
|
|
@@ -14256,7 +14735,11 @@ function renderStage3(kctx, pixi, matrices, record, now) {
|
|
|
14256
14735
|
key[KEY.s3Tx] = now.tx;
|
|
14257
14736
|
key[KEY.s3Ty] = now.ty;
|
|
14258
14737
|
let passes = 1;
|
|
14259
|
-
|
|
14738
|
+
const slots = record.slots;
|
|
14739
|
+
for (let index = 0; index < slots.length; index += 1) {
|
|
14740
|
+
const slot = slots[index];
|
|
14741
|
+
if (slot?.filter.enabled === true) passes += slot.passes;
|
|
14742
|
+
}
|
|
14260
14743
|
kctx.state.flushPasses += passes;
|
|
14261
14744
|
return true;
|
|
14262
14745
|
}
|
|
@@ -14662,7 +15145,8 @@ function createKeptState(config) {
|
|
|
14662
15145
|
warnedBudget: false,
|
|
14663
15146
|
verifySkipped: 0,
|
|
14664
15147
|
bypassed: false,
|
|
14665
|
-
flushPasses: 0
|
|
15148
|
+
flushPasses: 0,
|
|
15149
|
+
calm: false
|
|
14666
15150
|
};
|
|
14667
15151
|
}
|
|
14668
15152
|
/**
|
|
@@ -14798,11 +15282,12 @@ function readStack(slots, resolution, into) {
|
|
|
14798
15282
|
let enabled = 0;
|
|
14799
15283
|
let rs = Number.POSITIVE_INFINITY;
|
|
14800
15284
|
let padding = 0;
|
|
14801
|
-
for (
|
|
14802
|
-
|
|
15285
|
+
for (let index = 0; index < slots.length; index += 1) {
|
|
15286
|
+
const filter = slots[index]?.filter;
|
|
15287
|
+
if (filter === void 0 || !filter.enabled) continue;
|
|
14803
15288
|
enabled += 1;
|
|
14804
|
-
rs = Math.min(rs, resolutionOf(
|
|
14805
|
-
padding +=
|
|
15289
|
+
rs = Math.min(rs, resolutionOf(filter, resolution));
|
|
15290
|
+
padding += filter.padding;
|
|
14806
15291
|
}
|
|
14807
15292
|
into.enabled = enabled;
|
|
14808
15293
|
into.rs = enabled === 0 ? resolution : rs;
|
|
@@ -14834,8 +15319,9 @@ function holdsFilters(node) {
|
|
|
14834
15319
|
* @returns True when they do.
|
|
14835
15320
|
*/
|
|
14836
15321
|
function plainFilters(slots) {
|
|
14837
|
-
for (
|
|
14838
|
-
const
|
|
15322
|
+
for (let index = 0; index < slots.length; index += 1) {
|
|
15323
|
+
const filter = slots[index]?.filter;
|
|
15324
|
+
if (filter === void 0) continue;
|
|
14839
15325
|
if (filter.enabled && (filter.blendRequired || !plainBlend(filter.blendMode))) return false;
|
|
14840
15326
|
}
|
|
14841
15327
|
return true;
|
|
@@ -15126,16 +15612,18 @@ function neededBytes(record, now, box) {
|
|
|
15126
15612
|
return bytes;
|
|
15127
15613
|
}
|
|
15128
15614
|
/**
|
|
15129
|
-
* Hands the filters of a live record from its top to its stand-in, before stage 1 renders the top without them
|
|
15615
|
+
* Hands the filters of a live record from its top to its stand-in, before stage 1 renders the top without them: the
|
|
15616
|
+
* enabled ones, as `sync` hangs them (E3).
|
|
15130
15617
|
*
|
|
15131
15618
|
* @param kctx - The kept context.
|
|
15132
15619
|
* @param pixi - The module that draws.
|
|
15133
15620
|
* @param record - The record.
|
|
15621
|
+
* @param view - Its view.
|
|
15134
15622
|
*/
|
|
15135
|
-
function handFilters(kctx, pixi, record) {
|
|
15623
|
+
function handFilters(kctx, pixi, record, view) {
|
|
15136
15624
|
const standIn = standInOf(pixi, record);
|
|
15137
15625
|
kctx.sync.clearFilters(record.top);
|
|
15138
|
-
|
|
15626
|
+
kctx.sync.applyFilters(view, filtersOf$1(kctx.ctx.state.sync, record.entity), noMark$1, standIn);
|
|
15139
15627
|
}
|
|
15140
15628
|
/**
|
|
15141
15629
|
* Writes the take keys of a record: the linear part, the stack resolution and the translation of this frame, and the
|
|
@@ -15240,7 +15728,7 @@ function isEmptyBounds(bounds) {
|
|
|
15240
15728
|
*/
|
|
15241
15729
|
function renderStages(kctx, pixi, matrices, record, view, now) {
|
|
15242
15730
|
const live = record.mode === "live";
|
|
15243
|
-
if (live) handFilters(kctx, pixi, record);
|
|
15731
|
+
if (live) handFilters(kctx, pixi, record, view);
|
|
15244
15732
|
const first = firstInputs(record.slots);
|
|
15245
15733
|
let ok = renderStage1(kctx, pixi, matrices, record);
|
|
15246
15734
|
if (ok && first !== void 0) renderStage2(kctx, pixi, matrices, record, {
|
|
@@ -15538,6 +16026,23 @@ function drawingChanged(kctx, record, before) {
|
|
|
15538
16026
|
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
16027
|
}
|
|
15540
16028
|
/**
|
|
16029
|
+
* The frame of a kept record in a calm frame (no mark, no whole-frame reason) that owes nothing: no dirty flag, no
|
|
16030
|
+
* settle retake, no churn, not bypassed. Its step would find every key as it stands and its quad where it is, so it is
|
|
16031
|
+
* counted as drawn from its picture and left alone (E5).
|
|
16032
|
+
*
|
|
16033
|
+
* @param kctx - The kept context.
|
|
16034
|
+
* @param record - The record.
|
|
16035
|
+
* @returns True when the record was drawn as it stands; false when it is to be stepped.
|
|
16036
|
+
*/
|
|
16037
|
+
function stepCalm(kctx, record) {
|
|
16038
|
+
const { state } = kctx;
|
|
16039
|
+
if (!state.calm || state.bypassed || record.mode !== "kept" || record.dirtyS1 || record.dirtyS2 || record.dirtyS3 || record.settle || record.churn !== 0) return false;
|
|
16040
|
+
record.lastDrawn = state.frame;
|
|
16041
|
+
state.drawnPictures += 1;
|
|
16042
|
+
record.quiet += 1;
|
|
16043
|
+
return true;
|
|
16044
|
+
}
|
|
16045
|
+
/**
|
|
15541
16046
|
* One drawn frame of a record, inside the flush: eligibility, the keys of this frame, the policy of its life, the quad
|
|
15542
16047
|
* of a kept one; the dirty flags are cleared.
|
|
15543
16048
|
*
|
|
@@ -15639,7 +16144,7 @@ function bypassRecord(kctx, record, view, live) {
|
|
|
15639
16144
|
return;
|
|
15640
16145
|
}
|
|
15641
16146
|
kctx.sync.clearFilters(record.top);
|
|
15642
|
-
if (record.standIn !== void 0)
|
|
16147
|
+
if (record.standIn !== void 0) kctx.sync.applyFilters(view, filters, noMark$1, record.standIn);
|
|
15643
16148
|
kctx.sync.swapHidden(kctx.hidden, view, "kept", true);
|
|
15644
16149
|
if (record.quad !== void 0) record.quad.visible = record.top.visible && !kctx.sync.hiddenBy(view, "alpha") && !kctx.sync.hiddenBy(view, "cover");
|
|
15645
16150
|
}
|
|
@@ -15849,7 +16354,7 @@ function stepRecords(kctx, pixi, matrices) {
|
|
|
15849
16354
|
continue;
|
|
15850
16355
|
}
|
|
15851
16356
|
const view = kctx.ctx.state.sync.views.get(record.entity);
|
|
15852
|
-
if (view !== void 0) stepRecord(kctx, pixi, matrices, record, view);
|
|
16357
|
+
if (view !== void 0 && !stepCalm(kctx, record)) stepRecord(kctx, pixi, matrices, record, view);
|
|
15853
16358
|
}
|
|
15854
16359
|
}
|
|
15855
16360
|
/**
|
|
@@ -15902,9 +16407,10 @@ function createKeptApi(ctx, deps) {
|
|
|
15902
16407
|
const bypass = (on) => {
|
|
15903
16408
|
if (state.bypassed === on) return;
|
|
15904
16409
|
state.bypassed = on;
|
|
15905
|
-
for (
|
|
15906
|
-
const
|
|
15907
|
-
|
|
16410
|
+
for (let index = 0; index < state.records.length; index += 1) {
|
|
16411
|
+
const record = state.records[index];
|
|
16412
|
+
const view = record === void 0 ? void 0 : viewOf(record);
|
|
16413
|
+
if (record?.mode === "kept" && view !== void 0) bypassRecord(kctx, record, view, on);
|
|
15908
16414
|
}
|
|
15909
16415
|
};
|
|
15910
16416
|
/**
|
|
@@ -15913,7 +16419,9 @@ function createKeptApi(ctx, deps) {
|
|
|
15913
16419
|
*/
|
|
15914
16420
|
const sortRecords = () => {
|
|
15915
16421
|
let changed = state.unsorted;
|
|
15916
|
-
for (
|
|
16422
|
+
for (let index = 0; index < state.records.length; index += 1) {
|
|
16423
|
+
const record = state.records[index];
|
|
16424
|
+
if (record === void 0) continue;
|
|
15917
16425
|
const depth = depthOf(record.top);
|
|
15918
16426
|
if (depth !== record.depth) {
|
|
15919
16427
|
record.depth = depth;
|
|
@@ -15982,6 +16490,7 @@ function createKeptApi(ctx, deps) {
|
|
|
15982
16490
|
if (wholes.has("debug") || wholes.has("restore")) for (const record of state.records) record.dirtyS1 = true;
|
|
15983
16491
|
const marked = deps.demand.marked();
|
|
15984
16492
|
const loose = deps.demand.loose();
|
|
16493
|
+
state.calm = wholes.size === 0 && marked.size === 0 && loose.size === 0;
|
|
15985
16494
|
for (let index = 0; index < marked.size; index += 1) collectOne(marked.at(index));
|
|
15986
16495
|
for (let index = 0; index < loose.size; index += 1) collectOne(loose.at(index));
|
|
15987
16496
|
},
|
|
@@ -15995,6 +16504,7 @@ function createKeptApi(ctx, deps) {
|
|
|
15995
16504
|
if (state.on) {
|
|
15996
16505
|
sortRecords();
|
|
15997
16506
|
stepRecords(kctx, pixi, matrices);
|
|
16507
|
+
state.calm = false;
|
|
15998
16508
|
} else for (const record of state.records) if (record.mode === "kept") live(record);
|
|
15999
16509
|
flushRanges(kctx);
|
|
16000
16510
|
},
|
|
@@ -16379,7 +16889,7 @@ const ALL_SECTIONS = Object.freeze({
|
|
|
16379
16889
|
* @param next - The switches asked for; `undefined` for none.
|
|
16380
16890
|
* @returns The sections, frozen; `base` itself when nothing differs.
|
|
16381
16891
|
*/
|
|
16382
|
-
function switched(base, next) {
|
|
16892
|
+
function switched$1(base, next) {
|
|
16383
16893
|
if (next === void 0) return base;
|
|
16384
16894
|
const sections = { ...base };
|
|
16385
16895
|
let changed = false;
|
|
@@ -18211,7 +18721,7 @@ function createSinks(draws) {
|
|
|
18211
18721
|
*/
|
|
18212
18722
|
function createPerfState(config, sinks) {
|
|
18213
18723
|
const events = createEventRing();
|
|
18214
|
-
const sections = switched(ALL_SECTIONS, config.sections);
|
|
18724
|
+
const sections = switched$1(ALL_SECTIONS, config.sections);
|
|
18215
18725
|
sinks.sections = bitsOf(sections);
|
|
18216
18726
|
sinks.sectionsNext = sinks.sections;
|
|
18217
18727
|
return {
|
|
@@ -20959,7 +21469,7 @@ function stopPerf(perf, sinks) {
|
|
|
20959
21469
|
*/
|
|
20960
21470
|
function switchSections(pc, next) {
|
|
20961
21471
|
const { perf, sinks } = pc;
|
|
20962
|
-
const sections = switched(perf.sections, next);
|
|
21472
|
+
const sections = switched$1(perf.sections, next);
|
|
20963
21473
|
if (sections === perf.sections) return sections;
|
|
20964
21474
|
perf.sections = sections;
|
|
20965
21475
|
sinks.sectionsNext = bitsOf(sections);
|
|
@@ -20980,7 +21490,7 @@ const READ_ONLY_SECTIONS = "[game] monitor.sections switches the sections only o
|
|
|
20980
21490
|
*/
|
|
20981
21491
|
function refuseSections(pc, next) {
|
|
20982
21492
|
const { sections } = pc.perf;
|
|
20983
|
-
if (switched(sections, next) === sections) return sections;
|
|
21493
|
+
if (switched$1(sections, next) === sections) return sections;
|
|
20984
21494
|
throw Object.assign(/* @__PURE__ */ new Error(READ_ONLY_SECTIONS), { reason: "read-only" });
|
|
20985
21495
|
}
|
|
20986
21496
|
//#endregion
|
|
@@ -21545,6 +22055,23 @@ function hiddenBy(view, reason) {
|
|
|
21545
22055
|
return (view[maskOf(view, reason)] & BITS[reason]) !== 0;
|
|
21546
22056
|
}
|
|
21547
22057
|
/**
|
|
22058
|
+
* 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
|
|
22059
|
+
* hands its double back boxed at every read of a walk of `shown`. The top of a built-in visual, its wrapper or its
|
|
22060
|
+
* object, draws at alpha 0 exactly when the `"alpha"` reason hides it, and so does an adapter's own object. A node of no
|
|
22061
|
+
* view on a chain is the engine's (a clip container, a layer, the root), never faded. A game's object and the wrapper
|
|
22062
|
+
* of an adapter carry an alpha the engine does not hold: Pixi's is read.
|
|
22063
|
+
*
|
|
22064
|
+
* @param state - The sync state.
|
|
22065
|
+
* @param node - A node on the chain of a view, below the stage.
|
|
22066
|
+
* @returns True at alpha 0.
|
|
22067
|
+
*/
|
|
22068
|
+
function transparentNode(state, node) {
|
|
22069
|
+
const view = state.viewOfTop.get(node);
|
|
22070
|
+
if (view === void 0) return false;
|
|
22071
|
+
if (view.kind === "Display" || view.kind === "adapter" && node === view.wrapper) return node.alpha <= 0;
|
|
22072
|
+
return hiddenBy(view, "alpha");
|
|
22073
|
+
}
|
|
22074
|
+
/**
|
|
21548
22075
|
* Tells whether a node is hidden, and by these reasons alone: spec 23's eligibility of a picture whose ancestor is in
|
|
21549
22076
|
* an overlay's range (`"freeze"`) or is itself a kept top drawn through its quad (`"kept"`).
|
|
21550
22077
|
*
|
|
@@ -21678,6 +22205,8 @@ function clearAdapters(state) {
|
|
|
21678
22205
|
}
|
|
21679
22206
|
//#endregion
|
|
21680
22207
|
//#region src/plugins/renderer/sync/filters.ts
|
|
22208
|
+
/** What a display object without filters holds: one shared list, plain, so a still frame's compare makes none. */
|
|
22209
|
+
const NO_FILTERS = [];
|
|
21681
22210
|
/**
|
|
21682
22211
|
* The filters a display object holds now. Pixi answers `undefined` before the first write and
|
|
21683
22212
|
* `null` after a clear, which its getter type does not say.
|
|
@@ -21686,10 +22215,11 @@ function clearAdapters(state) {
|
|
|
21686
22215
|
* @returns The filters, empty when there are none.
|
|
21687
22216
|
*/
|
|
21688
22217
|
function heldFilters(target) {
|
|
21689
|
-
return target.filters ??
|
|
22218
|
+
return target.filters ?? NO_FILTERS;
|
|
21690
22219
|
}
|
|
21691
22220
|
/**
|
|
21692
|
-
* Tells whether a display object holds exactly these
|
|
22221
|
+
* Tells whether a display object holds exactly the enabled filters of these slots, in their order (E3): Pixi is handed
|
|
22222
|
+
* no disabled filter, which draws nothing but costs a filter push and pop, two batch breaks, every frame.
|
|
21693
22223
|
*
|
|
21694
22224
|
* @param target - The display object.
|
|
21695
22225
|
* @param slots - The slots to compare with.
|
|
@@ -21697,7 +22227,37 @@ function heldFilters(target) {
|
|
|
21697
22227
|
*/
|
|
21698
22228
|
function holds(target, slots) {
|
|
21699
22229
|
const held = heldFilters(target);
|
|
21700
|
-
|
|
22230
|
+
let at = 0;
|
|
22231
|
+
for (let index = 0; index < slots.length; index += 1) {
|
|
22232
|
+
const filter = slots[index]?.filter;
|
|
22233
|
+
if (filter === void 0 || !filter.enabled) continue;
|
|
22234
|
+
if (held[at] !== filter) return false;
|
|
22235
|
+
at += 1;
|
|
22236
|
+
}
|
|
22237
|
+
return at === held.length;
|
|
22238
|
+
}
|
|
22239
|
+
/**
|
|
22240
|
+
* The enabled filters of slots, in their order: what Pixi is handed.
|
|
22241
|
+
*
|
|
22242
|
+
* @param slots - The slots.
|
|
22243
|
+
* @returns A new list, or `undefined` when no filter is enabled.
|
|
22244
|
+
*/
|
|
22245
|
+
function enabledFilters(slots) {
|
|
22246
|
+
let enabled;
|
|
22247
|
+
for (const slot of slots) if (slot.filter.enabled) {
|
|
22248
|
+
enabled ??= [];
|
|
22249
|
+
enabled.push(slot.filter);
|
|
22250
|
+
}
|
|
22251
|
+
return enabled;
|
|
22252
|
+
}
|
|
22253
|
+
/**
|
|
22254
|
+
* Writes the enabled filters of slots onto a display object, or clears them when none is enabled.
|
|
22255
|
+
*
|
|
22256
|
+
* @param target - The display object.
|
|
22257
|
+
* @param slots - The slots.
|
|
22258
|
+
*/
|
|
22259
|
+
function writeFilters(target, slots) {
|
|
22260
|
+
target.filters = enabledFilters(slots) ?? null;
|
|
21701
22261
|
}
|
|
21702
22262
|
/**
|
|
21703
22263
|
* Takes every filter off a display object. Nothing is written when it holds none: Pixi copies the
|
|
@@ -21711,8 +22271,10 @@ function clearFilters(target) {
|
|
|
21711
22271
|
/**
|
|
21712
22272
|
* Hangs the filters on a view: on its wrapper when it has one, so they cover the entity's whole
|
|
21713
22273
|
* 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
|
-
*
|
|
22274
|
+
* (`kept.filterTarget`, spec 23 P9.1). Only the enabled ones: a disabled filter draws nothing, and Pixi would push and
|
|
22275
|
+
* pop it around the view every frame (E3); a view whose filters are all disabled holds none. Written only when they
|
|
22276
|
+
* differ from what the target holds, and `before` runs once just before that write: the caller marks the entity there.
|
|
22277
|
+
* A filter switched on or off later is written by `applySwitched` in the frame it was switched.
|
|
21716
22278
|
*
|
|
21717
22279
|
* @param view - The view.
|
|
21718
22280
|
* @param slots - The filters in draw order; empty clears them.
|
|
@@ -21722,7 +22284,7 @@ function clearFilters(target) {
|
|
|
21722
22284
|
function applyFilters(view, slots, before, target = view.wrapper ?? view.object) {
|
|
21723
22285
|
if (holds(target, slots)) return;
|
|
21724
22286
|
before();
|
|
21725
|
-
target
|
|
22287
|
+
writeFilters(target, slots);
|
|
21726
22288
|
}
|
|
21727
22289
|
/**
|
|
21728
22290
|
* The filters kept for an entity.
|
|
@@ -21815,6 +22377,45 @@ function moveFilters(state, entity, view, before, kept) {
|
|
|
21815
22377
|
clearFilters(view.object);
|
|
21816
22378
|
applyFilters(view, slots, before);
|
|
21817
22379
|
}
|
|
22380
|
+
/** What the walk of the switched filters reads, set before the walk: one at a time, no closure per frame. */
|
|
22381
|
+
const switched = {
|
|
22382
|
+
sync: void 0,
|
|
22383
|
+
kept: void 0,
|
|
22384
|
+
wrote: false
|
|
22385
|
+
};
|
|
22386
|
+
/**
|
|
22387
|
+
* Writes the enabled filters of one view again when one of them was switched on or off since they were handed to Pixi.
|
|
22388
|
+
*
|
|
22389
|
+
* @param slots - The filters of the view.
|
|
22390
|
+
* @param entity - The entity.
|
|
22391
|
+
*/
|
|
22392
|
+
function applySwitchedOne(slots, entity) {
|
|
22393
|
+
const { sync, kept } = switched;
|
|
22394
|
+
const view = sync?.views.get(entity);
|
|
22395
|
+
if (view === void 0 || kept === void 0) return;
|
|
22396
|
+
const target = kept.filterTarget(view);
|
|
22397
|
+
if (holds(target, slots)) return;
|
|
22398
|
+
writeFilters(target, slots);
|
|
22399
|
+
switched.wrote = true;
|
|
22400
|
+
}
|
|
22401
|
+
/**
|
|
22402
|
+
* Hands Pixi the filters of every view whose filters were switched on or off since they were written (E3), before the
|
|
22403
|
+
* frame is drawn, so a switch is drawn in the frame it was made. No mark: the writer of a switch tells the renderer
|
|
22404
|
+
* with `invalidate` first. A write moves `passesVersion` on: the paddings the walks of `shown` read changed (E4).
|
|
22405
|
+
*
|
|
22406
|
+
* @param state - The sync branch of the plugin state.
|
|
22407
|
+
* @param kept - The kept module: where each view's filters hang.
|
|
22408
|
+
*/
|
|
22409
|
+
function applySwitched(state, kept) {
|
|
22410
|
+
if (state.filters.size === 0) return;
|
|
22411
|
+
switched.sync = state;
|
|
22412
|
+
switched.kept = kept;
|
|
22413
|
+
switched.wrote = false;
|
|
22414
|
+
state.filters.forEach(applySwitchedOne);
|
|
22415
|
+
if (switched.wrote) state.passesVersion += 1;
|
|
22416
|
+
switched.sync = void 0;
|
|
22417
|
+
switched.kept = void 0;
|
|
22418
|
+
}
|
|
21818
22419
|
/**
|
|
21819
22420
|
* Forgets the filters and the filter area of an entity whose view left, only when the entity itself is gone. A live
|
|
21820
22421
|
* entity keeps them for its next view, in this frame, as a `ui` element that swaps its shape for a
|
|
@@ -25057,6 +25658,7 @@ function applyHud(sctx, entity, view, factor) {
|
|
|
25057
25658
|
if (view.hud === factor) return;
|
|
25058
25659
|
markView(sctx, entity);
|
|
25059
25660
|
view.hud = factor;
|
|
25661
|
+
sctx.ctx.state.sync.passesVersion += 1;
|
|
25060
25662
|
if (ownsAlpha(view)) {
|
|
25061
25663
|
applyAlpha(sctx, entity, view, componentAlpha(sctx, entity, view));
|
|
25062
25664
|
return;
|
|
@@ -25812,6 +26414,7 @@ function dropView(sctx, entity) {
|
|
|
25812
26414
|
state.views.delete(entity);
|
|
25813
26415
|
if (state.filters.has(entity) || state.areas.has(entity)) state.viewlessEntries = true;
|
|
25814
26416
|
state.orphans.delete(entity);
|
|
26417
|
+
state.held.delete(entity);
|
|
25815
26418
|
state.entityOf.delete(view.object);
|
|
25816
26419
|
state.viewOfTop.delete(view.wrapper ?? view.object);
|
|
25817
26420
|
untrackKey(state, entity, view.textureKey);
|
|
@@ -26034,9 +26637,11 @@ function renderFrame(ctx, modules) {
|
|
|
26034
26637
|
if (modules.viewport.applyPending()) {
|
|
26035
26638
|
const root = modules.sync.root();
|
|
26036
26639
|
modules.monitor.changed("resize");
|
|
26640
|
+
ctx.state.sync.passesVersion += 1;
|
|
26037
26641
|
if (root !== void 0) modules.viewport.apply(root);
|
|
26038
26642
|
modules.demand.whole("resize");
|
|
26039
26643
|
}
|
|
26644
|
+
modules.sync.stepFilters();
|
|
26040
26645
|
modules.sync.stepOverlays();
|
|
26041
26646
|
modules.sync.drainOverlayEvents(modules.monitor);
|
|
26042
26647
|
modules.kept.collect();
|
|
@@ -27796,8 +28401,9 @@ function emptyEntitySet$1() {
|
|
|
27796
28401
|
return /* @__PURE__ */ new Set();
|
|
27797
28402
|
}
|
|
27798
28403
|
/**
|
|
27799
|
-
* Where the walk of `shown` stopped for an entity's view, walked
|
|
27800
|
-
* endless loops
|
|
28404
|
+
* Where the walk of `shown` stopped for an entity's view, walked again only when `passesVersion` moved since (E4): an
|
|
28405
|
+
* entity with several endless loops is walked once, and the loops of a still board are answered from their records
|
|
28406
|
+
* (spec 26 R1.7.3). A record is made at an entity's first ask and written in place after.
|
|
27801
28407
|
*
|
|
27802
28408
|
* @param sctx - Domain context of the sync module.
|
|
27803
28409
|
* @param entity - The entity a loop is about to write.
|
|
@@ -27807,9 +28413,16 @@ function unseenOf(sctx, entity) {
|
|
|
27807
28413
|
const state = sctx.ctx.state.sync;
|
|
27808
28414
|
if (state.root === void 0 || !state.views.has(entity)) return "other";
|
|
27809
28415
|
const known = state.held.get(entity);
|
|
27810
|
-
if (known !== void 0) return known;
|
|
28416
|
+
if (known !== void 0 && known.at === state.passesVersion) return known.answer;
|
|
27811
28417
|
const answer = sctx.deps.demand.unseenBy(entity);
|
|
27812
|
-
state.held.set(entity,
|
|
28418
|
+
if (known === void 0) state.held.set(entity, {
|
|
28419
|
+
answer,
|
|
28420
|
+
at: state.passesVersion
|
|
28421
|
+
});
|
|
28422
|
+
else {
|
|
28423
|
+
known.answer = answer;
|
|
28424
|
+
known.at = state.passesVersion;
|
|
28425
|
+
}
|
|
27813
28426
|
return answer;
|
|
27814
28427
|
}
|
|
27815
28428
|
/**
|
|
@@ -28200,7 +28813,6 @@ function createSyncApi(ctx, deps) {
|
|
|
28200
28813
|
* view. A still frame (nothing changed, nothing waits) returns before any work and makes nothing.
|
|
28201
28814
|
*/
|
|
28202
28815
|
const pass = () => {
|
|
28203
|
-
if (state.held.size > 0) state.held.clear();
|
|
28204
28816
|
if (still()) return;
|
|
28205
28817
|
state.passesVersion += 1;
|
|
28206
28818
|
if (state.removed.size > 0) {
|
|
@@ -28347,6 +28959,7 @@ function createSyncApi(ctx, deps) {
|
|
|
28347
28959
|
layerContainer: (name) => layerContainer(state, name),
|
|
28348
28960
|
layerOf: (entity) => layerOf(sctx, entity),
|
|
28349
28961
|
stepOverlays: () => stepOverlays(sctx),
|
|
28962
|
+
stepFilters: () => applySwitched(state, deps.kept),
|
|
28350
28963
|
drainOverlayEvents: (monitor) => drainOverlayEvents(sctx, monitor),
|
|
28351
28964
|
warmStep: () => warmStep(sctx),
|
|
28352
28965
|
onWarm: (fn) => {
|
|
@@ -28861,7 +29474,8 @@ function createModules$1(ctx) {
|
|
|
28861
29474
|
const demand = createDemandApi(ctx, {
|
|
28862
29475
|
host,
|
|
28863
29476
|
views: ctx.state.sync.views,
|
|
28864
|
-
hiddenBy
|
|
29477
|
+
hiddenBy,
|
|
29478
|
+
transparent: (node) => transparentNode(ctx.state.sync, node)
|
|
28865
29479
|
});
|
|
28866
29480
|
const built = {};
|
|
28867
29481
|
const kept = createKeptApi(ctx, {
|
|
@@ -30994,7 +31608,8 @@ function same$1(stored, next) {
|
|
|
30994
31608
|
* every frame all the same.
|
|
30995
31609
|
*
|
|
30996
31610
|
* A frame that moves every field writes the track's own patch, filled in place. A frame that
|
|
30997
|
-
* leaves a field alone
|
|
31611
|
+
* leaves a field alone writes the track's patch of the fields it moves, kept for the next such
|
|
31612
|
+
* frame and built again only when the moved fields change, so no other field is written.
|
|
30998
31613
|
*
|
|
30999
31614
|
* @param actx - Domain context of the anim plugin.
|
|
31000
31615
|
* @param track - The track to write, its normalised time in `clock[1]`.
|
|
@@ -31009,7 +31624,7 @@ function writeFields(actx, track, final) {
|
|
|
31009
31624
|
}
|
|
31010
31625
|
beginTrack(actx, track, stored);
|
|
31011
31626
|
const owned = track.muted();
|
|
31012
|
-
const { names, live, moved, values, seen
|
|
31627
|
+
const { names, live, moved, values, seen } = track;
|
|
31013
31628
|
let fields = 0;
|
|
31014
31629
|
let count = 0;
|
|
31015
31630
|
for (let slot = 0; slot < names.length; slot++) {
|
|
@@ -31029,27 +31644,57 @@ function writeFields(actx, track, final) {
|
|
|
31029
31644
|
continue;
|
|
31030
31645
|
}
|
|
31031
31646
|
seen[slot] = next;
|
|
31032
|
-
patch[field] = next;
|
|
31033
31647
|
moved[slot] = 1;
|
|
31034
31648
|
count++;
|
|
31035
31649
|
}
|
|
31036
31650
|
if (count === 0) return;
|
|
31037
|
-
|
|
31651
|
+
const written = count === fields ? track.patch : partialPatch(track);
|
|
31652
|
+
fillPatch(track, written);
|
|
31653
|
+
ecs.set(track.entity, track.component, written);
|
|
31038
31654
|
}
|
|
31039
31655
|
/**
|
|
31040
|
-
*
|
|
31041
|
-
*
|
|
31656
|
+
* Puts the values of the moved slots into a patch of the track: each moved field gets what the
|
|
31657
|
+
* frame left in `values` for it.
|
|
31042
31658
|
*
|
|
31043
31659
|
* @param track - The track that writes.
|
|
31044
|
-
* @
|
|
31660
|
+
* @param written - Its full patch, or its patch of the moved fields.
|
|
31045
31661
|
*/
|
|
31046
|
-
function
|
|
31047
|
-
const
|
|
31662
|
+
function fillPatch(track, written) {
|
|
31663
|
+
const { names, moved, values } = track;
|
|
31664
|
+
for (let slot = 0; slot < names.length; slot++) {
|
|
31665
|
+
const field = names[slot];
|
|
31666
|
+
if (moved[slot] === 1 && field !== void 0) written[field] = values[slot] ?? 0;
|
|
31667
|
+
}
|
|
31668
|
+
}
|
|
31669
|
+
/**
|
|
31670
|
+
* The patch of a frame that moved only some of the fields of a track: the one the track keeps,
|
|
31671
|
+
* when the frame moved the same fields as the frame it was built for, otherwise a new one.
|
|
31672
|
+
*
|
|
31673
|
+
* @param track - The track that writes, its `moved` flags set by this frame.
|
|
31674
|
+
* @returns The patch of exactly the moved fields; their values are filled after.
|
|
31675
|
+
*/
|
|
31676
|
+
function partialPatch(track) {
|
|
31677
|
+
const { moved, partialMoved, partial } = track;
|
|
31678
|
+
if (partial === void 0) return keepPartial(track);
|
|
31679
|
+
for (let slot = 0; slot < moved.length; slot++) if (moved[slot] !== partialMoved[slot]) return keepPartial(track);
|
|
31680
|
+
return partial;
|
|
31681
|
+
}
|
|
31682
|
+
/**
|
|
31683
|
+
* Builds the patch of the fields the frame moved and keeps it on the track, with the `moved`
|
|
31684
|
+
* flags it was built for. Runs only when the moved fields change.
|
|
31685
|
+
*
|
|
31686
|
+
* @param track - The track that writes.
|
|
31687
|
+
* @returns The new patch, its values still to fill.
|
|
31688
|
+
*/
|
|
31689
|
+
function keepPartial(track) {
|
|
31690
|
+
const built = {};
|
|
31048
31691
|
for (let slot = 0; slot < track.names.length; slot++) {
|
|
31049
31692
|
const field = track.names[slot];
|
|
31050
|
-
if (track.moved[slot] === 1 && field !== void 0)
|
|
31693
|
+
if (track.moved[slot] === 1 && field !== void 0) built[field] = 0;
|
|
31051
31694
|
}
|
|
31052
|
-
|
|
31695
|
+
track.partialMoved.set(track.moved);
|
|
31696
|
+
track.partial = built;
|
|
31697
|
+
return built;
|
|
31053
31698
|
}
|
|
31054
31699
|
/**
|
|
31055
31700
|
* Releases the field bookkeeping of a track that is ending: the fields it owned, its own offsets
|
|
@@ -31289,7 +31934,9 @@ function startTrack$1(actx, entity, component, to, options, muted, driven = fals
|
|
|
31289
31934
|
masked: noMaskedFields(),
|
|
31290
31935
|
keep,
|
|
31291
31936
|
reveals: revealsOf(component, target),
|
|
31292
|
-
patch: { ...target }
|
|
31937
|
+
patch: { ...target },
|
|
31938
|
+
partial: void 0,
|
|
31939
|
+
partialMoved: new Uint8Array(names.length)
|
|
31293
31940
|
};
|
|
31294
31941
|
actx.state.nextId += 1;
|
|
31295
31942
|
if (track.ended) return track;
|
|
@@ -35123,9 +35770,24 @@ function overTargetOf(ctx, node, run) {
|
|
|
35123
35770
|
return takePending(ctx);
|
|
35124
35771
|
}
|
|
35125
35772
|
/**
|
|
35773
|
+
* Holds the feature that declares the scene now mounted, then releases the hold of the scene
|
|
35774
|
+
* before: the new hold first, so no phase sees the moment with neither. A scene no feature
|
|
35775
|
+
* declared holds nothing.
|
|
35776
|
+
*
|
|
35777
|
+
* @param ctx - Domain context of the plugin.
|
|
35778
|
+
* @param scene - The id of the scene now mounted.
|
|
35779
|
+
*/
|
|
35780
|
+
function holdOwner(ctx, scene) {
|
|
35781
|
+
const state = ctx.state;
|
|
35782
|
+
const release = state.held;
|
|
35783
|
+
const owner = state.owners.get(scene);
|
|
35784
|
+
state.held = owner === void 0 ? void 0 : ctx.deps.world.ecs.hold(owner);
|
|
35785
|
+
release?.();
|
|
35786
|
+
}
|
|
35787
|
+
/**
|
|
35126
35788
|
* 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, then the list the scene names
|
|
35128
|
-
* frame loop out of its idle cap, and only then the event.
|
|
35789
|
+
* out, then the ones of the new scene in, the hold on its feature, then the list the scene names
|
|
35790
|
+
* to `assets`, then the frame loop out of its idle cap, and only then the event.
|
|
35129
35791
|
*
|
|
35130
35792
|
* @param ctx - Domain context of the plugin.
|
|
35131
35793
|
* @param scene - The scene whose bundles are there.
|
|
@@ -35140,6 +35802,7 @@ function apply(ctx, scene, named) {
|
|
|
35140
35802
|
if (previous !== void 0) projection.unmount(previous.projections);
|
|
35141
35803
|
projection.mount(scene.projections, state.owner);
|
|
35142
35804
|
state.current = scene.id;
|
|
35805
|
+
holdOwner(ctx, scene.id);
|
|
35143
35806
|
state.named = named;
|
|
35144
35807
|
ctx.deps.assets.sceneBundles(named);
|
|
35145
35808
|
ctx.deps.time.wake();
|
|
@@ -35253,22 +35916,16 @@ function withDeps$5(ctx) {
|
|
|
35253
35916
|
};
|
|
35254
35917
|
}
|
|
35255
35918
|
/**
|
|
35256
|
-
*
|
|
35257
|
-
*
|
|
35258
|
-
*
|
|
35259
|
-
|
|
35260
|
-
function emptyOwners$1() {
|
|
35261
|
-
return /* @__PURE__ */ new Map();
|
|
35262
|
-
}
|
|
35263
|
-
/**
|
|
35264
|
-
* Reads the `scenes` key of every registered feature into the registry. Features register in
|
|
35265
|
-
* their own `onInit`, which runs before this `onStart`, so every scene of the game is here.
|
|
35919
|
+
* Reads the `scenes` key of every registered feature into the registry, and which feature declared
|
|
35920
|
+
* each scene: a duplicate id names both, and the switch holds the declaring feature while its
|
|
35921
|
+
* scene is mounted. Features register in their own `onInit`, which runs before this `onStart`, so
|
|
35922
|
+
* every scene of the game is here.
|
|
35266
35923
|
*
|
|
35267
35924
|
* @param ctx - Domain context of the plugin.
|
|
35268
35925
|
* @throws {Error} When two features declare the same scene id.
|
|
35269
35926
|
*/
|
|
35270
35927
|
function readScenes(ctx) {
|
|
35271
|
-
const owners =
|
|
35928
|
+
const owners = ctx.state.owners;
|
|
35272
35929
|
for (const feature of ctx.deps.flow.features.all()) {
|
|
35273
35930
|
const list = feature.description.scenes;
|
|
35274
35931
|
if (!Array.isArray(list)) continue;
|
|
@@ -35299,9 +35956,9 @@ function startScenes(ctx) {
|
|
|
35299
35956
|
scenes.state.teardown = scenes.deps.flow.onEnter("scene", (node, run) => enterScene(scenes, node, run));
|
|
35300
35957
|
}
|
|
35301
35958
|
/**
|
|
35302
|
-
* Removes the `onEnter` callback in `onStop` and forgets every scene
|
|
35303
|
-
* one named. Nothing is unmounted: a
|
|
35304
|
-
*
|
|
35959
|
+
* Removes the `onEnter` callback in `onStop` and forgets every scene, the bundles the mounted
|
|
35960
|
+
* one named and the hold on its feature. Nothing is unmounted and nothing is released: a teardown
|
|
35961
|
+
* context may not call another plugin, and `world.onStop` clears every entity and its scope anyway.
|
|
35305
35962
|
*
|
|
35306
35963
|
* @param teardown - What the teardown context carries.
|
|
35307
35964
|
* @param teardown.state - The plugin state.
|
|
@@ -35316,6 +35973,8 @@ function stopScenes(teardown) {
|
|
|
35316
35973
|
state.teardown?.();
|
|
35317
35974
|
state.teardown = void 0;
|
|
35318
35975
|
state.scenes.clear();
|
|
35976
|
+
state.owners.clear();
|
|
35977
|
+
state.held = void 0;
|
|
35319
35978
|
state.current = void 0;
|
|
35320
35979
|
state.pending = void 0;
|
|
35321
35980
|
state.named = [];
|
|
@@ -35332,6 +35991,15 @@ function emptyRegistry$1() {
|
|
|
35332
35991
|
return /* @__PURE__ */ new Map();
|
|
35333
35992
|
}
|
|
35334
35993
|
/**
|
|
35994
|
+
* Creates the table of which feature declared which scene, for the same reason as
|
|
35995
|
+
* `emptyRegistry`.
|
|
35996
|
+
*
|
|
35997
|
+
* @returns An empty table of scene id to feature name.
|
|
35998
|
+
*/
|
|
35999
|
+
function emptyOwners$1() {
|
|
36000
|
+
return /* @__PURE__ */ new Map();
|
|
36001
|
+
}
|
|
36002
|
+
/**
|
|
35335
36003
|
* Creates the one owner every mount of this plugin carries. One is enough: a single scene is
|
|
35336
36004
|
* mounted at a time and `unmount` is what flushes.
|
|
35337
36005
|
*
|
|
@@ -35344,8 +36012,8 @@ function sceneOwner() {
|
|
|
35344
36012
|
};
|
|
35345
36013
|
}
|
|
35346
36014
|
/**
|
|
35347
|
-
* Creates the initial scenes state: nothing declared, nothing mounted, nothing pending
|
|
35348
|
-
* registry
|
|
36015
|
+
* Creates the initial scenes state: nothing declared, nothing mounted, nothing pending, nothing
|
|
36016
|
+
* held. The registry and its owners are filled in `onStart` from the feature descriptions.
|
|
35349
36017
|
*
|
|
35350
36018
|
* @returns A fresh state, owned by one app.
|
|
35351
36019
|
*/
|
|
@@ -35356,7 +36024,9 @@ function createScenesState() {
|
|
|
35356
36024
|
pending: void 0,
|
|
35357
36025
|
named: [],
|
|
35358
36026
|
owner: sceneOwner(),
|
|
35359
|
-
teardown: void 0
|
|
36027
|
+
teardown: void 0,
|
|
36028
|
+
owners: emptyOwners$1(),
|
|
36029
|
+
held: void 0
|
|
35360
36030
|
};
|
|
35361
36031
|
}
|
|
35362
36032
|
//#endregion
|
|
@@ -52800,6 +53470,16 @@ function entitySet(entities) {
|
|
|
52800
53470
|
return new Set(entities);
|
|
52801
53471
|
}
|
|
52802
53472
|
/**
|
|
53473
|
+
* Takes the hold of the feature a root shows, when the root belongs to a feature.
|
|
53474
|
+
*
|
|
53475
|
+
* @param ctx - Domain context of the ui plugin.
|
|
53476
|
+
* @param feature - The feature of the root's component or projection, if any.
|
|
53477
|
+
* @returns The release, or `undefined` for a root of no feature.
|
|
53478
|
+
*/
|
|
53479
|
+
function holdFor(ctx, feature) {
|
|
53480
|
+
return feature === void 0 ? void 0 : ctx.deps.world.ecs.hold(feature);
|
|
53481
|
+
}
|
|
53482
|
+
/**
|
|
52803
53483
|
* Builds the reconcile half of the jsx module: the two systems of the frame and the root
|
|
52804
53484
|
* bookkeeping around them.
|
|
52805
53485
|
*
|
|
@@ -53404,6 +54084,7 @@ function createReconciler(ctx, modules, fields) {
|
|
|
53404
54084
|
if (root === void 0) continue;
|
|
53405
54085
|
if (root.element !== void 0 && state.elements.has(root.element)) continue;
|
|
53406
54086
|
state.removing.delete(entity);
|
|
54087
|
+
root.held?.();
|
|
53407
54088
|
state.roots.delete(entity);
|
|
53408
54089
|
state.byKey.delete(entity);
|
|
53409
54090
|
ecs.despawn(entity);
|
|
@@ -53821,15 +54502,21 @@ function createReconciler(ctx, modules, fields) {
|
|
|
53821
54502
|
if (element?.live === true && root.slot === void 0) ecs.set(element.entity, Order, { value: order });
|
|
53822
54503
|
}
|
|
53823
54504
|
/**
|
|
53824
|
-
* Reconciles one dirty root: reads its current tree and projection name, then diffs it.
|
|
53825
|
-
*
|
|
54505
|
+
* Reconciles one dirty root: reads its current tree and projection name, then diffs it. A
|
|
54506
|
+
* projection root takes the hold of its projection's feature here, at the first reconcile that
|
|
54507
|
+
* names its projection: its `Tree` hook fires inside the spawn, before the world knows the view.
|
|
54508
|
+
* The root stays dirty while the layout engine is not loaded, and a failure is logged for this
|
|
54509
|
+
* root only.
|
|
53826
54510
|
*
|
|
53827
54511
|
* @param root - A root with work this frame.
|
|
53828
54512
|
*/
|
|
53829
54513
|
function reconcileDirtyRoot(root) {
|
|
53830
54514
|
const tree = ecs.get(root.entity, Tree);
|
|
53831
54515
|
const place = ctx.deps.world.projection.keyOf(root.entity);
|
|
53832
|
-
if (place !== void 0)
|
|
54516
|
+
if (place !== void 0) {
|
|
54517
|
+
root.name = place.projection;
|
|
54518
|
+
if (root.popup === void 0) root.held ??= holdFor(ctx, state.featureOfProjection.get(place.projection));
|
|
54519
|
+
}
|
|
53833
54520
|
if (tree !== void 0) root.tree = tree.node;
|
|
53834
54521
|
refreshRootOrder(root);
|
|
53835
54522
|
root.dirty = !modules.layout.loaded();
|
|
@@ -53906,7 +54593,10 @@ function createReconciler(ctx, modules, fields) {
|
|
|
53906
54593
|
/**
|
|
53907
54594
|
* Records a root: a projection view that returned a tree, or a popup this plugin spawned. The
|
|
53908
54595
|
* `Tree` hook and the popup handler both call it, in either order, so a second call fills in
|
|
53909
|
-
* what the first one could not know.
|
|
54596
|
+
* what the first one could not know. A new popup root holds its component's feature; a root
|
|
54597
|
+
* first recorded by the `Tree` hook takes that hold when the popup handler names the component,
|
|
54598
|
+
* and a reclaimed root keeps the hold it has. A projection root takes its projection's hold at
|
|
54599
|
+
* its first reconcile.
|
|
53910
54600
|
*
|
|
53911
54601
|
* @param entity - The entity that carries `Tree`.
|
|
53912
54602
|
* @param name - The projection name, or the component name of a popup.
|
|
@@ -53922,6 +54612,7 @@ function createReconciler(ctx, modules, fields) {
|
|
|
53922
54612
|
known.popup = popup;
|
|
53923
54613
|
known.name = name;
|
|
53924
54614
|
known.cause = "change";
|
|
54615
|
+
known.held ??= holdFor(ctx, state.featureOfComponent.get(name));
|
|
53925
54616
|
}
|
|
53926
54617
|
return;
|
|
53927
54618
|
}
|
|
@@ -53945,7 +54636,8 @@ function createReconciler(ctx, modules, fields) {
|
|
|
53945
54636
|
cause: popup === void 0 ? void 0 : "change",
|
|
53946
54637
|
slot: void 0,
|
|
53947
54638
|
slotRect: void 0,
|
|
53948
|
-
pending: false
|
|
54639
|
+
pending: false,
|
|
54640
|
+
held: popup === void 0 ? void 0 : holdFor(ctx, state.featureOfComponent.get(name))
|
|
53949
54641
|
});
|
|
53950
54642
|
}
|
|
53951
54643
|
/**
|
|
@@ -56180,14 +56872,45 @@ function isComponent(entry) {
|
|
|
56180
56872
|
return typeof entry === "function" && entry.isUiComponent === true;
|
|
56181
56873
|
}
|
|
56182
56874
|
/**
|
|
56183
|
-
*
|
|
56875
|
+
* The name of a projection a feature lists, read without trusting the entry.
|
|
56876
|
+
*
|
|
56877
|
+
* @param entry - What the feature put under its `projections` key.
|
|
56878
|
+
* @returns The name, or `undefined` when the entry carries none.
|
|
56879
|
+
* @example
|
|
56880
|
+
* ```ts
|
|
56881
|
+
* projectionName({ name: "shop.sheet", layer: "ui" }); // "shop.sheet"
|
|
56882
|
+
* ```
|
|
56883
|
+
*/
|
|
56884
|
+
function projectionName(entry) {
|
|
56885
|
+
if (typeof entry !== "object" || entry === null) return void 0;
|
|
56886
|
+
const name = entry.name;
|
|
56887
|
+
return typeof name === "string" ? name : void 0;
|
|
56888
|
+
}
|
|
56889
|
+
/**
|
|
56890
|
+
* Fills the component registry from the `ui` key of every feature, in feature order, and records
|
|
56891
|
+
* which feature each component and each projection belongs to: a root holds that feature while it
|
|
56892
|
+
* is shown, so the feature's systems run with `systemsRun: "active"`. Two tables, because a
|
|
56893
|
+
* component and a projection may share a name.
|
|
56184
56894
|
*
|
|
56185
56895
|
* @param ctx - Domain context of the ui plugin.
|
|
56186
56896
|
* @param jsx - The jsx module, which owns the registry.
|
|
56187
56897
|
* @throws {Error} When two features define the same component name.
|
|
56188
56898
|
*/
|
|
56189
56899
|
function registerComponents(ctx, jsx) {
|
|
56190
|
-
|
|
56900
|
+
const { featureOfComponent, featureOfProjection } = ctx.state.jsx;
|
|
56901
|
+
for (const feature of ctx.deps.flow.features.all()) {
|
|
56902
|
+
for (const entry of feature.description.ui ?? []) {
|
|
56903
|
+
if (!isComponent(entry)) continue;
|
|
56904
|
+
jsx.register(entry);
|
|
56905
|
+
featureOfComponent.set(entry.name, feature.name);
|
|
56906
|
+
}
|
|
56907
|
+
const projections = feature.description.projections;
|
|
56908
|
+
if (!Array.isArray(projections)) continue;
|
|
56909
|
+
for (const entry of projections) {
|
|
56910
|
+
const name = projectionName(entry);
|
|
56911
|
+
if (name !== void 0) featureOfProjection.set(name, feature.name);
|
|
56912
|
+
}
|
|
56913
|
+
}
|
|
56191
56914
|
}
|
|
56192
56915
|
/**
|
|
56193
56916
|
* Opens the two systems of phase `layout`, the seven world hooks of `Tree`, `Box`, `Pressed` and
|
|
@@ -56267,6 +56990,8 @@ function stopUi(state) {
|
|
|
56267
56990
|
state.layout.scrolling = void 0;
|
|
56268
56991
|
state.layout.iconLine = void 0;
|
|
56269
56992
|
state.jsx.components.clear();
|
|
56993
|
+
state.jsx.featureOfComponent.clear();
|
|
56994
|
+
state.jsx.featureOfProjection.clear();
|
|
56270
56995
|
state.jsx.roots.clear();
|
|
56271
56996
|
state.jsx.elements.clear();
|
|
56272
56997
|
state.jsx.byIdentity.clear();
|
|
@@ -56662,7 +57387,9 @@ function createJsxState() {
|
|
|
56662
57387
|
written: 0,
|
|
56663
57388
|
debugTaps: [],
|
|
56664
57389
|
debugWarned: false,
|
|
56665
|
-
overlays: emptySet()
|
|
57390
|
+
overlays: emptySet(),
|
|
57391
|
+
featureOfComponent: emptyMap$1(),
|
|
57392
|
+
featureOfProjection: emptyMap$1()
|
|
56666
57393
|
};
|
|
56667
57394
|
}
|
|
56668
57395
|
//#endregion
|
|
@@ -57408,6 +58135,18 @@ function assertFeatures(name, children) {
|
|
|
57408
58135
|
for (const [index, child] of children.entries()) if (typeof child?.logicOnly !== "object" || !Array.isArray(child.features)) throw notAFeature(`Feature "${name}"`, `features[${index}]`);
|
|
57409
58136
|
}
|
|
57410
58137
|
/**
|
|
58138
|
+
* Throws for a `systemsRun` outside its type. The type already says so; a game in JavaScript, or
|
|
58139
|
+
* a factory that passes a string through, is caught here, before the world reads it.
|
|
58140
|
+
*
|
|
58141
|
+
* @param name - The feature that declares it.
|
|
58142
|
+
* @param value - What the description holds under `systemsRun`.
|
|
58143
|
+
* @throws {Error} When the value is present and neither `"always"` nor `"active"`.
|
|
58144
|
+
*/
|
|
58145
|
+
function assertSystemsRun(name, value) {
|
|
58146
|
+
if (value === void 0 || value === "always" || value === "active") return;
|
|
58147
|
+
throw new Error(`[game] Feature "${name}" has systemsRun "${String(value)}".\n Use "always" (the default) or "active".`);
|
|
58148
|
+
}
|
|
58149
|
+
/**
|
|
57411
58150
|
* Builds the plugin of one feature: it depends on `flow`, checks in `onInit` that the app composes
|
|
57412
58151
|
* every declared sub-feature, then registers its description, before any plugin above reads the
|
|
57413
58152
|
* registry in `onStart`.
|
|
@@ -57446,12 +58185,14 @@ function featurePluginOf(name, description, children) {
|
|
|
57446
58185
|
* same plugin built from the V1 keys of the description, for headless tests. A feature that
|
|
57447
58186
|
* declares `features` is a parent: its `onInit` throws when one of them is not composed. The
|
|
57448
58187
|
* plugin carries them in `features`, its `logicOnly` carries their `logicOnly`.
|
|
58188
|
+
* `systemsRun: "active"` runs the feature's systems only while it is active (see `SystemsRun`).
|
|
57449
58189
|
*
|
|
57450
58190
|
* @param name - Feature name. Shares the namespace with plugin names.
|
|
57451
58191
|
* @param description - What the feature brings: nodes, flows, slot contributions, sub-features.
|
|
57452
58192
|
* @returns The feature plugin, with its `logicOnly` twin.
|
|
57453
|
-
* @throws {Error} When the name is reserved or belongs to an engine plugin,
|
|
57454
|
-
* `features` that `defineFeature` did not make
|
|
58193
|
+
* @throws {Error} When the name is reserved or belongs to an engine plugin, for an entry of
|
|
58194
|
+
* `features` that `defineFeature` did not make, and for a `systemsRun` that is neither
|
|
58195
|
+
* `"always"` nor `"active"`.
|
|
57455
58196
|
* @example
|
|
57456
58197
|
* ```ts
|
|
57457
58198
|
* // The reward popup is a feature: the main flow only declares slot("afterOrder").
|
|
@@ -57466,6 +58207,7 @@ function featurePluginOf(name, description, children) {
|
|
|
57466
58207
|
*/
|
|
57467
58208
|
function defineFeature(name, description) {
|
|
57468
58209
|
if (takenNames().has(name)) throw new Error(`[game] Feature name "${name}" is reserved or already a plugin.\n Choose another feature name.`);
|
|
58210
|
+
assertSystemsRun(name, description.systemsRun);
|
|
57469
58211
|
const children = Object.freeze([...description.features ?? []]);
|
|
57470
58212
|
assertFeatures(name, children);
|
|
57471
58213
|
const logicOnly = Object.assign(featurePluginOf(name, logicKeysOf(description), children), { features: Object.freeze(children.map((child) => child.logicOnly)) });
|
|
@@ -57947,6 +58689,168 @@ function defineEffect(kind, options = {}) {
|
|
|
57947
58689
|
});
|
|
57948
58690
|
}
|
|
57949
58691
|
//#endregion
|
|
58692
|
+
//#region src/plugins/anim/tween/bezier.ts
|
|
58693
|
+
/**
|
|
58694
|
+
* @file anim/tween — the CSS timing function `cubic-bezier(x1, y1, x2, y2)` as an easing. The
|
|
58695
|
+
* curve is solved once, into a table of its progress at 512 even steps of time; a frame reads two
|
|
58696
|
+
* cells and draws a line between them. Where a line strays from the curve (the curve leaves an end
|
|
58697
|
+
* vertically), that step is solved again at each call, inside the bracket the table keeps. Either
|
|
58698
|
+
* way a call allocates nothing.
|
|
58699
|
+
*/
|
|
58700
|
+
/**
|
|
58701
|
+
* How many even steps of time the table splits 0…1 into. With 512, a line strays at most 1e-5
|
|
58702
|
+
* from any curve that keeps a finite slope (the CSS keywords, an overshoot both ways), so only a
|
|
58703
|
+
* curve that leaves an end vertically solves any step at a call; 8 KB per curve.
|
|
58704
|
+
*/
|
|
58705
|
+
const STEPS = 512;
|
|
58706
|
+
/**
|
|
58707
|
+
* How far the line of a step may stray from the curve at the step's middle before that step is
|
|
58708
|
+
* solved at each call instead: a fifth of the 1e-4 the factory promises, so the rest of the step,
|
|
58709
|
+
* where the line may stray a little more, stays inside the promise too (2e-5 measured).
|
|
58710
|
+
*/
|
|
58711
|
+
const STRAY = 2e-5;
|
|
58712
|
+
/** Halvings of a solve: 2^-48 of the bracket, far below what a double of progress shows. */
|
|
58713
|
+
const HALVINGS = 48;
|
|
58714
|
+
/**
|
|
58715
|
+
* One coordinate of a cubic curve from 0 to 1 with the two inner control values. Pure.
|
|
58716
|
+
*
|
|
58717
|
+
* @param along - The curve parameter, 0 to 1.
|
|
58718
|
+
* @param first - The first control value.
|
|
58719
|
+
* @param second - The second control value.
|
|
58720
|
+
* @returns The coordinate at `along`.
|
|
58721
|
+
* @example
|
|
58722
|
+
* ```ts
|
|
58723
|
+
* coordinate(0.5, 1 / 3, 2 / 3); // 0.5: the middle of a straight line
|
|
58724
|
+
* ```
|
|
58725
|
+
*/
|
|
58726
|
+
function coordinate(along, first, second) {
|
|
58727
|
+
const back = 1 - along;
|
|
58728
|
+
return 3 * back * back * along * first + 3 * back * along * along * second + along * along * along;
|
|
58729
|
+
}
|
|
58730
|
+
/**
|
|
58731
|
+
* The curve parameter whose x is `time`, found by halving a bracket of parameters. The x of the
|
|
58732
|
+
* curve never falls while x1 and x2 lie in 0…1, so halving always finds it. Pure.
|
|
58733
|
+
*
|
|
58734
|
+
* @param time - The x to reach.
|
|
58735
|
+
* @param x1 - The x of the first control point.
|
|
58736
|
+
* @param x2 - The x of the second control point.
|
|
58737
|
+
* @param low - A parameter whose x is at most `time`.
|
|
58738
|
+
* @param high - A parameter whose x is at least `time`.
|
|
58739
|
+
* @returns The parameter.
|
|
58740
|
+
* @example
|
|
58741
|
+
* ```ts
|
|
58742
|
+
* parameterAt(0.25, 0, 1, 0, 1); // 0.25: on a straight line the parameter is the time
|
|
58743
|
+
* ```
|
|
58744
|
+
*/
|
|
58745
|
+
function parameterAt(time, x1, x2, low, high) {
|
|
58746
|
+
let from = low;
|
|
58747
|
+
let to = high;
|
|
58748
|
+
for (let step = 0; step < HALVINGS; step++) {
|
|
58749
|
+
const middle = (from + to) / 2;
|
|
58750
|
+
if (coordinate(middle, x1, x2) < time) from = middle;
|
|
58751
|
+
else to = middle;
|
|
58752
|
+
}
|
|
58753
|
+
return (from + to) / 2;
|
|
58754
|
+
}
|
|
58755
|
+
/**
|
|
58756
|
+
* Solves a curve into its table: the parameter and the progress at every even step of time, and
|
|
58757
|
+
* the steps whose line strays from the curve at their middle.
|
|
58758
|
+
*
|
|
58759
|
+
* @param points - `[x1, y1, x2, y2]`, x1 and x2 in 0…1.
|
|
58760
|
+
* @returns The table.
|
|
58761
|
+
*/
|
|
58762
|
+
function solveTable(points) {
|
|
58763
|
+
const [x1 = 0, y1 = 0, x2 = 0, y2 = 0] = points;
|
|
58764
|
+
const params = /* @__PURE__ */ new Float64Array(513);
|
|
58765
|
+
const progress = /* @__PURE__ */ new Float64Array(513);
|
|
58766
|
+
const solved = new Uint8Array(STEPS);
|
|
58767
|
+
for (let index = 1; index < STEPS; index++) {
|
|
58768
|
+
params[index] = parameterAt(index / STEPS, x1, x2, 0, 1);
|
|
58769
|
+
progress[index] = coordinate(params[index] ?? 0, y1, y2);
|
|
58770
|
+
}
|
|
58771
|
+
params[STEPS] = 1;
|
|
58772
|
+
progress[STEPS] = 1;
|
|
58773
|
+
for (let index = 0; index < STEPS; index++) {
|
|
58774
|
+
const middle = parameterAt((index + .5) / STEPS, x1, x2, params[index] ?? 0, params[index + 1] ?? 1);
|
|
58775
|
+
const line = ((progress[index] ?? 0) + (progress[index + 1] ?? 0)) / 2;
|
|
58776
|
+
if (Math.abs(coordinate(middle, y1, y2) - line) > STRAY) solved[index] = 1;
|
|
58777
|
+
}
|
|
58778
|
+
return {
|
|
58779
|
+
points,
|
|
58780
|
+
params,
|
|
58781
|
+
progress,
|
|
58782
|
+
solved
|
|
58783
|
+
};
|
|
58784
|
+
}
|
|
58785
|
+
/**
|
|
58786
|
+
* The cell a solved step passes its time in and its progress out: a double handed to or returned
|
|
58787
|
+
* from a call V8 does not inline is boxed, a cell is not.
|
|
58788
|
+
*/
|
|
58789
|
+
const SOLVED = /* @__PURE__ */ new Float64Array(1);
|
|
58790
|
+
/**
|
|
58791
|
+
* The progress of a curve at one time: exactly 0 up to the start and 1 from the end on, a line
|
|
58792
|
+
* between two cells of the table in between, or the curve itself on a step whose line strays.
|
|
58793
|
+
*
|
|
58794
|
+
* @param table - The curve's table.
|
|
58795
|
+
* @param time - The time.
|
|
58796
|
+
* @returns The progress.
|
|
58797
|
+
*/
|
|
58798
|
+
function progressAt(table, time) {
|
|
58799
|
+
if (time <= 0) return 0;
|
|
58800
|
+
if (time >= 1) return 1;
|
|
58801
|
+
const at = time * STEPS;
|
|
58802
|
+
const index = Math.floor(at);
|
|
58803
|
+
if (table.solved[index] === 1) {
|
|
58804
|
+
SOLVED[0] = time;
|
|
58805
|
+
solveStep(table, index);
|
|
58806
|
+
return SOLVED[0] ?? 0;
|
|
58807
|
+
}
|
|
58808
|
+
const low = table.progress[index] ?? 0;
|
|
58809
|
+
return low + ((table.progress[index + 1] ?? 1) - low) * (at - index);
|
|
58810
|
+
}
|
|
58811
|
+
/**
|
|
58812
|
+
* The progress on a step whose line strays from the curve: the curve solved inside the bracket of
|
|
58813
|
+
* parameters the table keeps for that step. It reads the time from `SOLVED[0]` and leaves the
|
|
58814
|
+
* progress there. Kept apart so the common call stays small.
|
|
58815
|
+
*
|
|
58816
|
+
* @param table - The curve's table.
|
|
58817
|
+
* @param index - The step.
|
|
58818
|
+
*/
|
|
58819
|
+
function solveStep(table, index) {
|
|
58820
|
+
const points = table.points;
|
|
58821
|
+
const along = parameterAt(SOLVED[0] ?? 0, points[0] ?? 0, points[2] ?? 0, table.params[index] ?? 0, table.params[index + 1] ?? 1);
|
|
58822
|
+
SOLVED[0] = coordinate(along, points[1] ?? 0, points[3] ?? 0);
|
|
58823
|
+
}
|
|
58824
|
+
/**
|
|
58825
|
+
* The CSS timing function `cubic-bezier(x1, y1, x2, y2)` as an easing a tween, a keyframe segment
|
|
58826
|
+
* or a motion takes for `ease`. The curve is solved once, here, into a table; a frame reads it, so
|
|
58827
|
+
* an eased time costs a few reads and allocates nothing, and the progress stays within 1e-4 of the
|
|
58828
|
+
* exact curve. It is exactly 0 up to the start and exactly 1 from the end on, and may leave 0…1 on
|
|
58829
|
+
* the way (an overshoot). Make each curve once, at module level, and share it.
|
|
58830
|
+
*
|
|
58831
|
+
* @param x1 - The x of the first control point, 0 to 1.
|
|
58832
|
+
* @param y1 - The y of the first control point; below 0 or above 1 overshoots.
|
|
58833
|
+
* @param x2 - The x of the second control point, 0 to 1.
|
|
58834
|
+
* @param y2 - The y of the second control point; below 0 or above 1 overshoots.
|
|
58835
|
+
* @returns The curve: normalised time in, eased progress out.
|
|
58836
|
+
* @throws {Error} When x1 or x2 lies outside 0…1: the curve would be no function of time.
|
|
58837
|
+
* @example
|
|
58838
|
+
* ```ts
|
|
58839
|
+
* // The design's arrival curve, made once and named by every tween that arrives.
|
|
58840
|
+
* const arrive = cubicBezier(0.22, 0.61, 0.36, 1);
|
|
58841
|
+
* arrive(0.5); // 0.873 (to 1e-4)
|
|
58842
|
+
* const drop = defineAnimation("card.drop", {
|
|
58843
|
+
* slots: { card: type<Target>() },
|
|
58844
|
+
* build: ({ card }) => sequence(tween(card, Transform, { y: 40 }, { ms: 300, ease: arrive }))
|
|
58845
|
+
* });
|
|
58846
|
+
* ```
|
|
58847
|
+
*/
|
|
58848
|
+
function cubicBezier(x1, y1, x2, y2) {
|
|
58849
|
+
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.`);
|
|
58850
|
+
const table = solveTable(Float64Array.of(x1, y1, x2, y2));
|
|
58851
|
+
return (t) => progressAt(table, t);
|
|
58852
|
+
}
|
|
58853
|
+
//#endregion
|
|
57950
58854
|
//#region src/index.ts
|
|
57951
58855
|
/**
|
|
57952
58856
|
* The `@moku-labs/game` package — 2D puzzle game engine on Moku Core.
|
|
@@ -58155,4 +59059,4 @@ const screen = [
|
|
|
58155
59059
|
uiPlugin
|
|
58156
59060
|
];
|
|
58157
59061
|
//#endregion
|
|
58158
|
-
export {
|
|
59062
|
+
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 };
|