@moku-labs/game 0.15.6 → 0.15.8
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-CAEwtEAy.d.mts → commands-DWyII1jn.d.mts} +13 -5
- package/dist/control.d.mts +4 -4
- package/dist/control.mjs +1 -1
- package/dist/{define-CNctRrea.d.mts → define-Bebctfvs.d.mts} +2 -2
- package/dist/{headless-DDkRwLnJ.d.mts → headless-DobgwTKK.d.mts} +1 -1
- package/dist/{index-DytzVJFz.d.mts → index-B3dfnsUZ.d.mts} +14 -11
- package/dist/index.d.mts +5 -5
- package/dist/index.mjs +2 -2
- package/dist/inspect.d.mts +6 -6
- package/dist/jsx-dev-runtime.d.mts +1 -1
- package/dist/jsx-runtime.d.mts +1 -1
- package/dist/{run-C07NXds7.mjs → run-BqYmcNo7.mjs} +39 -6
- package/dist/{runtime-BGsDXKuO.d.mts → runtime-gnMApclv.d.mts} +1 -1
- package/dist/{src-DWJabPi4.mjs → src-DNVAJHwd.mjs} +2128 -398
- package/dist/testing.d.mts +4 -4
- package/dist/testing.mjs +1 -1
- package/dist/{types-CRgEt4wq.d.mts → types-BcEtn9XV.d.mts} +4 -4
- package/dist/{types-DnttyLeu.d.mts → types-C4D9UVgb.d.mts} +121 -35
- package/dist/{types-C2LZJIxH.d.mts → types-CPxx3NSG.d.mts} +90 -16
- package/dist/{types-CWSMo70I.d.mts → types-CYoGvBkh.d.mts} +114 -6
- package/dist/{types-DFoxDq-g.d.mts → types-Mj75CDO6.d.mts} +2 -2
- package/dist/{visual-gTuXlmvH.d.mts → visual-CxQ4Iqd3.d.mts} +6 -6
- package/dist/visual.d.mts +1 -1
- package/dist/visual.mjs +1 -1
- package/llms.txt +4 -4
- package/package.json +1 -1
|
@@ -598,6 +598,16 @@ const DROP_SHARE = .1;
|
|
|
598
598
|
const DWELL_BASE_MS = 5e3;
|
|
599
599
|
/** The dwell never grows past this, ms. */
|
|
600
600
|
const DWELL_MAX_MS = 12e4;
|
|
601
|
+
/** The render scales the adaptive rule steps through, highest first: shares of the canvas resolution. */
|
|
602
|
+
const SCALE_STEPS = Object.freeze([
|
|
603
|
+
1,
|
|
604
|
+
.85,
|
|
605
|
+
.75,
|
|
606
|
+
.6,
|
|
607
|
+
.5
|
|
608
|
+
]);
|
|
609
|
+
/** No scale to step through: the renderer allows none. */
|
|
610
|
+
const NO_SCALES = Object.freeze([]);
|
|
601
611
|
/** The thermal statuses in order of heat: Android's `THERMAL_STATUS_*` numbers are their indexes. */
|
|
602
612
|
const THERMAL_STATUSES = [
|
|
603
613
|
"none",
|
|
@@ -632,6 +642,22 @@ function ladderOf(displayHz, maxFps, floor) {
|
|
|
632
642
|
return Object.freeze(steps);
|
|
633
643
|
}
|
|
634
644
|
/**
|
|
645
|
+
* The render scales the rule may choose down to a floor, highest first, frozen; none when 1 alone reaches it. A floor
|
|
646
|
+
* between two steps stops at the step above it (the renderer's config only takes a step). Allocates: called when the
|
|
647
|
+
* renderer allows a floor, never per frame.
|
|
648
|
+
*
|
|
649
|
+
* @param floor - The lowest scale the renderer allows.
|
|
650
|
+
* @returns The scales.
|
|
651
|
+
* @example
|
|
652
|
+
* ```ts
|
|
653
|
+
* scaleStepsOf(0.75); // [1, 0.85, 0.75]
|
|
654
|
+
* ```
|
|
655
|
+
*/
|
|
656
|
+
function scaleStepsOf(floor) {
|
|
657
|
+
const steps = SCALE_STEPS.filter((step) => step >= floor);
|
|
658
|
+
return steps.length > 1 ? Object.freeze(steps) : NO_SCALES;
|
|
659
|
+
}
|
|
660
|
+
/**
|
|
635
661
|
* The index in `DISPLAY_RATES` a tick interval votes for: the rate whose period is within `DISPLAY_TOLERANCE` of
|
|
636
662
|
* it, or -1. An indexed loop: no allocation.
|
|
637
663
|
*
|
|
@@ -789,17 +815,20 @@ function thermalTarget(ladder, status, fps) {
|
|
|
789
815
|
//#region src/plugins/time/rate.ts
|
|
790
816
|
/**
|
|
791
817
|
* @file time — the adaptive frame rate (spec 21 G3–G6): the governor that drops to the next divisor of the display
|
|
792
|
-
* rate when drawn frames come late and returns one step after steady room,
|
|
793
|
-
*
|
|
794
|
-
*
|
|
795
|
-
*
|
|
818
|
+
* rate when drawn frames come late and returns one step after steady room, on a GPU-bound screen the render scale
|
|
819
|
+
* stepped before the rate, the back-off of failed returns, the display rate learned from the ticks in every app (and
|
|
820
|
+
* with the governor from the native shell), the thermal input, and `setRate`. Every number of the per-frame path lives
|
|
821
|
+
* in the governor and the display state, allocated once; a frame allocates nothing here. Only a change allocates: its
|
|
822
|
+
* payload, the snapshot, the request.
|
|
796
823
|
*/
|
|
797
|
-
/** The ladder before the display rate is known, and always without `adaptive
|
|
824
|
+
/** The ladder before the display rate is known, and always without `adaptive`; the scales the renderer allows none of. */
|
|
798
825
|
const NO_STEPS = Object.freeze([]);
|
|
799
826
|
/** Bit of a sample that came late. */
|
|
800
827
|
const MISSED = 1;
|
|
801
828
|
/** Bit of a sample whose frame was heavy for the higher rate. */
|
|
802
829
|
const HEAVY = 2;
|
|
830
|
+
/** Bit of a late sample whose frame kept the main thread longer than its frame. */
|
|
831
|
+
const BUSY = 4;
|
|
803
832
|
/** The thermal rank from which the rule never steps up: `"moderate"` and every hotter status. */
|
|
804
833
|
const HEAT_LIMIT_RANK = 2;
|
|
805
834
|
/** Milliseconds in a second: turns a rate into the period of one frame. */
|
|
@@ -852,6 +881,7 @@ function createGovernor(floor) {
|
|
|
852
881
|
return {
|
|
853
882
|
floor,
|
|
854
883
|
ladder: NO_STEPS,
|
|
884
|
+
scales: NO_STEPS,
|
|
855
885
|
thermal: null,
|
|
856
886
|
lastDrawn: false,
|
|
857
887
|
lastIdle: false,
|
|
@@ -865,12 +895,14 @@ function createGovernor(floor) {
|
|
|
865
895
|
dropTail: 0,
|
|
866
896
|
dropDrawn: 0,
|
|
867
897
|
dropMissed: 0,
|
|
898
|
+
dropBusy: 0,
|
|
868
899
|
returnTail: 0,
|
|
869
900
|
returnDrawn: 0,
|
|
870
901
|
returnMissed: 0,
|
|
871
902
|
returnHeavy: 0,
|
|
872
903
|
switchedAt: Number.NEGATIVE_INFINITY,
|
|
873
904
|
lastReturnAt: Number.NEGATIVE_INFINITY,
|
|
905
|
+
climbed: false,
|
|
874
906
|
dwellMs: DWELL_BASE_MS,
|
|
875
907
|
requested: 0,
|
|
876
908
|
requestSeq: 0
|
|
@@ -906,7 +938,8 @@ function snapshotOf(rate) {
|
|
|
906
938
|
ladder: governor?.ladder ?? NO_STEPS,
|
|
907
939
|
thermal: governor?.thermal ?? null,
|
|
908
940
|
asked,
|
|
909
|
-
heldBy: asked !== null && hz !== null && asked > hz ? "panel" : null
|
|
941
|
+
heldBy: asked !== null && hz !== null && asked > hz ? "panel" : null,
|
|
942
|
+
scale: rate.scale
|
|
910
943
|
});
|
|
911
944
|
}
|
|
912
945
|
/**
|
|
@@ -923,6 +956,7 @@ function createRateState(config) {
|
|
|
923
956
|
const mode = floor === void 0 ? "fixed" : "auto";
|
|
924
957
|
const parts = {
|
|
925
958
|
fps: config.maxFps,
|
|
959
|
+
scale: 1,
|
|
926
960
|
mode,
|
|
927
961
|
reason: "start",
|
|
928
962
|
display: createDisplayState(),
|
|
@@ -953,6 +987,7 @@ function emptyWindows(g) {
|
|
|
953
987
|
g.dropTail = g.written;
|
|
954
988
|
g.dropDrawn = 0;
|
|
955
989
|
g.dropMissed = 0;
|
|
990
|
+
g.dropBusy = 0;
|
|
956
991
|
g.returnTail = g.written;
|
|
957
992
|
g.returnDrawn = 0;
|
|
958
993
|
g.returnMissed = 0;
|
|
@@ -967,6 +1002,7 @@ function dropOldest(g) {
|
|
|
967
1002
|
const flags = g.sampleFlags[g.dropTail % 512] ?? 0;
|
|
968
1003
|
g.dropDrawn -= 1;
|
|
969
1004
|
if ((flags & MISSED) !== 0) g.dropMissed -= 1;
|
|
1005
|
+
if ((flags & BUSY) !== 0) g.dropBusy -= 1;
|
|
970
1006
|
g.dropTail += 1;
|
|
971
1007
|
}
|
|
972
1008
|
/**
|
|
@@ -1022,8 +1058,19 @@ function evidenceOf(g, reason) {
|
|
|
1022
1058
|
};
|
|
1023
1059
|
}
|
|
1024
1060
|
/**
|
|
1025
|
-
*
|
|
1026
|
-
*
|
|
1061
|
+
* Starts the back-off over: the base dwell, no return to judge.
|
|
1062
|
+
*
|
|
1063
|
+
* @param g - The governor.
|
|
1064
|
+
*/
|
|
1065
|
+
function resetDwell(g) {
|
|
1066
|
+
g.dwellMs = DWELL_BASE_MS;
|
|
1067
|
+
g.lastReturnAt = Number.NEGATIVE_INFINITY;
|
|
1068
|
+
g.climbed = false;
|
|
1069
|
+
}
|
|
1070
|
+
/**
|
|
1071
|
+
* The dwell's bookkeeping of a switch: the first drop after a return judges it (held less than a minute it failed and
|
|
1072
|
+
* the dwell doubles, held longer the dwell starts over), the steps down after it leave the dwell, a return notes when
|
|
1073
|
+
* it happened, `setRate` starts it over; the display and the heat leave it.
|
|
1027
1074
|
*
|
|
1028
1075
|
* @param g - The governor.
|
|
1029
1076
|
* @param reason - The reason of the switch.
|
|
@@ -1031,19 +1078,16 @@ function evidenceOf(g, reason) {
|
|
|
1031
1078
|
*/
|
|
1032
1079
|
function keepDwell(g, reason, at) {
|
|
1033
1080
|
switch (reason) {
|
|
1034
|
-
case "missed":
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
else if (held >= 6e4) g.dwellMs = DWELL_BASE_MS;
|
|
1081
|
+
case "missed":
|
|
1082
|
+
if (g.climbed) g.dwellMs = at - g.lastReturnAt < 6e4 ? Math.min(2 * g.dwellMs, DWELL_MAX_MS) : DWELL_BASE_MS;
|
|
1083
|
+
g.climbed = false;
|
|
1038
1084
|
break;
|
|
1039
|
-
}
|
|
1040
1085
|
case "headroom":
|
|
1041
1086
|
g.lastReturnAt = at;
|
|
1087
|
+
g.climbed = true;
|
|
1042
1088
|
break;
|
|
1043
1089
|
case "forced":
|
|
1044
|
-
case "auto":
|
|
1045
|
-
g.dwellMs = DWELL_BASE_MS;
|
|
1046
|
-
g.lastReturnAt = Number.NEGATIVE_INFINITY;
|
|
1090
|
+
case "auto": resetDwell(g);
|
|
1047
1091
|
}
|
|
1048
1092
|
}
|
|
1049
1093
|
/**
|
|
@@ -1082,22 +1126,25 @@ function requestRate(ctx) {
|
|
|
1082
1126
|
followNativeRate(ctx, (answer) => readAnswer(ctx, g, seq, answer));
|
|
1083
1127
|
}
|
|
1084
1128
|
/**
|
|
1085
|
-
* The one place the rate or
|
|
1086
|
-
* when
|
|
1087
|
-
* snapshot is rebuilt, `time:rate` is emitted with the evidence, the change is logged and the shell is
|
|
1088
|
-
* new rate. The new cap applies from the next tick: the frame that is due still runs.
|
|
1129
|
+
* The one place the rate, the render scale or the mode changes (the governor, the display, the heat and `setRate`
|
|
1130
|
+
* call it). Nothing when all stay. Otherwise the dwell is kept, both windows are emptied and the next interval is
|
|
1131
|
+
* skipped, the snapshot is rebuilt, `time:rate` is emitted with the evidence, the change is logged and the shell is
|
|
1132
|
+
* asked for the new rate. The new cap applies from the next tick: the frame that is due still runs, at the new scale.
|
|
1089
1133
|
*
|
|
1090
1134
|
* @param ctx - Domain context of the time plugin.
|
|
1091
1135
|
* @param to - The new rate.
|
|
1136
|
+
* @param scale - The new render scale.
|
|
1092
1137
|
* @param mode - The new mode.
|
|
1093
1138
|
* @param reason - Why.
|
|
1094
1139
|
* @param at - When, on the page clock.
|
|
1095
1140
|
*/
|
|
1096
|
-
function switchRate(ctx, to, mode, reason, at) {
|
|
1141
|
+
function switchRate(ctx, to, scale, mode, reason, at) {
|
|
1097
1142
|
const { rate } = ctx.state;
|
|
1098
|
-
if (to === rate.fps && mode === rate.mode) return;
|
|
1143
|
+
if (to === rate.fps && scale === rate.scale && mode === rate.mode) return;
|
|
1099
1144
|
const g = rate.governor;
|
|
1100
1145
|
const from = rate.fps;
|
|
1146
|
+
const fromScale = rate.scale;
|
|
1147
|
+
const scaleOnly = to === from && mode === rate.mode;
|
|
1101
1148
|
const evidence = evidenceOf(g, reason);
|
|
1102
1149
|
if (g !== void 0) {
|
|
1103
1150
|
keepDwell(g, reason, at);
|
|
@@ -1106,6 +1153,7 @@ function switchRate(ctx, to, mode, reason, at) {
|
|
|
1106
1153
|
emptyWindows(g);
|
|
1107
1154
|
}
|
|
1108
1155
|
rate.fps = to;
|
|
1156
|
+
rate.scale = scale;
|
|
1109
1157
|
rate.mode = mode;
|
|
1110
1158
|
rate.reason = reason;
|
|
1111
1159
|
rate.snapshot = snapshotOf(rate);
|
|
@@ -1117,8 +1165,15 @@ function switchRate(ctx, to, mode, reason, at) {
|
|
|
1117
1165
|
displayHz: rate.display.hz,
|
|
1118
1166
|
thermal: g?.thermal ?? null,
|
|
1119
1167
|
window: evidence,
|
|
1120
|
-
dwellMs: g?.dwellMs ?? 0
|
|
1168
|
+
dwellMs: g?.dwellMs ?? 0,
|
|
1169
|
+
scale
|
|
1170
|
+
});
|
|
1171
|
+
if (scale !== fromScale) ctx.log.info("time: render scale", {
|
|
1172
|
+
from: fromScale,
|
|
1173
|
+
to: scale,
|
|
1174
|
+
reason
|
|
1121
1175
|
});
|
|
1176
|
+
if (scaleOnly) return;
|
|
1122
1177
|
ctx.log.info("time: frame rate", {
|
|
1123
1178
|
from,
|
|
1124
1179
|
to,
|
|
@@ -1137,7 +1192,7 @@ function applyThermal(ctx, g) {
|
|
|
1137
1192
|
const { rate } = ctx.state;
|
|
1138
1193
|
if (rate.mode !== "auto" || g.ladder.length === 0) return;
|
|
1139
1194
|
const to = thermalTarget(g.ladder, g.thermal, rate.fps);
|
|
1140
|
-
if (to < rate.fps) switchRate(ctx, to, "auto", "thermal", nowOf());
|
|
1195
|
+
if (to < rate.fps) switchRate(ctx, to, rate.scale, "auto", "thermal", nowOf());
|
|
1141
1196
|
}
|
|
1142
1197
|
/**
|
|
1143
1198
|
* Stores a new thermal status of the shell and applies it. A status the shell sent wrong, and the same status
|
|
@@ -1182,7 +1237,7 @@ function learnDisplay(ctx, hz, at) {
|
|
|
1182
1237
|
if (g !== void 0) g.ladder = ladderOf(hz, ctx.config.maxFps, g.floor);
|
|
1183
1238
|
rate.snapshot = snapshotOf(rate);
|
|
1184
1239
|
if (g === void 0 || rate.mode !== "auto") return;
|
|
1185
|
-
if (!g.ladder.includes(rate.fps)) switchRate(ctx, g.ladder.find((step) => step <= rate.fps) ?? g.ladder.at(-1) ?? rate.fps, "auto", "display", at);
|
|
1240
|
+
if (!g.ladder.includes(rate.fps)) switchRate(ctx, g.ladder.find((step) => step <= rate.fps) ?? g.ladder.at(-1) ?? rate.fps, rate.scale, "auto", "display", at);
|
|
1186
1241
|
if (first) applyThermal(ctx, g);
|
|
1187
1242
|
}
|
|
1188
1243
|
/**
|
|
@@ -1279,31 +1334,54 @@ function shouldDrop(g) {
|
|
|
1279
1334
|
return g.dropMissed >= Math.max(8, Math.ceil(DROP_SHARE * g.dropDrawn));
|
|
1280
1335
|
}
|
|
1281
1336
|
/**
|
|
1282
|
-
* Tells whether the
|
|
1283
|
-
*
|
|
1337
|
+
* Tells whether the late frames of the drop window are GPU-bound: fewer than half of them kept the main thread longer
|
|
1338
|
+
* than their frame.
|
|
1339
|
+
*
|
|
1340
|
+
* @param g - The governor.
|
|
1341
|
+
* @returns True when the scale, not the rate, should give.
|
|
1342
|
+
*/
|
|
1343
|
+
function gpuBound(g) {
|
|
1344
|
+
return 2 * g.dropBusy < g.dropMissed;
|
|
1345
|
+
}
|
|
1346
|
+
/**
|
|
1347
|
+
* Tells whether the return window has room to step up: no heat, the dwell passed (twice the dwell for a step of the
|
|
1348
|
+
* scale), 90 drawn frames in three seconds with at most one late, and for a step of the rate a tenth heavy at most.
|
|
1284
1349
|
*
|
|
1285
1350
|
* @param g - The governor.
|
|
1286
1351
|
* @param at - The page time of the sample.
|
|
1287
|
-
* @
|
|
1352
|
+
* @param scaleStep - The step up is one of the render scale: it costs the GPU, not the main thread.
|
|
1353
|
+
* @returns True when the rule may return one step.
|
|
1288
1354
|
*/
|
|
1289
|
-
function canReturn(g, at) {
|
|
1290
|
-
return thermalRank(g.thermal) < HEAT_LIMIT_RANK && at - g.switchedAt >= g.dwellMs && g.returnDrawn >= 90 && g.returnMissed <= 1 && g.returnHeavy <= Math.floor(.1 * g.returnDrawn);
|
|
1355
|
+
function canReturn(g, at, scaleStep) {
|
|
1356
|
+
return thermalRank(g.thermal) < HEAT_LIMIT_RANK && at - g.switchedAt >= (scaleStep ? 2 * g.dwellMs : g.dwellMs) && g.returnDrawn >= 90 && g.returnMissed <= 1 && (scaleStep || g.returnHeavy <= Math.floor(.1 * g.returnDrawn));
|
|
1291
1357
|
}
|
|
1292
1358
|
/**
|
|
1293
|
-
* Decides on the windows after a sample
|
|
1294
|
-
*
|
|
1295
|
-
* no heat
|
|
1359
|
+
* Decides on the windows after a sample. Down one step when late intervals reach `max(8, 10 %)` of the second's: the
|
|
1360
|
+
* render scale when the window is GPU-bound and a lower scale is allowed, the rate otherwise. Up one step in the
|
|
1361
|
+
* reverse of the way down, with 90 steady drawn frames in three seconds, at most one late and no heat: the rate first,
|
|
1362
|
+
* at the scale it dropped at, after the dwell and with a tenth heavy at most, then the scale after twice the dwell. A
|
|
1363
|
+
* probe of the higher rate so never starts at full scale, and its failure changes no resolution.
|
|
1296
1364
|
*
|
|
1297
1365
|
* @param ctx - Domain context of the time plugin.
|
|
1298
1366
|
* @param g - The governor.
|
|
1299
1367
|
* @param at - The page time of the sample.
|
|
1300
1368
|
*/
|
|
1301
1369
|
function decide(ctx, g, at) {
|
|
1302
|
-
const { fps } = ctx.state.rate;
|
|
1303
|
-
|
|
1370
|
+
const { fps, scale } = ctx.state.rate;
|
|
1371
|
+
if (shouldDrop(g)) {
|
|
1372
|
+
const smaller = gpuBound(g) ? stepBelow(g.scales, scale) : void 0;
|
|
1373
|
+
const lower = stepBelow(g.ladder, fps);
|
|
1374
|
+
if (smaller !== void 0) switchRate(ctx, fps, smaller, "auto", "missed", at);
|
|
1375
|
+
else if (lower !== void 0) switchRate(ctx, lower, scale, "auto", "missed", at);
|
|
1376
|
+
return;
|
|
1377
|
+
}
|
|
1304
1378
|
const higher = stepAbove(g.ladder, fps);
|
|
1305
|
-
if (
|
|
1306
|
-
|
|
1379
|
+
if (higher !== void 0 && canReturn(g, at, false)) {
|
|
1380
|
+
switchRate(ctx, higher, scale, "auto", "headroom", at);
|
|
1381
|
+
return;
|
|
1382
|
+
}
|
|
1383
|
+
const larger = stepAbove(g.scales, scale);
|
|
1384
|
+
if (larger !== void 0 && canReturn(g, at, true)) switchRate(ctx, fps, larger, "auto", "headroom", at);
|
|
1307
1385
|
}
|
|
1308
1386
|
/**
|
|
1309
1387
|
* Judges the interval from the last run frame's start to this one: the first after a change is skipped, one that is
|
|
@@ -1325,10 +1403,11 @@ function judgeFrame$1(ctx, g, intervalMs, at) {
|
|
|
1325
1403
|
const higher = stepAbove(g.ladder, fps);
|
|
1326
1404
|
const missed = intervalMs > missLineMs(fps, ctx.state.rate.display.hz ?? fps);
|
|
1327
1405
|
const heavy = higher !== void 0 && g.lastWorkMs > .5 * SECOND_MS$1 / higher;
|
|
1406
|
+
const busy = missed && g.lastWorkMs > SECOND_MS$1 / fps;
|
|
1328
1407
|
const slot = g.written % 512;
|
|
1329
1408
|
evict$2(g, at);
|
|
1330
1409
|
g.sampleAt[slot] = at;
|
|
1331
|
-
g.sampleFlags[slot] = (missed ? MISSED : 0) | (heavy ? HEAVY : 0);
|
|
1410
|
+
g.sampleFlags[slot] = (missed ? MISSED : 0) | (heavy ? HEAVY : 0) | (busy ? BUSY : 0);
|
|
1332
1411
|
g.written += 1;
|
|
1333
1412
|
g.dropDrawn += 1;
|
|
1334
1413
|
g.returnDrawn += 1;
|
|
@@ -1337,6 +1416,7 @@ function judgeFrame$1(ctx, g, intervalMs, at) {
|
|
|
1337
1416
|
g.returnMissed += 1;
|
|
1338
1417
|
}
|
|
1339
1418
|
if (heavy) g.returnHeavy += 1;
|
|
1419
|
+
if (busy) g.dropBusy += 1;
|
|
1340
1420
|
decide(ctx, g, at);
|
|
1341
1421
|
}
|
|
1342
1422
|
/**
|
|
@@ -1358,6 +1438,39 @@ function closeFrame(ctx, g, workMs) {
|
|
|
1358
1438
|
g.lastWorkMs = Math.max(0, workMs - agentMs);
|
|
1359
1439
|
}
|
|
1360
1440
|
/**
|
|
1441
|
+
* Starts the governor over for a new scene: the back-off of failed returns goes back to the base dwell, both windows
|
|
1442
|
+
* are emptied and the next interval is skipped, so the new screen is judged on its own frames. The rate and the scale
|
|
1443
|
+
* stay. Nothing without a governor.
|
|
1444
|
+
*
|
|
1445
|
+
* @param rate - The rate state.
|
|
1446
|
+
*/
|
|
1447
|
+
function sceneRate(rate) {
|
|
1448
|
+
const g = rate.governor;
|
|
1449
|
+
if (g === void 0) return;
|
|
1450
|
+
resetDwell(g);
|
|
1451
|
+
g.skipNext = true;
|
|
1452
|
+
emptyWindows(g);
|
|
1453
|
+
}
|
|
1454
|
+
/**
|
|
1455
|
+
* Sets the render scales the rule may step through, down to `floor`. A scale under the new floor goes up to it at
|
|
1456
|
+
* once, quietly: the caller draws at what it decided. Nothing without a governor.
|
|
1457
|
+
*
|
|
1458
|
+
* @param rate - The rate state.
|
|
1459
|
+
* @param floor - The lowest scale the renderer allows; 1 allows none.
|
|
1460
|
+
* @returns The frame rate after the call.
|
|
1461
|
+
*/
|
|
1462
|
+
function allowScale(rate, floor) {
|
|
1463
|
+
const g = rate.governor;
|
|
1464
|
+
if (g === void 0) return rate.snapshot;
|
|
1465
|
+
g.scales = scaleStepsOf(floor);
|
|
1466
|
+
const lowest = g.scales.at(-1) ?? 1;
|
|
1467
|
+
if (rate.scale < lowest) {
|
|
1468
|
+
rate.scale = lowest;
|
|
1469
|
+
rate.snapshot = snapshotOf(rate);
|
|
1470
|
+
}
|
|
1471
|
+
return rate.snapshot;
|
|
1472
|
+
}
|
|
1473
|
+
/**
|
|
1361
1474
|
* Restarts the tick history of the display after a resume and, with a governor, its windows, and skips the next
|
|
1362
1475
|
* interval: a return from the background is a new situation. The display rate already known stays.
|
|
1363
1476
|
*
|
|
@@ -1403,10 +1516,10 @@ function resumeAuto(ctx) {
|
|
|
1403
1516
|
const { maxFps } = ctx.config;
|
|
1404
1517
|
const g = ctx.state.rate.governor;
|
|
1405
1518
|
if (g === void 0) {
|
|
1406
|
-
switchRate(ctx, maxFps, "fixed", "auto", nowOf());
|
|
1519
|
+
switchRate(ctx, maxFps, 1, "fixed", "auto", nowOf());
|
|
1407
1520
|
return;
|
|
1408
1521
|
}
|
|
1409
|
-
switchRate(ctx, autoRate(g, maxFps), "auto", "auto", nowOf());
|
|
1522
|
+
switchRate(ctx, autoRate(g, maxFps), 1, "auto", "auto", nowOf());
|
|
1410
1523
|
}
|
|
1411
1524
|
/**
|
|
1412
1525
|
* Forces the rate (the exact cap, never above `maxFps`) or gives it back to the config with `"auto"`.
|
|
@@ -1420,7 +1533,7 @@ function setRateChoice(ctx, choice) {
|
|
|
1420
1533
|
const value = choice;
|
|
1421
1534
|
if (!isChoice(value)) throw new Error(`[game] time.setRate takes "auto", 30, 60 or 120.\n Pass one of them, not ${String(value)}.`);
|
|
1422
1535
|
const { rate } = ctx.state;
|
|
1423
|
-
if (value !== "auto") switchRate(ctx, Math.min(value, ctx.config.maxFps), "forced", "forced", nowOf());
|
|
1536
|
+
if (value !== "auto") switchRate(ctx, Math.min(value, ctx.config.maxFps), 1, "forced", "forced", nowOf());
|
|
1424
1537
|
else if (rate.mode === "forced") resumeAuto(ctx);
|
|
1425
1538
|
return rate.snapshot;
|
|
1426
1539
|
}
|
|
@@ -1694,7 +1807,7 @@ function countFrameListeners(ctx) {
|
|
|
1694
1807
|
}
|
|
1695
1808
|
/**
|
|
1696
1809
|
* Creates the time API: frame callbacks per phase, the `Time` resource, scale, pause and
|
|
1697
|
-
* resume, `wake` for the idle cap, the measurement of an agent build, the frame rate, and `step`
|
|
1810
|
+
* resume, `wake` for the idle cap, the measurement of an agent build, the frame rate and its render scale, and `step`
|
|
1698
1811
|
* for tests and tools.
|
|
1699
1812
|
*
|
|
1700
1813
|
* @param ctx - Domain context of the time plugin.
|
|
@@ -1744,6 +1857,8 @@ function createTimeApi(ctx) {
|
|
|
1744
1857
|
},
|
|
1745
1858
|
rate: () => ctx.state.rate.snapshot,
|
|
1746
1859
|
setRate: (choice) => setRateChoice(ctx, choice),
|
|
1860
|
+
allowScale: (floor) => allowScale(ctx.state.rate, floor),
|
|
1861
|
+
sceneChanged: () => sceneRate(ctx.state.rate),
|
|
1747
1862
|
frameDrawn: () => {
|
|
1748
1863
|
ctx.state.rate.drawnNow = true;
|
|
1749
1864
|
},
|
|
@@ -1907,7 +2022,7 @@ const timePlugin = /*#__PURE__*/ createPlugin$1("time", {
|
|
|
1907
2022
|
},
|
|
1908
2023
|
events: (register) => register.map({
|
|
1909
2024
|
"time:thermal": "The native shell reported a thermal reading",
|
|
1910
|
-
"time:rate": "The loop's frame rate changed"
|
|
2025
|
+
"time:rate": "The loop's frame rate or render scale changed"
|
|
1911
2026
|
}),
|
|
1912
2027
|
createState: createTimeState,
|
|
1913
2028
|
api: createTimeApi,
|
|
@@ -3222,7 +3337,7 @@ function deliver$1(state, answer) {
|
|
|
3222
3337
|
* @param ctx - Domain context of the flow plugin.
|
|
3223
3338
|
* @param answer - The answer the closed gate refused.
|
|
3224
3339
|
*/
|
|
3225
|
-
function hold(ctx, answer) {
|
|
3340
|
+
function hold$1(ctx, answer) {
|
|
3226
3341
|
if (ctx.state.fx.mode === "fast") return;
|
|
3227
3342
|
if (!ctx.deps.time.isRunning()) return;
|
|
3228
3343
|
if (ctx.config.holdMs <= 0 && ctx.state.gate.held !== void 0) return;
|
|
@@ -3261,7 +3376,7 @@ function createGateApi(ctx) {
|
|
|
3261
3376
|
return {
|
|
3262
3377
|
answer: (answer) => {
|
|
3263
3378
|
if (ctx.state.gate.open === void 0) {
|
|
3264
|
-
hold(ctx, answer);
|
|
3379
|
+
hold$1(ctx, answer);
|
|
3265
3380
|
return false;
|
|
3266
3381
|
}
|
|
3267
3382
|
if (!accepts(ctx.state.gate, answer)) return false;
|
|
@@ -5391,7 +5506,7 @@ function startDeadline(timeoutMs) {
|
|
|
5391
5506
|
* @param running - The promise of `run()`.
|
|
5392
5507
|
* @returns `true` when the loop settled, `false` when the deadline won and the loop still runs.
|
|
5393
5508
|
*/
|
|
5394
|
-
async function settle$
|
|
5509
|
+
async function settle$2(ctx, running) {
|
|
5395
5510
|
const quiet = running.then(noop, noop);
|
|
5396
5511
|
const deadline = startDeadline(ctx.config.settleTimeoutMs);
|
|
5397
5512
|
try {
|
|
@@ -5420,7 +5535,7 @@ async function stopRunner(ctx) {
|
|
|
5420
5535
|
stop.abort("stop");
|
|
5421
5536
|
state.abort = stop;
|
|
5422
5537
|
}
|
|
5423
|
-
const settled = running === void 0 || await settle$
|
|
5538
|
+
const settled = running === void 0 || await settle$2(ctx, running);
|
|
5424
5539
|
state.running = void 0;
|
|
5425
5540
|
ctx.state.gate.held = void 0;
|
|
5426
5541
|
if (settled) state.abort = void 0;
|
|
@@ -12845,7 +12960,541 @@ function installRebindingRenderGroupPipe(pixi) {
|
|
|
12845
12960
|
};
|
|
12846
12961
|
}
|
|
12847
12962
|
//#endregion
|
|
12963
|
+
//#region src/plugins/renderer/host/render-pass.ts
|
|
12964
|
+
/** Pixi's `CLEAR.NONE`. */
|
|
12965
|
+
const CLEAR_NONE = 0;
|
|
12966
|
+
/** Pixi's `CLEAR.ALL`: colour, depth and stencil. */
|
|
12967
|
+
const CLEAR_ALL = 17664;
|
|
12968
|
+
/** Pixi's `CLEAR.COLOR`. */
|
|
12969
|
+
const CLEAR_COLOR = 16384;
|
|
12970
|
+
/**
|
|
12971
|
+
* The `CLEAR` bits of a pass's clear, as Pixi reads a boolean. `clearBits(true); // 17664`
|
|
12972
|
+
*
|
|
12973
|
+
* @param clear - The clear Pixi was handed.
|
|
12974
|
+
* @returns The bits.
|
|
12975
|
+
*/
|
|
12976
|
+
function clearBits(clear) {
|
|
12977
|
+
if (clear === true) return CLEAR_ALL;
|
|
12978
|
+
if (clear === false) return CLEAR_NONE;
|
|
12979
|
+
return clear;
|
|
12980
|
+
}
|
|
12981
|
+
/**
|
|
12982
|
+
* Builds the classes of the lazy canvas pass from the module object, at call time, because the bases arrive with the
|
|
12983
|
+
* lazily loaded module. Each system keeps its base's `static extension` object as it is, so the registry files it
|
|
12984
|
+
* under the same type and name. They cost one test of two booleans per pipeline request, and nothing else per draw.
|
|
12985
|
+
*
|
|
12986
|
+
* @param pixi - The loaded Pixi module.
|
|
12987
|
+
* @returns The render target system and the pipeline system.
|
|
12988
|
+
*/
|
|
12989
|
+
function createLazyCanvasClasses(pixi) {
|
|
12990
|
+
/**
|
|
12991
|
+
* Pixi's WebGPU adaptor, which begins a pass on the canvas at its first draw and sets the canvas's stencil and depth
|
|
12992
|
+
* operations by the rule of the file. One per renderer.
|
|
12993
|
+
*/
|
|
12994
|
+
class LazyCanvasAdaptor extends pixi.GpuRenderTargetAdaptor {
|
|
12995
|
+
/** The canvas is bound and its pass has not begun: the pipeline system begins it at the first draw. */
|
|
12996
|
+
pending = false;
|
|
12997
|
+
/** A pipeline that tests or writes depth was asked for: the canvas keeps its depth from then on. */
|
|
12998
|
+
depthUsed = false;
|
|
12999
|
+
/** The renderer: Pixi's own `_renderer` is private. */
|
|
13000
|
+
gpu = void 0;
|
|
13001
|
+
/** The render target system: Pixi's own `_renderTargetSystem` is private. */
|
|
13002
|
+
targets = void 0;
|
|
13003
|
+
/** The renderer's pipeline system is the lazy one: only then does a pass wait for a draw. */
|
|
13004
|
+
hooked = false;
|
|
13005
|
+
/** The canvas whose pass is pending or whose clear is owed. */
|
|
13006
|
+
canvas = void 0;
|
|
13007
|
+
/** The `CLEAR` bits the canvas owes. */
|
|
13008
|
+
owed = CLEAR_NONE;
|
|
13009
|
+
/** The colour of the owed clear, copied, when one was given. */
|
|
13010
|
+
owedColor = [
|
|
13011
|
+
0,
|
|
13012
|
+
0,
|
|
13013
|
+
0,
|
|
13014
|
+
0
|
|
13015
|
+
];
|
|
13016
|
+
/** `owedColor` holds the colour of the owed clear. */
|
|
13017
|
+
owedColorSet = false;
|
|
13018
|
+
/** The viewport of the canvas's pass to come, copied from the bind that deferred it. */
|
|
13019
|
+
view = new pixi.Rectangle();
|
|
13020
|
+
/** The canvas whose pass this adaptor began and has not ended. */
|
|
13021
|
+
openCanvas = void 0;
|
|
13022
|
+
/** The open canvas pass stores its stencil. */
|
|
13023
|
+
keepStencil = false;
|
|
13024
|
+
/** The open canvas pass stores its depth. */
|
|
13025
|
+
keepDepth = false;
|
|
13026
|
+
/** The pass being begun stores its stencil. */
|
|
13027
|
+
planStencil = false;
|
|
13028
|
+
/** The pass being begun stores its depth. */
|
|
13029
|
+
planDepth = false;
|
|
13030
|
+
/** The canvas pass that ended last stored its depth. */
|
|
13031
|
+
depthStored = false;
|
|
13032
|
+
/** A canvas pass began since the last `renderEnd`. */
|
|
13033
|
+
acquired = false;
|
|
13034
|
+
/** A mask was pushed on the canvas in the last frame drawn on it. */
|
|
13035
|
+
maskedBefore = false;
|
|
13036
|
+
/** A mask was pushed on the canvas in this frame. */
|
|
13037
|
+
maskedNow = false;
|
|
13038
|
+
/** The root bind of the canvas emitted no target change: the stencil system hears of it when its pass begins. */
|
|
13039
|
+
changeOwed = false;
|
|
13040
|
+
/**
|
|
13041
|
+
* Keeps the renderer and the render target system, then lets Pixi subscribe the adaptor.
|
|
13042
|
+
*
|
|
13043
|
+
* @param renderer - The WebGPU renderer.
|
|
13044
|
+
* @param renderTargetSystem - Its render target system.
|
|
13045
|
+
*/
|
|
13046
|
+
init(renderer, renderTargetSystem) {
|
|
13047
|
+
super.init(renderer, renderTargetSystem);
|
|
13048
|
+
this.gpu = renderer;
|
|
13049
|
+
this.targets = renderTargetSystem;
|
|
13050
|
+
}
|
|
13051
|
+
/** Forgets every pass of the old device, then lets Pixi drop its restore helper. */
|
|
13052
|
+
contextChange() {
|
|
13053
|
+
super.contextChange();
|
|
13054
|
+
this.pending = false;
|
|
13055
|
+
this.canvas = void 0;
|
|
13056
|
+
this.owed = CLEAR_NONE;
|
|
13057
|
+
this.owedColorSet = false;
|
|
13058
|
+
this.openCanvas = void 0;
|
|
13059
|
+
this.depthStored = false;
|
|
13060
|
+
this.changeOwed = false;
|
|
13061
|
+
}
|
|
13062
|
+
/**
|
|
13063
|
+
* Tells whether the root bind of a render may skip its target change: the root is a canvas whose pass is deferred.
|
|
13064
|
+
* The adaptor then owes the stencil system the change, until the canvas pass begins or another target is bound.
|
|
13065
|
+
*
|
|
13066
|
+
* @param root - The root target of the render.
|
|
13067
|
+
* @returns True when the bind may skip the change.
|
|
13068
|
+
*/
|
|
13069
|
+
owesChange(root) {
|
|
13070
|
+
this.changeOwed = this.defers(root);
|
|
13071
|
+
return this.changeOwed;
|
|
13072
|
+
}
|
|
13073
|
+
/**
|
|
13074
|
+
* Begins a pass as Pixi does on any target but the canvas inside a frame. On the canvas it reuses an open pass as
|
|
13075
|
+
* Pixi does, and otherwise ends the open pass and defers the canvas's, its clear owed.
|
|
13076
|
+
*
|
|
13077
|
+
* @param renderTarget - The target.
|
|
13078
|
+
* @param clear - What to clear.
|
|
13079
|
+
* @param clearColor - The colour to clear to.
|
|
13080
|
+
* @param viewport - The viewport.
|
|
13081
|
+
* @param mipLevel - The mip level.
|
|
13082
|
+
* @param layer - The array layer.
|
|
13083
|
+
*/
|
|
13084
|
+
startRenderPass(renderTarget, clear = true, clearColor, viewport, mipLevel = 0, layer = 0) {
|
|
13085
|
+
if (viewport === void 0 || mipLevel !== 0 || layer !== 0 || !this.defers(renderTarget)) {
|
|
13086
|
+
this.pending = false;
|
|
13087
|
+
this.openCanvas = void 0;
|
|
13088
|
+
this.changeOwed = false;
|
|
13089
|
+
super.startRenderPass(renderTarget, clear, clearColor, viewport, mipLevel, layer);
|
|
13090
|
+
return;
|
|
13091
|
+
}
|
|
13092
|
+
const bits = clearBits(clear);
|
|
13093
|
+
if (this.canvas !== void 0 && this.canvas !== renderTarget) this.settleCanvas();
|
|
13094
|
+
if (bits === CLEAR_NONE && this.isOpen(renderTarget)) {
|
|
13095
|
+
this.beginCanvas(renderTarget, CLEAR_NONE, void 0, viewport);
|
|
13096
|
+
return;
|
|
13097
|
+
}
|
|
13098
|
+
this.finishRenderPass();
|
|
13099
|
+
this.canvas = renderTarget;
|
|
13100
|
+
this.pending = true;
|
|
13101
|
+
this.owe(bits, clearColor);
|
|
13102
|
+
this.view.copyFrom(viewport);
|
|
13103
|
+
}
|
|
13104
|
+
/** Ends the open pass, as Pixi does. */
|
|
13105
|
+
finishRenderPass() {
|
|
13106
|
+
super.finishRenderPass();
|
|
13107
|
+
this.openCanvas = void 0;
|
|
13108
|
+
}
|
|
13109
|
+
/**
|
|
13110
|
+
* Copies out of a render target, as Pixi does. A copy that reads or writes the canvas first pays the clear it owes,
|
|
13111
|
+
* or a first pass it never had; a canvas that had its pass in this render and owes nothing begins no empty one.
|
|
13112
|
+
*
|
|
13113
|
+
* @param source - The target copied from.
|
|
13114
|
+
* @param destination - The texture copied into.
|
|
13115
|
+
* @param from - The origin in the source.
|
|
13116
|
+
* @param size - The size of the copy.
|
|
13117
|
+
* @param to - The origin in the destination.
|
|
13118
|
+
* @returns The destination.
|
|
13119
|
+
*/
|
|
13120
|
+
copyToTexture(source, destination, from, size, to) {
|
|
13121
|
+
const canvas = this.canvas;
|
|
13122
|
+
if (canvas !== void 0 && (source === canvas || destination.source === canvas.colorTexture) && (this.owed !== CLEAR_NONE || !this.acquired)) this.payOwed(canvas);
|
|
13123
|
+
return super.copyToTexture(source, destination, from, size, to);
|
|
13124
|
+
}
|
|
13125
|
+
/**
|
|
13126
|
+
* Copies a render target's depth, as Pixi does; a copy of the canvas's depth makes the canvas keep its depth from
|
|
13127
|
+
* then on, and pays the clear it owes first.
|
|
13128
|
+
*
|
|
13129
|
+
* @param source - The target copied from.
|
|
13130
|
+
* @param destination - The depth texture copied into.
|
|
13131
|
+
* @param from - The origin in the source.
|
|
13132
|
+
* @param size - The size of the copy.
|
|
13133
|
+
* @param to - The origin in the destination.
|
|
13134
|
+
*/
|
|
13135
|
+
copyDepthTexture(source, destination, from, size, to) {
|
|
13136
|
+
if (this.isCanvas(source)) {
|
|
13137
|
+
this.depthUsed = true;
|
|
13138
|
+
if (source === this.canvas) this.payOwed(source);
|
|
13139
|
+
}
|
|
13140
|
+
super.copyDepthTexture(source, destination, from, size, to);
|
|
13141
|
+
}
|
|
13142
|
+
/** Begins the pending canvas pass, with the clear it owes: a draw is about to be recorded into it. */
|
|
13143
|
+
beginPending() {
|
|
13144
|
+
const target = this.canvas;
|
|
13145
|
+
if (!this.pending || target === void 0) return;
|
|
13146
|
+
const bits = this.owed;
|
|
13147
|
+
const color = this.owedColorSet ? this.owedColor : void 0;
|
|
13148
|
+
this.pending = false;
|
|
13149
|
+
this.canvas = void 0;
|
|
13150
|
+
this.owed = CLEAR_NONE;
|
|
13151
|
+
this.owedColorSet = false;
|
|
13152
|
+
this.beginCanvas(target, bits, color, this.view);
|
|
13153
|
+
}
|
|
13154
|
+
/**
|
|
13155
|
+
* A pipeline that tests or writes depth was asked for: the canvas keeps its depth from now on, and an open canvas
|
|
13156
|
+
* pass that discards it begins again, with the depth cleared, as nothing wrote it yet, and stored.
|
|
13157
|
+
*/
|
|
13158
|
+
useDepth() {
|
|
13159
|
+
this.depthUsed = true;
|
|
13160
|
+
const canvas = this.openCanvas;
|
|
13161
|
+
if (canvas?.depthStencilAttachment !== void 0 && !this.keepDepth && this.passOpen()) this.restart(canvas);
|
|
13162
|
+
}
|
|
13163
|
+
/**
|
|
13164
|
+
* A stencil mask is about to be pushed on the bound target: on the canvas, the stencil is kept from now on.
|
|
13165
|
+
*
|
|
13166
|
+
* @param target - The bound target.
|
|
13167
|
+
*/
|
|
13168
|
+
maskPushes(target) {
|
|
13169
|
+
if (this.isCanvas(target)) this.maskedNow = true;
|
|
13170
|
+
}
|
|
13171
|
+
/**
|
|
13172
|
+
* A stencil mask was pushed on the bound target: an open canvas pass that discards its stencil begins again, the
|
|
13173
|
+
* stencil cleared, as no mask was open, and stored, before the mask is drawn. A pending canvas pass whose target
|
|
13174
|
+
* change is owed begins now, so the stencil system knows its target before it sets the mask's mode.
|
|
13175
|
+
*
|
|
13176
|
+
* @param target - The bound target.
|
|
13177
|
+
*/
|
|
13178
|
+
maskPushed(target) {
|
|
13179
|
+
if (this.changeOwed && this.pending && this.canvas === target) this.beginPending();
|
|
13180
|
+
else if (this.openCanvas === target && !this.keepStencil && this.passOpen()) this.restart(target);
|
|
13181
|
+
}
|
|
13182
|
+
/**
|
|
13183
|
+
* The end of a render call: pays the clear the canvas owes, gives a canvas that saw no pass one, so the frame
|
|
13184
|
+
* clears and presents it as Pixi's does, and moves the mask memory on after a frame drawn on the canvas.
|
|
13185
|
+
*
|
|
13186
|
+
* @param root - The root target of the render call.
|
|
13187
|
+
*/
|
|
13188
|
+
settle(root) {
|
|
13189
|
+
this.settleCanvas();
|
|
13190
|
+
this.acquired = false;
|
|
13191
|
+
if (this.isCanvas(root)) {
|
|
13192
|
+
this.maskedBefore = this.maskedNow;
|
|
13193
|
+
this.maskedNow = false;
|
|
13194
|
+
}
|
|
13195
|
+
}
|
|
13196
|
+
/**
|
|
13197
|
+
* Tells whether a pass on a target waits for its first draw: the target is a canvas without multisampling, and the
|
|
13198
|
+
* renderer's pipeline system is the lazy one that begins it.
|
|
13199
|
+
*
|
|
13200
|
+
* @param target - The target.
|
|
13201
|
+
* @returns True when the pass is deferred.
|
|
13202
|
+
*/
|
|
13203
|
+
defers(target) {
|
|
13204
|
+
if (!this.hooked) this.hooked = this.gpu?.pipeline instanceof LazyCanvasPipeline;
|
|
13205
|
+
return this.hooked && this.isCanvas(target);
|
|
13206
|
+
}
|
|
13207
|
+
/**
|
|
13208
|
+
* Tells whether a target is a canvas without multisampling: Pixi gives a canvas's GPU target its context.
|
|
13209
|
+
*
|
|
13210
|
+
* @param target - The target.
|
|
13211
|
+
* @returns True for such a canvas.
|
|
13212
|
+
*/
|
|
13213
|
+
isCanvas(target) {
|
|
13214
|
+
const targets = this.targets;
|
|
13215
|
+
if (targets === void 0) return false;
|
|
13216
|
+
const gpuTarget = targets.getGpuRenderTarget(target);
|
|
13217
|
+
return gpuTarget.contexts.length > 0 && !gpuTarget.msaa;
|
|
13218
|
+
}
|
|
13219
|
+
/**
|
|
13220
|
+
* Tells whether a pass of the encoder is open.
|
|
13221
|
+
*
|
|
13222
|
+
* @returns True while one is.
|
|
13223
|
+
*/
|
|
13224
|
+
passOpen() {
|
|
13225
|
+
const encoder = this.gpu?.encoder;
|
|
13226
|
+
return encoder !== void 0 && encoder.renderPassEncoder !== null;
|
|
13227
|
+
}
|
|
13228
|
+
/**
|
|
13229
|
+
* Tells whether the pass this adaptor began on a canvas is still open.
|
|
13230
|
+
*
|
|
13231
|
+
* @param target - The canvas.
|
|
13232
|
+
* @returns True when its pass is open.
|
|
13233
|
+
*/
|
|
13234
|
+
isOpen(target) {
|
|
13235
|
+
return this.openCanvas === target && this.passOpen();
|
|
13236
|
+
}
|
|
13237
|
+
/**
|
|
13238
|
+
* Adds a clear to the one the canvas owes; a colour clear given a colour keeps a copy of it.
|
|
13239
|
+
*
|
|
13240
|
+
* @param bits - The `CLEAR` bits.
|
|
13241
|
+
* @param color - The colour, when one was given.
|
|
13242
|
+
*/
|
|
13243
|
+
owe(bits, color) {
|
|
13244
|
+
const colour = bits & CLEAR_COLOR;
|
|
13245
|
+
this.owed |= bits;
|
|
13246
|
+
if (colour === 0) return;
|
|
13247
|
+
this.owedColorSet = Array.isArray(color);
|
|
13248
|
+
if (!Array.isArray(color)) return;
|
|
13249
|
+
this.owedColor[0] = color[0];
|
|
13250
|
+
this.owedColor[1] = color[1];
|
|
13251
|
+
this.owedColor[2] = color[2];
|
|
13252
|
+
this.owedColor[3] = color[3];
|
|
13253
|
+
}
|
|
13254
|
+
/**
|
|
13255
|
+
* Begins a pass on the canvas through Pixi, with the stencil and depth operations of the rule set on its
|
|
13256
|
+
* depth-stencil attachment first, where Pixi's descriptor reads them; a clear Pixi was asked for still wins. Pixi
|
|
13257
|
+
* reuses an open pass when nothing is cleared and nothing changed; a pass that really began is recorded.
|
|
13258
|
+
*
|
|
13259
|
+
* @param target - The canvas.
|
|
13260
|
+
* @param bits - The `CLEAR` bits.
|
|
13261
|
+
* @param color - The clear colour, when one was given.
|
|
13262
|
+
* @param viewport - The viewport.
|
|
13263
|
+
*/
|
|
13264
|
+
beginCanvas(target, bits, color, viewport) {
|
|
13265
|
+
const gpu = this.gpu;
|
|
13266
|
+
const targets = this.targets;
|
|
13267
|
+
if (gpu === void 0 || targets === void 0) return;
|
|
13268
|
+
this.planDepthStencil(target, targets);
|
|
13269
|
+
const before = gpu.encoder.renderPassEncoder;
|
|
13270
|
+
if (viewport !== this.view) this.view.copyFrom(viewport);
|
|
13271
|
+
super.startRenderPass(target, bits, color, viewport, 0, 0);
|
|
13272
|
+
this.openCanvas = target;
|
|
13273
|
+
if (gpu.encoder.renderPassEncoder !== before) this.began(target, targets);
|
|
13274
|
+
}
|
|
13275
|
+
/**
|
|
13276
|
+
* Sets the stencil and depth operations of the rule on the canvas's depth-stencil attachment, made first when
|
|
13277
|
+
* Pixi would make it, and keeps what the pass stores.
|
|
13278
|
+
*
|
|
13279
|
+
* @param target - The canvas.
|
|
13280
|
+
* @param targets - The render target system.
|
|
13281
|
+
*/
|
|
13282
|
+
planDepthStencil(target, targets) {
|
|
13283
|
+
if ((target.stencil || target.depth) && target.depthStencilAttachment === void 0) target.ensureDepthStencilTexture();
|
|
13284
|
+
const attachment = target.depthStencilAttachment;
|
|
13285
|
+
const masked = targets.getGpuRenderTarget(target).maskStackIndex !== 0;
|
|
13286
|
+
this.planStencil = masked || this.maskedBefore || this.maskedNow;
|
|
13287
|
+
this.planDepth = this.depthUsed && attachment !== void 0;
|
|
13288
|
+
if (attachment === void 0) return;
|
|
13289
|
+
attachment.stencilLoadOp = masked ? "load" : "clear";
|
|
13290
|
+
attachment.stencilStoreOp = this.planStencil ? "store" : "discard";
|
|
13291
|
+
attachment.depthLoadOp = this.depthStored ? "load" : "clear";
|
|
13292
|
+
attachment.depthStoreOp = this.planDepth ? "store" : "discard";
|
|
13293
|
+
}
|
|
13294
|
+
/**
|
|
13295
|
+
* Records a canvas pass that really began (Pixi did not reuse the open one), and tells the stencil system of the
|
|
13296
|
+
* canvas when the root bind owes it the change, now that there is a pass to set its reference on.
|
|
13297
|
+
*
|
|
13298
|
+
* @param target - The canvas.
|
|
13299
|
+
* @param targets - The render target system.
|
|
13300
|
+
*/
|
|
13301
|
+
began(target, targets) {
|
|
13302
|
+
this.acquired = true;
|
|
13303
|
+
this.keepStencil = this.planStencil;
|
|
13304
|
+
this.keepDepth = this.planDepth;
|
|
13305
|
+
this.depthStored = this.planDepth;
|
|
13306
|
+
if (this.changeOwed) {
|
|
13307
|
+
this.changeOwed = false;
|
|
13308
|
+
targets.onRenderTargetChange.emit(target);
|
|
13309
|
+
}
|
|
13310
|
+
}
|
|
13311
|
+
/**
|
|
13312
|
+
* Begins and ends one pass on a canvas now, with the clear it owes, and leaves the canvas bound with nothing owed.
|
|
13313
|
+
*
|
|
13314
|
+
* @param target - The canvas.
|
|
13315
|
+
*/
|
|
13316
|
+
payOwed(target) {
|
|
13317
|
+
const bits = this.owed;
|
|
13318
|
+
const color = this.owedColorSet ? this.owedColor : void 0;
|
|
13319
|
+
this.owed = CLEAR_NONE;
|
|
13320
|
+
this.owedColorSet = false;
|
|
13321
|
+
this.beginCanvas(target, bits, color, this.view);
|
|
13322
|
+
this.finishRenderPass();
|
|
13323
|
+
}
|
|
13324
|
+
/**
|
|
13325
|
+
* Ends the canvas's wait: pays the clear it owes, or a first pass it never had, and forgets it.
|
|
13326
|
+
*/
|
|
13327
|
+
settleCanvas() {
|
|
13328
|
+
const target = this.canvas;
|
|
13329
|
+
if (target === void 0) return;
|
|
13330
|
+
if (this.owed !== CLEAR_NONE || !this.acquired) this.payOwed(target);
|
|
13331
|
+
this.pending = false;
|
|
13332
|
+
this.canvas = void 0;
|
|
13333
|
+
}
|
|
13334
|
+
/**
|
|
13335
|
+
* Begins the open canvas pass again with nothing cleared, by the rule as it stands now, and sets its stencil
|
|
13336
|
+
* reference again: a new pass starts from 0, and Pixi sets it only when the mode or the target changes.
|
|
13337
|
+
*
|
|
13338
|
+
* @param target - The canvas.
|
|
13339
|
+
*/
|
|
13340
|
+
restart(target) {
|
|
13341
|
+
const gpu = this.gpu;
|
|
13342
|
+
const targets = this.targets;
|
|
13343
|
+
if (gpu === void 0 || targets === void 0) return;
|
|
13344
|
+
this.finishRenderPass();
|
|
13345
|
+
this.beginCanvas(target, CLEAR_NONE, void 0, this.view);
|
|
13346
|
+
gpu.encoder.setStencilReference(targets.getGpuRenderTarget(target).stencilReference);
|
|
13347
|
+
}
|
|
13348
|
+
}
|
|
13349
|
+
/** Pixi's WebGPU render target system over the lazy adaptor, which settles the canvas at the end of a render. */
|
|
13350
|
+
class LazyCanvasTargets extends pixi.GpuRenderTargetSystem {
|
|
13351
|
+
static extension = pixi.GpuRenderTargetSystem.extension;
|
|
13352
|
+
/** The adaptor, typed as the lazy one. */
|
|
13353
|
+
lazy;
|
|
13354
|
+
/**
|
|
13355
|
+
* Makes Pixi's system, then puts the lazy adaptor in the place of Pixi's.
|
|
13356
|
+
*
|
|
13357
|
+
* @param renderer - The WebGPU renderer.
|
|
13358
|
+
*/
|
|
13359
|
+
constructor(renderer) {
|
|
13360
|
+
super(renderer);
|
|
13361
|
+
const lazy = new LazyCanvasAdaptor();
|
|
13362
|
+
lazy.init(renderer, this);
|
|
13363
|
+
this.adaptor = lazy;
|
|
13364
|
+
this.lazy = lazy;
|
|
13365
|
+
}
|
|
13366
|
+
/**
|
|
13367
|
+
* Starts a render as Pixi does; a root canvas whose pass is deferred is marked bound first, so Pixi's bind of it
|
|
13368
|
+
* emits no target change: the adaptor emits it when the pass begins.
|
|
13369
|
+
*
|
|
13370
|
+
* @param options - The root binding.
|
|
13371
|
+
*/
|
|
13372
|
+
renderStart(options) {
|
|
13373
|
+
const root = this.getRenderTarget(options.target);
|
|
13374
|
+
if (this.renderTarget !== root && Boolean(root.flipY) === Boolean(options.flipY) && this.lazy.owesChange(root)) this.renderTarget = root;
|
|
13375
|
+
super.renderStart(options);
|
|
13376
|
+
}
|
|
13377
|
+
/** The end of a render call, before the encoder submits it: the canvas settles. */
|
|
13378
|
+
renderEnd() {
|
|
13379
|
+
this.lazy.settle(this.rootRenderTarget);
|
|
13380
|
+
}
|
|
13381
|
+
/** Makes sure the bound target has a stencil for a mask about to be pushed, and keeps it by the rule. */
|
|
13382
|
+
ensureDepthStencil() {
|
|
13383
|
+
const target = this.renderTarget;
|
|
13384
|
+
this.lazy.maskPushes(target);
|
|
13385
|
+
super.ensureDepthStencil();
|
|
13386
|
+
this.lazy.maskPushed(target);
|
|
13387
|
+
}
|
|
13388
|
+
}
|
|
13389
|
+
/** Pixi's WebGPU pipeline system, which begins a deferred canvas pass before a draw or a stencil reference needs it. */
|
|
13390
|
+
class LazyCanvasPipeline extends pixi.PipelineSystem {
|
|
13391
|
+
static extension = pixi.PipelineSystem.extension;
|
|
13392
|
+
/** The renderer: Pixi's own `_renderer` is private. */
|
|
13393
|
+
host;
|
|
13394
|
+
/** The lazy adaptor of the renderer, once found. */
|
|
13395
|
+
lazy = void 0;
|
|
13396
|
+
/**
|
|
13397
|
+
* Keeps the renderer it is made for.
|
|
13398
|
+
*
|
|
13399
|
+
* @param renderer - The WebGPU renderer.
|
|
13400
|
+
*/
|
|
13401
|
+
constructor(renderer) {
|
|
13402
|
+
super(renderer);
|
|
13403
|
+
this.host = renderer;
|
|
13404
|
+
}
|
|
13405
|
+
/**
|
|
13406
|
+
* Notes a state that tests or writes depth, begins a pending canvas pass, then answers Pixi's pipeline. Its five
|
|
13407
|
+
* arguments are named, not spread: a rest array would be made at every draw.
|
|
13408
|
+
*
|
|
13409
|
+
* @param geometry - The geometry.
|
|
13410
|
+
* @param program - The program.
|
|
13411
|
+
* @param state - The state.
|
|
13412
|
+
* @param topology - The topology.
|
|
13413
|
+
* @param overrides - The shader overrides.
|
|
13414
|
+
* @returns The pipeline.
|
|
13415
|
+
*/
|
|
13416
|
+
getPipeline(geometry, program, state, topology, overrides) {
|
|
13417
|
+
const lazy = this.lazy ?? this.link();
|
|
13418
|
+
if (lazy !== void 0) {
|
|
13419
|
+
if (!lazy.depthUsed && (state.depthTest || state.depthMask)) lazy.useDepth();
|
|
13420
|
+
if (lazy.pending) lazy.beginPending();
|
|
13421
|
+
}
|
|
13422
|
+
return super.getPipeline(geometry, program, state, topology, overrides);
|
|
13423
|
+
}
|
|
13424
|
+
/**
|
|
13425
|
+
* Begins a pending canvas pass, then answers Pixi's buffer names: the first call of a batch or a graphics draw.
|
|
13426
|
+
*
|
|
13427
|
+
* @param geometry - The geometry.
|
|
13428
|
+
* @param program - The program.
|
|
13429
|
+
* @returns The names of the buffers to bind.
|
|
13430
|
+
*/
|
|
13431
|
+
getBufferNamesToBind(geometry, program) {
|
|
13432
|
+
this.beginPending();
|
|
13433
|
+
return super.getBufferNamesToBind(geometry, program);
|
|
13434
|
+
}
|
|
13435
|
+
/**
|
|
13436
|
+
* Begins a pending canvas pass, then sets the stencil mode: the stencil system sets its reference on the pass next.
|
|
13437
|
+
*
|
|
13438
|
+
* @param stencilMode - The stencil mode.
|
|
13439
|
+
*/
|
|
13440
|
+
setStencilMode(stencilMode) {
|
|
13441
|
+
this.beginPending();
|
|
13442
|
+
super.setStencilMode(stencilMode);
|
|
13443
|
+
}
|
|
13444
|
+
/**
|
|
13445
|
+
* Begins a pending canvas pass, then answers Pixi's key: a render bundle is recorded or replayed next.
|
|
13446
|
+
*
|
|
13447
|
+
* @returns The key.
|
|
13448
|
+
*/
|
|
13449
|
+
get bundleStateKey() {
|
|
13450
|
+
this.beginPending();
|
|
13451
|
+
return super.bundleStateKey;
|
|
13452
|
+
}
|
|
13453
|
+
/** Begins the pending canvas pass of the renderer, when there is one. */
|
|
13454
|
+
beginPending() {
|
|
13455
|
+
const lazy = this.lazy ?? this.link();
|
|
13456
|
+
if (lazy?.pending === true) lazy.beginPending();
|
|
13457
|
+
}
|
|
13458
|
+
/**
|
|
13459
|
+
* Finds the renderer's lazy adaptor, once its render target system exists.
|
|
13460
|
+
*
|
|
13461
|
+
* @returns The adaptor, or `undefined` while there is none.
|
|
13462
|
+
*/
|
|
13463
|
+
link() {
|
|
13464
|
+
const adaptor = this.host.renderTarget.adaptor;
|
|
13465
|
+
if (adaptor instanceof LazyCanvasAdaptor) this.lazy = adaptor;
|
|
13466
|
+
return this.lazy;
|
|
13467
|
+
}
|
|
13468
|
+
}
|
|
13469
|
+
return {
|
|
13470
|
+
targets: LazyCanvasTargets,
|
|
13471
|
+
pipeline: LazyCanvasPipeline
|
|
13472
|
+
};
|
|
13473
|
+
}
|
|
13474
|
+
/**
|
|
13475
|
+
* Swaps Pixi's WebGPU render target and pipeline systems for the lazy canvas ones in the module's extension registry,
|
|
13476
|
+
* and registers Pixi's stencil system again after them: the registry appends a class it is given, and the stencil
|
|
13477
|
+
* system subscribes to the render target system when it is made, so it must come later. Every application made
|
|
13478
|
+
* afterwards defers its canvas pass. The registry is global to the module object, so the remover matters: it puts
|
|
13479
|
+
* Pixi's own classes back, the stencil system again last.
|
|
13480
|
+
*
|
|
13481
|
+
* @param pixi - The loaded Pixi module.
|
|
13482
|
+
* @returns The remover.
|
|
13483
|
+
* @throws {TypeError} When the module has none of the classes to subclass.
|
|
13484
|
+
*/
|
|
13485
|
+
function installLazyCanvasPass(pixi) {
|
|
13486
|
+
const lazy = createLazyCanvasClasses(pixi);
|
|
13487
|
+
pixi.extensions.remove(pixi.GpuRenderTargetSystem, pixi.GpuStencilSystem, pixi.PipelineSystem);
|
|
13488
|
+
pixi.extensions.add(lazy.targets, pixi.GpuStencilSystem, lazy.pipeline);
|
|
13489
|
+
return () => {
|
|
13490
|
+
pixi.extensions.remove(lazy.targets, pixi.GpuStencilSystem, lazy.pipeline);
|
|
13491
|
+
pixi.extensions.add(pixi.GpuRenderTargetSystem, pixi.GpuStencilSystem, pixi.PipelineSystem);
|
|
13492
|
+
};
|
|
13493
|
+
}
|
|
13494
|
+
//#endregion
|
|
12848
13495
|
//#region src/plugins/renderer/host/resolution.ts
|
|
13496
|
+
/** The short side, in CSS pixels, at or under which a coarse pointer is a phone: the rule of `effects.phone`. */
|
|
13497
|
+
const PHONE_SIDE$1 = 820;
|
|
12849
13498
|
/**
|
|
12850
13499
|
* Device pixels per CSS pixel of the window; 1 without a window. Moved here from `host/init.ts`.
|
|
12851
13500
|
*
|
|
@@ -12855,20 +13504,67 @@ function devicePixels() {
|
|
|
12855
13504
|
return typeof globalThis.devicePixelRatio === "number" ? globalThis.devicePixelRatio : 1;
|
|
12856
13505
|
}
|
|
12857
13506
|
/**
|
|
12858
|
-
* The resolution to draw at: the override, else the device pixels under the cap.
|
|
13507
|
+
* The resolution to draw at: the override, else the device pixels under the cap at the render scale.
|
|
12859
13508
|
*
|
|
12860
13509
|
* @param state - The resolution state of the host.
|
|
12861
13510
|
* @param state.dpr - Device pixels per CSS pixel.
|
|
12862
13511
|
* @param state.cap - The cap of `maxResolution`.
|
|
12863
13512
|
* @param state.override - The switch of `monitor.setResolution`, when set.
|
|
13513
|
+
* @param state.scale - The render scale of the adaptive frame rate.
|
|
12864
13514
|
* @returns The target resolution.
|
|
12865
13515
|
* @example
|
|
12866
13516
|
* ```ts
|
|
12867
|
-
* targetResolution({ dpr: 2.81, cap: 2, override: undefined }); //
|
|
13517
|
+
* targetResolution({ dpr: 2.81, cap: 2, override: undefined, scale: 0.75 }); // 1.5
|
|
12868
13518
|
* ```
|
|
12869
13519
|
*/
|
|
12870
13520
|
function targetResolution(state) {
|
|
12871
|
-
return state.override ?? Math.min(state.dpr, state.cap);
|
|
13521
|
+
return state.override ?? Math.min(state.dpr, state.cap) * state.scale;
|
|
13522
|
+
}
|
|
13523
|
+
/**
|
|
13524
|
+
* Tells a phone: a coarse pointer and a short side of at most 820 CSS px, the rule `effects` reads for `phone: "auto"`.
|
|
13525
|
+
* False without a window.
|
|
13526
|
+
*
|
|
13527
|
+
* @returns True on a phone.
|
|
13528
|
+
*/
|
|
13529
|
+
function isPhone() {
|
|
13530
|
+
if (typeof globalThis.matchMedia !== "function") return false;
|
|
13531
|
+
return globalThis.matchMedia("(pointer: coarse)").matches && Math.min(globalThis.innerWidth, globalThis.innerHeight) <= PHONE_SIDE$1;
|
|
13532
|
+
}
|
|
13533
|
+
/**
|
|
13534
|
+
* The lowest render scale the adaptive frame rate may choose on this device: the floor of `adaptiveScale` where its
|
|
13535
|
+
* `devices` take this one, otherwise 1, none.
|
|
13536
|
+
*
|
|
13537
|
+
* @param config - The resolved plugin config.
|
|
13538
|
+
* @returns The floor; 1 allows no scale.
|
|
13539
|
+
*/
|
|
13540
|
+
function scaleFloorOf(config) {
|
|
13541
|
+
const { adaptiveScale } = config;
|
|
13542
|
+
if (adaptiveScale === false) return 1;
|
|
13543
|
+
return adaptiveScale.devices === "all" || isPhone() ? adaptiveScale.floor : 1;
|
|
13544
|
+
}
|
|
13545
|
+
/**
|
|
13546
|
+
* Follows the render scale `time:rate` carries: remembers it and asks for the target with reason `"rate"`. The
|
|
13547
|
+
* override still wins.
|
|
13548
|
+
*
|
|
13549
|
+
* @param state - The host branch of the plugin state.
|
|
13550
|
+
* @param scale - The render scale the adaptive frame rate chose.
|
|
13551
|
+
* @returns True when a change is now pending.
|
|
13552
|
+
*/
|
|
13553
|
+
function followScale(state, scale) {
|
|
13554
|
+
if (scale === state.resolution.scale) return false;
|
|
13555
|
+
state.resolution.scale = scale;
|
|
13556
|
+
return askResolution(state, targetResolution(state.resolution), "rate");
|
|
13557
|
+
}
|
|
13558
|
+
/**
|
|
13559
|
+
* Lets the adaptive frame rate step the render scale down to the floor of this device, or to none while the override
|
|
13560
|
+
* holds, and takes the scale it answers: a scale it withdrew is 1 again.
|
|
13561
|
+
*
|
|
13562
|
+
* @param ctx - Domain context of the renderer plugin.
|
|
13563
|
+
*/
|
|
13564
|
+
function allowRuleScale(ctx) {
|
|
13565
|
+
const { resolution } = ctx.state.host;
|
|
13566
|
+
const floor = resolution.override === void 0 ? resolution.floor : 1;
|
|
13567
|
+
resolution.scale = ctx.deps.time.allowScale(floor).scale;
|
|
12872
13568
|
}
|
|
12873
13569
|
/**
|
|
12874
13570
|
* The one mechanism: remembers a resolution for the next frame. Nothing is applied here. While no
|
|
@@ -13461,7 +14157,8 @@ function createPoolState() {
|
|
|
13461
14157
|
/**
|
|
13462
14158
|
* Creates the initial host state: no Pixi, no application, nothing drawn, nothing listening, no
|
|
13463
14159
|
* counting, no bitmap text pipe and no render-group pipe installed, the resolution of a page without a window (device
|
|
13464
|
-
* pixels 1, cap 2, no override, nothing pending), no own GPU, no kept picture
|
|
14160
|
+
* pixels 1, cap 2, no override, nothing pending, the whole scale, no scale allowed yet), no own GPU, no kept picture
|
|
14161
|
+
* and no pool trim yet.
|
|
13465
14162
|
*
|
|
13466
14163
|
* @returns The host branch of the plugin state.
|
|
13467
14164
|
*/
|
|
@@ -13483,11 +14180,14 @@ function createHostState() {
|
|
|
13483
14180
|
uninstallPasses: void 0,
|
|
13484
14181
|
uninstallSdfText: void 0,
|
|
13485
14182
|
uninstallRenderGroup: void 0,
|
|
14183
|
+
uninstallRenderPass: void 0,
|
|
13486
14184
|
resolution: {
|
|
13487
14185
|
dpr: 1,
|
|
13488
14186
|
cap: 2,
|
|
13489
14187
|
override: void 0,
|
|
13490
|
-
pending: void 0
|
|
14188
|
+
pending: void 0,
|
|
14189
|
+
scale: 1,
|
|
14190
|
+
floor: 1
|
|
13491
14191
|
},
|
|
13492
14192
|
onResolutionChange: [],
|
|
13493
14193
|
ownGpu: void 0,
|
|
@@ -13604,11 +14304,42 @@ function installRenderGroup(ctx, pixi) {
|
|
|
13604
14304
|
}
|
|
13605
14305
|
}
|
|
13606
14306
|
/**
|
|
14307
|
+
* Swaps Pixi's WebGPU render target and pipeline systems for the renderer's, which begin the canvas's render pass at
|
|
14308
|
+
* its first draw and keep the canvas's stencil and depth only where something reads them (B3). Like the batched text it
|
|
14309
|
+
* runs before `new Application()`, once per host state, in every build. A Pixi without the pieces draws on with its own
|
|
14310
|
+
* systems, with a warning.
|
|
14311
|
+
*
|
|
14312
|
+
* @param ctx - Domain context of the renderer plugin.
|
|
14313
|
+
* @param pixi - The module the application is built from.
|
|
14314
|
+
*/
|
|
14315
|
+
function installRenderPass(ctx, pixi) {
|
|
14316
|
+
const state = ctx.state.host;
|
|
14317
|
+
if (state.uninstallRenderPass !== void 0) return;
|
|
14318
|
+
try {
|
|
14319
|
+
state.uninstallRenderPass = installLazyCanvasPass(pixi);
|
|
14320
|
+
} catch (error) {
|
|
14321
|
+
ctx.log.warn("renderer: the canvas pass begins at render start", { error });
|
|
14322
|
+
}
|
|
14323
|
+
}
|
|
14324
|
+
/**
|
|
14325
|
+
* Tells whether a feature brings a filter that reads what was drawn under its view (`defineFilter({ backdrop: true })`).
|
|
14326
|
+
* Pixi's WebGL renderer copies that from its back buffer, and skips such a filter, with a warning, without one; the back
|
|
14327
|
+
* buffer costs a full-screen copy per drawn frame, so it is asked for only then. WebGPU copies from the canvas and
|
|
14328
|
+
* ignores the option.
|
|
14329
|
+
*
|
|
14330
|
+
* @param features - Every feature of the game, its sub-features included.
|
|
14331
|
+
* @returns True when one of their filters is a backdrop.
|
|
14332
|
+
*/
|
|
14333
|
+
function wantsBackBuffer(features) {
|
|
14334
|
+
for (const feature of features) for (const entry of feature.description.filters ?? []) if ("backdrop" in entry.filter && entry.filter.backdrop === true) return true;
|
|
14335
|
+
return false;
|
|
14336
|
+
}
|
|
14337
|
+
/**
|
|
13607
14338
|
* Loads Pixi once, creates the application and appends its canvas, at the target resolution of
|
|
13608
14339
|
* now: a restore after a lost device comes back at it, override included. Pixi picks WebGL by
|
|
13609
14340
|
* itself when WebGPU is missing, so only a device with neither ends up in the catch of the
|
|
13610
14341
|
* caller. An agent build marks the Pixi load and the init on the page's timeline and hands Pixi
|
|
13611
|
-
* the engine's own device.
|
|
14342
|
+
* the engine's own device. A game with a backdrop filter gets Pixi's WebGL back buffer.
|
|
13612
14343
|
*
|
|
13613
14344
|
* @param ctx - Domain context of the renderer plugin.
|
|
13614
14345
|
* @param mount - Where the canvas goes.
|
|
@@ -13625,6 +14356,7 @@ async function createApplication(ctx, mount, deps) {
|
|
|
13625
14356
|
installTextBatch(ctx, pixi);
|
|
13626
14357
|
installCounting$1(ctx, pixi, deps);
|
|
13627
14358
|
installRenderGroup(ctx, pixi);
|
|
14359
|
+
installRenderPass(ctx, pixi);
|
|
13628
14360
|
state.resolution.dpr = devicePixels();
|
|
13629
14361
|
state.resolution.cap = ctx.config.maxResolution;
|
|
13630
14362
|
state.resolution.pending = void 0;
|
|
@@ -13636,7 +14368,8 @@ async function createApplication(ctx, mount, deps) {
|
|
|
13636
14368
|
resolution: targetResolution(state.resolution),
|
|
13637
14369
|
autoDensity: true,
|
|
13638
14370
|
autoStart: false,
|
|
13639
|
-
sharedTicker: false
|
|
14371
|
+
sharedTicker: false,
|
|
14372
|
+
useBackBuffer: wantsBackBuffer(ctx.deps.flow.features.all())
|
|
13640
14373
|
};
|
|
13641
14374
|
await (typeof __MOKU_GAME_AGENT__ !== "undefined" && __MOKU_GAME_AGENT__ ? initTimed(ctx, app, options) : app.init(options));
|
|
13642
14375
|
state.app = app;
|
|
@@ -13668,8 +14401,8 @@ function destroyApplication(state) {
|
|
|
13668
14401
|
/**
|
|
13669
14402
|
* Undoes everything `init` created. Works on the state alone, because `onStop` has no context.
|
|
13670
14403
|
* `ready` drops first, so a device that goes away during the teardown is ignored. The draw
|
|
13671
|
-
* counter, the batched text
|
|
13672
|
-
* the page starts from them. The pool trim starts over and its listeners go; Pixi's pool itself is left.
|
|
14404
|
+
* counter, the batched text, the render-group pipe and the lazy canvas pass swap Pixi's own classes back, so the next
|
|
14405
|
+
* application on the page starts from them. The pool trim starts over and its listeners go; Pixi's pool itself is left.
|
|
13673
14406
|
*
|
|
13674
14407
|
* @param state - The host branch of the plugin state.
|
|
13675
14408
|
*/
|
|
@@ -13687,6 +14420,8 @@ function stopHost(state) {
|
|
|
13687
14420
|
state.uninstallSdfText = void 0;
|
|
13688
14421
|
state.uninstallRenderGroup?.();
|
|
13689
14422
|
state.uninstallRenderGroup = void 0;
|
|
14423
|
+
state.uninstallRenderPass?.();
|
|
14424
|
+
state.uninstallRenderPass = void 0;
|
|
13690
14425
|
state.unsupported?.remove();
|
|
13691
14426
|
state.unsupported = void 0;
|
|
13692
14427
|
state.onReady.length = 0;
|
|
@@ -14199,6 +14934,7 @@ function createHostApi(ctx, deps) {
|
|
|
14199
14934
|
setResolution: (value, reason) => askResolution(state, value, reason),
|
|
14200
14935
|
overrideResolution: (value) => {
|
|
14201
14936
|
state.resolution.override = value;
|
|
14937
|
+
allowRuleScale(ctx);
|
|
14202
14938
|
refreshResolution(ctx, "debug");
|
|
14203
14939
|
return targetResolution(state.resolution);
|
|
14204
14940
|
},
|
|
@@ -14256,6 +14992,8 @@ function createHostApi(ctx, deps) {
|
|
|
14256
14992
|
watchDevice(ctx, restart);
|
|
14257
14993
|
watchVisibility(ctx);
|
|
14258
14994
|
watchPixelRatio(ctx);
|
|
14995
|
+
state.resolution.floor = scaleFloorOf(ctx.config);
|
|
14996
|
+
allowRuleScale(ctx);
|
|
14259
14997
|
state.ready = true;
|
|
14260
14998
|
for (const fn of state.onReady) fn();
|
|
14261
14999
|
}
|
|
@@ -23634,7 +24372,7 @@ function destroyPools(state) {
|
|
|
23634
24372
|
* view with `applyPose`.
|
|
23635
24373
|
*/
|
|
23636
24374
|
/** How deep a parent chain is followed; deeper is treated as a loop. */
|
|
23637
|
-
const MAX_DEPTH$
|
|
24375
|
+
const MAX_DEPTH$6 = 32;
|
|
23638
24376
|
/**
|
|
23639
24377
|
* The parent an entity moves with.
|
|
23640
24378
|
*
|
|
@@ -23656,7 +24394,7 @@ function parentOf$1(ecs, entity) {
|
|
|
23656
24394
|
*/
|
|
23657
24395
|
function isLeaving(ecs, entity) {
|
|
23658
24396
|
let current = entity;
|
|
23659
|
-
for (let depth = 0; depth < MAX_DEPTH$
|
|
24397
|
+
for (let depth = 0; depth < MAX_DEPTH$6 && current !== 0; depth += 1) {
|
|
23660
24398
|
if (ecs.has(current, Exiting)) return true;
|
|
23661
24399
|
current = parentOf$1(ecs, current);
|
|
23662
24400
|
}
|
|
@@ -23762,7 +24500,7 @@ function rootPoseInto(ecs, entity, out, own) {
|
|
|
23762
24500
|
out.pivot.x = start.pivot.x;
|
|
23763
24501
|
out.pivot.y = start.pivot.y;
|
|
23764
24502
|
let current = entity;
|
|
23765
|
-
for (let depth = 0; depth < MAX_DEPTH$
|
|
24503
|
+
for (let depth = 0; depth < MAX_DEPTH$6; depth += 1) {
|
|
23766
24504
|
const parent = parentOf$1(ecs, current);
|
|
23767
24505
|
if (parent === 0) break;
|
|
23768
24506
|
const above = ecs.get(parent, Transform) ?? Transform.defaults;
|
|
@@ -24103,7 +24841,7 @@ const RETAKE_STREAK = 3;
|
|
|
24103
24841
|
/** Frames in a row with no mark in it before a live range is kept as a picture again (spec 23's `CALM_AFTER`). */
|
|
24104
24842
|
const CALM_FRAMES = 8;
|
|
24105
24843
|
/** How deep a walk up the tree goes. */
|
|
24106
|
-
const MAX_DEPTH$
|
|
24844
|
+
const MAX_DEPTH$5 = 32;
|
|
24107
24845
|
/** CSS px a view may fall short of the viewport frame and still cover it: the reference frame rounds by a fraction. */
|
|
24108
24846
|
const COVER_SLACK = 1;
|
|
24109
24847
|
/** The whole-frame reasons after which a kept picture is taken again: the screen changed whole. */
|
|
@@ -24267,7 +25005,7 @@ function sameList$1(left, right) {
|
|
|
24267
25005
|
*/
|
|
24268
25006
|
function inRange(set, node, root) {
|
|
24269
25007
|
let current = node;
|
|
24270
|
-
for (let depth = 0; depth < MAX_DEPTH$
|
|
25008
|
+
for (let depth = 0; depth < MAX_DEPTH$5 && current !== null && current !== root; depth += 1) {
|
|
24271
25009
|
if (set.has(current)) return true;
|
|
24272
25010
|
current = current.parent;
|
|
24273
25011
|
}
|
|
@@ -27225,7 +27963,7 @@ function installPassCounting(pixi, sinks) {
|
|
|
27225
27963
|
//#endregion
|
|
27226
27964
|
//#region src/plugins/renderer/sync/hit-test.ts
|
|
27227
27965
|
/** How deep a parent chain or a container tree is followed. */
|
|
27228
|
-
const MAX_DEPTH$
|
|
27966
|
+
const MAX_DEPTH$4 = 32;
|
|
27229
27967
|
/** Below this effective alpha a view is treated as not there. */
|
|
27230
27968
|
const MIN_ALPHA = .01;
|
|
27231
27969
|
/**
|
|
@@ -27307,7 +28045,7 @@ function clipBoxOf(sctx, entity) {
|
|
|
27307
28045
|
function clippedOut(sctx, entity, x, y) {
|
|
27308
28046
|
const ecs = sctx.ctx.deps.world.ecs;
|
|
27309
28047
|
let current = parentOf$1(ecs, entity);
|
|
27310
|
-
for (let depth = 0; depth < MAX_DEPTH$
|
|
28048
|
+
for (let depth = 0; depth < MAX_DEPTH$4 && current !== 0; depth += 1) {
|
|
27311
28049
|
const box = clipBoxOf(sctx, current);
|
|
27312
28050
|
if (box !== void 0 && !inBox(box, localPoint(sctx, current, x, y))) return true;
|
|
27313
28051
|
current = parentOf$1(ecs, current);
|
|
@@ -27326,7 +28064,7 @@ function clippedOut(sctx, entity, x, y) {
|
|
|
27326
28064
|
function effectiveAlpha(object, root, off) {
|
|
27327
28065
|
let alpha = 1;
|
|
27328
28066
|
let current = object;
|
|
27329
|
-
for (let depth = 0; depth < MAX_DEPTH$
|
|
28067
|
+
for (let depth = 0; depth < MAX_DEPTH$4 && current !== void 0; depth += 1) {
|
|
27330
28068
|
if (current === root) return alpha;
|
|
27331
28069
|
if (!current.visible || off?.has(current) === true) return 0;
|
|
27332
28070
|
alpha *= current.alpha;
|
|
@@ -27356,7 +28094,7 @@ function isShown$1(object, root, off) {
|
|
|
27356
28094
|
* @returns The same array.
|
|
27357
28095
|
*/
|
|
27358
28096
|
function topFirst(container, out, depth = 0) {
|
|
27359
|
-
if (depth >= MAX_DEPTH$
|
|
28097
|
+
if (depth >= MAX_DEPTH$4) return out;
|
|
27360
28098
|
const ordered = container.children.toSorted((left, right) => left.zIndex - right.zIndex);
|
|
27361
28099
|
for (let index = ordered.length - 1; index >= 0; index -= 1) {
|
|
27362
28100
|
const child = ordered[index];
|
|
@@ -27445,7 +28183,7 @@ function hitAll(sctx, x, y) {
|
|
|
27445
28183
|
* hit test walks, so a box the doors report is the box a click reaches.
|
|
27446
28184
|
*/
|
|
27447
28185
|
/** How deep a parent chain is followed; deeper is treated as a loop. */
|
|
27448
|
-
const MAX_DEPTH$
|
|
28186
|
+
const MAX_DEPTH$3 = 32;
|
|
27449
28187
|
/**
|
|
27450
28188
|
* The four corners of a box, clockwise from the top-left one.
|
|
27451
28189
|
*
|
|
@@ -27539,7 +28277,7 @@ function boundsOf(sctx, entity) {
|
|
|
27539
28277
|
function layerOf(sctx, entity) {
|
|
27540
28278
|
const ecs = sctx.ctx.deps.world.ecs;
|
|
27541
28279
|
let top = entity;
|
|
27542
|
-
for (let depth = 0; depth < MAX_DEPTH$
|
|
28280
|
+
for (let depth = 0; depth < MAX_DEPTH$3; depth += 1) {
|
|
27543
28281
|
const parent = parentOf$1(ecs, top);
|
|
27544
28282
|
if (parent === 0) break;
|
|
27545
28283
|
top = parent;
|
|
@@ -27954,7 +28692,7 @@ function stepParts(sctx) {
|
|
|
27954
28692
|
//#endregion
|
|
27955
28693
|
//#region src/plugins/renderer/sync/overlay.ts
|
|
27956
28694
|
/** How deep a `Parent` chain or a walk up the tree goes. */
|
|
27957
|
-
const MAX_DEPTH$
|
|
28695
|
+
const MAX_DEPTH$2 = 32;
|
|
27958
28696
|
/** The owner `ui` gives the entities it spawns: the only overlays honoured (O4.8). */
|
|
27959
28697
|
const UI_OWNER_NAME = "ui";
|
|
27960
28698
|
/**
|
|
@@ -28023,7 +28761,7 @@ function clampInto(sctx, entity, into, raw) {
|
|
|
28023
28761
|
function rootOf(sctx, entity) {
|
|
28024
28762
|
const ecs = sctx.ctx.deps.world.ecs;
|
|
28025
28763
|
let current = entity;
|
|
28026
|
-
for (let depth = 0; depth < MAX_DEPTH$
|
|
28764
|
+
for (let depth = 0; depth < MAX_DEPTH$2; depth += 1) {
|
|
28027
28765
|
const parent = parentOf$1(ecs, current);
|
|
28028
28766
|
if (parent === 0) return current;
|
|
28029
28767
|
current = parent;
|
|
@@ -28236,7 +28974,7 @@ function drawnAlpha(sctx, record) {
|
|
|
28236
28974
|
const alphaFilter = sctx.deps.host.pixi()?.AlphaFilter;
|
|
28237
28975
|
let alpha = 1;
|
|
28238
28976
|
let node = view.wrapper ?? view.object;
|
|
28239
|
-
for (let depth = 0; depth < MAX_DEPTH$
|
|
28977
|
+
for (let depth = 0; depth < MAX_DEPTH$2 && node !== null; depth += 1) {
|
|
28240
28978
|
if (node === root) return alpha;
|
|
28241
28979
|
if (!node.visible) return 0;
|
|
28242
28980
|
const owner = state.viewOfTop.get(node);
|
|
@@ -29683,9 +30421,9 @@ function createRendererApi(ctx) {
|
|
|
29683
30421
|
//#region src/plugins/renderer/handlers.ts
|
|
29684
30422
|
/**
|
|
29685
30423
|
* @file renderer plugin — the hooks: a pause hands the captures still waiting for a drawn frame
|
|
29686
|
-
* their picture, since no frame comes while the clock is paused, and trims Pixi's texture pool
|
|
29687
|
-
*
|
|
29688
|
-
* changes of the frame rate.
|
|
30424
|
+
* their picture, since no frame comes while the clock is paused, and trims Pixi's texture pool; a change of the frame
|
|
30425
|
+
* rate brings the render scale the adaptive rule chose. In an agent build the performance monitor also hears the pause
|
|
30426
|
+
* stack, the flow path, the thermal readings, the changes of the frame rate and of the scale.
|
|
29689
30427
|
*/
|
|
29690
30428
|
/**
|
|
29691
30429
|
* Creates the hooks of the renderer. `time` is resolved when a hook fires, not while this factory
|
|
@@ -29743,13 +30481,23 @@ function createHandlers$5(ctx) {
|
|
|
29743
30481
|
if (typeof __MOKU_GAME_AGENT__ !== "undefined" && __MOKU_GAME_AGENT__) monitorOf().thermal(reading);
|
|
29744
30482
|
},
|
|
29745
30483
|
/**
|
|
29746
|
-
*
|
|
29747
|
-
*
|
|
30484
|
+
* Draws at the render scale the change carries from the next frame, in every build. An agent build writes the
|
|
30485
|
+
* change as a `rate` event line, the payload as its detail, and a move of the scale as a `scale` line.
|
|
29748
30486
|
*
|
|
29749
30487
|
* @param change - The change `time:rate` carried.
|
|
29750
30488
|
*/
|
|
29751
30489
|
"time:rate": (change) => {
|
|
29752
|
-
|
|
30490
|
+
const from = ctx.state.host.resolution.scale;
|
|
30491
|
+
followScale(ctx.state.host, change.scale);
|
|
30492
|
+
if (typeof __MOKU_GAME_AGENT__ !== "undefined" && __MOKU_GAME_AGENT__) {
|
|
30493
|
+
monitorOf().event("rate", change);
|
|
30494
|
+
if (change.scale !== from) monitorOf().event("scale", {
|
|
30495
|
+
from,
|
|
30496
|
+
to: change.scale,
|
|
30497
|
+
reason: change.reason,
|
|
30498
|
+
fps: change.to
|
|
30499
|
+
});
|
|
30500
|
+
}
|
|
29753
30501
|
}
|
|
29754
30502
|
};
|
|
29755
30503
|
}
|
|
@@ -29974,12 +30722,28 @@ function checkPoolTrim(poolTrim) {
|
|
|
29974
30722
|
if (!isCount(aboveMb)) throw new Error(`[game] renderer.poolTrim.aboveMb is ${String(aboveMb)}.\n Give the MiB the targets must grow by, 0 or more.`);
|
|
29975
30723
|
}
|
|
29976
30724
|
/**
|
|
30725
|
+
* Checks the adaptive render scale of the config: `false`, or a floor that is a step of the scale and the devices it
|
|
30726
|
+
* applies to.
|
|
30727
|
+
*
|
|
30728
|
+
* @param adaptiveScale - What the config holds.
|
|
30729
|
+
* @throws {Error} When it is neither `false` nor an object, the floor is not 1, 0.85, 0.75, 0.6 or 0.5, or the
|
|
30730
|
+
* devices are neither `"phone"` nor `"all"`.
|
|
30731
|
+
*/
|
|
30732
|
+
function checkAdaptiveScale(adaptiveScale) {
|
|
30733
|
+
if (adaptiveScale === false) return;
|
|
30734
|
+
if (typeof adaptiveScale !== "object" || adaptiveScale === null) throw new Error(`[game] renderer.adaptiveScale is ${String(adaptiveScale)}.\n Give { floor, devices } or false.`);
|
|
30735
|
+
const floor = "floor" in adaptiveScale ? adaptiveScale.floor : void 0;
|
|
30736
|
+
const devices = "devices" in adaptiveScale ? adaptiveScale.devices : void 0;
|
|
30737
|
+
if (typeof floor !== "number" || !SCALE_STEPS.includes(floor)) throw new Error(`[game] renderer.adaptiveScale.floor is ${String(floor)}.\n Give a step of the scale: 1, 0.85, 0.75, 0.6 or 0.5.`);
|
|
30738
|
+
if (devices !== "phone" && devices !== "all") throw new Error(`[game] renderer.adaptiveScale.devices is ${String(devices)}.\n Give "phone" or "all".`);
|
|
30739
|
+
}
|
|
30740
|
+
/**
|
|
29977
30741
|
* Checks the numbers of the config a game can get wrong, at `createApp`, in every build: the
|
|
29978
|
-
* resolution cap, the pool trim, the performance monitor, the budget of the kept pictures
|
|
29979
|
-
* probe draws of the warm-up.
|
|
30742
|
+
* resolution cap, the pool trim, the adaptive render scale, the performance monitor, the budget of the kept pictures
|
|
30743
|
+
* and the probe draws of the warm-up.
|
|
29980
30744
|
*
|
|
29981
30745
|
* @param config - The resolved plugin config.
|
|
29982
|
-
* @throws {Error} When `maxResolution` is not a number above 0, `poolTrim` fails its check,
|
|
30746
|
+
* @throws {Error} When `maxResolution` is not a number above 0, `poolTrim` or `adaptiveScale` fails its check,
|
|
29983
30747
|
* `monitor.budgetMs` is neither `"rate"` nor a number above 0, `monitor.frames` is not a whole
|
|
29984
30748
|
* number from 64 to 16384, `keptMb` is not a finite number of at least 0, or `warm` is not a
|
|
29985
30749
|
* whole number from 0 to 16.
|
|
@@ -29989,6 +30753,7 @@ function checkConfig(config) {
|
|
|
29989
30753
|
const { budgetMs, frames } = config.monitor;
|
|
29990
30754
|
if (!Number.isFinite(maxResolution) || maxResolution <= 0) throw new Error(`[game] renderer.maxResolution is ${maxResolution}.\n Give a number above 0.`);
|
|
29991
30755
|
checkPoolTrim(config.poolTrim);
|
|
30756
|
+
checkAdaptiveScale(config.adaptiveScale);
|
|
29992
30757
|
if (budgetMs !== "rate" && !(Number.isFinite(budgetMs) && budgetMs > 0)) throw new Error(`[game] renderer.monitor.budgetMs is ${String(budgetMs)}.\n Give the frame budget in milliseconds, or "rate" for one frame of the current rate.`);
|
|
29993
30758
|
if (!Number.isInteger(frames) || frames < RING_FRAMES.min || frames > RING_FRAMES.max) throw new Error(`[game] renderer.monitor.frames is ${frames}.\n Give a whole number from 64 to 16384.`);
|
|
29994
30759
|
if (typeof keptMb !== "number" || !Number.isFinite(keptMb) || keptMb < 0) throw new Error(`[game] renderer.keptMb is ${String(keptMb)}.\n Give the MiB kept pictures may hold, or 0 to keep none.`);
|
|
@@ -30074,7 +30839,11 @@ const rendererPlugin = /*#__PURE__*/ createPlugin$1("renderer", {
|
|
|
30074
30839
|
},
|
|
30075
30840
|
onDemand: false,
|
|
30076
30841
|
keptMb: 64,
|
|
30077
|
-
warm: 1
|
|
30842
|
+
warm: 1,
|
|
30843
|
+
adaptiveScale: {
|
|
30844
|
+
floor: .75,
|
|
30845
|
+
devices: "phone"
|
|
30846
|
+
}
|
|
30078
30847
|
},
|
|
30079
30848
|
events: (register) => register.map({ "renderer:device-lost": "The GPU device or context was lost; the game is paused" }),
|
|
30080
30849
|
createState: createRendererState,
|
|
@@ -35393,7 +36162,7 @@ async function settledRecord(state, bundle) {
|
|
|
35393
36162
|
* @param size - The size of the decoded image.
|
|
35394
36163
|
* @param mips - True for a texture uploaded with its full mip chain.
|
|
35395
36164
|
*/
|
|
35396
|
-
function resize(target, size, mips) {
|
|
36165
|
+
function resize$1(target, size, mips) {
|
|
35397
36166
|
if (target === void 0 || size === void 0) return;
|
|
35398
36167
|
target.width = size.width;
|
|
35399
36168
|
target.height = size.height;
|
|
@@ -35415,7 +36184,7 @@ function storeFont(io, record, owner, font, sizes) {
|
|
|
35415
36184
|
const old = record.fonts.get(file.key);
|
|
35416
36185
|
record.fonts.set(file.key, font);
|
|
35417
36186
|
for (const page of old?.pages ?? []) io.destroyTexture(page);
|
|
35418
|
-
for (const [at, texture] of font.pages.entries()) resize(pages[at], sizes.get(texture), false);
|
|
36187
|
+
for (const [at, texture] of font.pages.entries()) resize$1(pages[at], sizes.get(texture), false);
|
|
35419
36188
|
file.mb = sumMb(pages);
|
|
35420
36189
|
}
|
|
35421
36190
|
/**
|
|
@@ -35440,7 +36209,7 @@ function store(io, record, fresh, sizes) {
|
|
|
35440
36209
|
const old = record.textures.get(file.key);
|
|
35441
36210
|
record.textures.set(file.key, fresh.texture);
|
|
35442
36211
|
if (old !== void 0) io.destroyTexture(old);
|
|
35443
|
-
resize(file, sizes.get(fresh.texture), file.mips === true);
|
|
36212
|
+
resize$1(file, sizes.get(fresh.texture), file.mips === true);
|
|
35444
36213
|
}
|
|
35445
36214
|
/**
|
|
35446
36215
|
* Makes the numbers and the picture follow the files that were stored: the megabytes of the
|
|
@@ -35880,7 +36649,7 @@ function leavingOf(previous, scene) {
|
|
|
35880
36649
|
* Builds the scene in one synchronous block: the layers, then the projections only the old scene
|
|
35881
36650
|
* lists out, then the list of the new scene in (a projection both list stays and takes its new
|
|
35882
36651
|
* place), the new scene to the world's systems, then the list the scene names to `assets`, then
|
|
35883
|
-
* the frame loop out of its idle cap, and only then the event.
|
|
36652
|
+
* the frame loop out of its idle cap and the adaptive frame rate told of the new screen, and only then the event.
|
|
35884
36653
|
*
|
|
35885
36654
|
* @param ctx - Domain context of the plugin.
|
|
35886
36655
|
* @param scene - The scene whose bundles are there.
|
|
@@ -35899,6 +36668,7 @@ function apply(ctx, scene, named) {
|
|
|
35899
36668
|
state.named = named;
|
|
35900
36669
|
ctx.deps.assets.sceneBundles(named);
|
|
35901
36670
|
ctx.deps.time.wake();
|
|
36671
|
+
ctx.deps.time.sceneChanged();
|
|
35902
36672
|
ctx.emit("scenes:changed", {
|
|
35903
36673
|
from,
|
|
35904
36674
|
to: scene.id,
|
|
@@ -37645,96 +38415,6 @@ function createEffectsApi(ctx) {
|
|
|
37645
38415
|
};
|
|
37646
38416
|
}
|
|
37647
38417
|
//#endregion
|
|
37648
|
-
//#region src/plugins/effects/filters/glsl.ts
|
|
37649
|
-
/**
|
|
37650
|
-
* The head of every filter's GLSL. `#version 300 es` comes first, so Pixi compiles both stages
|
|
37651
|
-
* as GLSL ES 3.0 and the dev check compiles the same text; `highp` matches the `f32` of the WGSL
|
|
37652
|
-
* twin and the precision of the uniforms the vertex stage shares. Then the texture coordinate of
|
|
37653
|
-
* `defaultFilterVert`, the output, the input texture and the global filter uniforms as `vec4`.
|
|
37654
|
-
*/
|
|
37655
|
-
const GLSL_HEADER = `#version 300 es
|
|
37656
|
-
precision highp float;
|
|
37657
|
-
|
|
37658
|
-
in vec2 vTextureCoord;
|
|
37659
|
-
out vec4 finalColor;
|
|
37660
|
-
|
|
37661
|
-
uniform sampler2D uTexture;
|
|
37662
|
-
uniform vec4 uInputSize;
|
|
37663
|
-
uniform vec4 uInputPixel;
|
|
37664
|
-
uniform vec4 uInputClamp;
|
|
37665
|
-
uniform vec4 uOutputFrame;
|
|
37666
|
-
uniform vec4 uGlobalFrame;
|
|
37667
|
-
uniform vec4 uOutputTexture;
|
|
37668
|
-
|
|
37669
|
-
`;
|
|
37670
|
-
/** The GLSL type of each uniform type: a number is a `float`, a colour or a tuple a vector. */
|
|
37671
|
-
const GLSL_TYPES = {
|
|
37672
|
-
f32: "float",
|
|
37673
|
-
"vec2<f32>": "vec2",
|
|
37674
|
-
"vec3<f32>": "vec3",
|
|
37675
|
-
"vec4<f32>": "vec4"
|
|
37676
|
-
};
|
|
37677
|
-
/**
|
|
37678
|
-
* One `uniform` line per uniform, in declaration order, under the bare names the group keys.
|
|
37679
|
-
* Nothing without uniforms.
|
|
37680
|
-
*
|
|
37681
|
-
* @param uniforms - The uniforms in declaration order.
|
|
37682
|
-
* @returns The GLSL lines, or `""`.
|
|
37683
|
-
* @example
|
|
37684
|
-
* ```ts
|
|
37685
|
-
* glslUniforms([{ name: "color", type: "vec3<f32>", form: "color", size: 3 }]); // "uniform vec3 color;\n\n"
|
|
37686
|
-
* ```
|
|
37687
|
-
*/
|
|
37688
|
-
function glslUniforms(uniforms) {
|
|
37689
|
-
if (uniforms.length === 0) return "";
|
|
37690
|
-
return `${uniforms.map((uniform) => `uniform ${GLSL_TYPES[uniform.type]} ${uniform.name};\n`).join("")}\n`;
|
|
37691
|
-
}
|
|
37692
|
-
/**
|
|
37693
|
-
* The engine's half of each input in GLSL: the texture, and the function that reads it at the body's uv, its scale
|
|
37694
|
-
* and its clamp applied. The two uniforms it reads are declared before it by `glslUniforms`. Nothing without inputs.
|
|
37695
|
-
*
|
|
37696
|
-
* @param inputs - The inputs in declaration order.
|
|
37697
|
-
* @returns The GLSL lines, or `""`.
|
|
37698
|
-
* @example
|
|
37699
|
-
* ```ts
|
|
37700
|
-
* glslInputs([blurInput]).startsWith("uniform sampler2D blurTexture;"); // true
|
|
37701
|
-
* ```
|
|
37702
|
-
*/
|
|
37703
|
-
function glslInputs(inputs) {
|
|
37704
|
-
return inputs.map((input) => `uniform sampler2D ${input.texture};\n\nvec4 ${input.sample}(vec2 uv) {\n return textureLod(${input.texture}, clamp(uv * uInputSize.xy * ${input.scale.name}, ${input.clamp.name}.xy, ${input.clamp.name}.zw), 0.0);\n}
|
|
37705
|
-
|
|
37706
|
-
`).join("");
|
|
37707
|
-
}
|
|
37708
|
-
/** The GLSL twin of `canvasPixel(uv)`: the pixel's canvas position in CSS px (spec 23 P8.3). */
|
|
37709
|
-
const GLSL_CANVAS_PIXEL = `vec2 canvasPixel(vec2 uv) {
|
|
37710
|
-
return uOutputFrame.xy + keptOrigin + uv * uInputSize.xy;
|
|
37711
|
-
}
|
|
37712
|
-
|
|
37713
|
-
`;
|
|
37714
|
-
/**
|
|
37715
|
-
* The whole GLSL of a filter: the header, the uniforms (then the scale and the clamp of each input, then the
|
|
37716
|
-
* engine's `keptOrigin` of a body that calls `canvasPixel`), the inputs, the helper, the body.
|
|
37717
|
-
*
|
|
37718
|
-
* @param uniforms - The uniforms in declaration order.
|
|
37719
|
-
* @param inputs - The inputs in declaration order.
|
|
37720
|
-
* @param body - The fragment body the game wrote.
|
|
37721
|
-
* @param origin - The `keptOrigin` uniform, for a body that calls `canvasPixel`.
|
|
37722
|
-
* @returns The GLSL fragment source the `GlProgram` is built from.
|
|
37723
|
-
* @example
|
|
37724
|
-
* ```ts
|
|
37725
|
-
* assembleGlsl([], [], body).endsWith(body); // true
|
|
37726
|
-
* ```
|
|
37727
|
-
*/
|
|
37728
|
-
function assembleGlsl(uniforms, inputs, body, origin) {
|
|
37729
|
-
const declared = glslUniforms([
|
|
37730
|
-
...uniforms,
|
|
37731
|
-
...inputs.flatMap((input) => [input.scale, input.clamp]),
|
|
37732
|
-
...origin === void 0 ? [] : [origin]
|
|
37733
|
-
]);
|
|
37734
|
-
const helper = origin === void 0 ? "" : GLSL_CANVAS_PIXEL;
|
|
37735
|
-
return `${GLSL_HEADER}${declared}${glslInputs(inputs)}${helper}${body}`;
|
|
37736
|
-
}
|
|
37737
|
-
//#endregion
|
|
37738
38418
|
//#region src/plugins/effects/filters/wgsl.ts
|
|
37739
38419
|
/**
|
|
37740
38420
|
* The head of every filter shader: the global filter uniforms and the input texture and sampler
|
|
@@ -37811,30 +38491,165 @@ const CANVAS_PIXEL = `fn canvasPixel(uv: vec2<f32>) -> vec2<f32> {
|
|
|
37811
38491
|
return gfu.uOutputFrame.xy + fu.keptOrigin + uv * gfu.uInputSize.xy;
|
|
37812
38492
|
}
|
|
37813
38493
|
|
|
38494
|
+
`;
|
|
38495
|
+
/**
|
|
38496
|
+
* The engine's uniform a backdrop's `sampleBack` scales its uv by: one over the size of the back texture's backing,
|
|
38497
|
+
* CSS px. Filled by every apply from the texture Pixi copied.
|
|
38498
|
+
*/
|
|
38499
|
+
const BACK_SCALE = Object.freeze({
|
|
38500
|
+
name: "backScale",
|
|
38501
|
+
type: "vec2<f32>",
|
|
38502
|
+
form: "vector",
|
|
38503
|
+
size: 2
|
|
38504
|
+
});
|
|
38505
|
+
/** The engine's uniform a backdrop's `sampleBack` clamps to: half a texel inside the copied part of the back texture. */
|
|
38506
|
+
const BACK_CLAMP = Object.freeze({
|
|
38507
|
+
name: "backClamp",
|
|
38508
|
+
type: "vec4<f32>",
|
|
38509
|
+
form: "vector",
|
|
38510
|
+
size: 4
|
|
38511
|
+
});
|
|
38512
|
+
/**
|
|
38513
|
+
* What a backdrop's body reads what was drawn under its view through: the texture Pixi's filter system copies the
|
|
38514
|
+
* canvas into and binds at group 0 binding 3 (`Filter.blendRequired`), sampled at the body's uv with the engine's
|
|
38515
|
+
* scale and clamp, as an input is.
|
|
38516
|
+
*/
|
|
38517
|
+
const SAMPLE_BACK = `@group(0) @binding(3) var uBackTexture: texture_2d<f32>;
|
|
38518
|
+
|
|
38519
|
+
fn sampleBack(uv: vec2<f32>) -> vec4<f32> {
|
|
38520
|
+
return textureSampleLevel(uBackTexture, uSampler, clamp(uv * gfu.uInputSize.xy * fu.backScale, fu.backClamp.xy, fu.backClamp.zw), 0.0);
|
|
38521
|
+
}
|
|
38522
|
+
|
|
37814
38523
|
`;
|
|
37815
38524
|
/**
|
|
37816
38525
|
* The whole source of a filter: the header, the uniform struct (the uniforms, then the scale and the clamp of each
|
|
37817
|
-
* input, then the engine's `keptOrigin` of a body that calls `canvasPixel`),
|
|
37818
|
-
*
|
|
38526
|
+
* input, then a backdrop's back scale and clamp, then the engine's `keptOrigin` of a body that calls `canvasPixel`),
|
|
38527
|
+
* the input bindings, a backdrop's `sampleBack`, the `canvasPixel` helper, the body. A body without either helper
|
|
38528
|
+
* assembles byte-identically to one without the cache and without the backdrop.
|
|
37819
38529
|
*
|
|
37820
38530
|
* @param uniforms - The uniforms in declaration order.
|
|
37821
38531
|
* @param inputs - The inputs in declaration order.
|
|
37822
38532
|
* @param body - The fragment body the game wrote.
|
|
37823
38533
|
* @param origin - The `keptOrigin` uniform, for a body that calls `canvasPixel`.
|
|
38534
|
+
* @param backdrop - True for a filter that reads what was drawn under its view.
|
|
37824
38535
|
* @returns The WGSL source both stages are built from.
|
|
37825
38536
|
* @example
|
|
37826
38537
|
* ```ts
|
|
37827
|
-
* assemble([], [], body).
|
|
38538
|
+
* assemble([], [], body, undefined, true).includes("fn sampleBack(uv: vec2<f32>) -> vec4<f32>"); // true
|
|
37828
38539
|
* ```
|
|
37829
38540
|
*/
|
|
37830
|
-
function assemble(uniforms, inputs, body, origin) {
|
|
38541
|
+
function assemble(uniforms, inputs, body, origin, backdrop = false) {
|
|
37831
38542
|
const struct = uniformStruct([
|
|
37832
38543
|
...uniforms,
|
|
37833
38544
|
...inputs.flatMap((input) => [input.scale, input.clamp]),
|
|
38545
|
+
...backdrop ? [BACK_SCALE, BACK_CLAMP] : [],
|
|
37834
38546
|
...origin === void 0 ? [] : [origin]
|
|
37835
38547
|
]);
|
|
38548
|
+
const back = backdrop ? SAMPLE_BACK : "";
|
|
37836
38549
|
const helper = origin === void 0 ? "" : CANVAS_PIXEL;
|
|
37837
|
-
return `${HEADER}${struct}${inputBindings(inputs)}${helper}${body}`;
|
|
38550
|
+
return `${HEADER}${struct}${inputBindings(inputs)}${back}${helper}${body}`;
|
|
38551
|
+
}
|
|
38552
|
+
//#endregion
|
|
38553
|
+
//#region src/plugins/effects/filters/glsl.ts
|
|
38554
|
+
/**
|
|
38555
|
+
* The head of every filter's GLSL. `#version 300 es` comes first, so Pixi compiles both stages
|
|
38556
|
+
* as GLSL ES 3.0 and the dev check compiles the same text; `highp` matches the `f32` of the WGSL
|
|
38557
|
+
* twin and the precision of the uniforms the vertex stage shares. Then the texture coordinate of
|
|
38558
|
+
* `defaultFilterVert`, the output, the input texture and the global filter uniforms as `vec4`.
|
|
38559
|
+
*/
|
|
38560
|
+
const GLSL_HEADER = `#version 300 es
|
|
38561
|
+
precision highp float;
|
|
38562
|
+
|
|
38563
|
+
in vec2 vTextureCoord;
|
|
38564
|
+
out vec4 finalColor;
|
|
38565
|
+
|
|
38566
|
+
uniform sampler2D uTexture;
|
|
38567
|
+
uniform vec4 uInputSize;
|
|
38568
|
+
uniform vec4 uInputPixel;
|
|
38569
|
+
uniform vec4 uInputClamp;
|
|
38570
|
+
uniform vec4 uOutputFrame;
|
|
38571
|
+
uniform vec4 uGlobalFrame;
|
|
38572
|
+
uniform vec4 uOutputTexture;
|
|
38573
|
+
|
|
38574
|
+
`;
|
|
38575
|
+
/** The GLSL type of each uniform type: a number is a `float`, a colour or a tuple a vector. */
|
|
38576
|
+
const GLSL_TYPES = {
|
|
38577
|
+
f32: "float",
|
|
38578
|
+
"vec2<f32>": "vec2",
|
|
38579
|
+
"vec3<f32>": "vec3",
|
|
38580
|
+
"vec4<f32>": "vec4"
|
|
38581
|
+
};
|
|
38582
|
+
/**
|
|
38583
|
+
* One `uniform` line per uniform, in declaration order, under the bare names the group keys.
|
|
38584
|
+
* Nothing without uniforms.
|
|
38585
|
+
*
|
|
38586
|
+
* @param uniforms - The uniforms in declaration order.
|
|
38587
|
+
* @returns The GLSL lines, or `""`.
|
|
38588
|
+
* @example
|
|
38589
|
+
* ```ts
|
|
38590
|
+
* glslUniforms([{ name: "color", type: "vec3<f32>", form: "color", size: 3 }]); // "uniform vec3 color;\n\n"
|
|
38591
|
+
* ```
|
|
38592
|
+
*/
|
|
38593
|
+
function glslUniforms(uniforms) {
|
|
38594
|
+
if (uniforms.length === 0) return "";
|
|
38595
|
+
return `${uniforms.map((uniform) => `uniform ${GLSL_TYPES[uniform.type]} ${uniform.name};\n`).join("")}\n`;
|
|
38596
|
+
}
|
|
38597
|
+
/**
|
|
38598
|
+
* The engine's half of each input in GLSL: the texture, and the function that reads it at the body's uv, its scale
|
|
38599
|
+
* and its clamp applied. The two uniforms it reads are declared before it by `glslUniforms`. Nothing without inputs.
|
|
38600
|
+
*
|
|
38601
|
+
* @param inputs - The inputs in declaration order.
|
|
38602
|
+
* @returns The GLSL lines, or `""`.
|
|
38603
|
+
* @example
|
|
38604
|
+
* ```ts
|
|
38605
|
+
* glslInputs([blurInput]).startsWith("uniform sampler2D blurTexture;"); // true
|
|
38606
|
+
* ```
|
|
38607
|
+
*/
|
|
38608
|
+
function glslInputs(inputs) {
|
|
38609
|
+
return inputs.map((input) => `uniform sampler2D ${input.texture};\n\nvec4 ${input.sample}(vec2 uv) {\n return textureLod(${input.texture}, clamp(uv * uInputSize.xy * ${input.scale.name}, ${input.clamp.name}.xy, ${input.clamp.name}.zw), 0.0);\n}
|
|
38610
|
+
|
|
38611
|
+
`).join("");
|
|
38612
|
+
}
|
|
38613
|
+
/** The GLSL twin of `canvasPixel(uv)`: the pixel's canvas position in CSS px (spec 23 P8.3). */
|
|
38614
|
+
const GLSL_CANVAS_PIXEL = `vec2 canvasPixel(vec2 uv) {
|
|
38615
|
+
return uOutputFrame.xy + keptOrigin + uv * uInputSize.xy;
|
|
38616
|
+
}
|
|
38617
|
+
|
|
38618
|
+
`;
|
|
38619
|
+
/** The GLSL twin of `sampleBack(uv)`: what was drawn under the view, at the body's uv, scaled and clamped. */
|
|
38620
|
+
const GLSL_SAMPLE_BACK = `uniform sampler2D uBackTexture;
|
|
38621
|
+
|
|
38622
|
+
vec4 sampleBack(vec2 uv) {
|
|
38623
|
+
return textureLod(uBackTexture, clamp(uv * uInputSize.xy * backScale, backClamp.xy, backClamp.zw), 0.0);
|
|
38624
|
+
}
|
|
38625
|
+
|
|
38626
|
+
`;
|
|
38627
|
+
/**
|
|
38628
|
+
* The whole GLSL of a filter: the header, the uniforms (then the scale and the clamp of each input, then a backdrop's
|
|
38629
|
+
* back scale and clamp, then the engine's `keptOrigin` of a body that calls `canvasPixel`), the inputs, a backdrop's
|
|
38630
|
+
* `sampleBack`, the `canvasPixel` helper, the body.
|
|
38631
|
+
*
|
|
38632
|
+
* @param uniforms - The uniforms in declaration order.
|
|
38633
|
+
* @param inputs - The inputs in declaration order.
|
|
38634
|
+
* @param body - The fragment body the game wrote.
|
|
38635
|
+
* @param origin - The `keptOrigin` uniform, for a body that calls `canvasPixel`.
|
|
38636
|
+
* @param backdrop - True for a filter that reads what was drawn under its view.
|
|
38637
|
+
* @returns The GLSL fragment source the `GlProgram` is built from.
|
|
38638
|
+
* @example
|
|
38639
|
+
* ```ts
|
|
38640
|
+
* assembleGlsl([], [], body, undefined, true).includes("uniform sampler2D uBackTexture;"); // true
|
|
38641
|
+
* ```
|
|
38642
|
+
*/
|
|
38643
|
+
function assembleGlsl(uniforms, inputs, body, origin, backdrop = false) {
|
|
38644
|
+
const declared = glslUniforms([
|
|
38645
|
+
...uniforms,
|
|
38646
|
+
...inputs.flatMap((input) => [input.scale, input.clamp]),
|
|
38647
|
+
...backdrop ? [BACK_SCALE, BACK_CLAMP] : [],
|
|
38648
|
+
...origin === void 0 ? [] : [origin]
|
|
38649
|
+
]);
|
|
38650
|
+
const back = backdrop ? GLSL_SAMPLE_BACK : "";
|
|
38651
|
+
const helper = origin === void 0 ? "" : GLSL_CANVAS_PIXEL;
|
|
38652
|
+
return `${GLSL_HEADER}${declared}${glslInputs(inputs)}${back}${helper}${body}`;
|
|
37838
38653
|
}
|
|
37839
38654
|
//#endregion
|
|
37840
38655
|
//#region src/plugins/effects/filters/define.ts
|
|
@@ -37859,9 +38674,10 @@ const MAIN_GLSL = /\bvoid\s+main\s*\(/;
|
|
|
37859
38674
|
/**
|
|
37860
38675
|
* Names a uniform cannot take: the two fields every filter component carries, and the names the
|
|
37861
38676
|
* shader headers declare, which a game uniform would clash with (on WebGL only, for most). The
|
|
37862
|
-
* GLSL header also declares `finalColor` and `vTextureCoord
|
|
38677
|
+
* GLSL header also declares `finalColor` and `vTextureCoord`; a backdrop's, `uBackTexture`,
|
|
38678
|
+
* `backScale`, `backClamp` and `sampleBack`.
|
|
37863
38679
|
*/
|
|
37864
|
-
const RESERVED_NAME = /^(?:enabled|order|uInputSize|uInputPixel|uInputClamp|uOutputFrame|uGlobalFrame|uOutputTexture|uTexture|uSampler|finalColor|vTextureCoord|keptOrigin|canvasPixel)$/;
|
|
38680
|
+
const RESERVED_NAME = /^(?:enabled|order|uInputSize|uInputPixel|uInputClamp|uOutputFrame|uGlobalFrame|uOutputTexture|uTexture|uSampler|finalColor|vTextureCoord|keptOrigin|canvasPixel|uBackTexture|backScale|backClamp|sampleBack)$/;
|
|
37865
38681
|
/**
|
|
37866
38682
|
* What a body reads of the screen: the root target's frame, the fragment position, the output target's size, the
|
|
37867
38683
|
* origin of the output frame (its size `.zw` is the same in a picture and live).
|
|
@@ -37945,21 +38761,22 @@ function worstOf(a, b) {
|
|
|
37945
38761
|
}
|
|
37946
38762
|
/**
|
|
37947
38763
|
* What the kept-picture cache may do with a filter, from what the game declared and what its bodies read only, never
|
|
37948
|
-
* the engine's header (LEAD-FIXES-4 item 3): `cache: false`
|
|
37949
|
-
* nothing is `"screen"` too (the safe direction); a body that calls `canvasPixel` is `"placed"`,
|
|
37950
|
-
* `cache: "placed"`; everything else is `"local"`.
|
|
38764
|
+
* the engine's header (LEAD-FIXES-4 item 3): a backdrop and `cache: false` are `"screen"`; a body that reads the
|
|
38765
|
+
* screen and declares nothing is `"screen"` too (the safe direction); a body that calls `canvasPixel` is `"placed"`,
|
|
38766
|
+
* as is `cache: "placed"`; everything else is `"local"`.
|
|
37951
38767
|
*
|
|
37952
38768
|
* @param wgsl - The WGSL body.
|
|
37953
38769
|
* @param glsl - The GLSL body.
|
|
37954
38770
|
* @param cache - What the game declared.
|
|
38771
|
+
* @param backdrop - Whether the filter reads what was drawn under its view.
|
|
37955
38772
|
* @returns The category.
|
|
37956
38773
|
* @example
|
|
37957
38774
|
* ```ts
|
|
37958
|
-
* cacheOf("let p = gfu.uOutputFrame.xy + uv * gfu.uInputSize.xy;", "", undefined); // "screen"
|
|
38775
|
+
* cacheOf("let p = gfu.uOutputFrame.xy + uv * gfu.uInputSize.xy;", "", undefined, false); // "screen"
|
|
37959
38776
|
* ```
|
|
37960
38777
|
*/
|
|
37961
|
-
function cacheOf(wgsl, glsl, cache) {
|
|
37962
|
-
if (cache === false) return "screen";
|
|
38778
|
+
function cacheOf(wgsl, glsl, cache, backdrop) {
|
|
38779
|
+
if (cache === false || backdrop) return "screen";
|
|
37963
38780
|
if (cache === void 0 && (readsScreenIn(wgsl) || readsScreenIn(glsl))) return "screen";
|
|
37964
38781
|
if (cache === "placed" || callsCanvasPixel(wgsl, glsl)) return "placed";
|
|
37965
38782
|
return "local";
|
|
@@ -38026,6 +38843,48 @@ function readResolution(id, resolution) {
|
|
|
38026
38843
|
throw new Error(`[game] Filter "${id}": resolution must be from 0 to 4, 0 for the resolution of the canvas.\n Got ${String(resolution)}.`);
|
|
38027
38844
|
}
|
|
38028
38845
|
/**
|
|
38846
|
+
* Reads the declared backdrop switch: `true`, `false` or left out. A JavaScript caller may pass anything.
|
|
38847
|
+
*
|
|
38848
|
+
* @param id - The filter id.
|
|
38849
|
+
* @param backdrop - What the game declared.
|
|
38850
|
+
* @returns True for a backdrop.
|
|
38851
|
+
* @throws {Error} When it is given and is not a boolean.
|
|
38852
|
+
*/
|
|
38853
|
+
function readBackdrop(id, backdrop) {
|
|
38854
|
+
if (backdrop === void 0 || typeof backdrop === "boolean") return backdrop === true;
|
|
38855
|
+
throw new Error(`[game] Filter "${id}": backdrop must be true or false.\n Got ${JSON.stringify(backdrop)}.`);
|
|
38856
|
+
}
|
|
38857
|
+
/**
|
|
38858
|
+
* Refuses what a backdrop cannot have: a padding (Pixi copies the canvas under the padded box, and a copy past the
|
|
38859
|
+
* canvas edge fails on the GPU) and a cache that keeps it (what is under it changes without it).
|
|
38860
|
+
*
|
|
38861
|
+
* @param id - The filter id.
|
|
38862
|
+
* @param padding - The padding as declared, `0` when left out.
|
|
38863
|
+
* @param cache - The cache as declared.
|
|
38864
|
+
* @throws {Error} When the padding is not 0 or the cache keeps it.
|
|
38865
|
+
*/
|
|
38866
|
+
function checkBackdrop(id, padding, cache) {
|
|
38867
|
+
if (padding !== 0) throw new Error(`[game] Filter "${id}" is a backdrop: its padding must be 0.\n Pixi copies the canvas under the padded box, and a copy past the canvas edge fails: leave padding out.`);
|
|
38868
|
+
if (cache === "local" || cache === "placed") throw new Error(`[game] Filter "${id}" is a backdrop: it reads what is drawn under its view, so it is drawn live in every frame.\n Leave cache out, or set it to false.`);
|
|
38869
|
+
}
|
|
38870
|
+
/**
|
|
38871
|
+
* Reads where an input's first step reads: the view's own input (the default) or, for a backdrop, what was drawn under
|
|
38872
|
+
* the view. A JavaScript caller may pass anything.
|
|
38873
|
+
*
|
|
38874
|
+
* @param id - The filter id.
|
|
38875
|
+
* @param name - The input name.
|
|
38876
|
+
* @param from - What the game declared.
|
|
38877
|
+
* @param backdrop - Whether the filter is a backdrop.
|
|
38878
|
+
* @returns `"input"` or `"back"`.
|
|
38879
|
+
* @throws {Error} When it is neither, or `"back"` on a filter that is no backdrop.
|
|
38880
|
+
*/
|
|
38881
|
+
function readFrom(id, name, from, backdrop) {
|
|
38882
|
+
if (from === void 0 || from === "input") return "input";
|
|
38883
|
+
if (from !== "back") throw new Error(`[game] Filter "${id}": input "${name}" starts from ${JSON.stringify(from)}.\n Write from: "input" or from: "back".`);
|
|
38884
|
+
if (!backdrop) throw new Error(`[game] Filter "${id}": input "${name}" starts from "back", which only a backdrop filter can read.\n Give the filter backdrop: true.`);
|
|
38885
|
+
return "back";
|
|
38886
|
+
}
|
|
38887
|
+
/**
|
|
38029
38888
|
* Tells whether a value is a list of 2 to 4 finite numbers.
|
|
38030
38889
|
*
|
|
38031
38890
|
* @param value - A uniform declaration.
|
|
@@ -38206,21 +39065,23 @@ function readStages(id, name, stages) {
|
|
|
38206
39065
|
for (const [index, stage] of stages.entries()) {
|
|
38207
39066
|
if (!isDefinedFilter(stage)) throw new Error(`[game] Filter "${id}": stage ${index} of input "${name}" is not a filter made by defineFilter.\n List what defineFilter returned; a built-in kind cannot be a stage.`);
|
|
38208
39067
|
if (!drawsAlone(stage.filter)) throw new Error(`[game] Filter "${id}": stage "${stage.filter.id}" of input "${name}" has inputs or a fallback of its own.\n A stage draws alone: give it neither.`);
|
|
39068
|
+
if (stage.filter.backdrop) throw new Error(`[game] Filter "${id}": stage "${stage.filter.id}" of input "${name}" is a backdrop.\n A stage reads the step before it: give it no backdrop.`);
|
|
38209
39069
|
if (!read.includes(stage)) read.push(stage);
|
|
38210
39070
|
}
|
|
38211
39071
|
return Object.freeze(read);
|
|
38212
39072
|
}
|
|
38213
39073
|
/**
|
|
38214
|
-
* Reads the inputs of a definition: the names the engine declares for each, its
|
|
38215
|
-
* stages and its chain.
|
|
39074
|
+
* Reads the inputs of a definition: the names the engine declares for each, where its first step reads, its two
|
|
39075
|
+
* uniforms, its stages and its chain.
|
|
38216
39076
|
*
|
|
38217
39077
|
* @param id - The filter id.
|
|
38218
39078
|
* @param declared - What the game declared under `inputs`.
|
|
38219
39079
|
* @param uniforms - The read uniforms.
|
|
39080
|
+
* @param backdrop - Whether the filter is a backdrop: only then may an input start from back.
|
|
38220
39081
|
* @returns The inputs in declaration order, frozen; empty without `inputs`.
|
|
38221
39082
|
* @throws {Error} When `inputs` is not an object, has more than four entries, or an input breaks one of its rules.
|
|
38222
39083
|
*/
|
|
38223
|
-
function readInputs(id, declared, uniforms) {
|
|
39084
|
+
function readInputs(id, declared, uniforms, backdrop) {
|
|
38224
39085
|
if (declared === void 0) return Object.freeze([]);
|
|
38225
39086
|
if (typeof declared !== "object" || declared === null || Array.isArray(declared)) throw new Error(`[game] Filter "${id}": inputs must be an object of named inputs.\n Write inputs: { blur: { stages: [...], chain } }.`);
|
|
38226
39087
|
const entries = Object.entries(declared);
|
|
@@ -38228,10 +39089,12 @@ function readInputs(id, declared, uniforms) {
|
|
|
38228
39089
|
const taken = /* @__PURE__ */ new Set();
|
|
38229
39090
|
return Object.freeze(entries.map(([name, input]) => {
|
|
38230
39091
|
const names = checkInputNames(id, name, uniforms, taken);
|
|
39092
|
+
const from = readFrom(id, name, input.from, backdrop);
|
|
38231
39093
|
const stages = readStages(id, name, input.stages);
|
|
38232
39094
|
if (typeof input.chain !== "function") throw new TypeError(`[game] Filter "${id}": input "${name}" needs a chain.\n Write chain: (value, { resolution }) => [{ stage, scale }].`);
|
|
38233
39095
|
return Object.freeze({
|
|
38234
39096
|
name,
|
|
39097
|
+
from,
|
|
38235
39098
|
texture: names.texture,
|
|
38236
39099
|
sampler: names.sampler,
|
|
38237
39100
|
scale: Object.freeze({
|
|
@@ -38254,21 +39117,23 @@ function readInputs(id, declared, uniforms) {
|
|
|
38254
39117
|
}
|
|
38255
39118
|
/**
|
|
38256
39119
|
* Reads the fallback of a definition: another filter of ours, drawing alone, over a subset of its
|
|
38257
|
-
* uniforms with the same types.
|
|
39120
|
+
* uniforms with the same types; a backdrop only for a backdrop, which it draws in place of on the same view.
|
|
38258
39121
|
*
|
|
38259
39122
|
* @param id - The filter id.
|
|
38260
39123
|
* @param fallback - What the game declared under `fallback`.
|
|
38261
39124
|
* @param uniforms - The read uniforms.
|
|
39125
|
+
* @param backdrop - Whether the filter is a backdrop.
|
|
38262
39126
|
* @returns The fallback's definition, or `undefined` without one.
|
|
38263
|
-
* @throws {Error} When it is no filter of ours, has the same id, has inputs or a fallback,
|
|
38264
|
-
* one of this filter's with the same type.
|
|
39127
|
+
* @throws {Error} When it is no filter of ours, has the same id, has inputs or a fallback, a uniform of it is not
|
|
39128
|
+
* one of this filter's with the same type, or it is a backdrop and the filter is not.
|
|
38265
39129
|
*/
|
|
38266
|
-
function readFallback(id, fallback, uniforms) {
|
|
39130
|
+
function readFallback(id, fallback, uniforms, backdrop) {
|
|
38267
39131
|
if (fallback === void 0) return void 0;
|
|
38268
39132
|
if (!isDefinedFilter(fallback)) throw new Error(`[game] Filter "${id}": fallback is not a filter made by defineFilter.\n Pass what defineFilter returned.`);
|
|
38269
39133
|
const definition = fallback.filter;
|
|
38270
39134
|
if (definition.id === id) throw new Error(`[game] Filter "${id}": its fallback has the same id.\n Give the fallback an id of its own.`);
|
|
38271
39135
|
if (!drawsAlone(definition)) throw new Error(`[game] Filter "${id}": its fallback "${definition.id}" has inputs or a fallback of its own.\n A fallback draws alone and simply: give it neither.`);
|
|
39136
|
+
if (definition.backdrop && !backdrop) throw new Error(`[game] Filter "${id}": its fallback "${definition.id}" is a backdrop, and "${id}" is not.\n A fallback draws in its place on the same view: give it no backdrop, or make "${id}" a backdrop too.`);
|
|
38272
39137
|
for (const uniform of definition.uniforms) {
|
|
38273
39138
|
const own = uniforms.find((candidate) => candidate.name === uniform.name);
|
|
38274
39139
|
if (!(own?.type === uniform.type && own.form === uniform.form)) throw new Error(`[game] Filter "${id}": its fallback "${definition.id}" declares "${uniform.name}", which "${id}" ${own === void 0 ? "does not declare" : "declares as another type"}.\n A fallback draws with the value of ${id}: use a subset of its uniforms.`);
|
|
@@ -38318,18 +39183,20 @@ function cacheWithInputs(cache, inputs) {
|
|
|
38318
39183
|
* An `area` answers, from the value, the rectangle in the entity's own units outside of which the
|
|
38319
39184
|
* filter draws nothing: Pixi then renders and fills only that box, not the view's bounds. A
|
|
38320
39185
|
* `resolution` draws the view and its filters at that many texels per CSS px instead of the
|
|
38321
|
-
* canvas's.
|
|
38322
|
-
*
|
|
38323
|
-
*
|
|
39186
|
+
* canvas's. A `backdrop` reads what was drawn under its view inside that box: the body calls
|
|
39187
|
+
* `sampleBack(uv)`, and an input may start `from: "back"`; it is drawn live in every frame.
|
|
39188
|
+
* Register it through the `filters` key of a feature. The README shows a filter with a blur input,
|
|
39189
|
+
* one with an `area` and a backdrop.
|
|
38324
39190
|
*
|
|
38325
39191
|
* @param id - The filter id, also the component name; unique, and not one of the built-ins.
|
|
38326
39192
|
* @param spec - Both bodies, the uniforms, the passes one apply costs, the padding, the inputs, the fallback, the
|
|
38327
|
-
* cache category, the area and the
|
|
39193
|
+
* cache category, the area, the resolution and the backdrop switch.
|
|
38328
39194
|
* @returns The component type, with the frozen `filter` definition attached.
|
|
38329
39195
|
* @throws {Error} When the id is empty, the WGSL has no `mainFragment`, the GLSL no `void main(`,
|
|
38330
39196
|
* a uniform is reserved, badly named or of no known form, the passes or the padding break
|
|
38331
39197
|
* their rules, an input or the fallback breaks one of its rules, `passes` is given with inputs,
|
|
38332
|
-
* `area` is not a function,
|
|
39198
|
+
* `area` is not a function, `resolution` is not a number from 0 to 4, `backdrop` is not a
|
|
39199
|
+
* boolean, or a backdrop has a padding or a cache that keeps it.
|
|
38333
39200
|
* @example
|
|
38334
39201
|
* ```ts
|
|
38335
39202
|
* // features/board/effects.ts: a gold tint a motion hook tweens on a merge, on WebGPU and WebGL.
|
|
@@ -38343,21 +39210,23 @@ function defineFilter(id, spec) {
|
|
|
38343
39210
|
readSpec(id, spec);
|
|
38344
39211
|
const read = Object.entries(spec.uniforms ?? {}).map(([name, declaration]) => readUniform(id, name, declaration));
|
|
38345
39212
|
const uniforms = Object.freeze(read.map((entry) => entry.spec));
|
|
38346
|
-
const
|
|
39213
|
+
const backdrop = readBackdrop(id, spec.backdrop);
|
|
39214
|
+
const inputs = readInputs(id, spec.inputs, uniforms, backdrop);
|
|
38347
39215
|
if (spec.inputs !== void 0 && spec.passes !== void 0) throw new Error(`[game] Filter "${id}": passes is counted by the engine for a filter with inputs.\n Leave passes out.`);
|
|
38348
|
-
const fallback = readFallback(id, spec.fallback, uniforms);
|
|
39216
|
+
const fallback = readFallback(id, spec.fallback, uniforms, backdrop);
|
|
38349
39217
|
const passes = spec.passes ?? 1;
|
|
38350
39218
|
const padding = spec.padding ?? 0;
|
|
38351
39219
|
checkCost(id, passes, padding, uniforms);
|
|
38352
39220
|
checkCache(id, spec.cache);
|
|
39221
|
+
if (backdrop) checkBackdrop(id, padding, spec.cache);
|
|
38353
39222
|
const area = readArea(id, spec.area);
|
|
38354
39223
|
const resolution = readResolution(id, spec.resolution);
|
|
38355
39224
|
const origin = callsCanvasPixel(spec.wgsl, spec.glsl) ? KEPT_ORIGIN : void 0;
|
|
38356
|
-
const cache = cacheWithInputs(cacheOf(spec.wgsl, spec.glsl, spec.cache), inputs);
|
|
39225
|
+
const cache = cacheWithInputs(cacheOf(spec.wgsl, spec.glsl, spec.cache, backdrop), inputs);
|
|
38357
39226
|
const filter = Object.freeze({
|
|
38358
39227
|
id,
|
|
38359
|
-
source: assemble(uniforms, inputs, spec.wgsl, origin),
|
|
38360
|
-
glsl: assembleGlsl(uniforms, inputs, spec.glsl, origin),
|
|
39228
|
+
source: assemble(uniforms, inputs, spec.wgsl, origin, backdrop),
|
|
39229
|
+
glsl: assembleGlsl(uniforms, inputs, spec.glsl, origin, backdrop),
|
|
38361
39230
|
uniforms,
|
|
38362
39231
|
passes,
|
|
38363
39232
|
padding,
|
|
@@ -38366,9 +39235,10 @@ function defineFilter(id, spec) {
|
|
|
38366
39235
|
cache,
|
|
38367
39236
|
origin,
|
|
38368
39237
|
resolution,
|
|
39238
|
+
backdrop,
|
|
38369
39239
|
...area === void 0 ? {} : { area }
|
|
38370
39240
|
});
|
|
38371
|
-
if (spec.cache !== false && (readsScreenIn(spec.wgsl) || readsScreenIn(spec.glsl))) screenReaders.add(filter);
|
|
39241
|
+
if (!backdrop && spec.cache !== false && (readsScreenIn(spec.wgsl) || readsScreenIn(spec.glsl))) screenReaders.add(filter);
|
|
38372
39242
|
const defaults = Object.fromEntries([
|
|
38373
39243
|
...read.map((entry) => [entry.spec.name, entry.value]),
|
|
38374
39244
|
["enabled", true],
|
|
@@ -38828,7 +39698,9 @@ function brokenMark() {
|
|
|
38828
39698
|
* asks the renderer for no frame. A destroy frees the uniform buffers first: `Filter.destroy()`
|
|
38829
39699
|
* leaves them to a GC that runs after a minute (P10). A filter with inputs draws each place of its
|
|
38830
39700
|
* chains through Pixi's public `applyFilter` from inside its own `apply`, into pooled textures given
|
|
38831
|
-
* back in a `finally`; a stand-in draws in place of a broken kind or instance.
|
|
39701
|
+
* back in a `finally`; a stand-in draws in place of a broken kind or instance. A backdrop is built
|
|
39702
|
+
* with Pixi's `blendRequired`: Pixi copies the canvas under the view before it draws, and the apply
|
|
39703
|
+
* places `sampleBack` on that copy (`activeBackTexture`) and starts the chains `from: "back"` there.
|
|
38832
39704
|
*/
|
|
38833
39705
|
/**
|
|
38834
39706
|
* The resolution of every filter but `Blur`: the render target's, so a filter draws at the
|
|
@@ -39001,11 +39873,21 @@ function createOurs(pixi, definition, value) {
|
|
|
39001
39873
|
type: input.clamp.type
|
|
39002
39874
|
};
|
|
39003
39875
|
}
|
|
39876
|
+
if (definition.backdrop) {
|
|
39877
|
+
structures[BACK_SCALE.name] = {
|
|
39878
|
+
value: new Float32Array(BACK_SCALE.size),
|
|
39879
|
+
type: BACK_SCALE.type
|
|
39880
|
+
};
|
|
39881
|
+
structures[BACK_CLAMP.name] = {
|
|
39882
|
+
value: new Float32Array(BACK_CLAMP.size),
|
|
39883
|
+
type: BACK_CLAMP.type
|
|
39884
|
+
};
|
|
39885
|
+
}
|
|
39004
39886
|
if (definition.origin !== void 0) structures[definition.origin.name] = {
|
|
39005
39887
|
value: new Float32Array(definition.origin.size),
|
|
39006
39888
|
type: definition.origin.type
|
|
39007
39889
|
};
|
|
39008
|
-
const group = definition.uniforms.length > 0 || definition.inputs.length > 0 || definition.origin !== void 0 ? new pixi.UniformGroup(structures) : void 0;
|
|
39890
|
+
const group = definition.uniforms.length > 0 || definition.inputs.length > 0 || definition.backdrop || definition.origin !== void 0 ? new pixi.UniformGroup(structures) : void 0;
|
|
39009
39891
|
const resources = group === void 0 ? {} : { fu: group };
|
|
39010
39892
|
const empty = pixi.Texture.EMPTY.source;
|
|
39011
39893
|
for (const input of definition.inputs) {
|
|
@@ -39019,7 +39901,8 @@ function createOurs(pixi, definition, value) {
|
|
|
39019
39901
|
glProgram,
|
|
39020
39902
|
resources,
|
|
39021
39903
|
padding: paddingOf$1(definition, value),
|
|
39022
|
-
resolution: resolutionOf(definition.resolution)
|
|
39904
|
+
resolution: resolutionOf(definition.resolution),
|
|
39905
|
+
blendRequired: definition.backdrop
|
|
39023
39906
|
}),
|
|
39024
39907
|
group,
|
|
39025
39908
|
definition,
|
|
@@ -39278,10 +40161,85 @@ function createFilter(ectx, pixi, kind, value, probeMap) {
|
|
|
39278
40161
|
entity: 0
|
|
39279
40162
|
});
|
|
39280
40163
|
if (instance.kind === "wgsl" && instance.definition.inputs.length > 0) wireInputs(ectx, pixi, instance);
|
|
40164
|
+
else if (instance.kind === "wgsl" && instance.definition.backdrop) wireBack(pixi, instance);
|
|
39281
40165
|
guardApply(ectx, instance, kind);
|
|
39282
40166
|
return instance;
|
|
39283
40167
|
}
|
|
39284
40168
|
/**
|
|
40169
|
+
* What was drawn under the view of the running apply: the texture Pixi's filter system copied the canvas under the
|
|
40170
|
+
* view's box into, or its empty texture outside a backdrop's pop.
|
|
40171
|
+
*
|
|
40172
|
+
* @param pixi - The module the renderer loaded.
|
|
40173
|
+
* @param manager - Pixi's filter system, inside an apply.
|
|
40174
|
+
* @returns The back texture.
|
|
40175
|
+
*/
|
|
40176
|
+
function backOf(pixi, manager) {
|
|
40177
|
+
return manager.activeBackTexture ?? pixi.Texture.EMPTY;
|
|
40178
|
+
}
|
|
40179
|
+
/**
|
|
40180
|
+
* Writes the scale and the clamp that place a sample of the body's uv on a texture: one over the size of its backing,
|
|
40181
|
+
* and half a texel inside the part of it its frame covers, rounded to float32. Written only where a value differs, so
|
|
40182
|
+
* Pixi's bind group stays cached; no array is allocated.
|
|
40183
|
+
*
|
|
40184
|
+
* @param uniforms - The values of the uniform group.
|
|
40185
|
+
* @param scaleName - The `vec2` scale uniform.
|
|
40186
|
+
* @param clampName - The `vec4` clamp uniform.
|
|
40187
|
+
* @param texture - The texture the body samples through them.
|
|
40188
|
+
*/
|
|
40189
|
+
function writePlacement(uniforms, scaleName, clampName, texture) {
|
|
40190
|
+
const { source } = texture;
|
|
40191
|
+
const scale = uniforms[scaleName];
|
|
40192
|
+
const clamp = uniforms[clampName];
|
|
40193
|
+
const halfX = .5 / source.pixelWidth;
|
|
40194
|
+
const halfY = .5 / source.pixelHeight;
|
|
40195
|
+
if (scale instanceof Float32Array) {
|
|
40196
|
+
writeAt(scale, 0, 1 / source.width, true);
|
|
40197
|
+
writeAt(scale, 1, 1 / source.height, true);
|
|
40198
|
+
}
|
|
40199
|
+
if (clamp instanceof Float32Array) {
|
|
40200
|
+
writeAt(clamp, 0, halfX, true);
|
|
40201
|
+
writeAt(clamp, 1, halfY, true);
|
|
40202
|
+
writeAt(clamp, 2, texture.frame.width / source.width - halfX, true);
|
|
40203
|
+
writeAt(clamp, 3, texture.frame.height / source.height - halfY, true);
|
|
40204
|
+
}
|
|
40205
|
+
}
|
|
40206
|
+
/**
|
|
40207
|
+
* Places a backdrop's `sampleBack` on the back texture of this apply. Pixi binds the texture itself (group 0
|
|
40208
|
+
* binding 3); the engine fills the scale and the clamp, since the copy's backing is sized at the resolution of the
|
|
40209
|
+
* surface under the view, the input's at the view's.
|
|
40210
|
+
*
|
|
40211
|
+
* @param body - The backdrop body.
|
|
40212
|
+
* @param back - What was drawn under the view.
|
|
40213
|
+
*/
|
|
40214
|
+
function bindBack(body, back) {
|
|
40215
|
+
const uniforms = body.group?.uniforms;
|
|
40216
|
+
if (uniforms !== void 0) writePlacement(uniforms, BACK_SCALE.name, BACK_CLAMP.name, back);
|
|
40217
|
+
}
|
|
40218
|
+
/**
|
|
40219
|
+
* The apply of a backdrop without inputs, as Pixi calls it: `sampleBack` placed on the copy Pixi made, then the body
|
|
40220
|
+
* drawn from the view's input.
|
|
40221
|
+
*
|
|
40222
|
+
* @param pixi - The module the renderer loaded.
|
|
40223
|
+
* @param body - The backdrop body.
|
|
40224
|
+
* @param manager - Pixi's filter system.
|
|
40225
|
+
* @param input - The view's input texture.
|
|
40226
|
+
* @param output - The output.
|
|
40227
|
+
* @param clearMode - Whether the output is cleared.
|
|
40228
|
+
*/
|
|
40229
|
+
function applyBack(pixi, body, manager, input, output, clearMode) {
|
|
40230
|
+
bindBack(body, backOf(pixi, manager));
|
|
40231
|
+
manager.applyFilter(body.filter, input, output, clearMode);
|
|
40232
|
+
}
|
|
40233
|
+
/**
|
|
40234
|
+
* Gives a backdrop body without inputs its own `apply`, which places `sampleBack` before it draws.
|
|
40235
|
+
*
|
|
40236
|
+
* @param pixi - The module the renderer loaded.
|
|
40237
|
+
* @param body - The body, just built.
|
|
40238
|
+
*/
|
|
40239
|
+
function wireBack(pixi, body) {
|
|
40240
|
+
body.filter.apply = (manager, input, output, clearMode) => applyBack(pixi, body, manager, input, output, clearMode);
|
|
40241
|
+
}
|
|
40242
|
+
/**
|
|
39285
40243
|
* The share of the resolution the last place of one input draws at.
|
|
39286
40244
|
*
|
|
39287
40245
|
* @param inputs - The chains of the instance.
|
|
@@ -39307,8 +40265,12 @@ function stagesOf(inputs) {
|
|
|
39307
40265
|
* @param inputs - The chains of the instance.
|
|
39308
40266
|
*/
|
|
39309
40267
|
function giveBack(pixi, inputs) {
|
|
39310
|
-
|
|
39311
|
-
|
|
40268
|
+
const { taken } = inputs;
|
|
40269
|
+
for (let index = 0; index < taken.length; index += 1) {
|
|
40270
|
+
const target = taken[index];
|
|
40271
|
+
if (target !== void 0) pixi.TexturePool.returnTexture(target);
|
|
40272
|
+
}
|
|
40273
|
+
while (taken.length > 0) taken.pop();
|
|
39312
40274
|
}
|
|
39313
40275
|
/**
|
|
39314
40276
|
* Draws the places of one input, each from the one before, the first from `from`, into a pooled
|
|
@@ -39318,7 +40280,7 @@ function giveBack(pixi, inputs) {
|
|
|
39318
40280
|
* @param inputs - The chains of the instance.
|
|
39319
40281
|
* @param index - The input.
|
|
39320
40282
|
* @param manager - Pixi's filter system, inside an active filter pass.
|
|
39321
|
-
* @param from - The view's input, or a kept picture.
|
|
40283
|
+
* @param from - The view's input, the copy of what was drawn under the view, or a kept picture.
|
|
39322
40284
|
* @param resolution - The resolution of `from`.
|
|
39323
40285
|
* @param target - Where the last place draws, instead of a pooled texture.
|
|
39324
40286
|
* @returns The texture the last place drew into; `from` for an empty chain.
|
|
@@ -39360,20 +40322,7 @@ function bindInput(instance, index, last) {
|
|
|
39360
40322
|
const { resources } = instance.filter;
|
|
39361
40323
|
if (resources[input.texture] !== source) resources[input.texture] = source;
|
|
39362
40324
|
if (resources[input.sampler] !== source.style) resources[input.sampler] = source.style;
|
|
39363
|
-
|
|
39364
|
-
const clamp = uniforms[input.clamp.name];
|
|
39365
|
-
const halfX = .5 / source.pixelWidth;
|
|
39366
|
-
const halfY = .5 / source.pixelHeight;
|
|
39367
|
-
if (scale instanceof Float32Array) {
|
|
39368
|
-
writeAt(scale, 0, 1 / source.width, true);
|
|
39369
|
-
writeAt(scale, 1, 1 / source.height, true);
|
|
39370
|
-
}
|
|
39371
|
-
if (clamp instanceof Float32Array) {
|
|
39372
|
-
writeAt(clamp, 0, halfX, true);
|
|
39373
|
-
writeAt(clamp, 1, halfY, true);
|
|
39374
|
-
writeAt(clamp, 2, last.frame.width / source.width - halfX, true);
|
|
39375
|
-
writeAt(clamp, 3, last.frame.height / source.height - halfY, true);
|
|
39376
|
-
}
|
|
40325
|
+
writePlacement(uniforms, input.scale.name, input.clamp.name, last);
|
|
39377
40326
|
}
|
|
39378
40327
|
/**
|
|
39379
40328
|
* Points every input's texture and sampler back at Pixi's empty texture: a filter never holds a
|
|
@@ -39394,11 +40343,24 @@ function unbindInputs(pixi, instance) {
|
|
|
39394
40343
|
}
|
|
39395
40344
|
}
|
|
39396
40345
|
/**
|
|
40346
|
+
* Tells whether the chains of an instance must run again for the resolutions of this apply: the view's input's for a
|
|
40347
|
+
* chain that starts from it, what was drawn under the view's for one that starts from back.
|
|
40348
|
+
*
|
|
40349
|
+
* @param inputs - The chains of the instance.
|
|
40350
|
+
* @param resolution - The resolution of the view's input.
|
|
40351
|
+
* @param backResolution - The resolution of the back; the input's for a filter that reads none.
|
|
40352
|
+
* @returns True when one the chains start from changed.
|
|
40353
|
+
*/
|
|
40354
|
+
function resolutionMoved(inputs, resolution, backResolution) {
|
|
40355
|
+
return inputs.fromInput && resolution !== inputs.resolution || inputs.fromBack && backResolution !== inputs.backResolution;
|
|
40356
|
+
}
|
|
40357
|
+
/**
|
|
39397
40358
|
* The apply of a filter with inputs, as Pixi calls it: a resolution change runs the chains once
|
|
39398
|
-
* at the new value, then every place draws,
|
|
39399
|
-
*
|
|
39400
|
-
*
|
|
39401
|
-
*
|
|
40359
|
+
* at the new value, then every place draws, each chain from the view's input or, `from: "back"`,
|
|
40360
|
+
* from the copy Pixi made of the canvas under the view, the last texture of each input is bound, a
|
|
40361
|
+
* backdrop's `sampleBack` is placed, and the main body draws from the view's input. Held results are
|
|
40362
|
+
* bound instead and no stage draws. The pooled textures go back and the inputs are unbound in a
|
|
40363
|
+
* `finally`, also when a place throws; a throw then reaches the guard, which breaks the instance.
|
|
39402
40364
|
*
|
|
39403
40365
|
* @param ectx - Domain context of the effects plugin.
|
|
39404
40366
|
* @param pixi - The module the renderer loaded.
|
|
@@ -39411,16 +40373,23 @@ function unbindInputs(pixi, instance) {
|
|
|
39411
40373
|
*/
|
|
39412
40374
|
function applyInputs(ectx, pixi, instance, inputs, manager, input, output, clearMode) {
|
|
39413
40375
|
const { resolution } = input.source;
|
|
39414
|
-
|
|
40376
|
+
const { definition } = instance;
|
|
40377
|
+
const back = definition.backdrop ? backOf(pixi, manager) : input;
|
|
40378
|
+
const backResolution = inputs.fromBack ? back.source.resolution : resolution;
|
|
40379
|
+
if (inputs.held === void 0 && resolutionMoved(inputs, resolution, backResolution)) evaluateInRender(ectx, pixi, instance, inputs, resolution, backResolution);
|
|
39415
40380
|
if (instance.broken) {
|
|
39416
40381
|
drawBroken(instance, manager, input, output, clearMode);
|
|
39417
40382
|
return;
|
|
39418
40383
|
}
|
|
39419
40384
|
try {
|
|
39420
40385
|
for (let index = 0; index < inputs.places.length; index += 1) {
|
|
39421
|
-
const
|
|
40386
|
+
const fromBack = definition.inputs[index]?.from === "back";
|
|
40387
|
+
const start = fromBack ? back : input;
|
|
40388
|
+
const startResolution = fromBack ? backResolution : resolution;
|
|
40389
|
+
const last = inputs.held === void 0 ? drawChain(pixi, inputs, index, manager, start, startResolution) : inputs.held[index] ?? input;
|
|
39422
40390
|
bindInput(instance, index, last);
|
|
39423
40391
|
}
|
|
40392
|
+
if (definition.backdrop) bindBack(instance, back);
|
|
39424
40393
|
manager.applyFilter(instance.filter, input, output, clearMode);
|
|
39425
40394
|
} finally {
|
|
39426
40395
|
giveBack(pixi, inputs);
|
|
@@ -39442,7 +40411,7 @@ function applyInputs(ectx, pixi, instance, inputs, manager, input, output, clear
|
|
|
39442
40411
|
*/
|
|
39443
40412
|
function drawInto(ectx, pixi, instance, inputs, manager, picture, targets) {
|
|
39444
40413
|
const { resolution } = picture.source;
|
|
39445
|
-
if (!instance.broken && resolution !== inputs.resolution) evaluateInRender(ectx, pixi, instance, inputs, resolution);
|
|
40414
|
+
if (!instance.broken && resolution !== inputs.resolution) evaluateInRender(ectx, pixi, instance, inputs, resolution, resolution);
|
|
39446
40415
|
if (instance.broken) return;
|
|
39447
40416
|
try {
|
|
39448
40417
|
for (let index = 0; index < inputs.places.length; index += 1) {
|
|
@@ -39462,10 +40431,14 @@ function drawInto(ectx, pixi, instance, inputs, manager, picture, targets) {
|
|
|
39462
40431
|
* @param instance - The instance, just built.
|
|
39463
40432
|
*/
|
|
39464
40433
|
function wireInputs(ectx, pixi, instance) {
|
|
39465
|
-
const
|
|
40434
|
+
const declared = instance.definition.inputs;
|
|
40435
|
+
const count = declared.length;
|
|
39466
40436
|
const inputs = {
|
|
39467
40437
|
stale: true,
|
|
39468
40438
|
resolution: 0,
|
|
40439
|
+
backResolution: 0,
|
|
40440
|
+
fromInput: declared.some((input) => input.from === "input"),
|
|
40441
|
+
fromBack: declared.some((input) => input.from === "back"),
|
|
39469
40442
|
version: 0,
|
|
39470
40443
|
context: { resolution: 0 },
|
|
39471
40444
|
places: Array.from({ length: count }, () => []),
|
|
@@ -39491,7 +40464,9 @@ function wireInputs(ectx, pixi, instance) {
|
|
|
39491
40464
|
instance.inputs = inputs;
|
|
39492
40465
|
instance.filter.apply = (manager, input, output, clearMode) => applyInputs(ectx, pixi, instance, inputs, manager, input, output, clearMode);
|
|
39493
40466
|
const { resolution } = instance.definition;
|
|
39494
|
-
|
|
40467
|
+
const canvas = ectx.deps.renderer.host.resolution();
|
|
40468
|
+
const own = resolution > 0 ? resolution : canvas;
|
|
40469
|
+
evaluate(ectx, pixi, instance, inputs, own, inputs.fromBack ? canvas : own);
|
|
39495
40470
|
}
|
|
39496
40471
|
/**
|
|
39497
40472
|
* Reads one property of a value a chain returned, whatever its type.
|
|
@@ -39685,9 +40660,10 @@ function placeSteps(pixi, inputs, index, steps) {
|
|
|
39685
40660
|
return changed || rebuilt;
|
|
39686
40661
|
}
|
|
39687
40662
|
/**
|
|
39688
|
-
* Runs the chain of every input for the instance's value at a resolution and places its steps
|
|
40663
|
+
* Runs the chain of every input for the instance's value at a resolution and places its steps: a chain that starts
|
|
40664
|
+
* from the view's input at the input's resolution, one that starts from back at the back's.
|
|
39689
40665
|
* A chain that throws or returns a step it may not draw breaks the instance (reason `"chain"`).
|
|
39690
|
-
* The version moves when a place, a scale, a stage uniform or
|
|
40666
|
+
* The version moves when a place, a scale, a stage uniform or a resolution changed; the passes
|
|
39691
40667
|
* become the main body plus every place.
|
|
39692
40668
|
*
|
|
39693
40669
|
* @param ectx - Domain context of the effects plugin.
|
|
@@ -39695,14 +40671,15 @@ function placeSteps(pixi, inputs, index, steps) {
|
|
|
39695
40671
|
* @param instance - The instance.
|
|
39696
40672
|
* @param inputs - Its chains.
|
|
39697
40673
|
* @param resolution - The resolution of the texture the view is drawn into.
|
|
40674
|
+
* @param backResolution - The resolution of what was drawn under the view; the input's for a filter that reads none.
|
|
39698
40675
|
*/
|
|
39699
|
-
function evaluate(ectx, pixi, instance, inputs, resolution) {
|
|
40676
|
+
function evaluate(ectx, pixi, instance, inputs, resolution, backResolution) {
|
|
39700
40677
|
const kind = ectx.state.kinds.get(instance.kindId);
|
|
39701
|
-
let changed = resolution !== inputs.resolution;
|
|
40678
|
+
let changed = resolution !== inputs.resolution || backResolution !== inputs.backResolution;
|
|
39702
40679
|
let passes = 1;
|
|
39703
|
-
inputs.context.resolution = resolution;
|
|
39704
40680
|
for (const [index, input] of instance.definition.inputs.entries()) {
|
|
39705
40681
|
let steps;
|
|
40682
|
+
inputs.context.resolution = input.from === "back" ? backResolution : resolution;
|
|
39706
40683
|
try {
|
|
39707
40684
|
steps = input.chain(instance.value, inputs.context);
|
|
39708
40685
|
} catch (error) {
|
|
@@ -39718,6 +40695,7 @@ function evaluate(ectx, pixi, instance, inputs, resolution) {
|
|
|
39718
40695
|
passes += steps.length;
|
|
39719
40696
|
}
|
|
39720
40697
|
inputs.resolution = resolution;
|
|
40698
|
+
inputs.backResolution = backResolution;
|
|
39721
40699
|
inputs.stale = false;
|
|
39722
40700
|
instance.passes = passes;
|
|
39723
40701
|
if (changed) inputs.version += 1;
|
|
@@ -39732,9 +40710,10 @@ function evaluate(ectx, pixi, instance, inputs, resolution) {
|
|
|
39732
40710
|
* @param instance - The instance.
|
|
39733
40711
|
* @param inputs - Its chains.
|
|
39734
40712
|
* @param resolution - The resolution of the texture the view is drawn into.
|
|
40713
|
+
* @param backResolution - The resolution of what was drawn under the view.
|
|
39735
40714
|
*/
|
|
39736
|
-
function evaluateInRender(ectx, pixi, instance, inputs, resolution) {
|
|
39737
|
-
evaluate(ectx, pixi, instance, inputs, resolution);
|
|
40715
|
+
function evaluateInRender(ectx, pixi, instance, inputs, resolution, backResolution) {
|
|
40716
|
+
evaluate(ectx, pixi, instance, inputs, resolution, backResolution);
|
|
39738
40717
|
ectx.state.filterSync.all = true;
|
|
39739
40718
|
}
|
|
39740
40719
|
/**
|
|
@@ -39756,12 +40735,13 @@ function refreshInputs(ectx, pixi, instance, off) {
|
|
|
39756
40735
|
inputs.retired.length = 0;
|
|
39757
40736
|
}
|
|
39758
40737
|
if (instance.broken || !instance.filter.enabled || off || !inputs.stale) return;
|
|
39759
|
-
evaluate(ectx, pixi, instance, inputs, inputs.resolution);
|
|
40738
|
+
evaluate(ectx, pixi, instance, inputs, inputs.resolution, inputs.backResolution);
|
|
39760
40739
|
}
|
|
39761
40740
|
/**
|
|
39762
40741
|
* The body a stand-in of a dev build draws: the fallback with the striped mark over it, or the
|
|
39763
40742
|
* mark alone. The pair draws the fallback into one pooled texture of the input's frame and
|
|
39764
|
-
* resolution, then the mark from it into the output, and gives the texture back in a `finally`.
|
|
40743
|
+
* resolution, then the mark from it into the output, and gives the texture back in a `finally`. A
|
|
40744
|
+
* backdrop fallback has its `sampleBack` placed first.
|
|
39765
40745
|
*
|
|
39766
40746
|
* @param pixi - The module the renderer loaded.
|
|
39767
40747
|
* @param fallback - The checked fallback, or `undefined`.
|
|
@@ -39786,6 +40766,7 @@ function markedBody(pixi, fallback, value) {
|
|
|
39786
40766
|
request.height = input.frame.height;
|
|
39787
40767
|
request.resolution = input.source.resolution;
|
|
39788
40768
|
const pooled = pixi.TexturePool.getOptimalTexture(request);
|
|
40769
|
+
if (body.definition.backdrop) bindBack(body, backOf(pixi, manager));
|
|
39789
40770
|
try {
|
|
39790
40771
|
manager.applyFilter(body.filter, input, pooled, true);
|
|
39791
40772
|
manager.applyFilter(mark.filter, pooled, output, clearMode);
|
|
@@ -39815,7 +40796,10 @@ function createStandIn(ectx, pixi, kind, value) {
|
|
|
39815
40796
|
const usable = status === "ok" ? fallback : void 0;
|
|
39816
40797
|
let body;
|
|
39817
40798
|
if (typeof __MOKU_GAME_DEV__ !== "undefined" && __MOKU_GAME_DEV__ === true) body = markedBody(pixi, usable, value);
|
|
39818
|
-
else if (usable !== void 0)
|
|
40799
|
+
else if (usable !== void 0) {
|
|
40800
|
+
body = createOurs(pixi, usable, value);
|
|
40801
|
+
if (usable.backdrop) wireBack(pixi, body);
|
|
40802
|
+
}
|
|
39819
40803
|
if (body === void 0) return void 0;
|
|
39820
40804
|
body.filter.enabled = value.enabled;
|
|
39821
40805
|
body.areaStale = body.definition.area !== void 0;
|
|
@@ -40392,7 +41376,7 @@ function finish(ectx, run, definition, passed) {
|
|
|
40392
41376
|
* @param definition - The definition.
|
|
40393
41377
|
* @param messages - What the compiler said.
|
|
40394
41378
|
*/
|
|
40395
|
-
function settle(ectx, run, definition, messages) {
|
|
41379
|
+
function settle$1(ectx, run, definition, messages) {
|
|
40396
41380
|
const { checks } = ectx.state;
|
|
40397
41381
|
if (checks.run !== run || checks.status.get(definition) !== "pending") return;
|
|
40398
41382
|
const errors = messages.filter((message) => message.type === "error");
|
|
@@ -40418,7 +41402,7 @@ function checkWgsl(ectx, run, definition) {
|
|
|
40418
41402
|
finish(ectx, run, definition, true);
|
|
40419
41403
|
return;
|
|
40420
41404
|
}
|
|
40421
|
-
device.createShaderModule({ code: definition.source }).getCompilationInfo().then((info) => settle(ectx, run, definition, info.messages), () => settle(ectx, run, definition, []));
|
|
41405
|
+
device.createShaderModule({ code: definition.source }).getCompilationInfo().then((info) => settle$1(ectx, run, definition, info.messages), () => settle$1(ectx, run, definition, []));
|
|
40422
41406
|
}
|
|
40423
41407
|
/**
|
|
40424
41408
|
* Reads the errors of a WebGL info log, one per `ERROR: 0:<line>: <message>` line; a summary line
|
|
@@ -41001,14 +41985,27 @@ function cacheOfInstance(instance) {
|
|
|
41001
41985
|
return instance.kind === "wgsl" ? instance.definition.cache : CORE_CACHE[instance.kind];
|
|
41002
41986
|
}
|
|
41003
41987
|
/**
|
|
41988
|
+
* Tells whether one of the inputs of a definition starts from the view's own input: its chain draws a copy of the
|
|
41989
|
+
* view's subtree. A chain from back draws what was drawn under the view instead.
|
|
41990
|
+
*
|
|
41991
|
+
* @param definition - The definition.
|
|
41992
|
+
* @returns True when one does.
|
|
41993
|
+
*/
|
|
41994
|
+
function readsOwnInput(definition) {
|
|
41995
|
+
const { inputs } = definition;
|
|
41996
|
+
for (let index = 0; index < inputs.length; index += 1) if (inputs[index]?.from === "input") return true;
|
|
41997
|
+
return false;
|
|
41998
|
+
}
|
|
41999
|
+
/**
|
|
41004
42000
|
* Whether an instance shows a blurred copy of what is under it (spec 23 P8.5): the built-in `Blur`, or one of ours
|
|
41005
|
-
* with
|
|
42001
|
+
* with an input drawn from its own subtree. A backdrop whose chains start from back blurs the scene under the view,
|
|
42002
|
+
* not the view: an overlay holds no loops for it. The predicate an overlay's held loops read (spec 22).
|
|
41006
42003
|
*
|
|
41007
42004
|
* @param instance - The instance.
|
|
41008
42005
|
* @returns True, or `undefined` for every other filter (the slot leaves the key out).
|
|
41009
42006
|
*/
|
|
41010
42007
|
function blursOf(instance) {
|
|
41011
|
-
return instance.kind === "blur" || instance.kind === "wgsl" && instance.definition
|
|
42008
|
+
return instance.kind === "blur" || instance.kind === "wgsl" && readsOwnInput(instance.definition) ? true : void 0;
|
|
41012
42009
|
}
|
|
41013
42010
|
/**
|
|
41014
42011
|
* Tells whether a debug switch turned a kind off (`effects.diagnostics.disable`). The guard is
|
|
@@ -42035,7 +43032,7 @@ const EFFECTS_OWNER = Object.freeze({
|
|
|
42035
43032
|
name: "effects"
|
|
42036
43033
|
});
|
|
42037
43034
|
/** How deep a parent chain is followed, as `rootPoseOf` does; deeper is treated as a loop. */
|
|
42038
|
-
const MAX_DEPTH = 32;
|
|
43035
|
+
const MAX_DEPTH$1 = 32;
|
|
42039
43036
|
/**
|
|
42040
43037
|
* The one dynamic set of an emitter container (P9): positions, frames, rotations and colours
|
|
42041
43038
|
* upload every frame; `uvs` stays static, because a frame is set at birth and the same `update()`
|
|
@@ -42078,7 +43075,7 @@ function bornFields(size) {
|
|
|
42078
43075
|
*/
|
|
42079
43076
|
function topOf(ecs, host) {
|
|
42080
43077
|
let top = host;
|
|
42081
|
-
for (let depth = 0; depth < MAX_DEPTH; depth += 1) {
|
|
43078
|
+
for (let depth = 0; depth < MAX_DEPTH$1; depth += 1) {
|
|
42082
43079
|
const parent = parentOf$1(ecs, top);
|
|
42083
43080
|
if (parent === 0) break;
|
|
42084
43081
|
top = parent;
|
|
@@ -44185,6 +45182,10 @@ function submit(ctx, entity, answer) {
|
|
|
44185
45182
|
}
|
|
44186
45183
|
//#endregion
|
|
44187
45184
|
//#region src/plugins/input/components.ts
|
|
45185
|
+
const scrollableDefaults = {
|
|
45186
|
+
axis: "y",
|
|
45187
|
+
moving: false
|
|
45188
|
+
};
|
|
44188
45189
|
/**
|
|
44189
45190
|
* Fresh defaults for a component that names an intent. A function, so each component type gets
|
|
44190
45191
|
* its own payload object instead of sharing one.
|
|
@@ -44284,6 +45285,13 @@ const Pressed = /*#__PURE__*/ tag("Pressed");
|
|
|
44284
45285
|
*/
|
|
44285
45286
|
const PointerOver = /*#__PURE__*/ tag("PointerOver");
|
|
44286
45287
|
/**
|
|
45288
|
+
* On every ui scroll container, internal: ui writes it, input reads it, a game never sees it. A
|
|
45289
|
+
* press that leaves the tap slop along `axis` goes from the pressed view to the innermost scroll
|
|
45290
|
+
* of that axis the view hangs in by its `Parent` chain; a finger that lands on a view inside a
|
|
45291
|
+
* scroll that is `moving` stops it and answers nothing.
|
|
45292
|
+
*/
|
|
45293
|
+
const Scrollable = /*#__PURE__*/ component("Scrollable", scrollableDefaults);
|
|
45294
|
+
/**
|
|
44287
45295
|
* Where the one pointer is, in reference units. Written once per frame by the frame step. `id`
|
|
44288
45296
|
* names the pointer it follows, `-1` at rest.
|
|
44289
45297
|
*/
|
|
@@ -44293,8 +45301,12 @@ const Pointer = /*#__PURE__*/ resource("Pointer", pointerDefaults);
|
|
|
44293
45301
|
/**
|
|
44294
45302
|
* @file input plugin — who is under the finger. Every lookup goes through `renderer.sync.hitTest`,
|
|
44295
45303
|
* which answers `undefined` without a DOM, and every accept function refuses a view that is
|
|
44296
|
-
* already playing its exit. A trace takes a cell only inside a circle smaller than its box.
|
|
45304
|
+
* already playing its exit. A trace takes a cell only inside a circle smaller than its box. The
|
|
45305
|
+
* scroll a press belongs to is found up the pressed view's own `Parent` chain, never under the
|
|
45306
|
+
* point: a view drawn over a scroll from another root keeps its press.
|
|
44297
45307
|
*/
|
|
45308
|
+
/** How deep a `Parent` chain is followed; deeper is treated as a loop. */
|
|
45309
|
+
const MAX_DEPTH = 32;
|
|
44298
45310
|
/**
|
|
44299
45311
|
* Resolves what `app.input.*` was pointed at. A `{ projection, key }` goes through
|
|
44300
45312
|
* `world.projection.entityOf`, which answers for live views only, so a view in the despawn queue
|
|
@@ -44377,6 +45389,44 @@ function findPressed(ctx, x, y) {
|
|
|
44377
45389
|
return ctx.deps.renderer.sync.hitTest(x, y, (entity) => acceptPress(ctx, entity));
|
|
44378
45390
|
}
|
|
44379
45391
|
/**
|
|
45392
|
+
* The innermost scroll a view sits in that moves now: the view itself or the nearest ancestor of
|
|
45393
|
+
* its `Parent` chain that carries a `Scrollable` with `moving` true and is not on its way out. A
|
|
45394
|
+
* finger that lands on the view stops that scroll. A view of another root drawn over the scroll,
|
|
45395
|
+
* a HUD button or a popup, hangs in no chain of it, so it keeps its press. Reads only, allocates
|
|
45396
|
+
* nothing.
|
|
45397
|
+
*
|
|
45398
|
+
* @param ctx - Domain context of the input plugin.
|
|
45399
|
+
* @param entity - The view the hit test found under the finger.
|
|
45400
|
+
* @returns The scroll, or `undefined` when no scroll the view sits in moves.
|
|
45401
|
+
*/
|
|
45402
|
+
function movingScrollOf(ctx, entity) {
|
|
45403
|
+
const { ecs } = ctx.deps.world;
|
|
45404
|
+
let current = entity;
|
|
45405
|
+
for (let depth = 0; depth < MAX_DEPTH && current !== 0; depth += 1) {
|
|
45406
|
+
if (ecs.get(current, Scrollable)?.moving === true && !ecs.has(current, Exiting)) return current;
|
|
45407
|
+
current = parentOf$1(ecs, current);
|
|
45408
|
+
}
|
|
45409
|
+
}
|
|
45410
|
+
/**
|
|
45411
|
+
* The innermost scroll of an axis a pressed view sits in: the view itself or the nearest ancestor
|
|
45412
|
+
* of its `Parent` chain that carries a `Scrollable` of that axis and is not on its way out. A list
|
|
45413
|
+
* under a modal is in no chain of the modal's button, so a drift on that button never pans it.
|
|
45414
|
+
* Reads only, allocates nothing.
|
|
45415
|
+
*
|
|
45416
|
+
* @param ctx - Domain context of the input plugin.
|
|
45417
|
+
* @param entity - The pressed view.
|
|
45418
|
+
* @param axis - The dominant axis of the pan.
|
|
45419
|
+
* @returns The scroll, or `undefined` when the view sits in no scroll of that axis.
|
|
45420
|
+
*/
|
|
45421
|
+
function scrollOnAxisOf(ctx, entity, axis) {
|
|
45422
|
+
const { ecs } = ctx.deps.world;
|
|
45423
|
+
let current = entity;
|
|
45424
|
+
for (let depth = 0; depth < MAX_DEPTH && current !== 0; depth += 1) {
|
|
45425
|
+
if (ecs.get(current, Scrollable)?.axis === axis && !ecs.has(current, Exiting)) return current;
|
|
45426
|
+
current = parentOf$1(ecs, current);
|
|
45427
|
+
}
|
|
45428
|
+
}
|
|
45429
|
+
/**
|
|
44380
45430
|
* The topmost drop target at a point.
|
|
44381
45431
|
*
|
|
44382
45432
|
* @param ctx - Domain context of the input plugin.
|
|
@@ -45400,6 +46450,20 @@ function direction(from, to) {
|
|
|
45400
46450
|
return dy >= 0 ? "down" : "up";
|
|
45401
46451
|
}
|
|
45402
46452
|
/**
|
|
46453
|
+
* The axis a pan moves on: the dominant one, horizontal on an exact diagonal.
|
|
46454
|
+
*
|
|
46455
|
+
* @param from - Where the press started.
|
|
46456
|
+
* @param to - Where the finger is.
|
|
46457
|
+
* @returns The axis of the larger move.
|
|
46458
|
+
* @example
|
|
46459
|
+
* ```ts
|
|
46460
|
+
* axisOf({ x: 0, y: 0 }, { x: 20, y: -5 }); // "x"
|
|
46461
|
+
* ```
|
|
46462
|
+
*/
|
|
46463
|
+
function axisOf$1(from, to) {
|
|
46464
|
+
return Math.abs(to.x - from.x) >= Math.abs(to.y - from.y) ? "x" : "y";
|
|
46465
|
+
}
|
|
46466
|
+
/**
|
|
45403
46467
|
* Takes or releases the pointer capture of the canvas. A pointer that is already gone — a down
|
|
45404
46468
|
* and an up inside one frame — cannot be captured; the browser throws and the gesture goes on.
|
|
45405
46469
|
*
|
|
@@ -45418,7 +46482,7 @@ function capture(ctx, on) {
|
|
|
45418
46482
|
}
|
|
45419
46483
|
/**
|
|
45420
46484
|
* Ends the gesture: the press tag goes, the capture is released and the machine forgets the
|
|
45421
|
-
* pointer, whether it moved, the entity and its key.
|
|
46485
|
+
* pointer, whether it moved or caught a scroll, the entity and its key.
|
|
45422
46486
|
*
|
|
45423
46487
|
* @param ctx - Domain context of the input plugin.
|
|
45424
46488
|
*/
|
|
@@ -45429,16 +46493,30 @@ function toIdle(ctx) {
|
|
|
45429
46493
|
ctx.state.phase = "idle";
|
|
45430
46494
|
ctx.state.pointerId = void 0;
|
|
45431
46495
|
ctx.state.moved = false;
|
|
46496
|
+
ctx.state.caught = false;
|
|
45432
46497
|
ctx.state.entity = void 0;
|
|
45433
46498
|
ctx.state.key = void 0;
|
|
45434
46499
|
ctx.state.pressedMs = 0;
|
|
45435
46500
|
}
|
|
45436
46501
|
/**
|
|
46502
|
+
* Makes a view the pressed one: it is remembered with its projection key and tagged `Pressed`.
|
|
46503
|
+
*
|
|
46504
|
+
* @param ctx - Domain context of the input plugin.
|
|
46505
|
+
* @param entity - The view that takes the press.
|
|
46506
|
+
*/
|
|
46507
|
+
function press(ctx, entity) {
|
|
46508
|
+
ctx.state.entity = entity;
|
|
46509
|
+
ctx.state.key = ctx.deps.world.projection.keyOf(entity);
|
|
46510
|
+
ctx.deps.world.ecs.tag(entity, Pressed);
|
|
46511
|
+
}
|
|
46512
|
+
/**
|
|
45437
46513
|
* Takes the pointer and looks for the view under it. The pointer is taken even where no view is.
|
|
45438
46514
|
* While the finger rests (`rests`) the next finger that goes down takes the pointer over and this
|
|
45439
46515
|
* press is cancelled; once it starts a gesture — a drag, a move past the tap slop, a long press, a
|
|
45440
|
-
* trace — a second finger stays ignored.
|
|
45441
|
-
*
|
|
46516
|
+
* trace — a second finger stays ignored. When the view under the finger sits in a moving scroll,
|
|
46517
|
+
* that scroll takes the press: the finger stops it, and the press is caught. A view drawn over a
|
|
46518
|
+
* moving scroll from another root, a HUD or a popup button, keeps its press. A view that carries
|
|
46519
|
+
* `Traceable` starts a trace instead of a press: it is not tagged `Pressed`.
|
|
45442
46520
|
*
|
|
45443
46521
|
* @param ctx - Domain context of the input plugin.
|
|
45444
46522
|
* @param pointer - The `Pointer` resource of this frame.
|
|
@@ -45446,9 +46524,10 @@ function toIdle(ctx) {
|
|
|
45446
46524
|
* @param sample - The raw sample.
|
|
45447
46525
|
*/
|
|
45448
46526
|
function onDown(ctx, pointer, point, sample) {
|
|
45449
|
-
const { ecs
|
|
46527
|
+
const { ecs } = ctx.deps.world;
|
|
45450
46528
|
ctx.state.pointerId = sample.pointerId;
|
|
45451
46529
|
ctx.state.moved = false;
|
|
46530
|
+
ctx.state.caught = false;
|
|
45452
46531
|
ctx.state.start = {
|
|
45453
46532
|
x: point.x,
|
|
45454
46533
|
y: point.y
|
|
@@ -45460,16 +46539,42 @@ function onDown(ctx, pointer, point, sample) {
|
|
|
45460
46539
|
capture(ctx, true);
|
|
45461
46540
|
const entity = findPressed(ctx, point.x, point.y);
|
|
45462
46541
|
if (entity === void 0) return;
|
|
46542
|
+
const scroll = movingScrollOf(ctx, entity);
|
|
46543
|
+
if (scroll !== void 0) {
|
|
46544
|
+
ctx.state.caught = true;
|
|
46545
|
+
press(ctx, scroll);
|
|
46546
|
+
return;
|
|
46547
|
+
}
|
|
45463
46548
|
if (ecs.has(entity, Traceable)) {
|
|
45464
46549
|
beginTrace(ctx, entity, point);
|
|
45465
46550
|
return;
|
|
45466
46551
|
}
|
|
45467
|
-
ctx
|
|
45468
|
-
ctx.state.key = projection.keyOf(entity);
|
|
45469
|
-
ecs.tag(entity, Pressed);
|
|
46552
|
+
press(ctx, entity);
|
|
45470
46553
|
if (ecs.has(entity, Draggable) && ecs.has(entity, Swipeable)) ctx.log.warn("input: Draggable wins over Swipeable", { view: ctx.state.key });
|
|
45471
46554
|
}
|
|
45472
46555
|
/**
|
|
46556
|
+
* The press just left the tap slop: it goes to the scroll it pans, the innermost `Scrollable` of
|
|
46557
|
+
* the pressed view's own `Parent` chain (the view itself included) that moves on the dominant axis
|
|
46558
|
+
* of the move. The view loses `Pressed`, and the press is marked caught: it is a scroll gesture
|
|
46559
|
+
* now, so its release answers nothing, also when the finger came back inside the slop. A
|
|
46560
|
+
* draggable or swipeable view keeps its gesture, a caught press keeps its scroll, and a view that
|
|
46561
|
+
* sits in no scroll of that axis keeps its press, also a popup button drawn over a list.
|
|
46562
|
+
*
|
|
46563
|
+
* @param ctx - Domain context of the input plugin.
|
|
46564
|
+
* @param point - Where the finger is, in reference units.
|
|
46565
|
+
*/
|
|
46566
|
+
function handOff(ctx, point) {
|
|
46567
|
+
const { caught, entity, start } = ctx.state;
|
|
46568
|
+
const { ecs } = ctx.deps.world;
|
|
46569
|
+
if (entity === void 0 || caught) return;
|
|
46570
|
+
if (ecs.has(entity, Draggable) || ecs.has(entity, Swipeable)) return;
|
|
46571
|
+
const scroll = scrollOnAxisOf(ctx, entity, axisOf$1(start, point));
|
|
46572
|
+
if (scroll === void 0 || scroll === entity) return;
|
|
46573
|
+
ecs.untag(entity, Pressed);
|
|
46574
|
+
ctx.state.caught = true;
|
|
46575
|
+
press(ctx, scroll);
|
|
46576
|
+
}
|
|
46577
|
+
/**
|
|
45473
46578
|
* Turns a press on a draggable view into a drag once the finger left the start by more than
|
|
45474
46579
|
* `dragStartPx`. A draggable view with no projection key cannot be carried: `mute`, `lift` and
|
|
45475
46580
|
* `settle` belong to a projection, so the gesture is reported and given up.
|
|
@@ -45495,13 +46600,15 @@ function onMove(ctx, point) {
|
|
|
45495
46600
|
* Answers the release of a press: a tap when the finger stayed inside the slop, a swipe when it
|
|
45496
46601
|
* went far enough and fast enough. Neither: nothing happened, and nothing has to be undone. Every
|
|
45497
46602
|
* release inside the slop runs the `onTap` listeners first, so a view that carries only
|
|
45498
|
-
* `Touchable` is heard although it answers nothing.
|
|
46603
|
+
* `Touchable` is heard although it answers nothing. A caught press belongs to a scroll — it
|
|
46604
|
+
* stopped a moving one or was handed to one — and answers nothing and runs no listener.
|
|
45499
46605
|
*
|
|
45500
46606
|
* @param ctx - Domain context of the input plugin.
|
|
45501
46607
|
* @param entity - The pressed view.
|
|
45502
46608
|
* @param point - Where the finger let go, in reference units.
|
|
45503
46609
|
*/
|
|
45504
46610
|
function answerRelease(ctx, entity, point) {
|
|
46611
|
+
if (ctx.state.caught) return;
|
|
45505
46612
|
const { ecs } = ctx.deps.world;
|
|
45506
46613
|
const travelled = distance(ctx.state.start, point);
|
|
45507
46614
|
if (travelled <= ctx.config.tapSlopPx) {
|
|
@@ -45568,13 +46675,24 @@ function rests(ctx) {
|
|
|
45568
46675
|
return ctx.state.phase === "pressed" && !ctx.state.moved;
|
|
45569
46676
|
}
|
|
45570
46677
|
/**
|
|
46678
|
+
* Tells whether a sample takes the press past the tap slop for the first time: the press has not
|
|
46679
|
+
* moved yet and the finger is farther than `tapSlopPx` from the start.
|
|
46680
|
+
*
|
|
46681
|
+
* @param ctx - Domain context of the input plugin.
|
|
46682
|
+
* @param point - Where the finger is, in reference units.
|
|
46683
|
+
* @returns True in the one sample that turns the press into a moved one.
|
|
46684
|
+
*/
|
|
46685
|
+
function leavesSlop(ctx, point) {
|
|
46686
|
+
return ctx.state.phase === "pressed" && !ctx.state.moved && distance(ctx.state.start, point) > ctx.config.tapSlopPx;
|
|
46687
|
+
}
|
|
46688
|
+
/**
|
|
45571
46689
|
* Feeds one raw sample to the machine. A leave does nothing here. A sample of another pointer is
|
|
45572
46690
|
* dropped while a pointer is followed: the second finger is ignored. One exception: a press while
|
|
45573
46691
|
* the followed pointer rests takes the pointer over and cancels the resting press, so a resting
|
|
45574
46692
|
* thumb or palm never blocks the finger that plays. A move of an idle mouse or pen hit-tests
|
|
45575
46693
|
* nothing: its point is returned, so the drain moves the hover once per frame. A move of a press
|
|
45576
|
-
* past the tap slop marks the press as moved for good
|
|
45577
|
-
* cancel.
|
|
46694
|
+
* past the tap slop marks the press as moved for good and, in that one sample, hands it to the
|
|
46695
|
+
* scroll it pans. A lost capture ends the gesture like a cancel.
|
|
45578
46696
|
*
|
|
45579
46697
|
* @param ctx - Domain context of the input plugin.
|
|
45580
46698
|
* @param pointer - The `Pointer` resource of this frame.
|
|
@@ -45597,7 +46715,10 @@ function handleSample(ctx, pointer, sample) {
|
|
|
45597
46715
|
break;
|
|
45598
46716
|
case "move":
|
|
45599
46717
|
if (isHover(ctx, pointer, sample)) return point;
|
|
45600
|
-
if (
|
|
46718
|
+
if (leavesSlop(ctx, point)) {
|
|
46719
|
+
ctx.state.moved = true;
|
|
46720
|
+
handOff(ctx, point);
|
|
46721
|
+
}
|
|
45601
46722
|
if (ctx.state.phase === "tracing") extendTrace(ctx, point);
|
|
45602
46723
|
else onMove(ctx, point);
|
|
45603
46724
|
break;
|
|
@@ -45770,6 +46891,7 @@ function createInputState(_ctx) {
|
|
|
45770
46891
|
phase: "idle",
|
|
45771
46892
|
pointerId: void 0,
|
|
45772
46893
|
moved: false,
|
|
46894
|
+
caught: false,
|
|
45773
46895
|
entity: void 0,
|
|
45774
46896
|
key: void 0,
|
|
45775
46897
|
start: {
|
|
@@ -49179,6 +50301,31 @@ const Scroll = /*#__PURE__*/ component("Scroll", {
|
|
|
49179
50301
|
min: 0
|
|
49180
50302
|
});
|
|
49181
50303
|
/**
|
|
50304
|
+
* On every scroll container: where it stands and whether it moves, for a game system and the
|
|
50305
|
+
* read door. `ui` writes it in a frame where the offset or `max` changed, and once more with
|
|
50306
|
+
* `moving: false` when it stops; a game reads it and never writes it.
|
|
50307
|
+
*
|
|
50308
|
+
* @example
|
|
50309
|
+
* ```ts
|
|
50310
|
+
* // A board system dims the events of the order strip while it rests on the orders.
|
|
50311
|
+
* export const stripMist = system({
|
|
50312
|
+
* name: "board.stripMist",
|
|
50313
|
+
* phase: "animate",
|
|
50314
|
+
* query: [ScrollState, OrderStrip],
|
|
50315
|
+
* run: entities => {
|
|
50316
|
+
* for (const [entity, scroll] of entities) {
|
|
50317
|
+
* // scroll: { offset: 318, max: 390, moving: false }
|
|
50318
|
+
* }
|
|
50319
|
+
* }
|
|
50320
|
+
* });
|
|
50321
|
+
* ```
|
|
50322
|
+
*/
|
|
50323
|
+
const ScrollState = /*#__PURE__*/ component("ScrollState", {
|
|
50324
|
+
offset: 0,
|
|
50325
|
+
max: 0,
|
|
50326
|
+
moving: false
|
|
50327
|
+
});
|
|
50328
|
+
/**
|
|
49182
50329
|
* On the root entity of a popup kept beneath another popup (`popup(..., { over })`). Internal to
|
|
49183
50330
|
* `ui`: not exported from the package root. Every element of that root resolves `is.covered`.
|
|
49184
50331
|
*/
|
|
@@ -50911,8 +52058,9 @@ function windowFormOf(node) {
|
|
|
50911
52058
|
}
|
|
50912
52059
|
/**
|
|
50913
52060
|
* Refuses the props of a scroll its windowed form cannot take: some but not all of `rows`,
|
|
50914
|
-
* `rowHeight` and `row`,
|
|
50915
|
-
* patched, so a refused
|
|
52061
|
+
* `rowHeight` and `row`, a row height that is not above 0, or `axis: "x"`, since the rows of a
|
|
52062
|
+
* window stand one below the other. Called before anything is spawned or patched, so a refused
|
|
52063
|
+
* scroll leaves no half-made element.
|
|
50916
52064
|
*
|
|
50917
52065
|
* @param node - The node being placed.
|
|
50918
52066
|
* @throws {Error} For a windowed scroll whose props do not hold.
|
|
@@ -50922,7 +52070,50 @@ function windowFormOf(node) {
|
|
|
50922
52070
|
* ```
|
|
50923
52071
|
*/
|
|
50924
52072
|
function checkWindow(node) {
|
|
50925
|
-
if (node.type
|
|
52073
|
+
if (node.type !== "scroll") return;
|
|
52074
|
+
if (windowFormOf(node) !== void 0 && axisOf(node) === "x") throw new Error("[game] A windowed scroll moves on y only.\n Drop axis \"x\" or use the children form.");
|
|
52075
|
+
}
|
|
52076
|
+
/**
|
|
52077
|
+
* The axis a scroll moves on: `"x"` when its props say so, else `"y"`.
|
|
52078
|
+
*
|
|
52079
|
+
* @param node - The node of a scroll.
|
|
52080
|
+
* @returns The axis.
|
|
52081
|
+
* @example
|
|
52082
|
+
* ```ts
|
|
52083
|
+
* axisOf({ type: "scroll", props: { axis: "x" }, children: [] }); // "x"
|
|
52084
|
+
* ```
|
|
52085
|
+
*/
|
|
52086
|
+
function axisOf(node) {
|
|
52087
|
+
return node.props.axis === "x" ? "x" : "y";
|
|
52088
|
+
}
|
|
52089
|
+
/**
|
|
52090
|
+
* The motion record a scroll spawns with: its axis, still at the start, not placed yet. The
|
|
52091
|
+
* first solve puts it at its `offset` prop.
|
|
52092
|
+
*
|
|
52093
|
+
* @param node - The node of the scroll that enters.
|
|
52094
|
+
* @returns A new record.
|
|
52095
|
+
* @example
|
|
52096
|
+
* ```ts
|
|
52097
|
+
* scrollMotionOf({ type: "scroll", props: { axis: "x" }, children: [] }).mode; // "still"
|
|
52098
|
+
* ```
|
|
52099
|
+
*/
|
|
52100
|
+
function scrollMotionOf(node) {
|
|
52101
|
+
return {
|
|
52102
|
+
axis: axisOf(node),
|
|
52103
|
+
mode: "still",
|
|
52104
|
+
offset: 0,
|
|
52105
|
+
max: 0,
|
|
52106
|
+
velocity: 0,
|
|
52107
|
+
from: 0,
|
|
52108
|
+
to: 0,
|
|
52109
|
+
duration: 0,
|
|
52110
|
+
elapsed: 0,
|
|
52111
|
+
pointer: 0,
|
|
52112
|
+
start: 0,
|
|
52113
|
+
seen: 0,
|
|
52114
|
+
placed: false,
|
|
52115
|
+
atProp: false
|
|
52116
|
+
};
|
|
50926
52117
|
}
|
|
50927
52118
|
/**
|
|
50928
52119
|
* Builds a spacer: an empty node of a fixed height that never shrinks.
|
|
@@ -53348,15 +54539,23 @@ const VISUALS = [
|
|
|
53348
54539
|
];
|
|
53349
54540
|
/**
|
|
53350
54541
|
* The input components of an element and the Escape marker; a disabled or covered button drops
|
|
53351
|
-
* the answering one and the marker.
|
|
54542
|
+
* the answering one and the marker. A scroll's three hold its runtime state.
|
|
53352
54543
|
*/
|
|
53353
54544
|
const INPUTS = [
|
|
53354
54545
|
Tappable,
|
|
53355
54546
|
Touchable,
|
|
53356
54547
|
LocalWrite,
|
|
53357
54548
|
Scroll,
|
|
54549
|
+
Scrollable,
|
|
54550
|
+
ScrollState,
|
|
53358
54551
|
Escapable
|
|
53359
54552
|
];
|
|
54553
|
+
/** The components of a scroll that hold where it stands: added once, never patched by a render. */
|
|
54554
|
+
const SCROLL_STATE = namesOf$1([
|
|
54555
|
+
Scroll,
|
|
54556
|
+
Scrollable,
|
|
54557
|
+
ScrollState
|
|
54558
|
+
]);
|
|
53360
54559
|
/**
|
|
53361
54560
|
* One list of the visual and the input types. Its own function, because lint rule L5 refuses a
|
|
53362
54561
|
* collection built in a module-scope declaration.
|
|
@@ -53371,6 +54570,14 @@ function concatTypes(...lists) {
|
|
|
53371
54570
|
const LIVE_TYPES = concatTypes(VISUALS, INPUTS);
|
|
53372
54571
|
/** The owned fields of a type that gives none to a plugin: one list, never a new one. */
|
|
53373
54572
|
const NO_OWNED = [];
|
|
54573
|
+
/** The time of a reconcile a test steps by hand: no ms pass, so no scroll moves. */
|
|
54574
|
+
const NO_TIME = Object.freeze({
|
|
54575
|
+
delta: 0,
|
|
54576
|
+
elapsed: 0,
|
|
54577
|
+
scale: 1,
|
|
54578
|
+
frame: 0,
|
|
54579
|
+
idle: false
|
|
54580
|
+
});
|
|
53374
54581
|
/**
|
|
53375
54582
|
* Tells whether a list of component values holds one of a type.
|
|
53376
54583
|
*
|
|
@@ -53411,6 +54618,11 @@ const ELEMENT_OWNED = namesOf$1([
|
|
|
53411
54618
|
]);
|
|
53412
54619
|
/** The style of a scroll's content: one object, so a render never reads as a new style. */
|
|
53413
54620
|
const CONTENT_STYLE = Object.freeze({ origin: "topLeft" });
|
|
54621
|
+
/** The style of an x scroll's content: its children stand side by side. */
|
|
54622
|
+
const CONTENT_ROW_STYLE = Object.freeze({
|
|
54623
|
+
origin: "topLeft",
|
|
54624
|
+
direction: "row"
|
|
54625
|
+
});
|
|
53414
54626
|
/**
|
|
53415
54627
|
* The props, besides `style`, `state` and `content`, that a live element is written from: `bind`,
|
|
53416
54628
|
* `texture`, `name` and `fit` reach its visual, `intent`, `payload`, `local` and `escape` its
|
|
@@ -53448,23 +54660,24 @@ function writesSame(previous, next) {
|
|
|
53448
54660
|
/**
|
|
53449
54661
|
* The children one element is diffed against. A scroll container holds exactly one of them: the
|
|
53450
54662
|
* content the finger moves, which keeps the rects of its own children and carries them along. Its
|
|
53451
|
-
* pivot is its top-left corner, so the scroll step writes its offset straight into `y`; it
|
|
53452
|
-
* its children out as a column. The content of a windowed scroll
|
|
53453
|
-
* the two spacers and the rows, instead of the markup's children.
|
|
54663
|
+
* pivot is its top-left corner, so the scroll step writes its offset straight into `x` or `y`; it
|
|
54664
|
+
* lays its children out as a column, or as a row on the `x` axis. The content of a windowed scroll
|
|
54665
|
+
* holds the slots of its window, the two spacers and the rows, instead of the markup's children.
|
|
53454
54666
|
*
|
|
53455
54667
|
* @param node - The node the markup wrote.
|
|
53456
54668
|
* @param slots - The slots of a windowed scroll, top spacer to bottom spacer.
|
|
54669
|
+
* @param axis - The axis of a scroll, the one it spawned with.
|
|
53457
54670
|
* @returns The children of the element.
|
|
53458
54671
|
* @example
|
|
53459
54672
|
* ```ts
|
|
53460
54673
|
* childrenOf({ type: "scroll", props: {}, children: [] }).length; // 1
|
|
53461
54674
|
* ```
|
|
53462
54675
|
*/
|
|
53463
|
-
function childrenOf(node, slots) {
|
|
54676
|
+
function childrenOf(node, slots, axis = "y") {
|
|
53464
54677
|
if (node.type !== "scroll") return node.children;
|
|
53465
54678
|
return [{
|
|
53466
54679
|
type: CONTENT$1,
|
|
53467
|
-
props: { style: CONTENT_STYLE },
|
|
54680
|
+
props: { style: axis === "x" ? CONTENT_ROW_STYLE : CONTENT_STYLE },
|
|
53468
54681
|
children: slots ?? node.children
|
|
53469
54682
|
}];
|
|
53470
54683
|
}
|
|
@@ -53543,16 +54756,15 @@ function styleOf(node) {
|
|
|
53543
54756
|
return typeof style === "object" && style !== null ? style : void 0;
|
|
53544
54757
|
}
|
|
53545
54758
|
/**
|
|
53546
|
-
* Refuses the tags that cannot be placed: a text field that names no local field, a
|
|
53547
|
-
* scroll
|
|
54759
|
+
* Refuses the tags that cannot be placed: a text field that names no local field, and a windowed
|
|
54760
|
+
* scroll whose props do not hold.
|
|
53548
54761
|
*
|
|
53549
54762
|
* @param node - The node being placed.
|
|
53550
|
-
* @throws {Error} For an `input` tag without `local`, for a
|
|
53551
|
-
*
|
|
54763
|
+
* @throws {Error} For an `input` tag without `local`, and for a windowed scroll whose props do
|
|
54764
|
+
* not hold.
|
|
53552
54765
|
*/
|
|
53553
54766
|
function checkTag(node) {
|
|
53554
54767
|
if (node.type === "input" && typeof node.props.local !== "string") throw new Error("[game] An input needs a local field.\n Write <input local=\"name\" />.");
|
|
53555
|
-
if (node.type === "scroll" && node.props.axis === "x") throw new Error("[game] Horizontal scroll arrives after V3.\n Use axis \"y\".");
|
|
53556
54768
|
checkWindow(node);
|
|
53557
54769
|
}
|
|
53558
54770
|
/**
|
|
@@ -53575,16 +54787,28 @@ function answerOf(node) {
|
|
|
53575
54787
|
/**
|
|
53576
54788
|
* The input components of an element: a button answers the gate, writes local state, or only
|
|
53577
54789
|
* swallows the tap; a panel swallows the tap too, so nothing under it answers; a text field takes
|
|
53578
|
-
* the tap that starts its editing; a scroll container takes the press that moves its content
|
|
53579
|
-
*
|
|
53580
|
-
*
|
|
54790
|
+
* the tap that starts its editing; a scroll container takes the press that moves its content and
|
|
54791
|
+
* carries where it stands, on the axis it spawned with. A button of a covered popup answers
|
|
54792
|
+
* nothing, so only the top popup answers the gate. A button with the `escape` prop that answers
|
|
54793
|
+
* also carries `Escapable`, the control the Escape key taps.
|
|
53581
54794
|
*
|
|
53582
54795
|
* @param element - The element to make touchable.
|
|
53583
54796
|
* @returns The component values.
|
|
53584
54797
|
*/
|
|
53585
54798
|
function inputOf(element) {
|
|
53586
54799
|
const { type, node, is } = element;
|
|
53587
|
-
if (type === "scroll")
|
|
54800
|
+
if (type === "scroll") {
|
|
54801
|
+
const axis = element.scroll?.axis ?? "y";
|
|
54802
|
+
return [
|
|
54803
|
+
Touchable(),
|
|
54804
|
+
Scroll({ axis }),
|
|
54805
|
+
Scrollable({
|
|
54806
|
+
axis,
|
|
54807
|
+
moving: false
|
|
54808
|
+
}),
|
|
54809
|
+
ScrollState()
|
|
54810
|
+
];
|
|
54811
|
+
}
|
|
53588
54812
|
if (type === "panel" || type === "input") return [Touchable()];
|
|
53589
54813
|
if (type !== "button") return [];
|
|
53590
54814
|
if (is.disabled || is.covered) return [Touchable()];
|
|
@@ -53803,6 +55027,7 @@ function createReconciler(ctx, modules, fields) {
|
|
|
53803
55027
|
let slotsVersion = -1;
|
|
53804
55028
|
let slotsRoots = -1;
|
|
53805
55029
|
let placedUnder;
|
|
55030
|
+
let rendered = 0;
|
|
53806
55031
|
/**
|
|
53807
55032
|
* Registers the key of an element under its root, so `entityOf` and `find` answer for it. A key
|
|
53808
55033
|
* `ui` gave a node of its own, a spacer's, is never registered.
|
|
@@ -53899,13 +55124,16 @@ function createReconciler(ctx, modules, fields) {
|
|
|
53899
55124
|
trackHost(state, element);
|
|
53900
55125
|
registerKey(root, element);
|
|
53901
55126
|
if (element.type === "input") fields.enter(element);
|
|
53902
|
-
if (element.type === "scroll")
|
|
55127
|
+
if (element.type === "scroll") {
|
|
55128
|
+
state.scrolls.add(entity);
|
|
55129
|
+
element.scroll = scrollMotionOf(node);
|
|
55130
|
+
}
|
|
53903
55131
|
modules.layout.attach(element);
|
|
53904
55132
|
element.basis = {
|
|
53905
55133
|
epoch: state.epoch,
|
|
53906
55134
|
rowHeight: range.rowHeightOf(parent, node)
|
|
53907
55135
|
};
|
|
53908
|
-
diffChildren(root, element, childrenOf(node, range.slotsFor(element)), instance);
|
|
55136
|
+
diffChildren(root, element, childrenOf(node, range.slotsFor(element), element.scroll?.axis), instance);
|
|
53909
55137
|
root.needsSolve = true;
|
|
53910
55138
|
return entity;
|
|
53911
55139
|
}
|
|
@@ -54042,11 +55270,12 @@ function createReconciler(ctx, modules, fields) {
|
|
|
54042
55270
|
/**
|
|
54043
55271
|
* Writes the visual and input components of a live element again. A visual or an input the
|
|
54044
55272
|
* element no longer carries is removed, so a variant can trade the rectangle for a nine-slice and
|
|
54045
|
-
* back, and a button that became disabled stops answering the gate. `Scroll`
|
|
54046
|
-
*
|
|
54047
|
-
*
|
|
54048
|
-
* the
|
|
54049
|
-
*
|
|
55273
|
+
* back, and a button that became disabled stops answering the gate. `Scroll`, `Scrollable` and
|
|
55274
|
+
* `ScrollState` are only ever added: where a scroll stands belongs to the finger and the scroll
|
|
55275
|
+
* step, and a new look or a new rect never resets it. The look written is recorded as the rest
|
|
55276
|
+
* of the visual, so the rest the world holds is always the one the element is drawn with: a
|
|
55277
|
+
* restore brings it back to exactly that. The extras of the `components` prop are diffed last.
|
|
55278
|
+
* Every call is one write pass of `UiCounters`.
|
|
54050
55279
|
*
|
|
54051
55280
|
* @param element - The element that changed.
|
|
54052
55281
|
*/
|
|
@@ -54063,7 +55292,8 @@ function createReconciler(ctx, modules, fields) {
|
|
|
54063
55292
|
}
|
|
54064
55293
|
/**
|
|
54065
55294
|
* Writes one visual or input value onto a live element: added when the entity lacks it, else
|
|
54066
|
-
* its fields patched, but never a tag or
|
|
55295
|
+
* its fields patched, but never a tag or the state of a scroll, and never a field its type gives
|
|
55296
|
+
* to a plugin.
|
|
54067
55297
|
*
|
|
54068
55298
|
* @param element - The live element.
|
|
54069
55299
|
* @param value - The value of this render.
|
|
@@ -54073,7 +55303,7 @@ function createReconciler(ctx, modules, fields) {
|
|
|
54073
55303
|
ecs.add(element.entity, value);
|
|
54074
55304
|
return;
|
|
54075
55305
|
}
|
|
54076
|
-
if (value.value === true || value.type
|
|
55306
|
+
if (value.value === true || SCROLL_STATE.has(value.type.componentName)) return;
|
|
54077
55307
|
const owned = ecs.typeOf(value.type.componentName)?.owned ?? NO_OWNED;
|
|
54078
55308
|
ecs.set(element.entity, asHandle(value.type), livePatch(value.value, owned));
|
|
54079
55309
|
}
|
|
@@ -54152,6 +55382,7 @@ function createReconciler(ctx, modules, fields) {
|
|
|
54152
55382
|
*/
|
|
54153
55383
|
function patch(root, element, node, instance) {
|
|
54154
55384
|
checkWindow(node);
|
|
55385
|
+
warnAxis(element, node);
|
|
54155
55386
|
const change = compare(root, element, node);
|
|
54156
55387
|
const motion = node.props.motion;
|
|
54157
55388
|
const loopChanged = !sameLoop(element.motion?.loop, motion?.loop);
|
|
@@ -54172,7 +55403,22 @@ function createReconciler(ctx, modules, fields) {
|
|
|
54172
55403
|
rowHeight: change.rowHeight
|
|
54173
55404
|
};
|
|
54174
55405
|
}
|
|
54175
|
-
diffChildren(root, element, childrenOf(node, range.slotsFor(element)), instance);
|
|
55406
|
+
diffChildren(root, element, childrenOf(node, range.slotsFor(element), element.scroll?.axis), instance);
|
|
55407
|
+
}
|
|
55408
|
+
/**
|
|
55409
|
+
* Warns once when a render gives a live scroll another `axis`: it keeps the one it spawned with.
|
|
55410
|
+
*
|
|
55411
|
+
* @param element - The element being patched.
|
|
55412
|
+
* @param node - The node of this render.
|
|
55413
|
+
*/
|
|
55414
|
+
function warnAxis(element, node) {
|
|
55415
|
+
const axis = element.scroll?.axis;
|
|
55416
|
+
if (axis === void 0 || axis === axisOf(node) || element.warned.has("scroll-axis-fixed")) return;
|
|
55417
|
+
element.warned.add("scroll-axis-fixed");
|
|
55418
|
+
ctx.log.warn("ui:scroll-axis-fixed", {
|
|
55419
|
+
key: element.key ?? element.identity,
|
|
55420
|
+
axis
|
|
55421
|
+
});
|
|
54176
55422
|
}
|
|
54177
55423
|
/**
|
|
54178
55424
|
* Counts a patch that brings a loop of another reference that is not really another loop: the
|
|
@@ -54829,8 +56075,10 @@ function createReconciler(ctx, modules, fields) {
|
|
|
54829
56075
|
* again, the roots a scene placed again, the popups that may leave, the pull of the text being
|
|
54830
56076
|
* typed, the scroll offsets and the windowed ranges, the bound texts that show a string of
|
|
54831
56077
|
* another size, the viewport, the dirty roots and the hosted views.
|
|
56078
|
+
*
|
|
56079
|
+
* @param time - The frame's `Time`: the scrolls move by its `delta`.
|
|
54832
56080
|
*/
|
|
54833
|
-
function reconcile() {
|
|
56081
|
+
function reconcile(time = NO_TIME) {
|
|
54834
56082
|
sweep();
|
|
54835
56083
|
state.reconciles += 1;
|
|
54836
56084
|
if (state.remount) remountRoots();
|
|
@@ -54838,13 +56086,14 @@ function createReconciler(ctx, modules, fields) {
|
|
|
54838
56086
|
settlePopups(ctx, state, unmountRoot);
|
|
54839
56087
|
fields.pull();
|
|
54840
56088
|
const containers = scrollContainers();
|
|
54841
|
-
modules.layout.scroll(containers, lookup);
|
|
56089
|
+
modules.layout.scroll(containers, lookup, time);
|
|
54842
56090
|
range.step(containers);
|
|
54843
56091
|
markResizedTexts(ctx, state, modules.layout);
|
|
54844
56092
|
markResizedIcons(ctx, state, modules.layout);
|
|
54845
56093
|
const viewportChanged = modules.styles.useViewport(ctx.deps.renderer.viewport.size());
|
|
54846
56094
|
if (viewportChanged) state.epoch += 1;
|
|
54847
56095
|
const count = dirtyRoots(viewportChanged);
|
|
56096
|
+
rendered = count;
|
|
54848
56097
|
for (let index = 0; index < count; index += 1) {
|
|
54849
56098
|
const root = dirtyList[index];
|
|
54850
56099
|
if (root !== void 0) reconcileDirtyRoot(root);
|
|
@@ -54870,16 +56119,22 @@ function createReconciler(ctx, modules, fields) {
|
|
|
54870
56119
|
/**
|
|
54871
56120
|
* The second system of phase `layout`: every root takes its slot, then one solve per marked
|
|
54872
56121
|
* root, a root in a slot after the root of its slot and in its content box, then the rects, the
|
|
54873
|
-
* rest poses and the hooks,
|
|
54874
|
-
* the
|
|
54875
|
-
*
|
|
56122
|
+
* rest poses and the hooks, the scrolls settled on what the solve and the render gave them, and
|
|
56123
|
+
* the hosting of the hosts that came. The hints released since the last frame step are dropped
|
|
56124
|
+
* at its end, so the `change.Box` and `change.Transform` hooks the solve plays get the hint the
|
|
56125
|
+
* reconcile routed, and the next frame starts with none.
|
|
56126
|
+
*
|
|
56127
|
+
* @param time - The frame's `Time`: a scroll left moving asks for a frame only while it runs.
|
|
54876
56128
|
*/
|
|
54877
|
-
function solve() {
|
|
56129
|
+
function solve(time = NO_TIME) {
|
|
54878
56130
|
const viewport = modules.styles.viewport();
|
|
54879
56131
|
viewportBox.w = viewport?.width ?? 0;
|
|
54880
56132
|
viewportBox.h = viewport?.height ?? 0;
|
|
54881
56133
|
assignSlots();
|
|
54882
|
-
|
|
56134
|
+
const changed = layOutRoots(viewportBox);
|
|
56135
|
+
if (changed) ctx.deps.time.wake();
|
|
56136
|
+
if (changed || rendered > 0) modules.layout.settleScroll(scrollContainers(), lookup, time);
|
|
56137
|
+
rendered = 0;
|
|
54883
56138
|
settleOverlays(ctx, state, modules.layout);
|
|
54884
56139
|
if (state.risen.size > 0) {
|
|
54885
56140
|
hostViews(ctx, state, state.risen);
|
|
@@ -55056,7 +56311,8 @@ function sceneLayersOf(ctx) {
|
|
|
55056
56311
|
*
|
|
55057
56312
|
* @param ctx - Domain context of the ui plugin.
|
|
55058
56313
|
* @param modules - The styles and layout modules, injected in that order.
|
|
55059
|
-
* @returns The module: `tree`, `find` and `
|
|
56314
|
+
* @returns The module: `tree`, `find`, `lint`, `fill` and `scroll` reach the game, the rest the
|
|
56315
|
+
* frame.
|
|
55060
56316
|
*/
|
|
55061
56317
|
function createJsxApi(ctx, modules) {
|
|
55062
56318
|
const state = ctx.state.jsx;
|
|
@@ -55073,6 +56329,7 @@ function createJsxApi(ctx, modules) {
|
|
|
55073
56329
|
const frame = createReconciler(ctx, modules, fields);
|
|
55074
56330
|
const focus = createFocus(ctx, frame.markPointer, topRoot, fields);
|
|
55075
56331
|
const resolvedOf = (entity) => ctx.deps.world.ecs.get(entity, Text)?.resolved;
|
|
56332
|
+
const lookup = (entity) => state.elements.get(entity);
|
|
55076
56333
|
const find = (key) => {
|
|
55077
56334
|
for (const root of sortedRoots(state, layerNames())) {
|
|
55078
56335
|
const entity = state.byKey.get(root.entity)?.get(key);
|
|
@@ -55093,12 +56350,25 @@ function createJsxApi(ctx, modules) {
|
|
|
55093
56350
|
fields.fill(entity, value);
|
|
55094
56351
|
return true;
|
|
55095
56352
|
},
|
|
56353
|
+
scroll: (key, to) => {
|
|
56354
|
+
const entity = find(key);
|
|
56355
|
+
const element = entity === void 0 ? void 0 : lookup(entity);
|
|
56356
|
+
if (element?.scroll === void 0) {
|
|
56357
|
+
ctx.log.warn("ui:scroll-without-scroll", { key });
|
|
56358
|
+
return false;
|
|
56359
|
+
}
|
|
56360
|
+
if (!modules.layout.scrollTo(element, to, lookup)) {
|
|
56361
|
+
ctx.log.warn("ui:scroll-before-solve", { key });
|
|
56362
|
+
return false;
|
|
56363
|
+
}
|
|
56364
|
+
return true;
|
|
56365
|
+
},
|
|
55096
56366
|
open: fields.open,
|
|
55097
56367
|
pointer: (sample) => fields.pointer(sample),
|
|
55098
56368
|
tapped: (entity) => focus.tapped(entity),
|
|
55099
56369
|
reconcile: frame.reconcile,
|
|
55100
|
-
solve: () => {
|
|
55101
|
-
frame.solve();
|
|
56370
|
+
solve: (time) => {
|
|
56371
|
+
frame.solve(time);
|
|
55102
56372
|
focus.refresh();
|
|
55103
56373
|
fields.place();
|
|
55104
56374
|
},
|
|
@@ -56203,25 +57473,63 @@ function createPopupHandler(ctx, state, jsx) {
|
|
|
56203
57473
|
//#endregion
|
|
56204
57474
|
//#region src/plugins/ui/layout/scroll.ts
|
|
56205
57475
|
/**
|
|
56206
|
-
* @file ui/layout —
|
|
56207
|
-
*
|
|
56208
|
-
*
|
|
56209
|
-
*
|
|
57476
|
+
* @file ui/layout — scroll. A scroll container moves its content on its axis: with the finger,
|
|
57477
|
+
* then in a fling that slows down, or in a glide to a `snap` stop; it starts at its `offset`
|
|
57478
|
+
* prop, glides when the prop changes, and stays inside the range every solve gives it, with hard
|
|
57479
|
+
* edges. The content moves by one `Transform` write on the axis: no solve, no motion, the children
|
|
57480
|
+
* keep their rects. `Scroll` holds the offset the way the windowed range is cut with (0 and below),
|
|
57481
|
+
* `ScrollState` the way a game reads it (0 to `max`), `Scrollable` whether it moves, for `input`.
|
|
57482
|
+
* The exact numbers live on the element's `scroll` record and the samples of the press in the
|
|
57483
|
+
* layout state, written in place; the components get whole reference units. A frame allocates
|
|
57484
|
+
* nothing.
|
|
57485
|
+
*/
|
|
57486
|
+
/** How far ahead a release carries its velocity before the nearest `snap` stop is taken, in ms. */
|
|
57487
|
+
const PROJECT_MS = 260;
|
|
57488
|
+
/** What a fling keeps of its velocity per ms. */
|
|
57489
|
+
const DECAY_PER_MS = .998;
|
|
57490
|
+
/** The speed under which a fling stops, in reference units per ms. */
|
|
57491
|
+
const STOP_SPEED = .02;
|
|
57492
|
+
/** The shortest glide, in ms. */
|
|
57493
|
+
const GLIDE_MS = 260;
|
|
57494
|
+
/** The ms each reference unit of distance adds to a glide. */
|
|
57495
|
+
const GLIDE_MS_PER_UNIT = 1.1;
|
|
57496
|
+
/** The longest glide, in ms. */
|
|
57497
|
+
const GLIDE_MAX_MS = 620;
|
|
57498
|
+
/** The samples of the last ms before a release give its velocity. */
|
|
57499
|
+
const VELOCITY_MS = 100;
|
|
57500
|
+
/** A finger that stood still for longer than this before its release lets go with no velocity, in ms. */
|
|
57501
|
+
const STILL_MS = 50;
|
|
57502
|
+
/** The numbers of one sample in the ring: where the finger was on the axis, and when. */
|
|
57503
|
+
const SAMPLE_STRIDE = 2;
|
|
57504
|
+
/** The fewest samples a release velocity is read from. */
|
|
57505
|
+
const MIN_SAMPLES = 2;
|
|
57506
|
+
/**
|
|
57507
|
+
* Keeps an offset from the start inside 0 to `max`.
|
|
57508
|
+
*
|
|
57509
|
+
* @param offset - Where the content would stand, from the start.
|
|
57510
|
+
* @param max - The largest offset.
|
|
57511
|
+
* @returns The offset it stands at.
|
|
57512
|
+
* @example
|
|
57513
|
+
* ```ts
|
|
57514
|
+
* within(420, 390); // 390
|
|
57515
|
+
* ```
|
|
56210
57516
|
*/
|
|
57517
|
+
function within(offset, max) {
|
|
57518
|
+
return Math.min(max, Math.max(0, offset));
|
|
57519
|
+
}
|
|
56211
57520
|
/**
|
|
56212
|
-
*
|
|
56213
|
-
* the top.
|
|
57521
|
+
* How far a length runs past a window, in whole reference units: 0 when it fits.
|
|
56214
57522
|
*
|
|
56215
|
-
* @param
|
|
56216
|
-
* @param
|
|
56217
|
-
* @returns The offset
|
|
57523
|
+
* @param length - What is scrolled.
|
|
57524
|
+
* @param window - What shows of it.
|
|
57525
|
+
* @returns The largest offset.
|
|
56218
57526
|
* @example
|
|
56219
57527
|
* ```ts
|
|
56220
|
-
*
|
|
57528
|
+
* extent(696, 306); // 390
|
|
56221
57529
|
* ```
|
|
56222
57530
|
*/
|
|
56223
|
-
function
|
|
56224
|
-
return Math.
|
|
57531
|
+
function extent(length, window) {
|
|
57532
|
+
return Math.max(0, Math.round(length - window));
|
|
56225
57533
|
}
|
|
56226
57534
|
/**
|
|
56227
57535
|
* The content entity of a scroll container: its single child.
|
|
@@ -56235,106 +57543,491 @@ function contentOf(container, lookup) {
|
|
|
56235
57543
|
return first === void 0 ? void 0 : lookup(first);
|
|
56236
57544
|
}
|
|
56237
57545
|
/**
|
|
56238
|
-
* The
|
|
56239
|
-
*
|
|
57546
|
+
* The largest offset of a scroll on its axis: its content's rect past its own, or
|
|
57547
|
+
* `rows × rowHeight` past its height for a windowed scroll.
|
|
56240
57548
|
*
|
|
56241
|
-
* @param container - The scroll element.
|
|
57549
|
+
* @param container - The scroll element, with a solved rect.
|
|
56242
57550
|
* @param content - Its content element.
|
|
56243
|
-
* @
|
|
57551
|
+
* @param axis - The axis it moves on.
|
|
57552
|
+
* @returns The largest offset, in whole reference units.
|
|
56244
57553
|
*/
|
|
56245
|
-
function
|
|
57554
|
+
function maxOf(container, content, axis) {
|
|
57555
|
+
if (axis === "x") return extent(content.rect.w, container.rect.w);
|
|
56246
57556
|
const window = container.window;
|
|
56247
|
-
return window === void 0 ? content.rect.h : window.rows * window.rowHeight;
|
|
57557
|
+
return extent(window === void 0 ? content.rect.h : window.rows * window.rowHeight, container.rect.h);
|
|
57558
|
+
}
|
|
57559
|
+
/**
|
|
57560
|
+
* The `offset` prop of the scroll's last render: 0 when it is left out or is no finite number.
|
|
57561
|
+
*
|
|
57562
|
+
* @param container - The scroll element.
|
|
57563
|
+
* @returns The offset it asks for, from the start.
|
|
57564
|
+
*/
|
|
57565
|
+
function askedOffsetOf(container) {
|
|
57566
|
+
const offset = container.node.props.offset;
|
|
57567
|
+
return typeof offset === "number" && Number.isFinite(offset) ? offset : 0;
|
|
57568
|
+
}
|
|
57569
|
+
/**
|
|
57570
|
+
* The `snap` prop of the scroll's last render.
|
|
57571
|
+
*
|
|
57572
|
+
* @param container - The scroll element.
|
|
57573
|
+
* @returns The stops, or `undefined` when it names none.
|
|
57574
|
+
*/
|
|
57575
|
+
function snapOf(container) {
|
|
57576
|
+
const snap = container.node.props.snap;
|
|
57577
|
+
return Array.isArray(snap) && snap.length > 0 ? snap : void 0;
|
|
57578
|
+
}
|
|
57579
|
+
/**
|
|
57580
|
+
* The stop nearest to an offset, each stop kept inside 0 to `max`; the first of two as near.
|
|
57581
|
+
*
|
|
57582
|
+
* @param snap - The stops the scroll names.
|
|
57583
|
+
* @param offset - Where the release would carry it.
|
|
57584
|
+
* @param max - The largest offset.
|
|
57585
|
+
* @param fallback - Where it stays when no stop is a number.
|
|
57586
|
+
* @returns The stop.
|
|
57587
|
+
* @example
|
|
57588
|
+
* ```ts
|
|
57589
|
+
* nearestStop([0, 318, 390], 300, 390, 300); // 318
|
|
57590
|
+
* ```
|
|
57591
|
+
*/
|
|
57592
|
+
function nearestStop(snap, offset, max, fallback) {
|
|
57593
|
+
let nearest = fallback;
|
|
57594
|
+
let distance = Number.POSITIVE_INFINITY;
|
|
57595
|
+
for (const candidate of snap) {
|
|
57596
|
+
if (typeof candidate !== "number") continue;
|
|
57597
|
+
const kept = within(candidate, max);
|
|
57598
|
+
if (Math.abs(kept - offset) < distance) {
|
|
57599
|
+
nearest = kept;
|
|
57600
|
+
distance = Math.abs(kept - offset);
|
|
57601
|
+
}
|
|
57602
|
+
}
|
|
57603
|
+
return nearest;
|
|
56248
57604
|
}
|
|
56249
57605
|
/**
|
|
56250
|
-
*
|
|
57606
|
+
* Stops a scroll where it stands.
|
|
57607
|
+
*
|
|
57608
|
+
* @param motion - Its motion.
|
|
57609
|
+
*/
|
|
57610
|
+
function stop(motion) {
|
|
57611
|
+
motion.mode = "still";
|
|
57612
|
+
motion.velocity = 0;
|
|
57613
|
+
}
|
|
57614
|
+
/**
|
|
57615
|
+
* Starts a glide to an offset, kept inside the range, on the curve `1 − (1 − p)³` over
|
|
57616
|
+
* `min(620, 260 + distance × 1.1)` ms. A scroll already there stops instead.
|
|
57617
|
+
*
|
|
57618
|
+
* @param motion - Its motion.
|
|
57619
|
+
* @param target - Where it goes, from the start.
|
|
57620
|
+
*/
|
|
57621
|
+
function glideTo(motion, target) {
|
|
57622
|
+
const to = within(target, motion.max);
|
|
57623
|
+
const distance = Math.abs(to - motion.offset);
|
|
57624
|
+
if (distance === 0) {
|
|
57625
|
+
stop(motion);
|
|
57626
|
+
return;
|
|
57627
|
+
}
|
|
57628
|
+
motion.mode = "glide";
|
|
57629
|
+
motion.velocity = 0;
|
|
57630
|
+
motion.from = motion.offset;
|
|
57631
|
+
motion.to = to;
|
|
57632
|
+
motion.elapsed = 0;
|
|
57633
|
+
motion.duration = Math.min(GLIDE_MAX_MS, GLIDE_MS + distance * GLIDE_MS_PER_UNIT);
|
|
57634
|
+
}
|
|
57635
|
+
/**
|
|
57636
|
+
* Where a sample of the press starts in the ring: `back` samples before the newest one, 0 being
|
|
57637
|
+
* the newest. The position is at the index, the time right after it.
|
|
57638
|
+
*
|
|
57639
|
+
* @param state - The layout state, which keeps the samples of the press.
|
|
57640
|
+
* @param back - How many samples before the newest; 0 to the number of slots minus 1.
|
|
57641
|
+
* @returns The index of the sample in `scrollRing`.
|
|
57642
|
+
*/
|
|
57643
|
+
function slotBefore(state, back) {
|
|
57644
|
+
const slots = state.scrollRing.length / SAMPLE_STRIDE;
|
|
57645
|
+
return (state.scrollHead + slots - 1 - back) % slots * SAMPLE_STRIDE;
|
|
57646
|
+
}
|
|
57647
|
+
/**
|
|
57648
|
+
* Follows the finger on a held scroll. The first frame of a press, a hand-off included, takes the
|
|
57649
|
+
* finger where it is as the start, stops a fling or a glide where it stands, and ends the scroll's
|
|
57650
|
+
* standing at its `offset` prop; the offset then moves against the finger, inside 0 to `max`.
|
|
57651
|
+
* Every frame adds the finger's sample to the ring.
|
|
57652
|
+
*
|
|
57653
|
+
* @param state - The layout state, which keeps the samples of the press.
|
|
57654
|
+
* @param container - The held scroll.
|
|
57655
|
+
* @param motion - Its motion.
|
|
57656
|
+
* @param pointer - The `Pointer` resource of this frame.
|
|
57657
|
+
*/
|
|
57658
|
+
function hold(state, container, motion, pointer) {
|
|
57659
|
+
const at = motion.axis === "x" ? pointer.x : pointer.y;
|
|
57660
|
+
const ring = state.scrollRing;
|
|
57661
|
+
const slots = ring.length / SAMPLE_STRIDE;
|
|
57662
|
+
if (motion.mode !== "held" || state.scrolling !== container.entity) {
|
|
57663
|
+
motion.mode = "held";
|
|
57664
|
+
motion.velocity = 0;
|
|
57665
|
+
motion.pointer = at;
|
|
57666
|
+
motion.start = motion.offset;
|
|
57667
|
+
motion.atProp = false;
|
|
57668
|
+
state.scrolling = container.entity;
|
|
57669
|
+
state.scrollSamples = 0;
|
|
57670
|
+
state.scrollMoved = state.scrollClock;
|
|
57671
|
+
}
|
|
57672
|
+
motion.offset = within(motion.start - (at - motion.pointer), motion.max);
|
|
57673
|
+
if (state.scrollSamples > 0 && ring[slotBefore(state, 0)] !== at) state.scrollMoved = state.scrollClock;
|
|
57674
|
+
ring[state.scrollHead * SAMPLE_STRIDE] = at;
|
|
57675
|
+
ring[state.scrollHead * SAMPLE_STRIDE + 1] = state.scrollClock;
|
|
57676
|
+
state.scrollHead = (state.scrollHead + 1) % slots;
|
|
57677
|
+
state.scrollSamples = Math.min(slots, state.scrollSamples + 1);
|
|
57678
|
+
}
|
|
57679
|
+
/**
|
|
57680
|
+
* The velocity of the offset at a release, in units a ms, positive toward `max`: the finger's
|
|
57681
|
+
* move over its samples of the last 100 ms, against the finger. 0 when the finger stood still for
|
|
57682
|
+
* more than 50 ms before it let go, or left a single sample.
|
|
57683
|
+
*
|
|
57684
|
+
* @param state - The layout state, which keeps the samples of the press.
|
|
57685
|
+
* @returns The velocity.
|
|
57686
|
+
*/
|
|
57687
|
+
function releaseVelocity(state) {
|
|
57688
|
+
const ring = state.scrollRing;
|
|
57689
|
+
const now = state.scrollClock;
|
|
57690
|
+
if (state.scrollSamples < MIN_SAMPLES || now - state.scrollMoved > STILL_MS) return 0;
|
|
57691
|
+
const newest = slotBefore(state, 0);
|
|
57692
|
+
let oldest = newest;
|
|
57693
|
+
for (let back = 1; back < state.scrollSamples; back += 1) {
|
|
57694
|
+
const slot = slotBefore(state, back);
|
|
57695
|
+
if ((ring[slot + 1] ?? 0) < now - VELOCITY_MS) break;
|
|
57696
|
+
oldest = slot;
|
|
57697
|
+
}
|
|
57698
|
+
const span = (ring[newest + 1] ?? 0) - (ring[oldest + 1] ?? 0);
|
|
57699
|
+
return span > 0 ? ((ring[oldest] ?? 0) - (ring[newest] ?? 0)) / span : 0;
|
|
57700
|
+
}
|
|
57701
|
+
/**
|
|
57702
|
+
* Lets go of a scroll the finger held. With `snap` it glides to the stop nearest to where its
|
|
57703
|
+
* velocity carries in 260 ms; without, it flings, or stands when it was let go too slowly. A
|
|
57704
|
+
* press whose samples a later press took lets go with no velocity.
|
|
57705
|
+
*
|
|
57706
|
+
* @param state - The layout state, which keeps the samples of the press.
|
|
57707
|
+
* @param container - The scroll that was held.
|
|
57708
|
+
* @param motion - Its motion.
|
|
57709
|
+
* @param owned - Whether the samples are this press's.
|
|
57710
|
+
*/
|
|
57711
|
+
function letGo(state, container, motion, owned) {
|
|
57712
|
+
const velocity = owned ? releaseVelocity(state) : 0;
|
|
57713
|
+
const snap = snapOf(container);
|
|
57714
|
+
if (snap !== void 0) {
|
|
57715
|
+
glideTo(motion, nearestStop(snap, motion.offset + velocity * PROJECT_MS, motion.max, motion.offset));
|
|
57716
|
+
return;
|
|
57717
|
+
}
|
|
57718
|
+
if (Math.abs(velocity) < STOP_SPEED) {
|
|
57719
|
+
stop(motion);
|
|
57720
|
+
return;
|
|
57721
|
+
}
|
|
57722
|
+
motion.mode = "fling";
|
|
57723
|
+
motion.velocity = velocity;
|
|
57724
|
+
}
|
|
57725
|
+
/**
|
|
57726
|
+
* Moves a flinging scroll by the frame: the offset by its velocity, the velocity down by 0.998 a
|
|
57727
|
+
* ms. It stops under 0.02 units a ms, or on an edge: the edges are hard and nothing passes the
|
|
57728
|
+
* rest of the fling to a parent.
|
|
57729
|
+
*
|
|
57730
|
+
* @param motion - Its motion.
|
|
57731
|
+
* @param time - The frame's `Time`.
|
|
57732
|
+
*/
|
|
57733
|
+
function fling(motion, time) {
|
|
57734
|
+
const delta = time.delta;
|
|
57735
|
+
const next = motion.offset + motion.velocity * delta;
|
|
57736
|
+
motion.offset = within(next, motion.max);
|
|
57737
|
+
motion.velocity *= DECAY_PER_MS ** delta;
|
|
57738
|
+
if (motion.offset !== next || Math.abs(motion.velocity) < STOP_SPEED) stop(motion);
|
|
57739
|
+
}
|
|
57740
|
+
/**
|
|
57741
|
+
* Moves a gliding scroll by the frame along `1 − (1 − p)³`; at its end it stands on its target.
|
|
57742
|
+
*
|
|
57743
|
+
* @param motion - Its motion.
|
|
57744
|
+
* @param time - The frame's `Time`.
|
|
57745
|
+
*/
|
|
57746
|
+
function glide(motion, time) {
|
|
57747
|
+
motion.elapsed += time.delta;
|
|
57748
|
+
const progress = Math.min(1, motion.elapsed / motion.duration);
|
|
57749
|
+
if (progress === 1) {
|
|
57750
|
+
motion.offset = motion.to;
|
|
57751
|
+
stop(motion);
|
|
57752
|
+
return;
|
|
57753
|
+
}
|
|
57754
|
+
const left = 1 - progress;
|
|
57755
|
+
motion.offset = motion.from + (motion.to - motion.from) * (1 - left * left * left);
|
|
57756
|
+
}
|
|
57757
|
+
/**
|
|
57758
|
+
* Moves the content to a whole offset on the scroll's axis, from where the solve put it: one
|
|
57759
|
+
* `Transform` write, none when it stands there already. The rects of the solve are whole numbers
|
|
57760
|
+
* that Yoga hands out as doubles; rounded, the pose is a small integer, which `Object.assign`
|
|
57761
|
+
* copies into the world without a box.
|
|
56251
57762
|
*
|
|
56252
57763
|
* @param ctx - Domain context of the ui plugin.
|
|
57764
|
+
* @param state - The layout state, which keeps the patches.
|
|
56253
57765
|
* @param container - The scroll element.
|
|
56254
57766
|
* @param content - Its content element.
|
|
56255
|
-
* @param
|
|
57767
|
+
* @param motion - Its motion.
|
|
56256
57768
|
*/
|
|
56257
|
-
function moveContent(ctx, container, content,
|
|
57769
|
+
function moveContent(ctx, state, container, content, motion) {
|
|
56258
57770
|
const ecs = ctx.deps.world.ecs;
|
|
56259
|
-
ecs.
|
|
56260
|
-
|
|
56261
|
-
|
|
56262
|
-
|
|
56263
|
-
|
|
57771
|
+
const pose = ecs.get(content.entity, Transform);
|
|
57772
|
+
const offset = Math.round(motion.offset);
|
|
57773
|
+
if (pose === void 0) return;
|
|
57774
|
+
if (motion.axis === "x") {
|
|
57775
|
+
const x = Math.round(content.rect.x - container.rect.x) - offset;
|
|
57776
|
+
if (pose.x === x) return;
|
|
57777
|
+
state.scrollPatches.x.x = x;
|
|
57778
|
+
ecs.set(content.entity, Transform, state.scrollPatches.x);
|
|
57779
|
+
return;
|
|
57780
|
+
}
|
|
57781
|
+
const y = Math.round(content.rect.y - container.rect.y) - offset;
|
|
57782
|
+
if (pose.y === y) return;
|
|
57783
|
+
state.scrollPatches.y.y = y;
|
|
57784
|
+
ecs.set(content.entity, Transform, state.scrollPatches.y);
|
|
56264
57785
|
}
|
|
56265
57786
|
/**
|
|
56266
|
-
*
|
|
56267
|
-
* when
|
|
57787
|
+
* Writes what changed of a scroll, in whole units: `Scroll` when its offset or range moved,
|
|
57788
|
+
* `ScrollState` when the offset, `max` or `moving` changed, `Scrollable` when `moving` did, and
|
|
57789
|
+
* the content's pose. A frame where nothing changed writes nothing.
|
|
56268
57790
|
*
|
|
56269
57791
|
* @param ctx - Domain context of the ui plugin.
|
|
56270
|
-
* @param
|
|
57792
|
+
* @param state - The layout state, which keeps the patches.
|
|
57793
|
+
* @param container - The scroll element.
|
|
56271
57794
|
* @param content - Its content element.
|
|
56272
|
-
* @param
|
|
56273
|
-
|
|
56274
|
-
|
|
56275
|
-
|
|
56276
|
-
const
|
|
56277
|
-
|
|
56278
|
-
const
|
|
56279
|
-
const
|
|
56280
|
-
|
|
56281
|
-
|
|
56282
|
-
|
|
56283
|
-
|
|
56284
|
-
|
|
56285
|
-
|
|
57795
|
+
* @param motion - Its motion.
|
|
57796
|
+
*/
|
|
57797
|
+
function show(ctx, state, container, content, motion) {
|
|
57798
|
+
const ecs = ctx.deps.world.ecs;
|
|
57799
|
+
const patches = state.scrollPatches;
|
|
57800
|
+
const offset = Math.round(motion.offset);
|
|
57801
|
+
const moving = motion.mode !== "still";
|
|
57802
|
+
const scroll = ecs.get(container.entity, Scroll);
|
|
57803
|
+
const shown = ecs.get(container.entity, ScrollState);
|
|
57804
|
+
const scrollable = ecs.get(container.entity, Scrollable);
|
|
57805
|
+
const rangeMoved = scroll !== void 0 && (scroll.offset !== 0 - offset || scroll.min !== 0 - motion.max);
|
|
57806
|
+
const stateMoved = shown !== void 0 && (shown.offset !== offset || shown.max !== motion.max || shown.moving !== moving);
|
|
57807
|
+
if (rangeMoved) {
|
|
57808
|
+
patches.scroll.offset = 0 - offset;
|
|
57809
|
+
patches.scroll.min = 0 - motion.max;
|
|
57810
|
+
ecs.set(container.entity, Scroll, patches.scroll);
|
|
57811
|
+
}
|
|
57812
|
+
if (stateMoved) {
|
|
57813
|
+
patches.state.offset = offset;
|
|
57814
|
+
patches.state.max = motion.max;
|
|
57815
|
+
patches.state.moving = moving;
|
|
57816
|
+
ecs.set(container.entity, ScrollState, patches.state);
|
|
57817
|
+
}
|
|
57818
|
+
if (scrollable !== void 0 && scrollable.moving !== moving) {
|
|
57819
|
+
patches.scrollable.moving = moving;
|
|
57820
|
+
ecs.set(container.entity, Scrollable, patches.scrollable);
|
|
57821
|
+
}
|
|
57822
|
+
moveContent(ctx, state, container, content, motion);
|
|
56286
57823
|
}
|
|
56287
57824
|
/**
|
|
56288
|
-
*
|
|
56289
|
-
*
|
|
57825
|
+
* Tells whether a scroll has nothing to do this frame: its first solve has not placed it yet, or
|
|
57826
|
+
* it stands still and no finger is on it.
|
|
57827
|
+
*
|
|
57828
|
+
* @param motion - Its motion.
|
|
57829
|
+
* @param pressed - Whether a finger is on it.
|
|
57830
|
+
* @returns True when the frame leaves it alone.
|
|
57831
|
+
*/
|
|
57832
|
+
function isIdle(motion, pressed) {
|
|
57833
|
+
return !motion.placed || !pressed && motion.mode === "still";
|
|
57834
|
+
}
|
|
57835
|
+
/**
|
|
57836
|
+
* Moves one scroll by the frame: the finger while it is pressed, the release when it lets go,
|
|
57837
|
+
* then a fling or a glide, and writes what changed. A still scroll no finger is on costs a tag
|
|
57838
|
+
* check.
|
|
56290
57839
|
*
|
|
56291
57840
|
* @param ctx - Domain context of the ui plugin.
|
|
57841
|
+
* @param state - The layout state.
|
|
56292
57842
|
* @param container - The live scroll element.
|
|
56293
|
-
* @param contentHeight - `rows × rowHeight` of this render.
|
|
56294
57843
|
* @param lookup - How a child entity becomes its element.
|
|
56295
|
-
* @
|
|
57844
|
+
* @param pointer - The `Pointer` resource of this frame.
|
|
57845
|
+
* @param time - The frame's `Time`.
|
|
57846
|
+
* @returns True while the scroll moves.
|
|
56296
57847
|
*/
|
|
56297
|
-
function
|
|
57848
|
+
function stepOne(ctx, state, container, lookup, pointer, time) {
|
|
57849
|
+
const ecs = ctx.deps.world.ecs;
|
|
57850
|
+
const pressed = ecs.has(container.entity, Pressed);
|
|
57851
|
+
const released = !pressed && state.scrolling === container.entity;
|
|
57852
|
+
if (released) state.scrolling = void 0;
|
|
57853
|
+
const motion = container.scroll;
|
|
57854
|
+
if (motion === void 0 || isIdle(motion, pressed)) return false;
|
|
56298
57855
|
const content = contentOf(container, lookup);
|
|
56299
|
-
if (content === void 0
|
|
56300
|
-
|
|
57856
|
+
if (content === void 0 || !ecs.has(container.entity, Scroll)) return false;
|
|
57857
|
+
if (pressed) hold(state, container, motion, pointer);
|
|
57858
|
+
else if (motion.mode === "held") letGo(state, container, motion, released);
|
|
57859
|
+
if (motion.mode === "fling") fling(motion, time);
|
|
57860
|
+
else if (motion.mode === "glide") glide(motion, time);
|
|
57861
|
+
show(ctx, state, container, content, motion);
|
|
57862
|
+
return motion.mode !== "still";
|
|
56301
57863
|
}
|
|
56302
57864
|
/**
|
|
56303
|
-
* Moves
|
|
56304
|
-
*
|
|
57865
|
+
* Moves every scroll container by the frame: with the finger while it is pressed, then in a
|
|
57866
|
+
* fling or a glide after it lets go. Time is the frame's `delta`, so a time scale and a pause
|
|
57867
|
+
* apply. While a scroll moves, `time.wake()` keeps the frames coming; at a time scale of 0 no
|
|
57868
|
+
* frame is asked for: a fling or a glide stands where it is and goes on once time runs again,
|
|
57869
|
+
* and a finger's samples wake the loop themselves.
|
|
56305
57870
|
*
|
|
56306
57871
|
* @param ctx - Domain context of the ui plugin.
|
|
56307
|
-
* @param state - The layout state, which
|
|
57872
|
+
* @param state - The layout state, which keeps the samples of the press.
|
|
56308
57873
|
* @param containers - The live scroll elements.
|
|
56309
57874
|
* @param lookup - How a child entity becomes its element.
|
|
57875
|
+
* @param time - The frame's `Time`.
|
|
57876
|
+
*/
|
|
57877
|
+
function stepScroll(ctx, state, containers, lookup, time) {
|
|
57878
|
+
const pointer = ctx.deps.world.ecs.resource(Pointer);
|
|
57879
|
+
let moving = false;
|
|
57880
|
+
state.scrollClock += time.delta;
|
|
57881
|
+
for (const container of containers) if (stepOne(ctx, state, container, lookup, pointer, time)) moving = true;
|
|
57882
|
+
if (moving && time.delta > 0) ctx.deps.time.wake();
|
|
57883
|
+
}
|
|
57884
|
+
/**
|
|
57885
|
+
* Takes a new range: the offset keeps inside it at once, and so does a running glide. A scroll
|
|
57886
|
+
* that follows its `offset` prop goes on following it in the new range: standing, it stands at
|
|
57887
|
+
* the prop at once; gliding to it, it glides on to it from where it is. So a prop the first range
|
|
57888
|
+
* was too short for is reached once the content grows, a text measured again when its font
|
|
57889
|
+
* arrives.
|
|
57890
|
+
*
|
|
57891
|
+
* @param motion - The motion of the scroll.
|
|
57892
|
+
* @param max - The largest offset the solve gives it now.
|
|
57893
|
+
*/
|
|
57894
|
+
function resize(motion, max) {
|
|
57895
|
+
if (max === motion.max) return;
|
|
57896
|
+
motion.max = max;
|
|
57897
|
+
motion.offset = within(motion.offset, max);
|
|
57898
|
+
motion.from = within(motion.from, max);
|
|
57899
|
+
motion.to = within(motion.to, max);
|
|
57900
|
+
if (!motion.atProp) return;
|
|
57901
|
+
if (motion.mode === "glide") glideTo(motion, motion.seen);
|
|
57902
|
+
else motion.offset = within(motion.seen, max);
|
|
57903
|
+
}
|
|
57904
|
+
/**
|
|
57905
|
+
* Puts a scroll the solve just spawned at its `offset` prop, inside its range, and lets it follow
|
|
57906
|
+
* the prop from there. Its spawn waits in the queue of the phase: these values replace the queued
|
|
57907
|
+
* ones at the flush, before the first draw.
|
|
57908
|
+
*
|
|
57909
|
+
* @param ctx - Domain context of the ui plugin.
|
|
57910
|
+
* @param container - The scroll element, live since this solve.
|
|
57911
|
+
* @param content - Its content element.
|
|
57912
|
+
* @param motion - Its motion, not placed yet.
|
|
56310
57913
|
*/
|
|
56311
|
-
function
|
|
57914
|
+
function place$1(ctx, container, content, motion) {
|
|
56312
57915
|
const ecs = ctx.deps.world.ecs;
|
|
56313
|
-
const
|
|
57916
|
+
const rest = content.rest;
|
|
57917
|
+
motion.placed = true;
|
|
57918
|
+
motion.atProp = true;
|
|
57919
|
+
motion.max = maxOf(container, content, motion.axis);
|
|
57920
|
+
motion.seen = askedOffsetOf(container);
|
|
57921
|
+
motion.offset = within(motion.seen, motion.max);
|
|
57922
|
+
const offset = Math.round(motion.offset);
|
|
57923
|
+
const pose = motion.axis === "x" ? {
|
|
57924
|
+
...rest,
|
|
57925
|
+
x: content.rect.x - container.rect.x - offset
|
|
57926
|
+
} : {
|
|
57927
|
+
...rest,
|
|
57928
|
+
y: content.rect.y - container.rect.y - offset
|
|
57929
|
+
};
|
|
57930
|
+
ecs.add(container.entity, Scroll({
|
|
57931
|
+
axis: motion.axis,
|
|
57932
|
+
offset: 0 - offset,
|
|
57933
|
+
min: 0 - motion.max
|
|
57934
|
+
}));
|
|
57935
|
+
ecs.add(container.entity, ScrollState({
|
|
57936
|
+
offset,
|
|
57937
|
+
max: motion.max,
|
|
57938
|
+
moving: false
|
|
57939
|
+
}));
|
|
57940
|
+
ecs.add(content.entity, Transform(pose));
|
|
57941
|
+
}
|
|
57942
|
+
/**
|
|
57943
|
+
* Settles a placed scroll on what the solve and the render of this frame gave it: a new range at
|
|
57944
|
+
* once, then a glide to a new `offset` prop, which the scroll follows from then on. A scroll the
|
|
57945
|
+
* finger holds only remembers the prop.
|
|
57946
|
+
*
|
|
57947
|
+
* @param ctx - Domain context of the ui plugin.
|
|
57948
|
+
* @param state - The layout state, which keeps the patches.
|
|
57949
|
+
* @param container - The scroll element.
|
|
57950
|
+
* @param content - Its content element.
|
|
57951
|
+
* @param motion - Its motion, placed.
|
|
57952
|
+
*/
|
|
57953
|
+
function settle(ctx, state, container, content, motion) {
|
|
57954
|
+
resize(motion, maxOf(container, content, motion.axis));
|
|
57955
|
+
const asked = askedOffsetOf(container);
|
|
57956
|
+
if (asked !== motion.seen) {
|
|
57957
|
+
motion.seen = asked;
|
|
57958
|
+
if (motion.mode !== "held") {
|
|
57959
|
+
glideTo(motion, asked);
|
|
57960
|
+
motion.atProp = true;
|
|
57961
|
+
}
|
|
57962
|
+
}
|
|
57963
|
+
show(ctx, state, container, content, motion);
|
|
57964
|
+
}
|
|
57965
|
+
/**
|
|
57966
|
+
* Settles every scroll on what the solve and the render of this frame gave it. A scroll the solve
|
|
57967
|
+
* just spawned takes its place at its `offset` prop. A placed one keeps inside a new range at
|
|
57968
|
+
* once, a running glide's target too, and glides to a new `offset` prop; the same value on a
|
|
57969
|
+
* re-render does nothing, and a scroll the finger holds only remembers it. A scroll left moving
|
|
57970
|
+
* wakes the loop the way `stepScroll` does: not at a time scale of 0, where it stands.
|
|
57971
|
+
*
|
|
57972
|
+
* @param ctx - Domain context of the ui plugin.
|
|
57973
|
+
* @param state - The layout state, which keeps the patches.
|
|
57974
|
+
* @param containers - The live scroll elements.
|
|
57975
|
+
* @param lookup - How a child entity becomes its element.
|
|
57976
|
+
* @param time - The frame's `Time`.
|
|
57977
|
+
*/
|
|
57978
|
+
function settleScroll(ctx, state, containers, lookup, time) {
|
|
57979
|
+
let moving = false;
|
|
56314
57980
|
for (const container of containers) {
|
|
56315
|
-
const
|
|
57981
|
+
const motion = container.scroll;
|
|
56316
57982
|
const content = contentOf(container, lookup);
|
|
56317
|
-
if (
|
|
56318
|
-
if (
|
|
56319
|
-
|
|
56320
|
-
|
|
56321
|
-
continue;
|
|
56322
|
-
}
|
|
56323
|
-
if (state.scrolling !== container.entity) {
|
|
56324
|
-
state.scrolling = container.entity;
|
|
56325
|
-
state.scrollStart = {
|
|
56326
|
-
pointerY: pointer.y,
|
|
56327
|
-
offset: scroll.offset
|
|
56328
|
-
};
|
|
56329
|
-
}
|
|
56330
|
-
const min = Math.min(0, container.rect.h - contentHeightOf(container, content));
|
|
56331
|
-
const offset = clampOffset(state.scrollStart.offset + (pointer.y - state.scrollStart.pointerY), min);
|
|
56332
|
-
moveContent(ctx, container, content, {
|
|
56333
|
-
...scroll,
|
|
56334
|
-
offset,
|
|
56335
|
-
min
|
|
56336
|
-
});
|
|
57983
|
+
if (motion === void 0 || content === void 0) continue;
|
|
57984
|
+
if (motion.placed) settle(ctx, state, container, content, motion);
|
|
57985
|
+
else place$1(ctx, container, content, motion);
|
|
57986
|
+
if (motion.mode !== "still") moving = true;
|
|
56337
57987
|
}
|
|
57988
|
+
if (moving && time.delta > 0) ctx.deps.time.wake();
|
|
57989
|
+
}
|
|
57990
|
+
/**
|
|
57991
|
+
* Re-clamps a live windowed scroll to the content height of its new rows before its range is
|
|
57992
|
+
* cut, so a list that shrank below the window moves up in the same frame.
|
|
57993
|
+
*
|
|
57994
|
+
* @param ctx - Domain context of the ui plugin.
|
|
57995
|
+
* @param state - The layout state, which keeps the patches.
|
|
57996
|
+
* @param container - The live scroll element.
|
|
57997
|
+
* @param contentHeight - `rows × rowHeight` of this render.
|
|
57998
|
+
* @param lookup - How a child entity becomes its element.
|
|
57999
|
+
* @returns The offset the content stands at, 0 and below, as `Scroll` holds it.
|
|
58000
|
+
*/
|
|
58001
|
+
function clampScroll(ctx, state, container, contentHeight, lookup) {
|
|
58002
|
+
const motion = container.scroll;
|
|
58003
|
+
const content = contentOf(container, lookup);
|
|
58004
|
+
if (motion === void 0 || !motion.placed || content === void 0) return 0;
|
|
58005
|
+
resize(motion, extent(contentHeight, container.rect.h));
|
|
58006
|
+
show(ctx, state, container, content, motion);
|
|
58007
|
+
return 0 - Math.round(motion.offset);
|
|
58008
|
+
}
|
|
58009
|
+
/**
|
|
58010
|
+
* Puts a placed scroll at an offset at once, inside its range: a fling or a glide stops, the
|
|
58011
|
+
* scroll no longer follows its `offset` prop, and `ScrollState` says it stands. A finger that
|
|
58012
|
+
* holds it goes on from there.
|
|
58013
|
+
*
|
|
58014
|
+
* @param ctx - Domain context of the ui plugin.
|
|
58015
|
+
* @param state - The layout state, which keeps the patches.
|
|
58016
|
+
* @param container - The scroll element.
|
|
58017
|
+
* @param to - Where it goes, from the start; `NaN` leaves it where it is.
|
|
58018
|
+
* @param lookup - How a child entity becomes its element.
|
|
58019
|
+
* @returns True when the scroll took it; false before its first solve placed it.
|
|
58020
|
+
*/
|
|
58021
|
+
function scrollTo(ctx, state, container, to, lookup) {
|
|
58022
|
+
const motion = container.scroll;
|
|
58023
|
+
const content = contentOf(container, lookup);
|
|
58024
|
+
if (motion === void 0 || !motion.placed || content === void 0) return false;
|
|
58025
|
+
motion.offset = within(Number.isNaN(to) ? motion.offset : to, motion.max);
|
|
58026
|
+
motion.atProp = false;
|
|
58027
|
+
stop(motion);
|
|
58028
|
+
show(ctx, state, container, content, motion);
|
|
58029
|
+
ctx.deps.time.wake();
|
|
58030
|
+
return true;
|
|
56338
58031
|
}
|
|
56339
58032
|
//#endregion
|
|
56340
58033
|
//#region src/plugins/ui/layout/solve.ts
|
|
@@ -56549,7 +58242,9 @@ function textSourceOf(ctx) {
|
|
|
56549
58242
|
};
|
|
56550
58243
|
}
|
|
56551
58244
|
/**
|
|
56552
|
-
* Writes the style of an element onto its node and keeps its measure function in step.
|
|
58245
|
+
* Writes the style of an element onto its node and keeps its measure function in step. An `x`
|
|
58246
|
+
* scroll lays its content out the way a row does: along `x`, as wide as it is, as tall as the
|
|
58247
|
+
* scroll.
|
|
56553
58248
|
*
|
|
56554
58249
|
* @param state - The layout state.
|
|
56555
58250
|
* @param source - What a text element is measured through.
|
|
@@ -56558,7 +58253,8 @@ function textSourceOf(ctx) {
|
|
|
56558
58253
|
function writeStyle(state, source, element) {
|
|
56559
58254
|
const node = state.byEntity.get(element.entity);
|
|
56560
58255
|
if (node === void 0 || state.yoga === void 0) return;
|
|
56561
|
-
|
|
58256
|
+
const tag = element.scroll?.axis === "x" ? "row" : element.type;
|
|
58257
|
+
applyStyleToNode(state.yoga, node, element.style, tag, element.parentType);
|
|
56562
58258
|
installMeasure(state, node, element, source);
|
|
56563
58259
|
}
|
|
56564
58260
|
/**
|
|
@@ -56621,8 +58317,10 @@ function createLayoutApi(ctx) {
|
|
|
56621
58317
|
play(ctx, element, hook);
|
|
56622
58318
|
},
|
|
56623
58319
|
settled: (element) => canDespawn(element),
|
|
56624
|
-
scroll: (containers, lookup) => stepScroll(ctx, state, containers, lookup),
|
|
56625
|
-
|
|
58320
|
+
scroll: (containers, lookup, time) => stepScroll(ctx, state, containers, lookup, time),
|
|
58321
|
+
settleScroll: (containers, lookup, time) => settleScroll(ctx, state, containers, lookup, time),
|
|
58322
|
+
clampScroll: (container, contentHeight, lookup) => clampScroll(ctx, state, container, contentHeight, lookup),
|
|
58323
|
+
scrollTo: (container, to, lookup) => scrollTo(ctx, state, container, to, lookup),
|
|
56626
58324
|
windowOf,
|
|
56627
58325
|
popupHandler: (jsx) => createPopupHandler(ctx, state, jsx),
|
|
56628
58326
|
guideHandler: () => createGuideHandler(ctx),
|
|
@@ -57198,12 +58896,12 @@ function openRegistrations(ctx, jsx, layout) {
|
|
|
57198
58896
|
name: "ui.reconcile",
|
|
57199
58897
|
phase: "layout",
|
|
57200
58898
|
query: [],
|
|
57201
|
-
run: jsx.reconcile
|
|
58899
|
+
run: (_rows, frame) => jsx.reconcile(frame.time)
|
|
57202
58900
|
})), ecs.system(system({
|
|
57203
58901
|
name: "ui.solve",
|
|
57204
58902
|
phase: "layout",
|
|
57205
58903
|
query: [],
|
|
57206
|
-
run: jsx.solve
|
|
58904
|
+
run: (_rows, frame) => jsx.solve(frame.time)
|
|
57207
58905
|
})), ecs.onAdded(Tree, (entity) => {
|
|
57208
58906
|
const place = projection.keyOf(entity);
|
|
57209
58907
|
jsx.mountRoot(entity, place?.projection ?? "ui", ecs.get(entity, Layer)?.name ?? "ui");
|
|
@@ -57250,6 +58948,7 @@ function stopUi(state) {
|
|
|
57250
58948
|
state.layout.nodes = 0;
|
|
57251
58949
|
state.layout.yoga = void 0;
|
|
57252
58950
|
state.layout.scrolling = void 0;
|
|
58951
|
+
state.layout.scrollSamples = 0;
|
|
57253
58952
|
state.layout.iconLine = void 0;
|
|
57254
58953
|
state.jsx.components.clear();
|
|
57255
58954
|
state.jsx.roots.clear();
|
|
@@ -57540,7 +59239,7 @@ function createModules(ctx) {
|
|
|
57540
59239
|
};
|
|
57541
59240
|
}
|
|
57542
59241
|
/**
|
|
57543
|
-
* Creates the ui API: the
|
|
59242
|
+
* Creates the ui API: the five members a game calls, flat.
|
|
57544
59243
|
*
|
|
57545
59244
|
* @param ctx - Kernel context of the ui plugin.
|
|
57546
59245
|
* @returns The plugin API.
|
|
@@ -57551,7 +59250,8 @@ function createUiApi(ctx) {
|
|
|
57551
59250
|
tree: jsx.tree,
|
|
57552
59251
|
find: jsx.find,
|
|
57553
59252
|
lint: jsx.lint,
|
|
57554
|
-
fill: jsx.fill
|
|
59253
|
+
fill: jsx.fill,
|
|
59254
|
+
scroll: jsx.scroll
|
|
57555
59255
|
};
|
|
57556
59256
|
}
|
|
57557
59257
|
//#endregion
|
|
@@ -57653,8 +59353,8 @@ function createJsxState() {
|
|
|
57653
59353
|
//#endregion
|
|
57654
59354
|
//#region src/plugins/ui/layout/state.ts
|
|
57655
59355
|
/**
|
|
57656
|
-
* Creates an empty map. Its own non-exported function, because lint rule L5 refuses a
|
|
57657
|
-
* built inside an exported declaration.
|
|
59356
|
+
* Creates an empty map. Its own non-exported function, because lint rule L5 refuses a
|
|
59357
|
+
* collection built inside an exported declaration.
|
|
57658
59358
|
*
|
|
57659
59359
|
* @returns A map with nothing in it.
|
|
57660
59360
|
*/
|
|
@@ -57662,13 +59362,39 @@ function emptyMap() {
|
|
|
57662
59362
|
return /* @__PURE__ */ new Map();
|
|
57663
59363
|
}
|
|
57664
59364
|
/**
|
|
59365
|
+
* Creates the ring of pointer samples: a position and a time for each of `SAMPLES`. Its own
|
|
59366
|
+
* non-exported function, for the same rule.
|
|
59367
|
+
*
|
|
59368
|
+
* @returns The ring, zeroed.
|
|
59369
|
+
*/
|
|
59370
|
+
function sampleRing() {
|
|
59371
|
+
return /* @__PURE__ */ new Float64Array(10);
|
|
59372
|
+
}
|
|
59373
|
+
/**
|
|
59374
|
+
* Creates the patches the scroll step writes with, every field 0 or false.
|
|
59375
|
+
*
|
|
59376
|
+
* @returns The patches.
|
|
59377
|
+
*/
|
|
59378
|
+
function scrollPatches() {
|
|
59379
|
+
return {
|
|
59380
|
+
scroll: {
|
|
59381
|
+
offset: 0,
|
|
59382
|
+
min: 0
|
|
59383
|
+
},
|
|
59384
|
+
state: {
|
|
59385
|
+
offset: 0,
|
|
59386
|
+
max: 0,
|
|
59387
|
+
moving: false
|
|
59388
|
+
},
|
|
59389
|
+
scrollable: { moving: false },
|
|
59390
|
+
x: { x: 0 },
|
|
59391
|
+
y: { y: 0 }
|
|
59392
|
+
};
|
|
59393
|
+
}
|
|
59394
|
+
/**
|
|
57665
59395
|
* Creates the layout branch of the ui state. Everything starts empty: Yoga arrives in `onStart`.
|
|
57666
59396
|
*
|
|
57667
59397
|
* @returns The module state.
|
|
57668
|
-
* @example
|
|
57669
|
-
* ```ts
|
|
57670
|
-
* createLayoutState().nodes; // 0
|
|
57671
|
-
* ```
|
|
57672
59398
|
*/
|
|
57673
59399
|
function createLayoutState() {
|
|
57674
59400
|
return {
|
|
@@ -57678,10 +59404,12 @@ function createLayoutState() {
|
|
|
57678
59404
|
measured: 0,
|
|
57679
59405
|
solves: 0,
|
|
57680
59406
|
scrolling: void 0,
|
|
57681
|
-
|
|
57682
|
-
|
|
57683
|
-
|
|
57684
|
-
|
|
59407
|
+
scrollClock: 0,
|
|
59408
|
+
scrollRing: sampleRing(),
|
|
59409
|
+
scrollHead: 0,
|
|
59410
|
+
scrollSamples: 0,
|
|
59411
|
+
scrollMoved: 0,
|
|
59412
|
+
scrollPatches: scrollPatches(),
|
|
57685
59413
|
iconLine: void 0,
|
|
57686
59414
|
order: 0,
|
|
57687
59415
|
cleanups: []
|
|
@@ -57743,9 +59471,10 @@ function createUiState(_ctx) {
|
|
|
57743
59471
|
};
|
|
57744
59472
|
}
|
|
57745
59473
|
/**
|
|
57746
|
-
* Ui plugin: `app.ui.tree()`, `app.ui.find(...)`, `app.ui.lint()`, `app.ui.fill(...)`,
|
|
57747
|
-
* text field `<input local>`, the components `Box`, `LocalWrite
|
|
57748
|
-
* `DebugTrigger`, and the helpers `defineComponent`, `defineStyle`,
|
|
59474
|
+
* Ui plugin: `app.ui.tree()`, `app.ui.find(...)`, `app.ui.lint()`, `app.ui.fill(...)`,
|
|
59475
|
+
* `app.ui.scroll(...)`, the text field `<input local>`, the components `Box`, `LocalWrite`,
|
|
59476
|
+
* `DebugTrigger` and `ScrollState`, and the helpers `defineComponent`, `defineStyle`,
|
|
59477
|
+
* `defineTokens` and `popup`.
|
|
57749
59478
|
*
|
|
57750
59479
|
* @example
|
|
57751
59480
|
* ```ts
|
|
@@ -59151,6 +60880,7 @@ function cubicBezier(x1, y1, x2, y2) {
|
|
|
59151
60880
|
* | renderer | preference | "webgpu", WebGL is Pixi's fallback |
|
|
59152
60881
|
* | renderer | aspect | { min: 4/3, max: 21/9 }; shallow merge: a game that sets `aspect` gives both fields |
|
|
59153
60882
|
* | renderer | poolLimit | 256 |
|
|
60883
|
+
* | renderer | adaptiveScale | { floor: 0.75, devices: "phone" }, the render scale of the adaptive frame rate on a GPU-bound phone; false keeps the resolution; shallow merge: a game that sets it gives both fields |
|
|
59154
60884
|
* | renderer | unsupportedMessage | "This device cannot run the game." |
|
|
59155
60885
|
* | renderer | loadPixi | () => import("pixi.js") |
|
|
59156
60886
|
* | renderer | debug | { nineSlice: false, verifyFrames: false, kept: true, overdraw: false }; shallow merge: a game that sets `debug` replaces the whole object, and a switch it leaves out is off, except `kept`, which stays on unless set to false; `verifyFrames` and `overdraw` work in dev and debug builds only |
|
|
@@ -59315,4 +61045,4 @@ const screen = [
|
|
|
59315
61045
|
uiPlugin
|
|
59316
61046
|
];
|
|
59317
61047
|
//#endregion
|
|
59318
|
-
export {
|
|
61048
|
+
export { haptic as $, PERF_LOG_VERSION as $t, types_exports$11 as A, Emitter as At, Box as B, audioPlugin as Bt, types_exports$4 as C, Touchable as Ct, types_exports$8 as D, messageArgument as Dt, types_exports$7 as E, i18nPlugin as Et, types_exports$16 as F, Glow as Ft, defineTokens as G, Animation as Gt, LocalWrite as H, assetsPlugin as Ht, types_exports$17 as I, Noise as It, platformPlugin as J, PASS_KINDS as Jt, defineStyle as K, Frames as Kt, uiPlugin as L, Outline as Lt, types_exports$13 as M, Blur as Mt, types_exports$14 as N, ColorMatrix as Nt, types_exports$9 as O, messageDuration as Ot, types_exports$15 as P, Displacement as Pt, frames as Q, PERF_GAP_BUCKETS as Qt, resolve as R, defineFilter as Rt, types_exports$3 as S, Tappable as St, types_exports$6 as T, Traced as Tt, ScrollState as U, animPlugin as Ut, DebugTrigger as V, scenesPlugin as Vt, popup as W, AnimPlayer as Wt, defineAnimation as X, MONITOR_SECTIONS as Xt, HAPTIC_KINDS as Y, frameAt as Yt, external as Z, PERF_CAUSES as Zt, defineBundles as _, Pointer as _t, cubicBezier as a, refuseStart as an, sfx as at, types_exports$1 as b, Pressed as bt, hint as c, to as cn, stagger as ct, defineScene as d, SaveUnreadableError as dn, wait as dt, PROBLEM_RULES as en, mark as et, tr as f, lifecyclePlugin as fn, inputPlugin as ft, music as g, Hovered as gt, defineEmitter as h, Held as ht, screen as i, flowPlugin as in, set as it, types_exports$12 as j, Alpha as jt, types_exports$10 as k, effectsPlugin as kt, schedule as l, type as ln, tween as lt, flattenFeatures as m, clockPlugin as mn, DropTarget as mt, createPlugin as n, DEMAND_REASONS as nn, play$1 as nt, defineEffect as o, exit as on, spawn as ot, defineFeature as p, timePlugin as pn, Draggable as pt, textPlugin as q, rendererPlugin as qt, defineGame as r, worldPlugin as rn, sequence as rt, guide as s, slot as sn, spawned as st, createApp as t, SHADER_WHATS as tn, parallel as tt, projection as u, modelPlugin as un, use as ut, load as v, PointerOver as vt, types_exports$5 as w, Traceable as wt, types_exports$2 as x, Swipeable as xt, types_exports as y, Pressable as yt, defineMotion as z, debugPlugin as zt };
|