@neta-art/cohub 11.5.1 → 12.0.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/dist/board/editor/animation.js +2 -1
- package/dist/board/editor/creation.js +1 -1
- package/dist/board/editor/document-edits.d.ts +1 -1
- package/dist/board/editor/document-edits.js +1 -19
- package/dist/board/editor/document.js +2 -1
- package/dist/board/editor/index.d.ts +2 -1
- package/dist/board/editor/index.js +2 -1
- package/dist/board/editor/snapshots.js +11 -13
- package/dist/board/export/index.js +1 -1
- package/dist/board/export/scene.js +1 -1
- package/dist/board/model/client-geometry.d.ts +22 -0
- package/dist/board/model/client-geometry.js +94 -0
- package/dist/board/model/document-share.d.ts +5 -0
- package/dist/board/model/document-share.js +27 -0
- package/dist/board/model/index.d.ts +5 -4
- package/dist/board/model/index.js +4 -3
- package/dist/board/model/items/create.d.ts +5 -1
- package/dist/board/model/items/create.js +29 -58
- package/dist/board/model/items/index.d.ts +2 -2
- package/dist/board/model/items/index.js +2 -2
- package/dist/board/model/items/registry.js +2 -5
- package/dist/board/model/json.d.ts +4 -0
- package/dist/board/model/json.js +22 -0
- package/dist/board/model/media-playback.d.ts +6 -3
- package/dist/board/player/index.d.ts +6 -2
- package/dist/board/player/index.js +33 -15
- package/dist/board/render/index.d.ts +2 -2
- package/dist/board/render/index.js +6 -6
- package/dist/board/render/renderers/arrow-card-renderer.js +5 -1
- package/dist/board/render/renderers/audio-card-renderer.js +4 -1
- package/dist/board/render/renderers/board-renderer-registry.d.ts +4 -2
- package/dist/board/render/renderers/custom-card-renderer.js +5 -2
- package/dist/board/render/renderers/file-card-renderer.js +4 -1
- package/dist/board/render/renderers/frame-card-renderer.js +5 -1
- package/dist/board/render/renderers/image-card-renderer.js +13 -49
- package/dist/board/render/renderers/model-card-renderer.js +4 -1
- package/dist/board/render/renderers/shape-card-renderer.js +5 -1
- package/dist/board/render/renderers/task-card-renderer.js +6 -3
- package/dist/board/render/renderers/text-card-renderer.js +6 -2
- package/dist/board/render/renderers/unknown-card-renderer.js +4 -0
- package/dist/board/render/renderers/video-card-renderer.js +19 -18
- package/dist/board/render/script-host.d.ts +2 -0
- package/dist/board/render/script-host.js +44 -9
- package/dist/board/render/script-resolution.d.ts +4 -0
- package/dist/board/render/script-resolution.js +18 -0
- package/dist/board/render/surface.d.ts +9 -2
- package/dist/board/render/surface.js +15 -1
- package/dist/board/replica/index.js +4 -1
- package/dist/board/stage/assets.d.ts +12 -8
- package/dist/board/stage/assets.js +173 -88
- package/dist/board/stage/index.d.ts +3 -3
- package/dist/board/stage/index.js +3 -3
- package/dist/board/stage/lru.d.ts +3 -2
- package/dist/board/stage/lru.js +8 -4
- package/dist/board/stage/pointer.d.ts +1 -1
- package/dist/board/stage/pointer.js +4 -5
- package/dist/board/stage/scene.d.ts +3 -0
- package/dist/board/stage/scene.js +44 -8
- package/dist/board/stage/stage.d.ts +1 -0
- package/dist/board/stage/stage.js +123 -56
- package/dist/board/stage/views.d.ts +7 -2
- package/dist/board/stage/views.js +50 -21
- package/dist/chunks/environment.d.ts +16 -2
- package/dist/index.js +65 -22
- package/dist/protocol/dist/board-constants.js +56 -1
- package/dist/protocol/dist/board-layout.d.ts +8 -1
- package/dist/protocol/dist/board-layout.js +16 -4
- package/dist/protocol/dist/board-model.js +13 -19
- package/dist/protocol/dist/board-schema.d.ts +52 -0
- package/dist/protocol/dist/board-schema.js +8 -0
- package/dist/protocol/dist/core/media.js +1 -0
- package/dist/protocol/dist/index.d.ts +2 -2
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { MEDIA_VARIANT_SIZES, imageVariantUrl, mediaVariantSize, probeMediaInfo, variantSource, videoFrameUrl } from "../../media.js";
|
|
2
2
|
import { parseBoardMediaSource } from "../../protocol/dist/board-model.js";
|
|
3
|
-
import { featuredTaskArtifact } from "../../protocol/dist/board-task.js";
|
|
3
|
+
import { featuredTaskArtifact, taskArtifactPreviewUrl } from "../../protocol/dist/board-task.js";
|
|
4
4
|
import { boardModelFormat, isBoardModelItem } from "../model/media.js";
|
|
5
5
|
import { boardImageKeySource, imageAssetKey } from "../model/image-key.js";
|
|
6
6
|
import { loadVideoThumbnailTexture, videoTextureNaturalSize } from "../render/video-thumbnail.js";
|
|
@@ -8,12 +8,23 @@ import { DEFAULT_LRU_BUDGET, selectLruEvictions } from "./lru.js";
|
|
|
8
8
|
import { boardImageTexturePool, loadBoardImageTexture, unloadBoardImageTexture } from "./texture-pool.js";
|
|
9
9
|
import { Texture } from "pixi.js";
|
|
10
10
|
//#region src/board/stage/assets.ts
|
|
11
|
-
const TASK_TEXTURE_SIZE = 1536;
|
|
12
11
|
function taskPreviewKey(artifact) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
if (
|
|
16
|
-
|
|
12
|
+
const cover = taskArtifactPreviewUrl(artifact);
|
|
13
|
+
if (cover) return `url:${cover}`;
|
|
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);
|
|
27
|
+
return url && url !== source ? url : null;
|
|
17
28
|
}
|
|
18
29
|
const STILL_MEDIA = ["video", "model"];
|
|
19
30
|
function stillKey(media, src, mtimeMs) {
|
|
@@ -70,7 +81,7 @@ function footprintOf(texture) {
|
|
|
70
81
|
return (texture.width || 0) * (texture.height || 0) * 4;
|
|
71
82
|
}
|
|
72
83
|
function createBoardAssetManager(options) {
|
|
73
|
-
const concurrency = Math.max(1, options.concurrency ??
|
|
84
|
+
const concurrency = Math.max(1, options.concurrency ?? 6);
|
|
74
85
|
const limits = {
|
|
75
86
|
image: concurrency,
|
|
76
87
|
video: Math.max(1, Math.min(options.videoConcurrency ?? 2, concurrency)),
|
|
@@ -103,12 +114,20 @@ function createBoardAssetManager(options) {
|
|
|
103
114
|
});
|
|
104
115
|
if (url) pendingUnloads.set(pendingKey, promise);
|
|
105
116
|
}
|
|
117
|
+
const retiring = /* @__PURE__ */ new Set();
|
|
118
|
+
function retire(key, url, texture) {
|
|
119
|
+
const release = () => {
|
|
120
|
+
if (retiring.delete(release)) releaseTexture(key, url, texture);
|
|
121
|
+
};
|
|
122
|
+
retiring.add(release);
|
|
123
|
+
if (typeof requestAnimationFrame === "function") requestAnimationFrame(release);
|
|
124
|
+
else setTimeout(release, 0);
|
|
125
|
+
}
|
|
106
126
|
const resolveFileUrl = options.resolveFileUrl;
|
|
107
127
|
const entries = /* @__PURE__ */ new Map();
|
|
108
128
|
const pathVersions = /* @__PURE__ */ new Map();
|
|
109
|
-
const
|
|
129
|
+
const starters = /* @__PURE__ */ new Map();
|
|
110
130
|
const failedModels = /* @__PURE__ */ new Set();
|
|
111
|
-
const inflight = /* @__PURE__ */ new Set();
|
|
112
131
|
const queue = [];
|
|
113
132
|
const listeners = /* @__PURE__ */ new Set();
|
|
114
133
|
let active = 0;
|
|
@@ -118,8 +137,8 @@ function createBoardAssetManager(options) {
|
|
|
118
137
|
model: 0
|
|
119
138
|
};
|
|
120
139
|
let disposed = false;
|
|
121
|
-
function notify() {
|
|
122
|
-
for (const listener of listeners) listener();
|
|
140
|
+
function notify(key) {
|
|
141
|
+
for (const listener of listeners) listener(key);
|
|
123
142
|
}
|
|
124
143
|
function managedAssetKey(item) {
|
|
125
144
|
const key = boardAssetKey(item);
|
|
@@ -141,8 +160,13 @@ function createBoardAssetManager(options) {
|
|
|
141
160
|
let entry = entries.get(key);
|
|
142
161
|
if (!entry) {
|
|
143
162
|
entry = {
|
|
163
|
+
media: keySource(key)?.media ?? "image",
|
|
164
|
+
source: null,
|
|
144
165
|
url: null,
|
|
145
166
|
texture: null,
|
|
167
|
+
edge: 0,
|
|
168
|
+
want: 0,
|
|
169
|
+
natural: void 0,
|
|
146
170
|
refs: 0,
|
|
147
171
|
loading: false,
|
|
148
172
|
error: false,
|
|
@@ -162,21 +186,15 @@ function createBoardAssetManager(options) {
|
|
|
162
186
|
entry.retryTimer = null;
|
|
163
187
|
if (disposed || entries.get(key) !== entry) return;
|
|
164
188
|
if (entry.refs <= 0 || entry.texture || entry.loading) return;
|
|
165
|
-
const
|
|
166
|
-
if (!
|
|
189
|
+
const start = starters.get(key);
|
|
190
|
+
if (!start) return;
|
|
167
191
|
entry.error = false;
|
|
168
|
-
|
|
169
|
-
queue.push({
|
|
170
|
-
key,
|
|
171
|
-
load
|
|
172
|
-
});
|
|
173
|
-
pump();
|
|
174
|
-
}
|
|
192
|
+
start(entry);
|
|
175
193
|
}, delay);
|
|
176
194
|
}
|
|
177
195
|
function evict(key, entry) {
|
|
178
196
|
clearRetry(entry);
|
|
179
|
-
|
|
197
|
+
starters.delete(key);
|
|
180
198
|
if (entry.url || entry.texture) releaseTexture(key, entry.url ?? "", entry.texture);
|
|
181
199
|
entries.delete(key);
|
|
182
200
|
}
|
|
@@ -193,69 +211,128 @@ function createBoardAssetManager(options) {
|
|
|
193
211
|
if (entry) evict(key, entry);
|
|
194
212
|
}
|
|
195
213
|
}
|
|
196
|
-
function
|
|
214
|
+
function adopt(key, entry, loaded, error) {
|
|
197
215
|
entry.loading = false;
|
|
198
|
-
inflight.delete(key);
|
|
199
|
-
active -= 1;
|
|
200
|
-
const media = keySource(key)?.media;
|
|
201
|
-
if (media) activeByMedia[media] -= 1;
|
|
202
216
|
if (entries.get(key) !== entry) {
|
|
203
|
-
if (
|
|
204
|
-
if (!disposed) pump();
|
|
217
|
+
if (loaded) releaseTexture(key, loaded.url, loaded.texture);
|
|
205
218
|
return;
|
|
206
219
|
}
|
|
207
|
-
if (
|
|
220
|
+
if (loaded) {
|
|
221
|
+
const replaced = entry.texture;
|
|
222
|
+
const replacedUrl = entry.url ?? "";
|
|
223
|
+
const { texture } = loaded;
|
|
208
224
|
entry.error = false;
|
|
209
225
|
entry.attempts = 0;
|
|
210
226
|
entry.texture = texture;
|
|
227
|
+
entry.url = loaded.url;
|
|
228
|
+
entry.edge = loaded.edge;
|
|
229
|
+
if (loaded.edge === Number.POSITIVE_INFINITY && !variantSource(loaded.url)) entry.natural = videoTextureNaturalSize(texture) ?? {
|
|
230
|
+
width: texture.width,
|
|
231
|
+
height: texture.height
|
|
232
|
+
};
|
|
211
233
|
entry.lastUsedAt = now();
|
|
212
234
|
if (entry.refs <= 0) trim();
|
|
213
|
-
else notify();
|
|
214
|
-
|
|
215
|
-
entry
|
|
216
|
-
|
|
217
|
-
entry.retryAt = now() + Math.min(1e3 * 2 ** entry.attempts, MAX_RETRY_DELAY);
|
|
218
|
-
console.warn(`[board] failed to load preview for ${key}`, error);
|
|
219
|
-
if (media === "model") failedModels.add(key);
|
|
220
|
-
if (entry.refs <= 0) evict(key, entry);
|
|
221
|
-
else {
|
|
222
|
-
if (media !== "model") scheduleRetry(key, entry);
|
|
223
|
-
notify();
|
|
224
|
-
}
|
|
235
|
+
else notify(key);
|
|
236
|
+
if (replaced) retire(key, replacedUrl, replaced);
|
|
237
|
+
refine(key, entry);
|
|
238
|
+
return;
|
|
225
239
|
}
|
|
226
|
-
if (
|
|
240
|
+
if (entry.texture) {
|
|
241
|
+
entry.edge = Number.POSITIVE_INFINITY;
|
|
242
|
+
notify(key);
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
const media = entry.media;
|
|
246
|
+
entry.error = true;
|
|
247
|
+
entry.attempts += 1;
|
|
248
|
+
entry.retryAt = now() + Math.min(1e3 * 2 ** entry.attempts, MAX_RETRY_DELAY);
|
|
249
|
+
console.warn(`[board] failed to load preview for ${key}`, error);
|
|
250
|
+
if (media === "model") failedModels.add(key);
|
|
251
|
+
if (entry.refs <= 0) evict(key, entry);
|
|
252
|
+
else {
|
|
253
|
+
if (media !== "model") scheduleRetry(key, entry);
|
|
254
|
+
notify(key);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
function refinable(entry) {
|
|
258
|
+
return entry.media === "image" && entry.refs > 0 && entry.texture !== null && entry.source !== null && entry.want > entry.edge;
|
|
259
|
+
}
|
|
260
|
+
function refine(key, entry) {
|
|
261
|
+
if (refinable(entry) && !entry.loading && entry.source) enqueue(key, "image", textureLoad("image", entry.source));
|
|
262
|
+
}
|
|
263
|
+
function enqueue(key, media, load) {
|
|
264
|
+
if (queue.some((task) => task.key === key)) return;
|
|
265
|
+
queue.push({
|
|
266
|
+
key,
|
|
267
|
+
media,
|
|
268
|
+
load
|
|
269
|
+
});
|
|
270
|
+
pump();
|
|
227
271
|
}
|
|
228
272
|
function pump() {
|
|
229
273
|
while (!disposed && active < concurrency && queue.length > 0) {
|
|
230
|
-
const taskIndex = queue.findIndex(({
|
|
231
|
-
const media = keySource(key)?.media;
|
|
232
|
-
return !media || activeByMedia[media] < limits[media];
|
|
233
|
-
});
|
|
274
|
+
const taskIndex = queue.findIndex(({ media }) => activeByMedia[media] < limits[media]);
|
|
234
275
|
if (taskIndex < 0) return;
|
|
235
276
|
const [task] = queue.splice(taskIndex, 1);
|
|
236
277
|
if (!task) continue;
|
|
237
278
|
const entry = entries.get(task.key);
|
|
238
|
-
|
|
239
|
-
if (!entry || !source || entry.texture || entry.loading) continue;
|
|
279
|
+
if (!entry || entry.loading || entry.texture && !refinable(entry)) continue;
|
|
240
280
|
entry.loading = true;
|
|
241
|
-
inflight.add(task.key);
|
|
242
281
|
active += 1;
|
|
243
|
-
activeByMedia[
|
|
244
|
-
|
|
282
|
+
activeByMedia[task.media] += 1;
|
|
283
|
+
const done = (loaded, error) => {
|
|
284
|
+
active -= 1;
|
|
285
|
+
activeByMedia[task.media] -= 1;
|
|
286
|
+
adopt(task.key, entry, loaded, error);
|
|
287
|
+
if (!disposed) pump();
|
|
288
|
+
};
|
|
289
|
+
task.load(entry).then((loaded) => done(loaded), (error) => done(null, error));
|
|
245
290
|
}
|
|
246
291
|
}
|
|
247
|
-
function
|
|
248
|
-
const getUrl = source.kind === "url" ? () => Promise.resolve(source.value) : () => resolveFileUrl(source.value);
|
|
292
|
+
function textureLoad(media, source) {
|
|
249
293
|
return async (entry) => {
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
const
|
|
294
|
+
entry.source = source;
|
|
295
|
+
const edge = entry.want;
|
|
296
|
+
const variant = media === "image" ? variantUrl(source, edge) : null;
|
|
297
|
+
const url = variant ?? source;
|
|
298
|
+
const pending = pendingUnloads.get(`${media}:${url}`);
|
|
254
299
|
if (pending) await pending;
|
|
255
300
|
if (disposed) return null;
|
|
256
|
-
|
|
301
|
+
const texture = await loadTexture(url, media);
|
|
302
|
+
if (!texture) return null;
|
|
303
|
+
return {
|
|
304
|
+
texture,
|
|
305
|
+
url,
|
|
306
|
+
edge: !variant || Math.max(texture.width, texture.height) < edge ? Number.POSITIVE_INFINITY : edge
|
|
307
|
+
};
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
function resolveFile(key, media, path) {
|
|
311
|
+
return (entry) => {
|
|
312
|
+
const fresh = entry.attempts > 0;
|
|
313
|
+
entry.loading = true;
|
|
314
|
+
resolveFileUrl(path, fresh ? { fresh } : void 0).then((url) => {
|
|
315
|
+
if (disposed || entries.get(key) !== entry) return;
|
|
316
|
+
if (entry.refs <= 0) return evict(key, entry);
|
|
317
|
+
if (!url) return adopt(key, entry, null, /* @__PURE__ */ new Error(`Unavailable file: ${path}`));
|
|
318
|
+
entry.loading = false;
|
|
319
|
+
enqueue(key, media, textureLoad(media, url));
|
|
320
|
+
}, (error) => {
|
|
321
|
+
if (!disposed) adopt(key, entry, null, error);
|
|
322
|
+
});
|
|
257
323
|
};
|
|
258
324
|
}
|
|
325
|
+
function probeNaturalSize(key, entry, source) {
|
|
326
|
+
entry.natural = null;
|
|
327
|
+
probeMediaInfo(source, { type: "image" }).then(({ width, height }) => {
|
|
328
|
+
if (disposed || entries.get(key) !== entry || !width || !height) return;
|
|
329
|
+
entry.natural = {
|
|
330
|
+
width,
|
|
331
|
+
height
|
|
332
|
+
};
|
|
333
|
+
notify(key);
|
|
334
|
+
});
|
|
335
|
+
}
|
|
259
336
|
function modelPreviewable(item) {
|
|
260
337
|
const previews = options.modelPreviews;
|
|
261
338
|
if (!previews) return false;
|
|
@@ -274,20 +351,35 @@ function createBoardAssetManager(options) {
|
|
|
274
351
|
format,
|
|
275
352
|
size: snapshot?.size
|
|
276
353
|
};
|
|
277
|
-
return async () =>
|
|
354
|
+
return async () => ({
|
|
355
|
+
texture: Texture.from(await previews.load(request)),
|
|
356
|
+
url: "",
|
|
357
|
+
edge: Number.POSITIVE_INFINITY
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
function starter(item, key, source) {
|
|
361
|
+
if (source.media === "model") {
|
|
362
|
+
const load = modelLoader(item, key);
|
|
363
|
+
return load ? () => enqueue(key, "model", load) : null;
|
|
364
|
+
}
|
|
365
|
+
const media = source.media;
|
|
366
|
+
if (source.kind === "file") return resolveFile(key, media, source.value);
|
|
367
|
+
const load = textureLoad(media, source.value);
|
|
368
|
+
return () => enqueue(key, media, load);
|
|
278
369
|
}
|
|
279
370
|
const manager = {
|
|
280
371
|
assetKey: managedAssetKey,
|
|
281
372
|
invalidatePath(path) {
|
|
282
373
|
if (disposed) return;
|
|
283
|
-
|
|
374
|
+
const stale = [...entries.keys()].filter((key) => {
|
|
284
375
|
const source = keySource(key);
|
|
285
376
|
return source?.kind === "file" && source.value === path;
|
|
286
|
-
})
|
|
377
|
+
});
|
|
378
|
+
if (stale.length === 0) return;
|
|
287
379
|
pathVersions.set(path, (pathVersions.get(path) ?? 0) + 1);
|
|
288
|
-
notify();
|
|
380
|
+
for (const key of stale) notify(key);
|
|
289
381
|
},
|
|
290
|
-
requestItem(item) {
|
|
382
|
+
requestItem(item, edge = Number.POSITIVE_INFINITY) {
|
|
291
383
|
if (disposed) return;
|
|
292
384
|
const key = managedAssetKey(item);
|
|
293
385
|
if (!key || failedModels.has(key)) return;
|
|
@@ -295,22 +387,16 @@ function createBoardAssetManager(options) {
|
|
|
295
387
|
if (!source) return;
|
|
296
388
|
const entry = ensureEntry(key);
|
|
297
389
|
entry.lastUsedAt = now();
|
|
298
|
-
if (
|
|
390
|
+
if (edge > entry.want) entry.want = edge;
|
|
391
|
+
if (entry.texture) return refine(key, entry);
|
|
392
|
+
if (entry.loading || entry.retryTimer) return;
|
|
299
393
|
if (entry.error && now() < entry.retryAt) return;
|
|
300
|
-
entry.error = false;
|
|
301
|
-
clearRetry(entry);
|
|
302
394
|
if (queue.some((queued) => queued.key === key)) return;
|
|
303
|
-
const
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
loaders.set(key, load);
|
|
309
|
-
queue.push({
|
|
310
|
-
key,
|
|
311
|
-
load
|
|
312
|
-
});
|
|
313
|
-
pump();
|
|
395
|
+
const start = starter(item, key, source);
|
|
396
|
+
if (!start) return;
|
|
397
|
+
entry.error = false;
|
|
398
|
+
starters.set(key, start);
|
|
399
|
+
start(entry);
|
|
314
400
|
},
|
|
315
401
|
getTexture(key) {
|
|
316
402
|
const entry = entries.get(key);
|
|
@@ -319,12 +405,10 @@ function createBoardAssetManager(options) {
|
|
|
319
405
|
return entry.texture;
|
|
320
406
|
},
|
|
321
407
|
getNaturalSize(key) {
|
|
322
|
-
const
|
|
323
|
-
if (!texture) return null;
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
height: texture.height
|
|
327
|
-
};
|
|
408
|
+
const entry = entries.get(key);
|
|
409
|
+
if (!entry?.texture) return null;
|
|
410
|
+
if (entry.natural === void 0 && entry.source) probeNaturalSize(key, entry, entry.source);
|
|
411
|
+
return entry.natural ?? null;
|
|
328
412
|
},
|
|
329
413
|
hasError(key) {
|
|
330
414
|
return entries.get(key)?.error ?? false;
|
|
@@ -355,10 +439,10 @@ function createBoardAssetManager(options) {
|
|
|
355
439
|
if (keys.size === 0) return use(textures);
|
|
356
440
|
for (const key of keys) manager.acquire(key);
|
|
357
441
|
try {
|
|
358
|
-
for (const item of items) manager.requestItem(item);
|
|
442
|
+
for (const item of items) manager.requestItem(item, loadOptions?.edge?.(item));
|
|
359
443
|
const settled = () => [...keys].every((key) => {
|
|
360
444
|
const entry = entries.get(key);
|
|
361
|
-
return Boolean(entry?.
|
|
445
|
+
return Boolean(entry?.error) || Boolean(entry?.texture && !refinable(entry));
|
|
362
446
|
});
|
|
363
447
|
if (!settled()) {
|
|
364
448
|
const timeoutMs = loadOptions?.timeoutMs ?? 15e3;
|
|
@@ -394,13 +478,14 @@ function createBoardAssetManager(options) {
|
|
|
394
478
|
queue.length = 0;
|
|
395
479
|
listeners.clear();
|
|
396
480
|
pathVersions.clear();
|
|
397
|
-
|
|
481
|
+
starters.clear();
|
|
398
482
|
failedModels.clear();
|
|
399
483
|
for (const [key, entry] of entries) evict(key, entry);
|
|
400
484
|
entries.clear();
|
|
485
|
+
for (const release of [...retiring]) release();
|
|
401
486
|
}
|
|
402
487
|
};
|
|
403
488
|
return manager;
|
|
404
489
|
}
|
|
405
490
|
//#endregion
|
|
406
|
-
export { boardAssetKey, createBoardAssetManager };
|
|
491
|
+
export { boardAssetKey, boardTextureEdge, createBoardAssetManager };
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { DEFAULT_LRU_BUDGET, LruBudget, LruEntry, selectLruEvictions } from "./lru.js";
|
|
1
|
+
import { COMPACT_LRU_BUDGET, DEFAULT_LRU_BUDGET, LruBudget, LruEntry, selectLruEvictions } from "./lru.js";
|
|
2
2
|
import { BoardImageTexturePool, boardImageTexturePool, createBoardImageTexturePool, loadBoardImageTexture, unloadBoardImageTexture } from "./texture-pool.js";
|
|
3
|
-
import { BoardAssetManager, BoardAssetManagerOptions, BoardModelPreviewRequest, BoardModelPreviews, boardAssetKey, createBoardAssetManager } from "./assets.js";
|
|
3
|
+
import { BoardAssetManager, BoardAssetManagerOptions, BoardModelPreviewRequest, BoardModelPreviews, boardAssetKey, boardTextureEdge, createBoardAssetManager } from "./assets.js";
|
|
4
4
|
import { toBoardPointerEvent } from "./pointer.js";
|
|
5
5
|
import { createBoardPointerGestures } from "./pointer-gestures.js";
|
|
6
6
|
import { BoardLiveStroke, BoardScene, BoardSceneNode, SceneSyncInput, createBoardScene } from "./scene.js";
|
|
7
7
|
import { BoardItemView, BoardItemViewContext, BoardItemViewInstance, BoardItemViewLayer, createBoardItemViewLayer } from "./views.js";
|
|
8
8
|
import { BoardStage, BoardStageOptions, BoardStagePointerPhase, BoardStageTheme, mountBoardStage } from "./stage.js";
|
|
9
9
|
import { BoardOverlayInput, drawBoardOverlay, expandFrame } from "./overlay.js";
|
|
10
|
-
export { BoardAssetManager, BoardAssetManagerOptions, BoardImageTexturePool, BoardItemView, BoardItemViewContext, BoardItemViewInstance, BoardItemViewLayer, BoardLiveStroke, BoardModelPreviewRequest, BoardModelPreviews, BoardOverlayInput, BoardScene, BoardSceneNode, BoardStage, BoardStageOptions, BoardStagePointerPhase, BoardStageTheme, DEFAULT_LRU_BUDGET, LruBudget, LruEntry, SceneSyncInput, boardAssetKey, boardImageTexturePool, createBoardAssetManager, createBoardImageTexturePool, createBoardItemViewLayer, createBoardPointerGestures, createBoardScene, drawBoardOverlay, expandFrame, loadBoardImageTexture, mountBoardStage, selectLruEvictions, toBoardPointerEvent, unloadBoardImageTexture };
|
|
10
|
+
export { BoardAssetManager, BoardAssetManagerOptions, BoardImageTexturePool, BoardItemView, BoardItemViewContext, BoardItemViewInstance, BoardItemViewLayer, BoardLiveStroke, BoardModelPreviewRequest, BoardModelPreviews, BoardOverlayInput, BoardScene, BoardSceneNode, BoardStage, BoardStageOptions, BoardStagePointerPhase, BoardStageTheme, COMPACT_LRU_BUDGET, DEFAULT_LRU_BUDGET, LruBudget, LruEntry, SceneSyncInput, boardAssetKey, boardImageTexturePool, boardTextureEdge, createBoardAssetManager, createBoardImageTexturePool, createBoardItemViewLayer, createBoardPointerGestures, createBoardScene, drawBoardOverlay, expandFrame, loadBoardImageTexture, mountBoardStage, selectLruEvictions, toBoardPointerEvent, unloadBoardImageTexture };
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { DEFAULT_LRU_BUDGET, selectLruEvictions } from "./lru.js";
|
|
1
|
+
import { COMPACT_LRU_BUDGET, DEFAULT_LRU_BUDGET, selectLruEvictions } from "./lru.js";
|
|
2
2
|
import { boardImageTexturePool, createBoardImageTexturePool, loadBoardImageTexture, unloadBoardImageTexture } from "./texture-pool.js";
|
|
3
|
-
import { boardAssetKey, createBoardAssetManager } from "./assets.js";
|
|
3
|
+
import { boardAssetKey, boardTextureEdge, createBoardAssetManager } from "./assets.js";
|
|
4
4
|
import { toBoardPointerEvent } from "./pointer.js";
|
|
5
5
|
import { createBoardPointerGestures } from "./pointer-gestures.js";
|
|
6
6
|
import { createBoardScene } from "./scene.js";
|
|
7
7
|
import { drawBoardOverlay, expandFrame } from "./overlay.js";
|
|
8
8
|
import { createBoardItemViewLayer } from "./views.js";
|
|
9
9
|
import { mountBoardStage } from "./stage.js";
|
|
10
|
-
export { DEFAULT_LRU_BUDGET, boardAssetKey, boardImageTexturePool, createBoardAssetManager, createBoardImageTexturePool, createBoardItemViewLayer, createBoardPointerGestures, createBoardScene, drawBoardOverlay, expandFrame, loadBoardImageTexture, mountBoardStage, selectLruEvictions, toBoardPointerEvent, unloadBoardImageTexture };
|
|
10
|
+
export { COMPACT_LRU_BUDGET, DEFAULT_LRU_BUDGET, boardAssetKey, boardImageTexturePool, boardTextureEdge, createBoardAssetManager, createBoardImageTexturePool, createBoardItemViewLayer, createBoardPointerGestures, createBoardScene, drawBoardOverlay, expandFrame, loadBoardImageTexture, mountBoardStage, selectLruEvictions, toBoardPointerEvent, unloadBoardImageTexture };
|
|
@@ -20,8 +20,9 @@ type LruBudget = {
|
|
|
20
20
|
maxCount: number;
|
|
21
21
|
maxBytes: number;
|
|
22
22
|
};
|
|
23
|
-
/** Default cooling-pool ceiling:
|
|
23
|
+
/** Default cooling-pool ceiling: 512 images or ~192 MB of GPU memory. */
|
|
24
24
|
declare const DEFAULT_LRU_BUDGET: LruBudget;
|
|
25
|
+
declare const COMPACT_LRU_BUDGET: LruBudget;
|
|
25
26
|
/**
|
|
26
27
|
* Given the unreferenced (cooling) entries and a budget, return the keys to
|
|
27
28
|
* evict — oldest first — so the remaining pool fits within both limits. Returns
|
|
@@ -29,4 +30,4 @@ declare const DEFAULT_LRU_BUDGET: LruBudget;
|
|
|
29
30
|
*/
|
|
30
31
|
declare function selectLruEvictions(entries: LruEntry[], budget: LruBudget): string[];
|
|
31
32
|
//#endregion
|
|
32
|
-
export { DEFAULT_LRU_BUDGET, LruBudget, LruEntry, selectLruEvictions };
|
|
33
|
+
export { COMPACT_LRU_BUDGET, DEFAULT_LRU_BUDGET, LruBudget, LruEntry, selectLruEvictions };
|
package/dist/board/stage/lru.js
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
//#region src/board/stage/lru.ts
|
|
2
|
-
/** Default cooling-pool ceiling:
|
|
2
|
+
/** Default cooling-pool ceiling: 512 images or ~192 MB of GPU memory. */
|
|
3
3
|
const DEFAULT_LRU_BUDGET = {
|
|
4
|
-
maxCount:
|
|
5
|
-
maxBytes:
|
|
4
|
+
maxCount: 512,
|
|
5
|
+
maxBytes: 201326592
|
|
6
|
+
};
|
|
7
|
+
const COMPACT_LRU_BUDGET = {
|
|
8
|
+
maxCount: 256,
|
|
9
|
+
maxBytes: 100663296
|
|
6
10
|
};
|
|
7
11
|
/**
|
|
8
12
|
* Given the unreferenced (cooling) entries and a budget, return the keys to
|
|
@@ -26,4 +30,4 @@ function selectLruEvictions(entries, budget) {
|
|
|
26
30
|
return evict;
|
|
27
31
|
}
|
|
28
32
|
//#endregion
|
|
29
|
-
export { DEFAULT_LRU_BUDGET, selectLruEvictions };
|
|
33
|
+
export { COMPACT_LRU_BUDGET, DEFAULT_LRU_BUDGET, selectLruEvictions };
|
|
@@ -3,6 +3,6 @@ import "../model/index.js";
|
|
|
3
3
|
import { BoardPointerEvent } from "../editor/interaction.js";
|
|
4
4
|
import "../editor/index.js";
|
|
5
5
|
//#region src/board/stage/pointer.d.ts
|
|
6
|
-
declare function toBoardPointerEvent(event: PointerEvent, rect: DOMRect, camera: BoardViewport, coalesce?: boolean): BoardPointerEvent;
|
|
6
|
+
declare function toBoardPointerEvent(event: PointerEvent, rect: DOMRect | HTMLElement, camera: BoardViewport, coalesce?: boolean): BoardPointerEvent;
|
|
7
7
|
//#endregion
|
|
8
8
|
export { toBoardPointerEvent };
|
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
import { pointToWorld, screenPoint } from "../model/geometry.js";
|
|
2
|
+
import { elementClientToLocal } from "../model/client-geometry.js";
|
|
2
3
|
//#region src/board/stage/pointer.ts
|
|
3
4
|
function pressureOf(event) {
|
|
4
5
|
return event.pointerType === "pen" && event.pressure > 0 ? event.pressure : .5;
|
|
5
6
|
}
|
|
6
|
-
function screenOf(event, rect) {
|
|
7
|
-
return screenPoint(event.clientX - rect.left, event.clientY - rect.top);
|
|
8
|
-
}
|
|
9
7
|
function toBoardPointerEvent(event, rect, camera, coalesce = false) {
|
|
10
|
-
const
|
|
8
|
+
const local = "getBoundingClientRect" in rect ? elementClientToLocal(rect) : (x, y) => screenPoint(x - rect.left, y - rect.top);
|
|
9
|
+
const screen = local(event.clientX, event.clientY);
|
|
11
10
|
const coalesced = coalesce ? event.getCoalescedEvents?.() : void 0;
|
|
12
11
|
const samples = coalesced && coalesced.length > 1 ? coalesced.map((sample) => ({
|
|
13
|
-
world: pointToWorld(
|
|
12
|
+
world: pointToWorld(local(sample.clientX, sample.clientY), camera),
|
|
14
13
|
pressure: pressureOf(sample)
|
|
15
14
|
})) : void 0;
|
|
16
15
|
return {
|
|
@@ -32,6 +32,9 @@ type BoardSceneNode = {
|
|
|
32
32
|
type BoardScene = {
|
|
33
33
|
sync: (input: SceneSyncInput) => void;
|
|
34
34
|
getNode: (itemId: string) => BoardSceneNode | null;
|
|
35
|
+
holders: (key: string) => ReadonlySet<string>;
|
|
36
|
+
readonly liveVersion: number;
|
|
37
|
+
liveIds: () => ReadonlySet<string>;
|
|
35
38
|
readonly animated: boolean;
|
|
36
39
|
tick: (clock: number) => number | null;
|
|
37
40
|
destroy: (context: BoardRenderContext) => void;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { parseBoardItem } from "../../protocol/dist/board-model.js";
|
|
2
2
|
import { drawPointsBounds } from "../../protocol/dist/board-layout.js";
|
|
3
|
+
import { getBoardResolution, textZoomBucket } from "../render/text-resolution.js";
|
|
3
4
|
import { clippingAncestor, isClippingFrame, syncClipGroup } from "../render/clip.js";
|
|
4
5
|
import { Graphics } from "pixi.js";
|
|
5
6
|
//#region src/board/stage/scene.ts
|
|
@@ -12,6 +13,7 @@ const Z_LIFTED = 1e9;
|
|
|
12
13
|
const Z_LIVE_STROKE = Number.MAX_SAFE_INTEGER - 1;
|
|
13
14
|
const Z_OVERLAY = Number.MAX_SAFE_INTEGER;
|
|
14
15
|
const POOL_LIMIT_PER_RENDERER = 48;
|
|
16
|
+
const EMPTY_IDS = /* @__PURE__ */ new Set();
|
|
15
17
|
function idSetSignature(ids) {
|
|
16
18
|
let hash = ids.size;
|
|
17
19
|
for (const id of ids) {
|
|
@@ -31,20 +33,33 @@ function createBoardScene(options) {
|
|
|
31
33
|
const tickingIds = /* @__PURE__ */ new Set();
|
|
32
34
|
const pools = /* @__PURE__ */ new Map();
|
|
33
35
|
const heldKeys = /* @__PURE__ */ new Map();
|
|
36
|
+
const holders = /* @__PURE__ */ new Map();
|
|
34
37
|
let orderById = /* @__PURE__ */ new Map();
|
|
35
38
|
let farSig = null;
|
|
36
39
|
let farActive = false;
|
|
37
40
|
let lastStructureVersion = -1;
|
|
38
41
|
let lastZoom = NaN;
|
|
42
|
+
let lastResolution = "";
|
|
43
|
+
let liveVersion = 0;
|
|
39
44
|
let lifted = /* @__PURE__ */ new Set();
|
|
40
45
|
const liveStrokes = /* @__PURE__ */ new Map();
|
|
41
|
-
function setHeldKey(context, cardId,
|
|
46
|
+
function setHeldKey(context, cardId, item) {
|
|
47
|
+
const desiredKey = item ? context.assetKey(item) : null;
|
|
42
48
|
const heldKey = heldKeys.get(cardId) ?? null;
|
|
43
49
|
if (desiredKey === heldKey) return;
|
|
44
|
-
if (heldKey)
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
50
|
+
if (heldKey) {
|
|
51
|
+
context.releaseTexture(heldKey);
|
|
52
|
+
const ids = holders.get(heldKey);
|
|
53
|
+
ids?.delete(cardId);
|
|
54
|
+
if (ids?.size === 0) holders.delete(heldKey);
|
|
55
|
+
}
|
|
56
|
+
if (desiredKey && item) {
|
|
57
|
+
heldKeys.set(cardId, desiredKey);
|
|
58
|
+
const ids = holders.get(desiredKey);
|
|
59
|
+
if (ids) ids.add(cardId);
|
|
60
|
+
else holders.set(desiredKey, /* @__PURE__ */ new Set([cardId]));
|
|
61
|
+
context.acquireTexture(desiredKey, item);
|
|
62
|
+
} else heldKeys.delete(cardId);
|
|
48
63
|
}
|
|
49
64
|
function takeFromPool(rendererId) {
|
|
50
65
|
const pool = pools.get(rendererId);
|
|
@@ -119,7 +134,7 @@ function createBoardScene(options) {
|
|
|
119
134
|
scriptRevision: context.scripts?.revision?.(item.id) ?? 0
|
|
120
135
|
};
|
|
121
136
|
cards.set(item.id, entry);
|
|
122
|
-
setHeldKey(context, item.id,
|
|
137
|
+
setHeldKey(context, item.id, item);
|
|
123
138
|
renderer.zoom?.(container, context.zoom);
|
|
124
139
|
return entry;
|
|
125
140
|
}
|
|
@@ -277,6 +292,9 @@ function createBoardScene(options) {
|
|
|
277
292
|
}
|
|
278
293
|
function sync(input) {
|
|
279
294
|
const { items, scene, context, getItem, visibleIds, pinnedIds, globalSig, structureVersion, geometryVersion, gestureActive } = input;
|
|
295
|
+
const resolution = `${textZoomBucket(context.zoom)}:${getBoardResolution()}`;
|
|
296
|
+
const resolutionChanged = resolution !== lastResolution;
|
|
297
|
+
lastResolution = resolution;
|
|
280
298
|
const structureChanged = structureVersion !== lastStructureVersion;
|
|
281
299
|
if (structureChanged) {
|
|
282
300
|
lastStructureVersion = structureVersion;
|
|
@@ -304,7 +322,14 @@ function createBoardScene(options) {
|
|
|
304
322
|
if (farActive) {
|
|
305
323
|
const { orderedIds, unbatched } = visibleFacts(input);
|
|
306
324
|
for (const id of unbatched) wanted.add(id);
|
|
307
|
-
let
|
|
325
|
+
let retained = 0;
|
|
326
|
+
if (!farPainted) {
|
|
327
|
+
for (const id of cards.keys()) if ((!visibleIds || visibleIds.has(id)) && !farCovered.has(id) && !wanted.has(id)) {
|
|
328
|
+
wanted.add(id);
|
|
329
|
+
retained++;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
let budget = Math.max(0, FAR_LIVE_LIMIT - retained);
|
|
308
333
|
for (const id of orderedIds) {
|
|
309
334
|
if (budget === 0) break;
|
|
310
335
|
if (farCovered.has(id) || wanted.has(id)) continue;
|
|
@@ -346,13 +371,17 @@ function createBoardScene(options) {
|
|
|
346
371
|
entry.hovered = hovered;
|
|
347
372
|
entry.resizing = resizing;
|
|
348
373
|
entry.globalSig = globalSig;
|
|
349
|
-
setHeldKey(context, id,
|
|
374
|
+
setHeldKey(context, id, item);
|
|
350
375
|
if (changed) {
|
|
351
376
|
entry.renderer.update(entry.container, item, context);
|
|
352
377
|
entry.container.alpha = scene.opacity(id);
|
|
353
378
|
if (entry.renderer.tick) tickingIds.add(id);
|
|
379
|
+
} else if (resolutionChanged && entry.renderer.resolution) {
|
|
380
|
+
entry.renderer.resolution(entry.container, item, context);
|
|
381
|
+
if (entry.renderer.tick) tickingIds.add(id);
|
|
354
382
|
}
|
|
355
383
|
}
|
|
384
|
+
if (liveSetChanged) liveVersion += 1;
|
|
356
385
|
if (structureChanged || liveSetChanged) syncClips(scene);
|
|
357
386
|
if (context.zoom !== lastZoom) {
|
|
358
387
|
lastZoom = context.zoom;
|
|
@@ -391,6 +420,7 @@ function createBoardScene(options) {
|
|
|
391
420
|
pools.clear();
|
|
392
421
|
for (const key of heldKeys.values()) context.releaseTexture(key);
|
|
393
422
|
heldKeys.clear();
|
|
423
|
+
holders.clear();
|
|
394
424
|
orderById = /* @__PURE__ */ new Map();
|
|
395
425
|
for (const entry of clips.values()) entry.group.destroy({ children: true });
|
|
396
426
|
clips.clear();
|
|
@@ -402,6 +432,7 @@ function createBoardScene(options) {
|
|
|
402
432
|
visibleMemo = null;
|
|
403
433
|
lastStructureVersion = -1;
|
|
404
434
|
lastZoom = NaN;
|
|
435
|
+
lastResolution = "";
|
|
405
436
|
}
|
|
406
437
|
return {
|
|
407
438
|
sync,
|
|
@@ -412,6 +443,11 @@ function createBoardScene(options) {
|
|
|
412
443
|
container: entry.container
|
|
413
444
|
} : null;
|
|
414
445
|
},
|
|
446
|
+
holders: (key) => holders.get(key) ?? EMPTY_IDS,
|
|
447
|
+
get liveVersion() {
|
|
448
|
+
return liveVersion;
|
|
449
|
+
},
|
|
450
|
+
liveIds: () => new Set(cards.keys()),
|
|
415
451
|
get animated() {
|
|
416
452
|
return animatedIds.size > 0;
|
|
417
453
|
},
|
|
@@ -56,6 +56,7 @@ type BoardStageOptions = {
|
|
|
56
56
|
height: number;
|
|
57
57
|
}) => void;
|
|
58
58
|
onVisibleChange?: (ids: ReadonlySet<string> | null) => void;
|
|
59
|
+
onLiveChange?: (ids: ReadonlySet<string>) => void;
|
|
59
60
|
safeRect?: () => Rect;
|
|
60
61
|
scriptCapabilities?: Record<string, (id: string, input: unknown) => Promise<unknown>>;
|
|
61
62
|
};
|