@moku-labs/game 0.15.39 → 0.15.41
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/README.md +1 -1
- 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/{capture-PTJXDpxK.mjs → capture-BtS6_oWr.mjs} +61 -1
- package/dist/{commands-CMFdiDcg.d.mts → commands-DLk06fiL.d.mts} +5 -5
- package/dist/control.d.mts +4 -4
- package/dist/control.mjs +1 -1
- package/dist/{define-D27mfOhZ.d.mts → define-CgU4dz0e.d.mts} +2 -2
- package/dist/{headless-CRqMxzqv.d.mts → headless-1e3gEF1Y.d.mts} +1 -1
- package/dist/{headless-Bxxl_mQX.mjs → headless-CCiUwvlp.mjs} +1 -1
- package/dist/{index-DFAjLwxi.d.mts → index-D12s9KZ2.d.mts} +19 -7
- 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/{run-G4m1APMv.mjs → run-8s7kh2QD.mjs} +2 -2
- package/dist/{runtime-CbLZyDXj.d.mts → runtime-Bpg0WmM8.d.mts} +1 -1
- package/dist/{src-B2vblgmb.mjs → src-CWVwT9UM.mjs} +1858 -721
- package/dist/testing.d.mts +4 -4
- package/dist/testing.mjs +2 -2
- package/dist/{types-54KzmAA6.d.mts → types-B-o8Ul1v.d.mts} +2 -2
- package/dist/{types-0Br0SjTB.d.mts → types-CFEtGENK.d.mts} +1 -1
- package/dist/{types-H1FiWmKa.d.mts → types-DQ9VHXun.d.mts} +4 -4
- package/dist/{types-C_dk-lXz.d.mts → types-DrkR03nL.d.mts} +413 -31
- package/dist/{types-CGPWYbZO.d.mts → types-gQATSj41.d.mts} +2 -2
- package/dist/{visual-DjTJ02c3.d.mts → visual-pHKIFBSQ.d.mts} +6 -6
- package/dist/visual.d.mts +1 -1
- package/dist/visual.mjs +1 -1
- package/llms.txt +14 -4
- package/package.json +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-BhDjJH2R.mjs";
|
|
2
2
|
import { r as systemSource, t as memory } from "./memory-CWlqhS7l.mjs";
|
|
3
3
|
import { a as finishDrafts, i as enableDraftPatches, n as deepFreeze$1, o as openDrafts, r as dropDrafts } from "./drafts-BawpSaVp.mjs";
|
|
4
|
-
import { _ as
|
|
4
|
+
import { _ as compact, c as collectFlows, d as findNode, f as framePath, g as slotNames, h as restlessCapOf, i as servePaused, l as describeGraph, m as isRestCheckpoint, n as captureFrame, o as epochOf, p as graphHash, r as serveCaptures, s as pageNow$1, t as cancelCaptures, v as pushEntry } from "./capture-BtS6_oWr.mjs";
|
|
5
5
|
import { $ as component, A as localPoseOf, B as OverlayLevel, C as builtInStyles, D as textFor, E as readStyle, F as rootPoseOf, G as arcDefaults, H as Shape, I as Display, J as Exiting, K as componentsFor, L as HudPart, M as poseUnder, N as rootPointOf, O as applyPose, P as rootPoseInto, Q as Tree, R as NineSlice, U as Sprite, V as Parent, W as Transform, X as Now, Y as Layer, Z as Order, _ as uiFor, a as visualOf$1, b as Countdown, c as Covered, d as LocalWrite, f as Scroll, h as UiCounters, i as samePose, it as tag, j as parentOf$1, k as isLeaving, l as DebugTrigger, m as UI_OWNER, n as drawnRect, nt as system, o as visualRectOf, p as ScrollState, r as fitScaleOf, rt as systemsFor, s as Box, t as CONTENT$1, tt as resource, u as Escapable, x as Text, z as Overlay } from "./visual-ClVRMUAi.mjs";
|
|
6
6
|
import { a as isDev } from "./dev-BNndV-hU.mjs";
|
|
7
7
|
import { n as isComponentDefinition } from "./component-DGg5DqKK.mjs";
|
|
@@ -2444,7 +2444,7 @@ var SaveUnreadableError = class extends Error {
|
|
|
2444
2444
|
* isRecord(["key"]); // false: an array is not a record
|
|
2445
2445
|
* ```
|
|
2446
2446
|
*/
|
|
2447
|
-
function isRecord$
|
|
2447
|
+
function isRecord$5(value) {
|
|
2448
2448
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
2449
2449
|
}
|
|
2450
2450
|
/**
|
|
@@ -2459,7 +2459,7 @@ function isRecord$4(value) {
|
|
|
2459
2459
|
* ```
|
|
2460
2460
|
*/
|
|
2461
2461
|
function readStreams(value) {
|
|
2462
|
-
if (!isRecord$
|
|
2462
|
+
if (!isRecord$5(value)) return void 0;
|
|
2463
2463
|
const streams = {};
|
|
2464
2464
|
for (const [id, state] of Object.entries(value)) {
|
|
2465
2465
|
if (typeof state !== "number") return void 0;
|
|
@@ -2483,9 +2483,9 @@ function readStreams(value) {
|
|
|
2483
2483
|
* ```
|
|
2484
2484
|
*/
|
|
2485
2485
|
function readSaveDoc(state, savedVersion, schemaVersion) {
|
|
2486
|
-
const record = isRecord$
|
|
2486
|
+
const record = isRecord$5(state) ? state : void 0;
|
|
2487
2487
|
const branch = record ? record.rng : void 0;
|
|
2488
|
-
const rng = isRecord$
|
|
2488
|
+
const rng = isRecord$5(branch) ? branch : void 0;
|
|
2489
2489
|
const streams = readStreams(rng ? rng.streams : void 0);
|
|
2490
2490
|
if (!record || record.player === void 0 || !rng || typeof rng.seed !== "number" || !streams) throw new SaveUnreadableError(savedVersion, schemaVersion, /* @__PURE__ */ new Error("[game] The save is not a document of the shape { player, rng }.\n Restore the backup or start a new save."));
|
|
2491
2491
|
return {
|
|
@@ -3673,7 +3673,7 @@ function nextNodes(flow, name) {
|
|
|
3673
3673
|
*/
|
|
3674
3674
|
function targetProblem(flow, name, outcome, target) {
|
|
3675
3675
|
if (typeof target !== "string" && target.kind === "exit") {
|
|
3676
|
-
if (
|
|
3676
|
+
if (Object.hasOwn(flow.outcomes, target.outcome)) return void 0;
|
|
3677
3677
|
return `[game] Flow "${flow.id}": exit("${target.outcome}") of node "${name}" names no outcome of the flow.\n Declare outcomes.${target.outcome} on flow "${flow.id}".`;
|
|
3678
3678
|
}
|
|
3679
3679
|
const next = typeof target === "string" ? target : target.target;
|
|
@@ -3743,7 +3743,7 @@ function nodeProblems(flow) {
|
|
|
3743
3743
|
if (entry.kind === "slot" && !entry.name) problems.push(`[game] Flow "${flow.id}": the slot node "${name}" has no slot name.\n Give it a name: slot("${name}").`);
|
|
3744
3744
|
if (entry.kind !== "node") continue;
|
|
3745
3745
|
for (const type of entry.inbox) {
|
|
3746
|
-
if (
|
|
3746
|
+
if (Object.hasOwn(entry.outcomes, type)) continue;
|
|
3747
3747
|
problems.push(`[game] Flow "${flow.id}": inbox type "${type}" of node "${name}" is not an outcome.\n Add outcomes.${type} to node "${name}".`);
|
|
3748
3748
|
}
|
|
3749
3749
|
if (entry.rest && !entry.run && Object.keys(entry.outcomes).length === 0) problems.push(`[game] Flow "${flow.id}": rest node "${name}" has neither outcomes nor a run body.\n Declare outcomes or add a run body.`);
|
|
@@ -3889,6 +3889,138 @@ function featureProblems(flows, features) {
|
|
|
3889
3889
|
return problems;
|
|
3890
3890
|
}
|
|
3891
3891
|
/**
|
|
3892
|
+
* Names the outcomes of a table besides `done`: what a slot declares, or what a contributed flow
|
|
3893
|
+
* may leave its slot on.
|
|
3894
|
+
*
|
|
3895
|
+
* @param outcomes - The outcome table of a slot or a flow.
|
|
3896
|
+
* @returns The outcome names other than `done`, in declaration order.
|
|
3897
|
+
* @example
|
|
3898
|
+
* ```ts
|
|
3899
|
+
* declaredOutcomes({ done: type(), shop: type() }); // ["shop"]
|
|
3900
|
+
* ```
|
|
3901
|
+
*/
|
|
3902
|
+
function declaredOutcomes(outcomes) {
|
|
3903
|
+
return Object.keys(outcomes).filter((name) => name !== "done");
|
|
3904
|
+
}
|
|
3905
|
+
/**
|
|
3906
|
+
* Tells whether two lists hold the same names, in any order.
|
|
3907
|
+
*
|
|
3908
|
+
* @param first - One list of names.
|
|
3909
|
+
* @param second - The other list.
|
|
3910
|
+
* @returns True when every name of one is in the other and the lengths match.
|
|
3911
|
+
* @example
|
|
3912
|
+
* ```ts
|
|
3913
|
+
* sameNames(["shop", "board"], ["board", "shop"]); // true
|
|
3914
|
+
* ```
|
|
3915
|
+
*/
|
|
3916
|
+
function sameNames$1(first, second) {
|
|
3917
|
+
return first.length === second.length && first.every((name) => second.includes(name));
|
|
3918
|
+
}
|
|
3919
|
+
/**
|
|
3920
|
+
* Collects every slot node of the graph by slot name: the flow that holds it and what it
|
|
3921
|
+
* declares, in the order the graph declares them.
|
|
3922
|
+
*
|
|
3923
|
+
* @param flows - Every collected flow by id.
|
|
3924
|
+
* @returns Slot name to its slot nodes, at least one each.
|
|
3925
|
+
*/
|
|
3926
|
+
function slotDeclarations(flows) {
|
|
3927
|
+
const declarations = /* @__PURE__ */ new Map();
|
|
3928
|
+
for (const flow of flows.values()) for (const entry of Object.values(flow.nodes)) {
|
|
3929
|
+
if (entry.kind !== "slot") continue;
|
|
3930
|
+
const declaration = {
|
|
3931
|
+
flow: flow.id,
|
|
3932
|
+
declared: declaredOutcomes(entry.outcomes)
|
|
3933
|
+
};
|
|
3934
|
+
const list = declarations.get(entry.name);
|
|
3935
|
+
if (list === void 0) declarations.set(entry.name, [declaration]);
|
|
3936
|
+
else list.push(declaration);
|
|
3937
|
+
}
|
|
3938
|
+
return declarations;
|
|
3939
|
+
}
|
|
3940
|
+
/**
|
|
3941
|
+
* Reports the contributions of a slot that declares outcomes and that may leave it on an outcome
|
|
3942
|
+
* the slot does not declare: the loop would have no edge for it.
|
|
3943
|
+
*
|
|
3944
|
+
* @param slotName - Name of the slot.
|
|
3945
|
+
* @param declared - The outcomes the slot declares besides `done`.
|
|
3946
|
+
* @param contributions - The contributions of the slot.
|
|
3947
|
+
* @returns One sentence per contribution and outcome.
|
|
3948
|
+
*/
|
|
3949
|
+
function undeclaredExits(slotName, declared, contributions) {
|
|
3950
|
+
const problems = [];
|
|
3951
|
+
for (const { feature, flow } of contributions) for (const outcome of declaredOutcomes(flow.outcomes)) {
|
|
3952
|
+
if (declared.includes(outcome)) continue;
|
|
3953
|
+
problems.push(`[game] Slot "${slotName}": flow "${flow.id}" of feature "${feature}" exits "${outcome}", which the slot does not declare.\n Declare slot("${slotName}", { ${outcome}: type() }) or exit "done".`);
|
|
3954
|
+
}
|
|
3955
|
+
return problems;
|
|
3956
|
+
}
|
|
3957
|
+
/**
|
|
3958
|
+
* Reports the named outcomes of slots: one slot name declared with different outcomes in two
|
|
3959
|
+
* places, and a contribution of a slot that declares outcomes which may leave it on another one.
|
|
3960
|
+
* A slot that declares none takes any contribution: what it ignores is a warning.
|
|
3961
|
+
*
|
|
3962
|
+
* @param flows - Every collected flow by id.
|
|
3963
|
+
* @param features - Features API, read for the contributions of every slot.
|
|
3964
|
+
* @returns One sentence per problem.
|
|
3965
|
+
*/
|
|
3966
|
+
function slotOutcomeProblems(flows, features) {
|
|
3967
|
+
const problems = [];
|
|
3968
|
+
for (const [slotName, [first, ...others]] of slotDeclarations(flows)) {
|
|
3969
|
+
for (const other of others) {
|
|
3970
|
+
if (sameNames$1(first.declared, other.declared)) continue;
|
|
3971
|
+
problems.push(`[game] Slot "${slotName}" declares different outcomes in flow "${first.flow}" and flow "${other.flow}".\n Declare the slot once and use it in both flows.`);
|
|
3972
|
+
}
|
|
3973
|
+
if (first.declared.length === 0) continue;
|
|
3974
|
+
problems.push(...undeclaredExits(slotName, first.declared, features.contributions(slotName)));
|
|
3975
|
+
}
|
|
3976
|
+
return problems;
|
|
3977
|
+
}
|
|
3978
|
+
/**
|
|
3979
|
+
* Warns about the named exits of contributions that a slot declaring no outcomes ignores: the
|
|
3980
|
+
* slot hands over to its next contribution as after `done`.
|
|
3981
|
+
*
|
|
3982
|
+
* @param slotName - Name of the slot.
|
|
3983
|
+
* @param contributions - The contributions of the slot.
|
|
3984
|
+
* @returns One warning per contribution and outcome.
|
|
3985
|
+
*/
|
|
3986
|
+
function ignoredExits(slotName, contributions) {
|
|
3987
|
+
return contributions.flatMap(({ flow }) => declaredOutcomes(flow.outcomes).map((outcome) => `[game] Slot "${slotName}": flow "${flow.id}" exits "${outcome}", which the slot ignores.\n Declare slot("${slotName}", { ${outcome}: type() }) to route it, or exit "done".`));
|
|
3988
|
+
}
|
|
3989
|
+
/**
|
|
3990
|
+
* Warns about contributions without `when` in a slot that declares outcomes: a named exit leaves
|
|
3991
|
+
* the slot, so a later one runs only when the one before it exits `done`.
|
|
3992
|
+
*
|
|
3993
|
+
* @param slotName - Name of the slot.
|
|
3994
|
+
* @param contributions - The contributions of the slot, in order.
|
|
3995
|
+
* @returns One warning per contribution without `when` after the first one.
|
|
3996
|
+
*/
|
|
3997
|
+
function unguardedContributions(slotName, contributions) {
|
|
3998
|
+
const warnings = [];
|
|
3999
|
+
let earlier;
|
|
4000
|
+
for (const contribution of contributions) {
|
|
4001
|
+
if (contribution.when !== void 0) continue;
|
|
4002
|
+
if (earlier !== void 0) warnings.push(`[game] Slot "${slotName}": the flows "${earlier.flow.id}" and "${contribution.flow.id}" have no when.\n A named exit leaves the slot, so "${contribution.flow.id}" runs only when "${earlier.flow.id}" exits done; give them when.`);
|
|
4003
|
+
earlier = contribution;
|
|
4004
|
+
}
|
|
4005
|
+
return warnings;
|
|
4006
|
+
}
|
|
4007
|
+
/**
|
|
4008
|
+
* Warns about the named outcomes of slots: a named exit a slot that declares none ignores, and
|
|
4009
|
+
* contributions without `when` in a slot that declares some.
|
|
4010
|
+
*
|
|
4011
|
+
* @param flows - Every collected flow by id.
|
|
4012
|
+
* @param features - Features API, read for the contributions of every slot.
|
|
4013
|
+
* @returns One warning per finding.
|
|
4014
|
+
*/
|
|
4015
|
+
function slotOutcomeWarnings(flows, features) {
|
|
4016
|
+
const warnings = [];
|
|
4017
|
+
for (const [slotName, [first]] of slotDeclarations(flows)) {
|
|
4018
|
+
const contributions = features.contributions(slotName);
|
|
4019
|
+
warnings.push(...first.declared.length === 0 ? ignoredExits(slotName, contributions) : unguardedContributions(slotName, contributions));
|
|
4020
|
+
}
|
|
4021
|
+
return warnings;
|
|
4022
|
+
}
|
|
4023
|
+
/**
|
|
3892
4024
|
* Reads the scene id of one entry of a feature's `scenes` list. The list holds whatever
|
|
3893
4025
|
* `defineScene` returns; validation reads the `id` and ignores everything else.
|
|
3894
4026
|
*
|
|
@@ -4035,12 +4167,14 @@ function sizeWarnings(flow) {
|
|
|
4035
4167
|
* Validates the whole graph at `run()` and returns every problem as one `[game] …` sentence:
|
|
4036
4168
|
* unreachable node, outcome without an edge, missing target, barrier edge that does not reach a
|
|
4037
4169
|
* rest node of the same flow, cycle with no rest node, slot without a name, equal contribution
|
|
4038
|
-
* `order`, one flow contributed twice to a slot,
|
|
4039
|
-
*
|
|
4040
|
-
*
|
|
4041
|
-
*
|
|
4042
|
-
*
|
|
4043
|
-
*
|
|
4170
|
+
* `order`, one flow contributed twice to a slot, one slot name declared with different outcomes,
|
|
4171
|
+
* a contribution of a slot that declares outcomes which exits another one, `inbox` type that is
|
|
4172
|
+
* not an outcome, rest node with neither `run` nor outcomes, a `recover` on a node that is not a
|
|
4173
|
+
* rest node, a scene id no feature registers, an `over` node that names a scene, `safeNode` that
|
|
4174
|
+
* is not a checkpoint, colliding ids. A `recover` on a node that is not the safe node is a
|
|
4175
|
+
* warning: it never runs there. So are a named exit a slot that declares no outcomes ignores, and
|
|
4176
|
+
* two contributions without `when` in a slot that declares some. Types are bypassed by JSON data
|
|
4177
|
+
* and casts, so this runs even though the edge table is checked at compile time.
|
|
4044
4178
|
*
|
|
4045
4179
|
* @param flows - Every collected flow by id, the main flow first.
|
|
4046
4180
|
* @param features - Features API: slot contributions and feature names.
|
|
@@ -4054,8 +4188,8 @@ function validateGraph(flows, features, config) {
|
|
|
4054
4188
|
problems.push(...edgeProblems(flow), ...nodeProblems(flow), ...recoverProblems(flow), ...reachProblems(flow), ...cycleProblems(flow));
|
|
4055
4189
|
warnings.push(...sizeWarnings(flow));
|
|
4056
4190
|
}
|
|
4057
|
-
warnings.push(...slotNameWarnings(flows, features), ...recoverWarnings(flows, features, config));
|
|
4058
|
-
problems.push(...flowIdProblems(flows), ...featureProblems(flows, features), ...sceneProblems(flows, features), ...safeNodeProblems(config, features));
|
|
4191
|
+
warnings.push(...slotNameWarnings(flows, features), ...slotOutcomeWarnings(flows, features), ...recoverWarnings(flows, features, config));
|
|
4192
|
+
problems.push(...flowIdProblems(flows), ...featureProblems(flows, features), ...slotOutcomeProblems(flows, features), ...sceneProblems(flows, features), ...safeNodeProblems(config, features));
|
|
4059
4193
|
return {
|
|
4060
4194
|
problems,
|
|
4061
4195
|
warnings
|
|
@@ -4545,7 +4679,9 @@ function pickContribution(ctx, features, slotName, from) {
|
|
|
4545
4679
|
}
|
|
4546
4680
|
/**
|
|
4547
4681
|
* Follows one result to the next position: a node of the same flow, a mapped node, or the exit of
|
|
4548
|
-
* a sub-flow, which hands the outcome to the parent.
|
|
4682
|
+
* a sub-flow, which hands the outcome to the parent. The exit of a slot contribution returns to
|
|
4683
|
+
* the slot: on `done` or an outcome the slot does not declare to continue it, on a declared
|
|
4684
|
+
* outcome to leave it.
|
|
4549
4685
|
*
|
|
4550
4686
|
* @param ctx - Domain context of the flow plugin.
|
|
4551
4687
|
* @param frames - The position being planned; mutated.
|
|
@@ -4560,19 +4696,78 @@ function planFrom(ctx, frames, location, outcome, payload, depth) {
|
|
|
4560
4696
|
const target = location.flow.edges[location.name]?.[outcome];
|
|
4561
4697
|
if (target === void 0) return { problem: `[game] Flow "${location.flow.id}": outcome "${outcome}" of node "${location.name}" has no edge.\n Add edges.${location.name}.${outcome}.` };
|
|
4562
4698
|
if (typeof target === "string") return enterTarget(ctx, frames, location.flow, target, payload);
|
|
4563
|
-
if (target.kind === "map")
|
|
4564
|
-
const mapped = target.map(payload);
|
|
4565
|
-
const input = mapped === void 0 ? null : mapped;
|
|
4566
|
-
if (!isJson$1(input)) return { problem: `[game] The mapper of edge "${location.name}.${outcome}" returned a value that is not JSON.\n Return plain JSON from to("${target.target}", ...).` };
|
|
4567
|
-
return enterTarget(ctx, frames, location.flow, target.target, input);
|
|
4568
|
-
}
|
|
4699
|
+
if (target.kind === "map") return enterMapped(ctx, frames, location, outcome, target, payload);
|
|
4569
4700
|
frames.pop();
|
|
4570
4701
|
const parent = locateFrames(ctx, frames);
|
|
4571
4702
|
if (parent === void 0) return { problem: `[game] The main flow "${location.flow.id}" left with outcome "${target.outcome}".\n Give the top-level flow an edge instead of exit("${target.outcome}").` };
|
|
4572
|
-
if (parent.entry.kind === "slot") return continueSlot(frames, location.flow.id);
|
|
4703
|
+
if (parent.entry.kind === "slot") return leavesSlot(parent.entry, target.outcome) ? leaveSlot(frames, target.outcome, payload) : continueSlot(frames, location.flow.id);
|
|
4573
4704
|
return planFrom(ctx, frames, parent, target.outcome, payload, depth + 1);
|
|
4574
4705
|
}
|
|
4575
4706
|
/**
|
|
4707
|
+
* Follows a `to()` edge: maps the payload and enters the target with it. The mapper is game code:
|
|
4708
|
+
* a throw, or a result that is not JSON, is a problem, and the node fails like any other.
|
|
4709
|
+
*
|
|
4710
|
+
* @param ctx - Domain context of the flow plugin.
|
|
4711
|
+
* @param frames - The position being planned; mutated.
|
|
4712
|
+
* @param location - Where the result was produced.
|
|
4713
|
+
* @param outcome - Outcome name of the result.
|
|
4714
|
+
* @param target - The mapped target of the edge.
|
|
4715
|
+
* @param payload - Payload of the result, handed to the mapper.
|
|
4716
|
+
* @returns The plan, or the problem that stops it.
|
|
4717
|
+
*/
|
|
4718
|
+
function enterMapped(ctx, frames, location, outcome, target, payload) {
|
|
4719
|
+
let mapped;
|
|
4720
|
+
try {
|
|
4721
|
+
mapped = target.map(payload);
|
|
4722
|
+
} catch (error) {
|
|
4723
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
4724
|
+
return { problem: `[game] The mapper of edge "${location.name}.${outcome}" threw: ${message}.\n Return plain JSON from to("${target.target}", ...).` };
|
|
4725
|
+
}
|
|
4726
|
+
const input = mapped === void 0 ? null : mapped;
|
|
4727
|
+
if (!isJson$1(input)) return { problem: `[game] The mapper of edge "${location.name}.${outcome}" returned a value that is not JSON.\n Return plain JSON from to("${target.target}", ...).` };
|
|
4728
|
+
return enterTarget(ctx, frames, location.flow, target.target, input);
|
|
4729
|
+
}
|
|
4730
|
+
/**
|
|
4731
|
+
* Tells whether a contribution that exits `outcome` leaves its slot at once: the slot declares the
|
|
4732
|
+
* outcome, and it is not `done`, which hands over to the next contribution. Own keys only:
|
|
4733
|
+
* `"constructor" in outcomes` is true for every object.
|
|
4734
|
+
*
|
|
4735
|
+
* @param slot - The slot the contribution runs in.
|
|
4736
|
+
* @param outcome - The outcome the contribution exited with.
|
|
4737
|
+
* @returns True when the slot takes its own edge of that outcome next.
|
|
4738
|
+
* @example
|
|
4739
|
+
* ```ts
|
|
4740
|
+
* leavesSlot(slot("episodeStep", { shop: type() }), "shop"); // true
|
|
4741
|
+
* leavesSlot(slot("episodeStep", { shop: type() }), "done"); // false: the next contribution runs
|
|
4742
|
+
* ```
|
|
4743
|
+
*/
|
|
4744
|
+
function leavesSlot(slot, outcome) {
|
|
4745
|
+
return outcome !== "done" && Object.hasOwn(slot.outcomes, outcome);
|
|
4746
|
+
}
|
|
4747
|
+
/**
|
|
4748
|
+
* Plans the early leave of a slot: a contribution exited an outcome the slot declares. The plan
|
|
4749
|
+
* stands on the slot node like `continueSlot`, without `after`, and carries the outcome and its
|
|
4750
|
+
* payload: the loop takes the slot's own edge with them after the commit, so the later
|
|
4751
|
+
* contributions do not run and the next entry of the slot starts at its first one.
|
|
4752
|
+
*
|
|
4753
|
+
* @param frames - The position, already popped back to the slot node.
|
|
4754
|
+
* @param outcome - The declared outcome the contribution left on.
|
|
4755
|
+
* @param payload - Its payload, handed on to the slot's edge.
|
|
4756
|
+
* @returns The plan that stands on the slot node.
|
|
4757
|
+
*/
|
|
4758
|
+
function leaveSlot(frames, outcome, payload) {
|
|
4759
|
+
return {
|
|
4760
|
+
stack: frames,
|
|
4761
|
+
next: framePath(frames),
|
|
4762
|
+
rest: false,
|
|
4763
|
+
checkpoint: false,
|
|
4764
|
+
leave: {
|
|
4765
|
+
outcome,
|
|
4766
|
+
payload
|
|
4767
|
+
}
|
|
4768
|
+
};
|
|
4769
|
+
}
|
|
4770
|
+
/**
|
|
4576
4771
|
* Plans the return to a slot after one of its contributions ended. The next contribution is NOT
|
|
4577
4772
|
* picked here: `when` reads the committed state, and a plan is made before the commit of the edge.
|
|
4578
4773
|
* So the plan stands on the slot node and names the contribution that ended; the loop picks the
|
|
@@ -4609,7 +4804,7 @@ function asError$4(value) {
|
|
|
4609
4804
|
/**
|
|
4610
4805
|
* Commits the transaction of one node, releases its hints, journals the edge and emits
|
|
4611
4806
|
* `flow:edge`. This is the only commit of the engine: the edge of a node, and the recovery edge
|
|
4612
|
-
* of the safe node, both go through it.
|
|
4807
|
+
* of the safe node, both go through it. It counts the edge among those since the last rest node.
|
|
4613
4808
|
*
|
|
4614
4809
|
* @param ctx - Domain context of the flow plugin.
|
|
4615
4810
|
* @param modules - Injected sibling APIs.
|
|
@@ -4619,6 +4814,7 @@ function asError$4(value) {
|
|
|
4619
4814
|
*/
|
|
4620
4815
|
function commitEdge(ctx, modules, step, next) {
|
|
4621
4816
|
const commit = step.transaction.commit();
|
|
4817
|
+
ctx.state.runner.restless += 1;
|
|
4622
4818
|
ctx.deps.time.wake();
|
|
4623
4819
|
modules.fx.release();
|
|
4624
4820
|
const entry = pushEntry(ctx.state.runner, {
|
|
@@ -4682,6 +4878,8 @@ function handleFailure(ctx, modules, transaction, error, path, safe) {
|
|
|
4682
4878
|
});
|
|
4683
4879
|
state.stack = [...target];
|
|
4684
4880
|
state.slotAfter = void 0;
|
|
4881
|
+
state.slotLeave = void 0;
|
|
4882
|
+
state.restless = 0;
|
|
4685
4883
|
if (!retry) {
|
|
4686
4884
|
safe.inside = true;
|
|
4687
4885
|
safe.recover = true;
|
|
@@ -4707,9 +4905,11 @@ function handleAbort(modules, transaction, reason) {
|
|
|
4707
4905
|
}
|
|
4708
4906
|
/**
|
|
4709
4907
|
* Moves the position to a planned place and, when that place is a rest node, makes it the rest
|
|
4710
|
-
* point of the loop: the frame rollback and `bookmark` return to, a zeroed failure count, a
|
|
4711
|
-
* compacted journal at a checkpoint, `flow:rest` and the walk's
|
|
4712
|
-
* rest point in the store first. Used by the edge, and by the
|
|
4908
|
+
* point of the loop: the frame rollback and `bookmark` return to, a zeroed failure count, a zeroed
|
|
4909
|
+
* count of edges without a rest, a compacted journal at a checkpoint, `flow:rest` and the walk's
|
|
4910
|
+
* rest listeners. The caller marks the rest point in the store first. Used by the edge, and by the
|
|
4911
|
+
* loop when it enters a slot. A slot it stands on keeps the contribution that ended, or the
|
|
4912
|
+
* declared outcome one left on, for the next turn.
|
|
4713
4913
|
*
|
|
4714
4914
|
* @param ctx - Domain context of the flow plugin.
|
|
4715
4915
|
* @param plan - Where the loop stands now.
|
|
@@ -4718,9 +4918,11 @@ function arrive(ctx, plan) {
|
|
|
4718
4918
|
const state = ctx.state.runner;
|
|
4719
4919
|
state.stack = plan.stack;
|
|
4720
4920
|
state.slotAfter = plan.after;
|
|
4921
|
+
state.slotLeave = plan.leave;
|
|
4721
4922
|
if (!plan.rest) return;
|
|
4722
4923
|
state.restFrame = [...plan.stack];
|
|
4723
4924
|
state.failures = 0;
|
|
4925
|
+
state.restless = 0;
|
|
4724
4926
|
if (plan.checkpoint) compact(state);
|
|
4725
4927
|
ctx.emit("flow:rest", {
|
|
4726
4928
|
path: plan.next,
|
|
@@ -4730,7 +4932,9 @@ function arrive(ctx, plan) {
|
|
|
4730
4932
|
}
|
|
4731
4933
|
/**
|
|
4732
4934
|
* Takes the edge of one result: plan, commit, mark the barrier or the rest point, move the
|
|
4733
|
-
* position and announce a rest node.
|
|
4935
|
+
* position and announce a rest node. A plan with a problem fails the node, and so does an edge
|
|
4936
|
+
* beyond the cap of edges without a rest node: a loop of transit nodes that never rests would
|
|
4937
|
+
* freeze the game.
|
|
4734
4938
|
*
|
|
4735
4939
|
* @param ctx - Domain context of the flow plugin.
|
|
4736
4940
|
* @param modules - Injected sibling APIs.
|
|
@@ -4742,6 +4946,10 @@ async function finishNode(ctx, modules, step, safe) {
|
|
|
4742
4946
|
const state = ctx.state.runner;
|
|
4743
4947
|
const plan = planFrom(ctx, [...state.stack], step.location, step.result.outcome, step.result.payload, 0);
|
|
4744
4948
|
if ("problem" in plan) return handleFailure(ctx, modules, step.transaction, new Error(plan.problem), step.path, safe);
|
|
4949
|
+
if (state.restless >= state.restlessCap) {
|
|
4950
|
+
const restless = `[game] ${state.restlessCap} edges without a rest node, at "${step.path}".\n Put a rest node in the loop.`;
|
|
4951
|
+
return handleFailure(ctx, modules, step.transaction, new Error(restless), step.path, safe);
|
|
4952
|
+
}
|
|
4745
4953
|
const entry = commitEdge(ctx, modules, step, plan.next);
|
|
4746
4954
|
if (step.barrier) await ctx.deps.model.store.markBarrier(`${step.path}#${entry.index}@${step.now}`);
|
|
4747
4955
|
else if (plan.rest) ctx.deps.model.store.markRest();
|
|
@@ -4891,27 +5099,47 @@ function to(target, map) {
|
|
|
4891
5099
|
};
|
|
4892
5100
|
}
|
|
4893
5101
|
/**
|
|
4894
|
-
*
|
|
4895
|
-
*
|
|
5102
|
+
* Builds the slot. The body works on the widened shapes the runner reads: the signature above has
|
|
5103
|
+
* already kept `done` out of the declared outcomes for a typed caller, and the check here refuses
|
|
5104
|
+
* it past a cast.
|
|
4896
5105
|
*
|
|
4897
5106
|
* @param name - Slot name features contribute to.
|
|
5107
|
+
* @param outcomes - The named outcomes besides `done`.
|
|
4898
5108
|
* @returns The slot node as plain data.
|
|
4899
|
-
* @
|
|
4900
|
-
* ```ts
|
|
4901
|
-
* // The main flow leaves room after a finished order. Features contribute sub-flows to it.
|
|
4902
|
-
* nodes: { boot, home, board: boardFlow, afterOrder: slot("afterOrder") }
|
|
4903
|
-
* // slot("afterOrder"): { kind: "slot", name: "afterOrder", input: …, outcomes: { done: … } }
|
|
4904
|
-
* ```
|
|
5109
|
+
* @throws {TypeError} When `outcomes` declares `done`.
|
|
4905
5110
|
*/
|
|
4906
|
-
function slot(name) {
|
|
5111
|
+
function slot(name, outcomes) {
|
|
5112
|
+
if (outcomes !== void 0 && Object.hasOwn(outcomes, "done")) throw new TypeError(`[game] Slot "${name}" declares the outcome "done".\n done is every slot's own outcome; remove it from slot("${name}", ...).`);
|
|
4907
5113
|
return {
|
|
4908
5114
|
kind: "slot",
|
|
4909
5115
|
name,
|
|
4910
5116
|
input: type(),
|
|
4911
|
-
outcomes: {
|
|
5117
|
+
outcomes: {
|
|
5118
|
+
done: type(),
|
|
5119
|
+
...outcomes
|
|
5120
|
+
}
|
|
4912
5121
|
};
|
|
4913
5122
|
}
|
|
4914
5123
|
/**
|
|
5124
|
+
* Builds the entry. The body works on the widened shapes the runner reads: the signature above
|
|
5125
|
+
* has already checked the flow against the slot.
|
|
5126
|
+
*
|
|
5127
|
+
* @param host - The slot node the flow runs in.
|
|
5128
|
+
* @param spec - The flow, its `order` and an optional `when`.
|
|
5129
|
+
* @returns The entry under the slot's name.
|
|
5130
|
+
*/
|
|
5131
|
+
function contribution(host, spec) {
|
|
5132
|
+
const { flow, order, when } = spec;
|
|
5133
|
+
return { [host.name]: when === void 0 ? {
|
|
5134
|
+
flow,
|
|
5135
|
+
order
|
|
5136
|
+
} : {
|
|
5137
|
+
flow,
|
|
5138
|
+
order,
|
|
5139
|
+
when
|
|
5140
|
+
} };
|
|
5141
|
+
}
|
|
5142
|
+
/**
|
|
4915
5143
|
* Builds the `out` of a node context: one method per declared outcome. A method called without
|
|
4916
5144
|
* data produces a `null` payload. Called by the loop when it enters a node, never by a game.
|
|
4917
5145
|
*
|
|
@@ -5676,7 +5904,8 @@ function enterSubFlow(ctx, modules, location, sub, safe) {
|
|
|
5676
5904
|
}
|
|
5677
5905
|
/**
|
|
5678
5906
|
* Enters a slot: the first contribution whose `when` passes, or the slot's `done` edge when the
|
|
5679
|
-
* slot has no contribution to run.
|
|
5907
|
+
* slot has no contribution to run. When a contribution just left on an outcome the slot declares,
|
|
5908
|
+
* the slot takes its own edge of that outcome instead, with the contribution's payload.
|
|
5680
5909
|
*
|
|
5681
5910
|
* @param ctx - Domain context of the flow plugin.
|
|
5682
5911
|
* @param modules - Injected sibling APIs.
|
|
@@ -5687,6 +5916,14 @@ function enterSubFlow(ctx, modules, location, sub, safe) {
|
|
|
5687
5916
|
*/
|
|
5688
5917
|
function enterSlot(ctx, modules, location, slot, safe) {
|
|
5689
5918
|
const state = ctx.state.runner;
|
|
5919
|
+
const leave = state.slotLeave;
|
|
5920
|
+
if (leave !== void 0) {
|
|
5921
|
+
state.slotLeave = void 0;
|
|
5922
|
+
return applyResult(ctx, modules, location, leave, safe, {
|
|
5923
|
+
barrier: false,
|
|
5924
|
+
now: ctx.deps.clock.now()
|
|
5925
|
+
});
|
|
5926
|
+
}
|
|
5690
5927
|
const finished = state.slotAfter;
|
|
5691
5928
|
const contributions = modules.features.contributions(slot.name);
|
|
5692
5929
|
const from = finished === void 0 ? 0 : contributions.findIndex((item) => item.flow.id === finished) + 1;
|
|
@@ -5742,8 +5979,10 @@ function enterBookmark(ctx, modules, bookmark, safe) {
|
|
|
5742
5979
|
ctx.deps.model.store.markRest();
|
|
5743
5980
|
state.stack = framesOf(location.trail, bookmark.input);
|
|
5744
5981
|
state.slotAfter = void 0;
|
|
5982
|
+
state.slotLeave = void 0;
|
|
5745
5983
|
state.restFrame = [...state.stack];
|
|
5746
5984
|
state.failures = 0;
|
|
5985
|
+
state.restless = 0;
|
|
5747
5986
|
ctx.emit("flow:rest", {
|
|
5748
5987
|
path: bookmark.path,
|
|
5749
5988
|
checkpoint: isRestCheckpoint(location.entry)
|
|
@@ -5759,7 +5998,7 @@ function enterBookmark(ctx, modules, bookmark, safe) {
|
|
|
5759
5998
|
* @returns `"stop"` when the runner was stopped, `"continue"` otherwise.
|
|
5760
5999
|
* @throws {Error} When the position names no node of the graph.
|
|
5761
6000
|
*/
|
|
5762
|
-
async function advance(ctx, modules, safe) {
|
|
6001
|
+
async function advance$1(ctx, modules, safe) {
|
|
5763
6002
|
const state = ctx.state.runner;
|
|
5764
6003
|
if (stopped(state)) return "stop";
|
|
5765
6004
|
const restoring = state.seam?.restoring;
|
|
@@ -5777,7 +6016,8 @@ async function advance(ctx, modules, safe) {
|
|
|
5777
6016
|
* Runs the graph until aborted: enter node, `onEnter` stages, begin a transaction, await the
|
|
5778
6017
|
* body, commit on the edge, release hints, journal, emit `flow:edge`, follow the edge. An error
|
|
5779
6018
|
* discards, rolls back, is logged and retries, then enters `safeNode`, whose `recover` runs first
|
|
5780
|
-
* as one committed edge; a failure inside `safeNode` rejects.
|
|
6019
|
+
* as one committed edge; a failure inside `safeNode` rejects. More edges without a rest node than
|
|
6020
|
+
* the graph's cap is such an error too.
|
|
5781
6021
|
* Abort reasons: `"stop"` ends the loop, `"inbox"` turns the event into the outcome, `"restore"`
|
|
5782
6022
|
* enters the bookmark's frame.
|
|
5783
6023
|
*
|
|
@@ -5791,6 +6031,7 @@ async function runLoop(ctx, modules) {
|
|
|
5791
6031
|
const state = ctx.state.runner;
|
|
5792
6032
|
state.flows = collectGraph(ctx, modules.features);
|
|
5793
6033
|
checkGraph(ctx, modules.features);
|
|
6034
|
+
state.restlessCap = restlessCapOf(main, modules.features.contributions);
|
|
5794
6035
|
modules.features.seal();
|
|
5795
6036
|
await ctx.deps.model.store.load();
|
|
5796
6037
|
state.stack = [{
|
|
@@ -5800,13 +6041,15 @@ async function runLoop(ctx, modules) {
|
|
|
5800
6041
|
}];
|
|
5801
6042
|
state.restFrame = [...state.stack];
|
|
5802
6043
|
state.slotAfter = void 0;
|
|
6044
|
+
state.slotLeave = void 0;
|
|
5803
6045
|
state.failures = 0;
|
|
6046
|
+
state.restless = 0;
|
|
5804
6047
|
const safe = {
|
|
5805
6048
|
inside: false,
|
|
5806
6049
|
recover: false
|
|
5807
6050
|
};
|
|
5808
6051
|
const loop = { running: true };
|
|
5809
|
-
while (loop.running) loop.running = await advance(ctx, modules, safe) === "continue";
|
|
6052
|
+
while (loop.running) loop.running = await advance$1(ctx, modules, safe) === "continue";
|
|
5810
6053
|
}
|
|
5811
6054
|
/**
|
|
5812
6055
|
* Restores a bookmark through the running loop: the active node is aborted with reason
|
|
@@ -6680,6 +6923,9 @@ function createInboxState() {
|
|
|
6680
6923
|
//#endregion
|
|
6681
6924
|
//#region src/plugins/flow/runner/state.ts
|
|
6682
6925
|
/**
|
|
6926
|
+
* @file flow/runner — state factory.
|
|
6927
|
+
*/
|
|
6928
|
+
/**
|
|
6683
6929
|
* Creates the empty flow registry. It lives in its own function because the plugin's lint rule
|
|
6684
6930
|
* L5 refuses a collection built inside an exported declaration.
|
|
6685
6931
|
*
|
|
@@ -6709,6 +6955,9 @@ function createRunnerState() {
|
|
|
6709
6955
|
stack: [],
|
|
6710
6956
|
restFrame: void 0,
|
|
6711
6957
|
slotAfter: void 0,
|
|
6958
|
+
slotLeave: void 0,
|
|
6959
|
+
restless: 0,
|
|
6960
|
+
restlessCap: 64,
|
|
6712
6961
|
journal: [],
|
|
6713
6962
|
journalIndex: 0,
|
|
6714
6963
|
running: void 0,
|
|
@@ -24939,7 +25188,7 @@ function finishRun(warm) {
|
|
|
24939
25188
|
* @param pixi - The module that draws.
|
|
24940
25189
|
* @returns The tree, or `undefined` when it cannot be built.
|
|
24941
25190
|
*/
|
|
24942
|
-
function objectsOf(sctx, pixi) {
|
|
25191
|
+
function objectsOf$1(sctx, pixi) {
|
|
24943
25192
|
const { warm } = sctx.ctx.state.sync;
|
|
24944
25193
|
if (warm.objects !== void 0) return warm.objects;
|
|
24945
25194
|
try {
|
|
@@ -24967,7 +25216,7 @@ function warmStep(sctx) {
|
|
|
24967
25216
|
const { warm } = sctx.ctx.state.sync;
|
|
24968
25217
|
if (warm.head >= warm.queue.length) return false;
|
|
24969
25218
|
const pixi = sctx.deps.host.pixi();
|
|
24970
|
-
const objects = pixi === void 0 ? void 0 : objectsOf(sctx, pixi);
|
|
25219
|
+
const objects = pixi === void 0 ? void 0 : objectsOf$1(sctx, pixi);
|
|
24971
25220
|
if (pixi === void 0 || objects === void 0) return false;
|
|
24972
25221
|
const budget = sctx.ctx.config.warm;
|
|
24973
25222
|
let drawn = 0;
|
|
@@ -26026,6 +26275,1003 @@ function clearLayers(state) {
|
|
|
26026
26275
|
state.hudLayer = void 0;
|
|
26027
26276
|
}
|
|
26028
26277
|
//#endregion
|
|
26278
|
+
//#region src/plugins/renderer/sync/shape-path.ts
|
|
26279
|
+
/**
|
|
26280
|
+
* @file renderer/sync — the path of a `Shape`: a rectangle, a rounded rectangle or a triangle,
|
|
26281
|
+
* its fill, and its stroke, solid or dashed, or an arc, a stroke alone. A dashed stroke walks the
|
|
26282
|
+
* outline (straight edges, and rounded corners sampled as arcs) and strokes one open line per dash.
|
|
26283
|
+
* An arc runs on the circle inscribed in its box, from 12 o'clock clockwise.
|
|
26284
|
+
*/
|
|
26285
|
+
/** Chords per rounded corner of a dashed outline: under 0.3 u off the arc at a 48 u radius. */
|
|
26286
|
+
const CORNER_SAMPLES = 8;
|
|
26287
|
+
/** The gap between two dashes, as a fraction of the dash length. */
|
|
26288
|
+
const GAP_RATIO = .5;
|
|
26289
|
+
/** Segments of a full ring: a ring of 28 u across strays under 0.03 u off its circle. */
|
|
26290
|
+
const RING_SEGMENTS = 48;
|
|
26291
|
+
/** The fewest segments of an arc that draws: a sliver still bends. */
|
|
26292
|
+
const ARC_MIN_SEGMENTS = 2;
|
|
26293
|
+
/** 12 o'clock, where an arc starts: Pixi's angles run clockwise from 3 o'clock. */
|
|
26294
|
+
const TWELVE = -Math.PI / 2;
|
|
26295
|
+
/**
|
|
26296
|
+
* The corner radius the rectangle is drawn with: Pixi clamps it to half the shorter side.
|
|
26297
|
+
*
|
|
26298
|
+
* @param value - The shape value.
|
|
26299
|
+
* @returns The radius in reference units, 0 for square corners.
|
|
26300
|
+
* @example
|
|
26301
|
+
* ```ts
|
|
26302
|
+
* radiusOf({ ...Shape.defaults, w: 100, h: 20, radius: 40 }); // 10
|
|
26303
|
+
* ```
|
|
26304
|
+
*/
|
|
26305
|
+
function radiusOf(value) {
|
|
26306
|
+
return Math.max(0, Math.min(value.radius, value.w / 2, value.h / 2));
|
|
26307
|
+
}
|
|
26308
|
+
/**
|
|
26309
|
+
* The closed outline of a shape as points, clockwise from the top left (from the end of the top
|
|
26310
|
+
* left corner on a rounded rectangle); the last point repeats the first.
|
|
26311
|
+
*
|
|
26312
|
+
* @param value - The shape value.
|
|
26313
|
+
* @returns The points of the outline.
|
|
26314
|
+
* @example
|
|
26315
|
+
* ```ts
|
|
26316
|
+
* outlineOf({ ...Shape.defaults, kind: "triangle", w: 60, h: 40 });
|
|
26317
|
+
* // [{ x: 0, y: 0 }, { x: 60, y: 20 }, { x: 0, y: 40 }, { x: 0, y: 0 }]
|
|
26318
|
+
* ```
|
|
26319
|
+
*/
|
|
26320
|
+
function outlineOf(value) {
|
|
26321
|
+
const { w, h } = value;
|
|
26322
|
+
if (value.kind === "triangle") return [
|
|
26323
|
+
{
|
|
26324
|
+
x: 0,
|
|
26325
|
+
y: 0
|
|
26326
|
+
},
|
|
26327
|
+
{
|
|
26328
|
+
x: w,
|
|
26329
|
+
y: h / 2
|
|
26330
|
+
},
|
|
26331
|
+
{
|
|
26332
|
+
x: 0,
|
|
26333
|
+
y: h
|
|
26334
|
+
},
|
|
26335
|
+
{
|
|
26336
|
+
x: 0,
|
|
26337
|
+
y: 0
|
|
26338
|
+
}
|
|
26339
|
+
];
|
|
26340
|
+
const radius = radiusOf(value);
|
|
26341
|
+
if (radius === 0) return [
|
|
26342
|
+
{
|
|
26343
|
+
x: 0,
|
|
26344
|
+
y: 0
|
|
26345
|
+
},
|
|
26346
|
+
{
|
|
26347
|
+
x: w,
|
|
26348
|
+
y: 0
|
|
26349
|
+
},
|
|
26350
|
+
{
|
|
26351
|
+
x: w,
|
|
26352
|
+
y: h
|
|
26353
|
+
},
|
|
26354
|
+
{
|
|
26355
|
+
x: 0,
|
|
26356
|
+
y: h
|
|
26357
|
+
},
|
|
26358
|
+
{
|
|
26359
|
+
x: 0,
|
|
26360
|
+
y: 0
|
|
26361
|
+
}
|
|
26362
|
+
];
|
|
26363
|
+
const corners = [
|
|
26364
|
+
{
|
|
26365
|
+
edge: {
|
|
26366
|
+
x: w - radius,
|
|
26367
|
+
y: 0
|
|
26368
|
+
},
|
|
26369
|
+
center: {
|
|
26370
|
+
x: w - radius,
|
|
26371
|
+
y: radius
|
|
26372
|
+
}
|
|
26373
|
+
},
|
|
26374
|
+
{
|
|
26375
|
+
edge: {
|
|
26376
|
+
x: w,
|
|
26377
|
+
y: h - radius
|
|
26378
|
+
},
|
|
26379
|
+
center: {
|
|
26380
|
+
x: w - radius,
|
|
26381
|
+
y: h - radius
|
|
26382
|
+
}
|
|
26383
|
+
},
|
|
26384
|
+
{
|
|
26385
|
+
edge: {
|
|
26386
|
+
x: radius,
|
|
26387
|
+
y: h
|
|
26388
|
+
},
|
|
26389
|
+
center: {
|
|
26390
|
+
x: radius,
|
|
26391
|
+
y: h - radius
|
|
26392
|
+
}
|
|
26393
|
+
},
|
|
26394
|
+
{
|
|
26395
|
+
edge: {
|
|
26396
|
+
x: 0,
|
|
26397
|
+
y: radius
|
|
26398
|
+
},
|
|
26399
|
+
center: {
|
|
26400
|
+
x: radius,
|
|
26401
|
+
y: radius
|
|
26402
|
+
}
|
|
26403
|
+
}
|
|
26404
|
+
];
|
|
26405
|
+
const points = [{
|
|
26406
|
+
x: radius,
|
|
26407
|
+
y: 0
|
|
26408
|
+
}];
|
|
26409
|
+
for (const [index, { edge, center }] of corners.entries()) {
|
|
26410
|
+
const from = -Math.PI / 2 + index * Math.PI / 2;
|
|
26411
|
+
points.push(edge);
|
|
26412
|
+
for (let sample = 1; sample <= CORNER_SAMPLES; sample += 1) {
|
|
26413
|
+
const angle = from + sample * Math.PI / 2 / CORNER_SAMPLES;
|
|
26414
|
+
points.push({
|
|
26415
|
+
x: center.x + radius * Math.cos(angle),
|
|
26416
|
+
y: center.y + radius * Math.sin(angle)
|
|
26417
|
+
});
|
|
26418
|
+
}
|
|
26419
|
+
}
|
|
26420
|
+
return points;
|
|
26421
|
+
}
|
|
26422
|
+
/**
|
|
26423
|
+
* Adds a point to a line unless the line already ends there.
|
|
26424
|
+
*
|
|
26425
|
+
* @param line - The line.
|
|
26426
|
+
* @param point - The point.
|
|
26427
|
+
*/
|
|
26428
|
+
function extend(line, point) {
|
|
26429
|
+
const last = line.at(-1);
|
|
26430
|
+
if (last?.x !== point.x || last.y !== point.y) line.push(point);
|
|
26431
|
+
}
|
|
26432
|
+
/**
|
|
26433
|
+
* Cuts a polyline into dashes of `dash` length with gaps of half a dash, starting with a dash at
|
|
26434
|
+
* its first point. A dash bends round the corners it crosses; the last one may be shorter.
|
|
26435
|
+
*
|
|
26436
|
+
* @param outline - The points of the polyline.
|
|
26437
|
+
* @param dash - The dash length, above 0.
|
|
26438
|
+
* @returns One open line per dash.
|
|
26439
|
+
* @example
|
|
26440
|
+
* ```ts
|
|
26441
|
+
* dashesOf([{ x: 0, y: 0 }, { x: 40, y: 0 }], 10);
|
|
26442
|
+
* // [[{ x: 0, y: 0 }, { x: 10, y: 0 }], [{ x: 15, y: 0 }, { x: 25, y: 0 }], [{ x: 30, y: 0 }, { x: 40, y: 0 }]]
|
|
26443
|
+
* ```
|
|
26444
|
+
*/
|
|
26445
|
+
function dashesOf(outline, dash) {
|
|
26446
|
+
const first = outline[0];
|
|
26447
|
+
if (first === void 0) return [];
|
|
26448
|
+
const dashes = [];
|
|
26449
|
+
let line = [first];
|
|
26450
|
+
let left = dash;
|
|
26451
|
+
for (let index = 1; index < outline.length; index += 1) {
|
|
26452
|
+
let from = outline[index - 1] ?? first;
|
|
26453
|
+
const to = outline[index] ?? first;
|
|
26454
|
+
let length = Math.hypot(to.x - from.x, to.y - from.y);
|
|
26455
|
+
while (length > left) {
|
|
26456
|
+
from = {
|
|
26457
|
+
x: from.x + (to.x - from.x) * left / length,
|
|
26458
|
+
y: from.y + (to.y - from.y) * left / length
|
|
26459
|
+
};
|
|
26460
|
+
length -= left;
|
|
26461
|
+
if (line === void 0) {
|
|
26462
|
+
line = [from];
|
|
26463
|
+
left = dash;
|
|
26464
|
+
} else {
|
|
26465
|
+
extend(line, from);
|
|
26466
|
+
dashes.push(line);
|
|
26467
|
+
line = void 0;
|
|
26468
|
+
left = dash * GAP_RATIO;
|
|
26469
|
+
}
|
|
26470
|
+
}
|
|
26471
|
+
left -= length;
|
|
26472
|
+
if (line !== void 0) extend(line, to);
|
|
26473
|
+
}
|
|
26474
|
+
if (line !== void 0 && line.length > 1) dashes.push(line);
|
|
26475
|
+
return dashes;
|
|
26476
|
+
}
|
|
26477
|
+
/**
|
|
26478
|
+
* Draws the path of a shape and fills it in a colour at the alpha given. At alpha 0 the path stays unfilled, so only
|
|
26479
|
+
* a stroke shows. A clip mask is drawn here too, never dashed; the path of an arc here is its box, which is what it
|
|
26480
|
+
* clips its children to. A fill-only shape is drawn white and coloured by the object's tint, so a colour change draws
|
|
26481
|
+
* no path (spec 26 R2.1).
|
|
26482
|
+
*
|
|
26483
|
+
* @param object - The graphics to draw on.
|
|
26484
|
+
* @param value - The shape value.
|
|
26485
|
+
* @param fillAlpha - The alpha of the fill: the value's own for the shape, 1 for a clip mask.
|
|
26486
|
+
* @param color - The colour of the fill: the value's own by default, white for a tinted shape.
|
|
26487
|
+
*/
|
|
26488
|
+
function drawShapePath(object, value, fillAlpha, color = value.fill) {
|
|
26489
|
+
object.clear();
|
|
26490
|
+
if (value.kind === "triangle") object.moveTo(0, 0).lineTo(value.w, value.h / 2).lineTo(0, value.h).closePath();
|
|
26491
|
+
else if (value.kind === "rect" && value.radius > 0) object.roundRect(0, 0, value.w, value.h, value.radius);
|
|
26492
|
+
else object.rect(0, 0, value.w, value.h);
|
|
26493
|
+
if (fillAlpha <= 0) return;
|
|
26494
|
+
object.fill(fillAlpha < 1 ? {
|
|
26495
|
+
color,
|
|
26496
|
+
alpha: fillAlpha
|
|
26497
|
+
} : { color });
|
|
26498
|
+
}
|
|
26499
|
+
/**
|
|
26500
|
+
* Strokes the outline drawn by `drawShapePath`: solid over the same path, or, with a `dash`, as
|
|
26501
|
+
* one open line per dash on a new path. Nothing without a stroke width.
|
|
26502
|
+
*
|
|
26503
|
+
* @param object - The graphics the shape was drawn on.
|
|
26504
|
+
* @param value - The shape value.
|
|
26505
|
+
*/
|
|
26506
|
+
function strokeShape(object, value) {
|
|
26507
|
+
if (value.strokeWidth <= 0) return;
|
|
26508
|
+
const style = {
|
|
26509
|
+
color: value.stroke,
|
|
26510
|
+
width: value.strokeWidth
|
|
26511
|
+
};
|
|
26512
|
+
if (value.dash <= 0) {
|
|
26513
|
+
object.stroke(style);
|
|
26514
|
+
return;
|
|
26515
|
+
}
|
|
26516
|
+
object.beginPath();
|
|
26517
|
+
for (const [start, ...rest] of dashesOf(outlineOf(value), value.dash)) {
|
|
26518
|
+
if (start === void 0) continue;
|
|
26519
|
+
object.moveTo(start.x, start.y);
|
|
26520
|
+
for (const point of rest) object.lineTo(point.x, point.y);
|
|
26521
|
+
}
|
|
26522
|
+
object.stroke(style);
|
|
26523
|
+
}
|
|
26524
|
+
/**
|
|
26525
|
+
* The share of the circle an arc sweeps, clamped to 0..1: anything not above 0, `NaN` too, is 0; a whole ring when
|
|
26526
|
+
* the value leaves it out.
|
|
26527
|
+
*
|
|
26528
|
+
* @param value - The shape value.
|
|
26529
|
+
* @returns The share.
|
|
26530
|
+
* @example
|
|
26531
|
+
* ```ts
|
|
26532
|
+
* sweepOf({ ...Shape.defaults, kind: "arc", sweep: 1.4 }); // 1
|
|
26533
|
+
* ```
|
|
26534
|
+
*/
|
|
26535
|
+
function sweepOf(value) {
|
|
26536
|
+
const sweep = value.sweep ?? arcDefaults.sweep;
|
|
26537
|
+
if (Number.isNaN(sweep) || sweep <= 0) return 0;
|
|
26538
|
+
return Math.min(sweep, 1);
|
|
26539
|
+
}
|
|
26540
|
+
/**
|
|
26541
|
+
* The radius of the circle an arc runs on: inscribed in the shorter side of its box, inside its stroke.
|
|
26542
|
+
*
|
|
26543
|
+
* @param value - The shape value.
|
|
26544
|
+
* @returns The radius in reference units; 0 or below when the stroke leaves no circle.
|
|
26545
|
+
* @example
|
|
26546
|
+
* ```ts
|
|
26547
|
+
* arcRadiusOf({ ...Shape.defaults, kind: "arc", w: 28, h: 40, strokeWidth: 2 }); // 13
|
|
26548
|
+
* ```
|
|
26549
|
+
*/
|
|
26550
|
+
function arcRadiusOf(value) {
|
|
26551
|
+
return (Math.min(value.w, value.h) - value.strokeWidth) / 2;
|
|
26552
|
+
}
|
|
26553
|
+
/**
|
|
26554
|
+
* Tells whether an arc draws nothing: no stroke width, no sweep, or no circle inside its stroke.
|
|
26555
|
+
*
|
|
26556
|
+
* @param value - The shape value, an arc.
|
|
26557
|
+
* @returns True for an arc whose `Graphics` would hold no instruction.
|
|
26558
|
+
* @example
|
|
26559
|
+
* ```ts
|
|
26560
|
+
* isEmptyArc({ ...Shape.defaults, kind: "arc", w: 28, h: 28, strokeWidth: 2, sweep: 0 }); // true
|
|
26561
|
+
* ```
|
|
26562
|
+
*/
|
|
26563
|
+
function isEmptyArc(value) {
|
|
26564
|
+
return value.strokeWidth <= 0 || sweepOf(value) <= 0 || arcRadiusOf(value) <= 0;
|
|
26565
|
+
}
|
|
26566
|
+
/**
|
|
26567
|
+
* Strokes an arc: from 12 o'clock clockwise over its share of the circle, in segments as many as its angle needs (48
|
|
26568
|
+
* for a ring, at least 2), ended by its `cap`. A full ring is closed and has no ends. Nothing for an empty arc. Drawn
|
|
26569
|
+
* only when the shape changed: never rebuilt per frame.
|
|
26570
|
+
*
|
|
26571
|
+
* @param object - The graphics, cleared.
|
|
26572
|
+
* @param value - The shape value, an arc.
|
|
26573
|
+
*/
|
|
26574
|
+
function strokeArc(object, value) {
|
|
26575
|
+
if (isEmptyArc(value)) return;
|
|
26576
|
+
const sweep = sweepOf(value);
|
|
26577
|
+
const radius = arcRadiusOf(value);
|
|
26578
|
+
const ring = sweep >= 1;
|
|
26579
|
+
const segments = Math.max(ARC_MIN_SEGMENTS, Math.ceil(sweep * RING_SEGMENTS));
|
|
26580
|
+
const step = sweep * 2 * Math.PI / segments;
|
|
26581
|
+
const centreX = value.w / 2;
|
|
26582
|
+
const centreY = value.h / 2;
|
|
26583
|
+
const last = ring ? segments - 1 : segments;
|
|
26584
|
+
object.moveTo(centreX, centreY - radius);
|
|
26585
|
+
for (let index = 1; index <= last; index += 1) {
|
|
26586
|
+
const angle = TWELVE + index * step;
|
|
26587
|
+
object.lineTo(centreX + radius * Math.cos(angle), centreY + radius * Math.sin(angle));
|
|
26588
|
+
}
|
|
26589
|
+
if (ring) {
|
|
26590
|
+
object.closePath();
|
|
26591
|
+
object.stroke({
|
|
26592
|
+
color: value.stroke,
|
|
26593
|
+
width: value.strokeWidth
|
|
26594
|
+
});
|
|
26595
|
+
return;
|
|
26596
|
+
}
|
|
26597
|
+
object.stroke({
|
|
26598
|
+
color: value.stroke,
|
|
26599
|
+
width: value.strokeWidth,
|
|
26600
|
+
cap: value.cap ?? arcDefaults.cap
|
|
26601
|
+
});
|
|
26602
|
+
}
|
|
26603
|
+
/**
|
|
26604
|
+
* Paints a shape on its graphics: an arc as its stroke alone, every other kind as its path, its fill in a colour at its
|
|
26605
|
+
* own fill alpha, and its stroke. The one call that draws a shape's visual.
|
|
26606
|
+
*
|
|
26607
|
+
* @param object - The graphics.
|
|
26608
|
+
* @param value - The shape value.
|
|
26609
|
+
* @param color - The colour of the fill: white for a tinted shape, the value's own otherwise.
|
|
26610
|
+
*/
|
|
26611
|
+
function paintShape(object, value, color) {
|
|
26612
|
+
if (value.kind === "arc") {
|
|
26613
|
+
object.clear();
|
|
26614
|
+
strokeArc(object, value);
|
|
26615
|
+
return;
|
|
26616
|
+
}
|
|
26617
|
+
drawShapePath(object, value, value.fillAlpha, color);
|
|
26618
|
+
strokeShape(object, value);
|
|
26619
|
+
}
|
|
26620
|
+
//#endregion
|
|
26621
|
+
//#region src/plugins/renderer/sync/textures.ts
|
|
26622
|
+
/** The colour a missing texture is drawn in. */
|
|
26623
|
+
const PLACEHOLDER_TINT = 16711935;
|
|
26624
|
+
/**
|
|
26625
|
+
* Creates an empty set. It lives in its own non-exported function because lint rule L5 refuses a
|
|
26626
|
+
* collection built inside an exported declaration.
|
|
26627
|
+
*
|
|
26628
|
+
* @returns An empty set.
|
|
26629
|
+
*/
|
|
26630
|
+
function emptySet$5() {
|
|
26631
|
+
return /* @__PURE__ */ new Set();
|
|
26632
|
+
}
|
|
26633
|
+
/**
|
|
26634
|
+
* Asks the providers from the newest to the oldest. The first answer wins, so a bundle loaded
|
|
26635
|
+
* later shadows an older one.
|
|
26636
|
+
*
|
|
26637
|
+
* @param sctx - Domain context of the sync module.
|
|
26638
|
+
* @param key - The asset key.
|
|
26639
|
+
* @returns The texture, or `undefined` when nothing answered.
|
|
26640
|
+
*/
|
|
26641
|
+
function resolveTexture(sctx, key) {
|
|
26642
|
+
const providers = sctx.ctx.state.sync.providers;
|
|
26643
|
+
for (let index = providers.length - 1; index >= 0; index -= 1) {
|
|
26644
|
+
const texture = providers[index]?.(key);
|
|
26645
|
+
if (texture !== void 0) return texture;
|
|
26646
|
+
}
|
|
26647
|
+
}
|
|
26648
|
+
/**
|
|
26649
|
+
* The 1x1 white texture the placeholder is drawn with.
|
|
26650
|
+
*
|
|
26651
|
+
* @param sctx - Domain context of the sync module.
|
|
26652
|
+
* @returns The texture, or `undefined` while inert.
|
|
26653
|
+
*/
|
|
26654
|
+
function placeholderTexture(sctx) {
|
|
26655
|
+
return sctx.deps.host.pixi()?.Texture.WHITE;
|
|
26656
|
+
}
|
|
26657
|
+
/**
|
|
26658
|
+
* Reports a key no provider answers. One key warns once, however many views use it.
|
|
26659
|
+
*
|
|
26660
|
+
* @param sctx - Domain context of the sync module.
|
|
26661
|
+
* @param key - The asset key.
|
|
26662
|
+
*/
|
|
26663
|
+
function warnMissing(sctx, key) {
|
|
26664
|
+
const warned = sctx.ctx.state.sync.warned;
|
|
26665
|
+
if (key === "" || warned.has(key)) return;
|
|
26666
|
+
warned.add(key);
|
|
26667
|
+
sctx.ctx.log.warn("renderer: no texture for asset key", { key });
|
|
26668
|
+
}
|
|
26669
|
+
/**
|
|
26670
|
+
* Remembers that an entity draws a key, so `invalidate` finds it again.
|
|
26671
|
+
*
|
|
26672
|
+
* @param state - The sync branch of the plugin state.
|
|
26673
|
+
* @param entity - The entity.
|
|
26674
|
+
* @param key - The asset key.
|
|
26675
|
+
*/
|
|
26676
|
+
function trackKey(state, entity, key) {
|
|
26677
|
+
const users = state.byKey.get(key) ?? emptySet$5();
|
|
26678
|
+
users.add(entity);
|
|
26679
|
+
state.byKey.set(key, users);
|
|
26680
|
+
}
|
|
26681
|
+
/**
|
|
26682
|
+
* Forgets that an entity draws a key.
|
|
26683
|
+
*
|
|
26684
|
+
* @param state - The sync branch of the plugin state.
|
|
26685
|
+
* @param entity - The entity.
|
|
26686
|
+
* @param key - The asset key.
|
|
26687
|
+
*/
|
|
26688
|
+
function untrackKey(state, entity, key) {
|
|
26689
|
+
const users = state.byKey.get(key);
|
|
26690
|
+
if (users === void 0) return;
|
|
26691
|
+
users.delete(entity);
|
|
26692
|
+
if (users.size === 0) state.byKey.delete(key);
|
|
26693
|
+
}
|
|
26694
|
+
/**
|
|
26695
|
+
* The Pixi module of a renderer that draws. A texture made while it does not would be lost with
|
|
26696
|
+
* the device, so the member that asked throws.
|
|
26697
|
+
*
|
|
26698
|
+
* @param sctx - Domain context of the sync module.
|
|
26699
|
+
* @param member - The `textures` member that asked, for the message.
|
|
26700
|
+
* @returns The Pixi module.
|
|
26701
|
+
* @throws {Error} When the renderer does not draw.
|
|
26702
|
+
*/
|
|
26703
|
+
function drawingPixi(sctx, member) {
|
|
26704
|
+
const pixi = sctx.deps.host.pixi();
|
|
26705
|
+
if (pixi === void 0 || !sctx.deps.host.ready()) throw new Error(`[game] renderer.sync.textures.${member} needs a ready renderer.\n Check app.renderer.host.ready() first.`);
|
|
26706
|
+
return pixi;
|
|
26707
|
+
}
|
|
26708
|
+
/**
|
|
26709
|
+
* The default borders of a texture, from the four numbers `nine` gives.
|
|
26710
|
+
*
|
|
26711
|
+
* @param nine - Left, top, right and bottom in pixels.
|
|
26712
|
+
* @returns The borders, as Pixi reads them.
|
|
26713
|
+
* @example
|
|
26714
|
+
* ```ts
|
|
26715
|
+
* defaultBordersOf([24, 20, 16, 12]); // { left: 24, top: 20, right: 16, bottom: 12 }
|
|
26716
|
+
* ```
|
|
26717
|
+
*/
|
|
26718
|
+
function defaultBordersOf(nine) {
|
|
26719
|
+
return {
|
|
26720
|
+
left: nine[0],
|
|
26721
|
+
top: nine[1],
|
|
26722
|
+
right: nine[2],
|
|
26723
|
+
bottom: nine[3]
|
|
26724
|
+
};
|
|
26725
|
+
}
|
|
26726
|
+
/**
|
|
26727
|
+
* Makes a Pixi texture out of a decoded image, on behalf of `assets`.
|
|
26728
|
+
*
|
|
26729
|
+
* @param sctx - Domain context of the sync module.
|
|
26730
|
+
* @param image - The decoded image.
|
|
26731
|
+
* @param options - Nine-slice borders in pixels, and whether Pixi generates the mip chain.
|
|
26732
|
+
* @returns The new texture.
|
|
26733
|
+
* @throws {Error} When the renderer does not draw.
|
|
26734
|
+
*/
|
|
26735
|
+
function createTexture(sctx, image, options) {
|
|
26736
|
+
const pixi = drawingPixi(sctx, "create");
|
|
26737
|
+
const base = options?.mips === true ? pixi.Texture.from({
|
|
26738
|
+
resource: image,
|
|
26739
|
+
autoGenerateMipmaps: true
|
|
26740
|
+
}) : pixi.Texture.from(image);
|
|
26741
|
+
const nine = options?.nine;
|
|
26742
|
+
if (nine === void 0) return base;
|
|
26743
|
+
const bordered = new pixi.Texture({
|
|
26744
|
+
source: base.source,
|
|
26745
|
+
defaultBorders: defaultBordersOf(nine)
|
|
26746
|
+
});
|
|
26747
|
+
base.destroy(false);
|
|
26748
|
+
return bordered;
|
|
26749
|
+
}
|
|
26750
|
+
/**
|
|
26751
|
+
* Tells whether a frame lies inside a page of the given size. A negative number, or one that is
|
|
26752
|
+
* not a number, lies outside.
|
|
26753
|
+
*
|
|
26754
|
+
* @param frame - The frame in page pixels.
|
|
26755
|
+
* @param page - The size of the page.
|
|
26756
|
+
* @param page.width - Width of the page in pixels.
|
|
26757
|
+
* @param page.height - Height of the page in pixels.
|
|
26758
|
+
* @returns True when the whole frame is on the page.
|
|
26759
|
+
* @example
|
|
26760
|
+
* ```ts
|
|
26761
|
+
* frameFits({ x: 256, y: 384, width: 256, height: 128 }, { width: 512, height: 512 }); // true
|
|
26762
|
+
* ```
|
|
26763
|
+
*/
|
|
26764
|
+
function frameFits(frame, page) {
|
|
26765
|
+
return frame.x >= 0 && frame.y >= 0 && frame.width >= 0 && frame.height >= 0 && frame.x + frame.width <= page.width && frame.y + frame.height <= page.height;
|
|
26766
|
+
}
|
|
26767
|
+
/**
|
|
26768
|
+
* Cuts a texture out of an atlas page, on behalf of `assets`. The slice shares the page's source,
|
|
26769
|
+
* its frame is offset by the page's own frame, and it is marked as a slice so `destroyTexture`
|
|
26770
|
+
* frees only the wrapper.
|
|
26771
|
+
*
|
|
26772
|
+
* @param sctx - Domain context of the sync module.
|
|
26773
|
+
* @param page - The page texture.
|
|
26774
|
+
* @param frame - The frame in page pixels.
|
|
26775
|
+
* @param options - Nine-slice borders, in pixels.
|
|
26776
|
+
* @returns The slice.
|
|
26777
|
+
* @throws {Error} When the renderer does not draw, and when the frame does not fit in the page.
|
|
26778
|
+
*/
|
|
26779
|
+
function sliceTexture(sctx, page, frame, options) {
|
|
26780
|
+
const pixi = drawingPixi(sctx, "slice");
|
|
26781
|
+
if (!frameFits(frame, page)) throw new Error(`[game] renderer.sync.textures.slice: frame ${frame.x},${frame.y} ${frame.width}x${frame.height} is outside page ${page.width}x${page.height}.\n Run "bun run assets:pack".`);
|
|
26782
|
+
const nine = options?.nine;
|
|
26783
|
+
const slice = new pixi.Texture({
|
|
26784
|
+
source: page.source,
|
|
26785
|
+
frame: new pixi.Rectangle(page.frame.x + frame.x, page.frame.y + frame.y, frame.width, frame.height),
|
|
26786
|
+
...nine === void 0 ? {} : { defaultBorders: defaultBordersOf(nine) }
|
|
26787
|
+
});
|
|
26788
|
+
sctx.ctx.state.sync.slices.add(slice);
|
|
26789
|
+
return slice;
|
|
26790
|
+
}
|
|
26791
|
+
/**
|
|
26792
|
+
* The cache key of one crop: the asset key and the box it covers.
|
|
26793
|
+
*
|
|
26794
|
+
* @param key - The asset key.
|
|
26795
|
+
* @param width - Width of the box.
|
|
26796
|
+
* @param height - Height of the box.
|
|
26797
|
+
* @returns The key of the crop.
|
|
26798
|
+
* @example
|
|
26799
|
+
* ```ts
|
|
26800
|
+
* frameKeyOf("board.bg", 1080, 1920); // "board.bg@1080x1920"
|
|
26801
|
+
* ```
|
|
26802
|
+
*/
|
|
26803
|
+
function frameKeyOf(key, width, height) {
|
|
26804
|
+
return `${key}@${width}x${height}`;
|
|
26805
|
+
}
|
|
26806
|
+
/**
|
|
26807
|
+
* The texture a `"cover"` sprite draws: the part of `base` inside `crop`, sharing its source. One
|
|
26808
|
+
* crop per asset key and box, shared by every entity that shows it; the entity is added to its
|
|
26809
|
+
* users. A key that answers a new base has been invalidated, so every view of it resolves again
|
|
26810
|
+
* in this pass and joins the new crop; the old crop goes at once.
|
|
26811
|
+
*
|
|
26812
|
+
* @param sctx - Domain context of the sync module.
|
|
26813
|
+
* @param entity - The entity that shows the crop.
|
|
26814
|
+
* @param key - The asset key the base was resolved for.
|
|
26815
|
+
* @param base - The texture the key resolves to.
|
|
26816
|
+
* @param box - The box the sprite covers, which names the crop.
|
|
26817
|
+
* @param box.width - Width of the box.
|
|
26818
|
+
* @param box.height - Height of the box.
|
|
26819
|
+
* @param crop - The part of the base to show, in texture pixels.
|
|
26820
|
+
* @returns The crop and its `frames` key, or the base and `""` while inert.
|
|
26821
|
+
*/
|
|
26822
|
+
function coverTexture(sctx, entity, key, base, box, crop) {
|
|
26823
|
+
const frames = sctx.ctx.state.sync.frames;
|
|
26824
|
+
const frameKey = frameKeyOf(key, box.width, box.height);
|
|
26825
|
+
const cached = frames.get(frameKey);
|
|
26826
|
+
if (cached?.base === base) {
|
|
26827
|
+
cached.users.add(entity);
|
|
26828
|
+
return {
|
|
26829
|
+
texture: cached.texture,
|
|
26830
|
+
frameKey
|
|
26831
|
+
};
|
|
26832
|
+
}
|
|
26833
|
+
const pixi = sctx.deps.host.pixi();
|
|
26834
|
+
if (pixi === void 0) return {
|
|
26835
|
+
texture: base,
|
|
26836
|
+
frameKey: ""
|
|
26837
|
+
};
|
|
26838
|
+
if (cached !== void 0) cached.texture.destroy(false);
|
|
26839
|
+
const texture = new pixi.Texture({
|
|
26840
|
+
source: base.source,
|
|
26841
|
+
frame: new pixi.Rectangle(base.frame.x + crop.x, base.frame.y + crop.y, crop.width, crop.height)
|
|
26842
|
+
});
|
|
26843
|
+
const users = emptySet$5();
|
|
26844
|
+
users.add(entity);
|
|
26845
|
+
frames.set(frameKey, {
|
|
26846
|
+
base,
|
|
26847
|
+
texture,
|
|
26848
|
+
users
|
|
26849
|
+
});
|
|
26850
|
+
return {
|
|
26851
|
+
texture,
|
|
26852
|
+
frameKey
|
|
26853
|
+
};
|
|
26854
|
+
}
|
|
26855
|
+
/**
|
|
26856
|
+
* Lets an entity go of the crop it showed. The last user frees the crop, keeping the source it
|
|
26857
|
+
* shares with its base.
|
|
26858
|
+
*
|
|
26859
|
+
* @param state - The sync branch of the plugin state.
|
|
26860
|
+
* @param entity - The entity that no longer shows it.
|
|
26861
|
+
* @param frameKey - The `frames` key of the crop; `""` for none.
|
|
26862
|
+
*/
|
|
26863
|
+
function releaseCover(state, entity, frameKey) {
|
|
26864
|
+
const frame = state.frames.get(frameKey);
|
|
26865
|
+
if (frame === void 0) return;
|
|
26866
|
+
frame.users.delete(entity);
|
|
26867
|
+
if (frame.users.size > 0) return;
|
|
26868
|
+
frame.texture.destroy(false);
|
|
26869
|
+
state.frames.delete(frameKey);
|
|
26870
|
+
}
|
|
26871
|
+
/**
|
|
26872
|
+
* Frees the crops cut from one texture, keeping the source they share with it.
|
|
26873
|
+
*
|
|
26874
|
+
* @param state - The sync branch of the plugin state.
|
|
26875
|
+
* @param base - The texture that goes.
|
|
26876
|
+
*/
|
|
26877
|
+
function releaseFrames$1(state, base) {
|
|
26878
|
+
for (const [frameKey, frame] of state.frames) {
|
|
26879
|
+
if (frame.base !== base) continue;
|
|
26880
|
+
frame.texture.destroy(false);
|
|
26881
|
+
state.frames.delete(frameKey);
|
|
26882
|
+
}
|
|
26883
|
+
}
|
|
26884
|
+
/**
|
|
26885
|
+
* Frees every crop. Works on the state alone, because `onStop` has no context.
|
|
26886
|
+
*
|
|
26887
|
+
* @param state - The sync branch of the plugin state.
|
|
26888
|
+
*/
|
|
26889
|
+
function clearFrames(state) {
|
|
26890
|
+
for (const frame of state.frames.values()) frame.texture.destroy(false);
|
|
26891
|
+
state.frames.clear();
|
|
26892
|
+
}
|
|
26893
|
+
/**
|
|
26894
|
+
* Frees a texture, its source and the crops cut from it. A slice keeps the source: it belongs to
|
|
26895
|
+
* the page, which frees it. A source that goes lets go of its `change` listeners first, the ones
|
|
26896
|
+
* of its style too, so no bind group Pixi still keeps is told. A texture destroyed twice is a
|
|
26897
|
+
* no-op.
|
|
26898
|
+
*
|
|
26899
|
+
* @param state - The sync branch of the plugin state.
|
|
26900
|
+
* @param texture - The texture to free.
|
|
26901
|
+
*/
|
|
26902
|
+
function destroyTexture(state, texture) {
|
|
26903
|
+
releaseFrames$1(state, texture);
|
|
26904
|
+
if (texture.destroyed) return;
|
|
26905
|
+
if (state.slices.has(texture)) {
|
|
26906
|
+
texture.destroy(false);
|
|
26907
|
+
return;
|
|
26908
|
+
}
|
|
26909
|
+
texture.source.style.removeAllListeners("change");
|
|
26910
|
+
texture.source.removeAllListeners("change");
|
|
26911
|
+
texture.destroy(true);
|
|
26912
|
+
}
|
|
26913
|
+
//#endregion
|
|
26914
|
+
//#region src/plugins/renderer/sync/leave.ts
|
|
26915
|
+
/** Label of the container the look hangs in, last on the stage. */
|
|
26916
|
+
const TOP_LABEL = "renderer.leave";
|
|
26917
|
+
/** Clock milliseconds of one turn of the loader. */
|
|
26918
|
+
const TURN_MS = 1e3;
|
|
26919
|
+
/** A whole turn, in radians. */
|
|
26920
|
+
const TURN$1 = 2 * Math.PI;
|
|
26921
|
+
/** The engine's ring: three quarters of a white circle 96 u across, 10 u wide, round ends, turned about its centre. */
|
|
26922
|
+
const RING = Object.freeze({
|
|
26923
|
+
kind: "arc",
|
|
26924
|
+
w: 96,
|
|
26925
|
+
h: 96,
|
|
26926
|
+
fill: 16777215,
|
|
26927
|
+
fillAlpha: 0,
|
|
26928
|
+
alpha: 1,
|
|
26929
|
+
radius: 0,
|
|
26930
|
+
stroke: 16777215,
|
|
26931
|
+
strokeWidth: 10,
|
|
26932
|
+
dash: 0,
|
|
26933
|
+
clip: false,
|
|
26934
|
+
sweep: .75,
|
|
26935
|
+
cap: "round"
|
|
26936
|
+
});
|
|
26937
|
+
/**
|
|
26938
|
+
* Makes the engine's objects of the picture, once per application: the top container with the veil and the frame, and
|
|
26939
|
+
* in the frame the holder of a lifted layer, the ring and the spinner. Nothing hangs on the stage yet.
|
|
26940
|
+
*
|
|
26941
|
+
* @param sctx - Domain context of the sync module.
|
|
26942
|
+
* @returns The objects, or `undefined` while nothing draws (inert, a lost device).
|
|
26943
|
+
*/
|
|
26944
|
+
function objectsOf(sctx) {
|
|
26945
|
+
const leave = sctx.ctx.state.sync.leave;
|
|
26946
|
+
if (leave.objects !== void 0) return leave.objects;
|
|
26947
|
+
const pixi = sctx.deps.host.pixi();
|
|
26948
|
+
if (pixi === void 0 || sctx.deps.host.stage() === void 0) return void 0;
|
|
26949
|
+
const top = new pixi.Container();
|
|
26950
|
+
const veil = new pixi.Sprite(pixi.Texture.WHITE);
|
|
26951
|
+
const frame = new pixi.Container();
|
|
26952
|
+
const held = new pixi.Container();
|
|
26953
|
+
const ring = new pixi.Graphics();
|
|
26954
|
+
const spinner = new pixi.Sprite(pixi.Texture.WHITE);
|
|
26955
|
+
top.label = TOP_LABEL;
|
|
26956
|
+
veil.label = `${TOP_LABEL}.cover`;
|
|
26957
|
+
veil.anchor.set(0, 0);
|
|
26958
|
+
held.label = `${TOP_LABEL}.layer`;
|
|
26959
|
+
ring.label = `${TOP_LABEL}.ring`;
|
|
26960
|
+
paintShape(ring, RING, RING.stroke);
|
|
26961
|
+
ring.pivot.set(RING.w / 2, RING.h / 2);
|
|
26962
|
+
spinner.label = `${TOP_LABEL}.texture`;
|
|
26963
|
+
spinner.anchor.set(.5, .5);
|
|
26964
|
+
frame.addChild(held);
|
|
26965
|
+
frame.addChild(ring);
|
|
26966
|
+
frame.addChild(spinner);
|
|
26967
|
+
top.addChild(veil);
|
|
26968
|
+
top.addChild(frame);
|
|
26969
|
+
leave.objects = {
|
|
26970
|
+
top,
|
|
26971
|
+
veil,
|
|
26972
|
+
frame,
|
|
26973
|
+
held,
|
|
26974
|
+
ring,
|
|
26975
|
+
spinner
|
|
26976
|
+
};
|
|
26977
|
+
return leave.objects;
|
|
26978
|
+
}
|
|
26979
|
+
/**
|
|
26980
|
+
* What the loader of a look draws once due: a game texture, a layer above the veil, or the engine's ring. A texture key
|
|
26981
|
+
* no provider answers is warned once and draws the ring.
|
|
26982
|
+
*
|
|
26983
|
+
* @param sctx - Domain context of the sync module.
|
|
26984
|
+
* @param look - The look being shown.
|
|
26985
|
+
* @returns The loader's kind, and the texture of a `"texture"` loader.
|
|
26986
|
+
*/
|
|
26987
|
+
function loaderOf$1(sctx, look) {
|
|
26988
|
+
const loader = look.loader;
|
|
26989
|
+
if (loader === void 0) return { loader: "none" };
|
|
26990
|
+
if (loader.texture === void 0) return { loader: loader.due === void 0 ? "ring" : "layer" };
|
|
26991
|
+
const texture = resolveTexture(sctx, loader.texture);
|
|
26992
|
+
if (texture !== void 0) return {
|
|
26993
|
+
loader: "texture",
|
|
26994
|
+
texture
|
|
26995
|
+
};
|
|
26996
|
+
warnMissing(sctx, loader.texture);
|
|
26997
|
+
return { loader: "ring" };
|
|
26998
|
+
}
|
|
26999
|
+
/**
|
|
27000
|
+
* Puts a layer the look drew above the veil back under the root, at the place of its layer: the layer list of the
|
|
27001
|
+
* scene orders the root's children. A layer the list of a swap took back already, or let go, is left alone.
|
|
27002
|
+
*
|
|
27003
|
+
* @param sctx - Domain context of the sync module.
|
|
27004
|
+
* @param run - The look whose layer goes back.
|
|
27005
|
+
*/
|
|
27006
|
+
function unlift(sctx, run) {
|
|
27007
|
+
const lifted = run.lifted;
|
|
27008
|
+
if (lifted === void 0) return;
|
|
27009
|
+
delete run.lifted;
|
|
27010
|
+
const state = sctx.ctx.state.sync;
|
|
27011
|
+
if (lifted.parent !== state.leave.objects?.held) return;
|
|
27012
|
+
const name = run.layer;
|
|
27013
|
+
const at = state.layerList?.findIndex((spec) => spec.name === name) ?? -1;
|
|
27014
|
+
const root = state.root;
|
|
27015
|
+
state.passesVersion += 1;
|
|
27016
|
+
if (root === void 0 || at === -1) {
|
|
27017
|
+
detach$1(lifted);
|
|
27018
|
+
return;
|
|
27019
|
+
}
|
|
27020
|
+
root.addChildAt(lifted, Math.min(at, root.children.length));
|
|
27021
|
+
}
|
|
27022
|
+
/**
|
|
27023
|
+
* Takes the look down at once: its layer goes back, its objects leave the stage and are kept for the next look, and the
|
|
27024
|
+
* spinner lets the game's texture go. A frame is asked for, so the screen without it is drawn.
|
|
27025
|
+
*
|
|
27026
|
+
* @param sctx - Domain context of the sync module.
|
|
27027
|
+
*/
|
|
27028
|
+
function takeDown(sctx) {
|
|
27029
|
+
const leave = sctx.ctx.state.sync.leave;
|
|
27030
|
+
const run = leave.run;
|
|
27031
|
+
if (run !== void 0) unlift(sctx, run);
|
|
27032
|
+
delete leave.run;
|
|
27033
|
+
const objects = leave.objects;
|
|
27034
|
+
if (objects !== void 0) {
|
|
27035
|
+
detach$1(objects.top);
|
|
27036
|
+
const white = placeholderTexture(sctx);
|
|
27037
|
+
if (white !== void 0) objects.spinner.texture = white;
|
|
27038
|
+
}
|
|
27039
|
+
sctx.deps.demand.request();
|
|
27040
|
+
}
|
|
27041
|
+
/**
|
|
27042
|
+
* Moves the veil's level on the clock: up to 1 over `inMs` before the swap, down to 0 over `outMs` after it.
|
|
27043
|
+
*
|
|
27044
|
+
* @param run - The running look.
|
|
27045
|
+
* @param now - The clock now, ms.
|
|
27046
|
+
* @returns True when the level moved: the frame is drawn.
|
|
27047
|
+
*/
|
|
27048
|
+
function advance(run, now) {
|
|
27049
|
+
const cover = run.look.cover;
|
|
27050
|
+
const elapsed = Math.max(0, now - run.steppedAt);
|
|
27051
|
+
const before = run.level;
|
|
27052
|
+
run.steppedAt = now;
|
|
27053
|
+
if (cover === void 0) return false;
|
|
27054
|
+
if (run.landed) run.level = cover.outMs > 0 ? Math.max(0, before - elapsed / cover.outMs) : 0;
|
|
27055
|
+
else run.level = cover.inMs > 0 ? Math.min(1, before + elapsed / cover.inMs) : 1;
|
|
27056
|
+
return run.level !== before;
|
|
27057
|
+
}
|
|
27058
|
+
/**
|
|
27059
|
+
* Hangs the look last on the stage and over the canvas as it stands: the veil covers the screen, the frame carries the
|
|
27060
|
+
* root's transform, so what it holds lands where the scene's reference units do.
|
|
27061
|
+
*
|
|
27062
|
+
* @param sctx - Domain context of the sync module.
|
|
27063
|
+
* @param objects - The engine's objects.
|
|
27064
|
+
* @param stage - The stage of the application.
|
|
27065
|
+
* @param screen - The canvas in CSS pixels, the stage's own space.
|
|
27066
|
+
* @param screen.x - Its left edge.
|
|
27067
|
+
* @param screen.y - Its top edge.
|
|
27068
|
+
* @param screen.width - Its width.
|
|
27069
|
+
* @param screen.height - Its height.
|
|
27070
|
+
*/
|
|
27071
|
+
function hang$1(sctx, objects, stage, screen) {
|
|
27072
|
+
if (stage.children.at(-1) !== objects.top) stage.addChild(objects.top);
|
|
27073
|
+
objects.veil.position.set(screen.x, screen.y);
|
|
27074
|
+
objects.veil.scale.set(screen.width, screen.height);
|
|
27075
|
+
sctx.deps.viewport.apply(objects.frame);
|
|
27076
|
+
}
|
|
27077
|
+
/**
|
|
27078
|
+
* Asks a `"layer"` loader's `due`, once, the first frame it is due: what it draws is made now, and it answers the
|
|
27079
|
+
* layer that holds it. No layer, or a throw (logged), draws the ring instead.
|
|
27080
|
+
*
|
|
27081
|
+
* @param sctx - Domain context of the sync module.
|
|
27082
|
+
* @param run - The running look, its loader due.
|
|
27083
|
+
*/
|
|
27084
|
+
function ask(sctx, run) {
|
|
27085
|
+
run.asked = true;
|
|
27086
|
+
let layer;
|
|
27087
|
+
try {
|
|
27088
|
+
layer = run.look.loader?.due?.();
|
|
27089
|
+
} catch (error) {
|
|
27090
|
+
sctx.ctx.log.error("renderer: the loader of a scene being left threw", { error });
|
|
27091
|
+
}
|
|
27092
|
+
if (layer === void 0) run.loader = "ring";
|
|
27093
|
+
else run.layer = layer;
|
|
27094
|
+
}
|
|
27095
|
+
/**
|
|
27096
|
+
* Writes the veil of a running look: its colour, and its alpha at the level it stands at; hidden for a look without one.
|
|
27097
|
+
*
|
|
27098
|
+
* @param objects - The engine's objects.
|
|
27099
|
+
* @param run - The running look.
|
|
27100
|
+
*/
|
|
27101
|
+
function paintVeil(objects, run) {
|
|
27102
|
+
const cover = run.look.cover;
|
|
27103
|
+
objects.veil.visible = cover !== void 0;
|
|
27104
|
+
if (cover === void 0) return;
|
|
27105
|
+
objects.veil.tint = cover.color;
|
|
27106
|
+
objects.veil.alpha = cover.alpha * run.level;
|
|
27107
|
+
}
|
|
27108
|
+
/**
|
|
27109
|
+
* Keeps the layer of a shown `"layer"` loader in the holder above the veil until the swap. A layer the scene does not
|
|
27110
|
+
* declare draws nothing.
|
|
27111
|
+
*
|
|
27112
|
+
* @param sctx - Domain context of the sync module.
|
|
27113
|
+
* @param objects - The engine's objects.
|
|
27114
|
+
* @param run - The running look, its loader shown.
|
|
27115
|
+
*/
|
|
27116
|
+
function lift(sctx, objects, run) {
|
|
27117
|
+
const state = sctx.ctx.state.sync;
|
|
27118
|
+
const container = run.layer === void 0 ? void 0 : layerContainer(state, run.layer);
|
|
27119
|
+
if (container === void 0 || container.parent === objects.held) return;
|
|
27120
|
+
objects.held.addChild(container);
|
|
27121
|
+
run.lifted = container;
|
|
27122
|
+
state.passesVersion += 1;
|
|
27123
|
+
}
|
|
27124
|
+
/**
|
|
27125
|
+
* Turns the ring or the spinner at the centre of the frame: one turn a second from the switch start.
|
|
27126
|
+
*
|
|
27127
|
+
* @param objects - The engine's objects.
|
|
27128
|
+
* @param run - The running look, its loader shown.
|
|
27129
|
+
* @param now - The clock now, ms.
|
|
27130
|
+
* @param size - The frame in reference units.
|
|
27131
|
+
*/
|
|
27132
|
+
function turn(objects, run, now, size) {
|
|
27133
|
+
const angle = (now - run.startedAt) % TURN_MS / TURN_MS * TURN$1;
|
|
27134
|
+
const turned = run.loader === "ring" ? objects.ring : objects.spinner;
|
|
27135
|
+
if (run.texture !== void 0 && objects.spinner.texture !== run.texture) objects.spinner.texture = run.texture;
|
|
27136
|
+
turned.position.set(size.width / 2, size.height / 2);
|
|
27137
|
+
turned.rotation = angle;
|
|
27138
|
+
}
|
|
27139
|
+
/**
|
|
27140
|
+
* Shows the loader once the switch took `afterMs`, until the swap: a layer loader asks its `due` then and lifts the
|
|
27141
|
+
* layer it answers above the veil; a ring or a texture turns. A loader that comes or goes moves the walks of `shown`,
|
|
27142
|
+
* so the loops of a lifted layer's views run while it shows.
|
|
27143
|
+
*
|
|
27144
|
+
* @param sctx - Domain context of the sync module.
|
|
27145
|
+
* @param objects - The engine's objects.
|
|
27146
|
+
* @param run - The running look.
|
|
27147
|
+
* @param now - The clock now, ms.
|
|
27148
|
+
* @returns True when the loader came, went or turns in this frame: the frame is drawn.
|
|
27149
|
+
*/
|
|
27150
|
+
function paintLoader(sctx, objects, run, now) {
|
|
27151
|
+
const afterMs = run.look.loader?.afterMs ?? 0;
|
|
27152
|
+
const shown = !run.landed && run.loader !== "none" && now - run.startedAt >= afterMs;
|
|
27153
|
+
const due = shown && run.loader === "layer";
|
|
27154
|
+
if (due && !run.asked) ask(sctx, run);
|
|
27155
|
+
if (due) lift(sctx, objects, run);
|
|
27156
|
+
const toggled = shown !== run.shown;
|
|
27157
|
+
if (toggled) {
|
|
27158
|
+
run.shown = shown;
|
|
27159
|
+
sctx.ctx.state.sync.passesVersion += 1;
|
|
27160
|
+
}
|
|
27161
|
+
const isLayer = shown && run.loader === "layer";
|
|
27162
|
+
objects.held.visible = isLayer;
|
|
27163
|
+
objects.ring.visible = shown && run.loader === "ring";
|
|
27164
|
+
objects.spinner.visible = shown && run.loader === "texture";
|
|
27165
|
+
if (!shown || isLayer) return toggled;
|
|
27166
|
+
turn(objects, run, now, sctx.deps.viewport.size());
|
|
27167
|
+
return true;
|
|
27168
|
+
}
|
|
27169
|
+
/**
|
|
27170
|
+
* Shows a look from now, in place of one that runs: the veil fades in from where it stands (0, or the level of a veil
|
|
27171
|
+
* still fading out after the last swap), the loader waits for its `afterMs`. Inert, nothing is kept.
|
|
27172
|
+
*
|
|
27173
|
+
* @param sctx - Domain context of the sync module.
|
|
27174
|
+
* @param look - The veil and the loader, resolved.
|
|
27175
|
+
*/
|
|
27176
|
+
function showLeave(sctx, look) {
|
|
27177
|
+
const state = sctx.ctx.state.sync;
|
|
27178
|
+
if (state.root === void 0) return;
|
|
27179
|
+
const before = state.leave.run;
|
|
27180
|
+
if (before !== void 0) unlift(sctx, before);
|
|
27181
|
+
const now = sctx.ctx.deps.clock.now();
|
|
27182
|
+
const level = before?.look.cover === void 0 ? 0 : before.level;
|
|
27183
|
+
const loader = loaderOf$1(sctx, look);
|
|
27184
|
+
state.leave.run = {
|
|
27185
|
+
look,
|
|
27186
|
+
startedAt: now,
|
|
27187
|
+
steppedAt: now,
|
|
27188
|
+
level,
|
|
27189
|
+
landed: false,
|
|
27190
|
+
asked: false,
|
|
27191
|
+
shown: false,
|
|
27192
|
+
...loader
|
|
27193
|
+
};
|
|
27194
|
+
sctx.deps.demand.request();
|
|
27195
|
+
}
|
|
27196
|
+
/**
|
|
27197
|
+
* The swap came: the loader goes at once and the veil fades out over its `outMs` from where it stands; a look without
|
|
27198
|
+
* a veil, or with `outMs: 0`, is taken down at once. Nothing runs, or the swap was told already: nothing happens.
|
|
27199
|
+
*
|
|
27200
|
+
* @param sctx - Domain context of the sync module.
|
|
27201
|
+
*/
|
|
27202
|
+
function landLeave(sctx) {
|
|
27203
|
+
const run = sctx.ctx.state.sync.leave.run;
|
|
27204
|
+
if (run === void 0 || run.landed) return;
|
|
27205
|
+
const cover = run.look.cover;
|
|
27206
|
+
if (cover === void 0 || cover.outMs <= 0) {
|
|
27207
|
+
takeDown(sctx);
|
|
27208
|
+
return;
|
|
27209
|
+
}
|
|
27210
|
+
unlift(sctx, run);
|
|
27211
|
+
run.landed = true;
|
|
27212
|
+
run.steppedAt = sctx.ctx.deps.clock.now();
|
|
27213
|
+
sctx.deps.demand.request();
|
|
27214
|
+
}
|
|
27215
|
+
/**
|
|
27216
|
+
* The switch ended without a swap: the veil and the loader go at once. Nothing runs: nothing happens.
|
|
27217
|
+
*
|
|
27218
|
+
* @param sctx - Domain context of the sync module.
|
|
27219
|
+
*/
|
|
27220
|
+
function dropLeave(sctx) {
|
|
27221
|
+
if (sctx.ctx.state.sync.leave.run === void 0) return;
|
|
27222
|
+
takeDown(sctx);
|
|
27223
|
+
}
|
|
27224
|
+
/**
|
|
27225
|
+
* The look of this frame, before the decision: the veil's level from the clock, the loader once due, both placed over
|
|
27226
|
+
* the canvas as it stands, and a frame asked for while either moves. A look whose veil faded out after the swap is
|
|
27227
|
+
* taken down. One field read and no allocation while no look runs; a lost device leaves the look waiting for the
|
|
27228
|
+
* restore, which makes its objects again.
|
|
27229
|
+
*
|
|
27230
|
+
* @param sctx - Domain context of the sync module.
|
|
27231
|
+
*/
|
|
27232
|
+
function stepLeave(sctx) {
|
|
27233
|
+
const run = sctx.ctx.state.sync.leave.run;
|
|
27234
|
+
if (run === void 0) return;
|
|
27235
|
+
const objects = objectsOf(sctx);
|
|
27236
|
+
const stage = sctx.deps.host.stage();
|
|
27237
|
+
const screen = sctx.deps.host.screen();
|
|
27238
|
+
if (objects === void 0 || stage === void 0 || screen === void 0) return;
|
|
27239
|
+
const now = sctx.ctx.deps.clock.now();
|
|
27240
|
+
const moved = advance(run, now);
|
|
27241
|
+
if (run.landed && run.level <= 0) {
|
|
27242
|
+
takeDown(sctx);
|
|
27243
|
+
return;
|
|
27244
|
+
}
|
|
27245
|
+
hang$1(sctx, objects, stage, screen);
|
|
27246
|
+
paintVeil(objects, run);
|
|
27247
|
+
const loading = paintLoader(sctx, objects, run, now);
|
|
27248
|
+
if (moved || loading) sctx.deps.demand.request();
|
|
27249
|
+
}
|
|
27250
|
+
/**
|
|
27251
|
+
* Forgets the objects of a lost application: they go with its stage, and so does a layer lifted above the veil. The
|
|
27252
|
+
* running look stays: the step makes its objects again on the restored device.
|
|
27253
|
+
*
|
|
27254
|
+
* @param state - The sync branch of the plugin state.
|
|
27255
|
+
*/
|
|
27256
|
+
function forgetLeave$1(state) {
|
|
27257
|
+
delete state.leave.objects;
|
|
27258
|
+
if (state.leave.run !== void 0) delete state.leave.run.lifted;
|
|
27259
|
+
}
|
|
27260
|
+
/**
|
|
27261
|
+
* Drops the look and frees the engine's objects when the plugin stops. A lifted layer is the layer table's and the
|
|
27262
|
+
* spinner's texture is the game's: neither is freed with them.
|
|
27263
|
+
*
|
|
27264
|
+
* @param state - The sync branch of the plugin state.
|
|
27265
|
+
*/
|
|
27266
|
+
function stopLeave(state) {
|
|
27267
|
+
const objects = state.leave.objects;
|
|
27268
|
+
delete state.leave.run;
|
|
27269
|
+
delete state.leave.objects;
|
|
27270
|
+
if (objects === void 0) return;
|
|
27271
|
+
objects.held.removeChildren();
|
|
27272
|
+
objects.top.destroy({ children: true });
|
|
27273
|
+
}
|
|
27274
|
+
//#endregion
|
|
26029
27275
|
//#region src/plugins/renderer/sync/overlay-range.ts
|
|
26030
27276
|
/** A range retaken for a mark in this many frames in a row is drawn live instead (O8.4; spec 23's `CHURN_LIMIT`). */
|
|
26031
27277
|
const RETAKE_STREAK = 3;
|
|
@@ -26718,349 +27964,6 @@ function stopOverlays(state) {
|
|
|
26718
27964
|
overlay.range = void 0;
|
|
26719
27965
|
}
|
|
26720
27966
|
//#endregion
|
|
26721
|
-
//#region src/plugins/renderer/sync/shape-path.ts
|
|
26722
|
-
/**
|
|
26723
|
-
* @file renderer/sync — the path of a `Shape`: a rectangle, a rounded rectangle or a triangle,
|
|
26724
|
-
* its fill, and its stroke, solid or dashed, or an arc, a stroke alone. A dashed stroke walks the
|
|
26725
|
-
* outline (straight edges, and rounded corners sampled as arcs) and strokes one open line per dash.
|
|
26726
|
-
* An arc runs on the circle inscribed in its box, from 12 o'clock clockwise.
|
|
26727
|
-
*/
|
|
26728
|
-
/** Chords per rounded corner of a dashed outline: under 0.3 u off the arc at a 48 u radius. */
|
|
26729
|
-
const CORNER_SAMPLES = 8;
|
|
26730
|
-
/** The gap between two dashes, as a fraction of the dash length. */
|
|
26731
|
-
const GAP_RATIO = .5;
|
|
26732
|
-
/** Segments of a full ring: a ring of 28 u across strays under 0.03 u off its circle. */
|
|
26733
|
-
const RING_SEGMENTS = 48;
|
|
26734
|
-
/** The fewest segments of an arc that draws: a sliver still bends. */
|
|
26735
|
-
const ARC_MIN_SEGMENTS = 2;
|
|
26736
|
-
/** 12 o'clock, where an arc starts: Pixi's angles run clockwise from 3 o'clock. */
|
|
26737
|
-
const TWELVE = -Math.PI / 2;
|
|
26738
|
-
/**
|
|
26739
|
-
* The corner radius the rectangle is drawn with: Pixi clamps it to half the shorter side.
|
|
26740
|
-
*
|
|
26741
|
-
* @param value - The shape value.
|
|
26742
|
-
* @returns The radius in reference units, 0 for square corners.
|
|
26743
|
-
* @example
|
|
26744
|
-
* ```ts
|
|
26745
|
-
* radiusOf({ ...Shape.defaults, w: 100, h: 20, radius: 40 }); // 10
|
|
26746
|
-
* ```
|
|
26747
|
-
*/
|
|
26748
|
-
function radiusOf(value) {
|
|
26749
|
-
return Math.max(0, Math.min(value.radius, value.w / 2, value.h / 2));
|
|
26750
|
-
}
|
|
26751
|
-
/**
|
|
26752
|
-
* The closed outline of a shape as points, clockwise from the top left (from the end of the top
|
|
26753
|
-
* left corner on a rounded rectangle); the last point repeats the first.
|
|
26754
|
-
*
|
|
26755
|
-
* @param value - The shape value.
|
|
26756
|
-
* @returns The points of the outline.
|
|
26757
|
-
* @example
|
|
26758
|
-
* ```ts
|
|
26759
|
-
* outlineOf({ ...Shape.defaults, kind: "triangle", w: 60, h: 40 });
|
|
26760
|
-
* // [{ x: 0, y: 0 }, { x: 60, y: 20 }, { x: 0, y: 40 }, { x: 0, y: 0 }]
|
|
26761
|
-
* ```
|
|
26762
|
-
*/
|
|
26763
|
-
function outlineOf(value) {
|
|
26764
|
-
const { w, h } = value;
|
|
26765
|
-
if (value.kind === "triangle") return [
|
|
26766
|
-
{
|
|
26767
|
-
x: 0,
|
|
26768
|
-
y: 0
|
|
26769
|
-
},
|
|
26770
|
-
{
|
|
26771
|
-
x: w,
|
|
26772
|
-
y: h / 2
|
|
26773
|
-
},
|
|
26774
|
-
{
|
|
26775
|
-
x: 0,
|
|
26776
|
-
y: h
|
|
26777
|
-
},
|
|
26778
|
-
{
|
|
26779
|
-
x: 0,
|
|
26780
|
-
y: 0
|
|
26781
|
-
}
|
|
26782
|
-
];
|
|
26783
|
-
const radius = radiusOf(value);
|
|
26784
|
-
if (radius === 0) return [
|
|
26785
|
-
{
|
|
26786
|
-
x: 0,
|
|
26787
|
-
y: 0
|
|
26788
|
-
},
|
|
26789
|
-
{
|
|
26790
|
-
x: w,
|
|
26791
|
-
y: 0
|
|
26792
|
-
},
|
|
26793
|
-
{
|
|
26794
|
-
x: w,
|
|
26795
|
-
y: h
|
|
26796
|
-
},
|
|
26797
|
-
{
|
|
26798
|
-
x: 0,
|
|
26799
|
-
y: h
|
|
26800
|
-
},
|
|
26801
|
-
{
|
|
26802
|
-
x: 0,
|
|
26803
|
-
y: 0
|
|
26804
|
-
}
|
|
26805
|
-
];
|
|
26806
|
-
const corners = [
|
|
26807
|
-
{
|
|
26808
|
-
edge: {
|
|
26809
|
-
x: w - radius,
|
|
26810
|
-
y: 0
|
|
26811
|
-
},
|
|
26812
|
-
center: {
|
|
26813
|
-
x: w - radius,
|
|
26814
|
-
y: radius
|
|
26815
|
-
}
|
|
26816
|
-
},
|
|
26817
|
-
{
|
|
26818
|
-
edge: {
|
|
26819
|
-
x: w,
|
|
26820
|
-
y: h - radius
|
|
26821
|
-
},
|
|
26822
|
-
center: {
|
|
26823
|
-
x: w - radius,
|
|
26824
|
-
y: h - radius
|
|
26825
|
-
}
|
|
26826
|
-
},
|
|
26827
|
-
{
|
|
26828
|
-
edge: {
|
|
26829
|
-
x: radius,
|
|
26830
|
-
y: h
|
|
26831
|
-
},
|
|
26832
|
-
center: {
|
|
26833
|
-
x: radius,
|
|
26834
|
-
y: h - radius
|
|
26835
|
-
}
|
|
26836
|
-
},
|
|
26837
|
-
{
|
|
26838
|
-
edge: {
|
|
26839
|
-
x: 0,
|
|
26840
|
-
y: radius
|
|
26841
|
-
},
|
|
26842
|
-
center: {
|
|
26843
|
-
x: radius,
|
|
26844
|
-
y: radius
|
|
26845
|
-
}
|
|
26846
|
-
}
|
|
26847
|
-
];
|
|
26848
|
-
const points = [{
|
|
26849
|
-
x: radius,
|
|
26850
|
-
y: 0
|
|
26851
|
-
}];
|
|
26852
|
-
for (const [index, { edge, center }] of corners.entries()) {
|
|
26853
|
-
const from = -Math.PI / 2 + index * Math.PI / 2;
|
|
26854
|
-
points.push(edge);
|
|
26855
|
-
for (let sample = 1; sample <= CORNER_SAMPLES; sample += 1) {
|
|
26856
|
-
const angle = from + sample * Math.PI / 2 / CORNER_SAMPLES;
|
|
26857
|
-
points.push({
|
|
26858
|
-
x: center.x + radius * Math.cos(angle),
|
|
26859
|
-
y: center.y + radius * Math.sin(angle)
|
|
26860
|
-
});
|
|
26861
|
-
}
|
|
26862
|
-
}
|
|
26863
|
-
return points;
|
|
26864
|
-
}
|
|
26865
|
-
/**
|
|
26866
|
-
* Adds a point to a line unless the line already ends there.
|
|
26867
|
-
*
|
|
26868
|
-
* @param line - The line.
|
|
26869
|
-
* @param point - The point.
|
|
26870
|
-
*/
|
|
26871
|
-
function extend(line, point) {
|
|
26872
|
-
const last = line.at(-1);
|
|
26873
|
-
if (last?.x !== point.x || last.y !== point.y) line.push(point);
|
|
26874
|
-
}
|
|
26875
|
-
/**
|
|
26876
|
-
* Cuts a polyline into dashes of `dash` length with gaps of half a dash, starting with a dash at
|
|
26877
|
-
* its first point. A dash bends round the corners it crosses; the last one may be shorter.
|
|
26878
|
-
*
|
|
26879
|
-
* @param outline - The points of the polyline.
|
|
26880
|
-
* @param dash - The dash length, above 0.
|
|
26881
|
-
* @returns One open line per dash.
|
|
26882
|
-
* @example
|
|
26883
|
-
* ```ts
|
|
26884
|
-
* dashesOf([{ x: 0, y: 0 }, { x: 40, y: 0 }], 10);
|
|
26885
|
-
* // [[{ x: 0, y: 0 }, { x: 10, y: 0 }], [{ x: 15, y: 0 }, { x: 25, y: 0 }], [{ x: 30, y: 0 }, { x: 40, y: 0 }]]
|
|
26886
|
-
* ```
|
|
26887
|
-
*/
|
|
26888
|
-
function dashesOf(outline, dash) {
|
|
26889
|
-
const first = outline[0];
|
|
26890
|
-
if (first === void 0) return [];
|
|
26891
|
-
const dashes = [];
|
|
26892
|
-
let line = [first];
|
|
26893
|
-
let left = dash;
|
|
26894
|
-
for (let index = 1; index < outline.length; index += 1) {
|
|
26895
|
-
let from = outline[index - 1] ?? first;
|
|
26896
|
-
const to = outline[index] ?? first;
|
|
26897
|
-
let length = Math.hypot(to.x - from.x, to.y - from.y);
|
|
26898
|
-
while (length > left) {
|
|
26899
|
-
from = {
|
|
26900
|
-
x: from.x + (to.x - from.x) * left / length,
|
|
26901
|
-
y: from.y + (to.y - from.y) * left / length
|
|
26902
|
-
};
|
|
26903
|
-
length -= left;
|
|
26904
|
-
if (line === void 0) {
|
|
26905
|
-
line = [from];
|
|
26906
|
-
left = dash;
|
|
26907
|
-
} else {
|
|
26908
|
-
extend(line, from);
|
|
26909
|
-
dashes.push(line);
|
|
26910
|
-
line = void 0;
|
|
26911
|
-
left = dash * GAP_RATIO;
|
|
26912
|
-
}
|
|
26913
|
-
}
|
|
26914
|
-
left -= length;
|
|
26915
|
-
if (line !== void 0) extend(line, to);
|
|
26916
|
-
}
|
|
26917
|
-
if (line !== void 0 && line.length > 1) dashes.push(line);
|
|
26918
|
-
return dashes;
|
|
26919
|
-
}
|
|
26920
|
-
/**
|
|
26921
|
-
* Draws the path of a shape and fills it in a colour at the alpha given. At alpha 0 the path stays unfilled, so only
|
|
26922
|
-
* a stroke shows. A clip mask is drawn here too, never dashed; the path of an arc here is its box, which is what it
|
|
26923
|
-
* clips its children to. A fill-only shape is drawn white and coloured by the object's tint, so a colour change draws
|
|
26924
|
-
* no path (spec 26 R2.1).
|
|
26925
|
-
*
|
|
26926
|
-
* @param object - The graphics to draw on.
|
|
26927
|
-
* @param value - The shape value.
|
|
26928
|
-
* @param fillAlpha - The alpha of the fill: the value's own for the shape, 1 for a clip mask.
|
|
26929
|
-
* @param color - The colour of the fill: the value's own by default, white for a tinted shape.
|
|
26930
|
-
*/
|
|
26931
|
-
function drawShapePath(object, value, fillAlpha, color = value.fill) {
|
|
26932
|
-
object.clear();
|
|
26933
|
-
if (value.kind === "triangle") object.moveTo(0, 0).lineTo(value.w, value.h / 2).lineTo(0, value.h).closePath();
|
|
26934
|
-
else if (value.kind === "rect" && value.radius > 0) object.roundRect(0, 0, value.w, value.h, value.radius);
|
|
26935
|
-
else object.rect(0, 0, value.w, value.h);
|
|
26936
|
-
if (fillAlpha <= 0) return;
|
|
26937
|
-
object.fill(fillAlpha < 1 ? {
|
|
26938
|
-
color,
|
|
26939
|
-
alpha: fillAlpha
|
|
26940
|
-
} : { color });
|
|
26941
|
-
}
|
|
26942
|
-
/**
|
|
26943
|
-
* Strokes the outline drawn by `drawShapePath`: solid over the same path, or, with a `dash`, as
|
|
26944
|
-
* one open line per dash on a new path. Nothing without a stroke width.
|
|
26945
|
-
*
|
|
26946
|
-
* @param object - The graphics the shape was drawn on.
|
|
26947
|
-
* @param value - The shape value.
|
|
26948
|
-
*/
|
|
26949
|
-
function strokeShape(object, value) {
|
|
26950
|
-
if (value.strokeWidth <= 0) return;
|
|
26951
|
-
const style = {
|
|
26952
|
-
color: value.stroke,
|
|
26953
|
-
width: value.strokeWidth
|
|
26954
|
-
};
|
|
26955
|
-
if (value.dash <= 0) {
|
|
26956
|
-
object.stroke(style);
|
|
26957
|
-
return;
|
|
26958
|
-
}
|
|
26959
|
-
object.beginPath();
|
|
26960
|
-
for (const [start, ...rest] of dashesOf(outlineOf(value), value.dash)) {
|
|
26961
|
-
if (start === void 0) continue;
|
|
26962
|
-
object.moveTo(start.x, start.y);
|
|
26963
|
-
for (const point of rest) object.lineTo(point.x, point.y);
|
|
26964
|
-
}
|
|
26965
|
-
object.stroke(style);
|
|
26966
|
-
}
|
|
26967
|
-
/**
|
|
26968
|
-
* The share of the circle an arc sweeps, clamped to 0..1: anything not above 0, `NaN` too, is 0; a whole ring when
|
|
26969
|
-
* the value leaves it out.
|
|
26970
|
-
*
|
|
26971
|
-
* @param value - The shape value.
|
|
26972
|
-
* @returns The share.
|
|
26973
|
-
* @example
|
|
26974
|
-
* ```ts
|
|
26975
|
-
* sweepOf({ ...Shape.defaults, kind: "arc", sweep: 1.4 }); // 1
|
|
26976
|
-
* ```
|
|
26977
|
-
*/
|
|
26978
|
-
function sweepOf(value) {
|
|
26979
|
-
const sweep = value.sweep ?? arcDefaults.sweep;
|
|
26980
|
-
if (Number.isNaN(sweep) || sweep <= 0) return 0;
|
|
26981
|
-
return Math.min(sweep, 1);
|
|
26982
|
-
}
|
|
26983
|
-
/**
|
|
26984
|
-
* The radius of the circle an arc runs on: inscribed in the shorter side of its box, inside its stroke.
|
|
26985
|
-
*
|
|
26986
|
-
* @param value - The shape value.
|
|
26987
|
-
* @returns The radius in reference units; 0 or below when the stroke leaves no circle.
|
|
26988
|
-
* @example
|
|
26989
|
-
* ```ts
|
|
26990
|
-
* arcRadiusOf({ ...Shape.defaults, kind: "arc", w: 28, h: 40, strokeWidth: 2 }); // 13
|
|
26991
|
-
* ```
|
|
26992
|
-
*/
|
|
26993
|
-
function arcRadiusOf(value) {
|
|
26994
|
-
return (Math.min(value.w, value.h) - value.strokeWidth) / 2;
|
|
26995
|
-
}
|
|
26996
|
-
/**
|
|
26997
|
-
* Tells whether an arc draws nothing: no stroke width, no sweep, or no circle inside its stroke.
|
|
26998
|
-
*
|
|
26999
|
-
* @param value - The shape value, an arc.
|
|
27000
|
-
* @returns True for an arc whose `Graphics` would hold no instruction.
|
|
27001
|
-
* @example
|
|
27002
|
-
* ```ts
|
|
27003
|
-
* isEmptyArc({ ...Shape.defaults, kind: "arc", w: 28, h: 28, strokeWidth: 2, sweep: 0 }); // true
|
|
27004
|
-
* ```
|
|
27005
|
-
*/
|
|
27006
|
-
function isEmptyArc(value) {
|
|
27007
|
-
return value.strokeWidth <= 0 || sweepOf(value) <= 0 || arcRadiusOf(value) <= 0;
|
|
27008
|
-
}
|
|
27009
|
-
/**
|
|
27010
|
-
* Strokes an arc: from 12 o'clock clockwise over its share of the circle, in segments as many as its angle needs (48
|
|
27011
|
-
* for a ring, at least 2), ended by its `cap`. A full ring is closed and has no ends. Nothing for an empty arc. Drawn
|
|
27012
|
-
* only when the shape changed: never rebuilt per frame.
|
|
27013
|
-
*
|
|
27014
|
-
* @param object - The graphics, cleared.
|
|
27015
|
-
* @param value - The shape value, an arc.
|
|
27016
|
-
*/
|
|
27017
|
-
function strokeArc(object, value) {
|
|
27018
|
-
if (isEmptyArc(value)) return;
|
|
27019
|
-
const sweep = sweepOf(value);
|
|
27020
|
-
const radius = arcRadiusOf(value);
|
|
27021
|
-
const ring = sweep >= 1;
|
|
27022
|
-
const segments = Math.max(ARC_MIN_SEGMENTS, Math.ceil(sweep * RING_SEGMENTS));
|
|
27023
|
-
const step = sweep * 2 * Math.PI / segments;
|
|
27024
|
-
const centreX = value.w / 2;
|
|
27025
|
-
const centreY = value.h / 2;
|
|
27026
|
-
const last = ring ? segments - 1 : segments;
|
|
27027
|
-
object.moveTo(centreX, centreY - radius);
|
|
27028
|
-
for (let index = 1; index <= last; index += 1) {
|
|
27029
|
-
const angle = TWELVE + index * step;
|
|
27030
|
-
object.lineTo(centreX + radius * Math.cos(angle), centreY + radius * Math.sin(angle));
|
|
27031
|
-
}
|
|
27032
|
-
if (ring) {
|
|
27033
|
-
object.closePath();
|
|
27034
|
-
object.stroke({
|
|
27035
|
-
color: value.stroke,
|
|
27036
|
-
width: value.strokeWidth
|
|
27037
|
-
});
|
|
27038
|
-
return;
|
|
27039
|
-
}
|
|
27040
|
-
object.stroke({
|
|
27041
|
-
color: value.stroke,
|
|
27042
|
-
width: value.strokeWidth,
|
|
27043
|
-
cap: value.cap ?? arcDefaults.cap
|
|
27044
|
-
});
|
|
27045
|
-
}
|
|
27046
|
-
/**
|
|
27047
|
-
* Paints a shape on its graphics: an arc as its stroke alone, every other kind as its path, its fill in a colour at its
|
|
27048
|
-
* own fill alpha, and its stroke. The one call that draws a shape's visual.
|
|
27049
|
-
*
|
|
27050
|
-
* @param object - The graphics.
|
|
27051
|
-
* @param value - The shape value.
|
|
27052
|
-
* @param color - The colour of the fill: white for a tinted shape, the value's own otherwise.
|
|
27053
|
-
*/
|
|
27054
|
-
function paintShape(object, value, color) {
|
|
27055
|
-
if (value.kind === "arc") {
|
|
27056
|
-
object.clear();
|
|
27057
|
-
strokeArc(object, value);
|
|
27058
|
-
return;
|
|
27059
|
-
}
|
|
27060
|
-
drawShapePath(object, value, value.fillAlpha, color);
|
|
27061
|
-
strokeShape(object, value);
|
|
27062
|
-
}
|
|
27063
|
-
//#endregion
|
|
27064
27967
|
//#region src/plugins/renderer/sync/sizing.ts
|
|
27065
27968
|
/**
|
|
27066
27969
|
* Creates a layout to write into, every field present.
|
|
@@ -27196,299 +28099,6 @@ function layoutSpriteInto(out, texture, value) {
|
|
|
27196
28099
|
return out;
|
|
27197
28100
|
}
|
|
27198
28101
|
//#endregion
|
|
27199
|
-
//#region src/plugins/renderer/sync/textures.ts
|
|
27200
|
-
/** The colour a missing texture is drawn in. */
|
|
27201
|
-
const PLACEHOLDER_TINT = 16711935;
|
|
27202
|
-
/**
|
|
27203
|
-
* Creates an empty set. It lives in its own non-exported function because lint rule L5 refuses a
|
|
27204
|
-
* collection built inside an exported declaration.
|
|
27205
|
-
*
|
|
27206
|
-
* @returns An empty set.
|
|
27207
|
-
*/
|
|
27208
|
-
function emptySet$5() {
|
|
27209
|
-
return /* @__PURE__ */ new Set();
|
|
27210
|
-
}
|
|
27211
|
-
/**
|
|
27212
|
-
* Asks the providers from the newest to the oldest. The first answer wins, so a bundle loaded
|
|
27213
|
-
* later shadows an older one.
|
|
27214
|
-
*
|
|
27215
|
-
* @param sctx - Domain context of the sync module.
|
|
27216
|
-
* @param key - The asset key.
|
|
27217
|
-
* @returns The texture, or `undefined` when nothing answered.
|
|
27218
|
-
*/
|
|
27219
|
-
function resolveTexture(sctx, key) {
|
|
27220
|
-
const providers = sctx.ctx.state.sync.providers;
|
|
27221
|
-
for (let index = providers.length - 1; index >= 0; index -= 1) {
|
|
27222
|
-
const texture = providers[index]?.(key);
|
|
27223
|
-
if (texture !== void 0) return texture;
|
|
27224
|
-
}
|
|
27225
|
-
}
|
|
27226
|
-
/**
|
|
27227
|
-
* The 1x1 white texture the placeholder is drawn with.
|
|
27228
|
-
*
|
|
27229
|
-
* @param sctx - Domain context of the sync module.
|
|
27230
|
-
* @returns The texture, or `undefined` while inert.
|
|
27231
|
-
*/
|
|
27232
|
-
function placeholderTexture(sctx) {
|
|
27233
|
-
return sctx.deps.host.pixi()?.Texture.WHITE;
|
|
27234
|
-
}
|
|
27235
|
-
/**
|
|
27236
|
-
* Reports a key no provider answers. One key warns once, however many views use it.
|
|
27237
|
-
*
|
|
27238
|
-
* @param sctx - Domain context of the sync module.
|
|
27239
|
-
* @param key - The asset key.
|
|
27240
|
-
*/
|
|
27241
|
-
function warnMissing(sctx, key) {
|
|
27242
|
-
const warned = sctx.ctx.state.sync.warned;
|
|
27243
|
-
if (key === "" || warned.has(key)) return;
|
|
27244
|
-
warned.add(key);
|
|
27245
|
-
sctx.ctx.log.warn("renderer: no texture for asset key", { key });
|
|
27246
|
-
}
|
|
27247
|
-
/**
|
|
27248
|
-
* Remembers that an entity draws a key, so `invalidate` finds it again.
|
|
27249
|
-
*
|
|
27250
|
-
* @param state - The sync branch of the plugin state.
|
|
27251
|
-
* @param entity - The entity.
|
|
27252
|
-
* @param key - The asset key.
|
|
27253
|
-
*/
|
|
27254
|
-
function trackKey(state, entity, key) {
|
|
27255
|
-
const users = state.byKey.get(key) ?? emptySet$5();
|
|
27256
|
-
users.add(entity);
|
|
27257
|
-
state.byKey.set(key, users);
|
|
27258
|
-
}
|
|
27259
|
-
/**
|
|
27260
|
-
* Forgets that an entity draws a key.
|
|
27261
|
-
*
|
|
27262
|
-
* @param state - The sync branch of the plugin state.
|
|
27263
|
-
* @param entity - The entity.
|
|
27264
|
-
* @param key - The asset key.
|
|
27265
|
-
*/
|
|
27266
|
-
function untrackKey(state, entity, key) {
|
|
27267
|
-
const users = state.byKey.get(key);
|
|
27268
|
-
if (users === void 0) return;
|
|
27269
|
-
users.delete(entity);
|
|
27270
|
-
if (users.size === 0) state.byKey.delete(key);
|
|
27271
|
-
}
|
|
27272
|
-
/**
|
|
27273
|
-
* The Pixi module of a renderer that draws. A texture made while it does not would be lost with
|
|
27274
|
-
* the device, so the member that asked throws.
|
|
27275
|
-
*
|
|
27276
|
-
* @param sctx - Domain context of the sync module.
|
|
27277
|
-
* @param member - The `textures` member that asked, for the message.
|
|
27278
|
-
* @returns The Pixi module.
|
|
27279
|
-
* @throws {Error} When the renderer does not draw.
|
|
27280
|
-
*/
|
|
27281
|
-
function drawingPixi(sctx, member) {
|
|
27282
|
-
const pixi = sctx.deps.host.pixi();
|
|
27283
|
-
if (pixi === void 0 || !sctx.deps.host.ready()) throw new Error(`[game] renderer.sync.textures.${member} needs a ready renderer.\n Check app.renderer.host.ready() first.`);
|
|
27284
|
-
return pixi;
|
|
27285
|
-
}
|
|
27286
|
-
/**
|
|
27287
|
-
* The default borders of a texture, from the four numbers `nine` gives.
|
|
27288
|
-
*
|
|
27289
|
-
* @param nine - Left, top, right and bottom in pixels.
|
|
27290
|
-
* @returns The borders, as Pixi reads them.
|
|
27291
|
-
* @example
|
|
27292
|
-
* ```ts
|
|
27293
|
-
* defaultBordersOf([24, 20, 16, 12]); // { left: 24, top: 20, right: 16, bottom: 12 }
|
|
27294
|
-
* ```
|
|
27295
|
-
*/
|
|
27296
|
-
function defaultBordersOf(nine) {
|
|
27297
|
-
return {
|
|
27298
|
-
left: nine[0],
|
|
27299
|
-
top: nine[1],
|
|
27300
|
-
right: nine[2],
|
|
27301
|
-
bottom: nine[3]
|
|
27302
|
-
};
|
|
27303
|
-
}
|
|
27304
|
-
/**
|
|
27305
|
-
* Makes a Pixi texture out of a decoded image, on behalf of `assets`.
|
|
27306
|
-
*
|
|
27307
|
-
* @param sctx - Domain context of the sync module.
|
|
27308
|
-
* @param image - The decoded image.
|
|
27309
|
-
* @param options - Nine-slice borders in pixels, and whether Pixi generates the mip chain.
|
|
27310
|
-
* @returns The new texture.
|
|
27311
|
-
* @throws {Error} When the renderer does not draw.
|
|
27312
|
-
*/
|
|
27313
|
-
function createTexture(sctx, image, options) {
|
|
27314
|
-
const pixi = drawingPixi(sctx, "create");
|
|
27315
|
-
const base = options?.mips === true ? pixi.Texture.from({
|
|
27316
|
-
resource: image,
|
|
27317
|
-
autoGenerateMipmaps: true
|
|
27318
|
-
}) : pixi.Texture.from(image);
|
|
27319
|
-
const nine = options?.nine;
|
|
27320
|
-
if (nine === void 0) return base;
|
|
27321
|
-
const bordered = new pixi.Texture({
|
|
27322
|
-
source: base.source,
|
|
27323
|
-
defaultBorders: defaultBordersOf(nine)
|
|
27324
|
-
});
|
|
27325
|
-
base.destroy(false);
|
|
27326
|
-
return bordered;
|
|
27327
|
-
}
|
|
27328
|
-
/**
|
|
27329
|
-
* Tells whether a frame lies inside a page of the given size. A negative number, or one that is
|
|
27330
|
-
* not a number, lies outside.
|
|
27331
|
-
*
|
|
27332
|
-
* @param frame - The frame in page pixels.
|
|
27333
|
-
* @param page - The size of the page.
|
|
27334
|
-
* @param page.width - Width of the page in pixels.
|
|
27335
|
-
* @param page.height - Height of the page in pixels.
|
|
27336
|
-
* @returns True when the whole frame is on the page.
|
|
27337
|
-
* @example
|
|
27338
|
-
* ```ts
|
|
27339
|
-
* frameFits({ x: 256, y: 384, width: 256, height: 128 }, { width: 512, height: 512 }); // true
|
|
27340
|
-
* ```
|
|
27341
|
-
*/
|
|
27342
|
-
function frameFits(frame, page) {
|
|
27343
|
-
return frame.x >= 0 && frame.y >= 0 && frame.width >= 0 && frame.height >= 0 && frame.x + frame.width <= page.width && frame.y + frame.height <= page.height;
|
|
27344
|
-
}
|
|
27345
|
-
/**
|
|
27346
|
-
* Cuts a texture out of an atlas page, on behalf of `assets`. The slice shares the page's source,
|
|
27347
|
-
* its frame is offset by the page's own frame, and it is marked as a slice so `destroyTexture`
|
|
27348
|
-
* frees only the wrapper.
|
|
27349
|
-
*
|
|
27350
|
-
* @param sctx - Domain context of the sync module.
|
|
27351
|
-
* @param page - The page texture.
|
|
27352
|
-
* @param frame - The frame in page pixels.
|
|
27353
|
-
* @param options - Nine-slice borders, in pixels.
|
|
27354
|
-
* @returns The slice.
|
|
27355
|
-
* @throws {Error} When the renderer does not draw, and when the frame does not fit in the page.
|
|
27356
|
-
*/
|
|
27357
|
-
function sliceTexture(sctx, page, frame, options) {
|
|
27358
|
-
const pixi = drawingPixi(sctx, "slice");
|
|
27359
|
-
if (!frameFits(frame, page)) throw new Error(`[game] renderer.sync.textures.slice: frame ${frame.x},${frame.y} ${frame.width}x${frame.height} is outside page ${page.width}x${page.height}.\n Run "bun run assets:pack".`);
|
|
27360
|
-
const nine = options?.nine;
|
|
27361
|
-
const slice = new pixi.Texture({
|
|
27362
|
-
source: page.source,
|
|
27363
|
-
frame: new pixi.Rectangle(page.frame.x + frame.x, page.frame.y + frame.y, frame.width, frame.height),
|
|
27364
|
-
...nine === void 0 ? {} : { defaultBorders: defaultBordersOf(nine) }
|
|
27365
|
-
});
|
|
27366
|
-
sctx.ctx.state.sync.slices.add(slice);
|
|
27367
|
-
return slice;
|
|
27368
|
-
}
|
|
27369
|
-
/**
|
|
27370
|
-
* The cache key of one crop: the asset key and the box it covers.
|
|
27371
|
-
*
|
|
27372
|
-
* @param key - The asset key.
|
|
27373
|
-
* @param width - Width of the box.
|
|
27374
|
-
* @param height - Height of the box.
|
|
27375
|
-
* @returns The key of the crop.
|
|
27376
|
-
* @example
|
|
27377
|
-
* ```ts
|
|
27378
|
-
* frameKeyOf("board.bg", 1080, 1920); // "board.bg@1080x1920"
|
|
27379
|
-
* ```
|
|
27380
|
-
*/
|
|
27381
|
-
function frameKeyOf(key, width, height) {
|
|
27382
|
-
return `${key}@${width}x${height}`;
|
|
27383
|
-
}
|
|
27384
|
-
/**
|
|
27385
|
-
* The texture a `"cover"` sprite draws: the part of `base` inside `crop`, sharing its source. One
|
|
27386
|
-
* crop per asset key and box, shared by every entity that shows it; the entity is added to its
|
|
27387
|
-
* users. A key that answers a new base has been invalidated, so every view of it resolves again
|
|
27388
|
-
* in this pass and joins the new crop; the old crop goes at once.
|
|
27389
|
-
*
|
|
27390
|
-
* @param sctx - Domain context of the sync module.
|
|
27391
|
-
* @param entity - The entity that shows the crop.
|
|
27392
|
-
* @param key - The asset key the base was resolved for.
|
|
27393
|
-
* @param base - The texture the key resolves to.
|
|
27394
|
-
* @param box - The box the sprite covers, which names the crop.
|
|
27395
|
-
* @param box.width - Width of the box.
|
|
27396
|
-
* @param box.height - Height of the box.
|
|
27397
|
-
* @param crop - The part of the base to show, in texture pixels.
|
|
27398
|
-
* @returns The crop and its `frames` key, or the base and `""` while inert.
|
|
27399
|
-
*/
|
|
27400
|
-
function coverTexture(sctx, entity, key, base, box, crop) {
|
|
27401
|
-
const frames = sctx.ctx.state.sync.frames;
|
|
27402
|
-
const frameKey = frameKeyOf(key, box.width, box.height);
|
|
27403
|
-
const cached = frames.get(frameKey);
|
|
27404
|
-
if (cached?.base === base) {
|
|
27405
|
-
cached.users.add(entity);
|
|
27406
|
-
return {
|
|
27407
|
-
texture: cached.texture,
|
|
27408
|
-
frameKey
|
|
27409
|
-
};
|
|
27410
|
-
}
|
|
27411
|
-
const pixi = sctx.deps.host.pixi();
|
|
27412
|
-
if (pixi === void 0) return {
|
|
27413
|
-
texture: base,
|
|
27414
|
-
frameKey: ""
|
|
27415
|
-
};
|
|
27416
|
-
if (cached !== void 0) cached.texture.destroy(false);
|
|
27417
|
-
const texture = new pixi.Texture({
|
|
27418
|
-
source: base.source,
|
|
27419
|
-
frame: new pixi.Rectangle(base.frame.x + crop.x, base.frame.y + crop.y, crop.width, crop.height)
|
|
27420
|
-
});
|
|
27421
|
-
const users = emptySet$5();
|
|
27422
|
-
users.add(entity);
|
|
27423
|
-
frames.set(frameKey, {
|
|
27424
|
-
base,
|
|
27425
|
-
texture,
|
|
27426
|
-
users
|
|
27427
|
-
});
|
|
27428
|
-
return {
|
|
27429
|
-
texture,
|
|
27430
|
-
frameKey
|
|
27431
|
-
};
|
|
27432
|
-
}
|
|
27433
|
-
/**
|
|
27434
|
-
* Lets an entity go of the crop it showed. The last user frees the crop, keeping the source it
|
|
27435
|
-
* shares with its base.
|
|
27436
|
-
*
|
|
27437
|
-
* @param state - The sync branch of the plugin state.
|
|
27438
|
-
* @param entity - The entity that no longer shows it.
|
|
27439
|
-
* @param frameKey - The `frames` key of the crop; `""` for none.
|
|
27440
|
-
*/
|
|
27441
|
-
function releaseCover(state, entity, frameKey) {
|
|
27442
|
-
const frame = state.frames.get(frameKey);
|
|
27443
|
-
if (frame === void 0) return;
|
|
27444
|
-
frame.users.delete(entity);
|
|
27445
|
-
if (frame.users.size > 0) return;
|
|
27446
|
-
frame.texture.destroy(false);
|
|
27447
|
-
state.frames.delete(frameKey);
|
|
27448
|
-
}
|
|
27449
|
-
/**
|
|
27450
|
-
* Frees the crops cut from one texture, keeping the source they share with it.
|
|
27451
|
-
*
|
|
27452
|
-
* @param state - The sync branch of the plugin state.
|
|
27453
|
-
* @param base - The texture that goes.
|
|
27454
|
-
*/
|
|
27455
|
-
function releaseFrames$1(state, base) {
|
|
27456
|
-
for (const [frameKey, frame] of state.frames) {
|
|
27457
|
-
if (frame.base !== base) continue;
|
|
27458
|
-
frame.texture.destroy(false);
|
|
27459
|
-
state.frames.delete(frameKey);
|
|
27460
|
-
}
|
|
27461
|
-
}
|
|
27462
|
-
/**
|
|
27463
|
-
* Frees every crop. Works on the state alone, because `onStop` has no context.
|
|
27464
|
-
*
|
|
27465
|
-
* @param state - The sync branch of the plugin state.
|
|
27466
|
-
*/
|
|
27467
|
-
function clearFrames(state) {
|
|
27468
|
-
for (const frame of state.frames.values()) frame.texture.destroy(false);
|
|
27469
|
-
state.frames.clear();
|
|
27470
|
-
}
|
|
27471
|
-
/**
|
|
27472
|
-
* Frees a texture, its source and the crops cut from it. A slice keeps the source: it belongs to
|
|
27473
|
-
* the page, which frees it. A source that goes lets go of its `change` listeners first, the ones
|
|
27474
|
-
* of its style too, so no bind group Pixi still keeps is told. A texture destroyed twice is a
|
|
27475
|
-
* no-op.
|
|
27476
|
-
*
|
|
27477
|
-
* @param state - The sync branch of the plugin state.
|
|
27478
|
-
* @param texture - The texture to free.
|
|
27479
|
-
*/
|
|
27480
|
-
function destroyTexture(state, texture) {
|
|
27481
|
-
releaseFrames$1(state, texture);
|
|
27482
|
-
if (texture.destroyed) return;
|
|
27483
|
-
if (state.slices.has(texture)) {
|
|
27484
|
-
texture.destroy(false);
|
|
27485
|
-
return;
|
|
27486
|
-
}
|
|
27487
|
-
texture.source.style.removeAllListeners("change");
|
|
27488
|
-
texture.source.removeAllListeners("change");
|
|
27489
|
-
texture.destroy(true);
|
|
27490
|
-
}
|
|
27491
|
-
//#endregion
|
|
27492
28102
|
//#region src/plugins/renderer/sync/upload.ts
|
|
27493
28103
|
/** The calls of a source nobody waits for: none. */
|
|
27494
28104
|
const NOBODY = Object.freeze([]);
|
|
@@ -28875,6 +29485,7 @@ function stopSync(state) {
|
|
|
28875
29485
|
state.redraws = 0;
|
|
28876
29486
|
state.overdraw.listeners.length = 0;
|
|
28877
29487
|
clearLayers(state);
|
|
29488
|
+
stopLeave(state);
|
|
28878
29489
|
destroyPools(state);
|
|
28879
29490
|
state.root?.destroy({ children: true });
|
|
28880
29491
|
state.root = void 0;
|
|
@@ -29044,6 +29655,7 @@ function renderFrame(ctx, modules) {
|
|
|
29044
29655
|
modules.sync.stepFilters();
|
|
29045
29656
|
modules.sync.stepOverlays();
|
|
29046
29657
|
modules.sync.drainOverlayEvents(modules.monitor);
|
|
29658
|
+
modules.sync.stepLeave();
|
|
29047
29659
|
modules.kept.collect();
|
|
29048
29660
|
const overdraw = modules.sync.overdrawing();
|
|
29049
29661
|
if (overdraw) modules.demand.request();
|
|
@@ -29847,7 +30459,7 @@ function boundsOf(sctx, entity) {
|
|
|
29847
30459
|
* @param entity - The entity.
|
|
29848
30460
|
* @returns The layer name, or `undefined` when the root has no `Layer`.
|
|
29849
30461
|
*/
|
|
29850
|
-
function layerOf(sctx, entity) {
|
|
30462
|
+
function layerOf$1(sctx, entity) {
|
|
29851
30463
|
const ecs = sctx.ctx.deps.world.ecs;
|
|
29852
30464
|
let top = entity;
|
|
29853
30465
|
for (let depth = 0; depth < MAX_DEPTH$3; depth += 1) {
|
|
@@ -30474,7 +31086,7 @@ function stepParts(sctx) {
|
|
|
30474
31086
|
const view = state.views.get(entity);
|
|
30475
31087
|
if (view === void 0) continue;
|
|
30476
31088
|
const key = ecs.get(entity, HudPart)?.key ?? "";
|
|
30477
|
-
if (hud === void 0 || layerOf(sctx, entity) !== hud) {
|
|
31089
|
+
if (hud === void 0 || layerOf$1(sctx, entity) !== hud) {
|
|
30478
31090
|
warnOutside(sctx, entity, key);
|
|
30479
31091
|
applyHud(sctx, entity, view, 1);
|
|
30480
31092
|
continue;
|
|
@@ -31519,6 +32131,7 @@ function createSyncApi(ctx, deps) {
|
|
|
31519
32131
|
destroyPools(state);
|
|
31520
32132
|
clearLayers(state);
|
|
31521
32133
|
forgetBuild(state);
|
|
32134
|
+
forgetLeave$1(state);
|
|
31522
32135
|
state.root = void 0;
|
|
31523
32136
|
state.passesVersion += 1;
|
|
31524
32137
|
};
|
|
@@ -31612,11 +32225,17 @@ function createSyncApi(ctx, deps) {
|
|
|
31612
32225
|
area: (entity, area) => setArea(sctx, entity, area),
|
|
31613
32226
|
warm: (filter) => warmFilter(sctx, filter)
|
|
31614
32227
|
},
|
|
32228
|
+
leave: {
|
|
32229
|
+
show: (look) => showLeave(sctx, look),
|
|
32230
|
+
land: () => landLeave(sctx),
|
|
32231
|
+
drop: () => dropLeave(sctx)
|
|
32232
|
+
},
|
|
31615
32233
|
renderPasses: () => renderPassesOf(state, ctx.state.kept),
|
|
31616
32234
|
root: () => state.root,
|
|
31617
32235
|
layerContainer: (name) => layerContainer(state, name),
|
|
31618
|
-
layerOf: (entity) => layerOf(sctx, entity),
|
|
32236
|
+
layerOf: (entity) => layerOf$1(sctx, entity),
|
|
31619
32237
|
stepOverlays: () => stepOverlays(sctx),
|
|
32238
|
+
stepLeave: () => stepLeave(sctx),
|
|
31620
32239
|
stepFilters: () => applySwitched(state, deps.kept),
|
|
31621
32240
|
drainOverlayEvents: (monitor) => drainOverlayEvents(sctx, monitor),
|
|
31622
32241
|
warmStep: () => warmStep(sctx),
|
|
@@ -32205,6 +32824,7 @@ function exposeSync(sync) {
|
|
|
32205
32824
|
fonts: sync.fonts,
|
|
32206
32825
|
debug: sync.debug,
|
|
32207
32826
|
filters: sync.filters,
|
|
32827
|
+
leave: sync.leave,
|
|
32208
32828
|
renderPasses: sync.renderPasses,
|
|
32209
32829
|
loopsHeld: sync.loopsHeld,
|
|
32210
32830
|
displayOf: sync.displayOf
|
|
@@ -32446,8 +33066,8 @@ function createAsideQueue() {
|
|
|
32446
33066
|
}
|
|
32447
33067
|
/**
|
|
32448
33068
|
* Creates the initial sync state: no root, no layers, no view, no orphan, no pool, no provider, no filter, no
|
|
32449
|
-
* filter area, no slice, no hidden mask, no overlay, no warm-up, no upload, no redraw counted, no unseen walk kept
|
|
32450
|
-
* no scene build.
|
|
33069
|
+
* filter area, no slice, no hidden mask, no overlay, no warm-up, no upload, no redraw counted, no unseen walk kept,
|
|
33070
|
+
* no scene build and no picture of a scene being left.
|
|
32451
33071
|
*
|
|
32452
33072
|
* @param debug - The debug switches of the config; copied, so the frozen config stays untouched.
|
|
32453
33073
|
* @returns The sync branch of the plugin state.
|
|
@@ -32512,6 +33132,7 @@ function createSyncState(debug) {
|
|
|
32512
33132
|
count: 0,
|
|
32513
33133
|
reported: false
|
|
32514
33134
|
},
|
|
33135
|
+
leave: {},
|
|
32515
33136
|
cleanups: []
|
|
32516
33137
|
};
|
|
32517
33138
|
}
|
|
@@ -35179,7 +35800,7 @@ const NO_ANIMATION_ID = "[game] A play effect carries no animation id.\n Build
|
|
|
35179
35800
|
* isRecord([1, 2]); // false
|
|
35180
35801
|
* ```
|
|
35181
35802
|
*/
|
|
35182
|
-
function isRecord$
|
|
35803
|
+
function isRecord$4(value) {
|
|
35183
35804
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
35184
35805
|
}
|
|
35185
35806
|
/**
|
|
@@ -35304,7 +35925,7 @@ function stepAnim(actx, rt, time) {
|
|
|
35304
35925
|
* @returns True when it carries an id and a build function.
|
|
35305
35926
|
*/
|
|
35306
35927
|
function isAnimation(entry) {
|
|
35307
|
-
return isRecord$
|
|
35928
|
+
return isRecord$4(entry) && typeof entry.id === "string" && typeof entry.build === "function";
|
|
35308
35929
|
}
|
|
35309
35930
|
/**
|
|
35310
35931
|
* Reads the `animations` of every feature into the registry, in feature order.
|
|
@@ -35331,7 +35952,7 @@ function registerAnimations(actx) {
|
|
|
35331
35952
|
*/
|
|
35332
35953
|
function readTarget$1(entry) {
|
|
35333
35954
|
if (typeof entry === "number") return entry;
|
|
35334
|
-
if (!isRecord$
|
|
35955
|
+
if (!isRecord$4(entry)) return void 0;
|
|
35335
35956
|
if (typeof entry.projection === "string" && typeof entry.key === "string") return {
|
|
35336
35957
|
projection: entry.projection,
|
|
35337
35958
|
key: entry.key
|
|
@@ -35347,7 +35968,7 @@ function readTarget$1(entry) {
|
|
|
35347
35968
|
*/
|
|
35348
35969
|
function readSlots(value) {
|
|
35349
35970
|
const slots = {};
|
|
35350
|
-
if (!isRecord$
|
|
35971
|
+
if (!isRecord$4(value)) return slots;
|
|
35351
35972
|
for (const [name, entry] of Object.entries(value)) {
|
|
35352
35973
|
if (Array.isArray(entry)) {
|
|
35353
35974
|
slots[name] = entry.map((item) => readTarget$1(item)).filter((target) => target !== void 0);
|
|
@@ -35365,7 +35986,7 @@ function readSlots(value) {
|
|
|
35365
35986
|
* @returns The animation id and its slots, or `undefined` for a foreign payload.
|
|
35366
35987
|
*/
|
|
35367
35988
|
function readPlayPayload(payload) {
|
|
35368
|
-
if (!isRecord$
|
|
35989
|
+
if (!isRecord$4(payload) || typeof payload.animation !== "string") return void 0;
|
|
35369
35990
|
return {
|
|
35370
35991
|
animation: payload.animation,
|
|
35371
35992
|
slots: readSlots(payload.slots)
|
|
@@ -35689,7 +36310,7 @@ function emptyManifest() {
|
|
|
35689
36310
|
* @param value - Anything that came out of JSON.
|
|
35690
36311
|
* @returns True for an object that is not an array.
|
|
35691
36312
|
*/
|
|
35692
|
-
function isRecord$
|
|
36313
|
+
function isRecord$3(value) {
|
|
35693
36314
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
35694
36315
|
}
|
|
35695
36316
|
/**
|
|
@@ -35729,7 +36350,7 @@ function stringAt(source, key) {
|
|
|
35729
36350
|
* @returns The four borders, or `undefined`.
|
|
35730
36351
|
*/
|
|
35731
36352
|
function parseNine(value) {
|
|
35732
|
-
if (!isRecord$
|
|
36353
|
+
if (!isRecord$3(value)) return void 0;
|
|
35733
36354
|
return {
|
|
35734
36355
|
left: numberAt(value, "left"),
|
|
35735
36356
|
top: numberAt(value, "top"),
|
|
@@ -35744,7 +36365,7 @@ function parseNine(value) {
|
|
|
35744
36365
|
* @returns The frame, or `undefined`.
|
|
35745
36366
|
*/
|
|
35746
36367
|
function parseAtlas(value) {
|
|
35747
|
-
if (!isRecord$
|
|
36368
|
+
if (!isRecord$3(value)) return void 0;
|
|
35748
36369
|
return {
|
|
35749
36370
|
page: stringAt(value, "page"),
|
|
35750
36371
|
x: numberAt(value, "x"),
|
|
@@ -35771,7 +36392,7 @@ function parseKind(value) {
|
|
|
35771
36392
|
*/
|
|
35772
36393
|
function parseFontPages(value) {
|
|
35773
36394
|
if (!Array.isArray(value)) return void 0;
|
|
35774
|
-
return value.filter((entry) => isRecord$
|
|
36395
|
+
return value.filter((entry) => isRecord$3(entry)).map((entry) => ({
|
|
35775
36396
|
path: stringAt(entry, "path"),
|
|
35776
36397
|
width: numberAt(entry, "width"),
|
|
35777
36398
|
height: numberAt(entry, "height"),
|
|
@@ -35786,7 +36407,7 @@ function parseFontPages(value) {
|
|
|
35786
36407
|
*/
|
|
35787
36408
|
function parseAtlasPages(value) {
|
|
35788
36409
|
if (!Array.isArray(value)) return void 0;
|
|
35789
|
-
return value.filter((entry) => isRecord$
|
|
36410
|
+
return value.filter((entry) => isRecord$3(entry)).map((entry) => ({
|
|
35790
36411
|
id: stringAt(entry, "id"),
|
|
35791
36412
|
path: stringAt(entry, "path"),
|
|
35792
36413
|
width: numberAt(entry, "width"),
|
|
@@ -35802,7 +36423,7 @@ function parseAtlasPages(value) {
|
|
|
35802
36423
|
* @returns The file, or `undefined` when the element is not an object.
|
|
35803
36424
|
*/
|
|
35804
36425
|
function parseFile(raw) {
|
|
35805
|
-
if (!isRecord$
|
|
36426
|
+
if (!isRecord$3(raw)) return void 0;
|
|
35806
36427
|
const kind = parseKind(raw.kind);
|
|
35807
36428
|
const pages = parseFontPages(raw.pages);
|
|
35808
36429
|
const nine = parseNine(raw.nine);
|
|
@@ -35873,7 +36494,7 @@ function textAt(source, key) {
|
|
|
35873
36494
|
* @throws {Error} When the entry is not an object or its tier is unknown.
|
|
35874
36495
|
*/
|
|
35875
36496
|
function parseBundle(name, raw) {
|
|
35876
|
-
if (!isRecord$
|
|
36497
|
+
if (!isRecord$3(raw)) throw new Error(`[game] assets: bundle "${name}" of the manifest is not an object.\n Rebuild the manifest with "bun run assets:keys".`);
|
|
35877
36498
|
const files = (Array.isArray(raw.files) ? raw.files : []).map((entry) => parseFile(entry)).filter((file) => file !== void 0).toSorted((left, right) => left.key.localeCompare(right.key));
|
|
35878
36499
|
const pages = parseAtlasPages(raw.pages);
|
|
35879
36500
|
const parent = textAt(raw, "parent");
|
|
@@ -35914,9 +36535,9 @@ function parseVersion(value) {
|
|
|
35914
36535
|
* ```
|
|
35915
36536
|
*/
|
|
35916
36537
|
function parseManifest(raw) {
|
|
35917
|
-
if (!isRecord$
|
|
36538
|
+
if (!isRecord$3(raw)) throw new Error("[game] assets: the manifest is not an object.\n Point \"manifest\" at a manifest.json, or pass the parsed object.");
|
|
35918
36539
|
const version = parseVersion(raw.version);
|
|
35919
|
-
const source = isRecord$
|
|
36540
|
+
const source = isRecord$3(raw.bundles) ? raw.bundles : {};
|
|
35920
36541
|
const bundles = {};
|
|
35921
36542
|
for (const name of Object.keys(source).toSorted((left, right) => left.localeCompare(right))) bundles[name] = parseBundle(name, source[name]);
|
|
35922
36543
|
return {
|
|
@@ -39101,6 +39722,468 @@ function dropBuild(build) {
|
|
|
39101
39722
|
build.resolve();
|
|
39102
39723
|
}
|
|
39103
39724
|
//#endregion
|
|
39725
|
+
//#region src/plugins/scenes/leave.ts
|
|
39726
|
+
/**
|
|
39727
|
+
* What a game gets when it writes nothing: black at 45 %, 150 ms in and out, the engine's ring once the switch took
|
|
39728
|
+
* 400 ms. The level under `config.leave`, and where a part written over `false` starts. `index.ts` writes the same
|
|
39729
|
+
* value inline as the plugin's default config; a test pins the two equal.
|
|
39730
|
+
*/
|
|
39731
|
+
const FALLBACK_LEAVE = Object.freeze({
|
|
39732
|
+
cover: Object.freeze({
|
|
39733
|
+
color: 0,
|
|
39734
|
+
alpha: .45,
|
|
39735
|
+
inMs: 150,
|
|
39736
|
+
outMs: 150
|
|
39737
|
+
}),
|
|
39738
|
+
loader: Object.freeze({ afterMs: 400 })
|
|
39739
|
+
});
|
|
39740
|
+
/**
|
|
39741
|
+
* Merges one level's veil over the one below, field by field. `false` turns it off; a veil written over `false`
|
|
39742
|
+
* starts from the engine's.
|
|
39743
|
+
*
|
|
39744
|
+
* @param below - The veil of the levels below, or `false`.
|
|
39745
|
+
* @param over - What this level writes: nothing, `false` or fields.
|
|
39746
|
+
* @returns The veil, or `false`.
|
|
39747
|
+
* @example
|
|
39748
|
+
* ```ts
|
|
39749
|
+
* coverOver(false, { alpha: 0.6 }); // { color: 0, alpha: 0.6, inMs: 150, outMs: 150 }
|
|
39750
|
+
* ```
|
|
39751
|
+
*/
|
|
39752
|
+
function coverOver(below, over) {
|
|
39753
|
+
if (over === void 0) return below;
|
|
39754
|
+
if (over === false) return false;
|
|
39755
|
+
const base = below === false ? FALLBACK_LEAVE.cover : below;
|
|
39756
|
+
return {
|
|
39757
|
+
color: over.color ?? base.color,
|
|
39758
|
+
alpha: over.alpha ?? base.alpha,
|
|
39759
|
+
inMs: over.inMs ?? base.inMs,
|
|
39760
|
+
outMs: over.outMs ?? base.outMs
|
|
39761
|
+
};
|
|
39762
|
+
}
|
|
39763
|
+
/**
|
|
39764
|
+
* The look of a loader: its projection, else its texture, else nothing (the engine's ring).
|
|
39765
|
+
*
|
|
39766
|
+
* @param loader - A level that names a look, or the level below.
|
|
39767
|
+
* @returns At most one of the two fields.
|
|
39768
|
+
* @example
|
|
39769
|
+
* ```ts
|
|
39770
|
+
* lookOf({ texture: "ui.loader.ring", projection: "loading" }); // { projection: "loading" }
|
|
39771
|
+
* ```
|
|
39772
|
+
*/
|
|
39773
|
+
function lookOf(loader) {
|
|
39774
|
+
if (loader.projection !== void 0) return { projection: loader.projection };
|
|
39775
|
+
if (loader.texture !== void 0) return { texture: loader.texture };
|
|
39776
|
+
return {};
|
|
39777
|
+
}
|
|
39778
|
+
/**
|
|
39779
|
+
* Merges one level's loader over the one below. `afterMs` is a field of its own; `texture` and `projection` are one
|
|
39780
|
+
* choice: a level that names either replaces both of the level below. `false` turns it off; a loader written over
|
|
39781
|
+
* `false` starts from the engine's.
|
|
39782
|
+
*
|
|
39783
|
+
* @param below - The loader of the levels below, or `false`.
|
|
39784
|
+
* @param over - What this level writes: nothing, `false` or fields.
|
|
39785
|
+
* @returns The loader, or `false`.
|
|
39786
|
+
* @example
|
|
39787
|
+
* ```ts
|
|
39788
|
+
* loaderOver({ afterMs: 400, projection: "loading" }, { texture: "ui.loader.ring" }); // { afterMs: 400, texture: "ui.loader.ring" }
|
|
39789
|
+
* ```
|
|
39790
|
+
*/
|
|
39791
|
+
function loaderOver(below, over) {
|
|
39792
|
+
if (over === void 0) return below;
|
|
39793
|
+
if (over === false) return false;
|
|
39794
|
+
const base = below === false ? FALLBACK_LEAVE.loader : below;
|
|
39795
|
+
const named = over.texture !== void 0 || over.projection !== void 0;
|
|
39796
|
+
return {
|
|
39797
|
+
afterMs: over.afterMs ?? base.afterMs,
|
|
39798
|
+
...lookOf(named ? over : base)
|
|
39799
|
+
};
|
|
39800
|
+
}
|
|
39801
|
+
/**
|
|
39802
|
+
* Resolves what a switch shows over the scene being left: the scene's `leave` over the config's over the engine's,
|
|
39803
|
+
* field by field. `false` at a level turns both parts off there.
|
|
39804
|
+
*
|
|
39805
|
+
* @param config - `config.leave`.
|
|
39806
|
+
* @param scene - The `leave` of the scene being left.
|
|
39807
|
+
* @returns Both parts, each resolved or `false`; `undefined` when both are off.
|
|
39808
|
+
* @example
|
|
39809
|
+
* ```ts
|
|
39810
|
+
* leaveOf({ loader: false }, { cover: { alpha: 0.6 } }); // { cover: { color: 0, alpha: 0.6, inMs: 150, outMs: 150 }, loader: false }
|
|
39811
|
+
* ```
|
|
39812
|
+
*/
|
|
39813
|
+
function leaveOf(config, scene) {
|
|
39814
|
+
let cover = FALLBACK_LEAVE.cover;
|
|
39815
|
+
let loader = FALLBACK_LEAVE.loader;
|
|
39816
|
+
for (const level of [config, scene]) if (level === false) {
|
|
39817
|
+
cover = false;
|
|
39818
|
+
loader = false;
|
|
39819
|
+
} else if (level !== void 0) {
|
|
39820
|
+
cover = coverOver(cover, level.cover);
|
|
39821
|
+
loader = loaderOver(loader, level.loader);
|
|
39822
|
+
}
|
|
39823
|
+
if (cover === false && loader === false) return void 0;
|
|
39824
|
+
return {
|
|
39825
|
+
cover,
|
|
39826
|
+
loader
|
|
39827
|
+
};
|
|
39828
|
+
}
|
|
39829
|
+
/**
|
|
39830
|
+
* The layer a mounted projection's views draw in, read from its first view.
|
|
39831
|
+
*
|
|
39832
|
+
* @param ctx - Domain context of the plugin.
|
|
39833
|
+
* @param name - The projection.
|
|
39834
|
+
* @returns The layer, or `undefined` while it has no view.
|
|
39835
|
+
*/
|
|
39836
|
+
function layerOf(ctx, name) {
|
|
39837
|
+
const { projection, ecs } = ctx.deps.world;
|
|
39838
|
+
const entity = projection.entitiesOf(name)[0];
|
|
39839
|
+
return entity === void 0 ? void 0 : ecs.get(entity, Layer)?.name;
|
|
39840
|
+
}
|
|
39841
|
+
/**
|
|
39842
|
+
* Unmounts the loader projection a switch mounted, once: one it did not mount is left alone.
|
|
39843
|
+
*
|
|
39844
|
+
* @param ctx - Domain context of the plugin.
|
|
39845
|
+
* @param leaving - The look of the switch.
|
|
39846
|
+
*/
|
|
39847
|
+
function unmountLoader(ctx, leaving) {
|
|
39848
|
+
const name = leaving.projection;
|
|
39849
|
+
if (name === void 0) return;
|
|
39850
|
+
delete leaving.projection;
|
|
39851
|
+
ctx.deps.world.projection.unmount([name]);
|
|
39852
|
+
}
|
|
39853
|
+
/**
|
|
39854
|
+
* Mounts a loader projection for the switch, unless it is mounted already (by another owner): such a one is drawn as it
|
|
39855
|
+
* is and never unmounted by the switch. A mount that throws (an unknown name, a layer the scene being left does not
|
|
39856
|
+
* declare) is warned.
|
|
39857
|
+
*
|
|
39858
|
+
* @param ctx - Domain context of the plugin.
|
|
39859
|
+
* @param name - The loader projection.
|
|
39860
|
+
* @param from - The scene being left.
|
|
39861
|
+
* @param leaving - The look of the switch: it keeps the name to unmount at the end.
|
|
39862
|
+
* @returns False when the mount threw.
|
|
39863
|
+
*/
|
|
39864
|
+
function mountOnce(ctx, name, from, leaving) {
|
|
39865
|
+
const projection = ctx.deps.world.projection;
|
|
39866
|
+
if (projection.entitiesOf(name).length > 0) return true;
|
|
39867
|
+
try {
|
|
39868
|
+
projection.mount([name], ctx.state.owner);
|
|
39869
|
+
} catch (error) {
|
|
39870
|
+
ctx.log.warn("scenes:loader-unmounted", {
|
|
39871
|
+
projection: name,
|
|
39872
|
+
scene: from.id,
|
|
39873
|
+
error
|
|
39874
|
+
});
|
|
39875
|
+
return false;
|
|
39876
|
+
}
|
|
39877
|
+
leaving.projection = name;
|
|
39878
|
+
return true;
|
|
39879
|
+
}
|
|
39880
|
+
/**
|
|
39881
|
+
* Tells whether a projection of the scene being left draws in a layer: lifted above the veil, that layer would take
|
|
39882
|
+
* the scene's own views with it.
|
|
39883
|
+
*
|
|
39884
|
+
* @param ctx - Domain context of the plugin.
|
|
39885
|
+
* @param from - The scene being left.
|
|
39886
|
+
* @param layer - The layer of the loader.
|
|
39887
|
+
* @returns True when one of its projections has a view in that layer.
|
|
39888
|
+
*/
|
|
39889
|
+
function sharesLayer(ctx, from, layer) {
|
|
39890
|
+
return from.projections.some((other) => layerOf(ctx, other) === layer);
|
|
39891
|
+
}
|
|
39892
|
+
/**
|
|
39893
|
+
* Mounts a loader projection once its loader is due, called by the renderer in that frame, and answers the layer its
|
|
39894
|
+
* views draw in, which the renderer draws above the veil. A mount that throws, a layer a projection of the scene being
|
|
39895
|
+
* left draws in too, and a projection with no view give the engine's ring. A switch that ended meanwhile mounts
|
|
39896
|
+
* nothing.
|
|
39897
|
+
*
|
|
39898
|
+
* @param ctx - Domain context of the plugin.
|
|
39899
|
+
* @param name - The loader projection.
|
|
39900
|
+
* @param from - The scene being left.
|
|
39901
|
+
* @param leaving - The look of the switch: it keeps the name to unmount at the end.
|
|
39902
|
+
* @returns The layer, or `undefined` for the ring.
|
|
39903
|
+
*/
|
|
39904
|
+
function mountLoader(ctx, name, from, leaving) {
|
|
39905
|
+
if (leaving.done || !mountOnce(ctx, name, from, leaving)) return void 0;
|
|
39906
|
+
const layer = layerOf(ctx, name);
|
|
39907
|
+
if (layer === void 0 || !sharesLayer(ctx, from, layer)) return layer;
|
|
39908
|
+
ctx.log.warn("scenes:loader-layer-shared", {
|
|
39909
|
+
projection: name,
|
|
39910
|
+
layer,
|
|
39911
|
+
scene: from.id
|
|
39912
|
+
});
|
|
39913
|
+
unmountLoader(ctx, leaving);
|
|
39914
|
+
}
|
|
39915
|
+
/**
|
|
39916
|
+
* The renderer's loader of a resolved one: a texture as it is, nothing for the ring, and for a projection the `due`
|
|
39917
|
+
* the renderer calls once the loader is due, which mounts it then: a switch done sooner never runs its `from` and
|
|
39918
|
+
* `view`. A projection a scene of the switch lists is no loader: warned, the ring.
|
|
39919
|
+
*
|
|
39920
|
+
* @param ctx - Domain context of the plugin.
|
|
39921
|
+
* @param loader - The resolved loader.
|
|
39922
|
+
* @param between - The scene being left and the scene being switched to.
|
|
39923
|
+
* @param between.from - The scene being left.
|
|
39924
|
+
* @param between.to - The scene being switched to.
|
|
39925
|
+
* @param leaving - The look of the switch.
|
|
39926
|
+
* @returns What the renderer draws.
|
|
39927
|
+
*/
|
|
39928
|
+
function loaderLook(ctx, loader, between, leaving) {
|
|
39929
|
+
const { afterMs, texture, projection } = loader;
|
|
39930
|
+
const { from, to } = between;
|
|
39931
|
+
if (texture !== void 0) return {
|
|
39932
|
+
afterMs,
|
|
39933
|
+
texture
|
|
39934
|
+
};
|
|
39935
|
+
if (projection === void 0) return { afterMs };
|
|
39936
|
+
if (from.projections.includes(projection) || to.projections.includes(projection)) {
|
|
39937
|
+
ctx.log.warn("scenes:loader-listed", {
|
|
39938
|
+
projection,
|
|
39939
|
+
from: from.id,
|
|
39940
|
+
to: to.id
|
|
39941
|
+
});
|
|
39942
|
+
return { afterMs };
|
|
39943
|
+
}
|
|
39944
|
+
return {
|
|
39945
|
+
afterMs,
|
|
39946
|
+
due: () => mountLoader(ctx, projection, from, leaving)
|
|
39947
|
+
};
|
|
39948
|
+
}
|
|
39949
|
+
/**
|
|
39950
|
+
* Tells whether a switch shows anything over the scene being left: a real change of scene, a live run that was not
|
|
39951
|
+
* aborted, a world that runs live, a drawing renderer. The first scene of a run, a fast walk, a world walking fast or
|
|
39952
|
+
* paused and a headless app show nothing.
|
|
39953
|
+
*
|
|
39954
|
+
* @param ctx - Domain context of the plugin.
|
|
39955
|
+
* @param run - The runner's mode and signal.
|
|
39956
|
+
* @returns The scene being left, or `undefined` when nothing is shown.
|
|
39957
|
+
*/
|
|
39958
|
+
function leftScene(ctx, run) {
|
|
39959
|
+
const current = ctx.state.current;
|
|
39960
|
+
if (current === void 0 || run.mode !== "live" || run.signal.aborted) return void 0;
|
|
39961
|
+
if (ctx.deps.world.ecs.mode() !== "live" || !ctx.deps.renderer.host.ready()) return void 0;
|
|
39962
|
+
return ctx.state.scenes.get(current);
|
|
39963
|
+
}
|
|
39964
|
+
/**
|
|
39965
|
+
* Shows the veil and the loader of a switch that starts now, before its bundles are awaited: the scene being left
|
|
39966
|
+
* decides. A loader projection is mounted only once its loader is due. An abort of the node drops both at once, from
|
|
39967
|
+
* its listener.
|
|
39968
|
+
*
|
|
39969
|
+
* @param ctx - Domain context of the plugin.
|
|
39970
|
+
* @param scene - The scene being switched to.
|
|
39971
|
+
* @param run - The runner's mode and signal.
|
|
39972
|
+
* @returns The look of the switch, or `undefined` when it shows nothing.
|
|
39973
|
+
*/
|
|
39974
|
+
function startLeave(ctx, scene, run) {
|
|
39975
|
+
const from = leftScene(ctx, run);
|
|
39976
|
+
if (from === void 0) return void 0;
|
|
39977
|
+
const leave = leaveOf(ctx.config.leave, from.leave);
|
|
39978
|
+
if (leave === void 0) return void 0;
|
|
39979
|
+
const listening = new AbortController();
|
|
39980
|
+
const leaving = {
|
|
39981
|
+
done: false,
|
|
39982
|
+
off: () => listening.abort()
|
|
39983
|
+
};
|
|
39984
|
+
const look = {};
|
|
39985
|
+
if (leave.cover !== false) look.cover = leave.cover;
|
|
39986
|
+
if (leave.loader !== false) look.loader = loaderLook(ctx, leave.loader, {
|
|
39987
|
+
from,
|
|
39988
|
+
to: scene
|
|
39989
|
+
}, leaving);
|
|
39990
|
+
ctx.deps.renderer.sync.leave.show(look);
|
|
39991
|
+
run.signal.addEventListener("abort", () => endLeave(ctx, leaving, "drop"), {
|
|
39992
|
+
once: true,
|
|
39993
|
+
signal: listening.signal
|
|
39994
|
+
});
|
|
39995
|
+
ctx.state.leaving = leaving;
|
|
39996
|
+
return leaving;
|
|
39997
|
+
}
|
|
39998
|
+
/**
|
|
39999
|
+
* Ends the look of a switch once: `"land"` at the swap (the loader goes, the veil fades out over the next scene),
|
|
40000
|
+
* `"drop"` on an abort or a throw (both go at once). A loader projection mounted for the wait is unmounted. A look
|
|
40001
|
+
* ended already, or a switch that showed nothing, is left alone.
|
|
40002
|
+
*
|
|
40003
|
+
* @param ctx - Domain context of the plugin.
|
|
40004
|
+
* @param leaving - The look of the switch.
|
|
40005
|
+
* @param how - `"land"` after the swap, `"drop"` without one.
|
|
40006
|
+
*/
|
|
40007
|
+
function endLeave(ctx, leaving, how) {
|
|
40008
|
+
if (leaving === void 0 || leaving.done) return;
|
|
40009
|
+
leaving.done = true;
|
|
40010
|
+
leaving.off();
|
|
40011
|
+
if (ctx.state.leaving === leaving) delete ctx.state.leaving;
|
|
40012
|
+
const leave = ctx.deps.renderer.sync.leave;
|
|
40013
|
+
if (how === "land") leave.land();
|
|
40014
|
+
else leave.drop();
|
|
40015
|
+
unmountLoader(ctx, leaving);
|
|
40016
|
+
}
|
|
40017
|
+
/**
|
|
40018
|
+
* Forgets the look of a running switch when the plugin stops: its abort listener goes and a later end does nothing.
|
|
40019
|
+
* Nothing else is called: a teardown context may not call another plugin; the renderer drops the look at its own stop,
|
|
40020
|
+
* and `world.onStop` the loader projection.
|
|
40021
|
+
*
|
|
40022
|
+
* @param state - The plugin state.
|
|
40023
|
+
*/
|
|
40024
|
+
function forgetLeave(state) {
|
|
40025
|
+
const leaving = state.leaving;
|
|
40026
|
+
if (leaving === void 0) return;
|
|
40027
|
+
leaving.done = true;
|
|
40028
|
+
leaving.off();
|
|
40029
|
+
delete state.leaving;
|
|
40030
|
+
}
|
|
40031
|
+
//#endregion
|
|
40032
|
+
//#region src/plugins/scenes/leave-check.ts
|
|
40033
|
+
/** The largest colour, `0xffffff`. */
|
|
40034
|
+
const MAX_COLOR = 16777215;
|
|
40035
|
+
/**
|
|
40036
|
+
* Tells whether a value is a finite number of at least 0: a duration.
|
|
40037
|
+
*
|
|
40038
|
+
* @param value - The value.
|
|
40039
|
+
* @returns True for a finite number `>= 0`.
|
|
40040
|
+
* @example
|
|
40041
|
+
* ```ts
|
|
40042
|
+
* isDuration(-1); // false
|
|
40043
|
+
* ```
|
|
40044
|
+
*/
|
|
40045
|
+
function isDuration(value) {
|
|
40046
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0;
|
|
40047
|
+
}
|
|
40048
|
+
/**
|
|
40049
|
+
* Tells whether a value is an alpha: a number from 0 to 1.
|
|
40050
|
+
*
|
|
40051
|
+
* @param value - The value.
|
|
40052
|
+
* @returns True for a number in `[0, 1]`.
|
|
40053
|
+
* @example
|
|
40054
|
+
* ```ts
|
|
40055
|
+
* isAlpha(1.2); // false
|
|
40056
|
+
* ```
|
|
40057
|
+
*/
|
|
40058
|
+
function isAlpha(value) {
|
|
40059
|
+
return typeof value === "number" && value >= 0 && value <= 1;
|
|
40060
|
+
}
|
|
40061
|
+
/**
|
|
40062
|
+
* Tells whether a value is a colour `0xRRGGBB`: a whole number from 0 to `0xffffff`.
|
|
40063
|
+
*
|
|
40064
|
+
* @param value - The value.
|
|
40065
|
+
* @returns True for a colour.
|
|
40066
|
+
* @example
|
|
40067
|
+
* ```ts
|
|
40068
|
+
* isColor(0x0b1020); // true
|
|
40069
|
+
* ```
|
|
40070
|
+
*/
|
|
40071
|
+
function isColor$1(value) {
|
|
40072
|
+
return Number.isInteger(value) && typeof value === "number" && value >= 0 && value <= MAX_COLOR;
|
|
40073
|
+
}
|
|
40074
|
+
/**
|
|
40075
|
+
* Tells whether a value is a name: a string that is not empty.
|
|
40076
|
+
*
|
|
40077
|
+
* @param value - The value.
|
|
40078
|
+
* @returns True for a string with at least one character.
|
|
40079
|
+
* @example
|
|
40080
|
+
* ```ts
|
|
40081
|
+
* isName(""); // false
|
|
40082
|
+
* ```
|
|
40083
|
+
*/
|
|
40084
|
+
function isName(value) {
|
|
40085
|
+
return typeof value === "string" && value.length > 0;
|
|
40086
|
+
}
|
|
40087
|
+
/**
|
|
40088
|
+
* Tells whether a value is a plain object a field can be read from.
|
|
40089
|
+
*
|
|
40090
|
+
* @param value - The value.
|
|
40091
|
+
* @returns True for an object that is no array and not `null`.
|
|
40092
|
+
* @example
|
|
40093
|
+
* ```ts
|
|
40094
|
+
* isRecord([]); // false
|
|
40095
|
+
* ```
|
|
40096
|
+
*/
|
|
40097
|
+
function isRecord$2(value) {
|
|
40098
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
40099
|
+
}
|
|
40100
|
+
/** The fields of a veil. */
|
|
40101
|
+
const COVER_RULES = Object.freeze({
|
|
40102
|
+
color: {
|
|
40103
|
+
ok: isColor$1,
|
|
40104
|
+
hint: "a colour from 0x000000 to 0xffffff"
|
|
40105
|
+
},
|
|
40106
|
+
alpha: {
|
|
40107
|
+
ok: isAlpha,
|
|
40108
|
+
hint: "the alpha the veil fades in to, a number from 0 to 1"
|
|
40109
|
+
},
|
|
40110
|
+
inMs: {
|
|
40111
|
+
ok: isDuration,
|
|
40112
|
+
hint: "the milliseconds of the fade-in, 0 or more"
|
|
40113
|
+
},
|
|
40114
|
+
outMs: {
|
|
40115
|
+
ok: isDuration,
|
|
40116
|
+
hint: "the milliseconds of the fade-out, 0 or more"
|
|
40117
|
+
}
|
|
40118
|
+
});
|
|
40119
|
+
/** The fields of a loader. */
|
|
40120
|
+
const LOADER_RULES = Object.freeze({
|
|
40121
|
+
afterMs: {
|
|
40122
|
+
ok: isDuration,
|
|
40123
|
+
hint: "the milliseconds the loader waits for, 0 or more"
|
|
40124
|
+
},
|
|
40125
|
+
texture: {
|
|
40126
|
+
ok: isName,
|
|
40127
|
+
hint: "an asset key such as \"ui.loader.ring\""
|
|
40128
|
+
},
|
|
40129
|
+
projection: {
|
|
40130
|
+
ok: isName,
|
|
40131
|
+
hint: "the name of a registered projection such as \"loading\""
|
|
40132
|
+
}
|
|
40133
|
+
});
|
|
40134
|
+
/**
|
|
40135
|
+
* Builds the error of a field of the wrong shape.
|
|
40136
|
+
*
|
|
40137
|
+
* @param where - Where the part was written.
|
|
40138
|
+
* @param value - What the field holds.
|
|
40139
|
+
* @param hint - What to give instead.
|
|
40140
|
+
* @returns The error, in the engine's two-line format.
|
|
40141
|
+
* @example
|
|
40142
|
+
* ```ts
|
|
40143
|
+
* wrong("scenes.leave.cover.alpha", 2, "a number from 0 to 1").message;
|
|
40144
|
+
* // '[game] scenes.leave.cover.alpha is 2.\n Give a number from 0 to 1.'
|
|
40145
|
+
* ```
|
|
40146
|
+
*/
|
|
40147
|
+
function wrong(where, value, hint) {
|
|
40148
|
+
return /* @__PURE__ */ new Error(`[game] ${where} is ${String(value)}.\n Give ${hint}.`);
|
|
40149
|
+
}
|
|
40150
|
+
/**
|
|
40151
|
+
* Checks one part of a `leave`: nothing, `false`, or an object whose every field it knows has the right shape.
|
|
40152
|
+
*
|
|
40153
|
+
* @param where - Where the part was written, `scenes.leave.cover`.
|
|
40154
|
+
* @param part - What the part holds.
|
|
40155
|
+
* @param rules - The fields of the part.
|
|
40156
|
+
* @throws {Error} For a part that is neither `false` nor an object, and for the first field of the wrong shape.
|
|
40157
|
+
*/
|
|
40158
|
+
function checkPart(where, part, rules) {
|
|
40159
|
+
if (part === void 0 || part === false) return;
|
|
40160
|
+
if (!isRecord$2(part)) throw wrong(where, part, "an object of its fields, or false");
|
|
40161
|
+
for (const [field, rule] of Object.entries(rules)) {
|
|
40162
|
+
const value = part[field];
|
|
40163
|
+
if (value !== void 0 && !rule.ok(value)) throw wrong(`${where}.${field}`, value, rule.hint);
|
|
40164
|
+
}
|
|
40165
|
+
}
|
|
40166
|
+
/**
|
|
40167
|
+
* Checks a `leave` a game wrote: nothing, `false`, or an object whose `cover` and `loader` are each nothing, `false`
|
|
40168
|
+
* or an object of the right fields: a colour, an alpha from 0 to 1, durations of 0 or more, and names that are not
|
|
40169
|
+
* empty.
|
|
40170
|
+
*
|
|
40171
|
+
* @param where - Where it was written, for the message: `scenes.leave`, or `Scene "board": leave`.
|
|
40172
|
+
* @param leave - What was written.
|
|
40173
|
+
* @throws {Error} For the first part or field of the wrong shape.
|
|
40174
|
+
* @example
|
|
40175
|
+
* ```ts
|
|
40176
|
+
* checkLeave("scenes.leave", { cover: { alpha: 2 } });
|
|
40177
|
+
* // throws '[game] scenes.leave.cover.alpha is 2.\n Give the alpha the veil fades in to, a number from 0 to 1.'
|
|
40178
|
+
* ```
|
|
40179
|
+
*/
|
|
40180
|
+
function checkLeave(where, leave) {
|
|
40181
|
+
if (leave === void 0 || leave === false) return;
|
|
40182
|
+
if (!isRecord$2(leave)) throw wrong(where, leave, "{ cover, loader } or false");
|
|
40183
|
+
checkPart(`${where}.cover`, leave.cover, COVER_RULES);
|
|
40184
|
+
checkPart(`${where}.loader`, leave.loader, LOADER_RULES);
|
|
40185
|
+
}
|
|
40186
|
+
//#endregion
|
|
39104
40187
|
//#region src/plugins/scenes/switch.ts
|
|
39105
40188
|
/**
|
|
39106
40189
|
* A promise that resolves when the node was aborted. Already aborted resolves at once.
|
|
@@ -39298,11 +40381,31 @@ async function standUp(ctx, scene, named, run) {
|
|
|
39298
40381
|
swapIn(ctx, scene, named);
|
|
39299
40382
|
}
|
|
39300
40383
|
/**
|
|
39301
|
-
*
|
|
39302
|
-
*
|
|
39303
|
-
*
|
|
39304
|
-
*
|
|
39305
|
-
*
|
|
40384
|
+
* Awaits every bundle the scene names and only then builds the scene. In live mode it waits between
|
|
40385
|
+
* the two for the textures of those bundles to reach the GPU; a fast walk does not, so tests and
|
|
40386
|
+
* restores keep their pace. An aborted node, or a plugin stopped during the wait, returns without
|
|
40387
|
+
* touching anything. A live switch with a drawing renderer resolves at the commit of the scene
|
|
40388
|
+
* built aside, frames later.
|
|
40389
|
+
*
|
|
40390
|
+
* @param ctx - Domain context of the plugin.
|
|
40391
|
+
* @param scene - The scene to mount.
|
|
40392
|
+
* @param named - The bundles it names, read from the committed state.
|
|
40393
|
+
* @param run - The runner's mode and signal.
|
|
40394
|
+
* @returns A promise that resolves once the scene stands, or the node was aborted.
|
|
40395
|
+
* @throws {Error} Whatever a load, the mount or the build threw.
|
|
40396
|
+
*/
|
|
40397
|
+
async function loadAndStand(ctx, scene, named, run) {
|
|
40398
|
+
await untilAborted(ctx, named, run.signal);
|
|
40399
|
+
if (run.mode === "live" && !run.signal.aborted) await untilWarmed(ctx, named, run.signal);
|
|
40400
|
+
const stopped = ctx.state.teardown === void 0;
|
|
40401
|
+
if (run.signal.aborted || stopped) return;
|
|
40402
|
+
await standUp(ctx, scene, named, run);
|
|
40403
|
+
}
|
|
40404
|
+
/**
|
|
40405
|
+
* Switches to another scene: it reads what the scene names from the committed state, shows the
|
|
40406
|
+
* veil and the loader of the scene being left at once (a cold first visit reacts in the next
|
|
40407
|
+
* frame), then awaits the bundles and builds the scene (`loadAndStand`). The look lands when the
|
|
40408
|
+
* scene stands, and is dropped when the switch ends without it or throws.
|
|
39306
40409
|
*
|
|
39307
40410
|
* @param ctx - Domain context of the plugin.
|
|
39308
40411
|
* @param target - Id of the scene to mount.
|
|
@@ -39314,11 +40417,15 @@ async function switchTo(ctx, target, run) {
|
|
|
39314
40417
|
const scene = ctx.state.scenes.get(target);
|
|
39315
40418
|
if (scene === void 0) throw new Error(`[game] Scene "${target}" is not registered.\n List it in the scenes of a feature.`);
|
|
39316
40419
|
const named = namedBundles(scene, committedState(ctx));
|
|
39317
|
-
|
|
39318
|
-
|
|
39319
|
-
|
|
39320
|
-
|
|
39321
|
-
|
|
40420
|
+
let leaving;
|
|
40421
|
+
try {
|
|
40422
|
+
leaving = startLeave(ctx, scene, run);
|
|
40423
|
+
await loadAndStand(ctx, scene, named, run);
|
|
40424
|
+
} catch (error) {
|
|
40425
|
+
endLeave(ctx, leaving, "drop");
|
|
40426
|
+
throw error;
|
|
40427
|
+
}
|
|
40428
|
+
endLeave(ctx, leaving, ctx.state.current === scene.id ? "land" : "drop");
|
|
39322
40429
|
}
|
|
39323
40430
|
/**
|
|
39324
40431
|
* Reads again what the mounted scene names, at a node that keeps it: a bundle it names now and
|
|
@@ -39432,15 +40539,17 @@ function readScenes(ctx) {
|
|
|
39432
40539
|
}
|
|
39433
40540
|
}
|
|
39434
40541
|
/**
|
|
39435
|
-
* Refuses a
|
|
39436
|
-
* `"auto"` nor a finite number above 0
|
|
39437
|
-
* is a boundary.
|
|
40542
|
+
* Refuses a config a switch cannot run with, in `onInit`, so `createApp` throws: a building budget
|
|
40543
|
+
* that is neither `"auto"` nor a finite number above 0, and a `leave` of the wrong shape. A game
|
|
40544
|
+
* written in plain JS passes anything, so the config is a boundary.
|
|
39438
40545
|
*
|
|
39439
40546
|
* @param config - The resolved config of the plugin.
|
|
39440
|
-
* @throws {Error} When `buildMs` is neither `"auto"` nor a finite number above 0
|
|
40547
|
+
* @throws {Error} When `buildMs` is neither `"auto"` nor a finite number above 0, and for a `leave`
|
|
40548
|
+
* `checkLeave` refuses.
|
|
39441
40549
|
*/
|
|
39442
40550
|
function checkConfig(config) {
|
|
39443
40551
|
const value = config.buildMs;
|
|
40552
|
+
checkLeave("scenes.leave", config.leave);
|
|
39444
40553
|
if (value === "auto") return;
|
|
39445
40554
|
if (isPositiveFinite(value)) return;
|
|
39446
40555
|
throw new Error(`[game] scenes.buildMs is ${String(value)}.\n Give "auto" or the milliseconds a frame may spend on the next scene, a finite number above 0 such as 4.`);
|
|
@@ -39464,7 +40573,8 @@ function startScenes(ctx) {
|
|
|
39464
40573
|
}
|
|
39465
40574
|
/**
|
|
39466
40575
|
* Removes the `onEnter` callback in `onStop` and forgets every scene and the bundles the mounted
|
|
39467
|
-
* one named. A running build loses its frame callback and its switch returns
|
|
40576
|
+
* one named. A running build loses its frame callback and its switch returns; the look of a running
|
|
40577
|
+
* switch loses its abort listener (the renderer drops it at its own stop). Nothing is unmounted
|
|
39468
40578
|
* or discarded: a teardown context may not call another plugin, and `world.onStop` clears every
|
|
39469
40579
|
* entity and the build table anyway.
|
|
39470
40580
|
*
|
|
@@ -39482,6 +40592,7 @@ function stopScenes(teardown) {
|
|
|
39482
40592
|
state.teardown = void 0;
|
|
39483
40593
|
dropBuild(state.build);
|
|
39484
40594
|
delete state.build;
|
|
40595
|
+
forgetLeave(state);
|
|
39485
40596
|
state.scenes.clear();
|
|
39486
40597
|
state.current = void 0;
|
|
39487
40598
|
state.pending = void 0;
|
|
@@ -39550,6 +40661,9 @@ function createScenesState() {
|
|
|
39550
40661
|
*
|
|
39551
40662
|
* // A game gives the next scene a fixed 3 ms of each frame (two thirds of it by default) while the old one stays on the screen.
|
|
39552
40663
|
* createApp({ plugins: [...screen, boardFeature], pluginConfigs: { scenes: { buildMs: 3 } } });
|
|
40664
|
+
*
|
|
40665
|
+
* // No veil and no loader over a scene being left, on any switch.
|
|
40666
|
+
* createApp({ plugins: [...screen, boardFeature], pluginConfigs: { scenes: { leave: false } } });
|
|
39553
40667
|
* ```
|
|
39554
40668
|
*/
|
|
39555
40669
|
const scenesPlugin = /*#__PURE__*/ createPlugin$1("scenes", {
|
|
@@ -39561,7 +40675,18 @@ const scenesPlugin = /*#__PURE__*/ createPlugin$1("scenes", {
|
|
|
39561
40675
|
modelPlugin,
|
|
39562
40676
|
rendererPlugin
|
|
39563
40677
|
],
|
|
39564
|
-
config: {
|
|
40678
|
+
config: {
|
|
40679
|
+
buildMs: "auto",
|
|
40680
|
+
leave: {
|
|
40681
|
+
cover: {
|
|
40682
|
+
color: 0,
|
|
40683
|
+
alpha: .45,
|
|
40684
|
+
inMs: 150,
|
|
40685
|
+
outMs: 150
|
|
40686
|
+
},
|
|
40687
|
+
loader: { afterMs: 400 }
|
|
40688
|
+
}
|
|
40689
|
+
},
|
|
39565
40690
|
events: (register) => register.map({ "scenes:changed": "The mounted scene changed" }),
|
|
39566
40691
|
createState: createScenesState,
|
|
39567
40692
|
api: createScenesApi,
|
|
@@ -64680,14 +65805,15 @@ function namesOf(id, layers, projections) {
|
|
|
64680
65805
|
* layer names come from the keys of `layers` and that `ui`, so a projection whose `layer` or
|
|
64681
65806
|
* `lift` is not among them does not compile — and throws here for a caller without types.
|
|
64682
65807
|
* `bundles` names bundles that depend on the committed state: the scene is mounted once those
|
|
64683
|
-
* are in too.
|
|
65808
|
+
* are in too. `leave` is what a live switch shows over the scene when it is left, merged over the
|
|
65809
|
+
* config's: `false` shows nothing.
|
|
64684
65810
|
*
|
|
64685
65811
|
* @param id - Id of the scene. A node names it with `defineNode({ scene: id })`.
|
|
64686
|
-
* @param scene - The bundle, the optional bundles of state, the layers, the projections
|
|
64687
|
-
* optional music key
|
|
65812
|
+
* @param scene - The bundle, the optional bundles of state, the layers, the projections, the
|
|
65813
|
+
* optional music key and the optional `leave`.
|
|
64688
65814
|
* @returns The scene as frozen data, ready for the `scenes` key of a feature.
|
|
64689
65815
|
* @throws {Error} For an integer-like layer name, for a `layer` or `lift` that is not declared, for two HUD layers,
|
|
64690
|
-
* an `above` other than `"overlays"`,
|
|
65816
|
+
* an `above` other than `"overlays"`, a `ui` declared after the HUD layer, and a `leave` of the wrong shape.
|
|
64691
65817
|
* @example
|
|
64692
65818
|
* ```ts
|
|
64693
65819
|
* // features/board/view/scene.ts of a game.
|
|
@@ -64712,18 +65838,28 @@ function namesOf(id, layers, projections) {
|
|
|
64712
65838
|
* projections: [wardRoom, wardGlows]
|
|
64713
65839
|
* });
|
|
64714
65840
|
* wardScene.bundles?.({ player: {}, session: { episode: "ep02" } }); // ["ep02"]
|
|
65841
|
+
*
|
|
65842
|
+
* // Leaving the board dims it in navy and turns the game's ring texture once the switch took 400 ms.
|
|
65843
|
+
* defineScene("board", {
|
|
65844
|
+
* bundle: "board",
|
|
65845
|
+
* layers: { cells: {} },
|
|
65846
|
+
* projections: [],
|
|
65847
|
+
* leave: { cover: { color: 0x0b1020, alpha: 0.6 }, loader: { texture: "ui.loader.ring" } }
|
|
65848
|
+
* }).leave; // { cover: { color: 0x0b1020, alpha: 0.6 }, loader: { texture: "ui.loader.ring" } }
|
|
64715
65849
|
* ```
|
|
64716
65850
|
*/
|
|
64717
65851
|
function defineScene(id, scene) {
|
|
64718
65852
|
const layers = layersOf(id, scene.layers);
|
|
64719
65853
|
const projections = scene.projections;
|
|
65854
|
+
checkLeave(`Scene "${id}": leave`, scene.leave);
|
|
64720
65855
|
return Object.freeze({
|
|
64721
65856
|
id,
|
|
64722
65857
|
bundle: scene.bundle,
|
|
64723
65858
|
music: scene.music,
|
|
64724
65859
|
layers: Object.freeze(layers),
|
|
64725
65860
|
projections: Object.freeze(namesOf(id, layers, projections)),
|
|
64726
|
-
...scene.bundles === void 0 ? {} : { bundles: scene.bundles }
|
|
65861
|
+
...scene.bundles === void 0 ? {} : { bundles: scene.bundles },
|
|
65862
|
+
...scene.leave === void 0 ? {} : { leave: scene.leave }
|
|
64727
65863
|
});
|
|
64728
65864
|
}
|
|
64729
65865
|
/**
|
|
@@ -65156,6 +66292,7 @@ function cubicBezier(x1, y1, x2, y2) {
|
|
|
65156
66292
|
* | assets | baseUrl | undefined |
|
|
65157
66293
|
* | assets | io | undefined, the browser fetch and the renderer textures |
|
|
65158
66294
|
* | scenes | buildMs | "auto", two thirds of a frame ((1000 / time.rate().fps) * 2 / 3, 11.1 ms at 60 Hz), read at each build start; a number fixes it |
|
|
66295
|
+
* | scenes | leave | { cover: { color: 0x000000, alpha: 0.45, inMs: 150, outMs: 150 }, loader: { afterMs: 400 } }, what a live switch shows over the scene being left: a veil from its start and a loader (the engine's ring, a `texture` or a `projection`) once it took `afterMs`; a scene's `leave` is merged over it field by field, a field left out keeps this default, `false` turns a part or the whole off |
|
|
65159
66296
|
* | anim | maxTracks | 2000 |
|
|
65160
66297
|
* | anim | reducedMotion | false |
|
|
65161
66298
|
* | i18n | locale | "en" |
|
|
@@ -65300,4 +66437,4 @@ const screen = [
|
|
|
65300
66437
|
uiPlugin
|
|
65301
66438
|
];
|
|
65302
66439
|
//#endregion
|
|
65303
|
-
export { sfx as $, flowPlugin as $t, types_exports$10 as A, Glow as At, defineMotion as B, Animation as Bt, types_exports$3 as C, messageDuration as Ct, types_exports$7 as D, Blur as Dt, types_exports$6 as E, Alpha as Et, types_exports$15 as F, audioPlugin as Ft, external as G, MONITOR_SECTIONS as Gt, platformPlugin as H, rendererPlugin as Ht, types_exports$16 as I, scenesPlugin as It, mark as J, PERF_LOG_VERSION as Jt, frames as K, PERF_CAUSES as Kt, types_exports$17 as L, assetsPlugin as Lt, types_exports$12 as M, Outline as Mt, types_exports$13 as N, defineFilter as Nt, types_exports$8 as O, ColorMatrix as Ot, types_exports$14 as P, debugPlugin as Pt, set as Q, worldPlugin as Qt, uiPlugin as R, animPlugin as Rt, types_exports$2 as S, messageArgument as St, types_exports$5 as T, Emitter as Tt, HAPTIC_KINDS as U, PASS_KINDS as Ut, textPlugin as V, Frames as Vt, defineAnimation as W, frameAt as Wt, play$1 as X, SHADER_WHATS as Xt, parallel as Y, PROBLEM_RULES as Yt, sequence as Z, DEMAND_REASONS as Zt, defineBundles as _, Tappable as _t, cubicBezier as a,
|
|
66440
|
+
export { sfx as $, flowPlugin as $t, types_exports$10 as A, Glow as At, defineMotion as B, Animation as Bt, types_exports$3 as C, messageDuration as Ct, types_exports$7 as D, Blur as Dt, types_exports$6 as E, Alpha as Et, types_exports$15 as F, audioPlugin as Ft, external as G, MONITOR_SECTIONS as Gt, platformPlugin as H, rendererPlugin as Ht, types_exports$16 as I, scenesPlugin as It, mark as J, PERF_LOG_VERSION as Jt, frames as K, PERF_CAUSES as Kt, types_exports$17 as L, assetsPlugin as Lt, types_exports$12 as M, Outline as Mt, types_exports$13 as N, defineFilter as Nt, types_exports$8 as O, ColorMatrix as Ot, types_exports$14 as P, debugPlugin as Pt, set as Q, worldPlugin as Qt, uiPlugin as R, animPlugin as Rt, types_exports$2 as S, messageArgument as St, types_exports$5 as T, Emitter as Tt, HAPTIC_KINDS as U, PASS_KINDS as Ut, textPlugin as V, Frames as Vt, defineAnimation as W, frameAt as Wt, play$1 as X, SHADER_WHATS as Xt, parallel as Y, PROBLEM_RULES as Yt, sequence as Z, DEMAND_REASONS as Zt, defineBundles as _, Tappable as _t, cubicBezier as a, type as an, use as at, types_exports as b, Traced as bt, hint as c, lifecyclePlugin as cn, Draggable as ct, defineScene as d, Hovered as dt, refuseStart as en, spawn as et, tr as f, Pointer as ft, music as g, Swipeable as gt, defineEmitter as h, Pressed as ht, screen as i, to as in, tween as it, types_exports$11 as j, Noise as jt, types_exports$9 as k, Displacement as kt, schedule as l, timePlugin as ln, DropTarget as lt, flattenFeatures as m, Pressable as mt, createPlugin as n, exit as nn, stagger as nt, defineEffect as o, modelPlugin as on, wait as ot, defineFeature as p, PointerOver as pt, haptic as q, PERF_GAP_BUCKETS as qt, defineGame as r, slot as rn, stopSfx as rt, guide as s, SaveUnreadableError as sn, inputPlugin as st, createApp as t, contribution as tn, spawned as tt, projection as u, clockPlugin as un, Held as ut, load as v, Touchable as vt, types_exports$4 as w, effectsPlugin as wt, types_exports$1 as x, i18nPlugin as xt, ready as y, Traceable as yt, resolve as z, AnimPlayer as zt };
|