@neta-art/cohub 12.1.1 → 12.3.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 +85 -8
- package/dist/board/editor/clipboard.js +1 -1
- package/dist/board/editor/creation.js +1 -1
- package/dist/board/editor/document.d.ts +3 -1
- package/dist/board/editor/document.js +8 -6
- package/dist/board/editor/editor.d.ts +8 -3
- package/dist/board/editor/editor.js +8 -2
- package/dist/board/editor/index.d.ts +3 -3
- package/dist/board/editor/index.js +2 -2
- package/dist/board/editor/interaction.d.ts +23 -2
- package/dist/board/editor/interaction.js +93 -4
- package/dist/board/editor/query.d.ts +1 -1
- package/dist/board/editor/query.js +3 -7
- package/dist/board/editor/selection.js +1 -1
- package/dist/board/editor/snapshots.js +5 -57
- package/dist/board/editor/tool.d.ts +4 -1
- package/dist/board/editor/tool.js +6 -1
- package/dist/board/export/index.d.ts +2 -2
- package/dist/board/export/index.js +1 -1
- package/dist/board/export/node/fonts.js +4 -0
- package/dist/board/export/node/index.d.ts +2 -0
- package/dist/board/export/node/index.js +8 -1
- package/dist/board/export/node/scripts.js +1 -1
- package/dist/board/export/scene.d.ts +2 -2
- package/dist/board/export/scene.js +2 -2
- package/dist/board/model/asset-key.d.ts +24 -0
- package/dist/board/model/asset-key.js +104 -0
- package/dist/board/model/export-assets.d.ts +7 -3
- package/dist/board/model/export-assets.js +34 -13
- package/dist/board/model/index.d.ts +9 -7
- package/dist/board/model/index.js +22 -20
- package/dist/board/model/items/create.js +3 -3
- package/dist/board/model/items/definition.d.ts +9 -1
- package/dist/board/model/items/index.d.ts +2 -2
- package/dist/board/model/items/registry.d.ts +2 -1
- package/dist/board/model/items/registry.js +1 -0
- package/dist/board/model/media-sizes.d.ts +28 -0
- package/dist/board/model/media-sizes.js +64 -0
- package/dist/board/model/palette.d.ts +1 -0
- package/dist/board/model/palette.js +20 -130
- package/dist/board/model/sampling.js +1 -1
- package/dist/board/model/text-metrics.d.ts +28 -3
- package/dist/board/model/text-metrics.js +36 -2
- package/dist/board/model/themes.d.ts +495 -0
- package/dist/board/model/themes.js +500 -0
- package/dist/board/render/card-tag.js +2 -2
- package/dist/board/render/index.d.ts +11 -7
- package/dist/board/render/index.js +9 -5
- package/dist/board/render/labels.d.ts +15 -0
- package/dist/board/render/labels.js +14 -0
- package/dist/board/render/lod.d.ts +30 -0
- package/dist/board/render/lod.js +33 -0
- package/dist/board/render/media-interaction.d.ts +5 -2
- package/dist/board/render/media-interaction.js +15 -3
- package/dist/board/render/palette.js +2 -25
- package/dist/board/render/renderers/arrow-card-renderer.js +7 -5
- package/dist/board/render/renderers/audio-card-renderer.js +3 -2
- package/dist/board/render/renderers/board-renderer-registry.d.ts +22 -1
- package/dist/board/render/renderers/board-renderer-registry.js +9 -4
- package/dist/board/render/renderers/custom-card-renderer.js +5 -5
- package/dist/board/render/renderers/draw-card-renderer.js +1 -1
- package/dist/board/render/renderers/far-plate.d.ts +14 -1
- package/dist/board/render/renderers/far-plate.js +31 -8
- package/dist/board/render/renderers/file-card-renderer.js +49 -31
- package/dist/board/render/renderers/frame-card-renderer.js +9 -3
- package/dist/board/render/renderers/image-card-renderer.js +34 -8
- package/dist/board/render/renderers/model-card-renderer.js +6 -6
- package/dist/board/render/renderers/shape-card-renderer.js +25 -24
- package/dist/board/render/renderers/task-card-renderer.js +9 -10
- package/dist/board/render/renderers/text-card-renderer.js +86 -41
- package/dist/board/render/renderers/video-card-renderer.js +5 -4
- package/dist/board/render/script-export.js +1 -1
- package/dist/board/render/script-host.js +3 -3
- package/dist/board/render/style.d.ts +8 -2
- package/dist/board/render/style.js +8 -2
- package/dist/board/render/surface.d.ts +5 -0
- package/dist/board/render/surface.js +13 -3
- package/dist/board/render/text-edit.d.ts +32 -0
- package/dist/board/render/text-edit.js +134 -0
- package/dist/board/render/text-fit.d.ts +1 -3
- package/dist/board/render/text-fit.js +6 -49
- package/dist/board/render/text-measurement.d.ts +25 -1
- package/dist/board/render/text-measurement.js +157 -19
- package/dist/board/render/text-resolution.d.ts +1 -6
- package/dist/board/render/text-resolution.js +1 -10
- package/dist/board/render/theme.d.ts +15 -0
- package/dist/board/render/theme.js +15 -0
- package/dist/board/replica/index.d.ts +4 -2
- package/dist/board/replica/index.js +28 -17
- package/dist/board/replica/indexeddb.d.ts +16 -0
- package/dist/board/replica/indexeddb.js +155 -0
- package/dist/board/stage/assets.d.ts +28 -5
- package/dist/board/stage/assets.js +293 -102
- package/dist/board/stage/bench.d.ts +35 -0
- package/dist/board/stage/bench.js +153 -0
- package/dist/board/stage/device.d.ts +41 -0
- package/dist/board/stage/device.js +62 -0
- package/dist/board/stage/frame-budget.d.ts +19 -0
- package/dist/board/stage/frame-budget.js +69 -0
- package/dist/board/stage/idle.d.ts +4 -0
- package/dist/board/stage/idle.js +26 -0
- package/dist/board/stage/index.d.ts +11 -7
- package/dist/board/stage/index.js +7 -4
- package/dist/board/stage/lru.d.ts +13 -9
- package/dist/board/stage/lru.js +38 -20
- package/dist/board/stage/presses.d.ts +21 -0
- package/dist/board/stage/presses.js +66 -0
- package/dist/board/stage/scene.d.ts +15 -1
- package/dist/board/stage/scene.js +248 -141
- package/dist/board/stage/stage.d.ts +28 -2
- package/dist/board/stage/stage.js +404 -112
- package/dist/board/stage/texture-pool.d.ts +10 -1
- package/dist/board/stage/texture-pool.js +72 -2
- package/dist/chunks/environment.d.ts +6 -0
- package/dist/chunks/http.d.ts +9 -1
- package/dist/chunks/http.js +791 -4
- package/dist/index.d.ts +52 -1
- package/dist/index.js +202 -862
- package/dist/media.d.ts +3 -27
- package/dist/media.js +3 -82
- package/dist/protocol/dist/board-constants.js +3 -3
- package/dist/protocol/dist/board-layout.d.ts +17 -3
- package/dist/protocol/dist/board-layout.js +68 -11
- package/dist/protocol/dist/board-model.d.ts +15 -1
- package/dist/protocol/dist/board-model.js +12 -2
- package/dist/protocol/dist/board-patch.js +1 -1
- package/dist/protocol/dist/board-schema.d.ts +2 -1
- package/dist/protocol/dist/board-schema.js +4 -1
- package/dist/protocol/dist/core/media.d.ts +35 -1
- package/dist/protocol/dist/core/media.js +127 -1
- package/dist/protocol/dist/index.d.ts +3 -3
- package/package.json +4 -2
- package/dist/board/model/image-key.d.ts +0 -9
- package/dist/board/model/image-key.js +0 -33
|
@@ -1,97 +1,53 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { boardModelFormat, isBoardModelItem } from "../model/media.js";
|
|
5
|
-
import { boardImageKeySource, imageAssetKey } from "../model/image-key.js";
|
|
1
|
+
import { probeMediaInfo } from "../../protocol/dist/core/media.js";
|
|
2
|
+
import { MEDIA_VARIANT_SIZES, imageVariantUrl, variantSource } from "../../media.js";
|
|
3
|
+
import { boardModelFormat } from "../model/media.js";
|
|
6
4
|
import { loadVideoThumbnailTexture, videoTextureNaturalSize } from "../render/video-thumbnail.js";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
5
|
+
import { boardAssetKey, boardAssetKeySource, boardAssetRefKey } from "../model/asset-key.js";
|
|
6
|
+
import { planLruTrim } from "./lru.js";
|
|
7
|
+
import { defaultBoardDeviceBudget } from "./device.js";
|
|
8
|
+
import { scheduleBoardIdle } from "./idle.js";
|
|
9
|
+
import { boardImageTexturePool, loadBoardImageTexture, loadFittedImageTexture, readTextureTone, shrinkBoardTexture, unloadBoardImageTexture } from "./texture-pool.js";
|
|
9
10
|
import { Texture } from "pixi.js";
|
|
10
11
|
//#region src/board/stage/assets.ts
|
|
11
|
-
function
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
if (artifact?.type !== "video") return null;
|
|
15
|
-
const frame = videoFrameUrl(artifact.url, 768);
|
|
16
|
-
return frame ? `url:${frame}` : `video-url:${encodeURIComponent(artifact.url)}`;
|
|
17
|
-
}
|
|
18
|
-
const LARGEST_VARIANT = MEDIA_VARIANT_SIZES[MEDIA_VARIANT_SIZES.length - 1];
|
|
19
|
-
function boardTextureEdge(item, scale) {
|
|
20
|
-
const crop = item.type === "image" ? item.props.crop : void 0;
|
|
21
|
-
const pixels = Math.max(item.frame.width / (crop?.w || 1), item.frame.height / (crop?.h || 1)) * scale;
|
|
22
|
-
return pixels > LARGEST_VARIANT ? Number.POSITIVE_INFINITY : mediaVariantSize(pixels);
|
|
23
|
-
}
|
|
24
|
-
function variantUrl(source, edge) {
|
|
25
|
-
if (!(edge <= LARGEST_VARIANT)) return null;
|
|
26
|
-
const url = imageVariantUrl(source, edge);
|
|
12
|
+
function variantUrl(source, edge, cover) {
|
|
13
|
+
if (!(edge <= 1536)) return null;
|
|
14
|
+
const url = imageVariantUrl(source, edge, cover ? "cover" : "contain");
|
|
27
15
|
return url && url !== source ? url : null;
|
|
28
16
|
}
|
|
29
|
-
const
|
|
30
|
-
function
|
|
31
|
-
|
|
32
|
-
if (!source) return null;
|
|
33
|
-
return source.kind === "url" ? `${media}-url:${encodeURIComponent(source.value)}` : `${media}:${encodeURIComponent(source.value)}:${mtimeMs ?? "unknown"}`;
|
|
17
|
+
const SMALLEST_VARIANT = MEDIA_VARIANT_SIZES[0];
|
|
18
|
+
function boundEdge({ width, height }, cover) {
|
|
19
|
+
return cover ? Math.min(width, height) : Math.max(width, height);
|
|
34
20
|
}
|
|
35
|
-
function
|
|
36
|
-
|
|
37
|
-
if (item.type === "video") return stillKey("video", item.props.src, item.props.snapshot?.mtimeMs);
|
|
38
|
-
if (item.type === "file" && isBoardModelItem(item)) return stillKey("model", item.props.src, item.props.snapshot?.mtimeMs);
|
|
39
|
-
return imageAssetKey(item);
|
|
40
|
-
}
|
|
41
|
-
function keySource(key) {
|
|
42
|
-
let baseKey = key;
|
|
43
|
-
if (key.startsWith("local:")) {
|
|
44
|
-
const separator = key.indexOf(":", 6);
|
|
45
|
-
if (separator < 0) return null;
|
|
46
|
-
baseKey = key.slice(separator + 1);
|
|
47
|
-
}
|
|
48
|
-
for (const media of STILL_MEDIA) {
|
|
49
|
-
if (baseKey.startsWith(`${media}-url:`)) try {
|
|
50
|
-
return {
|
|
51
|
-
kind: "url",
|
|
52
|
-
media,
|
|
53
|
-
value: decodeURIComponent(baseKey.slice(media.length + 5))
|
|
54
|
-
};
|
|
55
|
-
} catch {
|
|
56
|
-
return null;
|
|
57
|
-
}
|
|
58
|
-
if (baseKey.startsWith(`${media}:`)) {
|
|
59
|
-
const separator = baseKey.lastIndexOf(":");
|
|
60
|
-
if (separator <= media.length) return null;
|
|
61
|
-
try {
|
|
62
|
-
return {
|
|
63
|
-
kind: "file",
|
|
64
|
-
media,
|
|
65
|
-
value: decodeURIComponent(baseKey.slice(media.length + 1, separator))
|
|
66
|
-
};
|
|
67
|
-
} catch {
|
|
68
|
-
return null;
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
const image = boardImageKeySource(baseKey);
|
|
73
|
-
return image ? {
|
|
74
|
-
...image,
|
|
75
|
-
media: "image"
|
|
76
|
-
} : null;
|
|
21
|
+
function sharpEdge(texture, edge, resized, cover) {
|
|
22
|
+
return resized && boundEdge(texture, cover) >= edge ? edge : Number.POSITIVE_INFINITY;
|
|
77
23
|
}
|
|
78
24
|
const MAX_RETRY_DELAY = 3e4;
|
|
25
|
+
const PREFETCH_CONCURRENCY = 2;
|
|
26
|
+
const PREFETCH_POOL_SHARE = .5;
|
|
27
|
+
const SHRINK_EDGE = 256;
|
|
28
|
+
const SHRINK_CONCURRENCY = 4;
|
|
29
|
+
const TRIM_DELAY = 400;
|
|
30
|
+
const TRIM_MAX_DELAY = 2e3;
|
|
31
|
+
const TONE_LIMIT = 8192;
|
|
79
32
|
function footprintOf(texture) {
|
|
80
33
|
if (!texture) return 0;
|
|
81
34
|
return (texture.width || 0) * (texture.height || 0) * 4;
|
|
82
35
|
}
|
|
83
36
|
function createBoardAssetManager(options) {
|
|
84
|
-
const concurrency = Math.max(1, options.concurrency ??
|
|
37
|
+
const concurrency = Math.max(1, options.concurrency ?? defaultBoardDeviceBudget().concurrency);
|
|
85
38
|
const limits = {
|
|
86
39
|
image: concurrency,
|
|
87
40
|
video: Math.max(1, Math.min(options.videoConcurrency ?? 2, concurrency)),
|
|
88
41
|
model: 1
|
|
89
42
|
};
|
|
90
|
-
const budget = options.lruBudget ??
|
|
43
|
+
const budget = options.lruBudget ?? defaultBoardDeviceBudget().lru;
|
|
44
|
+
const prefetchLimit = Math.min(PREFETCH_CONCURRENCY, concurrency - 1);
|
|
91
45
|
const now = options.now ?? (() => Date.now());
|
|
92
46
|
const imagePool = options.imageTexturePool ?? boardImageTexturePool;
|
|
93
47
|
const useSharedImagePool = !options.loadTexture && !options.unloadTexture;
|
|
94
48
|
const loadTexture = options.loadTexture ?? ((url, media) => media === "video" ? loadVideoThumbnailTexture(url) : useSharedImagePool ? imagePool.acquire(url) : loadBoardImageTexture(url));
|
|
49
|
+
const shrinkTexture = options.shrinkTexture ?? (useSharedImagePool ? shrinkBoardTexture : null);
|
|
50
|
+
const readTone = options.readTone === void 0 ? typeof createImageBitmap === "function" ? readTextureTone : null : options.readTone;
|
|
95
51
|
const pendingUnloads = /* @__PURE__ */ new Map();
|
|
96
52
|
const unloadTexture = options.unloadTexture ?? (async (url, texture, media) => {
|
|
97
53
|
if (media !== "image" || !url) {
|
|
@@ -106,8 +62,7 @@ function createBoardAssetManager(options) {
|
|
|
106
62
|
}
|
|
107
63
|
await unloadBoardImageTexture(url, texture);
|
|
108
64
|
});
|
|
109
|
-
function releaseTexture(
|
|
110
|
-
const media = keySource(key)?.media ?? "image";
|
|
65
|
+
function releaseTexture(media, url, texture) {
|
|
111
66
|
const pendingKey = `${media}:${url}`;
|
|
112
67
|
const promise = unloadTexture(url, texture, media).catch(() => {}).then(() => {
|
|
113
68
|
if (url && pendingUnloads.get(pendingKey) === promise) pendingUnloads.delete(pendingKey);
|
|
@@ -115,9 +70,9 @@ function createBoardAssetManager(options) {
|
|
|
115
70
|
if (url) pendingUnloads.set(pendingKey, promise);
|
|
116
71
|
}
|
|
117
72
|
const retiring = /* @__PURE__ */ new Set();
|
|
118
|
-
function retire(
|
|
73
|
+
function retire(media, url, texture) {
|
|
119
74
|
const release = () => {
|
|
120
|
-
if (retiring.delete(release)) releaseTexture(
|
|
75
|
+
if (retiring.delete(release)) releaseTexture(media, url, texture);
|
|
121
76
|
};
|
|
122
77
|
retiring.add(release);
|
|
123
78
|
if (typeof requestAnimationFrame === "function") requestAnimationFrame(release);
|
|
@@ -137,13 +92,61 @@ function createBoardAssetManager(options) {
|
|
|
137
92
|
model: 0
|
|
138
93
|
};
|
|
139
94
|
let disposed = false;
|
|
140
|
-
|
|
141
|
-
|
|
95
|
+
let focus = null;
|
|
96
|
+
let trimTimer = null;
|
|
97
|
+
let trimSince = 0;
|
|
98
|
+
let shrinking = 0;
|
|
99
|
+
let warm = /* @__PURE__ */ new Set();
|
|
100
|
+
let warming = 0;
|
|
101
|
+
const unwarmable = /* @__PURE__ */ new Set();
|
|
102
|
+
function isWarm(key) {
|
|
103
|
+
return warm.has(key) && (entries.get(key)?.refs ?? 0) <= 0;
|
|
104
|
+
}
|
|
105
|
+
function notify(key, change = "texture") {
|
|
106
|
+
for (const listener of listeners) listener(key, change);
|
|
107
|
+
}
|
|
108
|
+
const tones = /* @__PURE__ */ new Map();
|
|
109
|
+
const toneQueue = /* @__PURE__ */ new Set();
|
|
110
|
+
let cancelTone = null;
|
|
111
|
+
function queueTone(key) {
|
|
112
|
+
if (!readTone || tones.has(key)) return;
|
|
113
|
+
toneQueue.add(key);
|
|
114
|
+
cancelTone ??= scheduleBoardIdle(readNextTone, 0);
|
|
142
115
|
}
|
|
116
|
+
async function readNextTone() {
|
|
117
|
+
const [key] = toneQueue;
|
|
118
|
+
if (key === void 0) {
|
|
119
|
+
cancelTone = null;
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
toneQueue.delete(key);
|
|
123
|
+
const texture = entries.get(key)?.texture;
|
|
124
|
+
const tone = texture && readTone ? await readTone(texture).catch(() => null) : null;
|
|
125
|
+
if (disposed) return;
|
|
126
|
+
if (tone !== null) {
|
|
127
|
+
if (tones.size >= TONE_LIMIT) tones.delete(tones.keys().next().value);
|
|
128
|
+
tones.set(key, tone);
|
|
129
|
+
notify(key, "tone");
|
|
130
|
+
}
|
|
131
|
+
cancelTone = toneQueue.size > 0 ? scheduleBoardIdle(readNextTone, 0) : null;
|
|
132
|
+
}
|
|
133
|
+
const keys = /* @__PURE__ */ new WeakMap();
|
|
134
|
+
let keyEpoch = 0;
|
|
143
135
|
function managedAssetKey(item) {
|
|
144
|
-
const
|
|
136
|
+
const cached = keys.get(item);
|
|
137
|
+
if (cached?.epoch === keyEpoch) return cached.key;
|
|
138
|
+
const key = resolveAssetKey(item);
|
|
139
|
+
keys.set(item, {
|
|
140
|
+
epoch: keyEpoch,
|
|
141
|
+
key
|
|
142
|
+
});
|
|
143
|
+
return key;
|
|
144
|
+
}
|
|
145
|
+
function resolveAssetKey(item) {
|
|
146
|
+
const asset = options.itemAsset?.(item);
|
|
147
|
+
const key = asset === void 0 ? boardAssetKey(item) : asset && boardAssetRefKey(asset);
|
|
145
148
|
if (!key) return null;
|
|
146
|
-
const source =
|
|
149
|
+
const source = boardAssetKeySource(key);
|
|
147
150
|
if (source?.media === "video" && options.loadVideoPreviews === false) return null;
|
|
148
151
|
if (source?.media === "model" && !modelPreviewable(item)) return null;
|
|
149
152
|
if (source?.kind !== "file") return key;
|
|
@@ -159,13 +162,16 @@ function createBoardAssetManager(options) {
|
|
|
159
162
|
function ensureEntry(key) {
|
|
160
163
|
let entry = entries.get(key);
|
|
161
164
|
if (!entry) {
|
|
165
|
+
const source = boardAssetKeySource(key);
|
|
162
166
|
entry = {
|
|
163
|
-
media:
|
|
167
|
+
media: source?.media ?? "image",
|
|
168
|
+
cover: source?.fit === "cover",
|
|
164
169
|
source: null,
|
|
165
170
|
url: null,
|
|
166
171
|
texture: null,
|
|
167
172
|
edge: 0,
|
|
168
173
|
want: 0,
|
|
174
|
+
center: null,
|
|
169
175
|
natural: void 0,
|
|
170
176
|
refs: 0,
|
|
171
177
|
loading: false,
|
|
@@ -195,28 +201,71 @@ function createBoardAssetManager(options) {
|
|
|
195
201
|
function evict(key, entry) {
|
|
196
202
|
clearRetry(entry);
|
|
197
203
|
starters.delete(key);
|
|
198
|
-
if (entry.url || entry.texture) releaseTexture(
|
|
204
|
+
if (entry.url || entry.texture) releaseTexture(entry.media, entry.url ?? "", entry.texture);
|
|
199
205
|
entries.delete(key);
|
|
200
206
|
}
|
|
207
|
+
function scheduleTrim() {
|
|
208
|
+
const at = now();
|
|
209
|
+
if (trimTimer === null) trimSince = at;
|
|
210
|
+
else if (at - trimSince >= TRIM_MAX_DELAY) return;
|
|
211
|
+
else clearTimeout(trimTimer);
|
|
212
|
+
trimTimer = setTimeout(() => {
|
|
213
|
+
trimTimer = null;
|
|
214
|
+
if (!disposed) trim();
|
|
215
|
+
}, TRIM_DELAY);
|
|
216
|
+
}
|
|
217
|
+
function shrunkFootprint({ media, texture }) {
|
|
218
|
+
if (!shrinkTexture || media !== "image" || !texture) return void 0;
|
|
219
|
+
const scale = SHRINK_EDGE / Math.max(texture.width, texture.height);
|
|
220
|
+
return scale < 1 ? Math.round(texture.width * scale) * Math.round(texture.height * scale) * 4 : void 0;
|
|
221
|
+
}
|
|
201
222
|
function trim() {
|
|
202
223
|
const cooling = [];
|
|
203
224
|
for (const [key, entry] of entries) if (entry.refs <= 0 && entry.texture && !entry.loading) cooling.push({
|
|
204
225
|
key,
|
|
205
226
|
lastUsedAt: entry.lastUsedAt,
|
|
206
|
-
bytes: footprintOf(entry.texture)
|
|
227
|
+
bytes: footprintOf(entry.texture),
|
|
228
|
+
shrunkBytes: shrunkFootprint(entry)
|
|
207
229
|
});
|
|
208
|
-
const
|
|
209
|
-
for (const key of
|
|
230
|
+
const plan = planLruTrim(cooling, budget);
|
|
231
|
+
for (const key of plan.evict) {
|
|
210
232
|
const entry = entries.get(key);
|
|
211
233
|
if (entry) evict(key, entry);
|
|
212
234
|
}
|
|
235
|
+
for (const key of plan.shrink.slice(0, Math.max(0, SHRINK_CONCURRENCY - shrinking))) {
|
|
236
|
+
const entry = entries.get(key);
|
|
237
|
+
if (entry) shrink(key, entry);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
function shrink(key, entry) {
|
|
241
|
+
const { texture, url } = entry;
|
|
242
|
+
if (!shrinkTexture || !texture) return;
|
|
243
|
+
entry.loading = true;
|
|
244
|
+
shrinking += 1;
|
|
245
|
+
shrinkTexture(texture, SHRINK_EDGE).catch(() => null).then((small) => {
|
|
246
|
+
shrinking -= 1;
|
|
247
|
+
entry.loading = false;
|
|
248
|
+
const current = entries.get(key) === entry;
|
|
249
|
+
if (small && current && entry.refs <= 0) {
|
|
250
|
+
entry.texture = small;
|
|
251
|
+
entry.url = "";
|
|
252
|
+
entry.edge = SHRINK_EDGE;
|
|
253
|
+
releaseTexture(entry.media, url ?? "", texture);
|
|
254
|
+
} else {
|
|
255
|
+
if (small) releaseTexture(entry.media, "", small);
|
|
256
|
+
if (current && entry.refs > 0) refine(key, entry);
|
|
257
|
+
else if (current) evict(key, entry);
|
|
258
|
+
}
|
|
259
|
+
if (!disposed) trim();
|
|
260
|
+
});
|
|
213
261
|
}
|
|
214
262
|
function adopt(key, entry, loaded, error) {
|
|
215
263
|
entry.loading = false;
|
|
216
264
|
if (entries.get(key) !== entry) {
|
|
217
|
-
if (loaded) releaseTexture(
|
|
265
|
+
if (loaded) releaseTexture(entry.media, loaded.url, loaded.texture);
|
|
218
266
|
return;
|
|
219
267
|
}
|
|
268
|
+
if (warm.delete(key) && !loaded && !entry.texture && entry.refs <= 0) unwarmable.add(key);
|
|
220
269
|
if (loaded) {
|
|
221
270
|
const replaced = entry.texture;
|
|
222
271
|
const replacedUrl = entry.url ?? "";
|
|
@@ -231,9 +280,10 @@ function createBoardAssetManager(options) {
|
|
|
231
280
|
height: texture.height
|
|
232
281
|
};
|
|
233
282
|
entry.lastUsedAt = now();
|
|
234
|
-
if (entry.refs <= 0)
|
|
283
|
+
if (entry.refs <= 0) scheduleTrim();
|
|
235
284
|
else notify(key);
|
|
236
|
-
if (replaced) retire(
|
|
285
|
+
if (replaced) retire(entry.media, replacedUrl, replaced);
|
|
286
|
+
queueTone(key);
|
|
237
287
|
refine(key, entry);
|
|
238
288
|
return;
|
|
239
289
|
}
|
|
@@ -254,11 +304,11 @@ function createBoardAssetManager(options) {
|
|
|
254
304
|
notify(key);
|
|
255
305
|
}
|
|
256
306
|
}
|
|
257
|
-
function refinable(entry) {
|
|
258
|
-
return entry.media === "image" && entry.refs > 0 && entry.texture !== null && entry.source !== null && entry.want > entry.edge;
|
|
307
|
+
function refinable(key, entry) {
|
|
308
|
+
return entry.media === "image" && (entry.refs > 0 || warm.has(key)) && entry.texture !== null && entry.source !== null && entry.want > entry.edge;
|
|
259
309
|
}
|
|
260
310
|
function refine(key, entry) {
|
|
261
|
-
if (refinable(entry) && !entry.loading && entry.source) enqueue(key, "image", textureLoad("image", entry.source));
|
|
311
|
+
if (refinable(key, entry) && !entry.loading && entry.source) enqueue(key, "image", textureLoad("image", entry.source));
|
|
262
312
|
}
|
|
263
313
|
function enqueue(key, media, load) {
|
|
264
314
|
if (queue.some((task) => task.key === key)) return;
|
|
@@ -269,20 +319,43 @@ function createBoardAssetManager(options) {
|
|
|
269
319
|
});
|
|
270
320
|
pump();
|
|
271
321
|
}
|
|
322
|
+
function nextTask() {
|
|
323
|
+
let best = -1;
|
|
324
|
+
let bestWarm = true;
|
|
325
|
+
let nearest = Number.POSITIVE_INFINITY;
|
|
326
|
+
for (let index = 0; index < queue.length; index += 1) {
|
|
327
|
+
const task = queue[index];
|
|
328
|
+
if (activeByMedia[task.media] >= limits[task.media]) continue;
|
|
329
|
+
const prefetching = isWarm(task.key);
|
|
330
|
+
if (prefetching && (!bestWarm || warming >= prefetchLimit)) continue;
|
|
331
|
+
const center = entries.get(task.key)?.center;
|
|
332
|
+
const distance = focus && center ? (center.x - focus.x) ** 2 + (center.y - focus.y) ** 2 : 0;
|
|
333
|
+
if (best < 0 || bestWarm && !prefetching || distance < nearest) {
|
|
334
|
+
nearest = distance;
|
|
335
|
+
best = index;
|
|
336
|
+
bestWarm = prefetching;
|
|
337
|
+
if (!focus && !prefetching) return index;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
return best;
|
|
341
|
+
}
|
|
272
342
|
function pump() {
|
|
273
343
|
while (!disposed && active < concurrency && queue.length > 0) {
|
|
274
|
-
const taskIndex =
|
|
344
|
+
const taskIndex = nextTask();
|
|
275
345
|
if (taskIndex < 0) return;
|
|
276
346
|
const [task] = queue.splice(taskIndex, 1);
|
|
277
347
|
if (!task) continue;
|
|
278
348
|
const entry = entries.get(task.key);
|
|
279
|
-
if (!entry || entry.loading || entry.texture && !refinable(entry)) continue;
|
|
349
|
+
if (!entry || entry.loading || entry.texture && !refinable(task.key, entry)) continue;
|
|
350
|
+
const prefetching = isWarm(task.key);
|
|
280
351
|
entry.loading = true;
|
|
281
352
|
active += 1;
|
|
282
353
|
activeByMedia[task.media] += 1;
|
|
354
|
+
if (prefetching) warming += 1;
|
|
283
355
|
const done = (loaded, error) => {
|
|
284
356
|
active -= 1;
|
|
285
357
|
activeByMedia[task.media] -= 1;
|
|
358
|
+
if (prefetching) warming -= 1;
|
|
286
359
|
adopt(task.key, entry, loaded, error);
|
|
287
360
|
if (!disposed) pump();
|
|
288
361
|
};
|
|
@@ -292,18 +365,39 @@ function createBoardAssetManager(options) {
|
|
|
292
365
|
function textureLoad(media, source) {
|
|
293
366
|
return async (entry) => {
|
|
294
367
|
entry.source = source;
|
|
295
|
-
const edge = entry
|
|
296
|
-
const variant = media === "image" ? variantUrl(source, edge) : null;
|
|
368
|
+
const { cover, want: edge } = entry;
|
|
369
|
+
const variant = media === "image" ? variantUrl(source, Math.max(edge, SMALLEST_VARIANT), cover) : null;
|
|
297
370
|
const url = variant ?? source;
|
|
298
371
|
const pending = pendingUnloads.get(`${media}:${url}`);
|
|
299
372
|
if (pending) await pending;
|
|
300
373
|
if (disposed) return null;
|
|
374
|
+
if (media === "image" && !variant && useSharedImagePool && Number.isFinite(edge)) {
|
|
375
|
+
const fitted = await loadFittedImageTexture(url, edge, cover).catch(() => null);
|
|
376
|
+
if (fitted) return {
|
|
377
|
+
texture: fitted,
|
|
378
|
+
url: "",
|
|
379
|
+
edge: sharpEdge(fitted, edge, true, cover)
|
|
380
|
+
};
|
|
381
|
+
}
|
|
301
382
|
const texture = await loadTexture(url, media);
|
|
302
383
|
if (!texture) return null;
|
|
384
|
+
const bound = boundEdge(texture, cover);
|
|
385
|
+
if (media === "image" && shrinkTexture && edge < bound / 2) {
|
|
386
|
+
const longEdge = Math.round(edge * Math.max(texture.width, texture.height) / bound);
|
|
387
|
+
const small = await shrinkTexture(texture, longEdge).catch(() => null);
|
|
388
|
+
if (small) {
|
|
389
|
+
releaseTexture(media, url, texture);
|
|
390
|
+
return {
|
|
391
|
+
texture: small,
|
|
392
|
+
url: "",
|
|
393
|
+
edge
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
}
|
|
303
397
|
return {
|
|
304
398
|
texture,
|
|
305
399
|
url,
|
|
306
|
-
edge:
|
|
400
|
+
edge: sharpEdge(texture, edge, Boolean(variant), cover)
|
|
307
401
|
};
|
|
308
402
|
};
|
|
309
403
|
}
|
|
@@ -313,7 +407,7 @@ function createBoardAssetManager(options) {
|
|
|
313
407
|
entry.loading = true;
|
|
314
408
|
resolveFileUrl(path, fresh ? { fresh } : void 0).then((url) => {
|
|
315
409
|
if (disposed || entries.get(key) !== entry) return;
|
|
316
|
-
if (entry.refs <= 0) return evict(key, entry);
|
|
410
|
+
if (entry.refs <= 0 && !warm.has(key)) return evict(key, entry);
|
|
317
411
|
if (!url) return adopt(key, entry, null, /* @__PURE__ */ new Error(`Unavailable file: ${path}`));
|
|
318
412
|
entry.loading = false;
|
|
319
413
|
enqueue(key, media, textureLoad(media, url));
|
|
@@ -372,21 +466,29 @@ function createBoardAssetManager(options) {
|
|
|
372
466
|
invalidatePath(path) {
|
|
373
467
|
if (disposed) return;
|
|
374
468
|
const stale = [...entries.keys()].filter((key) => {
|
|
375
|
-
const source =
|
|
469
|
+
const source = boardAssetKeySource(key);
|
|
376
470
|
return source?.kind === "file" && source.value === path;
|
|
377
471
|
});
|
|
378
472
|
if (stale.length === 0) return;
|
|
379
473
|
pathVersions.set(path, (pathVersions.get(path) ?? 0) + 1);
|
|
474
|
+
keyEpoch += 1;
|
|
380
475
|
for (const key of stale) notify(key);
|
|
381
476
|
},
|
|
477
|
+
invalidateItemAssets() {
|
|
478
|
+
if (options.itemAsset) keyEpoch += 1;
|
|
479
|
+
},
|
|
382
480
|
requestItem(item, edge = Number.POSITIVE_INFINITY) {
|
|
383
481
|
if (disposed) return;
|
|
384
482
|
const key = managedAssetKey(item);
|
|
385
483
|
if (!key || failedModels.has(key)) return;
|
|
386
|
-
const source =
|
|
484
|
+
const source = boardAssetKeySource(key);
|
|
387
485
|
if (!source) return;
|
|
388
486
|
const entry = ensureEntry(key);
|
|
389
487
|
entry.lastUsedAt = now();
|
|
488
|
+
entry.center = {
|
|
489
|
+
x: item.frame.x + item.frame.width / 2,
|
|
490
|
+
y: item.frame.y + item.frame.height / 2
|
|
491
|
+
};
|
|
390
492
|
if (edge > entry.want) entry.want = edge;
|
|
391
493
|
if (entry.texture) return refine(key, entry);
|
|
392
494
|
if (entry.loading || entry.retryTimer) return;
|
|
@@ -413,8 +515,16 @@ function createBoardAssetManager(options) {
|
|
|
413
515
|
hasError(key) {
|
|
414
516
|
return entries.get(key)?.error ?? false;
|
|
415
517
|
},
|
|
518
|
+
getTone(key) {
|
|
519
|
+
return tones.get(key) ?? null;
|
|
520
|
+
},
|
|
521
|
+
focus(point) {
|
|
522
|
+
focus = point;
|
|
523
|
+
},
|
|
416
524
|
acquire(key) {
|
|
525
|
+
unwarmable.delete(key);
|
|
417
526
|
const entry = ensureEntry(key);
|
|
527
|
+
if (entry.refs <= 0) entry.want = 0;
|
|
418
528
|
entry.refs += 1;
|
|
419
529
|
entry.lastUsedAt = now();
|
|
420
530
|
},
|
|
@@ -427,7 +537,56 @@ function createBoardAssetManager(options) {
|
|
|
427
537
|
clearRetry(entry);
|
|
428
538
|
if (entry.loading) return;
|
|
429
539
|
if (!entry.texture) evict(key, entry);
|
|
430
|
-
else
|
|
540
|
+
else scheduleTrim();
|
|
541
|
+
},
|
|
542
|
+
prefetch(requests) {
|
|
543
|
+
if (disposed) return;
|
|
544
|
+
const next = /* @__PURE__ */ new Set();
|
|
545
|
+
let room = budget.maxBytes * PREFETCH_POOL_SHARE;
|
|
546
|
+
for (const entry of entries.values()) if (entry.refs <= 0) room -= footprintOf(entry.texture);
|
|
547
|
+
for (const { item, edge } of requests) {
|
|
548
|
+
const key = managedAssetKey(item);
|
|
549
|
+
if (!key || !Number.isFinite(edge) || unwarmable.has(key) || boardAssetKeySource(key)?.media !== "image") continue;
|
|
550
|
+
const entry = entries.get(key);
|
|
551
|
+
const sharp = entry?.texture && entry.edge >= edge;
|
|
552
|
+
if (entry && (entry.refs > 0 || sharp || entry.error || entry.loading && !warm.has(key))) continue;
|
|
553
|
+
room -= edge * edge * 4;
|
|
554
|
+
if (room < 0) break;
|
|
555
|
+
next.add(key);
|
|
556
|
+
}
|
|
557
|
+
for (const key of warm) {
|
|
558
|
+
if (next.has(key)) continue;
|
|
559
|
+
const entry = entries.get(key);
|
|
560
|
+
if (!entry || entry.refs > 0 || entry.loading) continue;
|
|
561
|
+
const index = queue.findIndex((task) => task.key === key);
|
|
562
|
+
if (index >= 0) queue.splice(index, 1);
|
|
563
|
+
if (!entry.texture) evict(key, entry);
|
|
564
|
+
}
|
|
565
|
+
const fresh = new Set([...next].filter((key) => !warm.has(key)));
|
|
566
|
+
warm = next;
|
|
567
|
+
for (const { item, edge } of requests) {
|
|
568
|
+
const key = managedAssetKey(item);
|
|
569
|
+
if (!key || !next.has(key)) continue;
|
|
570
|
+
const entry = ensureEntry(key);
|
|
571
|
+
entry.want = edge;
|
|
572
|
+
entry.center = {
|
|
573
|
+
x: item.frame.x + item.frame.width / 2,
|
|
574
|
+
y: item.frame.y + item.frame.height / 2
|
|
575
|
+
};
|
|
576
|
+
if (!fresh.delete(key)) continue;
|
|
577
|
+
if (entry.texture) {
|
|
578
|
+
refine(key, entry);
|
|
579
|
+
continue;
|
|
580
|
+
}
|
|
581
|
+
const start = starter(item, key, boardAssetKeySource(key));
|
|
582
|
+
if (!start) {
|
|
583
|
+
warm.delete(key);
|
|
584
|
+
evict(key, entry);
|
|
585
|
+
continue;
|
|
586
|
+
}
|
|
587
|
+
starters.set(key, start);
|
|
588
|
+
start(entry);
|
|
589
|
+
}
|
|
431
590
|
},
|
|
432
591
|
async withTextures(items, use, loadOptions) {
|
|
433
592
|
const keys = /* @__PURE__ */ new Set();
|
|
@@ -442,7 +601,7 @@ function createBoardAssetManager(options) {
|
|
|
442
601
|
for (const item of items) manager.requestItem(item, loadOptions?.edge?.(item));
|
|
443
602
|
const settled = () => [...keys].every((key) => {
|
|
444
603
|
const entry = entries.get(key);
|
|
445
|
-
return Boolean(entry?.error) || Boolean(entry?.texture && !refinable(entry));
|
|
604
|
+
return Boolean(entry?.error) || Boolean(entry?.texture && !refinable(key, entry));
|
|
446
605
|
});
|
|
447
606
|
if (!settled()) {
|
|
448
607
|
const timeoutMs = loadOptions?.timeoutMs ?? 15e3;
|
|
@@ -467,6 +626,31 @@ function createBoardAssetManager(options) {
|
|
|
467
626
|
for (const key of keys) manager.release(key);
|
|
468
627
|
}
|
|
469
628
|
},
|
|
629
|
+
stats() {
|
|
630
|
+
let textures = 0;
|
|
631
|
+
let cooling = 0;
|
|
632
|
+
let bytes = 0;
|
|
633
|
+
for (const entry of entries.values()) {
|
|
634
|
+
if (!entry.texture) continue;
|
|
635
|
+
textures += 1;
|
|
636
|
+
if (entry.refs <= 0) cooling += 1;
|
|
637
|
+
bytes += footprintOf(entry.texture);
|
|
638
|
+
}
|
|
639
|
+
let loading = active - warming;
|
|
640
|
+
let prefetching = warming;
|
|
641
|
+
for (const task of queue) {
|
|
642
|
+
if (!entries.has(task.key)) continue;
|
|
643
|
+
if (isWarm(task.key)) prefetching += 1;
|
|
644
|
+
else loading += 1;
|
|
645
|
+
}
|
|
646
|
+
return {
|
|
647
|
+
textures,
|
|
648
|
+
cooling,
|
|
649
|
+
bytes,
|
|
650
|
+
loading,
|
|
651
|
+
prefetching
|
|
652
|
+
};
|
|
653
|
+
},
|
|
470
654
|
subscribe(listener) {
|
|
471
655
|
listeners.add(listener);
|
|
472
656
|
return () => {
|
|
@@ -475,7 +659,14 @@ function createBoardAssetManager(options) {
|
|
|
475
659
|
},
|
|
476
660
|
destroy() {
|
|
477
661
|
disposed = true;
|
|
662
|
+
if (trimTimer !== null) clearTimeout(trimTimer);
|
|
663
|
+
trimTimer = null;
|
|
664
|
+
cancelTone?.();
|
|
665
|
+
toneQueue.clear();
|
|
666
|
+
tones.clear();
|
|
478
667
|
queue.length = 0;
|
|
668
|
+
warm.clear();
|
|
669
|
+
unwarmable.clear();
|
|
479
670
|
listeners.clear();
|
|
480
671
|
pathVersions.clear();
|
|
481
672
|
starters.clear();
|
|
@@ -488,4 +679,4 @@ function createBoardAssetManager(options) {
|
|
|
488
679
|
return manager;
|
|
489
680
|
}
|
|
490
681
|
//#endregion
|
|
491
|
-
export {
|
|
682
|
+
export { createBoardAssetManager };
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { BoardStage } from "./stage.js";
|
|
2
|
+
//#region src/board/stage/bench.d.ts
|
|
3
|
+
type BoardBenchPhase = {
|
|
4
|
+
name: string;
|
|
5
|
+
frames: number;
|
|
6
|
+
wheel: Pick<WheelEventInit, "deltaX" | "deltaY" | "ctrlKey">;
|
|
7
|
+
rest?: number;
|
|
8
|
+
};
|
|
9
|
+
type BoardBenchPercentiles = {
|
|
10
|
+
p50: number;
|
|
11
|
+
p95: number;
|
|
12
|
+
max: number;
|
|
13
|
+
};
|
|
14
|
+
type BoardBenchSettle = {
|
|
15
|
+
shown: number;
|
|
16
|
+
ready: number;
|
|
17
|
+
loaded: number;
|
|
18
|
+
};
|
|
19
|
+
type BoardBenchPhaseReport = BoardBenchSettle & {
|
|
20
|
+
name: string;
|
|
21
|
+
frame: BoardBenchPercentiles;
|
|
22
|
+
work: BoardBenchPercentiles;
|
|
23
|
+
long: number;
|
|
24
|
+
};
|
|
25
|
+
type BoardBenchReport = {
|
|
26
|
+
open: BoardBenchSettle;
|
|
27
|
+
phases: BoardBenchPhaseReport[];
|
|
28
|
+
};
|
|
29
|
+
declare const BOARD_BENCH_PHASES: readonly BoardBenchPhase[];
|
|
30
|
+
declare function runBoardStageBench(stage: BoardStage, { since, phases }?: {
|
|
31
|
+
since?: number;
|
|
32
|
+
phases?: readonly BoardBenchPhase[];
|
|
33
|
+
}): Promise<BoardBenchReport>;
|
|
34
|
+
//#endregion
|
|
35
|
+
export { BOARD_BENCH_PHASES, BoardBenchPercentiles, BoardBenchPhase, BoardBenchPhaseReport, BoardBenchReport, BoardBenchSettle, runBoardStageBench };
|