@moku-labs/game 0.15.26 → 0.15.27
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/page.mjs +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-KWKfdywA.d.mts → commands-BTAIwlyn.d.mts} +5 -5
- package/dist/control.d.mts +4 -4
- package/dist/{define-uiyy88hF.d.mts → define-CIg6SOO0.d.mts} +2 -2
- package/dist/{headless-PssyKRFN.d.mts → headless-DDymHw9o.d.mts} +1 -1
- package/dist/{index-CM1goGHf.d.mts → index-CURQhlzb.d.mts} +7 -5
- package/dist/index.d.mts +5 -5
- package/dist/index.mjs +3 -3
- package/dist/inspect.d.mts +6 -6
- package/dist/inspect.mjs +1 -1
- package/dist/jsx-dev-runtime.d.mts +1 -1
- package/dist/jsx-runtime.d.mts +1 -1
- package/dist/{runtime-C0YaA9fC.d.mts → runtime-jtZ4eVu7.d.mts} +1 -1
- package/dist/{sources-Dpc4NXvG.mjs → sources-ncFMQIus.mjs} +1 -1
- package/dist/{src-DWYFupNb.mjs → src-DLs4b_Vs.mjs} +458 -73
- package/dist/testing.d.mts +4 -4
- package/dist/testing.mjs +1 -1
- package/dist/{types-_OQ6H8WL.d.mts → types--1Zq5_6Q.d.mts} +21 -3
- package/dist/{types-C-wAHgpD.d.mts → types-BbuRDHDK.d.mts} +119 -20
- package/dist/{types-ChZ4Ej7O.d.mts → types-DFROr3sM.d.mts} +68 -11
- package/dist/{types-BdLJ4lTA.d.mts → types-DHxDVC5b.d.mts} +4 -4
- package/dist/{types-sfaYGg9l.d.mts → types-WoDZEVQI.d.mts} +2 -2
- package/dist/{visual-kEC9nb-o.mjs → visual-ClVRMUAi.mjs} +35 -5
- package/dist/{visual-DmDWJEzs.d.mts → visual-Dj1tlgUk.d.mts} +6 -6
- package/dist/visual.d.mts +1 -1
- package/dist/visual.mjs +1 -1
- package/package.json +1 -1
|
@@ -2,7 +2,7 @@ import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.mjs";
|
|
|
2
2
|
import { r as systemSource, t as memory } from "./memory-CWlqhS7l.mjs";
|
|
3
3
|
import { a as finishDrafts, i as enableDraftPatches, n as deepFreeze$1, o as openDrafts, r as dropDrafts } from "./drafts-BawpSaVp.mjs";
|
|
4
4
|
import { _ as pushEntry, c as collectFlows, d as findNode, f as framePath, g as compact, h as slotNames, i as servePaused, l as describeGraph, m as isRestCheckpoint, n as captureFrame, o as epochOf, p as graphHash, r as serveCaptures, s as pageNow$1, t as cancelCaptures } from "./capture-PTJXDpxK.mjs";
|
|
5
|
-
import { $ as component, A as localPoseOf, B as OverlayLevel, C as builtInStyles, D as textFor, E as readStyle, F as rootPoseOf, G as arcDefaults, H as Shape, I as Display, J as Exiting, K as componentsFor, L as HudPart, M as poseUnder, N as rootPointOf, O as applyPose, P as rootPoseInto, Q as Tree, R as NineSlice, U as Sprite, V as Parent, W as Transform, X as Now, Y as Layer, Z as Order, _ as uiFor, a as visualOf$1, b as Countdown, c as Covered, d as LocalWrite, f as Scroll, h as UiCounters, i as samePose, it as tag, j as parentOf$1, k as isLeaving, l as DebugTrigger, m as UI_OWNER, n as drawnRect, nt as system, o as visualRectOf, p as ScrollState, r as fitScaleOf, rt as systemsFor, s as Box, t as CONTENT$1, tt as resource, u as Escapable, x as Text, z as Overlay } from "./visual-
|
|
5
|
+
import { $ as component, A as localPoseOf, B as OverlayLevel, C as builtInStyles, D as textFor, E as readStyle, F as rootPoseOf, G as arcDefaults, H as Shape, I as Display, J as Exiting, K as componentsFor, L as HudPart, M as poseUnder, N as rootPointOf, O as applyPose, P as rootPoseInto, Q as Tree, R as NineSlice, U as Sprite, V as Parent, W as Transform, X as Now, Y as Layer, Z as Order, _ as uiFor, a as visualOf$1, b as Countdown, c as Covered, d as LocalWrite, f as Scroll, h as UiCounters, i as samePose, it as tag, j as parentOf$1, k as isLeaving, l as DebugTrigger, m as UI_OWNER, n as drawnRect, nt as system, o as visualRectOf, p as ScrollState, r as fitScaleOf, rt as systemsFor, s as Box, t as CONTENT$1, tt as resource, u as Escapable, x as Text, z as Overlay } from "./visual-ClVRMUAi.mjs";
|
|
6
6
|
import { a as isDev } from "./dev-BNndV-hU.mjs";
|
|
7
7
|
import { n as isComponentDefinition } from "./component-DGg5DqKK.mjs";
|
|
8
8
|
import { n as flatten } from "./flatten-nUX9PpGo.mjs";
|
|
@@ -16195,6 +16195,14 @@ function emptyMap$8() {
|
|
|
16195
16195
|
return /* @__PURE__ */ new Map();
|
|
16196
16196
|
}
|
|
16197
16197
|
/**
|
|
16198
|
+
* Creates an empty set. Same reason as `emptyRecords`.
|
|
16199
|
+
*
|
|
16200
|
+
* @returns An empty set.
|
|
16201
|
+
*/
|
|
16202
|
+
function emptySet$7() {
|
|
16203
|
+
return /* @__PURE__ */ new Set();
|
|
16204
|
+
}
|
|
16205
|
+
/**
|
|
16198
16206
|
* Creates an empty weak set. Same reason as `emptyRecords`.
|
|
16199
16207
|
*
|
|
16200
16208
|
* @returns An empty weak set.
|
|
@@ -16229,6 +16237,7 @@ function createKeptState(config) {
|
|
|
16229
16237
|
on: allowed && config.debug.kept !== false,
|
|
16230
16238
|
allowed,
|
|
16231
16239
|
warnedBudget: false,
|
|
16240
|
+
warnedBlend: emptySet$7(),
|
|
16232
16241
|
verifySkipped: 0,
|
|
16233
16242
|
bypassed: false,
|
|
16234
16243
|
flushPasses: 0,
|
|
@@ -16317,6 +16326,7 @@ function stopKept(state) {
|
|
|
16317
16326
|
state.flushPasses = 0;
|
|
16318
16327
|
state.drawnPictures = 0;
|
|
16319
16328
|
state.warnedBudget = false;
|
|
16329
|
+
state.warnedBlend.clear();
|
|
16320
16330
|
state.verifySkipped = 0;
|
|
16321
16331
|
state.bypassed = false;
|
|
16322
16332
|
}
|
|
@@ -16514,7 +16524,64 @@ function ancestorsPlain(kctx, record, stage) {
|
|
|
16514
16524
|
function eligible(kctx, record, view, stack) {
|
|
16515
16525
|
const { top } = record;
|
|
16516
16526
|
const stage = kctx.host.stage();
|
|
16517
|
-
return kctx
|
|
16527
|
+
return keepingOn(kctx) && stage !== void 0 && stack.enabled > 0 && plainFilters(record.slots) && drawsAsPicture(top, view) && topDrawn(kctx.sync, view) && ancestorsPlain(kctx, record, stage);
|
|
16528
|
+
}
|
|
16529
|
+
/**
|
|
16530
|
+
* Tells whether the kept cache may keep anything this frame: switched on and not bypassed.
|
|
16531
|
+
*
|
|
16532
|
+
* @param kctx - The kept context.
|
|
16533
|
+
* @returns True when pictures may be kept.
|
|
16534
|
+
*/
|
|
16535
|
+
function keepingOn(kctx) {
|
|
16536
|
+
return kctx.state.on && !kctx.state.bypassed;
|
|
16537
|
+
}
|
|
16538
|
+
/**
|
|
16539
|
+
* Tells whether a top and its visual draw as a picture does: the top visible and not faded out, both blending
|
|
16540
|
+
* normally (the visual sits inside the wrapper of a kept bare view, D4).
|
|
16541
|
+
*
|
|
16542
|
+
* @param top - The record's top.
|
|
16543
|
+
* @param view - Its view.
|
|
16544
|
+
* @returns True when they do.
|
|
16545
|
+
*/
|
|
16546
|
+
function drawsAsPicture(top, view) {
|
|
16547
|
+
return top.visible && top.alpha > 0 && plainBlend(top.blendMode) && plainBlend(view.object.blendMode);
|
|
16548
|
+
}
|
|
16549
|
+
/**
|
|
16550
|
+
* Tells whether a node under a top blends otherwise than normally, at any depth (F68): drawn into a picture, it would
|
|
16551
|
+
* blend with the picture's empty texture, not with what is drawn under the view, and a glow would cover the board under
|
|
16552
|
+
* it instead of lighting it. Recursive, by index over `children`: allocates nothing.
|
|
16553
|
+
*
|
|
16554
|
+
* @param node - The top, or a node under it; its own blend is not read (`eligible` reads the top's and the visual's).
|
|
16555
|
+
* @returns True when a node under it blends otherwise.
|
|
16556
|
+
*/
|
|
16557
|
+
function blendsBelow(node) {
|
|
16558
|
+
const { children } = node;
|
|
16559
|
+
for (let index = 0; index < children.length; index += 1) {
|
|
16560
|
+
const child = children[index];
|
|
16561
|
+
if (child !== void 0 && (!plainBlend(child.blendMode) || blendsBelow(child))) return true;
|
|
16562
|
+
}
|
|
16563
|
+
return false;
|
|
16564
|
+
}
|
|
16565
|
+
/**
|
|
16566
|
+
* Tells whether a record's subtree holds a node that blends otherwise than normally, and remembers a yes (`blended`):
|
|
16567
|
+
* a refused record is not walked again until a change under its top clears it (`stepRecord`). The first refusal of a
|
|
16568
|
+
* top is warned once per run, by its label.
|
|
16569
|
+
*
|
|
16570
|
+
* @param kctx - The kept context.
|
|
16571
|
+
* @param record - The record.
|
|
16572
|
+
* @returns True when the record may not be taken.
|
|
16573
|
+
*/
|
|
16574
|
+
function blendedBelow(kctx, record) {
|
|
16575
|
+
if (record.blended) return true;
|
|
16576
|
+
if (!blendsBelow(record.top)) return false;
|
|
16577
|
+
record.blended = true;
|
|
16578
|
+
const { warnedBlend } = kctx.state;
|
|
16579
|
+
const label = labelOf$3(record.top);
|
|
16580
|
+
if (!warnedBlend.has(label)) {
|
|
16581
|
+
warnedBlend.add(label);
|
|
16582
|
+
kctx.ctx.log.warn("renderer:kept-blend", { picture: label });
|
|
16583
|
+
}
|
|
16584
|
+
return true;
|
|
16518
16585
|
}
|
|
16519
16586
|
/**
|
|
16520
16587
|
* Tells whether a view of kind `"Display"` hangs in a subtree, the top included: a game's own object it may write
|
|
@@ -16582,7 +16649,8 @@ function createRecord$1(kctx, pixi, entity, view, slots, category) {
|
|
|
16582
16649
|
refused: 0,
|
|
16583
16650
|
cut: false,
|
|
16584
16651
|
direct: false,
|
|
16585
|
-
displays: void 0
|
|
16652
|
+
displays: void 0,
|
|
16653
|
+
blended: false
|
|
16586
16654
|
};
|
|
16587
16655
|
const { state } = kctx;
|
|
16588
16656
|
state.records.push(record);
|
|
@@ -16761,8 +16829,8 @@ function writeTakeKeys(record, now, box) {
|
|
|
16761
16829
|
/**
|
|
16762
16830
|
* Takes or retakes the picture of a record: stages 1, 2 (when its first filter has inputs) and 3, at the phase of this
|
|
16763
16831
|
* frame. The bounds are cut to the filter area of the top when it holds one (F43). Refused, and the view drawn live,
|
|
16764
|
-
* for a `Display` in the subtree without `onDemand`,
|
|
16765
|
-
* has no room (P6.3, warned once).
|
|
16832
|
+
* for a `Display` in the subtree without `onDemand`, a node under the top that blends otherwise (F68, warned once per
|
|
16833
|
+
* top), empty bounds, a picture over 4096 device pixels, or a budget that has no room (P6.3, warned once).
|
|
16766
16834
|
*
|
|
16767
16835
|
* @param kctx - The kept context.
|
|
16768
16836
|
* @param pixi - The module that draws.
|
|
@@ -16775,6 +16843,7 @@ function writeTakeKeys(record, now, box) {
|
|
|
16775
16843
|
function takePicture(kctx, pixi, matrices, record, view, now) {
|
|
16776
16844
|
const { top } = record;
|
|
16777
16845
|
if (!kctx.ctx.config.onDemand && holdsDisplay(kctx, top)) return false;
|
|
16846
|
+
if (blendedBelow(kctx, record)) return false;
|
|
16778
16847
|
const drawn = drawnBounds(pixi, top);
|
|
16779
16848
|
if (!drawn.isValid) return false;
|
|
16780
16849
|
const local = clippedToArea(drawn, top.filterArea);
|
|
@@ -17114,7 +17183,8 @@ function wrapBare(kctx, record, view) {
|
|
|
17114
17183
|
}
|
|
17115
17184
|
/**
|
|
17116
17185
|
* A live record's frame (P4.2): still frames are counted; at two (eight after churning) the record is taken, a bare
|
|
17117
|
-
* view in a wrapper of its own (D4).
|
|
17186
|
+
* view in a wrapper of its own (D4). A record with a node under its top that blends otherwise stays live, before any
|
|
17187
|
+
* wrap (F68).
|
|
17118
17188
|
*
|
|
17119
17189
|
* @param kctx - The kept context.
|
|
17120
17190
|
* @param pixi - The module that draws.
|
|
@@ -17129,6 +17199,7 @@ function stepLive(kctx, pixi, matrices, record, view) {
|
|
|
17129
17199
|
record.still = changed ? 0 : record.still + 1;
|
|
17130
17200
|
if (record.still < (record.calm ? 8 : 2)) return;
|
|
17131
17201
|
if (record.refused > 0 && state.bytes + record.refused - record.bytes > state.budget) return;
|
|
17202
|
+
if (blendedBelow(kctx, record)) return;
|
|
17132
17203
|
wrapBare(kctx, record, view);
|
|
17133
17204
|
if (takePicture(kctx, pixi, matrices, record, view, NOW)) toKept(kctx, record, view);
|
|
17134
17205
|
else record.still = 0;
|
|
@@ -17311,7 +17382,8 @@ function stepCalm(kctx, record) {
|
|
|
17311
17382
|
}
|
|
17312
17383
|
/**
|
|
17313
17384
|
* One drawn frame of a record, inside the flush: eligibility, the keys of this frame, the policy of its life, the quad
|
|
17314
|
-
* of a kept one; the dirty flags are cleared.
|
|
17385
|
+
* of a kept one; the dirty flags are cleared. A change under the top forgets a blended node found before: the next
|
|
17386
|
+
* take walks the subtree again (F68).
|
|
17315
17387
|
*
|
|
17316
17388
|
* @param kctx - The kept context.
|
|
17317
17389
|
* @param pixi - The module that draws.
|
|
@@ -17321,6 +17393,7 @@ function stepCalm(kctx, record) {
|
|
|
17321
17393
|
*/
|
|
17322
17394
|
function stepRecord(kctx, pixi, matrices, record, view) {
|
|
17323
17395
|
noteDrawing(kctx, record, BEFORE);
|
|
17396
|
+
if (record.dirtyS1) record.blended = false;
|
|
17324
17397
|
NOW.r = kctx.host.resolution();
|
|
17325
17398
|
readStack(record.slots, NOW.r, pixi.AlphaFilter, NOW);
|
|
17326
17399
|
if (eligible(kctx, record, view, NOW)) stepEligible(kctx, pixi, matrices, record, view);
|
|
@@ -23947,10 +24020,21 @@ const PROBE_SIZE = 4;
|
|
|
23947
24020
|
/** The content subjects the renderer warms itself, in queue order; the text follows once a font is installed. */
|
|
23948
24021
|
const CONTENT = Object.freeze([
|
|
23949
24022
|
"sprite",
|
|
24023
|
+
"sprite-add",
|
|
24024
|
+
"sprite-screen",
|
|
23950
24025
|
"graphics",
|
|
23951
24026
|
"particles-normal",
|
|
23952
24027
|
"particles-add"
|
|
23953
24028
|
]);
|
|
24029
|
+
/** The probe object of each content subject the tree holds from its first warm frame on. */
|
|
24030
|
+
const FIXED_NODES = Object.freeze({
|
|
24031
|
+
sprite: "sprite",
|
|
24032
|
+
"sprite-add": "spriteAdd",
|
|
24033
|
+
"sprite-screen": "spriteScreen",
|
|
24034
|
+
graphics: "graphics",
|
|
24035
|
+
"particles-normal": "particlesNormal",
|
|
24036
|
+
"particles-add": "particlesAdd"
|
|
24037
|
+
});
|
|
23954
24038
|
/** Every target a WebGPU pipeline is keyed by. */
|
|
23955
24039
|
const ALL_VARIANTS = Object.freeze([
|
|
23956
24040
|
"canvas",
|
|
@@ -23980,16 +24064,18 @@ function particleProperties() {
|
|
|
23980
24064
|
}
|
|
23981
24065
|
/**
|
|
23982
24066
|
* A 4 × 4 sprite of Pixi's white texture: the batch pipeline of sprites, nine-slices, plain bitmap text and batched
|
|
23983
|
-
* graphics.
|
|
24067
|
+
* graphics, with the blend of a `Sprite` or a `NineSlice` glow (part of a WebGPU pipeline's key).
|
|
23984
24068
|
*
|
|
23985
24069
|
* @param pixi - The module that draws.
|
|
23986
|
-
* @
|
|
24070
|
+
* @param blend - `"normal"` (Pixi's own mode left as it is), `"add"` or `"screen"`.
|
|
24071
|
+
* @returns The sprite, labelled `renderer.warm.sprite`, or `renderer.warm.sprite-<blend>` for a glow.
|
|
23987
24072
|
*/
|
|
23988
|
-
function whiteSprite(pixi) {
|
|
24073
|
+
function whiteSprite(pixi, blend = "normal") {
|
|
23989
24074
|
const sprite = new pixi.Sprite(pixi.Texture.WHITE);
|
|
23990
|
-
sprite.label = "renderer.warm.sprite"
|
|
24075
|
+
sprite.label = blend === "normal" ? "renderer.warm.sprite" : `renderer.warm.sprite-${blend}`;
|
|
23991
24076
|
sprite.width = PROBE_SIZE;
|
|
23992
24077
|
sprite.height = PROBE_SIZE;
|
|
24078
|
+
if (blend !== "normal") sprite.blendMode = blend;
|
|
23993
24079
|
return sprite;
|
|
23994
24080
|
}
|
|
23995
24081
|
/**
|
|
@@ -24052,6 +24138,8 @@ function buildObjects(pixi) {
|
|
|
24052
24138
|
holder,
|
|
24053
24139
|
list: [],
|
|
24054
24140
|
sprite: whiteSprite(pixi),
|
|
24141
|
+
spriteAdd: whiteSprite(pixi, "add"),
|
|
24142
|
+
spriteScreen: whiteSprite(pixi, "screen"),
|
|
24055
24143
|
graphics,
|
|
24056
24144
|
particlesNormal: particlesOf(pixi, "normal"),
|
|
24057
24145
|
particlesAdd: particlesOf(pixi, "add"),
|
|
@@ -24075,6 +24163,8 @@ function dropObjects(warm) {
|
|
|
24075
24163
|
objects.clipFrame,
|
|
24076
24164
|
objects.holder,
|
|
24077
24165
|
objects.sprite,
|
|
24166
|
+
objects.spriteAdd,
|
|
24167
|
+
objects.spriteScreen,
|
|
24078
24168
|
objects.graphics,
|
|
24079
24169
|
objects.particlesNormal,
|
|
24080
24170
|
objects.particlesAdd,
|
|
@@ -24251,27 +24341,22 @@ function warmFilter(sctx, filter) {
|
|
|
24251
24341
|
* @returns The node, or `undefined` for the text without a font.
|
|
24252
24342
|
*/
|
|
24253
24343
|
function nodeOf$1(pixi, warm, objects, subject) {
|
|
24254
|
-
|
|
24255
|
-
|
|
24256
|
-
|
|
24257
|
-
|
|
24258
|
-
|
|
24259
|
-
|
|
24260
|
-
|
|
24261
|
-
|
|
24262
|
-
|
|
24263
|
-
|
|
24264
|
-
|
|
24265
|
-
|
|
24266
|
-
|
|
24267
|
-
|
|
24268
|
-
|
|
24269
|
-
|
|
24270
|
-
default:
|
|
24271
|
-
objects.list[0] = subject;
|
|
24272
|
-
objects.holder.filters = objects.list;
|
|
24273
|
-
return objects.holder;
|
|
24274
|
-
}
|
|
24344
|
+
if (typeof subject !== "string") {
|
|
24345
|
+
objects.list[0] = subject;
|
|
24346
|
+
objects.holder.filters = objects.list;
|
|
24347
|
+
return objects.holder;
|
|
24348
|
+
}
|
|
24349
|
+
if (subject !== "text") return objects[FIXED_NODES[subject]];
|
|
24350
|
+
if (warm.textFont === void 0) return void 0;
|
|
24351
|
+
objects.text ??= new pixi.BitmapText({
|
|
24352
|
+
label: "renderer.warm.text",
|
|
24353
|
+
text: "0",
|
|
24354
|
+
style: {
|
|
24355
|
+
fontFamily: warm.textFont,
|
|
24356
|
+
fontSize: 16
|
|
24357
|
+
}
|
|
24358
|
+
});
|
|
24359
|
+
return objects.text;
|
|
24275
24360
|
}
|
|
24276
24361
|
/**
|
|
24277
24362
|
* The name of a probe's filter in the log: the name of its WebGPU program (the definition id of one of `effects`'
|
|
@@ -25077,7 +25162,8 @@ function poolKeyOf(kind, textureKey, hollow) {
|
|
|
25077
25162
|
return hollow ? `${kind}.hollow:${textureKey}` : `${kind}:${textureKey}`;
|
|
25078
25163
|
}
|
|
25079
25164
|
/**
|
|
25080
|
-
* Brings the object of a view that left back to the state a fresh one has: detached, drawn, opaque, unmoved,
|
|
25165
|
+
* Brings the object of a view that left back to the state a fresh one has: detached, drawn, opaque, unmoved, blending
|
|
25166
|
+
* normally, white.
|
|
25081
25167
|
* Its hidden reasons go with `clearHidden`, so a pooled object never brings the reason of the view it belonged to; a
|
|
25082
25168
|
* sprite and a graphics lose their tint (a fill-only shape is coloured by it, spec 26 R2.1).
|
|
25083
25169
|
*
|
|
@@ -25089,14 +25175,15 @@ function resetObject(view) {
|
|
|
25089
25175
|
resetNode(view.object);
|
|
25090
25176
|
}
|
|
25091
25177
|
/**
|
|
25092
|
-
* Brings a detached object back to a fresh one's pose and look: drawn, opaque, unmoved,
|
|
25093
|
-
* graphics.
|
|
25178
|
+
* Brings a detached object back to a fresh one's pose and look: drawn, opaque, unmoved, blending as its parent (no
|
|
25179
|
+
* glow of the view it drew for), white for a sprite and a graphics.
|
|
25094
25180
|
*
|
|
25095
25181
|
* @param object - The object, out of the tree.
|
|
25096
25182
|
*/
|
|
25097
25183
|
function resetNode(object) {
|
|
25098
25184
|
object.visible = true;
|
|
25099
25185
|
object.alpha = 1;
|
|
25186
|
+
object.blendMode = "inherit";
|
|
25100
25187
|
object.rotation = 0;
|
|
25101
25188
|
object.zIndex = 0;
|
|
25102
25189
|
object.position.set(0, 0);
|
|
@@ -27197,6 +27284,42 @@ function retargetKey(state, entity, view, key) {
|
|
|
27197
27284
|
if (key !== "") trackKey(state, entity, key);
|
|
27198
27285
|
}
|
|
27199
27286
|
/**
|
|
27287
|
+
* The Pixi blend mode a `blend` of a `Sprite` or a `NineSlice` draws with: `"add"` and `"screen"` as they are, Pixi's
|
|
27288
|
+
* own blends of the sprite batch; `"inherit"`, what a fresh object holds, for `"normal"` and a value without the field.
|
|
27289
|
+
* Any other string (Pixi's advanced modes, `"multiply"`, a typo) is never handed to Pixi, which would draw an advanced
|
|
27290
|
+
* mode through a filter pass of its own: it draws normally, and each such value is warned once per run.
|
|
27291
|
+
*
|
|
27292
|
+
* @param sctx - Domain context of the sync module.
|
|
27293
|
+
* @param blend - The component's `blend`, as a game wrote it.
|
|
27294
|
+
* @returns The Pixi blend mode.
|
|
27295
|
+
*/
|
|
27296
|
+
function blendModeOf(sctx, blend) {
|
|
27297
|
+
if (blend === void 0 || blend === "normal") return "inherit";
|
|
27298
|
+
if (blend === "add" || blend === "screen") return blend;
|
|
27299
|
+
const warned = sctx.ctx.state.sync.blendWarned;
|
|
27300
|
+
if (!warned.has(blend)) {
|
|
27301
|
+
warned.add(blend);
|
|
27302
|
+
sctx.ctx.log.warn("renderer:blend-unknown", { blend });
|
|
27303
|
+
}
|
|
27304
|
+
return "inherit";
|
|
27305
|
+
}
|
|
27306
|
+
/**
|
|
27307
|
+
* Writes the blend of a sprite or a nine-slice on its own object, never on the wrapper: a glow blends its own pixels,
|
|
27308
|
+
* not its children's. Only a mode that differs from what Pixi holds is written, the entity marked before: Pixi rebuilds
|
|
27309
|
+
* the instructions of the render group on a new mode.
|
|
27310
|
+
*
|
|
27311
|
+
* @param sctx - Domain context of the sync module.
|
|
27312
|
+
* @param entity - The entity.
|
|
27313
|
+
* @param object - The view's own object: the sprite, or the nine-slice, filled or hollow.
|
|
27314
|
+
* @param blend - The component's `blend`.
|
|
27315
|
+
*/
|
|
27316
|
+
function writeBlend(sctx, entity, object, blend) {
|
|
27317
|
+
const mode = blendModeOf(sctx, blend);
|
|
27318
|
+
if (object.blendMode === mode) return;
|
|
27319
|
+
markView(sctx, entity);
|
|
27320
|
+
object.blendMode = mode;
|
|
27321
|
+
}
|
|
27322
|
+
/**
|
|
27200
27323
|
* Writes the texture and the tint of a sprite, each only when it differs from what Pixi holds.
|
|
27201
27324
|
*
|
|
27202
27325
|
* @param sctx - Domain context of the sync module.
|
|
@@ -27325,7 +27448,7 @@ function applyHud(sctx, entity, view, factor) {
|
|
|
27325
27448
|
}
|
|
27326
27449
|
/**
|
|
27327
27450
|
* Writes the `Sprite` component onto its display object: the texture, or its crop for a `"cover"`
|
|
27328
|
-
* sprite, the stretch into the box, and the box as the hit box, the anchor applied. The stretch and
|
|
27451
|
+
* sprite, the blend, the stretch into the box, and the box as the hit box, the anchor applied. The stretch and
|
|
27329
27452
|
* the hit box are written into the view's own objects. A new stretch of a view without a wrapper
|
|
27330
27453
|
* changes the object's own units, so its filter area is written again (B4); a write that keeps it (an
|
|
27331
27454
|
* alpha tween, a tint) writes no area.
|
|
@@ -27345,7 +27468,7 @@ function applySprite(sctx, entity, view) {
|
|
|
27345
27468
|
return stretched;
|
|
27346
27469
|
}
|
|
27347
27470
|
/**
|
|
27348
|
-
* The body of `applySprite`: the texture or its crop, the stretch, the anchor and the hit box.
|
|
27471
|
+
* The body of `applySprite`: the texture or its crop, the blend, the stretch, the anchor and the hit box.
|
|
27349
27472
|
*
|
|
27350
27473
|
* @param sctx - Domain context of the sync module.
|
|
27351
27474
|
* @param entity - The entity.
|
|
@@ -27360,6 +27483,7 @@ function writeSprite(sctx, entity, view) {
|
|
|
27360
27483
|
if (texture === void 0) warnMissing(sctx, value.texture);
|
|
27361
27484
|
view.placeholder = texture === void 0;
|
|
27362
27485
|
applyAlpha(sctx, entity, view, value);
|
|
27486
|
+
writeBlend(sctx, entity, object, value.blend);
|
|
27363
27487
|
if (texture === void 0) {
|
|
27364
27488
|
showCover(sctx, entity, view, "");
|
|
27365
27489
|
drawPlaceholder(sctx, entity, object, view, value, placeholderTexture(sctx));
|
|
@@ -27425,8 +27549,9 @@ function writeSlice(sctx, entity, object, field, value) {
|
|
|
27425
27549
|
* Writes the `NineSlice` component onto its display object and recomputes the hit box. Pixi reads
|
|
27426
27550
|
* a texture's borders only in the constructor, so they are copied here on every write: a pooled
|
|
27427
27551
|
* object, a new key and a reloaded bundle all get the insets of the texture they draw now. A `hollow` that flipped
|
|
27428
|
-
* swaps the view's object first (F59)
|
|
27429
|
-
* or only the size, allocates nothing: the borders, the outline's
|
|
27552
|
+
* swaps the view's object first (F59), and the blend is written on the object drawn now. The hit box is the whole
|
|
27553
|
+
* panel, hollow or not. A write that changes nothing, or only the size, allocates nothing: the borders, the outline's
|
|
27554
|
+
* shape and the clip's box are written in place.
|
|
27430
27555
|
*
|
|
27431
27556
|
* @param sctx - Domain context of the sync module.
|
|
27432
27557
|
* @param entity - The entity.
|
|
@@ -27457,6 +27582,7 @@ function applyNineSlice(sctx, entity, view) {
|
|
|
27457
27582
|
changed = writeSlice(sctx, entity, object, "bottomHeight", borders.bottom) || changed;
|
|
27458
27583
|
changed = writeSlice(sctx, entity, object, "tint", missing ? 16711935 : value.tint) || changed;
|
|
27459
27584
|
applyAlpha(sctx, entity, view, value);
|
|
27585
|
+
writeBlend(sctx, entity, object, value.blend);
|
|
27460
27586
|
changed = writeSlice(sctx, entity, object, "width", value.width) || changed;
|
|
27461
27587
|
changed = writeSlice(sctx, entity, object, "height", value.height) || changed;
|
|
27462
27588
|
writeHitBox(view.hitBox, value.width, value.height);
|
|
@@ -28196,6 +28322,7 @@ function stopSync(state) {
|
|
|
28196
28322
|
state.byKey.clear();
|
|
28197
28323
|
state.invalidated.clear();
|
|
28198
28324
|
state.warned.clear();
|
|
28325
|
+
state.blendWarned.clear();
|
|
28199
28326
|
state.added.clear();
|
|
28200
28327
|
state.removed.clear();
|
|
28201
28328
|
state.reparented.clear();
|
|
@@ -31547,6 +31674,7 @@ function createSyncState(debug) {
|
|
|
31547
31674
|
slices: emptyWeakSet(),
|
|
31548
31675
|
invalidated: emptySet$4(),
|
|
31549
31676
|
warned: emptySet$4(),
|
|
31677
|
+
blendWarned: emptySet$4(),
|
|
31550
31678
|
added: emptySet$4(),
|
|
31551
31679
|
removed: emptySet$4(),
|
|
31552
31680
|
reparented: emptySet$4(),
|
|
@@ -38180,11 +38308,121 @@ function applyAllGains(state) {
|
|
|
38180
38308
|
applyGain(state, "sfx");
|
|
38181
38309
|
}
|
|
38182
38310
|
//#endregion
|
|
38311
|
+
//#region src/plugins/audio/named.ts
|
|
38312
|
+
/**
|
|
38313
|
+
* @file audio plugin — named sounds: an `sfx` with an `id` plays through a gain of its own into its
|
|
38314
|
+
* bus, so the next sound with that id, `stopSfx` and `app.audio.stop` can fade it out. One entry
|
|
38315
|
+
* per id in `state.named`; the entry object is the token a sound still decoding checks before it
|
|
38316
|
+
* starts. A sound without an id never comes here.
|
|
38317
|
+
*/
|
|
38318
|
+
/**
|
|
38319
|
+
* The key a sound with an id waits under in `pendingSfx` while the unlock resumes the context.
|
|
38320
|
+
* The `#` keeps the ids apart from the asset keys, so the latest sound per id waits.
|
|
38321
|
+
*
|
|
38322
|
+
* @param id - The id of the sound.
|
|
38323
|
+
* @returns The key of the waiting sound.
|
|
38324
|
+
* @example
|
|
38325
|
+
* ```ts
|
|
38326
|
+
* waitingKey("voice"); // "#voice"
|
|
38327
|
+
* ```
|
|
38328
|
+
*/
|
|
38329
|
+
function waitingKey(id) {
|
|
38330
|
+
return `#${id}`;
|
|
38331
|
+
}
|
|
38332
|
+
/**
|
|
38333
|
+
* Fades one named sound out on the context clock and stops its source at the end of the fade. A
|
|
38334
|
+
* sound still decoding has neither a gain nor a source, so nothing is scheduled.
|
|
38335
|
+
*
|
|
38336
|
+
* @param state - The plugin state.
|
|
38337
|
+
* @param entry - The sound to fade.
|
|
38338
|
+
* @param seconds - Length of the fade, in seconds of the context clock.
|
|
38339
|
+
*/
|
|
38340
|
+
function fadeNamed(state, entry, seconds) {
|
|
38341
|
+
const context = state.context;
|
|
38342
|
+
if (context === void 0) return;
|
|
38343
|
+
const now = context.currentTime;
|
|
38344
|
+
if (entry.gain !== void 0) ramp(entry.gain, 0, seconds, now);
|
|
38345
|
+
entry.source?.stop(now + seconds);
|
|
38346
|
+
}
|
|
38347
|
+
/**
|
|
38348
|
+
* Takes an id for a new sound: the sound that plays, or decodes, under it fades out over the fade
|
|
38349
|
+
* of the new one, and a fresh entry takes its place.
|
|
38350
|
+
*
|
|
38351
|
+
* @param state - The plugin state.
|
|
38352
|
+
* @param id - The id of the new sound.
|
|
38353
|
+
* @param seconds - Length of the fade of the replaced sound, in seconds.
|
|
38354
|
+
* @returns The entry of the new sound, its token.
|
|
38355
|
+
*/
|
|
38356
|
+
function claimNamed(state, id, seconds) {
|
|
38357
|
+
const playing = state.named.get(id);
|
|
38358
|
+
if (playing !== void 0) fadeNamed(state, playing, seconds);
|
|
38359
|
+
const entry = {
|
|
38360
|
+
gain: void 0,
|
|
38361
|
+
source: void 0
|
|
38362
|
+
};
|
|
38363
|
+
state.named.set(id, entry);
|
|
38364
|
+
return entry;
|
|
38365
|
+
}
|
|
38366
|
+
/**
|
|
38367
|
+
* Starts one named sound: a gain at 1 into the bus, the source into that gain. When the source
|
|
38368
|
+
* ends, its gain is disconnected and the id is freed, unless a later sound took the id meanwhile.
|
|
38369
|
+
*
|
|
38370
|
+
* @param state - The plugin state.
|
|
38371
|
+
* @param context - The running audio context.
|
|
38372
|
+
* @param named - The id, its entry, the decoded buffer and the gain of the bus.
|
|
38373
|
+
* @param named.id - The id the sound plays under.
|
|
38374
|
+
* @param named.entry - The entry `claimNamed` returned for it.
|
|
38375
|
+
* @param named.buffer - The decoded buffer of the sound.
|
|
38376
|
+
* @param named.bus - The gain node of the bus the sound goes to.
|
|
38377
|
+
*/
|
|
38378
|
+
function startNamed(state, context, named) {
|
|
38379
|
+
const { id, entry } = named;
|
|
38380
|
+
const gain = context.createGain();
|
|
38381
|
+
const source = context.createBufferSource();
|
|
38382
|
+
gain.gain.value = 1;
|
|
38383
|
+
gain.connect(named.bus);
|
|
38384
|
+
source.buffer = named.buffer;
|
|
38385
|
+
source.connect(gain);
|
|
38386
|
+
source.addEventListener("ended", () => {
|
|
38387
|
+
gain.disconnect();
|
|
38388
|
+
if (state.named.get(id) === entry) state.named.delete(id);
|
|
38389
|
+
}, { once: true });
|
|
38390
|
+
entry.gain = gain;
|
|
38391
|
+
entry.source = source;
|
|
38392
|
+
source.start();
|
|
38393
|
+
}
|
|
38394
|
+
/**
|
|
38395
|
+
* Ends the sound under one id: the stop of `stopSfx` and of `app.audio.stop`. The entry leaves the
|
|
38396
|
+
* map, so a sound still decoding starts nothing, and the one that plays fades out. A sound with
|
|
38397
|
+
* that id waiting for the unlock is dropped. An id nothing plays under changes nothing.
|
|
38398
|
+
*
|
|
38399
|
+
* @param state - The plugin state.
|
|
38400
|
+
* @param id - The id the sound was started with.
|
|
38401
|
+
* @param fadeMs - Length of the fade, in real milliseconds.
|
|
38402
|
+
*/
|
|
38403
|
+
function stopNamed(state, id, fadeMs) {
|
|
38404
|
+
state.pendingSfx.delete(waitingKey(id));
|
|
38405
|
+
const entry = state.named.get(id);
|
|
38406
|
+
if (entry === void 0) return;
|
|
38407
|
+
state.named.delete(id);
|
|
38408
|
+
fadeNamed(state, entry, fadeMs / 1e3);
|
|
38409
|
+
}
|
|
38410
|
+
/**
|
|
38411
|
+
* Stops every named sound at once and forgets the ids. `onStop` runs it: a teardown has no time
|
|
38412
|
+
* for a fade, and the context is closed right after.
|
|
38413
|
+
*
|
|
38414
|
+
* @param state - The plugin state.
|
|
38415
|
+
*/
|
|
38416
|
+
function stopAllNamed(state) {
|
|
38417
|
+
for (const entry of state.named.values()) entry.source?.stop();
|
|
38418
|
+
state.named.clear();
|
|
38419
|
+
}
|
|
38420
|
+
//#endregion
|
|
38183
38421
|
//#region src/plugins/audio/api.ts
|
|
38184
38422
|
/**
|
|
38185
|
-
* @file audio plugin — API factory. Three buses, the unlock flag
|
|
38186
|
-
* is persisted; a game keeps the player's choice in the player state and
|
|
38187
|
-
* it on the next commit.
|
|
38423
|
+
* @file audio plugin — API factory. Three buses, the unlock flag, the journal of started sounds and the stop of a
|
|
38424
|
+
* named sound. Nothing here is persisted; a game keeps the player's choice in the player state and
|
|
38425
|
+
* `config.volumes` applies it on the next commit.
|
|
38188
38426
|
*/
|
|
38189
38427
|
/**
|
|
38190
38428
|
* Brings a value into 0..1. `NaN`, which is what a slider divided by zero produces, becomes 0.
|
|
@@ -38226,7 +38464,7 @@ function setBusVolume(state, bus, value) {
|
|
|
38226
38464
|
applyGain(state, bus);
|
|
38227
38465
|
}
|
|
38228
38466
|
/**
|
|
38229
|
-
* Creates the audio API: the three buses, the unlock flag and the
|
|
38467
|
+
* Creates the audio API: the three buses, the unlock flag, the journal and the stop of a named sound.
|
|
38230
38468
|
*
|
|
38231
38469
|
* @param ctx - Kernel context of the audio plugin.
|
|
38232
38470
|
* @returns The plugin API.
|
|
@@ -38243,7 +38481,8 @@ function createAudioApi(ctx) {
|
|
|
38243
38481
|
},
|
|
38244
38482
|
muted: (bus) => state.buses[busOf(state, bus)].muted,
|
|
38245
38483
|
unlocked: () => state.unlocked,
|
|
38246
|
-
journal: () => state.journal
|
|
38484
|
+
journal: () => state.journal,
|
|
38485
|
+
stop: (id, options) => stopNamed(state, id, options?.fadeMs ?? ctx.config.sfxFadeMs)
|
|
38247
38486
|
};
|
|
38248
38487
|
}
|
|
38249
38488
|
//#endregion
|
|
@@ -38251,13 +38490,15 @@ function createAudioApi(ctx) {
|
|
|
38251
38490
|
/**
|
|
38252
38491
|
* Replaces the journal with a frozen list that ends with one started sound, stamped with the
|
|
38253
38492
|
* elapsed game time, and keeps only the newest `config.journal` entries. A size of 0, or anything
|
|
38254
|
-
* that is not a positive number, records nothing.
|
|
38493
|
+
* that is not a positive number, records nothing. The entry has an `id` only when the sound has
|
|
38494
|
+
* one: the caller leaves the field out otherwise, so no entry carries an `undefined` key.
|
|
38255
38495
|
*
|
|
38256
38496
|
* @param ctx - Domain context of the plugin.
|
|
38257
38497
|
* @param sound - What started.
|
|
38258
38498
|
* @param sound.key - Asset key of the sound.
|
|
38259
38499
|
* @param sound.bus - The bus it plays on.
|
|
38260
38500
|
* @param sound.kind - `"sfx"` for a play, `"music"` for a track.
|
|
38501
|
+
* @param sound.id - The id of a named sound; absent for every other sound.
|
|
38261
38502
|
*/
|
|
38262
38503
|
function recordSound(ctx, sound) {
|
|
38263
38504
|
const size = Math.floor(ctx.config.journal);
|
|
@@ -38385,18 +38626,47 @@ function fieldOf(payload, field) {
|
|
|
38385
38626
|
return payload[field];
|
|
38386
38627
|
}
|
|
38387
38628
|
/**
|
|
38388
|
-
* Reads the
|
|
38629
|
+
* Reads the fade of a descriptor that ends a named sound.
|
|
38389
38630
|
*
|
|
38390
38631
|
* @param descriptor - The descriptor the handler was called with.
|
|
38632
|
+
* @param fadeMs - The configured fade, used when the descriptor names none.
|
|
38633
|
+
* @returns The fade, in milliseconds.
|
|
38634
|
+
*/
|
|
38635
|
+
function fadeOf(descriptor, fadeMs) {
|
|
38636
|
+
return validFade(fieldOf(descriptor.payload, "fadeMs"), fadeMs);
|
|
38637
|
+
}
|
|
38638
|
+
/**
|
|
38639
|
+
* Takes a fade a descriptor named only when the clock can schedule it: a finite number, 0 or more.
|
|
38640
|
+
* `NaN` or a negative fade would make the ramp and the stop throw inside the handler.
|
|
38641
|
+
*
|
|
38642
|
+
* @param fade - The `fadeMs` the payload carries.
|
|
38643
|
+
* @param fallback - The configured fade.
|
|
38644
|
+
* @returns The fade, in milliseconds.
|
|
38645
|
+
* @example
|
|
38646
|
+
* ```ts
|
|
38647
|
+
* validFade(-5, 120); // 120
|
|
38648
|
+
* ```
|
|
38649
|
+
*/
|
|
38650
|
+
function validFade(fade, fallback) {
|
|
38651
|
+
return typeof fade === "number" && Number.isFinite(fade) && fade >= 0 ? fade : fallback;
|
|
38652
|
+
}
|
|
38653
|
+
/**
|
|
38654
|
+
* Reads the key, the bus, the id and the fade out of the `sfx` descriptor `anim` builds.
|
|
38655
|
+
*
|
|
38656
|
+
* @param descriptor - The descriptor the handler was called with.
|
|
38657
|
+
* @param fadeMs - The configured fade of a named sound, used when the descriptor names none.
|
|
38391
38658
|
* @returns The request, or `undefined` when the descriptor names no key.
|
|
38392
38659
|
*/
|
|
38393
|
-
function sfxOf(descriptor) {
|
|
38660
|
+
function sfxOf(descriptor, fadeMs) {
|
|
38394
38661
|
const key = fieldOf(descriptor.payload, "key");
|
|
38395
38662
|
const bus = fieldOf(descriptor.payload, "bus");
|
|
38663
|
+
const id = fieldOf(descriptor.payload, "id");
|
|
38396
38664
|
if (typeof key !== "string") return void 0;
|
|
38397
38665
|
return {
|
|
38398
38666
|
key,
|
|
38399
|
-
bus: typeof bus === "string" ? bus : DEFAULT_BUS$1
|
|
38667
|
+
bus: typeof bus === "string" ? bus : DEFAULT_BUS$1,
|
|
38668
|
+
id: typeof id === "string" ? id : void 0,
|
|
38669
|
+
fadeMs: fadeOf(descriptor, fadeMs)
|
|
38400
38670
|
};
|
|
38401
38671
|
}
|
|
38402
38672
|
/**
|
|
@@ -38408,10 +38678,9 @@ function sfxOf(descriptor) {
|
|
|
38408
38678
|
*/
|
|
38409
38679
|
function musicOf(descriptor, fadeMs) {
|
|
38410
38680
|
const key = fieldOf(descriptor.payload, "key");
|
|
38411
|
-
const fade = fieldOf(descriptor.payload, "fadeMs");
|
|
38412
38681
|
return {
|
|
38413
38682
|
key: typeof key === "string" ? key : null,
|
|
38414
|
-
fadeMs:
|
|
38683
|
+
fadeMs: validFade(fieldOf(descriptor.payload, "fadeMs"), fadeMs)
|
|
38415
38684
|
};
|
|
38416
38685
|
}
|
|
38417
38686
|
/**
|
|
@@ -38470,9 +38739,10 @@ async function bufferOf(ctx, key) {
|
|
|
38470
38739
|
/**
|
|
38471
38740
|
* Plays one sound: the handler of the kind `"sfx"`. Every play is its own source, so the same key
|
|
38472
38741
|
* twice in one frame is heard twice, and the promise resolves when the sound STARTED. The handler
|
|
38473
|
-
* ignores its signal: a sound that began plays to its end
|
|
38474
|
-
* pending the sound waits in `pendingSfx`, one per
|
|
38475
|
-
*
|
|
38742
|
+
* ignores its signal: a sound that began plays to its end, unless a sound with its id or a stop
|
|
38743
|
+
* ends it. While a gesture's `resume()` is pending the sound waits in `pendingSfx`, one per id
|
|
38744
|
+
* and one per key without an id. A locked context no gesture is resuming drops it: the start
|
|
38745
|
+
* attempt of the unlock queues nothing.
|
|
38476
38746
|
*
|
|
38477
38747
|
* @param ctx - Domain context of the plugin.
|
|
38478
38748
|
* @param descriptor - The `sfx` descriptor `anim` built.
|
|
@@ -38480,20 +38750,79 @@ async function bufferOf(ctx, key) {
|
|
|
38480
38750
|
async function playSfx(ctx, descriptor) {
|
|
38481
38751
|
const state = ctx.state;
|
|
38482
38752
|
if (state.context === void 0) return;
|
|
38483
|
-
const request = sfxOf(descriptor);
|
|
38753
|
+
const request = sfxOf(descriptor, ctx.config.sfxFadeMs);
|
|
38484
38754
|
if (request === void 0) return;
|
|
38485
38755
|
if (!state.unlocked) {
|
|
38486
|
-
|
|
38756
|
+
queueWhileResuming(state, request);
|
|
38487
38757
|
return;
|
|
38488
38758
|
}
|
|
38489
38759
|
await playRequest(ctx, request);
|
|
38490
38760
|
}
|
|
38491
38761
|
/**
|
|
38762
|
+
* Keeps a sound fired while the context is locked, one per key and one per id, when a gesture's
|
|
38763
|
+
* `resume()` is on its way. A locked context nobody resumes drops it.
|
|
38764
|
+
*
|
|
38765
|
+
* @param state - The plugin state.
|
|
38766
|
+
* @param request - The sound that was fired.
|
|
38767
|
+
*/
|
|
38768
|
+
function queueWhileResuming(state, request) {
|
|
38769
|
+
if (!state.resuming) return;
|
|
38770
|
+
state.pendingSfx.set(request.id === void 0 ? request.key : waitingKey(request.id), request);
|
|
38771
|
+
}
|
|
38772
|
+
/**
|
|
38773
|
+
* Ends the sound under one id: the handler of the kind `"stopSfx"`, for an effect, an animation
|
|
38774
|
+
* step and a released hint alike. A descriptor without an id ends nothing.
|
|
38775
|
+
*
|
|
38776
|
+
* @param ctx - Domain context of the plugin.
|
|
38777
|
+
* @param descriptor - The `stopSfx` descriptor `anim` built.
|
|
38778
|
+
*/
|
|
38779
|
+
function stopNamedSfx(ctx, descriptor) {
|
|
38780
|
+
const id = fieldOf(descriptor.payload, "id");
|
|
38781
|
+
if (typeof id !== "string") return;
|
|
38782
|
+
stopNamed(ctx.state, id, fadeOf(descriptor, ctx.config.sfxFadeMs));
|
|
38783
|
+
}
|
|
38784
|
+
/**
|
|
38785
|
+
* Plays a sound under its id: the sound that has the id fades out over the fade of this one, and
|
|
38786
|
+
* this one starts through its own gain once its buffer is there, unless a later sound with the id
|
|
38787
|
+
* or a stop took its place while it decoded. A file that is missing frees the id.
|
|
38788
|
+
*
|
|
38789
|
+
* @param ctx - Domain context of the plugin.
|
|
38790
|
+
* @param request - The key, the fade and the id of the sound.
|
|
38791
|
+
* @param sound - The id and the bus, both checked.
|
|
38792
|
+
* @param sound.id - The id the sound plays under.
|
|
38793
|
+
* @param sound.bus - The bus it goes to.
|
|
38794
|
+
*/
|
|
38795
|
+
async function playNamed(ctx, request, sound) {
|
|
38796
|
+
const state = ctx.state;
|
|
38797
|
+
const { id, bus } = sound;
|
|
38798
|
+
const entry = claimNamed(state, id, request.fadeMs / 1e3);
|
|
38799
|
+
const buffer = await bufferOf(ctx, request.key);
|
|
38800
|
+
if (state.named.get(id) !== entry) return;
|
|
38801
|
+
const context = state.context;
|
|
38802
|
+
const gain = state.buses[bus].gain;
|
|
38803
|
+
if (buffer === void 0 || context === void 0 || gain === void 0) {
|
|
38804
|
+
state.named.delete(id);
|
|
38805
|
+
return;
|
|
38806
|
+
}
|
|
38807
|
+
startNamed(state, context, {
|
|
38808
|
+
id,
|
|
38809
|
+
entry,
|
|
38810
|
+
buffer,
|
|
38811
|
+
bus: gain
|
|
38812
|
+
});
|
|
38813
|
+
recordSound(ctx, {
|
|
38814
|
+
key: request.key,
|
|
38815
|
+
bus,
|
|
38816
|
+
kind: "sfx",
|
|
38817
|
+
id
|
|
38818
|
+
});
|
|
38819
|
+
}
|
|
38820
|
+
/**
|
|
38492
38821
|
* Starts one source of a sound on its bus. `playSfx` calls it for a live play and the unlock for
|
|
38493
|
-
* every sound that waited for the resume.
|
|
38822
|
+
* every sound that waited for the resume. A sound with an id goes through `playNamed`.
|
|
38494
38823
|
*
|
|
38495
38824
|
* @param ctx - Domain context of the plugin.
|
|
38496
|
-
* @param request - The key and the
|
|
38825
|
+
* @param request - The key, the bus, the id and the fade of the sound.
|
|
38497
38826
|
*/
|
|
38498
38827
|
async function playRequest(ctx, request) {
|
|
38499
38828
|
const state = ctx.state;
|
|
@@ -38503,6 +38832,13 @@ async function playRequest(ctx, request) {
|
|
|
38503
38832
|
ctx.log.warn("audio: unknown bus", { bus: request.bus });
|
|
38504
38833
|
return;
|
|
38505
38834
|
}
|
|
38835
|
+
if (request.id !== void 0) {
|
|
38836
|
+
await playNamed(ctx, request, {
|
|
38837
|
+
id: request.id,
|
|
38838
|
+
bus: request.bus
|
|
38839
|
+
});
|
|
38840
|
+
return;
|
|
38841
|
+
}
|
|
38506
38842
|
const buffer = await bufferOf(ctx, request.key);
|
|
38507
38843
|
const gain = state.buses[request.bus].gain;
|
|
38508
38844
|
if (buffer === void 0 || gain === void 0) return;
|
|
@@ -39027,8 +39363,8 @@ function createContext(config) {
|
|
|
39027
39363
|
return typeof browserContext === "function" ? new browserContext() : void 0;
|
|
39028
39364
|
}
|
|
39029
39365
|
/**
|
|
39030
|
-
* Starts the plugin in `onStart`: the audio session first, before any sound exists, then
|
|
39031
|
-
* effect handlers, then the context with its graph and its unlock. The unlock asks the context to
|
|
39366
|
+
* Starts the plugin in `onStart`: the audio session first, before any sound exists, then the
|
|
39367
|
+
* three effect handlers, then the context with its graph and its unlock. The unlock asks the context to
|
|
39032
39368
|
* run at once: a shell that lets audio play is unlocked with no touch, and a browser that wants a
|
|
39033
39369
|
* gesture gets the two listeners. The handlers are registered even headless, so a node that
|
|
39034
39370
|
* awaits a sound resolves in plain Bun exactly as it does in a browser.
|
|
@@ -39040,7 +39376,7 @@ function startAudio(ctx) {
|
|
|
39040
39376
|
const audio = withDeps$4(ctx);
|
|
39041
39377
|
const state = audio.state;
|
|
39042
39378
|
const fx = audio.deps.flow.fx;
|
|
39043
|
-
state.removers.push(fx.handle("sfx", (descriptor) => playSfx(audio, descriptor), { runInFast: false }), fx.handle("music", (descriptor) => playMusic(audio, musicOf(descriptor, ctx.config.musicFadeMs)), { runInFast: false }));
|
|
39379
|
+
state.removers.push(fx.handle("sfx", (descriptor) => playSfx(audio, descriptor), { runInFast: false }), fx.handle("stopSfx", (descriptor) => stopNamedSfx(audio, descriptor), { runInFast: false }), fx.handle("music", (descriptor) => playMusic(audio, musicOf(descriptor, ctx.config.musicFadeMs)), { runInFast: false }));
|
|
39044
39380
|
const context = createContext(ctx.config);
|
|
39045
39381
|
if (context === void 0) return;
|
|
39046
39382
|
state.context = context;
|
|
@@ -39049,9 +39385,9 @@ function startAudio(ctx) {
|
|
|
39049
39385
|
startUnlock(audio);
|
|
39050
39386
|
}
|
|
39051
39387
|
/**
|
|
39052
|
-
* Frees everything `onStart` opened: the
|
|
39053
|
-
* music track, every streamed track still fading with its timer, the
|
|
39054
|
-
* and the context itself. The tracks are freed before the close, while their nodes can still
|
|
39388
|
+
* Frees everything `onStart` opened: the three handler registrations, the two window listeners,
|
|
39389
|
+
* the music track, every named sound, every streamed track still fading with its timer, the
|
|
39390
|
+
* journal, the audio session and the context itself. The tracks are freed before the close, while their nodes can still
|
|
39055
39391
|
* disconnect. A browser caps how many contexts a page may hold, so the close is the point of this
|
|
39056
39392
|
* teardown.
|
|
39057
39393
|
*
|
|
@@ -39062,6 +39398,7 @@ async function stopAudio(state) {
|
|
|
39062
39398
|
for (const remove of state.removers.splice(0)) remove();
|
|
39063
39399
|
removeUnlock(state);
|
|
39064
39400
|
stopMusic(state);
|
|
39401
|
+
stopAllNamed(state);
|
|
39065
39402
|
for (const entry of state.retiring) disposeStream(state, entry.stream);
|
|
39066
39403
|
state.decoded.clear();
|
|
39067
39404
|
state.journal = Object.freeze([]);
|
|
@@ -39241,12 +39578,20 @@ function emptyWarned$2() {
|
|
|
39241
39578
|
/**
|
|
39242
39579
|
* Creates the queue of sounds fired while the unlock is pending.
|
|
39243
39580
|
*
|
|
39244
|
-
* @returns An empty queue, keyed by asset key.
|
|
39581
|
+
* @returns An empty queue, keyed by `"#" + id` or by asset key.
|
|
39245
39582
|
*/
|
|
39246
39583
|
function emptyPending() {
|
|
39247
39584
|
return /* @__PURE__ */ new Map();
|
|
39248
39585
|
}
|
|
39249
39586
|
/**
|
|
39587
|
+
* Creates the map of the sounds that play under an id.
|
|
39588
|
+
*
|
|
39589
|
+
* @returns An empty map, keyed by id.
|
|
39590
|
+
*/
|
|
39591
|
+
function emptyNamed() {
|
|
39592
|
+
return /* @__PURE__ */ new Map();
|
|
39593
|
+
}
|
|
39594
|
+
/**
|
|
39250
39595
|
* Creates the set of streamed tracks that fade out.
|
|
39251
39596
|
*
|
|
39252
39597
|
* @returns An empty set of retiring tracks.
|
|
@@ -39291,6 +39636,7 @@ function createAudioState(ctx) {
|
|
|
39291
39636
|
unlocked: false,
|
|
39292
39637
|
resuming: false,
|
|
39293
39638
|
pendingSfx: emptyPending(),
|
|
39639
|
+
named: emptyNamed(),
|
|
39294
39640
|
decoded: emptyCache$1(),
|
|
39295
39641
|
warned: emptyWarned$2(),
|
|
39296
39642
|
warnedVolumes: false,
|
|
@@ -39304,8 +39650,8 @@ function createAudioState(ctx) {
|
|
|
39304
39650
|
};
|
|
39305
39651
|
}
|
|
39306
39652
|
/**
|
|
39307
|
-
* Audio plugin: `app.audio`, the handlers of the effect kinds `sfx` and `music`, and
|
|
39308
|
-
* buses a game drives from its player state.
|
|
39653
|
+
* Audio plugin: `app.audio`, the handlers of the effect kinds `sfx`, `stopSfx` and `music`, and
|
|
39654
|
+
* the three buses a game drives from its player state.
|
|
39309
39655
|
*
|
|
39310
39656
|
* @example
|
|
39311
39657
|
* ```ts
|
|
@@ -39333,6 +39679,7 @@ const audioPlugin = /*#__PURE__*/ createPlugin$1("audio", {
|
|
|
39333
39679
|
sfx: 1
|
|
39334
39680
|
},
|
|
39335
39681
|
musicFadeMs: 600,
|
|
39682
|
+
sfxFadeMs: 120,
|
|
39336
39683
|
volumes: void 0,
|
|
39337
39684
|
context: void 0,
|
|
39338
39685
|
journal: 0,
|
|
@@ -48874,24 +49221,61 @@ function spawned(id) {
|
|
|
48874
49221
|
}
|
|
48875
49222
|
/**
|
|
48876
49223
|
* A sound. `anim` owns the descriptor because it sits below `audio`; `audio` owns the handler,
|
|
48877
|
-
* and a game without `audio` plays nothing and hears no error.
|
|
49224
|
+
* and a game without `audio` plays nothing and hears no error. A sound with an `id` is named:
|
|
49225
|
+
* one sound plays per id, a new one with the same id fades the old one out over `fadeMs`, and
|
|
49226
|
+
* `stopSfx(id)` stops it. The payload carries `id` and `fadeMs` only when they are given, so a
|
|
49227
|
+
* sound without them stays `{ key, bus }`.
|
|
48878
49228
|
*
|
|
48879
49229
|
* @param key - Asset key of the sound.
|
|
48880
|
-
* @param options - The bus
|
|
49230
|
+
* @param options - The bus, the name and the fade.
|
|
48881
49231
|
* @param options.bus - `"sfx"` when omitted.
|
|
49232
|
+
* @param options.id - Name of the sound; one sound per id plays.
|
|
49233
|
+
* @param options.fadeMs - Fade of the sound with the same id this one replaces; `audio`'s
|
|
49234
|
+
* `sfxFadeMs` when omitted.
|
|
48882
49235
|
* @returns The descriptor, usable as a timeline step and as `fx(...)` in a node.
|
|
48883
49236
|
* @example
|
|
48884
49237
|
* ```ts
|
|
48885
49238
|
* sfx("board.merge"); // { kind: "sfx", payload: { key: "board.merge", bus: "sfx" }, cosmetic: true }
|
|
49239
|
+
* sfx("voice.hello", { id: "voice", fadeMs: 200 });
|
|
49240
|
+
* // { kind: "sfx", payload: { key: "voice.hello", bus: "sfx", id: "voice", fadeMs: 200 }, cosmetic: true }
|
|
48886
49241
|
* ```
|
|
48887
49242
|
*/
|
|
48888
49243
|
function sfx(key, options) {
|
|
49244
|
+
const payload = {
|
|
49245
|
+
key,
|
|
49246
|
+
bus: options?.bus ?? DEFAULT_BUS
|
|
49247
|
+
};
|
|
49248
|
+
if (options?.id !== void 0) payload.id = options.id;
|
|
49249
|
+
if (options?.fadeMs !== void 0) payload.fadeMs = options.fadeMs;
|
|
48889
49250
|
return Object.freeze({
|
|
48890
49251
|
kind: "sfx",
|
|
48891
|
-
payload: Object.freeze(
|
|
48892
|
-
|
|
48893
|
-
|
|
48894
|
-
|
|
49252
|
+
payload: Object.freeze(payload),
|
|
49253
|
+
cosmetic: true
|
|
49254
|
+
});
|
|
49255
|
+
}
|
|
49256
|
+
/**
|
|
49257
|
+
* Stops the named sound `sfx(key, { id })` started, fading it out over `fadeMs`. `audio` owns the
|
|
49258
|
+
* handler; an id nothing plays is silent. It works as `fx.start(stopSfx("voice"))` in a node, as a
|
|
49259
|
+
* step a timeline reaches, and as the hint `hint("stopSfx", { id: "voice" })`. A timeline that is
|
|
49260
|
+
* finished or cut fires no effect, this one included: a game that must stop the voice then stops
|
|
49261
|
+
* it from the node.
|
|
49262
|
+
*
|
|
49263
|
+
* @param id - The id the sound was played with.
|
|
49264
|
+
* @param options - The fade.
|
|
49265
|
+
* @param options.fadeMs - Fade-out in ms; `audio`'s `sfxFadeMs` when omitted.
|
|
49266
|
+
* @returns The descriptor, usable as a timeline step and as `fx(...)` in a node.
|
|
49267
|
+
* @example
|
|
49268
|
+
* ```ts
|
|
49269
|
+
* stopSfx("voice", { fadeMs: 200 }); // { kind: "stopSfx", payload: { id: "voice", fadeMs: 200 }, cosmetic: true }
|
|
49270
|
+
* stopSfx("voice"); // { kind: "stopSfx", payload: { id: "voice" }, cosmetic: true }
|
|
49271
|
+
* ```
|
|
49272
|
+
*/
|
|
49273
|
+
function stopSfx(id, options) {
|
|
49274
|
+
const payload = { id };
|
|
49275
|
+
if (options?.fadeMs !== void 0) payload.fadeMs = options.fadeMs;
|
|
49276
|
+
return Object.freeze({
|
|
49277
|
+
kind: "stopSfx",
|
|
49278
|
+
payload: Object.freeze(payload),
|
|
48895
49279
|
cosmetic: true
|
|
48896
49280
|
});
|
|
48897
49281
|
}
|
|
@@ -62466,6 +62850,7 @@ function cubicBezier(x1, y1, x2, y2) {
|
|
|
62466
62850
|
* | ui | textInput | { caretWidth: 3, caret: 0x000000, selection: 0x3390ff, selectionAlpha: 0.35, composingUnderline: 3, keyboardMargin: 16 }; shallow merge |
|
|
62467
62851
|
* | audio | buses | { master: 1, music: 0.6, sfx: 1 }; shallow merge: a game that sets `buses` gives all three fields |
|
|
62468
62852
|
* | audio | musicFadeMs | 600 |
|
|
62853
|
+
* | audio | sfxFadeMs | 120 |
|
|
62469
62854
|
* | audio | volumes | undefined, the buses stay at `buses` |
|
|
62470
62855
|
* | audio | context | undefined, the real AudioContext |
|
|
62471
62856
|
* | audio | journal | 0, no started sound is kept |
|
|
@@ -62597,4 +62982,4 @@ const screen = [
|
|
|
62597
62982
|
uiPlugin
|
|
62598
62983
|
];
|
|
62599
62984
|
//#endregion
|
|
62600
|
-
export { sfx as $,
|
|
62985
|
+
export { sfx as $, flowPlugin as $t, types_exports$10 as A, Glow as At, defineMotion as B, Animation as Bt, types_exports$3 as C, messageDuration as Ct, types_exports$7 as D, Blur as Dt, types_exports$6 as E, Alpha as Et, types_exports$15 as F, audioPlugin as Ft, external as G, MONITOR_SECTIONS as Gt, platformPlugin as H, rendererPlugin as Ht, types_exports$16 as I, scenesPlugin as It, mark as J, PERF_LOG_VERSION as Jt, frames as K, PERF_CAUSES as Kt, types_exports$17 as L, assetsPlugin as Lt, types_exports$12 as M, Outline as Mt, types_exports$13 as N, defineFilter as Nt, types_exports$8 as O, ColorMatrix as Ot, types_exports$14 as P, debugPlugin as Pt, set as Q, worldPlugin as Qt, uiPlugin as R, animPlugin as Rt, types_exports$2 as S, messageArgument as St, types_exports$5 as T, Emitter as Tt, HAPTIC_KINDS as U, PASS_KINDS as Ut, textPlugin as V, Frames as Vt, defineAnimation as W, frameAt as Wt, play$1 as X, SHADER_WHATS as Xt, parallel as Y, PROBLEM_RULES as Yt, sequence as Z, DEMAND_REASONS as Zt, defineBundles as _, Tappable as _t, cubicBezier as a, modelPlugin as an, use as at, types_exports as b, Traced as bt, hint as c, timePlugin as cn, Draggable as ct, defineScene as d, Hovered as dt, refuseStart as en, spawn as et, tr as f, Pointer as ft, music as g, Swipeable as gt, defineEmitter as h, Pressed as ht, screen as i, type as in, tween as it, types_exports$11 as j, Noise as jt, types_exports$9 as k, Displacement as kt, schedule as l, clockPlugin as ln, DropTarget as lt, flattenFeatures as m, Pressable as mt, createPlugin as n, slot as nn, stagger as nt, defineEffect as o, SaveUnreadableError as on, wait as ot, defineFeature as p, PointerOver as pt, haptic as q, PERF_GAP_BUCKETS as qt, defineGame as r, to as rn, stopSfx as rt, guide as s, lifecyclePlugin as sn, inputPlugin as st, createApp as t, exit as tn, spawned as tt, projection as u, Held as ut, load as v, Touchable as vt, types_exports$4 as w, effectsPlugin as wt, types_exports$1 as x, i18nPlugin as xt, ready as y, Traceable as yt, resolve as z, AnimPlayer as zt };
|