@moku-labs/game 0.15.39 → 0.15.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app/page.d.mts +1 -1
- package/dist/app/system.d.mts +3 -3
- package/dist/app.d.mts +3 -3
- package/dist/app.mjs +1 -1
- package/dist/assets.d.mts +1 -1
- package/dist/{commands-CMFdiDcg.d.mts → commands-CvwzXCxM.d.mts} +5 -5
- package/dist/control.d.mts +4 -4
- package/dist/{define-D27mfOhZ.d.mts → define-DRRs7A6i.d.mts} +2 -2
- package/dist/{headless-CRqMxzqv.d.mts → headless-CEwDAhgH.d.mts} +1 -1
- package/dist/{index-DFAjLwxi.d.mts → index-BQGifHXV.d.mts} +19 -7
- package/dist/index.d.mts +4 -4
- package/dist/index.mjs +1 -1
- package/dist/inspect.d.mts +6 -6
- package/dist/jsx-dev-runtime.d.mts +1 -1
- package/dist/jsx-runtime.d.mts +1 -1
- package/dist/{runtime-CbLZyDXj.d.mts → runtime-7RDFd4Ti.d.mts} +1 -1
- package/dist/{src-B2vblgmb.mjs → src-Cz-QeLTp.mjs} +1572 -684
- package/dist/testing.d.mts +4 -4
- package/dist/testing.mjs +1 -1
- package/dist/{types-H1FiWmKa.d.mts → types-B4DmdMle.d.mts} +4 -4
- package/dist/{types-54KzmAA6.d.mts → types-CP22WjU9.d.mts} +2 -2
- package/dist/{types-CGPWYbZO.d.mts → types-CoTkzbEB.d.mts} +2 -2
- package/dist/{types-C_dk-lXz.d.mts → types-DY_DRbGY.d.mts} +292 -6
- package/dist/{types-0Br0SjTB.d.mts → types-Dnh8LZxo.d.mts} +1 -1
- package/dist/{visual-DjTJ02c3.d.mts → visual-BcS4CTps.d.mts} +6 -6
- package/dist/visual.d.mts +1 -1
- package/llms.txt +1 -0
- package/package.json +1 -1
|
@@ -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 {
|
|
@@ -5759,7 +5759,7 @@ function enterBookmark(ctx, modules, bookmark, safe) {
|
|
|
5759
5759
|
* @returns `"stop"` when the runner was stopped, `"continue"` otherwise.
|
|
5760
5760
|
* @throws {Error} When the position names no node of the graph.
|
|
5761
5761
|
*/
|
|
5762
|
-
async function advance(ctx, modules, safe) {
|
|
5762
|
+
async function advance$1(ctx, modules, safe) {
|
|
5763
5763
|
const state = ctx.state.runner;
|
|
5764
5764
|
if (stopped(state)) return "stop";
|
|
5765
5765
|
const restoring = state.seam?.restoring;
|
|
@@ -5806,7 +5806,7 @@ async function runLoop(ctx, modules) {
|
|
|
5806
5806
|
recover: false
|
|
5807
5807
|
};
|
|
5808
5808
|
const loop = { running: true };
|
|
5809
|
-
while (loop.running) loop.running = await advance(ctx, modules, safe) === "continue";
|
|
5809
|
+
while (loop.running) loop.running = await advance$1(ctx, modules, safe) === "continue";
|
|
5810
5810
|
}
|
|
5811
5811
|
/**
|
|
5812
5812
|
* Restores a bookmark through the running loop: the active node is aborted with reason
|
|
@@ -24939,7 +24939,7 @@ function finishRun(warm) {
|
|
|
24939
24939
|
* @param pixi - The module that draws.
|
|
24940
24940
|
* @returns The tree, or `undefined` when it cannot be built.
|
|
24941
24941
|
*/
|
|
24942
|
-
function objectsOf(sctx, pixi) {
|
|
24942
|
+
function objectsOf$1(sctx, pixi) {
|
|
24943
24943
|
const { warm } = sctx.ctx.state.sync;
|
|
24944
24944
|
if (warm.objects !== void 0) return warm.objects;
|
|
24945
24945
|
try {
|
|
@@ -24967,7 +24967,7 @@ function warmStep(sctx) {
|
|
|
24967
24967
|
const { warm } = sctx.ctx.state.sync;
|
|
24968
24968
|
if (warm.head >= warm.queue.length) return false;
|
|
24969
24969
|
const pixi = sctx.deps.host.pixi();
|
|
24970
|
-
const objects = pixi === void 0 ? void 0 : objectsOf(sctx, pixi);
|
|
24970
|
+
const objects = pixi === void 0 ? void 0 : objectsOf$1(sctx, pixi);
|
|
24971
24971
|
if (pixi === void 0 || objects === void 0) return false;
|
|
24972
24972
|
const budget = sctx.ctx.config.warm;
|
|
24973
24973
|
let drawn = 0;
|
|
@@ -26026,6 +26026,1003 @@ function clearLayers(state) {
|
|
|
26026
26026
|
state.hudLayer = void 0;
|
|
26027
26027
|
}
|
|
26028
26028
|
//#endregion
|
|
26029
|
+
//#region src/plugins/renderer/sync/shape-path.ts
|
|
26030
|
+
/**
|
|
26031
|
+
* @file renderer/sync — the path of a `Shape`: a rectangle, a rounded rectangle or a triangle,
|
|
26032
|
+
* its fill, and its stroke, solid or dashed, or an arc, a stroke alone. A dashed stroke walks the
|
|
26033
|
+
* outline (straight edges, and rounded corners sampled as arcs) and strokes one open line per dash.
|
|
26034
|
+
* An arc runs on the circle inscribed in its box, from 12 o'clock clockwise.
|
|
26035
|
+
*/
|
|
26036
|
+
/** Chords per rounded corner of a dashed outline: under 0.3 u off the arc at a 48 u radius. */
|
|
26037
|
+
const CORNER_SAMPLES = 8;
|
|
26038
|
+
/** The gap between two dashes, as a fraction of the dash length. */
|
|
26039
|
+
const GAP_RATIO = .5;
|
|
26040
|
+
/** Segments of a full ring: a ring of 28 u across strays under 0.03 u off its circle. */
|
|
26041
|
+
const RING_SEGMENTS = 48;
|
|
26042
|
+
/** The fewest segments of an arc that draws: a sliver still bends. */
|
|
26043
|
+
const ARC_MIN_SEGMENTS = 2;
|
|
26044
|
+
/** 12 o'clock, where an arc starts: Pixi's angles run clockwise from 3 o'clock. */
|
|
26045
|
+
const TWELVE = -Math.PI / 2;
|
|
26046
|
+
/**
|
|
26047
|
+
* The corner radius the rectangle is drawn with: Pixi clamps it to half the shorter side.
|
|
26048
|
+
*
|
|
26049
|
+
* @param value - The shape value.
|
|
26050
|
+
* @returns The radius in reference units, 0 for square corners.
|
|
26051
|
+
* @example
|
|
26052
|
+
* ```ts
|
|
26053
|
+
* radiusOf({ ...Shape.defaults, w: 100, h: 20, radius: 40 }); // 10
|
|
26054
|
+
* ```
|
|
26055
|
+
*/
|
|
26056
|
+
function radiusOf(value) {
|
|
26057
|
+
return Math.max(0, Math.min(value.radius, value.w / 2, value.h / 2));
|
|
26058
|
+
}
|
|
26059
|
+
/**
|
|
26060
|
+
* The closed outline of a shape as points, clockwise from the top left (from the end of the top
|
|
26061
|
+
* left corner on a rounded rectangle); the last point repeats the first.
|
|
26062
|
+
*
|
|
26063
|
+
* @param value - The shape value.
|
|
26064
|
+
* @returns The points of the outline.
|
|
26065
|
+
* @example
|
|
26066
|
+
* ```ts
|
|
26067
|
+
* outlineOf({ ...Shape.defaults, kind: "triangle", w: 60, h: 40 });
|
|
26068
|
+
* // [{ x: 0, y: 0 }, { x: 60, y: 20 }, { x: 0, y: 40 }, { x: 0, y: 0 }]
|
|
26069
|
+
* ```
|
|
26070
|
+
*/
|
|
26071
|
+
function outlineOf(value) {
|
|
26072
|
+
const { w, h } = value;
|
|
26073
|
+
if (value.kind === "triangle") return [
|
|
26074
|
+
{
|
|
26075
|
+
x: 0,
|
|
26076
|
+
y: 0
|
|
26077
|
+
},
|
|
26078
|
+
{
|
|
26079
|
+
x: w,
|
|
26080
|
+
y: h / 2
|
|
26081
|
+
},
|
|
26082
|
+
{
|
|
26083
|
+
x: 0,
|
|
26084
|
+
y: h
|
|
26085
|
+
},
|
|
26086
|
+
{
|
|
26087
|
+
x: 0,
|
|
26088
|
+
y: 0
|
|
26089
|
+
}
|
|
26090
|
+
];
|
|
26091
|
+
const radius = radiusOf(value);
|
|
26092
|
+
if (radius === 0) return [
|
|
26093
|
+
{
|
|
26094
|
+
x: 0,
|
|
26095
|
+
y: 0
|
|
26096
|
+
},
|
|
26097
|
+
{
|
|
26098
|
+
x: w,
|
|
26099
|
+
y: 0
|
|
26100
|
+
},
|
|
26101
|
+
{
|
|
26102
|
+
x: w,
|
|
26103
|
+
y: h
|
|
26104
|
+
},
|
|
26105
|
+
{
|
|
26106
|
+
x: 0,
|
|
26107
|
+
y: h
|
|
26108
|
+
},
|
|
26109
|
+
{
|
|
26110
|
+
x: 0,
|
|
26111
|
+
y: 0
|
|
26112
|
+
}
|
|
26113
|
+
];
|
|
26114
|
+
const corners = [
|
|
26115
|
+
{
|
|
26116
|
+
edge: {
|
|
26117
|
+
x: w - radius,
|
|
26118
|
+
y: 0
|
|
26119
|
+
},
|
|
26120
|
+
center: {
|
|
26121
|
+
x: w - radius,
|
|
26122
|
+
y: radius
|
|
26123
|
+
}
|
|
26124
|
+
},
|
|
26125
|
+
{
|
|
26126
|
+
edge: {
|
|
26127
|
+
x: w,
|
|
26128
|
+
y: h - radius
|
|
26129
|
+
},
|
|
26130
|
+
center: {
|
|
26131
|
+
x: w - radius,
|
|
26132
|
+
y: h - radius
|
|
26133
|
+
}
|
|
26134
|
+
},
|
|
26135
|
+
{
|
|
26136
|
+
edge: {
|
|
26137
|
+
x: radius,
|
|
26138
|
+
y: h
|
|
26139
|
+
},
|
|
26140
|
+
center: {
|
|
26141
|
+
x: radius,
|
|
26142
|
+
y: h - radius
|
|
26143
|
+
}
|
|
26144
|
+
},
|
|
26145
|
+
{
|
|
26146
|
+
edge: {
|
|
26147
|
+
x: 0,
|
|
26148
|
+
y: radius
|
|
26149
|
+
},
|
|
26150
|
+
center: {
|
|
26151
|
+
x: radius,
|
|
26152
|
+
y: radius
|
|
26153
|
+
}
|
|
26154
|
+
}
|
|
26155
|
+
];
|
|
26156
|
+
const points = [{
|
|
26157
|
+
x: radius,
|
|
26158
|
+
y: 0
|
|
26159
|
+
}];
|
|
26160
|
+
for (const [index, { edge, center }] of corners.entries()) {
|
|
26161
|
+
const from = -Math.PI / 2 + index * Math.PI / 2;
|
|
26162
|
+
points.push(edge);
|
|
26163
|
+
for (let sample = 1; sample <= CORNER_SAMPLES; sample += 1) {
|
|
26164
|
+
const angle = from + sample * Math.PI / 2 / CORNER_SAMPLES;
|
|
26165
|
+
points.push({
|
|
26166
|
+
x: center.x + radius * Math.cos(angle),
|
|
26167
|
+
y: center.y + radius * Math.sin(angle)
|
|
26168
|
+
});
|
|
26169
|
+
}
|
|
26170
|
+
}
|
|
26171
|
+
return points;
|
|
26172
|
+
}
|
|
26173
|
+
/**
|
|
26174
|
+
* Adds a point to a line unless the line already ends there.
|
|
26175
|
+
*
|
|
26176
|
+
* @param line - The line.
|
|
26177
|
+
* @param point - The point.
|
|
26178
|
+
*/
|
|
26179
|
+
function extend(line, point) {
|
|
26180
|
+
const last = line.at(-1);
|
|
26181
|
+
if (last?.x !== point.x || last.y !== point.y) line.push(point);
|
|
26182
|
+
}
|
|
26183
|
+
/**
|
|
26184
|
+
* Cuts a polyline into dashes of `dash` length with gaps of half a dash, starting with a dash at
|
|
26185
|
+
* its first point. A dash bends round the corners it crosses; the last one may be shorter.
|
|
26186
|
+
*
|
|
26187
|
+
* @param outline - The points of the polyline.
|
|
26188
|
+
* @param dash - The dash length, above 0.
|
|
26189
|
+
* @returns One open line per dash.
|
|
26190
|
+
* @example
|
|
26191
|
+
* ```ts
|
|
26192
|
+
* dashesOf([{ x: 0, y: 0 }, { x: 40, y: 0 }], 10);
|
|
26193
|
+
* // [[{ x: 0, y: 0 }, { x: 10, y: 0 }], [{ x: 15, y: 0 }, { x: 25, y: 0 }], [{ x: 30, y: 0 }, { x: 40, y: 0 }]]
|
|
26194
|
+
* ```
|
|
26195
|
+
*/
|
|
26196
|
+
function dashesOf(outline, dash) {
|
|
26197
|
+
const first = outline[0];
|
|
26198
|
+
if (first === void 0) return [];
|
|
26199
|
+
const dashes = [];
|
|
26200
|
+
let line = [first];
|
|
26201
|
+
let left = dash;
|
|
26202
|
+
for (let index = 1; index < outline.length; index += 1) {
|
|
26203
|
+
let from = outline[index - 1] ?? first;
|
|
26204
|
+
const to = outline[index] ?? first;
|
|
26205
|
+
let length = Math.hypot(to.x - from.x, to.y - from.y);
|
|
26206
|
+
while (length > left) {
|
|
26207
|
+
from = {
|
|
26208
|
+
x: from.x + (to.x - from.x) * left / length,
|
|
26209
|
+
y: from.y + (to.y - from.y) * left / length
|
|
26210
|
+
};
|
|
26211
|
+
length -= left;
|
|
26212
|
+
if (line === void 0) {
|
|
26213
|
+
line = [from];
|
|
26214
|
+
left = dash;
|
|
26215
|
+
} else {
|
|
26216
|
+
extend(line, from);
|
|
26217
|
+
dashes.push(line);
|
|
26218
|
+
line = void 0;
|
|
26219
|
+
left = dash * GAP_RATIO;
|
|
26220
|
+
}
|
|
26221
|
+
}
|
|
26222
|
+
left -= length;
|
|
26223
|
+
if (line !== void 0) extend(line, to);
|
|
26224
|
+
}
|
|
26225
|
+
if (line !== void 0 && line.length > 1) dashes.push(line);
|
|
26226
|
+
return dashes;
|
|
26227
|
+
}
|
|
26228
|
+
/**
|
|
26229
|
+
* 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
|
|
26230
|
+
* 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
|
|
26231
|
+
* clips its children to. A fill-only shape is drawn white and coloured by the object's tint, so a colour change draws
|
|
26232
|
+
* no path (spec 26 R2.1).
|
|
26233
|
+
*
|
|
26234
|
+
* @param object - The graphics to draw on.
|
|
26235
|
+
* @param value - The shape value.
|
|
26236
|
+
* @param fillAlpha - The alpha of the fill: the value's own for the shape, 1 for a clip mask.
|
|
26237
|
+
* @param color - The colour of the fill: the value's own by default, white for a tinted shape.
|
|
26238
|
+
*/
|
|
26239
|
+
function drawShapePath(object, value, fillAlpha, color = value.fill) {
|
|
26240
|
+
object.clear();
|
|
26241
|
+
if (value.kind === "triangle") object.moveTo(0, 0).lineTo(value.w, value.h / 2).lineTo(0, value.h).closePath();
|
|
26242
|
+
else if (value.kind === "rect" && value.radius > 0) object.roundRect(0, 0, value.w, value.h, value.radius);
|
|
26243
|
+
else object.rect(0, 0, value.w, value.h);
|
|
26244
|
+
if (fillAlpha <= 0) return;
|
|
26245
|
+
object.fill(fillAlpha < 1 ? {
|
|
26246
|
+
color,
|
|
26247
|
+
alpha: fillAlpha
|
|
26248
|
+
} : { color });
|
|
26249
|
+
}
|
|
26250
|
+
/**
|
|
26251
|
+
* Strokes the outline drawn by `drawShapePath`: solid over the same path, or, with a `dash`, as
|
|
26252
|
+
* one open line per dash on a new path. Nothing without a stroke width.
|
|
26253
|
+
*
|
|
26254
|
+
* @param object - The graphics the shape was drawn on.
|
|
26255
|
+
* @param value - The shape value.
|
|
26256
|
+
*/
|
|
26257
|
+
function strokeShape(object, value) {
|
|
26258
|
+
if (value.strokeWidth <= 0) return;
|
|
26259
|
+
const style = {
|
|
26260
|
+
color: value.stroke,
|
|
26261
|
+
width: value.strokeWidth
|
|
26262
|
+
};
|
|
26263
|
+
if (value.dash <= 0) {
|
|
26264
|
+
object.stroke(style);
|
|
26265
|
+
return;
|
|
26266
|
+
}
|
|
26267
|
+
object.beginPath();
|
|
26268
|
+
for (const [start, ...rest] of dashesOf(outlineOf(value), value.dash)) {
|
|
26269
|
+
if (start === void 0) continue;
|
|
26270
|
+
object.moveTo(start.x, start.y);
|
|
26271
|
+
for (const point of rest) object.lineTo(point.x, point.y);
|
|
26272
|
+
}
|
|
26273
|
+
object.stroke(style);
|
|
26274
|
+
}
|
|
26275
|
+
/**
|
|
26276
|
+
* The share of the circle an arc sweeps, clamped to 0..1: anything not above 0, `NaN` too, is 0; a whole ring when
|
|
26277
|
+
* the value leaves it out.
|
|
26278
|
+
*
|
|
26279
|
+
* @param value - The shape value.
|
|
26280
|
+
* @returns The share.
|
|
26281
|
+
* @example
|
|
26282
|
+
* ```ts
|
|
26283
|
+
* sweepOf({ ...Shape.defaults, kind: "arc", sweep: 1.4 }); // 1
|
|
26284
|
+
* ```
|
|
26285
|
+
*/
|
|
26286
|
+
function sweepOf(value) {
|
|
26287
|
+
const sweep = value.sweep ?? arcDefaults.sweep;
|
|
26288
|
+
if (Number.isNaN(sweep) || sweep <= 0) return 0;
|
|
26289
|
+
return Math.min(sweep, 1);
|
|
26290
|
+
}
|
|
26291
|
+
/**
|
|
26292
|
+
* The radius of the circle an arc runs on: inscribed in the shorter side of its box, inside its stroke.
|
|
26293
|
+
*
|
|
26294
|
+
* @param value - The shape value.
|
|
26295
|
+
* @returns The radius in reference units; 0 or below when the stroke leaves no circle.
|
|
26296
|
+
* @example
|
|
26297
|
+
* ```ts
|
|
26298
|
+
* arcRadiusOf({ ...Shape.defaults, kind: "arc", w: 28, h: 40, strokeWidth: 2 }); // 13
|
|
26299
|
+
* ```
|
|
26300
|
+
*/
|
|
26301
|
+
function arcRadiusOf(value) {
|
|
26302
|
+
return (Math.min(value.w, value.h) - value.strokeWidth) / 2;
|
|
26303
|
+
}
|
|
26304
|
+
/**
|
|
26305
|
+
* Tells whether an arc draws nothing: no stroke width, no sweep, or no circle inside its stroke.
|
|
26306
|
+
*
|
|
26307
|
+
* @param value - The shape value, an arc.
|
|
26308
|
+
* @returns True for an arc whose `Graphics` would hold no instruction.
|
|
26309
|
+
* @example
|
|
26310
|
+
* ```ts
|
|
26311
|
+
* isEmptyArc({ ...Shape.defaults, kind: "arc", w: 28, h: 28, strokeWidth: 2, sweep: 0 }); // true
|
|
26312
|
+
* ```
|
|
26313
|
+
*/
|
|
26314
|
+
function isEmptyArc(value) {
|
|
26315
|
+
return value.strokeWidth <= 0 || sweepOf(value) <= 0 || arcRadiusOf(value) <= 0;
|
|
26316
|
+
}
|
|
26317
|
+
/**
|
|
26318
|
+
* Strokes an arc: from 12 o'clock clockwise over its share of the circle, in segments as many as its angle needs (48
|
|
26319
|
+
* 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
|
|
26320
|
+
* only when the shape changed: never rebuilt per frame.
|
|
26321
|
+
*
|
|
26322
|
+
* @param object - The graphics, cleared.
|
|
26323
|
+
* @param value - The shape value, an arc.
|
|
26324
|
+
*/
|
|
26325
|
+
function strokeArc(object, value) {
|
|
26326
|
+
if (isEmptyArc(value)) return;
|
|
26327
|
+
const sweep = sweepOf(value);
|
|
26328
|
+
const radius = arcRadiusOf(value);
|
|
26329
|
+
const ring = sweep >= 1;
|
|
26330
|
+
const segments = Math.max(ARC_MIN_SEGMENTS, Math.ceil(sweep * RING_SEGMENTS));
|
|
26331
|
+
const step = sweep * 2 * Math.PI / segments;
|
|
26332
|
+
const centreX = value.w / 2;
|
|
26333
|
+
const centreY = value.h / 2;
|
|
26334
|
+
const last = ring ? segments - 1 : segments;
|
|
26335
|
+
object.moveTo(centreX, centreY - radius);
|
|
26336
|
+
for (let index = 1; index <= last; index += 1) {
|
|
26337
|
+
const angle = TWELVE + index * step;
|
|
26338
|
+
object.lineTo(centreX + radius * Math.cos(angle), centreY + radius * Math.sin(angle));
|
|
26339
|
+
}
|
|
26340
|
+
if (ring) {
|
|
26341
|
+
object.closePath();
|
|
26342
|
+
object.stroke({
|
|
26343
|
+
color: value.stroke,
|
|
26344
|
+
width: value.strokeWidth
|
|
26345
|
+
});
|
|
26346
|
+
return;
|
|
26347
|
+
}
|
|
26348
|
+
object.stroke({
|
|
26349
|
+
color: value.stroke,
|
|
26350
|
+
width: value.strokeWidth,
|
|
26351
|
+
cap: value.cap ?? arcDefaults.cap
|
|
26352
|
+
});
|
|
26353
|
+
}
|
|
26354
|
+
/**
|
|
26355
|
+
* 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
|
|
26356
|
+
* own fill alpha, and its stroke. The one call that draws a shape's visual.
|
|
26357
|
+
*
|
|
26358
|
+
* @param object - The graphics.
|
|
26359
|
+
* @param value - The shape value.
|
|
26360
|
+
* @param color - The colour of the fill: white for a tinted shape, the value's own otherwise.
|
|
26361
|
+
*/
|
|
26362
|
+
function paintShape(object, value, color) {
|
|
26363
|
+
if (value.kind === "arc") {
|
|
26364
|
+
object.clear();
|
|
26365
|
+
strokeArc(object, value);
|
|
26366
|
+
return;
|
|
26367
|
+
}
|
|
26368
|
+
drawShapePath(object, value, value.fillAlpha, color);
|
|
26369
|
+
strokeShape(object, value);
|
|
26370
|
+
}
|
|
26371
|
+
//#endregion
|
|
26372
|
+
//#region src/plugins/renderer/sync/textures.ts
|
|
26373
|
+
/** The colour a missing texture is drawn in. */
|
|
26374
|
+
const PLACEHOLDER_TINT = 16711935;
|
|
26375
|
+
/**
|
|
26376
|
+
* Creates an empty set. It lives in its own non-exported function because lint rule L5 refuses a
|
|
26377
|
+
* collection built inside an exported declaration.
|
|
26378
|
+
*
|
|
26379
|
+
* @returns An empty set.
|
|
26380
|
+
*/
|
|
26381
|
+
function emptySet$5() {
|
|
26382
|
+
return /* @__PURE__ */ new Set();
|
|
26383
|
+
}
|
|
26384
|
+
/**
|
|
26385
|
+
* Asks the providers from the newest to the oldest. The first answer wins, so a bundle loaded
|
|
26386
|
+
* later shadows an older one.
|
|
26387
|
+
*
|
|
26388
|
+
* @param sctx - Domain context of the sync module.
|
|
26389
|
+
* @param key - The asset key.
|
|
26390
|
+
* @returns The texture, or `undefined` when nothing answered.
|
|
26391
|
+
*/
|
|
26392
|
+
function resolveTexture(sctx, key) {
|
|
26393
|
+
const providers = sctx.ctx.state.sync.providers;
|
|
26394
|
+
for (let index = providers.length - 1; index >= 0; index -= 1) {
|
|
26395
|
+
const texture = providers[index]?.(key);
|
|
26396
|
+
if (texture !== void 0) return texture;
|
|
26397
|
+
}
|
|
26398
|
+
}
|
|
26399
|
+
/**
|
|
26400
|
+
* The 1x1 white texture the placeholder is drawn with.
|
|
26401
|
+
*
|
|
26402
|
+
* @param sctx - Domain context of the sync module.
|
|
26403
|
+
* @returns The texture, or `undefined` while inert.
|
|
26404
|
+
*/
|
|
26405
|
+
function placeholderTexture(sctx) {
|
|
26406
|
+
return sctx.deps.host.pixi()?.Texture.WHITE;
|
|
26407
|
+
}
|
|
26408
|
+
/**
|
|
26409
|
+
* Reports a key no provider answers. One key warns once, however many views use it.
|
|
26410
|
+
*
|
|
26411
|
+
* @param sctx - Domain context of the sync module.
|
|
26412
|
+
* @param key - The asset key.
|
|
26413
|
+
*/
|
|
26414
|
+
function warnMissing(sctx, key) {
|
|
26415
|
+
const warned = sctx.ctx.state.sync.warned;
|
|
26416
|
+
if (key === "" || warned.has(key)) return;
|
|
26417
|
+
warned.add(key);
|
|
26418
|
+
sctx.ctx.log.warn("renderer: no texture for asset key", { key });
|
|
26419
|
+
}
|
|
26420
|
+
/**
|
|
26421
|
+
* Remembers that an entity draws a key, so `invalidate` finds it again.
|
|
26422
|
+
*
|
|
26423
|
+
* @param state - The sync branch of the plugin state.
|
|
26424
|
+
* @param entity - The entity.
|
|
26425
|
+
* @param key - The asset key.
|
|
26426
|
+
*/
|
|
26427
|
+
function trackKey(state, entity, key) {
|
|
26428
|
+
const users = state.byKey.get(key) ?? emptySet$5();
|
|
26429
|
+
users.add(entity);
|
|
26430
|
+
state.byKey.set(key, users);
|
|
26431
|
+
}
|
|
26432
|
+
/**
|
|
26433
|
+
* Forgets that an entity draws a key.
|
|
26434
|
+
*
|
|
26435
|
+
* @param state - The sync branch of the plugin state.
|
|
26436
|
+
* @param entity - The entity.
|
|
26437
|
+
* @param key - The asset key.
|
|
26438
|
+
*/
|
|
26439
|
+
function untrackKey(state, entity, key) {
|
|
26440
|
+
const users = state.byKey.get(key);
|
|
26441
|
+
if (users === void 0) return;
|
|
26442
|
+
users.delete(entity);
|
|
26443
|
+
if (users.size === 0) state.byKey.delete(key);
|
|
26444
|
+
}
|
|
26445
|
+
/**
|
|
26446
|
+
* The Pixi module of a renderer that draws. A texture made while it does not would be lost with
|
|
26447
|
+
* the device, so the member that asked throws.
|
|
26448
|
+
*
|
|
26449
|
+
* @param sctx - Domain context of the sync module.
|
|
26450
|
+
* @param member - The `textures` member that asked, for the message.
|
|
26451
|
+
* @returns The Pixi module.
|
|
26452
|
+
* @throws {Error} When the renderer does not draw.
|
|
26453
|
+
*/
|
|
26454
|
+
function drawingPixi(sctx, member) {
|
|
26455
|
+
const pixi = sctx.deps.host.pixi();
|
|
26456
|
+
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.`);
|
|
26457
|
+
return pixi;
|
|
26458
|
+
}
|
|
26459
|
+
/**
|
|
26460
|
+
* The default borders of a texture, from the four numbers `nine` gives.
|
|
26461
|
+
*
|
|
26462
|
+
* @param nine - Left, top, right and bottom in pixels.
|
|
26463
|
+
* @returns The borders, as Pixi reads them.
|
|
26464
|
+
* @example
|
|
26465
|
+
* ```ts
|
|
26466
|
+
* defaultBordersOf([24, 20, 16, 12]); // { left: 24, top: 20, right: 16, bottom: 12 }
|
|
26467
|
+
* ```
|
|
26468
|
+
*/
|
|
26469
|
+
function defaultBordersOf(nine) {
|
|
26470
|
+
return {
|
|
26471
|
+
left: nine[0],
|
|
26472
|
+
top: nine[1],
|
|
26473
|
+
right: nine[2],
|
|
26474
|
+
bottom: nine[3]
|
|
26475
|
+
};
|
|
26476
|
+
}
|
|
26477
|
+
/**
|
|
26478
|
+
* Makes a Pixi texture out of a decoded image, on behalf of `assets`.
|
|
26479
|
+
*
|
|
26480
|
+
* @param sctx - Domain context of the sync module.
|
|
26481
|
+
* @param image - The decoded image.
|
|
26482
|
+
* @param options - Nine-slice borders in pixels, and whether Pixi generates the mip chain.
|
|
26483
|
+
* @returns The new texture.
|
|
26484
|
+
* @throws {Error} When the renderer does not draw.
|
|
26485
|
+
*/
|
|
26486
|
+
function createTexture(sctx, image, options) {
|
|
26487
|
+
const pixi = drawingPixi(sctx, "create");
|
|
26488
|
+
const base = options?.mips === true ? pixi.Texture.from({
|
|
26489
|
+
resource: image,
|
|
26490
|
+
autoGenerateMipmaps: true
|
|
26491
|
+
}) : pixi.Texture.from(image);
|
|
26492
|
+
const nine = options?.nine;
|
|
26493
|
+
if (nine === void 0) return base;
|
|
26494
|
+
const bordered = new pixi.Texture({
|
|
26495
|
+
source: base.source,
|
|
26496
|
+
defaultBorders: defaultBordersOf(nine)
|
|
26497
|
+
});
|
|
26498
|
+
base.destroy(false);
|
|
26499
|
+
return bordered;
|
|
26500
|
+
}
|
|
26501
|
+
/**
|
|
26502
|
+
* Tells whether a frame lies inside a page of the given size. A negative number, or one that is
|
|
26503
|
+
* not a number, lies outside.
|
|
26504
|
+
*
|
|
26505
|
+
* @param frame - The frame in page pixels.
|
|
26506
|
+
* @param page - The size of the page.
|
|
26507
|
+
* @param page.width - Width of the page in pixels.
|
|
26508
|
+
* @param page.height - Height of the page in pixels.
|
|
26509
|
+
* @returns True when the whole frame is on the page.
|
|
26510
|
+
* @example
|
|
26511
|
+
* ```ts
|
|
26512
|
+
* frameFits({ x: 256, y: 384, width: 256, height: 128 }, { width: 512, height: 512 }); // true
|
|
26513
|
+
* ```
|
|
26514
|
+
*/
|
|
26515
|
+
function frameFits(frame, page) {
|
|
26516
|
+
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;
|
|
26517
|
+
}
|
|
26518
|
+
/**
|
|
26519
|
+
* Cuts a texture out of an atlas page, on behalf of `assets`. The slice shares the page's source,
|
|
26520
|
+
* its frame is offset by the page's own frame, and it is marked as a slice so `destroyTexture`
|
|
26521
|
+
* frees only the wrapper.
|
|
26522
|
+
*
|
|
26523
|
+
* @param sctx - Domain context of the sync module.
|
|
26524
|
+
* @param page - The page texture.
|
|
26525
|
+
* @param frame - The frame in page pixels.
|
|
26526
|
+
* @param options - Nine-slice borders, in pixels.
|
|
26527
|
+
* @returns The slice.
|
|
26528
|
+
* @throws {Error} When the renderer does not draw, and when the frame does not fit in the page.
|
|
26529
|
+
*/
|
|
26530
|
+
function sliceTexture(sctx, page, frame, options) {
|
|
26531
|
+
const pixi = drawingPixi(sctx, "slice");
|
|
26532
|
+
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".`);
|
|
26533
|
+
const nine = options?.nine;
|
|
26534
|
+
const slice = new pixi.Texture({
|
|
26535
|
+
source: page.source,
|
|
26536
|
+
frame: new pixi.Rectangle(page.frame.x + frame.x, page.frame.y + frame.y, frame.width, frame.height),
|
|
26537
|
+
...nine === void 0 ? {} : { defaultBorders: defaultBordersOf(nine) }
|
|
26538
|
+
});
|
|
26539
|
+
sctx.ctx.state.sync.slices.add(slice);
|
|
26540
|
+
return slice;
|
|
26541
|
+
}
|
|
26542
|
+
/**
|
|
26543
|
+
* The cache key of one crop: the asset key and the box it covers.
|
|
26544
|
+
*
|
|
26545
|
+
* @param key - The asset key.
|
|
26546
|
+
* @param width - Width of the box.
|
|
26547
|
+
* @param height - Height of the box.
|
|
26548
|
+
* @returns The key of the crop.
|
|
26549
|
+
* @example
|
|
26550
|
+
* ```ts
|
|
26551
|
+
* frameKeyOf("board.bg", 1080, 1920); // "board.bg@1080x1920"
|
|
26552
|
+
* ```
|
|
26553
|
+
*/
|
|
26554
|
+
function frameKeyOf(key, width, height) {
|
|
26555
|
+
return `${key}@${width}x${height}`;
|
|
26556
|
+
}
|
|
26557
|
+
/**
|
|
26558
|
+
* The texture a `"cover"` sprite draws: the part of `base` inside `crop`, sharing its source. One
|
|
26559
|
+
* crop per asset key and box, shared by every entity that shows it; the entity is added to its
|
|
26560
|
+
* users. A key that answers a new base has been invalidated, so every view of it resolves again
|
|
26561
|
+
* in this pass and joins the new crop; the old crop goes at once.
|
|
26562
|
+
*
|
|
26563
|
+
* @param sctx - Domain context of the sync module.
|
|
26564
|
+
* @param entity - The entity that shows the crop.
|
|
26565
|
+
* @param key - The asset key the base was resolved for.
|
|
26566
|
+
* @param base - The texture the key resolves to.
|
|
26567
|
+
* @param box - The box the sprite covers, which names the crop.
|
|
26568
|
+
* @param box.width - Width of the box.
|
|
26569
|
+
* @param box.height - Height of the box.
|
|
26570
|
+
* @param crop - The part of the base to show, in texture pixels.
|
|
26571
|
+
* @returns The crop and its `frames` key, or the base and `""` while inert.
|
|
26572
|
+
*/
|
|
26573
|
+
function coverTexture(sctx, entity, key, base, box, crop) {
|
|
26574
|
+
const frames = sctx.ctx.state.sync.frames;
|
|
26575
|
+
const frameKey = frameKeyOf(key, box.width, box.height);
|
|
26576
|
+
const cached = frames.get(frameKey);
|
|
26577
|
+
if (cached?.base === base) {
|
|
26578
|
+
cached.users.add(entity);
|
|
26579
|
+
return {
|
|
26580
|
+
texture: cached.texture,
|
|
26581
|
+
frameKey
|
|
26582
|
+
};
|
|
26583
|
+
}
|
|
26584
|
+
const pixi = sctx.deps.host.pixi();
|
|
26585
|
+
if (pixi === void 0) return {
|
|
26586
|
+
texture: base,
|
|
26587
|
+
frameKey: ""
|
|
26588
|
+
};
|
|
26589
|
+
if (cached !== void 0) cached.texture.destroy(false);
|
|
26590
|
+
const texture = new pixi.Texture({
|
|
26591
|
+
source: base.source,
|
|
26592
|
+
frame: new pixi.Rectangle(base.frame.x + crop.x, base.frame.y + crop.y, crop.width, crop.height)
|
|
26593
|
+
});
|
|
26594
|
+
const users = emptySet$5();
|
|
26595
|
+
users.add(entity);
|
|
26596
|
+
frames.set(frameKey, {
|
|
26597
|
+
base,
|
|
26598
|
+
texture,
|
|
26599
|
+
users
|
|
26600
|
+
});
|
|
26601
|
+
return {
|
|
26602
|
+
texture,
|
|
26603
|
+
frameKey
|
|
26604
|
+
};
|
|
26605
|
+
}
|
|
26606
|
+
/**
|
|
26607
|
+
* Lets an entity go of the crop it showed. The last user frees the crop, keeping the source it
|
|
26608
|
+
* shares with its base.
|
|
26609
|
+
*
|
|
26610
|
+
* @param state - The sync branch of the plugin state.
|
|
26611
|
+
* @param entity - The entity that no longer shows it.
|
|
26612
|
+
* @param frameKey - The `frames` key of the crop; `""` for none.
|
|
26613
|
+
*/
|
|
26614
|
+
function releaseCover(state, entity, frameKey) {
|
|
26615
|
+
const frame = state.frames.get(frameKey);
|
|
26616
|
+
if (frame === void 0) return;
|
|
26617
|
+
frame.users.delete(entity);
|
|
26618
|
+
if (frame.users.size > 0) return;
|
|
26619
|
+
frame.texture.destroy(false);
|
|
26620
|
+
state.frames.delete(frameKey);
|
|
26621
|
+
}
|
|
26622
|
+
/**
|
|
26623
|
+
* Frees the crops cut from one texture, keeping the source they share with it.
|
|
26624
|
+
*
|
|
26625
|
+
* @param state - The sync branch of the plugin state.
|
|
26626
|
+
* @param base - The texture that goes.
|
|
26627
|
+
*/
|
|
26628
|
+
function releaseFrames$1(state, base) {
|
|
26629
|
+
for (const [frameKey, frame] of state.frames) {
|
|
26630
|
+
if (frame.base !== base) continue;
|
|
26631
|
+
frame.texture.destroy(false);
|
|
26632
|
+
state.frames.delete(frameKey);
|
|
26633
|
+
}
|
|
26634
|
+
}
|
|
26635
|
+
/**
|
|
26636
|
+
* Frees every crop. Works on the state alone, because `onStop` has no context.
|
|
26637
|
+
*
|
|
26638
|
+
* @param state - The sync branch of the plugin state.
|
|
26639
|
+
*/
|
|
26640
|
+
function clearFrames(state) {
|
|
26641
|
+
for (const frame of state.frames.values()) frame.texture.destroy(false);
|
|
26642
|
+
state.frames.clear();
|
|
26643
|
+
}
|
|
26644
|
+
/**
|
|
26645
|
+
* Frees a texture, its source and the crops cut from it. A slice keeps the source: it belongs to
|
|
26646
|
+
* the page, which frees it. A source that goes lets go of its `change` listeners first, the ones
|
|
26647
|
+
* of its style too, so no bind group Pixi still keeps is told. A texture destroyed twice is a
|
|
26648
|
+
* no-op.
|
|
26649
|
+
*
|
|
26650
|
+
* @param state - The sync branch of the plugin state.
|
|
26651
|
+
* @param texture - The texture to free.
|
|
26652
|
+
*/
|
|
26653
|
+
function destroyTexture(state, texture) {
|
|
26654
|
+
releaseFrames$1(state, texture);
|
|
26655
|
+
if (texture.destroyed) return;
|
|
26656
|
+
if (state.slices.has(texture)) {
|
|
26657
|
+
texture.destroy(false);
|
|
26658
|
+
return;
|
|
26659
|
+
}
|
|
26660
|
+
texture.source.style.removeAllListeners("change");
|
|
26661
|
+
texture.source.removeAllListeners("change");
|
|
26662
|
+
texture.destroy(true);
|
|
26663
|
+
}
|
|
26664
|
+
//#endregion
|
|
26665
|
+
//#region src/plugins/renderer/sync/leave.ts
|
|
26666
|
+
/** Label of the container the look hangs in, last on the stage. */
|
|
26667
|
+
const TOP_LABEL = "renderer.leave";
|
|
26668
|
+
/** Clock milliseconds of one turn of the loader. */
|
|
26669
|
+
const TURN_MS = 1e3;
|
|
26670
|
+
/** A whole turn, in radians. */
|
|
26671
|
+
const TURN$1 = 2 * Math.PI;
|
|
26672
|
+
/** The engine's ring: three quarters of a white circle 96 u across, 10 u wide, round ends, turned about its centre. */
|
|
26673
|
+
const RING = Object.freeze({
|
|
26674
|
+
kind: "arc",
|
|
26675
|
+
w: 96,
|
|
26676
|
+
h: 96,
|
|
26677
|
+
fill: 16777215,
|
|
26678
|
+
fillAlpha: 0,
|
|
26679
|
+
alpha: 1,
|
|
26680
|
+
radius: 0,
|
|
26681
|
+
stroke: 16777215,
|
|
26682
|
+
strokeWidth: 10,
|
|
26683
|
+
dash: 0,
|
|
26684
|
+
clip: false,
|
|
26685
|
+
sweep: .75,
|
|
26686
|
+
cap: "round"
|
|
26687
|
+
});
|
|
26688
|
+
/**
|
|
26689
|
+
* Makes the engine's objects of the picture, once per application: the top container with the veil and the frame, and
|
|
26690
|
+
* in the frame the holder of a lifted layer, the ring and the spinner. Nothing hangs on the stage yet.
|
|
26691
|
+
*
|
|
26692
|
+
* @param sctx - Domain context of the sync module.
|
|
26693
|
+
* @returns The objects, or `undefined` while nothing draws (inert, a lost device).
|
|
26694
|
+
*/
|
|
26695
|
+
function objectsOf(sctx) {
|
|
26696
|
+
const leave = sctx.ctx.state.sync.leave;
|
|
26697
|
+
if (leave.objects !== void 0) return leave.objects;
|
|
26698
|
+
const pixi = sctx.deps.host.pixi();
|
|
26699
|
+
if (pixi === void 0 || sctx.deps.host.stage() === void 0) return void 0;
|
|
26700
|
+
const top = new pixi.Container();
|
|
26701
|
+
const veil = new pixi.Sprite(pixi.Texture.WHITE);
|
|
26702
|
+
const frame = new pixi.Container();
|
|
26703
|
+
const held = new pixi.Container();
|
|
26704
|
+
const ring = new pixi.Graphics();
|
|
26705
|
+
const spinner = new pixi.Sprite(pixi.Texture.WHITE);
|
|
26706
|
+
top.label = TOP_LABEL;
|
|
26707
|
+
veil.label = `${TOP_LABEL}.cover`;
|
|
26708
|
+
veil.anchor.set(0, 0);
|
|
26709
|
+
held.label = `${TOP_LABEL}.layer`;
|
|
26710
|
+
ring.label = `${TOP_LABEL}.ring`;
|
|
26711
|
+
paintShape(ring, RING, RING.stroke);
|
|
26712
|
+
ring.pivot.set(RING.w / 2, RING.h / 2);
|
|
26713
|
+
spinner.label = `${TOP_LABEL}.texture`;
|
|
26714
|
+
spinner.anchor.set(.5, .5);
|
|
26715
|
+
frame.addChild(held);
|
|
26716
|
+
frame.addChild(ring);
|
|
26717
|
+
frame.addChild(spinner);
|
|
26718
|
+
top.addChild(veil);
|
|
26719
|
+
top.addChild(frame);
|
|
26720
|
+
leave.objects = {
|
|
26721
|
+
top,
|
|
26722
|
+
veil,
|
|
26723
|
+
frame,
|
|
26724
|
+
held,
|
|
26725
|
+
ring,
|
|
26726
|
+
spinner
|
|
26727
|
+
};
|
|
26728
|
+
return leave.objects;
|
|
26729
|
+
}
|
|
26730
|
+
/**
|
|
26731
|
+
* 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
|
|
26732
|
+
* no provider answers is warned once and draws the ring.
|
|
26733
|
+
*
|
|
26734
|
+
* @param sctx - Domain context of the sync module.
|
|
26735
|
+
* @param look - The look being shown.
|
|
26736
|
+
* @returns The loader's kind, and the texture of a `"texture"` loader.
|
|
26737
|
+
*/
|
|
26738
|
+
function loaderOf$1(sctx, look) {
|
|
26739
|
+
const loader = look.loader;
|
|
26740
|
+
if (loader === void 0) return { loader: "none" };
|
|
26741
|
+
if (loader.texture === void 0) return { loader: loader.due === void 0 ? "ring" : "layer" };
|
|
26742
|
+
const texture = resolveTexture(sctx, loader.texture);
|
|
26743
|
+
if (texture !== void 0) return {
|
|
26744
|
+
loader: "texture",
|
|
26745
|
+
texture
|
|
26746
|
+
};
|
|
26747
|
+
warnMissing(sctx, loader.texture);
|
|
26748
|
+
return { loader: "ring" };
|
|
26749
|
+
}
|
|
26750
|
+
/**
|
|
26751
|
+
* Puts a layer the look drew above the veil back under the root, at the place of its layer: the layer list of the
|
|
26752
|
+
* scene orders the root's children. A layer the list of a swap took back already, or let go, is left alone.
|
|
26753
|
+
*
|
|
26754
|
+
* @param sctx - Domain context of the sync module.
|
|
26755
|
+
* @param run - The look whose layer goes back.
|
|
26756
|
+
*/
|
|
26757
|
+
function unlift(sctx, run) {
|
|
26758
|
+
const lifted = run.lifted;
|
|
26759
|
+
if (lifted === void 0) return;
|
|
26760
|
+
delete run.lifted;
|
|
26761
|
+
const state = sctx.ctx.state.sync;
|
|
26762
|
+
if (lifted.parent !== state.leave.objects?.held) return;
|
|
26763
|
+
const name = run.layer;
|
|
26764
|
+
const at = state.layerList?.findIndex((spec) => spec.name === name) ?? -1;
|
|
26765
|
+
const root = state.root;
|
|
26766
|
+
state.passesVersion += 1;
|
|
26767
|
+
if (root === void 0 || at === -1) {
|
|
26768
|
+
detach$1(lifted);
|
|
26769
|
+
return;
|
|
26770
|
+
}
|
|
26771
|
+
root.addChildAt(lifted, Math.min(at, root.children.length));
|
|
26772
|
+
}
|
|
26773
|
+
/**
|
|
26774
|
+
* Takes the look down at once: its layer goes back, its objects leave the stage and are kept for the next look, and the
|
|
26775
|
+
* spinner lets the game's texture go. A frame is asked for, so the screen without it is drawn.
|
|
26776
|
+
*
|
|
26777
|
+
* @param sctx - Domain context of the sync module.
|
|
26778
|
+
*/
|
|
26779
|
+
function takeDown(sctx) {
|
|
26780
|
+
const leave = sctx.ctx.state.sync.leave;
|
|
26781
|
+
const run = leave.run;
|
|
26782
|
+
if (run !== void 0) unlift(sctx, run);
|
|
26783
|
+
delete leave.run;
|
|
26784
|
+
const objects = leave.objects;
|
|
26785
|
+
if (objects !== void 0) {
|
|
26786
|
+
detach$1(objects.top);
|
|
26787
|
+
const white = placeholderTexture(sctx);
|
|
26788
|
+
if (white !== void 0) objects.spinner.texture = white;
|
|
26789
|
+
}
|
|
26790
|
+
sctx.deps.demand.request();
|
|
26791
|
+
}
|
|
26792
|
+
/**
|
|
26793
|
+
* Moves the veil's level on the clock: up to 1 over `inMs` before the swap, down to 0 over `outMs` after it.
|
|
26794
|
+
*
|
|
26795
|
+
* @param run - The running look.
|
|
26796
|
+
* @param now - The clock now, ms.
|
|
26797
|
+
* @returns True when the level moved: the frame is drawn.
|
|
26798
|
+
*/
|
|
26799
|
+
function advance(run, now) {
|
|
26800
|
+
const cover = run.look.cover;
|
|
26801
|
+
const elapsed = Math.max(0, now - run.steppedAt);
|
|
26802
|
+
const before = run.level;
|
|
26803
|
+
run.steppedAt = now;
|
|
26804
|
+
if (cover === void 0) return false;
|
|
26805
|
+
if (run.landed) run.level = cover.outMs > 0 ? Math.max(0, before - elapsed / cover.outMs) : 0;
|
|
26806
|
+
else run.level = cover.inMs > 0 ? Math.min(1, before + elapsed / cover.inMs) : 1;
|
|
26807
|
+
return run.level !== before;
|
|
26808
|
+
}
|
|
26809
|
+
/**
|
|
26810
|
+
* Hangs the look last on the stage and over the canvas as it stands: the veil covers the screen, the frame carries the
|
|
26811
|
+
* root's transform, so what it holds lands where the scene's reference units do.
|
|
26812
|
+
*
|
|
26813
|
+
* @param sctx - Domain context of the sync module.
|
|
26814
|
+
* @param objects - The engine's objects.
|
|
26815
|
+
* @param stage - The stage of the application.
|
|
26816
|
+
* @param screen - The canvas in CSS pixels, the stage's own space.
|
|
26817
|
+
* @param screen.x - Its left edge.
|
|
26818
|
+
* @param screen.y - Its top edge.
|
|
26819
|
+
* @param screen.width - Its width.
|
|
26820
|
+
* @param screen.height - Its height.
|
|
26821
|
+
*/
|
|
26822
|
+
function hang$1(sctx, objects, stage, screen) {
|
|
26823
|
+
if (stage.children.at(-1) !== objects.top) stage.addChild(objects.top);
|
|
26824
|
+
objects.veil.position.set(screen.x, screen.y);
|
|
26825
|
+
objects.veil.scale.set(screen.width, screen.height);
|
|
26826
|
+
sctx.deps.viewport.apply(objects.frame);
|
|
26827
|
+
}
|
|
26828
|
+
/**
|
|
26829
|
+
* Asks a `"layer"` loader's `due`, once, the first frame it is due: what it draws is made now, and it answers the
|
|
26830
|
+
* layer that holds it. No layer, or a throw (logged), draws the ring instead.
|
|
26831
|
+
*
|
|
26832
|
+
* @param sctx - Domain context of the sync module.
|
|
26833
|
+
* @param run - The running look, its loader due.
|
|
26834
|
+
*/
|
|
26835
|
+
function ask(sctx, run) {
|
|
26836
|
+
run.asked = true;
|
|
26837
|
+
let layer;
|
|
26838
|
+
try {
|
|
26839
|
+
layer = run.look.loader?.due?.();
|
|
26840
|
+
} catch (error) {
|
|
26841
|
+
sctx.ctx.log.error("renderer: the loader of a scene being left threw", { error });
|
|
26842
|
+
}
|
|
26843
|
+
if (layer === void 0) run.loader = "ring";
|
|
26844
|
+
else run.layer = layer;
|
|
26845
|
+
}
|
|
26846
|
+
/**
|
|
26847
|
+
* Writes the veil of a running look: its colour, and its alpha at the level it stands at; hidden for a look without one.
|
|
26848
|
+
*
|
|
26849
|
+
* @param objects - The engine's objects.
|
|
26850
|
+
* @param run - The running look.
|
|
26851
|
+
*/
|
|
26852
|
+
function paintVeil(objects, run) {
|
|
26853
|
+
const cover = run.look.cover;
|
|
26854
|
+
objects.veil.visible = cover !== void 0;
|
|
26855
|
+
if (cover === void 0) return;
|
|
26856
|
+
objects.veil.tint = cover.color;
|
|
26857
|
+
objects.veil.alpha = cover.alpha * run.level;
|
|
26858
|
+
}
|
|
26859
|
+
/**
|
|
26860
|
+
* Keeps the layer of a shown `"layer"` loader in the holder above the veil until the swap. A layer the scene does not
|
|
26861
|
+
* declare draws nothing.
|
|
26862
|
+
*
|
|
26863
|
+
* @param sctx - Domain context of the sync module.
|
|
26864
|
+
* @param objects - The engine's objects.
|
|
26865
|
+
* @param run - The running look, its loader shown.
|
|
26866
|
+
*/
|
|
26867
|
+
function lift(sctx, objects, run) {
|
|
26868
|
+
const state = sctx.ctx.state.sync;
|
|
26869
|
+
const container = run.layer === void 0 ? void 0 : layerContainer(state, run.layer);
|
|
26870
|
+
if (container === void 0 || container.parent === objects.held) return;
|
|
26871
|
+
objects.held.addChild(container);
|
|
26872
|
+
run.lifted = container;
|
|
26873
|
+
state.passesVersion += 1;
|
|
26874
|
+
}
|
|
26875
|
+
/**
|
|
26876
|
+
* Turns the ring or the spinner at the centre of the frame: one turn a second from the switch start.
|
|
26877
|
+
*
|
|
26878
|
+
* @param objects - The engine's objects.
|
|
26879
|
+
* @param run - The running look, its loader shown.
|
|
26880
|
+
* @param now - The clock now, ms.
|
|
26881
|
+
* @param size - The frame in reference units.
|
|
26882
|
+
*/
|
|
26883
|
+
function turn(objects, run, now, size) {
|
|
26884
|
+
const angle = (now - run.startedAt) % TURN_MS / TURN_MS * TURN$1;
|
|
26885
|
+
const turned = run.loader === "ring" ? objects.ring : objects.spinner;
|
|
26886
|
+
if (run.texture !== void 0 && objects.spinner.texture !== run.texture) objects.spinner.texture = run.texture;
|
|
26887
|
+
turned.position.set(size.width / 2, size.height / 2);
|
|
26888
|
+
turned.rotation = angle;
|
|
26889
|
+
}
|
|
26890
|
+
/**
|
|
26891
|
+
* Shows the loader once the switch took `afterMs`, until the swap: a layer loader asks its `due` then and lifts the
|
|
26892
|
+
* layer it answers above the veil; a ring or a texture turns. A loader that comes or goes moves the walks of `shown`,
|
|
26893
|
+
* so the loops of a lifted layer's views run while it shows.
|
|
26894
|
+
*
|
|
26895
|
+
* @param sctx - Domain context of the sync module.
|
|
26896
|
+
* @param objects - The engine's objects.
|
|
26897
|
+
* @param run - The running look.
|
|
26898
|
+
* @param now - The clock now, ms.
|
|
26899
|
+
* @returns True when the loader came, went or turns in this frame: the frame is drawn.
|
|
26900
|
+
*/
|
|
26901
|
+
function paintLoader(sctx, objects, run, now) {
|
|
26902
|
+
const afterMs = run.look.loader?.afterMs ?? 0;
|
|
26903
|
+
const shown = !run.landed && run.loader !== "none" && now - run.startedAt >= afterMs;
|
|
26904
|
+
const due = shown && run.loader === "layer";
|
|
26905
|
+
if (due && !run.asked) ask(sctx, run);
|
|
26906
|
+
if (due) lift(sctx, objects, run);
|
|
26907
|
+
const toggled = shown !== run.shown;
|
|
26908
|
+
if (toggled) {
|
|
26909
|
+
run.shown = shown;
|
|
26910
|
+
sctx.ctx.state.sync.passesVersion += 1;
|
|
26911
|
+
}
|
|
26912
|
+
const isLayer = shown && run.loader === "layer";
|
|
26913
|
+
objects.held.visible = isLayer;
|
|
26914
|
+
objects.ring.visible = shown && run.loader === "ring";
|
|
26915
|
+
objects.spinner.visible = shown && run.loader === "texture";
|
|
26916
|
+
if (!shown || isLayer) return toggled;
|
|
26917
|
+
turn(objects, run, now, sctx.deps.viewport.size());
|
|
26918
|
+
return true;
|
|
26919
|
+
}
|
|
26920
|
+
/**
|
|
26921
|
+
* 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
|
|
26922
|
+
* still fading out after the last swap), the loader waits for its `afterMs`. Inert, nothing is kept.
|
|
26923
|
+
*
|
|
26924
|
+
* @param sctx - Domain context of the sync module.
|
|
26925
|
+
* @param look - The veil and the loader, resolved.
|
|
26926
|
+
*/
|
|
26927
|
+
function showLeave(sctx, look) {
|
|
26928
|
+
const state = sctx.ctx.state.sync;
|
|
26929
|
+
if (state.root === void 0) return;
|
|
26930
|
+
const before = state.leave.run;
|
|
26931
|
+
if (before !== void 0) unlift(sctx, before);
|
|
26932
|
+
const now = sctx.ctx.deps.clock.now();
|
|
26933
|
+
const level = before?.look.cover === void 0 ? 0 : before.level;
|
|
26934
|
+
const loader = loaderOf$1(sctx, look);
|
|
26935
|
+
state.leave.run = {
|
|
26936
|
+
look,
|
|
26937
|
+
startedAt: now,
|
|
26938
|
+
steppedAt: now,
|
|
26939
|
+
level,
|
|
26940
|
+
landed: false,
|
|
26941
|
+
asked: false,
|
|
26942
|
+
shown: false,
|
|
26943
|
+
...loader
|
|
26944
|
+
};
|
|
26945
|
+
sctx.deps.demand.request();
|
|
26946
|
+
}
|
|
26947
|
+
/**
|
|
26948
|
+
* The swap came: the loader goes at once and the veil fades out over its `outMs` from where it stands; a look without
|
|
26949
|
+
* a veil, or with `outMs: 0`, is taken down at once. Nothing runs, or the swap was told already: nothing happens.
|
|
26950
|
+
*
|
|
26951
|
+
* @param sctx - Domain context of the sync module.
|
|
26952
|
+
*/
|
|
26953
|
+
function landLeave(sctx) {
|
|
26954
|
+
const run = sctx.ctx.state.sync.leave.run;
|
|
26955
|
+
if (run === void 0 || run.landed) return;
|
|
26956
|
+
const cover = run.look.cover;
|
|
26957
|
+
if (cover === void 0 || cover.outMs <= 0) {
|
|
26958
|
+
takeDown(sctx);
|
|
26959
|
+
return;
|
|
26960
|
+
}
|
|
26961
|
+
unlift(sctx, run);
|
|
26962
|
+
run.landed = true;
|
|
26963
|
+
run.steppedAt = sctx.ctx.deps.clock.now();
|
|
26964
|
+
sctx.deps.demand.request();
|
|
26965
|
+
}
|
|
26966
|
+
/**
|
|
26967
|
+
* The switch ended without a swap: the veil and the loader go at once. Nothing runs: nothing happens.
|
|
26968
|
+
*
|
|
26969
|
+
* @param sctx - Domain context of the sync module.
|
|
26970
|
+
*/
|
|
26971
|
+
function dropLeave(sctx) {
|
|
26972
|
+
if (sctx.ctx.state.sync.leave.run === void 0) return;
|
|
26973
|
+
takeDown(sctx);
|
|
26974
|
+
}
|
|
26975
|
+
/**
|
|
26976
|
+
* The look of this frame, before the decision: the veil's level from the clock, the loader once due, both placed over
|
|
26977
|
+
* the canvas as it stands, and a frame asked for while either moves. A look whose veil faded out after the swap is
|
|
26978
|
+
* taken down. One field read and no allocation while no look runs; a lost device leaves the look waiting for the
|
|
26979
|
+
* restore, which makes its objects again.
|
|
26980
|
+
*
|
|
26981
|
+
* @param sctx - Domain context of the sync module.
|
|
26982
|
+
*/
|
|
26983
|
+
function stepLeave(sctx) {
|
|
26984
|
+
const run = sctx.ctx.state.sync.leave.run;
|
|
26985
|
+
if (run === void 0) return;
|
|
26986
|
+
const objects = objectsOf(sctx);
|
|
26987
|
+
const stage = sctx.deps.host.stage();
|
|
26988
|
+
const screen = sctx.deps.host.screen();
|
|
26989
|
+
if (objects === void 0 || stage === void 0 || screen === void 0) return;
|
|
26990
|
+
const now = sctx.ctx.deps.clock.now();
|
|
26991
|
+
const moved = advance(run, now);
|
|
26992
|
+
if (run.landed && run.level <= 0) {
|
|
26993
|
+
takeDown(sctx);
|
|
26994
|
+
return;
|
|
26995
|
+
}
|
|
26996
|
+
hang$1(sctx, objects, stage, screen);
|
|
26997
|
+
paintVeil(objects, run);
|
|
26998
|
+
const loading = paintLoader(sctx, objects, run, now);
|
|
26999
|
+
if (moved || loading) sctx.deps.demand.request();
|
|
27000
|
+
}
|
|
27001
|
+
/**
|
|
27002
|
+
* Forgets the objects of a lost application: they go with its stage, and so does a layer lifted above the veil. The
|
|
27003
|
+
* running look stays: the step makes its objects again on the restored device.
|
|
27004
|
+
*
|
|
27005
|
+
* @param state - The sync branch of the plugin state.
|
|
27006
|
+
*/
|
|
27007
|
+
function forgetLeave$1(state) {
|
|
27008
|
+
delete state.leave.objects;
|
|
27009
|
+
if (state.leave.run !== void 0) delete state.leave.run.lifted;
|
|
27010
|
+
}
|
|
27011
|
+
/**
|
|
27012
|
+
* Drops the look and frees the engine's objects when the plugin stops. A lifted layer is the layer table's and the
|
|
27013
|
+
* spinner's texture is the game's: neither is freed with them.
|
|
27014
|
+
*
|
|
27015
|
+
* @param state - The sync branch of the plugin state.
|
|
27016
|
+
*/
|
|
27017
|
+
function stopLeave(state) {
|
|
27018
|
+
const objects = state.leave.objects;
|
|
27019
|
+
delete state.leave.run;
|
|
27020
|
+
delete state.leave.objects;
|
|
27021
|
+
if (objects === void 0) return;
|
|
27022
|
+
objects.held.removeChildren();
|
|
27023
|
+
objects.top.destroy({ children: true });
|
|
27024
|
+
}
|
|
27025
|
+
//#endregion
|
|
26029
27026
|
//#region src/plugins/renderer/sync/overlay-range.ts
|
|
26030
27027
|
/** 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
27028
|
const RETAKE_STREAK = 3;
|
|
@@ -26718,349 +27715,6 @@ function stopOverlays(state) {
|
|
|
26718
27715
|
overlay.range = void 0;
|
|
26719
27716
|
}
|
|
26720
27717
|
//#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
27718
|
//#region src/plugins/renderer/sync/sizing.ts
|
|
27065
27719
|
/**
|
|
27066
27720
|
* Creates a layout to write into, every field present.
|
|
@@ -27196,299 +27850,6 @@ function layoutSpriteInto(out, texture, value) {
|
|
|
27196
27850
|
return out;
|
|
27197
27851
|
}
|
|
27198
27852
|
//#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
27853
|
//#region src/plugins/renderer/sync/upload.ts
|
|
27493
27854
|
/** The calls of a source nobody waits for: none. */
|
|
27494
27855
|
const NOBODY = Object.freeze([]);
|
|
@@ -28875,6 +29236,7 @@ function stopSync(state) {
|
|
|
28875
29236
|
state.redraws = 0;
|
|
28876
29237
|
state.overdraw.listeners.length = 0;
|
|
28877
29238
|
clearLayers(state);
|
|
29239
|
+
stopLeave(state);
|
|
28878
29240
|
destroyPools(state);
|
|
28879
29241
|
state.root?.destroy({ children: true });
|
|
28880
29242
|
state.root = void 0;
|
|
@@ -29044,6 +29406,7 @@ function renderFrame(ctx, modules) {
|
|
|
29044
29406
|
modules.sync.stepFilters();
|
|
29045
29407
|
modules.sync.stepOverlays();
|
|
29046
29408
|
modules.sync.drainOverlayEvents(modules.monitor);
|
|
29409
|
+
modules.sync.stepLeave();
|
|
29047
29410
|
modules.kept.collect();
|
|
29048
29411
|
const overdraw = modules.sync.overdrawing();
|
|
29049
29412
|
if (overdraw) modules.demand.request();
|
|
@@ -29847,7 +30210,7 @@ function boundsOf(sctx, entity) {
|
|
|
29847
30210
|
* @param entity - The entity.
|
|
29848
30211
|
* @returns The layer name, or `undefined` when the root has no `Layer`.
|
|
29849
30212
|
*/
|
|
29850
|
-
function layerOf(sctx, entity) {
|
|
30213
|
+
function layerOf$1(sctx, entity) {
|
|
29851
30214
|
const ecs = sctx.ctx.deps.world.ecs;
|
|
29852
30215
|
let top = entity;
|
|
29853
30216
|
for (let depth = 0; depth < MAX_DEPTH$3; depth += 1) {
|
|
@@ -30474,7 +30837,7 @@ function stepParts(sctx) {
|
|
|
30474
30837
|
const view = state.views.get(entity);
|
|
30475
30838
|
if (view === void 0) continue;
|
|
30476
30839
|
const key = ecs.get(entity, HudPart)?.key ?? "";
|
|
30477
|
-
if (hud === void 0 || layerOf(sctx, entity) !== hud) {
|
|
30840
|
+
if (hud === void 0 || layerOf$1(sctx, entity) !== hud) {
|
|
30478
30841
|
warnOutside(sctx, entity, key);
|
|
30479
30842
|
applyHud(sctx, entity, view, 1);
|
|
30480
30843
|
continue;
|
|
@@ -31519,6 +31882,7 @@ function createSyncApi(ctx, deps) {
|
|
|
31519
31882
|
destroyPools(state);
|
|
31520
31883
|
clearLayers(state);
|
|
31521
31884
|
forgetBuild(state);
|
|
31885
|
+
forgetLeave$1(state);
|
|
31522
31886
|
state.root = void 0;
|
|
31523
31887
|
state.passesVersion += 1;
|
|
31524
31888
|
};
|
|
@@ -31612,11 +31976,17 @@ function createSyncApi(ctx, deps) {
|
|
|
31612
31976
|
area: (entity, area) => setArea(sctx, entity, area),
|
|
31613
31977
|
warm: (filter) => warmFilter(sctx, filter)
|
|
31614
31978
|
},
|
|
31979
|
+
leave: {
|
|
31980
|
+
show: (look) => showLeave(sctx, look),
|
|
31981
|
+
land: () => landLeave(sctx),
|
|
31982
|
+
drop: () => dropLeave(sctx)
|
|
31983
|
+
},
|
|
31615
31984
|
renderPasses: () => renderPassesOf(state, ctx.state.kept),
|
|
31616
31985
|
root: () => state.root,
|
|
31617
31986
|
layerContainer: (name) => layerContainer(state, name),
|
|
31618
|
-
layerOf: (entity) => layerOf(sctx, entity),
|
|
31987
|
+
layerOf: (entity) => layerOf$1(sctx, entity),
|
|
31619
31988
|
stepOverlays: () => stepOverlays(sctx),
|
|
31989
|
+
stepLeave: () => stepLeave(sctx),
|
|
31620
31990
|
stepFilters: () => applySwitched(state, deps.kept),
|
|
31621
31991
|
drainOverlayEvents: (monitor) => drainOverlayEvents(sctx, monitor),
|
|
31622
31992
|
warmStep: () => warmStep(sctx),
|
|
@@ -32205,6 +32575,7 @@ function exposeSync(sync) {
|
|
|
32205
32575
|
fonts: sync.fonts,
|
|
32206
32576
|
debug: sync.debug,
|
|
32207
32577
|
filters: sync.filters,
|
|
32578
|
+
leave: sync.leave,
|
|
32208
32579
|
renderPasses: sync.renderPasses,
|
|
32209
32580
|
loopsHeld: sync.loopsHeld,
|
|
32210
32581
|
displayOf: sync.displayOf
|
|
@@ -32446,8 +32817,8 @@ function createAsideQueue() {
|
|
|
32446
32817
|
}
|
|
32447
32818
|
/**
|
|
32448
32819
|
* 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.
|
|
32820
|
+
* filter area, no slice, no hidden mask, no overlay, no warm-up, no upload, no redraw counted, no unseen walk kept,
|
|
32821
|
+
* no scene build and no picture of a scene being left.
|
|
32451
32822
|
*
|
|
32452
32823
|
* @param debug - The debug switches of the config; copied, so the frozen config stays untouched.
|
|
32453
32824
|
* @returns The sync branch of the plugin state.
|
|
@@ -32512,6 +32883,7 @@ function createSyncState(debug) {
|
|
|
32512
32883
|
count: 0,
|
|
32513
32884
|
reported: false
|
|
32514
32885
|
},
|
|
32886
|
+
leave: {},
|
|
32515
32887
|
cleanups: []
|
|
32516
32888
|
};
|
|
32517
32889
|
}
|
|
@@ -35179,7 +35551,7 @@ const NO_ANIMATION_ID = "[game] A play effect carries no animation id.\n Build
|
|
|
35179
35551
|
* isRecord([1, 2]); // false
|
|
35180
35552
|
* ```
|
|
35181
35553
|
*/
|
|
35182
|
-
function isRecord$
|
|
35554
|
+
function isRecord$4(value) {
|
|
35183
35555
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
35184
35556
|
}
|
|
35185
35557
|
/**
|
|
@@ -35304,7 +35676,7 @@ function stepAnim(actx, rt, time) {
|
|
|
35304
35676
|
* @returns True when it carries an id and a build function.
|
|
35305
35677
|
*/
|
|
35306
35678
|
function isAnimation(entry) {
|
|
35307
|
-
return isRecord$
|
|
35679
|
+
return isRecord$4(entry) && typeof entry.id === "string" && typeof entry.build === "function";
|
|
35308
35680
|
}
|
|
35309
35681
|
/**
|
|
35310
35682
|
* Reads the `animations` of every feature into the registry, in feature order.
|
|
@@ -35331,7 +35703,7 @@ function registerAnimations(actx) {
|
|
|
35331
35703
|
*/
|
|
35332
35704
|
function readTarget$1(entry) {
|
|
35333
35705
|
if (typeof entry === "number") return entry;
|
|
35334
|
-
if (!isRecord$
|
|
35706
|
+
if (!isRecord$4(entry)) return void 0;
|
|
35335
35707
|
if (typeof entry.projection === "string" && typeof entry.key === "string") return {
|
|
35336
35708
|
projection: entry.projection,
|
|
35337
35709
|
key: entry.key
|
|
@@ -35347,7 +35719,7 @@ function readTarget$1(entry) {
|
|
|
35347
35719
|
*/
|
|
35348
35720
|
function readSlots(value) {
|
|
35349
35721
|
const slots = {};
|
|
35350
|
-
if (!isRecord$
|
|
35722
|
+
if (!isRecord$4(value)) return slots;
|
|
35351
35723
|
for (const [name, entry] of Object.entries(value)) {
|
|
35352
35724
|
if (Array.isArray(entry)) {
|
|
35353
35725
|
slots[name] = entry.map((item) => readTarget$1(item)).filter((target) => target !== void 0);
|
|
@@ -35365,7 +35737,7 @@ function readSlots(value) {
|
|
|
35365
35737
|
* @returns The animation id and its slots, or `undefined` for a foreign payload.
|
|
35366
35738
|
*/
|
|
35367
35739
|
function readPlayPayload(payload) {
|
|
35368
|
-
if (!isRecord$
|
|
35740
|
+
if (!isRecord$4(payload) || typeof payload.animation !== "string") return void 0;
|
|
35369
35741
|
return {
|
|
35370
35742
|
animation: payload.animation,
|
|
35371
35743
|
slots: readSlots(payload.slots)
|
|
@@ -35689,7 +36061,7 @@ function emptyManifest() {
|
|
|
35689
36061
|
* @param value - Anything that came out of JSON.
|
|
35690
36062
|
* @returns True for an object that is not an array.
|
|
35691
36063
|
*/
|
|
35692
|
-
function isRecord$
|
|
36064
|
+
function isRecord$3(value) {
|
|
35693
36065
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
35694
36066
|
}
|
|
35695
36067
|
/**
|
|
@@ -35729,7 +36101,7 @@ function stringAt(source, key) {
|
|
|
35729
36101
|
* @returns The four borders, or `undefined`.
|
|
35730
36102
|
*/
|
|
35731
36103
|
function parseNine(value) {
|
|
35732
|
-
if (!isRecord$
|
|
36104
|
+
if (!isRecord$3(value)) return void 0;
|
|
35733
36105
|
return {
|
|
35734
36106
|
left: numberAt(value, "left"),
|
|
35735
36107
|
top: numberAt(value, "top"),
|
|
@@ -35744,7 +36116,7 @@ function parseNine(value) {
|
|
|
35744
36116
|
* @returns The frame, or `undefined`.
|
|
35745
36117
|
*/
|
|
35746
36118
|
function parseAtlas(value) {
|
|
35747
|
-
if (!isRecord$
|
|
36119
|
+
if (!isRecord$3(value)) return void 0;
|
|
35748
36120
|
return {
|
|
35749
36121
|
page: stringAt(value, "page"),
|
|
35750
36122
|
x: numberAt(value, "x"),
|
|
@@ -35771,7 +36143,7 @@ function parseKind(value) {
|
|
|
35771
36143
|
*/
|
|
35772
36144
|
function parseFontPages(value) {
|
|
35773
36145
|
if (!Array.isArray(value)) return void 0;
|
|
35774
|
-
return value.filter((entry) => isRecord$
|
|
36146
|
+
return value.filter((entry) => isRecord$3(entry)).map((entry) => ({
|
|
35775
36147
|
path: stringAt(entry, "path"),
|
|
35776
36148
|
width: numberAt(entry, "width"),
|
|
35777
36149
|
height: numberAt(entry, "height"),
|
|
@@ -35786,7 +36158,7 @@ function parseFontPages(value) {
|
|
|
35786
36158
|
*/
|
|
35787
36159
|
function parseAtlasPages(value) {
|
|
35788
36160
|
if (!Array.isArray(value)) return void 0;
|
|
35789
|
-
return value.filter((entry) => isRecord$
|
|
36161
|
+
return value.filter((entry) => isRecord$3(entry)).map((entry) => ({
|
|
35790
36162
|
id: stringAt(entry, "id"),
|
|
35791
36163
|
path: stringAt(entry, "path"),
|
|
35792
36164
|
width: numberAt(entry, "width"),
|
|
@@ -35802,7 +36174,7 @@ function parseAtlasPages(value) {
|
|
|
35802
36174
|
* @returns The file, or `undefined` when the element is not an object.
|
|
35803
36175
|
*/
|
|
35804
36176
|
function parseFile(raw) {
|
|
35805
|
-
if (!isRecord$
|
|
36177
|
+
if (!isRecord$3(raw)) return void 0;
|
|
35806
36178
|
const kind = parseKind(raw.kind);
|
|
35807
36179
|
const pages = parseFontPages(raw.pages);
|
|
35808
36180
|
const nine = parseNine(raw.nine);
|
|
@@ -35873,7 +36245,7 @@ function textAt(source, key) {
|
|
|
35873
36245
|
* @throws {Error} When the entry is not an object or its tier is unknown.
|
|
35874
36246
|
*/
|
|
35875
36247
|
function parseBundle(name, raw) {
|
|
35876
|
-
if (!isRecord$
|
|
36248
|
+
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
36249
|
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
36250
|
const pages = parseAtlasPages(raw.pages);
|
|
35879
36251
|
const parent = textAt(raw, "parent");
|
|
@@ -35914,9 +36286,9 @@ function parseVersion(value) {
|
|
|
35914
36286
|
* ```
|
|
35915
36287
|
*/
|
|
35916
36288
|
function parseManifest(raw) {
|
|
35917
|
-
if (!isRecord$
|
|
36289
|
+
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
36290
|
const version = parseVersion(raw.version);
|
|
35919
|
-
const source = isRecord$
|
|
36291
|
+
const source = isRecord$3(raw.bundles) ? raw.bundles : {};
|
|
35920
36292
|
const bundles = {};
|
|
35921
36293
|
for (const name of Object.keys(source).toSorted((left, right) => left.localeCompare(right))) bundles[name] = parseBundle(name, source[name]);
|
|
35922
36294
|
return {
|
|
@@ -39101,6 +39473,468 @@ function dropBuild(build) {
|
|
|
39101
39473
|
build.resolve();
|
|
39102
39474
|
}
|
|
39103
39475
|
//#endregion
|
|
39476
|
+
//#region src/plugins/scenes/leave.ts
|
|
39477
|
+
/**
|
|
39478
|
+
* What a game gets when it writes nothing: black at 45 %, 150 ms in and out, the engine's ring once the switch took
|
|
39479
|
+
* 400 ms. The level under `config.leave`, and where a part written over `false` starts. `index.ts` writes the same
|
|
39480
|
+
* value inline as the plugin's default config; a test pins the two equal.
|
|
39481
|
+
*/
|
|
39482
|
+
const FALLBACK_LEAVE = Object.freeze({
|
|
39483
|
+
cover: Object.freeze({
|
|
39484
|
+
color: 0,
|
|
39485
|
+
alpha: .45,
|
|
39486
|
+
inMs: 150,
|
|
39487
|
+
outMs: 150
|
|
39488
|
+
}),
|
|
39489
|
+
loader: Object.freeze({ afterMs: 400 })
|
|
39490
|
+
});
|
|
39491
|
+
/**
|
|
39492
|
+
* Merges one level's veil over the one below, field by field. `false` turns it off; a veil written over `false`
|
|
39493
|
+
* starts from the engine's.
|
|
39494
|
+
*
|
|
39495
|
+
* @param below - The veil of the levels below, or `false`.
|
|
39496
|
+
* @param over - What this level writes: nothing, `false` or fields.
|
|
39497
|
+
* @returns The veil, or `false`.
|
|
39498
|
+
* @example
|
|
39499
|
+
* ```ts
|
|
39500
|
+
* coverOver(false, { alpha: 0.6 }); // { color: 0, alpha: 0.6, inMs: 150, outMs: 150 }
|
|
39501
|
+
* ```
|
|
39502
|
+
*/
|
|
39503
|
+
function coverOver(below, over) {
|
|
39504
|
+
if (over === void 0) return below;
|
|
39505
|
+
if (over === false) return false;
|
|
39506
|
+
const base = below === false ? FALLBACK_LEAVE.cover : below;
|
|
39507
|
+
return {
|
|
39508
|
+
color: over.color ?? base.color,
|
|
39509
|
+
alpha: over.alpha ?? base.alpha,
|
|
39510
|
+
inMs: over.inMs ?? base.inMs,
|
|
39511
|
+
outMs: over.outMs ?? base.outMs
|
|
39512
|
+
};
|
|
39513
|
+
}
|
|
39514
|
+
/**
|
|
39515
|
+
* The look of a loader: its projection, else its texture, else nothing (the engine's ring).
|
|
39516
|
+
*
|
|
39517
|
+
* @param loader - A level that names a look, or the level below.
|
|
39518
|
+
* @returns At most one of the two fields.
|
|
39519
|
+
* @example
|
|
39520
|
+
* ```ts
|
|
39521
|
+
* lookOf({ texture: "ui.loader.ring", projection: "loading" }); // { projection: "loading" }
|
|
39522
|
+
* ```
|
|
39523
|
+
*/
|
|
39524
|
+
function lookOf(loader) {
|
|
39525
|
+
if (loader.projection !== void 0) return { projection: loader.projection };
|
|
39526
|
+
if (loader.texture !== void 0) return { texture: loader.texture };
|
|
39527
|
+
return {};
|
|
39528
|
+
}
|
|
39529
|
+
/**
|
|
39530
|
+
* Merges one level's loader over the one below. `afterMs` is a field of its own; `texture` and `projection` are one
|
|
39531
|
+
* choice: a level that names either replaces both of the level below. `false` turns it off; a loader written over
|
|
39532
|
+
* `false` starts from the engine's.
|
|
39533
|
+
*
|
|
39534
|
+
* @param below - The loader of the levels below, or `false`.
|
|
39535
|
+
* @param over - What this level writes: nothing, `false` or fields.
|
|
39536
|
+
* @returns The loader, or `false`.
|
|
39537
|
+
* @example
|
|
39538
|
+
* ```ts
|
|
39539
|
+
* loaderOver({ afterMs: 400, projection: "loading" }, { texture: "ui.loader.ring" }); // { afterMs: 400, texture: "ui.loader.ring" }
|
|
39540
|
+
* ```
|
|
39541
|
+
*/
|
|
39542
|
+
function loaderOver(below, over) {
|
|
39543
|
+
if (over === void 0) return below;
|
|
39544
|
+
if (over === false) return false;
|
|
39545
|
+
const base = below === false ? FALLBACK_LEAVE.loader : below;
|
|
39546
|
+
const named = over.texture !== void 0 || over.projection !== void 0;
|
|
39547
|
+
return {
|
|
39548
|
+
afterMs: over.afterMs ?? base.afterMs,
|
|
39549
|
+
...lookOf(named ? over : base)
|
|
39550
|
+
};
|
|
39551
|
+
}
|
|
39552
|
+
/**
|
|
39553
|
+
* Resolves what a switch shows over the scene being left: the scene's `leave` over the config's over the engine's,
|
|
39554
|
+
* field by field. `false` at a level turns both parts off there.
|
|
39555
|
+
*
|
|
39556
|
+
* @param config - `config.leave`.
|
|
39557
|
+
* @param scene - The `leave` of the scene being left.
|
|
39558
|
+
* @returns Both parts, each resolved or `false`; `undefined` when both are off.
|
|
39559
|
+
* @example
|
|
39560
|
+
* ```ts
|
|
39561
|
+
* leaveOf({ loader: false }, { cover: { alpha: 0.6 } }); // { cover: { color: 0, alpha: 0.6, inMs: 150, outMs: 150 }, loader: false }
|
|
39562
|
+
* ```
|
|
39563
|
+
*/
|
|
39564
|
+
function leaveOf(config, scene) {
|
|
39565
|
+
let cover = FALLBACK_LEAVE.cover;
|
|
39566
|
+
let loader = FALLBACK_LEAVE.loader;
|
|
39567
|
+
for (const level of [config, scene]) if (level === false) {
|
|
39568
|
+
cover = false;
|
|
39569
|
+
loader = false;
|
|
39570
|
+
} else if (level !== void 0) {
|
|
39571
|
+
cover = coverOver(cover, level.cover);
|
|
39572
|
+
loader = loaderOver(loader, level.loader);
|
|
39573
|
+
}
|
|
39574
|
+
if (cover === false && loader === false) return void 0;
|
|
39575
|
+
return {
|
|
39576
|
+
cover,
|
|
39577
|
+
loader
|
|
39578
|
+
};
|
|
39579
|
+
}
|
|
39580
|
+
/**
|
|
39581
|
+
* The layer a mounted projection's views draw in, read from its first view.
|
|
39582
|
+
*
|
|
39583
|
+
* @param ctx - Domain context of the plugin.
|
|
39584
|
+
* @param name - The projection.
|
|
39585
|
+
* @returns The layer, or `undefined` while it has no view.
|
|
39586
|
+
*/
|
|
39587
|
+
function layerOf(ctx, name) {
|
|
39588
|
+
const { projection, ecs } = ctx.deps.world;
|
|
39589
|
+
const entity = projection.entitiesOf(name)[0];
|
|
39590
|
+
return entity === void 0 ? void 0 : ecs.get(entity, Layer)?.name;
|
|
39591
|
+
}
|
|
39592
|
+
/**
|
|
39593
|
+
* Unmounts the loader projection a switch mounted, once: one it did not mount is left alone.
|
|
39594
|
+
*
|
|
39595
|
+
* @param ctx - Domain context of the plugin.
|
|
39596
|
+
* @param leaving - The look of the switch.
|
|
39597
|
+
*/
|
|
39598
|
+
function unmountLoader(ctx, leaving) {
|
|
39599
|
+
const name = leaving.projection;
|
|
39600
|
+
if (name === void 0) return;
|
|
39601
|
+
delete leaving.projection;
|
|
39602
|
+
ctx.deps.world.projection.unmount([name]);
|
|
39603
|
+
}
|
|
39604
|
+
/**
|
|
39605
|
+
* Mounts a loader projection for the switch, unless it is mounted already (by another owner): such a one is drawn as it
|
|
39606
|
+
* is and never unmounted by the switch. A mount that throws (an unknown name, a layer the scene being left does not
|
|
39607
|
+
* declare) is warned.
|
|
39608
|
+
*
|
|
39609
|
+
* @param ctx - Domain context of the plugin.
|
|
39610
|
+
* @param name - The loader projection.
|
|
39611
|
+
* @param from - The scene being left.
|
|
39612
|
+
* @param leaving - The look of the switch: it keeps the name to unmount at the end.
|
|
39613
|
+
* @returns False when the mount threw.
|
|
39614
|
+
*/
|
|
39615
|
+
function mountOnce(ctx, name, from, leaving) {
|
|
39616
|
+
const projection = ctx.deps.world.projection;
|
|
39617
|
+
if (projection.entitiesOf(name).length > 0) return true;
|
|
39618
|
+
try {
|
|
39619
|
+
projection.mount([name], ctx.state.owner);
|
|
39620
|
+
} catch (error) {
|
|
39621
|
+
ctx.log.warn("scenes:loader-unmounted", {
|
|
39622
|
+
projection: name,
|
|
39623
|
+
scene: from.id,
|
|
39624
|
+
error
|
|
39625
|
+
});
|
|
39626
|
+
return false;
|
|
39627
|
+
}
|
|
39628
|
+
leaving.projection = name;
|
|
39629
|
+
return true;
|
|
39630
|
+
}
|
|
39631
|
+
/**
|
|
39632
|
+
* Tells whether a projection of the scene being left draws in a layer: lifted above the veil, that layer would take
|
|
39633
|
+
* the scene's own views with it.
|
|
39634
|
+
*
|
|
39635
|
+
* @param ctx - Domain context of the plugin.
|
|
39636
|
+
* @param from - The scene being left.
|
|
39637
|
+
* @param layer - The layer of the loader.
|
|
39638
|
+
* @returns True when one of its projections has a view in that layer.
|
|
39639
|
+
*/
|
|
39640
|
+
function sharesLayer(ctx, from, layer) {
|
|
39641
|
+
return from.projections.some((other) => layerOf(ctx, other) === layer);
|
|
39642
|
+
}
|
|
39643
|
+
/**
|
|
39644
|
+
* Mounts a loader projection once its loader is due, called by the renderer in that frame, and answers the layer its
|
|
39645
|
+
* views draw in, which the renderer draws above the veil. A mount that throws, a layer a projection of the scene being
|
|
39646
|
+
* left draws in too, and a projection with no view give the engine's ring. A switch that ended meanwhile mounts
|
|
39647
|
+
* nothing.
|
|
39648
|
+
*
|
|
39649
|
+
* @param ctx - Domain context of the plugin.
|
|
39650
|
+
* @param name - The loader projection.
|
|
39651
|
+
* @param from - The scene being left.
|
|
39652
|
+
* @param leaving - The look of the switch: it keeps the name to unmount at the end.
|
|
39653
|
+
* @returns The layer, or `undefined` for the ring.
|
|
39654
|
+
*/
|
|
39655
|
+
function mountLoader(ctx, name, from, leaving) {
|
|
39656
|
+
if (leaving.done || !mountOnce(ctx, name, from, leaving)) return void 0;
|
|
39657
|
+
const layer = layerOf(ctx, name);
|
|
39658
|
+
if (layer === void 0 || !sharesLayer(ctx, from, layer)) return layer;
|
|
39659
|
+
ctx.log.warn("scenes:loader-layer-shared", {
|
|
39660
|
+
projection: name,
|
|
39661
|
+
layer,
|
|
39662
|
+
scene: from.id
|
|
39663
|
+
});
|
|
39664
|
+
unmountLoader(ctx, leaving);
|
|
39665
|
+
}
|
|
39666
|
+
/**
|
|
39667
|
+
* The renderer's loader of a resolved one: a texture as it is, nothing for the ring, and for a projection the `due`
|
|
39668
|
+
* the renderer calls once the loader is due, which mounts it then: a switch done sooner never runs its `from` and
|
|
39669
|
+
* `view`. A projection a scene of the switch lists is no loader: warned, the ring.
|
|
39670
|
+
*
|
|
39671
|
+
* @param ctx - Domain context of the plugin.
|
|
39672
|
+
* @param loader - The resolved loader.
|
|
39673
|
+
* @param between - The scene being left and the scene being switched to.
|
|
39674
|
+
* @param between.from - The scene being left.
|
|
39675
|
+
* @param between.to - The scene being switched to.
|
|
39676
|
+
* @param leaving - The look of the switch.
|
|
39677
|
+
* @returns What the renderer draws.
|
|
39678
|
+
*/
|
|
39679
|
+
function loaderLook(ctx, loader, between, leaving) {
|
|
39680
|
+
const { afterMs, texture, projection } = loader;
|
|
39681
|
+
const { from, to } = between;
|
|
39682
|
+
if (texture !== void 0) return {
|
|
39683
|
+
afterMs,
|
|
39684
|
+
texture
|
|
39685
|
+
};
|
|
39686
|
+
if (projection === void 0) return { afterMs };
|
|
39687
|
+
if (from.projections.includes(projection) || to.projections.includes(projection)) {
|
|
39688
|
+
ctx.log.warn("scenes:loader-listed", {
|
|
39689
|
+
projection,
|
|
39690
|
+
from: from.id,
|
|
39691
|
+
to: to.id
|
|
39692
|
+
});
|
|
39693
|
+
return { afterMs };
|
|
39694
|
+
}
|
|
39695
|
+
return {
|
|
39696
|
+
afterMs,
|
|
39697
|
+
due: () => mountLoader(ctx, projection, from, leaving)
|
|
39698
|
+
};
|
|
39699
|
+
}
|
|
39700
|
+
/**
|
|
39701
|
+
* Tells whether a switch shows anything over the scene being left: a real change of scene, a live run that was not
|
|
39702
|
+
* aborted, a world that runs live, a drawing renderer. The first scene of a run, a fast walk, a world walking fast or
|
|
39703
|
+
* paused and a headless app show nothing.
|
|
39704
|
+
*
|
|
39705
|
+
* @param ctx - Domain context of the plugin.
|
|
39706
|
+
* @param run - The runner's mode and signal.
|
|
39707
|
+
* @returns The scene being left, or `undefined` when nothing is shown.
|
|
39708
|
+
*/
|
|
39709
|
+
function leftScene(ctx, run) {
|
|
39710
|
+
const current = ctx.state.current;
|
|
39711
|
+
if (current === void 0 || run.mode !== "live" || run.signal.aborted) return void 0;
|
|
39712
|
+
if (ctx.deps.world.ecs.mode() !== "live" || !ctx.deps.renderer.host.ready()) return void 0;
|
|
39713
|
+
return ctx.state.scenes.get(current);
|
|
39714
|
+
}
|
|
39715
|
+
/**
|
|
39716
|
+
* Shows the veil and the loader of a switch that starts now, before its bundles are awaited: the scene being left
|
|
39717
|
+
* decides. A loader projection is mounted only once its loader is due. An abort of the node drops both at once, from
|
|
39718
|
+
* its listener.
|
|
39719
|
+
*
|
|
39720
|
+
* @param ctx - Domain context of the plugin.
|
|
39721
|
+
* @param scene - The scene being switched to.
|
|
39722
|
+
* @param run - The runner's mode and signal.
|
|
39723
|
+
* @returns The look of the switch, or `undefined` when it shows nothing.
|
|
39724
|
+
*/
|
|
39725
|
+
function startLeave(ctx, scene, run) {
|
|
39726
|
+
const from = leftScene(ctx, run);
|
|
39727
|
+
if (from === void 0) return void 0;
|
|
39728
|
+
const leave = leaveOf(ctx.config.leave, from.leave);
|
|
39729
|
+
if (leave === void 0) return void 0;
|
|
39730
|
+
const listening = new AbortController();
|
|
39731
|
+
const leaving = {
|
|
39732
|
+
done: false,
|
|
39733
|
+
off: () => listening.abort()
|
|
39734
|
+
};
|
|
39735
|
+
const look = {};
|
|
39736
|
+
if (leave.cover !== false) look.cover = leave.cover;
|
|
39737
|
+
if (leave.loader !== false) look.loader = loaderLook(ctx, leave.loader, {
|
|
39738
|
+
from,
|
|
39739
|
+
to: scene
|
|
39740
|
+
}, leaving);
|
|
39741
|
+
ctx.deps.renderer.sync.leave.show(look);
|
|
39742
|
+
run.signal.addEventListener("abort", () => endLeave(ctx, leaving, "drop"), {
|
|
39743
|
+
once: true,
|
|
39744
|
+
signal: listening.signal
|
|
39745
|
+
});
|
|
39746
|
+
ctx.state.leaving = leaving;
|
|
39747
|
+
return leaving;
|
|
39748
|
+
}
|
|
39749
|
+
/**
|
|
39750
|
+
* Ends the look of a switch once: `"land"` at the swap (the loader goes, the veil fades out over the next scene),
|
|
39751
|
+
* `"drop"` on an abort or a throw (both go at once). A loader projection mounted for the wait is unmounted. A look
|
|
39752
|
+
* ended already, or a switch that showed nothing, is left alone.
|
|
39753
|
+
*
|
|
39754
|
+
* @param ctx - Domain context of the plugin.
|
|
39755
|
+
* @param leaving - The look of the switch.
|
|
39756
|
+
* @param how - `"land"` after the swap, `"drop"` without one.
|
|
39757
|
+
*/
|
|
39758
|
+
function endLeave(ctx, leaving, how) {
|
|
39759
|
+
if (leaving === void 0 || leaving.done) return;
|
|
39760
|
+
leaving.done = true;
|
|
39761
|
+
leaving.off();
|
|
39762
|
+
if (ctx.state.leaving === leaving) delete ctx.state.leaving;
|
|
39763
|
+
const leave = ctx.deps.renderer.sync.leave;
|
|
39764
|
+
if (how === "land") leave.land();
|
|
39765
|
+
else leave.drop();
|
|
39766
|
+
unmountLoader(ctx, leaving);
|
|
39767
|
+
}
|
|
39768
|
+
/**
|
|
39769
|
+
* Forgets the look of a running switch when the plugin stops: its abort listener goes and a later end does nothing.
|
|
39770
|
+
* Nothing else is called: a teardown context may not call another plugin; the renderer drops the look at its own stop,
|
|
39771
|
+
* and `world.onStop` the loader projection.
|
|
39772
|
+
*
|
|
39773
|
+
* @param state - The plugin state.
|
|
39774
|
+
*/
|
|
39775
|
+
function forgetLeave(state) {
|
|
39776
|
+
const leaving = state.leaving;
|
|
39777
|
+
if (leaving === void 0) return;
|
|
39778
|
+
leaving.done = true;
|
|
39779
|
+
leaving.off();
|
|
39780
|
+
delete state.leaving;
|
|
39781
|
+
}
|
|
39782
|
+
//#endregion
|
|
39783
|
+
//#region src/plugins/scenes/leave-check.ts
|
|
39784
|
+
/** The largest colour, `0xffffff`. */
|
|
39785
|
+
const MAX_COLOR = 16777215;
|
|
39786
|
+
/**
|
|
39787
|
+
* Tells whether a value is a finite number of at least 0: a duration.
|
|
39788
|
+
*
|
|
39789
|
+
* @param value - The value.
|
|
39790
|
+
* @returns True for a finite number `>= 0`.
|
|
39791
|
+
* @example
|
|
39792
|
+
* ```ts
|
|
39793
|
+
* isDuration(-1); // false
|
|
39794
|
+
* ```
|
|
39795
|
+
*/
|
|
39796
|
+
function isDuration(value) {
|
|
39797
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0;
|
|
39798
|
+
}
|
|
39799
|
+
/**
|
|
39800
|
+
* Tells whether a value is an alpha: a number from 0 to 1.
|
|
39801
|
+
*
|
|
39802
|
+
* @param value - The value.
|
|
39803
|
+
* @returns True for a number in `[0, 1]`.
|
|
39804
|
+
* @example
|
|
39805
|
+
* ```ts
|
|
39806
|
+
* isAlpha(1.2); // false
|
|
39807
|
+
* ```
|
|
39808
|
+
*/
|
|
39809
|
+
function isAlpha(value) {
|
|
39810
|
+
return typeof value === "number" && value >= 0 && value <= 1;
|
|
39811
|
+
}
|
|
39812
|
+
/**
|
|
39813
|
+
* Tells whether a value is a colour `0xRRGGBB`: a whole number from 0 to `0xffffff`.
|
|
39814
|
+
*
|
|
39815
|
+
* @param value - The value.
|
|
39816
|
+
* @returns True for a colour.
|
|
39817
|
+
* @example
|
|
39818
|
+
* ```ts
|
|
39819
|
+
* isColor(0x0b1020); // true
|
|
39820
|
+
* ```
|
|
39821
|
+
*/
|
|
39822
|
+
function isColor$1(value) {
|
|
39823
|
+
return Number.isInteger(value) && typeof value === "number" && value >= 0 && value <= MAX_COLOR;
|
|
39824
|
+
}
|
|
39825
|
+
/**
|
|
39826
|
+
* Tells whether a value is a name: a string that is not empty.
|
|
39827
|
+
*
|
|
39828
|
+
* @param value - The value.
|
|
39829
|
+
* @returns True for a string with at least one character.
|
|
39830
|
+
* @example
|
|
39831
|
+
* ```ts
|
|
39832
|
+
* isName(""); // false
|
|
39833
|
+
* ```
|
|
39834
|
+
*/
|
|
39835
|
+
function isName(value) {
|
|
39836
|
+
return typeof value === "string" && value.length > 0;
|
|
39837
|
+
}
|
|
39838
|
+
/**
|
|
39839
|
+
* Tells whether a value is a plain object a field can be read from.
|
|
39840
|
+
*
|
|
39841
|
+
* @param value - The value.
|
|
39842
|
+
* @returns True for an object that is no array and not `null`.
|
|
39843
|
+
* @example
|
|
39844
|
+
* ```ts
|
|
39845
|
+
* isRecord([]); // false
|
|
39846
|
+
* ```
|
|
39847
|
+
*/
|
|
39848
|
+
function isRecord$2(value) {
|
|
39849
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
39850
|
+
}
|
|
39851
|
+
/** The fields of a veil. */
|
|
39852
|
+
const COVER_RULES = Object.freeze({
|
|
39853
|
+
color: {
|
|
39854
|
+
ok: isColor$1,
|
|
39855
|
+
hint: "a colour from 0x000000 to 0xffffff"
|
|
39856
|
+
},
|
|
39857
|
+
alpha: {
|
|
39858
|
+
ok: isAlpha,
|
|
39859
|
+
hint: "the alpha the veil fades in to, a number from 0 to 1"
|
|
39860
|
+
},
|
|
39861
|
+
inMs: {
|
|
39862
|
+
ok: isDuration,
|
|
39863
|
+
hint: "the milliseconds of the fade-in, 0 or more"
|
|
39864
|
+
},
|
|
39865
|
+
outMs: {
|
|
39866
|
+
ok: isDuration,
|
|
39867
|
+
hint: "the milliseconds of the fade-out, 0 or more"
|
|
39868
|
+
}
|
|
39869
|
+
});
|
|
39870
|
+
/** The fields of a loader. */
|
|
39871
|
+
const LOADER_RULES = Object.freeze({
|
|
39872
|
+
afterMs: {
|
|
39873
|
+
ok: isDuration,
|
|
39874
|
+
hint: "the milliseconds the loader waits for, 0 or more"
|
|
39875
|
+
},
|
|
39876
|
+
texture: {
|
|
39877
|
+
ok: isName,
|
|
39878
|
+
hint: "an asset key such as \"ui.loader.ring\""
|
|
39879
|
+
},
|
|
39880
|
+
projection: {
|
|
39881
|
+
ok: isName,
|
|
39882
|
+
hint: "the name of a registered projection such as \"loading\""
|
|
39883
|
+
}
|
|
39884
|
+
});
|
|
39885
|
+
/**
|
|
39886
|
+
* Builds the error of a field of the wrong shape.
|
|
39887
|
+
*
|
|
39888
|
+
* @param where - Where the part was written.
|
|
39889
|
+
* @param value - What the field holds.
|
|
39890
|
+
* @param hint - What to give instead.
|
|
39891
|
+
* @returns The error, in the engine's two-line format.
|
|
39892
|
+
* @example
|
|
39893
|
+
* ```ts
|
|
39894
|
+
* wrong("scenes.leave.cover.alpha", 2, "a number from 0 to 1").message;
|
|
39895
|
+
* // '[game] scenes.leave.cover.alpha is 2.\n Give a number from 0 to 1.'
|
|
39896
|
+
* ```
|
|
39897
|
+
*/
|
|
39898
|
+
function wrong(where, value, hint) {
|
|
39899
|
+
return /* @__PURE__ */ new Error(`[game] ${where} is ${String(value)}.\n Give ${hint}.`);
|
|
39900
|
+
}
|
|
39901
|
+
/**
|
|
39902
|
+
* Checks one part of a `leave`: nothing, `false`, or an object whose every field it knows has the right shape.
|
|
39903
|
+
*
|
|
39904
|
+
* @param where - Where the part was written, `scenes.leave.cover`.
|
|
39905
|
+
* @param part - What the part holds.
|
|
39906
|
+
* @param rules - The fields of the part.
|
|
39907
|
+
* @throws {Error} For a part that is neither `false` nor an object, and for the first field of the wrong shape.
|
|
39908
|
+
*/
|
|
39909
|
+
function checkPart(where, part, rules) {
|
|
39910
|
+
if (part === void 0 || part === false) return;
|
|
39911
|
+
if (!isRecord$2(part)) throw wrong(where, part, "an object of its fields, or false");
|
|
39912
|
+
for (const [field, rule] of Object.entries(rules)) {
|
|
39913
|
+
const value = part[field];
|
|
39914
|
+
if (value !== void 0 && !rule.ok(value)) throw wrong(`${where}.${field}`, value, rule.hint);
|
|
39915
|
+
}
|
|
39916
|
+
}
|
|
39917
|
+
/**
|
|
39918
|
+
* Checks a `leave` a game wrote: nothing, `false`, or an object whose `cover` and `loader` are each nothing, `false`
|
|
39919
|
+
* 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
|
|
39920
|
+
* empty.
|
|
39921
|
+
*
|
|
39922
|
+
* @param where - Where it was written, for the message: `scenes.leave`, or `Scene "board": leave`.
|
|
39923
|
+
* @param leave - What was written.
|
|
39924
|
+
* @throws {Error} For the first part or field of the wrong shape.
|
|
39925
|
+
* @example
|
|
39926
|
+
* ```ts
|
|
39927
|
+
* checkLeave("scenes.leave", { cover: { alpha: 2 } });
|
|
39928
|
+
* // throws '[game] scenes.leave.cover.alpha is 2.\n Give the alpha the veil fades in to, a number from 0 to 1.'
|
|
39929
|
+
* ```
|
|
39930
|
+
*/
|
|
39931
|
+
function checkLeave(where, leave) {
|
|
39932
|
+
if (leave === void 0 || leave === false) return;
|
|
39933
|
+
if (!isRecord$2(leave)) throw wrong(where, leave, "{ cover, loader } or false");
|
|
39934
|
+
checkPart(`${where}.cover`, leave.cover, COVER_RULES);
|
|
39935
|
+
checkPart(`${where}.loader`, leave.loader, LOADER_RULES);
|
|
39936
|
+
}
|
|
39937
|
+
//#endregion
|
|
39104
39938
|
//#region src/plugins/scenes/switch.ts
|
|
39105
39939
|
/**
|
|
39106
39940
|
* A promise that resolves when the node was aborted. Already aborted resolves at once.
|
|
@@ -39298,11 +40132,31 @@ async function standUp(ctx, scene, named, run) {
|
|
|
39298
40132
|
swapIn(ctx, scene, named);
|
|
39299
40133
|
}
|
|
39300
40134
|
/**
|
|
39301
|
-
*
|
|
39302
|
-
*
|
|
39303
|
-
*
|
|
39304
|
-
*
|
|
39305
|
-
*
|
|
40135
|
+
* Awaits every bundle the scene names and only then builds the scene. In live mode it waits between
|
|
40136
|
+
* the two for the textures of those bundles to reach the GPU; a fast walk does not, so tests and
|
|
40137
|
+
* restores keep their pace. An aborted node, or a plugin stopped during the wait, returns without
|
|
40138
|
+
* touching anything. A live switch with a drawing renderer resolves at the commit of the scene
|
|
40139
|
+
* built aside, frames later.
|
|
40140
|
+
*
|
|
40141
|
+
* @param ctx - Domain context of the plugin.
|
|
40142
|
+
* @param scene - The scene to mount.
|
|
40143
|
+
* @param named - The bundles it names, read from the committed state.
|
|
40144
|
+
* @param run - The runner's mode and signal.
|
|
40145
|
+
* @returns A promise that resolves once the scene stands, or the node was aborted.
|
|
40146
|
+
* @throws {Error} Whatever a load, the mount or the build threw.
|
|
40147
|
+
*/
|
|
40148
|
+
async function loadAndStand(ctx, scene, named, run) {
|
|
40149
|
+
await untilAborted(ctx, named, run.signal);
|
|
40150
|
+
if (run.mode === "live" && !run.signal.aborted) await untilWarmed(ctx, named, run.signal);
|
|
40151
|
+
const stopped = ctx.state.teardown === void 0;
|
|
40152
|
+
if (run.signal.aborted || stopped) return;
|
|
40153
|
+
await standUp(ctx, scene, named, run);
|
|
40154
|
+
}
|
|
40155
|
+
/**
|
|
40156
|
+
* Switches to another scene: it reads what the scene names from the committed state, shows the
|
|
40157
|
+
* veil and the loader of the scene being left at once (a cold first visit reacts in the next
|
|
40158
|
+
* frame), then awaits the bundles and builds the scene (`loadAndStand`). The look lands when the
|
|
40159
|
+
* scene stands, and is dropped when the switch ends without it or throws.
|
|
39306
40160
|
*
|
|
39307
40161
|
* @param ctx - Domain context of the plugin.
|
|
39308
40162
|
* @param target - Id of the scene to mount.
|
|
@@ -39314,11 +40168,15 @@ async function switchTo(ctx, target, run) {
|
|
|
39314
40168
|
const scene = ctx.state.scenes.get(target);
|
|
39315
40169
|
if (scene === void 0) throw new Error(`[game] Scene "${target}" is not registered.\n List it in the scenes of a feature.`);
|
|
39316
40170
|
const named = namedBundles(scene, committedState(ctx));
|
|
39317
|
-
|
|
39318
|
-
|
|
39319
|
-
|
|
39320
|
-
|
|
39321
|
-
|
|
40171
|
+
let leaving;
|
|
40172
|
+
try {
|
|
40173
|
+
leaving = startLeave(ctx, scene, run);
|
|
40174
|
+
await loadAndStand(ctx, scene, named, run);
|
|
40175
|
+
} catch (error) {
|
|
40176
|
+
endLeave(ctx, leaving, "drop");
|
|
40177
|
+
throw error;
|
|
40178
|
+
}
|
|
40179
|
+
endLeave(ctx, leaving, ctx.state.current === scene.id ? "land" : "drop");
|
|
39322
40180
|
}
|
|
39323
40181
|
/**
|
|
39324
40182
|
* Reads again what the mounted scene names, at a node that keeps it: a bundle it names now and
|
|
@@ -39432,15 +40290,17 @@ function readScenes(ctx) {
|
|
|
39432
40290
|
}
|
|
39433
40291
|
}
|
|
39434
40292
|
/**
|
|
39435
|
-
* Refuses a
|
|
39436
|
-
* `"auto"` nor a finite number above 0
|
|
39437
|
-
* is a boundary.
|
|
40293
|
+
* Refuses a config a switch cannot run with, in `onInit`, so `createApp` throws: a building budget
|
|
40294
|
+
* that is neither `"auto"` nor a finite number above 0, and a `leave` of the wrong shape. A game
|
|
40295
|
+
* written in plain JS passes anything, so the config is a boundary.
|
|
39438
40296
|
*
|
|
39439
40297
|
* @param config - The resolved config of the plugin.
|
|
39440
|
-
* @throws {Error} When `buildMs` is neither `"auto"` nor a finite number above 0
|
|
40298
|
+
* @throws {Error} When `buildMs` is neither `"auto"` nor a finite number above 0, and for a `leave`
|
|
40299
|
+
* `checkLeave` refuses.
|
|
39441
40300
|
*/
|
|
39442
40301
|
function checkConfig(config) {
|
|
39443
40302
|
const value = config.buildMs;
|
|
40303
|
+
checkLeave("scenes.leave", config.leave);
|
|
39444
40304
|
if (value === "auto") return;
|
|
39445
40305
|
if (isPositiveFinite(value)) return;
|
|
39446
40306
|
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 +40324,8 @@ function startScenes(ctx) {
|
|
|
39464
40324
|
}
|
|
39465
40325
|
/**
|
|
39466
40326
|
* 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
|
|
40327
|
+
* one named. A running build loses its frame callback and its switch returns; the look of a running
|
|
40328
|
+
* switch loses its abort listener (the renderer drops it at its own stop). Nothing is unmounted
|
|
39468
40329
|
* or discarded: a teardown context may not call another plugin, and `world.onStop` clears every
|
|
39469
40330
|
* entity and the build table anyway.
|
|
39470
40331
|
*
|
|
@@ -39482,6 +40343,7 @@ function stopScenes(teardown) {
|
|
|
39482
40343
|
state.teardown = void 0;
|
|
39483
40344
|
dropBuild(state.build);
|
|
39484
40345
|
delete state.build;
|
|
40346
|
+
forgetLeave(state);
|
|
39485
40347
|
state.scenes.clear();
|
|
39486
40348
|
state.current = void 0;
|
|
39487
40349
|
state.pending = void 0;
|
|
@@ -39550,6 +40412,9 @@ function createScenesState() {
|
|
|
39550
40412
|
*
|
|
39551
40413
|
* // 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
40414
|
* createApp({ plugins: [...screen, boardFeature], pluginConfigs: { scenes: { buildMs: 3 } } });
|
|
40415
|
+
*
|
|
40416
|
+
* // No veil and no loader over a scene being left, on any switch.
|
|
40417
|
+
* createApp({ plugins: [...screen, boardFeature], pluginConfigs: { scenes: { leave: false } } });
|
|
39553
40418
|
* ```
|
|
39554
40419
|
*/
|
|
39555
40420
|
const scenesPlugin = /*#__PURE__*/ createPlugin$1("scenes", {
|
|
@@ -39561,7 +40426,18 @@ const scenesPlugin = /*#__PURE__*/ createPlugin$1("scenes", {
|
|
|
39561
40426
|
modelPlugin,
|
|
39562
40427
|
rendererPlugin
|
|
39563
40428
|
],
|
|
39564
|
-
config: {
|
|
40429
|
+
config: {
|
|
40430
|
+
buildMs: "auto",
|
|
40431
|
+
leave: {
|
|
40432
|
+
cover: {
|
|
40433
|
+
color: 0,
|
|
40434
|
+
alpha: .45,
|
|
40435
|
+
inMs: 150,
|
|
40436
|
+
outMs: 150
|
|
40437
|
+
},
|
|
40438
|
+
loader: { afterMs: 400 }
|
|
40439
|
+
}
|
|
40440
|
+
},
|
|
39565
40441
|
events: (register) => register.map({ "scenes:changed": "The mounted scene changed" }),
|
|
39566
40442
|
createState: createScenesState,
|
|
39567
40443
|
api: createScenesApi,
|
|
@@ -64680,14 +65556,15 @@ function namesOf(id, layers, projections) {
|
|
|
64680
65556
|
* layer names come from the keys of `layers` and that `ui`, so a projection whose `layer` or
|
|
64681
65557
|
* `lift` is not among them does not compile — and throws here for a caller without types.
|
|
64682
65558
|
* `bundles` names bundles that depend on the committed state: the scene is mounted once those
|
|
64683
|
-
* are in too.
|
|
65559
|
+
* are in too. `leave` is what a live switch shows over the scene when it is left, merged over the
|
|
65560
|
+
* config's: `false` shows nothing.
|
|
64684
65561
|
*
|
|
64685
65562
|
* @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
|
|
65563
|
+
* @param scene - The bundle, the optional bundles of state, the layers, the projections, the
|
|
65564
|
+
* optional music key and the optional `leave`.
|
|
64688
65565
|
* @returns The scene as frozen data, ready for the `scenes` key of a feature.
|
|
64689
65566
|
* @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"`,
|
|
65567
|
+
* an `above` other than `"overlays"`, a `ui` declared after the HUD layer, and a `leave` of the wrong shape.
|
|
64691
65568
|
* @example
|
|
64692
65569
|
* ```ts
|
|
64693
65570
|
* // features/board/view/scene.ts of a game.
|
|
@@ -64712,18 +65589,28 @@ function namesOf(id, layers, projections) {
|
|
|
64712
65589
|
* projections: [wardRoom, wardGlows]
|
|
64713
65590
|
* });
|
|
64714
65591
|
* wardScene.bundles?.({ player: {}, session: { episode: "ep02" } }); // ["ep02"]
|
|
65592
|
+
*
|
|
65593
|
+
* // Leaving the board dims it in navy and turns the game's ring texture once the switch took 400 ms.
|
|
65594
|
+
* defineScene("board", {
|
|
65595
|
+
* bundle: "board",
|
|
65596
|
+
* layers: { cells: {} },
|
|
65597
|
+
* projections: [],
|
|
65598
|
+
* leave: { cover: { color: 0x0b1020, alpha: 0.6 }, loader: { texture: "ui.loader.ring" } }
|
|
65599
|
+
* }).leave; // { cover: { color: 0x0b1020, alpha: 0.6 }, loader: { texture: "ui.loader.ring" } }
|
|
64715
65600
|
* ```
|
|
64716
65601
|
*/
|
|
64717
65602
|
function defineScene(id, scene) {
|
|
64718
65603
|
const layers = layersOf(id, scene.layers);
|
|
64719
65604
|
const projections = scene.projections;
|
|
65605
|
+
checkLeave(`Scene "${id}": leave`, scene.leave);
|
|
64720
65606
|
return Object.freeze({
|
|
64721
65607
|
id,
|
|
64722
65608
|
bundle: scene.bundle,
|
|
64723
65609
|
music: scene.music,
|
|
64724
65610
|
layers: Object.freeze(layers),
|
|
64725
65611
|
projections: Object.freeze(namesOf(id, layers, projections)),
|
|
64726
|
-
...scene.bundles === void 0 ? {} : { bundles: scene.bundles }
|
|
65612
|
+
...scene.bundles === void 0 ? {} : { bundles: scene.bundles },
|
|
65613
|
+
...scene.leave === void 0 ? {} : { leave: scene.leave }
|
|
64727
65614
|
});
|
|
64728
65615
|
}
|
|
64729
65616
|
/**
|
|
@@ -65156,6 +66043,7 @@ function cubicBezier(x1, y1, x2, y2) {
|
|
|
65156
66043
|
* | assets | baseUrl | undefined |
|
|
65157
66044
|
* | assets | io | undefined, the browser fetch and the renderer textures |
|
|
65158
66045
|
* | 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 |
|
|
66046
|
+
* | 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
66047
|
* | anim | maxTracks | 2000 |
|
|
65160
66048
|
* | anim | reducedMotion | false |
|
|
65161
66049
|
* | i18n | locale | "en" |
|