fundus 0.22.1 → 0.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +119 -7
- package/build/client/_app/immutable/chunks/CCIxsMRK.js +1 -0
- package/build/client/_app/immutable/chunks/CCIxsMRK.js.br +0 -0
- package/build/client/_app/immutable/chunks/CCIxsMRK.js.gz +0 -0
- package/build/client/_app/immutable/chunks/{BrWiKjrr.js → CIZHDrUF.js} +1 -1
- package/build/client/_app/immutable/chunks/CIZHDrUF.js.br +0 -0
- package/build/client/_app/immutable/chunks/CIZHDrUF.js.gz +0 -0
- package/build/client/_app/immutable/chunks/{D68C0q2O.js → CnnMN4wM.js} +7 -7
- package/build/client/_app/immutable/chunks/CnnMN4wM.js.br +0 -0
- package/build/client/_app/immutable/chunks/CnnMN4wM.js.gz +0 -0
- package/build/client/_app/immutable/entry/{app.C6utCKyq.js → app.BPBGxhbo.js} +2 -2
- package/build/client/_app/immutable/entry/app.BPBGxhbo.js.br +0 -0
- package/build/client/_app/immutable/entry/app.BPBGxhbo.js.gz +0 -0
- package/build/client/_app/immutable/entry/start.CTftTL5f.js +1 -0
- package/build/client/_app/immutable/entry/start.CTftTL5f.js.br +0 -0
- package/build/client/_app/immutable/entry/start.CTftTL5f.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{0.D3X2aDLe.js → 0.CquXHiCP.js} +1 -1
- package/build/client/_app/immutable/nodes/0.CquXHiCP.js.br +0 -0
- package/build/client/_app/immutable/nodes/0.CquXHiCP.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{1.CoCDztxo.js → 1.xSi5hUiK.js} +1 -1
- package/build/client/_app/immutable/nodes/1.xSi5hUiK.js.br +1 -0
- package/build/client/_app/immutable/nodes/1.xSi5hUiK.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{2.D6Bsk2OP.js → 2.CgzHO6l0.js} +1 -1
- package/build/client/_app/immutable/nodes/2.CgzHO6l0.js.br +0 -0
- package/build/client/_app/immutable/nodes/2.CgzHO6l0.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{3.ZFuSk2Qg.js → 3.B-HZ9QO3.js} +1 -1
- package/build/client/_app/immutable/nodes/3.B-HZ9QO3.js.br +0 -0
- package/build/client/_app/immutable/nodes/3.B-HZ9QO3.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{4.CGsGyx14.js → 4._rTlKXDo.js} +1 -1
- package/build/client/_app/immutable/nodes/4._rTlKXDo.js.br +0 -0
- package/build/client/_app/immutable/nodes/{4.CGsGyx14.js.gz → 4._rTlKXDo.js.gz} +0 -0
- package/build/client/_app/version.json +1 -1
- package/build/client/_app/version.json.br +0 -0
- package/build/client/_app/version.json.gz +0 -0
- package/build/handler.js +7 -7
- package/build/index.js +7 -7
- package/build/server/chunks/chunks/{exports.js-BO7CNNsS.js → exports.js-CcnW-JJJ.js} +2 -2
- package/build/server/chunks/chunks/{exports.js-BO7CNNsS.js.map → exports.js-CcnW-JJJ.js.map} +1 -1
- package/build/server/chunks/chunks/{internal.js-CFdkiEzR.js → internal.js-ByBTomBV.js} +2 -2
- package/build/server/chunks/chunks/{internal.js-CFdkiEzR.js.map → internal.js-ByBTomBV.js.map} +1 -1
- package/build/server/chunks/chunks/internal2.js-BlXt2rfE.js.map +1 -1
- package/build/server/chunks/chunks/{shared.js-CwOc93ku.js → shared.js-CCqNepI3.js} +2 -2
- package/build/server/chunks/chunks/{shared.js-CwOc93ku.js.map → shared.js-CCqNepI3.js.map} +1 -1
- package/build/server/chunks/chunks/{utils.js-CWPwVcP0.js → utils.js-GaDYRoLP.js} +3 -3
- package/build/server/chunks/chunks/{utils.js-CWPwVcP0.js.map → utils.js-GaDYRoLP.js.map} +1 -1
- package/build/server/chunks/{handler-lAULIciq.js → handler-DegMVxhC.js} +5 -5
- package/build/server/chunks/{handler-lAULIciq.js.map → handler-DegMVxhC.js.map} +1 -1
- package/build/server/chunks/{index.js-BZzPujOC.js → index.js-BrCnc8Gq.js} +5 -5
- package/build/server/chunks/{index.js-BZzPujOC.js.map → index.js-BrCnc8Gq.js.map} +1 -1
- package/build/server/chunks/{manifest.js-B45gle3C.js → manifest.js-mKxXvxuB.js} +7 -7
- package/build/server/chunks/{manifest.js-B45gle3C.js.map → manifest.js-mKxXvxuB.js.map} +1 -1
- package/build/server/chunks/nodes/{0.js-CeflwKeh.js → 0.js-Bnw_8E4c.js} +2 -2
- package/build/server/chunks/nodes/{0.js-CeflwKeh.js.map → 0.js-Bnw_8E4c.js.map} +1 -1
- package/build/server/chunks/nodes/1.js-DpvNtg1z.js +7 -0
- package/build/server/chunks/nodes/{1.js-BokZtd9m.js.map → 1.js-DpvNtg1z.js.map} +1 -1
- package/build/server/chunks/nodes/{2.js-DDXiE5Ra.js → 2.js-h7fLpCKT.js} +2 -2
- package/build/server/chunks/nodes/{2.js-DDXiE5Ra.js.map → 2.js-h7fLpCKT.js.map} +1 -1
- package/build/server/chunks/nodes/3.js-XlL1_Sjt.js +7 -0
- package/build/server/chunks/nodes/{3.js-BZ_k58Ty.js.map → 3.js-XlL1_Sjt.js.map} +1 -1
- package/build/server/chunks/nodes/{4.js-C4Z183Ra.js → 4.js-BRkm0s7e.js} +2 -2
- package/build/server/chunks/nodes/{4.js-C4Z183Ra.js.map → 4.js-BRkm0s7e.js.map} +1 -1
- package/dist/core/manifest-entry.d.ts +6 -7
- package/dist/runtime/canvas.d.ts +37 -0
- package/dist/runtime/canvas.js +71 -0
- package/dist/runtime/components/Slice.svelte +12 -23
- package/dist/runtime/geometry.d.ts +61 -33
- package/dist/runtime/geometry.js +54 -38
- package/dist/runtime/index.d.ts +6 -4
- package/dist/runtime/index.js +3 -3
- package/dist/runtime/kind-handlers.d.ts +5 -30
- package/dist/runtime/kind-handlers.js +16 -31
- package/dist/runtime/manifest.d.ts +13 -15
- package/dist/runtime/manifest.js +23 -23
- package/dist/runtime/preload-registry.d.ts +7 -26
- package/dist/runtime/preload-registry.js +27 -8
- package/dist/runtime/retained-entry.d.ts +48 -0
- package/dist/runtime/retained-entry.js +61 -0
- package/dist/runtime/slice-source-lifecycle.js +2 -2
- package/package.json +1 -1
- package/build/client/_app/immutable/chunks/BrWiKjrr.js.br +0 -0
- package/build/client/_app/immutable/chunks/BrWiKjrr.js.gz +0 -0
- package/build/client/_app/immutable/chunks/CK_6irtk.js +0 -1
- package/build/client/_app/immutable/chunks/CK_6irtk.js.br +0 -0
- package/build/client/_app/immutable/chunks/CK_6irtk.js.gz +0 -0
- package/build/client/_app/immutable/chunks/D68C0q2O.js.br +0 -0
- package/build/client/_app/immutable/chunks/D68C0q2O.js.gz +0 -0
- package/build/client/_app/immutable/entry/app.C6utCKyq.js.br +0 -0
- package/build/client/_app/immutable/entry/app.C6utCKyq.js.gz +0 -0
- package/build/client/_app/immutable/entry/start.BaAqfP-b.js +0 -1
- package/build/client/_app/immutable/entry/start.BaAqfP-b.js.br +0 -0
- package/build/client/_app/immutable/entry/start.BaAqfP-b.js.gz +0 -0
- package/build/client/_app/immutable/nodes/0.D3X2aDLe.js.br +0 -0
- package/build/client/_app/immutable/nodes/0.D3X2aDLe.js.gz +0 -0
- package/build/client/_app/immutable/nodes/1.CoCDztxo.js.br +0 -1
- package/build/client/_app/immutable/nodes/1.CoCDztxo.js.gz +0 -0
- package/build/client/_app/immutable/nodes/2.D6Bsk2OP.js.br +0 -0
- package/build/client/_app/immutable/nodes/2.D6Bsk2OP.js.gz +0 -0
- package/build/client/_app/immutable/nodes/3.ZFuSk2Qg.js.br +0 -0
- package/build/client/_app/immutable/nodes/3.ZFuSk2Qg.js.gz +0 -0
- package/build/client/_app/immutable/nodes/4.CGsGyx14.js.br +0 -0
- package/build/server/chunks/nodes/1.js-BokZtd9m.js +0 -7
- package/build/server/chunks/nodes/3.js-BZ_k58Ty.js +0 -7
package/dist/runtime/geometry.js
CHANGED
|
@@ -145,6 +145,9 @@ export function computeCanvasPaintLayout(entry, boxWidth, boxHeight, renderRatio
|
|
|
145
145
|
originY: overdrawDestination.top * renderRatio
|
|
146
146
|
};
|
|
147
147
|
}
|
|
148
|
+
function mapEdges(edges, transform) {
|
|
149
|
+
return [transform(edges[0]), transform(edges[1]), transform(edges[2]), transform(edges[3])];
|
|
150
|
+
}
|
|
148
151
|
/** Cumulative, rounded edges so adjacent cells abut exactly on integer pixels. */
|
|
149
152
|
function cumulativeEdges(sizes) {
|
|
150
153
|
let accumulated = 0;
|
|
@@ -156,15 +159,14 @@ function cumulativeEdges(sizes) {
|
|
|
156
159
|
return edges;
|
|
157
160
|
}
|
|
158
161
|
/**
|
|
159
|
-
*
|
|
160
|
-
*
|
|
161
|
-
*
|
|
162
|
-
*
|
|
162
|
+
* The grid for a slice filling an integer `boxWidth × boxHeight` box with its
|
|
163
|
+
* core at the origin: destination edges are integers, so adjacent cells abut
|
|
164
|
+
* exactly. With overdraw the grid covers the box expanded by
|
|
165
|
+
* `computeOverdrawDestination` on each side, so the first edges are negative
|
|
166
|
+
* and the last extend past the box.
|
|
163
167
|
*/
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
return [];
|
|
167
|
-
// Render into a virtual box expanded by the overdraw, then shift every cell
|
|
168
|
+
function integerSliceGrid(entry, boxWidth, boxHeight) {
|
|
169
|
+
// Render into a virtual box expanded by the overdraw, then shift every edge
|
|
168
170
|
// back so the element core lands exactly in the layout box.
|
|
169
171
|
const overdrawDestination = computeOverdrawDestination(entry, boxWidth, boxHeight);
|
|
170
172
|
const paintWidth = boxWidth + overdrawDestination.left + overdrawDestination.right;
|
|
@@ -209,37 +211,51 @@ export function computeRenderCells(entry, boxWidth, boxHeight) {
|
|
|
209
211
|
bottomDestination = bottom / pixelRatio;
|
|
210
212
|
}
|
|
211
213
|
const centerDestinationHeight = Math.max(0, paintHeight - topDestination - bottomDestination);
|
|
212
|
-
const sourceColumns = [left, centerWidth, right];
|
|
213
|
-
const sourceRows = [top, centerHeight, bottom];
|
|
214
|
-
const sourceColumnStarts = [0, left, left + centerWidth];
|
|
215
|
-
const sourceRowStarts = [0, top, top + centerHeight];
|
|
216
214
|
const columnEdges = cumulativeEdges([leftDestination, centerDestinationWidth, rightDestination]);
|
|
217
215
|
const rowEdges = cumulativeEdges([topDestination, centerDestinationHeight, bottomDestination]);
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
216
|
+
return {
|
|
217
|
+
sourceX: [0, left, left + centerWidth, left + centerWidth + right],
|
|
218
|
+
sourceY: [0, top, top + centerHeight, top + centerHeight + bottom],
|
|
219
|
+
destX: mapEdges(columnEdges, (edge) => edge - overdrawDestination.left),
|
|
220
|
+
destY: mapEdges(rowEdges, (edge) => edge - overdrawDestination.top)
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* The renderer-agnostic paint grid for a slice whose core fills the box
|
|
225
|
+
* `(x, y, width, height)`, in any unit the caller draws in — pass device
|
|
226
|
+
* pixels for seam-free edges on a GPU. This is exactly the geometry `<Slice>`
|
|
227
|
+
* and `drawSlice` paint: the box is laid out on the integer grid
|
|
228
|
+
* `max(1, round(size))` per axis, then scaled back to the requested size and
|
|
229
|
+
* offset by `(x, y)`. Paint cells by the rule documented on `SliceGrid`.
|
|
230
|
+
*
|
|
231
|
+
* Baked-in overdraw paints outside the core box, so `destX[0]` / `destY[0]`
|
|
232
|
+
* may be smaller than `x` / `y` and the last edges may exceed `x + width` /
|
|
233
|
+
* `y + height`. The last edges also exceed the box when it is smaller than
|
|
234
|
+
* the fixed borders: the center clamps to zero and the borders keep their
|
|
235
|
+
* size.
|
|
236
|
+
*
|
|
237
|
+
* A non-positive `width` (or `height`) yields destination edges that all
|
|
238
|
+
* equal `x` (or `y`), so nothing is painted.
|
|
239
|
+
*
|
|
240
|
+
* @throws {RangeError} When any coordinate or dimension is not finite.
|
|
241
|
+
*/
|
|
242
|
+
export function sliceGrid(entry, x, y, width, height) {
|
|
243
|
+
if (![x, y, width, height].every(Number.isFinite)) {
|
|
244
|
+
throw new RangeError('Slice grid coordinates and dimensions must be finite.');
|
|
243
245
|
}
|
|
244
|
-
|
|
246
|
+
const renderWidth = width > 0 ? Math.max(1, Math.round(width)) : 0;
|
|
247
|
+
const renderHeight = height > 0 ? Math.max(1, Math.round(height)) : 0;
|
|
248
|
+
const grid = integerSliceGrid(entry, renderWidth, renderHeight);
|
|
249
|
+
const place = (edges, origin, size, renderSize) => {
|
|
250
|
+
if (renderSize === 0)
|
|
251
|
+
return [origin, origin, origin, origin];
|
|
252
|
+
const scale = size / renderSize;
|
|
253
|
+
return mapEdges(edges, (edge) => origin + edge * scale);
|
|
254
|
+
};
|
|
255
|
+
return {
|
|
256
|
+
sourceX: grid.sourceX,
|
|
257
|
+
sourceY: grid.sourceY,
|
|
258
|
+
destX: place(grid.destX, x, width, renderWidth),
|
|
259
|
+
destY: place(grid.destY, y, height, renderHeight)
|
|
260
|
+
};
|
|
245
261
|
}
|
package/dist/runtime/index.d.ts
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
export { Manifest, createManifest } from './manifest.js';
|
|
2
|
+
export { drawImage, drawSlice } from './canvas.js';
|
|
3
|
+
export type { CanvasDrawTarget } from './canvas.js';
|
|
4
|
+
export { retainEntry } from './retained-entry.js';
|
|
5
|
+
export type { RetainedEntry, RetainedSourceOf } from './retained-entry.js';
|
|
2
6
|
export { default as Image } from './components/Image.svelte';
|
|
3
7
|
export { default as Slice } from './components/Slice.svelte';
|
|
4
|
-
export { sliceLayoutGeometry } from './geometry.js';
|
|
5
|
-
export type { SliceLayoutGeometry, SliceLayoutEntry } from './geometry.js';
|
|
8
|
+
export { sliceGrid, sliceLayoutGeometry } from './geometry.js';
|
|
9
|
+
export type { SliceGrid, SliceLayoutGeometry, SliceLayoutEntry } from './geometry.js';
|
|
6
10
|
export { default as Video } from './components/Video.svelte';
|
|
7
11
|
export { default as ChromaKeyVideo } from './components/ChromaKeyVideo.svelte';
|
|
8
|
-
export { resolveRetainedSource } from './preload-registry.js';
|
|
9
|
-
export { audioSourceOf } from './kind-handlers.js';
|
|
10
12
|
export { setFundusLogger } from './logging.js';
|
|
11
13
|
export type { FundusLogger, RenderedSource } from './logging.js';
|
|
12
14
|
export type { SourceStatus } from './preload-registry.js';
|
package/dist/runtime/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
// Supported host-facing manifest and rendering APIs for the `fundus` root export.
|
|
2
2
|
export { Manifest, createManifest } from './manifest.js';
|
|
3
|
+
export { drawImage, drawSlice } from './canvas.js';
|
|
4
|
+
export { retainEntry } from './retained-entry.js';
|
|
3
5
|
export { default as Image } from './components/Image.svelte';
|
|
4
6
|
export { default as Slice } from './components/Slice.svelte';
|
|
5
|
-
export { sliceLayoutGeometry } from './geometry.js';
|
|
7
|
+
export { sliceGrid, sliceLayoutGeometry } from './geometry.js';
|
|
6
8
|
export { default as Video } from './components/Video.svelte';
|
|
7
9
|
export { default as ChromaKeyVideo } from './components/ChromaKeyVideo.svelte';
|
|
8
|
-
export { resolveRetainedSource } from './preload-registry.js';
|
|
9
|
-
export { audioSourceOf } from './kind-handlers.js';
|
|
10
10
|
export { setFundusLogger } from './logging.js';
|
|
11
11
|
export { DEFAULT_CHROMA_KEY_PARAMETERS } from '../core/index.js';
|
|
@@ -1,25 +1,7 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The runtime part of the type-plugin contract: per-kind preload
|
|
3
|
-
* strategy and its inverse release strategy. Each first-party kind registers
|
|
4
|
-
* a handler that knows how its delivered file is loaded — images fetch +
|
|
5
|
-
* optionally decode, slices fetch and retain a decoded form (an ImageBitmap
|
|
6
|
-
* for multi-cell slices, the decoded element for one-slices), media kinds
|
|
7
|
-
* (video/audio) fetch fully and mint an object URL — and how what it loaded
|
|
8
|
-
* is disposed (revoke the object URL, close the bitmap; images have nothing
|
|
9
|
-
* to dispose). An unknown kind falls back to a plain full fetch: no decode,
|
|
10
|
-
* no retention.
|
|
11
|
-
*
|
|
12
|
-
* Handlers are pure strategy: refcounting, cross-manifest dedup, and
|
|
13
|
-
* in-flight bookkeeping live in the module-wide registry
|
|
14
|
-
* (preload-registry.ts); kind-agnostic orchestration lives in `Manifest`.
|
|
15
|
-
*/
|
|
16
|
-
import type { AudioManifestEntry, PreloadOptions } from '../core/index.js';
|
|
17
|
-
/** The playable URL hosts feed to their own audio engine. */
|
|
18
|
-
export declare function audioSourceOf(entry: AudioManifestEntry): string;
|
|
19
1
|
/**
|
|
20
2
|
* What a handler's `preload` acquired and its `release` must dispose. For
|
|
21
3
|
* retained media kinds this is the minted object URL, for slices the decoded
|
|
22
|
-
* bitmap;
|
|
4
|
+
* bitmap; image elements and unknown kinds hold no disposable resources.
|
|
23
5
|
*/
|
|
24
6
|
export interface KindHandlerResources {
|
|
25
7
|
/** Object URL minted over the fully fetched file (retained media kinds). */
|
|
@@ -27,9 +9,9 @@ export interface KindHandlerResources {
|
|
|
27
9
|
/** Decoded pixels of a multi-cell slice source, pinned against cache eviction. */
|
|
28
10
|
imageBitmap?: ImageBitmap;
|
|
29
11
|
/**
|
|
30
|
-
* Decoded
|
|
31
|
-
* `SliceSourceLifecycle
|
|
32
|
-
*
|
|
12
|
+
* Decoded image or one-slice source, reused for canvas drawing and by
|
|
13
|
+
* `SliceSourceLifecycle`. Its decode also warms the browser image cache.
|
|
14
|
+
* Nothing to dispose; release drops the registry's reference.
|
|
33
15
|
*/
|
|
34
16
|
image?: HTMLImageElement;
|
|
35
17
|
}
|
|
@@ -42,7 +24,7 @@ export interface KindRuntimeHandler {
|
|
|
42
24
|
* and return the resources `release` disposes. `entry` is the manifest
|
|
43
25
|
* entry the source came from, for strategies keyed on entry metadata.
|
|
44
26
|
*/
|
|
45
|
-
preload(url: string, byteSize: number,
|
|
27
|
+
preload(url: string, byteSize: number, entry?: Record<string, unknown>): Promise<KindHandlerResources>;
|
|
46
28
|
/**
|
|
47
29
|
* The inverse of `preload`: dispose the resources it returned (revoke the
|
|
48
30
|
* object URL, close the bitmap). Omitted = the kind acquires nothing
|
|
@@ -51,12 +33,5 @@ export interface KindRuntimeHandler {
|
|
|
51
33
|
release?(resources: KindHandlerResources): void;
|
|
52
34
|
}
|
|
53
35
|
export declare const RETAINED_KINDS: ReadonlySet<string>;
|
|
54
|
-
/**
|
|
55
|
-
* Whether a source of `kind` decodes under the given per-kind preload options
|
|
56
|
-
* (options are namespaced per plugin type, like `plugins.<type>` in
|
|
57
|
-
* the config). Image-backed kinds decode by default; each kind opts out
|
|
58
|
-
* independently (`{ image: { decode: false } }` leaves slices decoding).
|
|
59
|
-
*/
|
|
60
|
-
export declare function shouldDecode(kind: string, options: PreloadOptions): boolean;
|
|
61
36
|
/** The runtime handler owning `kind`'s preload/release strategy (fallback for unknown kinds). */
|
|
62
37
|
export declare function runtimeHandlerFor(kind: string): KindRuntimeHandler;
|
|
@@ -1,11 +1,4 @@
|
|
|
1
1
|
import { plugins } from '../core/plugins/registry.js';
|
|
2
|
-
import { resolveRetainedSource } from './preload-registry.js';
|
|
3
|
-
/** The playable URL hosts feed to their own audio engine. */
|
|
4
|
-
export function audioSourceOf(entry) {
|
|
5
|
-
return resolveRetainedSource(entry.src);
|
|
6
|
-
}
|
|
7
|
-
/** Entry kinds whose delivered file is an image the browser can pre-decode. */
|
|
8
|
-
const IMAGE_BACKED_KINDS = new Set(['image', 'slice']);
|
|
9
2
|
/**
|
|
10
3
|
* Entry kinds whose delivered file is retained as an object URL by
|
|
11
4
|
* `preload()` while held. A chroma-key-video's own `src` is a video file, so
|
|
@@ -18,19 +11,6 @@ const RETAINED_KIND_CATALOGUE = new Set([
|
|
|
18
11
|
'audio'
|
|
19
12
|
]);
|
|
20
13
|
export const RETAINED_KINDS = new Set([...RETAINED_KIND_CATALOGUE].filter((kind) => plugins.has(kind)));
|
|
21
|
-
/**
|
|
22
|
-
* Whether a source of `kind` decodes under the given per-kind preload options
|
|
23
|
-
* (options are namespaced per plugin type, like `plugins.<type>` in
|
|
24
|
-
* the config). Image-backed kinds decode by default; each kind opts out
|
|
25
|
-
* independently (`{ image: { decode: false } }` leaves slices decoding).
|
|
26
|
-
*/
|
|
27
|
-
export function shouldDecode(kind, options) {
|
|
28
|
-
if (!IMAGE_BACKED_KINDS.has(kind))
|
|
29
|
-
return false;
|
|
30
|
-
// The image-backed check above narrows the kind — only these carry `decode`.
|
|
31
|
-
const kindOptions = options[kind];
|
|
32
|
-
return kindOptions?.decode ?? true;
|
|
33
|
-
}
|
|
34
14
|
/**
|
|
35
15
|
* Whether a fetch response represents a successfully delivered resource.
|
|
36
16
|
*
|
|
@@ -66,23 +46,24 @@ function verifyByteSize(url, expected, actual) {
|
|
|
66
46
|
}
|
|
67
47
|
}
|
|
68
48
|
/**
|
|
69
|
-
* The image strategy: fetch the full file, then decode
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
49
|
+
* The image strategy: fetch the full file, then decode it via an
|
|
50
|
+
* HTMLImageElement where a DOM exists (the browser serves the bytes from
|
|
51
|
+
* cache); headless environments (Node tests, SSR) skip decoding.
|
|
52
|
+
* Retains the decoded element for canvas drawing until the last holder releases.
|
|
53
|
+
* Nothing to explicitly dispose — release drops the registry's reference.
|
|
74
54
|
*/
|
|
75
55
|
const imageHandler = {
|
|
76
|
-
async preload(url, byteSize
|
|
56
|
+
async preload(url, byteSize) {
|
|
77
57
|
const response = await fetchCompletely(url);
|
|
78
58
|
// Read the body fully so the file is actually transferred (and
|
|
79
59
|
// cached), not just the response headers.
|
|
80
60
|
const bytes = await response.arrayBuffer();
|
|
81
61
|
verifyByteSize(url, byteSize, bytes.byteLength);
|
|
82
|
-
if (
|
|
62
|
+
if (typeof globalThis.Image === 'function') {
|
|
83
63
|
const image = new globalThis.Image();
|
|
84
64
|
image.src = url;
|
|
85
65
|
await image.decode();
|
|
66
|
+
return { image };
|
|
86
67
|
}
|
|
87
68
|
return {};
|
|
88
69
|
}
|
|
@@ -101,17 +82,21 @@ const imageHandler = {
|
|
|
101
82
|
* dispose. Slice sources are small (the insets cover every rendered size),
|
|
102
83
|
* so retention is cheap. Environments without `createImageBitmap` retain the
|
|
103
84
|
* decoded element for every mode.
|
|
85
|
+
*
|
|
86
|
+
* Bitmaps are premultiplied so WebGL uploads match the premultiplied texture a
|
|
87
|
+
* retained element yields with `UNPACK_PREMULTIPLY_ALPHA_WEBGL` (bitmaps ignore
|
|
88
|
+
* that flag); Canvas 2D draws either form identically.
|
|
104
89
|
*/
|
|
105
90
|
const sliceHandler = {
|
|
106
|
-
async preload(url, byteSize,
|
|
91
|
+
async preload(url, byteSize, entry) {
|
|
107
92
|
const response = await fetchCompletely(url);
|
|
108
93
|
const blob = await response.blob();
|
|
109
94
|
verifyByteSize(url, byteSize, blob.size);
|
|
110
|
-
if (!shouldDecode('slice', options))
|
|
111
|
-
return {};
|
|
112
95
|
const oneSlice = entry?.slicing?.mode === 'one';
|
|
113
96
|
if (!oneSlice && typeof globalThis.createImageBitmap === 'function') {
|
|
114
|
-
return {
|
|
97
|
+
return {
|
|
98
|
+
imageBitmap: await globalThis.createImageBitmap(blob, { premultiplyAlpha: 'premultiply' })
|
|
99
|
+
};
|
|
115
100
|
}
|
|
116
101
|
if (typeof globalThis.Image === 'function') {
|
|
117
102
|
const image = new globalThis.Image();
|
|
@@ -23,6 +23,8 @@ export interface EntrySource {
|
|
|
23
23
|
*/
|
|
24
24
|
entry: Record<string, unknown>;
|
|
25
25
|
}
|
|
26
|
+
/** Reject an entry `byteSize` that is not a non-negative safe integer. */
|
|
27
|
+
export declare function assertEntryByteSize(src: string, byteSize: unknown): asserts byteSize is number;
|
|
26
28
|
/**
|
|
27
29
|
* Collect every deliverable source of an entry, including the entries codegen
|
|
28
30
|
* inlined in place of asset references: a source is any nested object carrying
|
|
@@ -38,30 +40,26 @@ export declare class Manifest {
|
|
|
38
40
|
/**
|
|
39
41
|
* Fetch every deliverable file of this manifest up front — including the
|
|
40
42
|
* reference closure, which codegen inlined into the entries — and decode
|
|
41
|
-
* image-backed entries. Options are namespaced per asset
|
|
42
|
-
* `plugins.<type>` in the config
|
|
43
|
-
* skips decoding for image entries only; slices keep decoding unless
|
|
44
|
-
* `slice: { decode: false }` is passed too. An option applies to every
|
|
45
|
-
* entry of its kind, inlined reference entries included.
|
|
43
|
+
* image-backed entries where a DOM exists. Options are namespaced per asset
|
|
44
|
+
* kind, mirroring `plugins.<type>` in the config; no kind defines any yet.
|
|
46
45
|
*
|
|
47
46
|
* Idempotent and concurrent-safe: one in-flight promise per manifest; a
|
|
48
47
|
* second call joins it (or resolves immediately once done), and URLs shared
|
|
49
48
|
* by several entries are fetched once. Sharing goes further than this
|
|
50
49
|
* manifest: every URL is acquired through the module-wide refcounted
|
|
51
|
-
* registry, so a URL another
|
|
52
|
-
* is never fetched again — this manifest
|
|
53
|
-
* preload rolls back this manifest's
|
|
54
|
-
* so it can be retried.
|
|
55
|
-
* per preload run (the first call after construction or `release()`).
|
|
50
|
+
* registry, so a URL another holder (a manifest or a `retainEntry` handle)
|
|
51
|
+
* already loaded (or is loading) is never fetched again — this manifest
|
|
52
|
+
* just takes a hold on it. A failed preload rolls back this manifest's
|
|
53
|
+
* holds and clears the in-flight state so it can be retried.
|
|
56
54
|
*/
|
|
57
55
|
preload(options?: PreloadOptions): Promise<void>;
|
|
58
56
|
/**
|
|
59
57
|
* Drop this manifest's holds on everything it preloaded. Assets shared
|
|
60
|
-
* with another
|
|
61
|
-
* nobody holds anymore are disposed via their
|
|
62
|
-
* (retained media object URLs are revoked, slice
|
|
63
|
-
*
|
|
64
|
-
* network URL.
|
|
58
|
+
* with another holder (a manifest or a `retainEntry` handle) stay fully
|
|
59
|
+
* alive for that holder; assets nobody holds anymore are disposed via their
|
|
60
|
+
* kind's release strategy (retained media object URLs are revoked, slice
|
|
61
|
+
* bitmaps closed, decoded image references dropped). Components fall back
|
|
62
|
+
* to the network URL.
|
|
65
63
|
*
|
|
66
64
|
* Safe in every state: before or without `preload()` it is a no-op, and
|
|
67
65
|
* calling it while a `preload()` is still in flight drops the holds
|
package/dist/runtime/manifest.js
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
import { acquireSource, releaseSource } from './preload-registry.js';
|
|
2
|
+
/** Reject an entry `byteSize` that is not a non-negative safe integer. */
|
|
3
|
+
export function assertEntryByteSize(src, byteSize) {
|
|
4
|
+
if (!Number.isSafeInteger(byteSize) || byteSize < 0) {
|
|
5
|
+
throw new Error(`Manifest entry for "${src}" requires a non-negative safe integer "byteSize".`);
|
|
6
|
+
}
|
|
7
|
+
}
|
|
2
8
|
/**
|
|
3
9
|
* Collect every deliverable source of an entry, including the entries codegen
|
|
4
10
|
* inlined in place of asset references: a source is any nested object carrying
|
|
@@ -23,9 +29,7 @@ export function collectEntrySources(entry) {
|
|
|
23
29
|
if (typeof record.id === 'string' &&
|
|
24
30
|
typeof record.kind === 'string' &&
|
|
25
31
|
typeof record.src === 'string') {
|
|
26
|
-
|
|
27
|
-
throw new Error(`Manifest entry for "${record.src}" requires a non-negative safe integer "byteSize".`);
|
|
28
|
-
}
|
|
32
|
+
assertEntryByteSize(record.src, record.byteSize);
|
|
29
33
|
sources.push({
|
|
30
34
|
id: record.id,
|
|
31
35
|
url: record.src,
|
|
@@ -43,8 +47,8 @@ export class Manifest {
|
|
|
43
47
|
#preloading = null;
|
|
44
48
|
/**
|
|
45
49
|
* URLs this manifest currently holds in the module-wide registry (each
|
|
46
|
-
* held at most once — the registry's reference count counts
|
|
47
|
-
*
|
|
50
|
+
* held at most once — the registry's reference count counts holders, not
|
|
51
|
+
* preload calls). Replaced wholesale by `release()`: an
|
|
48
52
|
* in-flight preload run keeps operating on the set object it captured, so
|
|
49
53
|
* a release during the run (which drains that set) turns the run's own
|
|
50
54
|
* failure rollback into a no-op instead of a double release.
|
|
@@ -64,25 +68,21 @@ export class Manifest {
|
|
|
64
68
|
/**
|
|
65
69
|
* Fetch every deliverable file of this manifest up front — including the
|
|
66
70
|
* reference closure, which codegen inlined into the entries — and decode
|
|
67
|
-
* image-backed entries. Options are namespaced per asset
|
|
68
|
-
* `plugins.<type>` in the config
|
|
69
|
-
* skips decoding for image entries only; slices keep decoding unless
|
|
70
|
-
* `slice: { decode: false }` is passed too. An option applies to every
|
|
71
|
-
* entry of its kind, inlined reference entries included.
|
|
71
|
+
* image-backed entries where a DOM exists. Options are namespaced per asset
|
|
72
|
+
* kind, mirroring `plugins.<type>` in the config; no kind defines any yet.
|
|
72
73
|
*
|
|
73
74
|
* Idempotent and concurrent-safe: one in-flight promise per manifest; a
|
|
74
75
|
* second call joins it (or resolves immediately once done), and URLs shared
|
|
75
76
|
* by several entries are fetched once. Sharing goes further than this
|
|
76
77
|
* manifest: every URL is acquired through the module-wide refcounted
|
|
77
|
-
* registry, so a URL another
|
|
78
|
-
* is never fetched again — this manifest
|
|
79
|
-
* preload rolls back this manifest's
|
|
80
|
-
* so it can be retried.
|
|
81
|
-
* per preload run (the first call after construction or `release()`).
|
|
78
|
+
* registry, so a URL another holder (a manifest or a `retainEntry` handle)
|
|
79
|
+
* already loaded (or is loading) is never fetched again — this manifest
|
|
80
|
+
* just takes a hold on it. A failed preload rolls back this manifest's
|
|
81
|
+
* holds and clears the in-flight state so it can be retried.
|
|
82
82
|
*/
|
|
83
83
|
preload(options = {}) {
|
|
84
84
|
if (!this.#preloading) {
|
|
85
|
-
const tracked = this.#preloadAll(
|
|
85
|
+
const tracked = this.#preloadAll(this.#heldUrls).catch((cause) => {
|
|
86
86
|
// Reset for retry — unless release() or a follow-up preload()
|
|
87
87
|
// already superseded this run while it was in flight.
|
|
88
88
|
if (this.#preloading === tracked)
|
|
@@ -95,11 +95,11 @@ export class Manifest {
|
|
|
95
95
|
}
|
|
96
96
|
/**
|
|
97
97
|
* Drop this manifest's holds on everything it preloaded. Assets shared
|
|
98
|
-
* with another
|
|
99
|
-
* nobody holds anymore are disposed via their
|
|
100
|
-
* (retained media object URLs are revoked, slice
|
|
101
|
-
*
|
|
102
|
-
* network URL.
|
|
98
|
+
* with another holder (a manifest or a `retainEntry` handle) stay fully
|
|
99
|
+
* alive for that holder; assets nobody holds anymore are disposed via their
|
|
100
|
+
* kind's release strategy (retained media object URLs are revoked, slice
|
|
101
|
+
* bitmaps closed, decoded image references dropped). Components fall back
|
|
102
|
+
* to the network URL.
|
|
103
103
|
*
|
|
104
104
|
* Safe in every state: before or without `preload()` it is a no-op, and
|
|
105
105
|
* calling it while a `preload()` is still in flight drops the holds
|
|
@@ -117,7 +117,7 @@ export class Manifest {
|
|
|
117
117
|
// captured it) finds nothing left to release.
|
|
118
118
|
held.clear();
|
|
119
119
|
}
|
|
120
|
-
async #preloadAll(
|
|
120
|
+
async #preloadAll(held) {
|
|
121
121
|
const sourceByUrl = new Map();
|
|
122
122
|
for (const entry of Object.values(this.#entries)) {
|
|
123
123
|
for (const source of collectEntrySources(entry)) {
|
|
@@ -132,7 +132,7 @@ export class Manifest {
|
|
|
132
132
|
const acquisitions = [];
|
|
133
133
|
for (const [url, source] of sourceByUrl) {
|
|
134
134
|
held.add(url);
|
|
135
|
-
acquisitions.push(acquireSource(url, source.kind, source.byteSize,
|
|
135
|
+
acquisitions.push(acquireSource(url, source.kind, source.byteSize, source.entry));
|
|
136
136
|
}
|
|
137
137
|
const outcomes = await Promise.allSettled(acquisitions);
|
|
138
138
|
const failure = outcomes.find((outcome) => outcome.status === 'rejected');
|
|
@@ -1,30 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* URL — reference count, in-flight promise, loaded handler resources.
|
|
4
|
-
*
|
|
5
|
-
* Guarantees:
|
|
6
|
-
* - **Once-per-URL fetches across manifests, all kinds** — a URL in flight or
|
|
7
|
-
* loaded is never fetched again while held, including image and slice.
|
|
8
|
-
* - **The reference count counts holding manifests, not calls** — each
|
|
9
|
-
* `Manifest` acquires a URL at most once per hold (its held-URL set guards
|
|
10
|
-
* idempotent `preload()` calls).
|
|
11
|
-
* - **Release at zero disposes via the kind handler** — the object URL is
|
|
12
|
-
* revoked and the entry cleared, so releasing one of two holders keeps the
|
|
13
|
-
* resource fully alive for the other, and a released URL can be re-acquired
|
|
14
|
-
* with a fresh fetch.
|
|
15
|
-
* - **No leak on release-during-in-flight** — dropping the last hold while
|
|
16
|
-
* the fetch is still running lets the acquisition complete, then disposes
|
|
17
|
-
* immediately (no cancellation, no throw).
|
|
18
|
-
*/
|
|
19
|
-
import type { PreloadOptions } from '../core/index.js';
|
|
20
|
-
/**
|
|
21
|
-
* Acquire one hold on `url` for a manifest: increments the reference count
|
|
2
|
+
* Acquire one hold on `url` for a holder: increments the reference count
|
|
22
3
|
* immediately, then loads via the kind handler unless the URL is already
|
|
23
4
|
* loaded (resolves at once) or in flight (joins the pending load). A failed
|
|
24
5
|
* load clears the in-flight state so the URL stays retryable; the caller
|
|
25
6
|
* rolls back its own hold via `releaseSource`.
|
|
26
7
|
*/
|
|
27
|
-
export declare function acquireSource(url: string, kind: string, byteSize: number,
|
|
8
|
+
export declare function acquireSource(url: string, kind: string, byteSize: number, sourceEntry?: Record<string, unknown>): Promise<void>;
|
|
28
9
|
/**
|
|
29
10
|
* Drop one hold on `url`. At zero holds the kind handler's release strategy
|
|
30
11
|
* runs (object URL revoked) and the entry is cleared — unless the load is
|
|
@@ -33,19 +14,19 @@ export declare function acquireSource(url: string, kind: string, byteSize: numbe
|
|
|
33
14
|
*/
|
|
34
15
|
export declare function releaseSource(url: string): void;
|
|
35
16
|
/**
|
|
36
|
-
* The retained object URL of a source while some
|
|
17
|
+
* The retained object URL of a source while some holder holds it, or the
|
|
37
18
|
* source itself when not (or no longer) preloaded — after a full release a
|
|
38
19
|
* consumer falls back to the network URL and simply streams.
|
|
39
20
|
*/
|
|
40
21
|
export declare function resolveRetainedSource(url: string): string;
|
|
41
22
|
/**
|
|
42
|
-
* The retained decoded source of
|
|
43
|
-
*
|
|
44
|
-
*
|
|
23
|
+
* The retained decoded source of an image or slice URL while a holder holds
|
|
24
|
+
* it — a multi-cell slice bitmap or decoded image element — or `null` when
|
|
25
|
+
* not (or no longer) preloaded; the consumer then
|
|
45
26
|
* falls back to an async image load. Never returns a closed bitmap: closing
|
|
46
27
|
* happens only when the entry is dropped from the registry.
|
|
47
28
|
*/
|
|
48
|
-
export declare function
|
|
29
|
+
export declare function resolveRetainedImageSource(url: string): ImageBitmap | HTMLImageElement | null;
|
|
49
30
|
/**
|
|
50
31
|
* A source's preload state, for render-time diagnostics:
|
|
51
32
|
* - `loaded` — fully preloaded and held (every kind resolves non-null
|
|
@@ -1,13 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The module-wide refcounted source registry: one entry per
|
|
3
|
+
* URL — reference count, in-flight promise, loaded handler resources.
|
|
4
|
+
*
|
|
5
|
+
* Guarantees:
|
|
6
|
+
* - **Once-per-URL fetches across holders, all kinds** — a URL in flight or
|
|
7
|
+
* loaded is never fetched again while held, including image and slice.
|
|
8
|
+
* - **The reference count counts holders, not calls** — a holder is a
|
|
9
|
+
* `Manifest` or a `retainEntry` handle. Each `Manifest` acquires a URL at
|
|
10
|
+
* most once per hold (its held-URL set guards idempotent `preload()` calls);
|
|
11
|
+
* each handle takes exactly one hold and its idempotent `release` drops it.
|
|
12
|
+
* - **Release at zero disposes via the kind handler** — the object URL is
|
|
13
|
+
* revoked and the entry cleared, so releasing one of two holders keeps the
|
|
14
|
+
* resource fully alive for the other, and a released URL can be re-acquired
|
|
15
|
+
* with a fresh fetch.
|
|
16
|
+
* - **No leak on release-during-in-flight** — dropping the last hold while
|
|
17
|
+
* the fetch is still running lets the acquisition complete, then disposes
|
|
18
|
+
* immediately (no cancellation, no throw).
|
|
19
|
+
*/
|
|
1
20
|
import { runtimeHandlerFor } from './kind-handlers.js';
|
|
2
21
|
const registryByUrl = new Map();
|
|
3
22
|
/**
|
|
4
|
-
* Acquire one hold on `url` for a
|
|
23
|
+
* Acquire one hold on `url` for a holder: increments the reference count
|
|
5
24
|
* immediately, then loads via the kind handler unless the URL is already
|
|
6
25
|
* loaded (resolves at once) or in flight (joins the pending load). A failed
|
|
7
26
|
* load clears the in-flight state so the URL stays retryable; the caller
|
|
8
27
|
* rolls back its own hold via `releaseSource`.
|
|
9
28
|
*/
|
|
10
|
-
export function acquireSource(url, kind, byteSize,
|
|
29
|
+
export function acquireSource(url, kind, byteSize, sourceEntry) {
|
|
11
30
|
let entry = registryByUrl.get(url);
|
|
12
31
|
if (!entry) {
|
|
13
32
|
entry = { kind, byteSize, referenceCount: 0, pending: null, resources: null };
|
|
@@ -23,7 +42,7 @@ export function acquireSource(url, kind, byteSize, options, sourceEntry) {
|
|
|
23
42
|
if (!pending) {
|
|
24
43
|
const acquired = entry;
|
|
25
44
|
pending = runtimeHandlerFor(kind)
|
|
26
|
-
.preload(url, byteSize,
|
|
45
|
+
.preload(url, byteSize, sourceEntry)
|
|
27
46
|
.then((resources) => {
|
|
28
47
|
acquired.pending = null;
|
|
29
48
|
if (acquired.referenceCount === 0) {
|
|
@@ -65,7 +84,7 @@ export function releaseSource(url) {
|
|
|
65
84
|
registryByUrl.delete(url);
|
|
66
85
|
}
|
|
67
86
|
/**
|
|
68
|
-
* The retained object URL of a source while some
|
|
87
|
+
* The retained object URL of a source while some holder holds it, or the
|
|
69
88
|
* source itself when not (or no longer) preloaded — after a full release a
|
|
70
89
|
* consumer falls back to the network URL and simply streams.
|
|
71
90
|
*/
|
|
@@ -73,13 +92,13 @@ export function resolveRetainedSource(url) {
|
|
|
73
92
|
return registryByUrl.get(url)?.resources?.objectUrl ?? url;
|
|
74
93
|
}
|
|
75
94
|
/**
|
|
76
|
-
* The retained decoded source of
|
|
77
|
-
*
|
|
78
|
-
*
|
|
95
|
+
* The retained decoded source of an image or slice URL while a holder holds
|
|
96
|
+
* it — a multi-cell slice bitmap or decoded image element — or `null` when
|
|
97
|
+
* not (or no longer) preloaded; the consumer then
|
|
79
98
|
* falls back to an async image load. Never returns a closed bitmap: closing
|
|
80
99
|
* happens only when the entry is dropped from the registry.
|
|
81
100
|
*/
|
|
82
|
-
export function
|
|
101
|
+
export function resolveRetainedImageSource(url) {
|
|
83
102
|
const resources = registryByUrl.get(url)?.resources;
|
|
84
103
|
return resources?.imageBitmap ?? resources?.image ?? null;
|
|
85
104
|
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Handle-based retention of one manifest entry's own source: the renderer-
|
|
3
|
+
* agnostic way to hold a loaded file (Canvas 2D, WebGL, audio engines)
|
|
4
|
+
* without depending on some manifest still being preloaded. Each handle is
|
|
5
|
+
* one holder in the module-wide registry (preload-registry.ts), sharing loads
|
|
6
|
+
* and disposal with manifests.
|
|
7
|
+
*/
|
|
8
|
+
import type { ManifestEntry } from '../core/index.js';
|
|
9
|
+
/**
|
|
10
|
+
* The retained source a `RetainedEntry` exposes for its entry kind: decoded
|
|
11
|
+
* pixels for image-backed kinds, a playable URL (an object URL over the fully
|
|
12
|
+
* fetched file where supported) for media kinds.
|
|
13
|
+
*/
|
|
14
|
+
export type RetainedSourceOf<E extends ManifestEntry> = E extends {
|
|
15
|
+
kind: 'image' | 'slice';
|
|
16
|
+
} ? HTMLImageElement | ImageBitmap : E extends {
|
|
17
|
+
kind: 'video' | 'chroma-key-video' | 'audio';
|
|
18
|
+
} ? string : never;
|
|
19
|
+
/** One hold on an entry's loaded source, from `retainEntry`. */
|
|
20
|
+
export interface RetainedEntry<E extends ManifestEntry> {
|
|
21
|
+
readonly entry: E;
|
|
22
|
+
/**
|
|
23
|
+
* The loaded source. The same object for every handle while any holder
|
|
24
|
+
* keeps the source loaded; a later re-retain may load a new one.
|
|
25
|
+
*
|
|
26
|
+
* @throws {Error} After `release()`.
|
|
27
|
+
*/
|
|
28
|
+
readonly source: RetainedSourceOf<E>;
|
|
29
|
+
/** Drop this handle's hold. Idempotent; safe to call unbound (`onDestroy(handle.release)`). */
|
|
30
|
+
readonly release: () => void;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Take one hold on `entry`'s own source and resolve with a handle to it once
|
|
34
|
+
* loaded. An already loaded source (e.g. after `manifest.preload()`) resolves
|
|
35
|
+
* without loading, a source in flight is joined, and an absent one loads via
|
|
36
|
+
* its kind — deduplicated and cached exactly like `preload()`. Only the
|
|
37
|
+
* entry's own `src` is retained: a chroma-key-video's `mask` and `fallback`
|
|
38
|
+
* are separate entries to retain on their own.
|
|
39
|
+
*
|
|
40
|
+
* The hold lasts until `release()`, even when the source was already
|
|
41
|
+
* preloaded; while any holder keeps it, the source object stays the same.
|
|
42
|
+
* Always release, including when a caller stops waiting for the promise.
|
|
43
|
+
*
|
|
44
|
+
* @throws {Error} (rejects) When the entry's `byteSize` is invalid, the load
|
|
45
|
+
* fails, or an image-backed source cannot be decoded in this environment.
|
|
46
|
+
* A rejection leaves no hold behind.
|
|
47
|
+
*/
|
|
48
|
+
export declare function retainEntry<E extends ManifestEntry>(entry: E): Promise<RetainedEntry<E>>;
|