@neta-art/cohub 9.0.1 → 11.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/README.md +108 -102
- package/dist/board/editor/align.d.ts +11 -0
- package/dist/board/editor/align.js +72 -0
- package/dist/board/editor/animation.d.ts +17 -0
- package/dist/board/editor/animation.js +187 -0
- package/dist/board/editor/camera.d.ts +32 -0
- package/dist/board/editor/camera.js +137 -0
- package/dist/board/editor/clipboard.d.ts +41 -0
- package/dist/board/editor/clipboard.js +129 -0
- package/dist/board/editor/connection-ports.d.ts +19 -0
- package/dist/board/editor/connection-ports.js +85 -0
- package/dist/board/editor/context.d.ts +111 -0
- package/dist/board/editor/context.js +30 -0
- package/dist/board/editor/copy.d.ts +17 -0
- package/dist/board/editor/copy.js +121 -0
- package/dist/board/editor/creation.d.ts +51 -0
- package/dist/board/editor/creation.js +225 -0
- package/dist/board/editor/deletion.d.ts +9 -0
- package/dist/board/editor/deletion.js +90 -0
- package/dist/board/editor/document-edits.d.ts +22 -0
- package/dist/board/editor/document-edits.js +193 -0
- package/dist/board/editor/document.d.ts +41 -0
- package/dist/board/editor/document.js +302 -0
- package/dist/board/editor/editor.d.ts +225 -0
- package/dist/board/editor/editor.js +439 -0
- package/dist/board/editor/index.d.ts +18 -0
- package/dist/board/editor/index.js +12 -0
- package/dist/board/editor/interaction.d.ts +115 -0
- package/dist/board/editor/interaction.js +584 -0
- package/dist/board/editor/query.d.ts +27 -0
- package/dist/board/editor/query.js +203 -0
- package/dist/board/editor/remote.d.ts +11 -0
- package/dist/board/editor/remote.js +55 -0
- package/dist/board/editor/selection-transform.d.ts +24 -0
- package/dist/board/editor/selection-transform.js +81 -0
- package/dist/board/editor/selection.d.ts +33 -0
- package/dist/board/editor/selection.js +197 -0
- package/dist/board/editor/snapping.d.ts +33 -0
- package/dist/board/editor/snapping.js +75 -0
- package/dist/board/editor/snapshots.d.ts +27 -0
- package/dist/board/editor/snapshots.js +132 -0
- package/dist/board/editor/spatial.d.ts +41 -0
- package/dist/board/editor/spatial.js +191 -0
- package/dist/board/editor/state.d.ts +12 -0
- package/dist/board/editor/state.js +49 -0
- package/dist/board/editor/tool.d.ts +10 -0
- package/dist/board/editor/tool.js +19 -0
- package/dist/board/editor/wheel.d.ts +5 -0
- package/dist/board/editor/wheel.js +17 -0
- package/dist/board/export/index.d.ts +28 -6
- package/dist/board/export/index.js +47 -13
- package/dist/board/export/node/index.d.ts +56 -0
- package/dist/board/{headless → export/node}/index.js +26 -21
- package/dist/board/export/node/scripts.d.ts +16 -0
- package/dist/board/export/node/scripts.js +258 -0
- package/dist/board/export/scene.d.ts +8 -5
- package/dist/board/export/scene.js +24 -9
- package/dist/board/{animation.d.ts → model/animation.d.ts} +8 -5
- package/dist/board/{animation.js → model/animation.js} +37 -10
- package/dist/board/{core → model}/arrow-geometry.d.ts +2 -2
- package/dist/board/{core → model}/arrow-geometry.js +2 -2
- package/dist/board/model/authoring.d.ts +20 -0
- package/dist/board/model/authoring.js +185 -0
- package/dist/board/{core → model}/draw-geometry.d.ts +2 -2
- package/dist/board/{core → model}/draw-geometry.js +1 -1
- package/dist/board/model/draw-input.d.ts +14 -0
- package/dist/board/model/draw-input.js +28 -0
- package/dist/board/{core → model}/export-assets.d.ts +1 -1
- package/dist/board/{core → model}/export-assets.js +1 -1
- package/dist/board/{core → model}/export-plan.d.ts +2 -2
- package/dist/board/{core → model}/export-plan.js +3 -2
- package/dist/board/{core → model}/file-preview.d.ts +1 -1
- package/dist/board/{core → model}/file-preview.js +2 -2
- package/dist/board/{core → model}/file-snapshot.d.ts +1 -1
- package/dist/board/{core → model}/file-snapshot.js +4 -3
- package/dist/board/{geometry.d.ts → model/geometry.d.ts} +3 -3
- package/dist/board/{geometry.js → model/geometry.js} +1 -1
- package/dist/board/{image-key.d.ts → model/image-key.d.ts} +2 -2
- package/dist/board/{image-key.js → model/image-key.js} +7 -3
- package/dist/board/model/index.d.ts +42 -0
- package/dist/board/model/index.js +39 -0
- package/dist/board/model/items/builtin.d.ts +5 -0
- package/dist/board/model/items/builtin.js +119 -0
- package/dist/board/model/items/create.d.ts +61 -0
- package/dist/board/model/items/create.js +272 -0
- package/dist/board/model/items/definition.d.ts +35 -0
- package/dist/board/model/items/definition.js +18 -0
- package/dist/board/model/items/id.d.ts +5 -0
- package/dist/board/model/items/id.js +11 -0
- package/dist/board/model/items/index.d.ts +6 -0
- package/dist/board/model/items/index.js +6 -0
- package/dist/board/model/items/registry.d.ts +35 -0
- package/dist/board/model/items/registry.js +84 -0
- package/dist/board/{media-playback.d.ts → model/media-playback.d.ts} +4 -3
- package/dist/board/{media-playback.js → model/media-playback.js} +15 -5
- package/dist/board/{media.d.ts → model/media.d.ts} +1 -1
- package/dist/board/{media.js → model/media.js} +8 -2
- package/dist/board/{core → model}/palette.d.ts +1 -1
- package/dist/board/{core → model}/palette.js +1 -1
- package/dist/board/{presets.d.ts → model/presets.d.ts} +3 -3
- package/dist/board/{presets.js → model/presets.js} +1 -1
- package/dist/board/{replay.d.ts → model/replay.d.ts} +5 -5
- package/dist/board/{replay.js → model/replay.js} +2 -2
- package/dist/board/model/sampling.d.ts +19 -0
- package/dist/board/model/sampling.js +96 -0
- package/dist/board/{core → model}/scene.d.ts +1 -1
- package/dist/board/{core → model}/scene.js +1 -1
- package/dist/board/model/script-example.d.ts +4 -0
- package/dist/board/model/script-example.js +21 -0
- package/dist/board/model/script-hit.d.ts +10 -0
- package/dist/board/model/script-hit.js +52 -0
- package/dist/board/model/script-index.d.ts +7 -0
- package/dist/board/model/script-index.js +15 -0
- package/dist/board/model/script-scene.d.ts +24 -0
- package/dist/board/model/script-scene.js +267 -0
- package/dist/board/model/script.d.ts +105 -0
- package/dist/board/model/script.js +1 -0
- package/dist/board/model/shape-types.d.ts +8 -0
- package/dist/board/model/shape-types.js +8 -0
- package/dist/board/{task.d.ts → model/task.d.ts} +4 -4
- package/dist/board/{task.js → model/task.js} +3 -3
- package/dist/board/{core → model}/text-metrics.d.ts +1 -1
- package/dist/board/{core → model}/text-metrics.js +1 -1
- package/dist/board/{core → model}/tool-styles.d.ts +1 -1
- package/dist/board/{core → model}/tool-styles.js +1 -1
- package/dist/board/{runtime/board-player.d.ts → player/index.d.ts} +2 -2
- package/dist/board/{runtime/board-player.js → player/index.js} +5 -4
- package/dist/board/render/board-background.d.ts +1 -1
- package/dist/board/render/clip.d.ts +1 -1
- package/dist/board/render/css-color.d.ts +6 -1
- package/dist/board/render/css-color.js +34 -20
- package/dist/board/render/diagnostics.d.ts +20 -0
- package/dist/board/render/diagnostics.js +117 -0
- package/dist/board/render/index.d.ts +9 -6
- package/dist/board/render/index.js +10 -7
- package/dist/board/render/media-interaction.d.ts +2 -2
- package/dist/board/render/media-interaction.js +1 -1
- package/dist/board/render/palette.d.ts +3 -2
- package/dist/board/render/palette.js +7 -3
- package/dist/board/render/renderers/arrow-card-renderer.js +4 -3
- package/dist/board/render/renderers/audio-card-renderer.js +2 -1
- package/dist/board/render/renderers/board-renderer-registry.d.ts +20 -10
- package/dist/board/render/renderers/board-renderer-registry.js +17 -17
- package/dist/board/render/renderers/custom-card-renderer.d.ts +5 -0
- package/dist/board/render/renderers/{sketch-card-renderer.js → custom-card-renderer.js} +16 -15
- package/dist/board/render/renderers/draw-card-renderer.js +4 -3
- package/dist/board/render/renderers/effect-card-renderer.js +22 -5
- package/dist/board/render/renderers/far-plate.js +1 -1
- package/dist/board/render/renderers/file-card-renderer.d.ts +1 -1
- package/dist/board/render/renderers/file-card-renderer.js +2 -2
- package/dist/board/render/renderers/frame-card-renderer.js +30 -14
- package/dist/board/render/renderers/image-card-renderer.d.ts +1 -1
- package/dist/board/render/renderers/image-card-renderer.js +43 -4
- package/dist/board/render/renderers/shape-card-renderer.js +11 -28
- package/dist/board/render/renderers/task-card-renderer.js +1 -1
- package/dist/board/render/renderers/text-card-renderer.js +6 -4
- package/dist/board/render/renderers/unknown-card-renderer.js +1 -0
- package/dist/board/render/renderers/video-card-renderer.d.ts +1 -1
- package/dist/board/render/renderers/video-card-renderer.js +2 -1
- package/dist/board/render/sampling.d.ts +22 -0
- package/dist/board/render/sampling.js +21 -0
- package/dist/board/render/script-export.d.ts +11 -0
- package/dist/board/render/script-export.js +86 -0
- package/dist/board/render/script-host.d.ts +31 -0
- package/dist/board/render/script-host.js +421 -0
- package/dist/board/render/script-modules.d.ts +2 -0
- package/dist/board/render/script-modules.js +2 -0
- package/dist/board/render/script-runtime.d.ts +29 -0
- package/dist/board/render/script-runtime.js +213 -0
- package/dist/board/render/script-sandbox.d.ts +4 -0
- package/dist/board/render/script-sandbox.js +173 -0
- package/dist/board/render/stroke.d.ts +3 -2
- package/dist/board/render/stroke.js +23 -2
- package/dist/board/render/text-measurement.js +2 -2
- package/dist/board/replica/index.d.ts +70 -0
- package/dist/board/replica/index.js +276 -0
- package/dist/board/stage/assets.d.ts +41 -0
- package/dist/board/stage/assets.js +355 -0
- package/dist/board/stage/index.d.ts +8 -0
- package/dist/board/stage/index.js +8 -0
- package/dist/board/stage/lru.d.ts +32 -0
- package/dist/board/stage/lru.js +29 -0
- package/dist/board/stage/maintenance.d.ts +5 -0
- package/dist/board/stage/maintenance.js +11 -0
- package/dist/board/stage/pointer.d.ts +8 -0
- package/dist/board/stage/pointer.js +33 -0
- package/dist/board/stage/scene.d.ts +85 -0
- package/dist/board/stage/scene.js +515 -0
- package/dist/board/stage/script-pointer.d.ts +19 -0
- package/dist/board/stage/script-pointer.js +35 -0
- package/dist/board/stage/stage.d.ts +76 -0
- package/dist/board/stage/stage.js +774 -0
- package/dist/board/stage/texture-pool.d.ts +24 -0
- package/dist/board/stage/texture-pool.js +153 -0
- package/dist/board/stage/views.d.ts +38 -0
- package/dist/board/stage/views.js +93 -0
- package/dist/chunks/environment.d.ts +289 -23
- package/dist/chunks/host-bridge.d.ts +2 -2
- package/dist/chunks/http.d.ts +185 -71
- package/dist/chunks/http.js +368 -269
- package/dist/chunks/transport.js +3 -1
- package/dist/chunks/websocket.d.ts +9 -1
- package/dist/chunks/websocket.js +16 -2
- package/dist/host-bridge.js +2 -1
- package/dist/http.d.ts +2 -2
- package/dist/index.d.ts +182 -4
- package/dist/index.js +1393 -129
- package/dist/media.d.ts +1 -10
- package/dist/media.js +16 -27
- package/dist/protocol/dist/board-assets.d.ts +1 -0
- package/dist/protocol/dist/board-constants.js +2 -2
- package/dist/protocol/dist/board-model.d.ts +201 -11
- package/dist/protocol/dist/board-model.js +33 -14
- package/dist/protocol/dist/board-patch.js +3 -2
- package/dist/protocol/dist/board-schema.d.ts +4 -2
- package/dist/protocol/dist/board-schema.js +18 -4
- package/dist/protocol/dist/board-script-modules.d.ts +25 -0
- package/dist/protocol/dist/board-script-modules.js +127 -0
- package/dist/protocol/dist/board.d.ts +28 -2
- package/dist/protocol/dist/board.js +3 -1
- package/dist/protocol/dist/core/media.d.ts +5 -0
- package/dist/protocol/dist/core/media.js +39 -0
- package/dist/protocol/dist/display/index.d.ts +1 -0
- package/dist/protocol/dist/index.d.ts +7 -5
- package/dist/protocol/dist/realtime/types.d.ts +1 -0
- package/dist/protocol/dist/rtc/index.d.ts +1 -0
- package/dist/types.d.ts +1 -1
- package/package.json +21 -8
- package/dist/board/core/shape-definition.d.ts +0 -23
- package/dist/board/core/shape-definition.js +0 -45
- package/dist/board/core/shape-types.d.ts +0 -38
- package/dist/board/core/shape-types.js +0 -22
- package/dist/board/headless/index.d.ts +0 -55
- package/dist/board/headless/sketch.d.ts +0 -19
- package/dist/board/headless/sketch.js +0 -146
- package/dist/board/index.d.ts +0 -30
- package/dist/board/index.js +0 -29
- package/dist/board/render/renderers/sketch-card-renderer.d.ts +0 -5
- package/dist/board/render/sketch-host.d.ts +0 -14
- package/dist/board/render/sketch-host.js +0 -216
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
import { mediaPreviewCandidates } from "../../media.js";
|
|
2
|
+
import { parseBoardMediaSource } from "../../protocol/dist/board-model.js";
|
|
3
|
+
import { featuredTaskArtifact } from "../model/task.js";
|
|
4
|
+
import { boardImageKeySource, imageAssetKey } from "../model/image-key.js";
|
|
5
|
+
import { loadVideoThumbnailTexture, videoTextureNaturalSize } from "../render/video-thumbnail.js";
|
|
6
|
+
import { DEFAULT_LRU_BUDGET, selectLruEvictions } from "./lru.js";
|
|
7
|
+
import { boardImageTexturePool, loadBoardImageTexture, unloadBoardImageTexture } from "./texture-pool.js";
|
|
8
|
+
//#region src/board/stage/assets.ts
|
|
9
|
+
const TASK_TEXTURE_SIZE = 1536;
|
|
10
|
+
function taskPreviewKey(artifact) {
|
|
11
|
+
if (!artifact || artifact.type === "text") return null;
|
|
12
|
+
const [still] = mediaPreviewCandidates(artifact, { size: TASK_TEXTURE_SIZE });
|
|
13
|
+
if (still) return `url:${still}`;
|
|
14
|
+
return artifact.type === "video" ? `video-url:${encodeURIComponent(artifact.url)}` : null;
|
|
15
|
+
}
|
|
16
|
+
function boardAssetKey(item) {
|
|
17
|
+
if (item.type === "task") return taskPreviewKey(featuredTaskArtifact(item.props.snapshot.artifacts));
|
|
18
|
+
if (item.type === "video") {
|
|
19
|
+
const source = parseBoardMediaSource(item.props.src);
|
|
20
|
+
if (!source) return null;
|
|
21
|
+
return source.kind === "url" ? `video-url:${encodeURIComponent(source.value)}` : `video:${encodeURIComponent(source.value)}:${item.props.snapshot?.mtimeMs ?? "unknown"}`;
|
|
22
|
+
}
|
|
23
|
+
return imageAssetKey(item);
|
|
24
|
+
}
|
|
25
|
+
function keySource(key) {
|
|
26
|
+
let baseKey = key;
|
|
27
|
+
if (key.startsWith("local:")) {
|
|
28
|
+
const separator = key.indexOf(":", 6);
|
|
29
|
+
if (separator < 0) return null;
|
|
30
|
+
baseKey = key.slice(separator + 1);
|
|
31
|
+
}
|
|
32
|
+
if (baseKey.startsWith("video-url:")) try {
|
|
33
|
+
return {
|
|
34
|
+
kind: "url",
|
|
35
|
+
media: "video",
|
|
36
|
+
value: decodeURIComponent(baseKey.slice(10))
|
|
37
|
+
};
|
|
38
|
+
} catch {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
if (baseKey.startsWith("video:")) {
|
|
42
|
+
const separator = baseKey.lastIndexOf(":");
|
|
43
|
+
if (separator <= 6) return null;
|
|
44
|
+
try {
|
|
45
|
+
return {
|
|
46
|
+
kind: "file",
|
|
47
|
+
media: "video",
|
|
48
|
+
value: decodeURIComponent(baseKey.slice(6, separator))
|
|
49
|
+
};
|
|
50
|
+
} catch {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const image = boardImageKeySource(baseKey);
|
|
55
|
+
return image ? {
|
|
56
|
+
...image,
|
|
57
|
+
media: "image"
|
|
58
|
+
} : null;
|
|
59
|
+
}
|
|
60
|
+
const MAX_RETRY_DELAY = 3e4;
|
|
61
|
+
function footprintOf(texture) {
|
|
62
|
+
if (!texture) return 0;
|
|
63
|
+
return (texture.width || 0) * (texture.height || 0) * 4;
|
|
64
|
+
}
|
|
65
|
+
function createBoardAssetManager(options) {
|
|
66
|
+
const concurrency = Math.max(1, options.concurrency ?? 4);
|
|
67
|
+
const videoConcurrency = Math.max(1, Math.min(options.videoConcurrency ?? 2, concurrency));
|
|
68
|
+
const budget = options.lruBudget ?? DEFAULT_LRU_BUDGET;
|
|
69
|
+
const now = options.now ?? (() => Date.now());
|
|
70
|
+
const imagePool = options.imageTexturePool ?? boardImageTexturePool;
|
|
71
|
+
const useSharedImagePool = !options.loadTexture && !options.unloadTexture;
|
|
72
|
+
const loadTexture = options.loadTexture ?? ((url, media) => media === "video" ? loadVideoThumbnailTexture(url) : useSharedImagePool ? imagePool.acquire(url) : loadBoardImageTexture(url));
|
|
73
|
+
const pendingUnloads = /* @__PURE__ */ new Map();
|
|
74
|
+
const unloadTexture = options.unloadTexture ?? (async (url, texture, media) => {
|
|
75
|
+
if (media === "video" || !url) {
|
|
76
|
+
texture?.destroy(true);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
if (useSharedImagePool) {
|
|
80
|
+
await imagePool.release(url);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
await unloadBoardImageTexture(url, texture);
|
|
84
|
+
});
|
|
85
|
+
function releaseTexture(key, url, texture) {
|
|
86
|
+
const media = keySource(key)?.media ?? "image";
|
|
87
|
+
const pendingKey = `${media}:${url}`;
|
|
88
|
+
const promise = unloadTexture(url, texture, media).catch(() => {}).then(() => {
|
|
89
|
+
if (url && pendingUnloads.get(pendingKey) === promise) pendingUnloads.delete(pendingKey);
|
|
90
|
+
});
|
|
91
|
+
if (url) pendingUnloads.set(pendingKey, promise);
|
|
92
|
+
}
|
|
93
|
+
const resolveFileUrl = options.resolveFileUrl;
|
|
94
|
+
const entries = /* @__PURE__ */ new Map();
|
|
95
|
+
const pathVersions = /* @__PURE__ */ new Map();
|
|
96
|
+
const resolvers = /* @__PURE__ */ new Map();
|
|
97
|
+
const inflight = /* @__PURE__ */ new Set();
|
|
98
|
+
const queue = [];
|
|
99
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
100
|
+
let active = 0;
|
|
101
|
+
let activeVideos = 0;
|
|
102
|
+
let disposed = false;
|
|
103
|
+
function notify() {
|
|
104
|
+
for (const listener of listeners) listener();
|
|
105
|
+
}
|
|
106
|
+
function managedAssetKey(item) {
|
|
107
|
+
const key = boardAssetKey(item);
|
|
108
|
+
if (!key) return null;
|
|
109
|
+
const source = keySource(key);
|
|
110
|
+
if (source?.media === "video" && options.loadVideoPreviews === false) return null;
|
|
111
|
+
if (source?.kind !== "file") return key;
|
|
112
|
+
const version = pathVersions.get(source.value) ?? 0;
|
|
113
|
+
return version > 0 ? `local:${version}:${key}` : key;
|
|
114
|
+
}
|
|
115
|
+
function clearRetry(entry) {
|
|
116
|
+
if (entry.retryTimer) {
|
|
117
|
+
clearTimeout(entry.retryTimer);
|
|
118
|
+
entry.retryTimer = null;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
function ensureEntry(key) {
|
|
122
|
+
let entry = entries.get(key);
|
|
123
|
+
if (!entry) {
|
|
124
|
+
entry = {
|
|
125
|
+
url: null,
|
|
126
|
+
texture: null,
|
|
127
|
+
refs: 0,
|
|
128
|
+
loading: false,
|
|
129
|
+
error: false,
|
|
130
|
+
attempts: 0,
|
|
131
|
+
retryAt: 0,
|
|
132
|
+
retryTimer: null,
|
|
133
|
+
lastUsedAt: now()
|
|
134
|
+
};
|
|
135
|
+
entries.set(key, entry);
|
|
136
|
+
}
|
|
137
|
+
return entry;
|
|
138
|
+
}
|
|
139
|
+
function scheduleRetry(key, entry) {
|
|
140
|
+
clearRetry(entry);
|
|
141
|
+
const delay = Math.max(0, entry.retryAt - now());
|
|
142
|
+
entry.retryTimer = setTimeout(() => {
|
|
143
|
+
entry.retryTimer = null;
|
|
144
|
+
if (disposed || entries.get(key) !== entry) return;
|
|
145
|
+
if (entry.refs <= 0 || entry.texture || entry.loading) return;
|
|
146
|
+
const getUrl = resolvers.get(key);
|
|
147
|
+
if (!getUrl) return;
|
|
148
|
+
entry.error = false;
|
|
149
|
+
if (!queue.some((task) => task.key === key)) {
|
|
150
|
+
queue.push({
|
|
151
|
+
key,
|
|
152
|
+
getUrl
|
|
153
|
+
});
|
|
154
|
+
pump();
|
|
155
|
+
}
|
|
156
|
+
}, delay);
|
|
157
|
+
}
|
|
158
|
+
function evict(key, entry) {
|
|
159
|
+
clearRetry(entry);
|
|
160
|
+
resolvers.delete(key);
|
|
161
|
+
if (entry.url || entry.texture) releaseTexture(key, entry.url ?? "", entry.texture);
|
|
162
|
+
entries.delete(key);
|
|
163
|
+
}
|
|
164
|
+
function trim() {
|
|
165
|
+
const cooling = [];
|
|
166
|
+
for (const [key, entry] of entries) if (entry.refs <= 0 && entry.texture && !entry.loading) cooling.push({
|
|
167
|
+
key,
|
|
168
|
+
lastUsedAt: entry.lastUsedAt,
|
|
169
|
+
bytes: footprintOf(entry.texture)
|
|
170
|
+
});
|
|
171
|
+
const evictions = selectLruEvictions(cooling, budget);
|
|
172
|
+
for (const key of evictions) {
|
|
173
|
+
const entry = entries.get(key);
|
|
174
|
+
if (entry) evict(key, entry);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
function settle(key, entry, texture, error) {
|
|
178
|
+
entry.loading = false;
|
|
179
|
+
inflight.delete(key);
|
|
180
|
+
active -= 1;
|
|
181
|
+
if (keySource(key)?.media === "video") activeVideos -= 1;
|
|
182
|
+
if (entries.get(key) !== entry) {
|
|
183
|
+
if (texture) releaseTexture(key, entry.url ?? "", texture);
|
|
184
|
+
if (!disposed) pump();
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
if (texture) {
|
|
188
|
+
entry.error = false;
|
|
189
|
+
entry.attempts = 0;
|
|
190
|
+
entry.texture = texture;
|
|
191
|
+
entry.lastUsedAt = now();
|
|
192
|
+
if (entry.refs <= 0) trim();
|
|
193
|
+
else notify();
|
|
194
|
+
} else {
|
|
195
|
+
entry.error = true;
|
|
196
|
+
entry.attempts += 1;
|
|
197
|
+
entry.retryAt = now() + Math.min(1e3 * 2 ** entry.attempts, MAX_RETRY_DELAY);
|
|
198
|
+
console.warn(`[board] failed to load preview for ${key}`, error);
|
|
199
|
+
if (entry.refs <= 0) evict(key, entry);
|
|
200
|
+
else {
|
|
201
|
+
scheduleRetry(key, entry);
|
|
202
|
+
notify();
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
if (!disposed) pump();
|
|
206
|
+
}
|
|
207
|
+
function pump() {
|
|
208
|
+
while (!disposed && active < concurrency && queue.length > 0) {
|
|
209
|
+
const taskIndex = queue.findIndex(({ key }) => {
|
|
210
|
+
return keySource(key)?.media !== "video" || activeVideos < videoConcurrency;
|
|
211
|
+
});
|
|
212
|
+
if (taskIndex < 0) return;
|
|
213
|
+
const [task] = queue.splice(taskIndex, 1);
|
|
214
|
+
if (!task) continue;
|
|
215
|
+
const entry = entries.get(task.key);
|
|
216
|
+
const source = keySource(task.key);
|
|
217
|
+
if (!entry || !source || entry.texture || entry.loading) continue;
|
|
218
|
+
entry.loading = true;
|
|
219
|
+
inflight.add(task.key);
|
|
220
|
+
active += 1;
|
|
221
|
+
if (source.media === "video") activeVideos += 1;
|
|
222
|
+
task.getUrl().then(async (url) => {
|
|
223
|
+
if (!url || disposed) return null;
|
|
224
|
+
entry.url = url;
|
|
225
|
+
const pendingKey = `${source.media}:${url}`;
|
|
226
|
+
const pending = pendingUnloads.get(pendingKey);
|
|
227
|
+
if (pending) await pending;
|
|
228
|
+
if (disposed) return null;
|
|
229
|
+
return loadTexture(url, source.media);
|
|
230
|
+
}).then((texture) => settle(task.key, entry, texture ?? null), (error) => settle(task.key, entry, null, error));
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
const manager = {
|
|
234
|
+
assetKey: managedAssetKey,
|
|
235
|
+
invalidatePath(path) {
|
|
236
|
+
if (disposed) return;
|
|
237
|
+
if (![...entries.keys()].some((key) => {
|
|
238
|
+
const source = keySource(key);
|
|
239
|
+
return source?.kind === "file" && source.value === path;
|
|
240
|
+
})) return;
|
|
241
|
+
pathVersions.set(path, (pathVersions.get(path) ?? 0) + 1);
|
|
242
|
+
notify();
|
|
243
|
+
},
|
|
244
|
+
requestItem(item) {
|
|
245
|
+
if (disposed) return;
|
|
246
|
+
const key = managedAssetKey(item);
|
|
247
|
+
if (!key) return;
|
|
248
|
+
const source = keySource(key);
|
|
249
|
+
if (!source) return;
|
|
250
|
+
const entry = ensureEntry(key);
|
|
251
|
+
entry.lastUsedAt = now();
|
|
252
|
+
if (entry.texture || entry.loading) return;
|
|
253
|
+
if (entry.error && now() < entry.retryAt) return;
|
|
254
|
+
entry.error = false;
|
|
255
|
+
clearRetry(entry);
|
|
256
|
+
if (queue.some((queued) => queued.key === key)) return;
|
|
257
|
+
const getUrl = source.kind === "url" ? () => Promise.resolve(source.value) : () => resolveFileUrl(source.value);
|
|
258
|
+
resolvers.set(key, getUrl);
|
|
259
|
+
queue.push({
|
|
260
|
+
key,
|
|
261
|
+
getUrl
|
|
262
|
+
});
|
|
263
|
+
pump();
|
|
264
|
+
},
|
|
265
|
+
getTexture(key) {
|
|
266
|
+
const entry = entries.get(key);
|
|
267
|
+
if (!entry?.texture) return null;
|
|
268
|
+
entry.lastUsedAt = now();
|
|
269
|
+
return entry.texture;
|
|
270
|
+
},
|
|
271
|
+
getNaturalSize(key) {
|
|
272
|
+
const texture = manager.getTexture(key);
|
|
273
|
+
if (!texture) return null;
|
|
274
|
+
return videoTextureNaturalSize(texture) ?? {
|
|
275
|
+
width: texture.width,
|
|
276
|
+
height: texture.height
|
|
277
|
+
};
|
|
278
|
+
},
|
|
279
|
+
hasError(key) {
|
|
280
|
+
return entries.get(key)?.error ?? false;
|
|
281
|
+
},
|
|
282
|
+
acquire(key) {
|
|
283
|
+
const entry = ensureEntry(key);
|
|
284
|
+
entry.refs += 1;
|
|
285
|
+
entry.lastUsedAt = now();
|
|
286
|
+
},
|
|
287
|
+
release(key) {
|
|
288
|
+
const entry = entries.get(key);
|
|
289
|
+
if (!entry) return;
|
|
290
|
+
entry.refs -= 1;
|
|
291
|
+
if (entry.refs > 0) return;
|
|
292
|
+
entry.lastUsedAt = now();
|
|
293
|
+
clearRetry(entry);
|
|
294
|
+
if (entry.loading) return;
|
|
295
|
+
if (!entry.texture) evict(key, entry);
|
|
296
|
+
else trim();
|
|
297
|
+
},
|
|
298
|
+
async withTextures(items, use, loadOptions) {
|
|
299
|
+
const keys = /* @__PURE__ */ new Set();
|
|
300
|
+
if (!disposed) for (const item of items) {
|
|
301
|
+
const key = managedAssetKey(item);
|
|
302
|
+
if (key) keys.add(key);
|
|
303
|
+
}
|
|
304
|
+
const textures = /* @__PURE__ */ new Map();
|
|
305
|
+
if (keys.size === 0) return use(textures);
|
|
306
|
+
for (const key of keys) manager.acquire(key);
|
|
307
|
+
try {
|
|
308
|
+
for (const item of items) manager.requestItem(item);
|
|
309
|
+
const settled = () => [...keys].every((key) => {
|
|
310
|
+
const entry = entries.get(key);
|
|
311
|
+
return Boolean(entry?.texture) || Boolean(entry?.error);
|
|
312
|
+
});
|
|
313
|
+
if (!settled()) {
|
|
314
|
+
const timeoutMs = loadOptions?.timeoutMs ?? 15e3;
|
|
315
|
+
await new Promise((resolve) => {
|
|
316
|
+
const timer = setTimeout(finish, timeoutMs);
|
|
317
|
+
const unsubscribe = manager.subscribe(() => {
|
|
318
|
+
if (settled()) finish();
|
|
319
|
+
});
|
|
320
|
+
function finish() {
|
|
321
|
+
clearTimeout(timer);
|
|
322
|
+
unsubscribe();
|
|
323
|
+
resolve();
|
|
324
|
+
}
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
for (const key of keys) {
|
|
328
|
+
const texture = entries.get(key)?.texture;
|
|
329
|
+
if (texture) textures.set(key, texture);
|
|
330
|
+
}
|
|
331
|
+
return await use(textures);
|
|
332
|
+
} finally {
|
|
333
|
+
for (const key of keys) manager.release(key);
|
|
334
|
+
}
|
|
335
|
+
},
|
|
336
|
+
subscribe(listener) {
|
|
337
|
+
listeners.add(listener);
|
|
338
|
+
return () => {
|
|
339
|
+
listeners.delete(listener);
|
|
340
|
+
};
|
|
341
|
+
},
|
|
342
|
+
destroy() {
|
|
343
|
+
disposed = true;
|
|
344
|
+
queue.length = 0;
|
|
345
|
+
listeners.clear();
|
|
346
|
+
pathVersions.clear();
|
|
347
|
+
resolvers.clear();
|
|
348
|
+
for (const [key, entry] of entries) evict(key, entry);
|
|
349
|
+
entries.clear();
|
|
350
|
+
}
|
|
351
|
+
};
|
|
352
|
+
return manager;
|
|
353
|
+
}
|
|
354
|
+
//#endregion
|
|
355
|
+
export { boardAssetKey, createBoardAssetManager };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { DEFAULT_LRU_BUDGET, LruBudget, LruEntry, selectLruEvictions } from "./lru.js";
|
|
2
|
+
import { BoardImageTexturePool, boardImageTexturePool, createBoardImageTexturePool, loadBoardImageTexture, unloadBoardImageTexture } from "./texture-pool.js";
|
|
3
|
+
import { BoardAssetManager, BoardAssetManagerOptions, boardAssetKey, createBoardAssetManager } from "./assets.js";
|
|
4
|
+
import { toBoardPointerEvent } from "./pointer.js";
|
|
5
|
+
import { BoardLiveStroke, BoardScene, BoardSceneNode, SceneOverlayInput, SceneSyncInput, createBoardScene } from "./scene.js";
|
|
6
|
+
import { BoardItemView, BoardItemViewContext, BoardItemViewInstance, BoardItemViewLayer, createBoardItemViewLayer } from "./views.js";
|
|
7
|
+
import { BoardStage, BoardStageOptions, BoardStagePointerPhase, BoardStageTheme, mountBoardStage } from "./stage.js";
|
|
8
|
+
export { BoardAssetManager, BoardAssetManagerOptions, BoardImageTexturePool, BoardItemView, BoardItemViewContext, BoardItemViewInstance, BoardItemViewLayer, BoardLiveStroke, BoardScene, BoardSceneNode, BoardStage, BoardStageOptions, BoardStagePointerPhase, BoardStageTheme, DEFAULT_LRU_BUDGET, LruBudget, LruEntry, SceneOverlayInput, SceneSyncInput, boardAssetKey, boardImageTexturePool, createBoardAssetManager, createBoardImageTexturePool, createBoardItemViewLayer, createBoardScene, loadBoardImageTexture, mountBoardStage, selectLruEvictions, toBoardPointerEvent, unloadBoardImageTexture };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { DEFAULT_LRU_BUDGET, selectLruEvictions } from "./lru.js";
|
|
2
|
+
import { boardImageTexturePool, createBoardImageTexturePool, loadBoardImageTexture, unloadBoardImageTexture } from "./texture-pool.js";
|
|
3
|
+
import { boardAssetKey, createBoardAssetManager } from "./assets.js";
|
|
4
|
+
import { toBoardPointerEvent } from "./pointer.js";
|
|
5
|
+
import { createBoardScene } from "./scene.js";
|
|
6
|
+
import { createBoardItemViewLayer } from "./views.js";
|
|
7
|
+
import { mountBoardStage } from "./stage.js";
|
|
8
|
+
export { DEFAULT_LRU_BUDGET, boardAssetKey, boardImageTexturePool, createBoardAssetManager, createBoardImageTexturePool, createBoardItemViewLayer, createBoardScene, loadBoardImageTexture, mountBoardStage, selectLruEvictions, toBoardPointerEvent, unloadBoardImageTexture };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
//#region src/board/stage/lru.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Pure eviction policy for the board texture LRU cache.
|
|
4
|
+
*
|
|
5
|
+
* Textures whose cards have scrolled off-screen are not freed immediately:
|
|
6
|
+
* they enter a "cooling" pool (reference count zero but still on the GPU) so a
|
|
7
|
+
* quick pan back is instant instead of re-fetching. The pool is bounded by
|
|
8
|
+
* both entry count and approximate GPU bytes; when a budget is exceeded the least
|
|
9
|
+
* recently used entries are evicted first. Keeping this decision pure makes the
|
|
10
|
+
* release-vs-churn trade-off trivial to test.
|
|
11
|
+
*/
|
|
12
|
+
type LruEntry = {
|
|
13
|
+
key: string;
|
|
14
|
+
/** Timestamp (ms) the entry was last used; smaller = older = evict first. */
|
|
15
|
+
lastUsedAt: number;
|
|
16
|
+
/** Approximate GPU footprint in bytes. */
|
|
17
|
+
bytes: number;
|
|
18
|
+
};
|
|
19
|
+
type LruBudget = {
|
|
20
|
+
maxCount: number;
|
|
21
|
+
maxBytes: number;
|
|
22
|
+
};
|
|
23
|
+
/** Default cooling-pool ceiling: 64 images or ~128 MB of GPU memory. */
|
|
24
|
+
declare const DEFAULT_LRU_BUDGET: LruBudget;
|
|
25
|
+
/**
|
|
26
|
+
* Given the unreferenced (cooling) entries and a budget, return the keys to
|
|
27
|
+
* evict — oldest first — so the remaining pool fits within both limits. Returns
|
|
28
|
+
* an empty array when the pool already fits.
|
|
29
|
+
*/
|
|
30
|
+
declare function selectLruEvictions(entries: LruEntry[], budget: LruBudget): string[];
|
|
31
|
+
//#endregion
|
|
32
|
+
export { DEFAULT_LRU_BUDGET, LruBudget, LruEntry, selectLruEvictions };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
//#region src/board/stage/lru.ts
|
|
2
|
+
/** Default cooling-pool ceiling: 64 images or ~128 MB of GPU memory. */
|
|
3
|
+
const DEFAULT_LRU_BUDGET = {
|
|
4
|
+
maxCount: 64,
|
|
5
|
+
maxBytes: 134217728
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* Given the unreferenced (cooling) entries and a budget, return the keys to
|
|
9
|
+
* evict — oldest first — so the remaining pool fits within both limits. Returns
|
|
10
|
+
* an empty array when the pool already fits.
|
|
11
|
+
*/
|
|
12
|
+
function selectLruEvictions(entries, budget) {
|
|
13
|
+
if (entries.length === 0) return [];
|
|
14
|
+
let count = entries.length;
|
|
15
|
+
let bytes = 0;
|
|
16
|
+
for (const entry of entries) bytes += entry.bytes;
|
|
17
|
+
if (count <= budget.maxCount && bytes <= budget.maxBytes) return [];
|
|
18
|
+
const sorted = [...entries].sort((a, b) => a.lastUsedAt - b.lastUsedAt);
|
|
19
|
+
const evict = [];
|
|
20
|
+
for (const entry of sorted) {
|
|
21
|
+
if (count <= budget.maxCount && bytes <= budget.maxBytes) break;
|
|
22
|
+
evict.push(entry.key);
|
|
23
|
+
count -= 1;
|
|
24
|
+
bytes -= entry.bytes;
|
|
25
|
+
}
|
|
26
|
+
return evict;
|
|
27
|
+
}
|
|
28
|
+
//#endregion
|
|
29
|
+
export { DEFAULT_LRU_BUDGET, selectLruEvictions };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Ticker } from "pixi.js";
|
|
2
|
+
//#region src/board/stage/maintenance.ts
|
|
3
|
+
function scheduleBoardMaintenance(renderer) {
|
|
4
|
+
renderer.events.setTargetElement(null);
|
|
5
|
+
const scheduler = renderer.scheduler;
|
|
6
|
+
Ticker.system.remove(scheduler._update, scheduler);
|
|
7
|
+
const timer = setInterval(() => scheduler._update(), 1e3);
|
|
8
|
+
return () => clearInterval(timer);
|
|
9
|
+
}
|
|
10
|
+
//#endregion
|
|
11
|
+
export { scheduleBoardMaintenance };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { BoardViewport } from "../model/geometry.js";
|
|
2
|
+
import "../model/index.js";
|
|
3
|
+
import { BoardPointerEvent } from "../editor/interaction.js";
|
|
4
|
+
import "../editor/index.js";
|
|
5
|
+
//#region src/board/stage/pointer.d.ts
|
|
6
|
+
declare function toBoardPointerEvent(event: PointerEvent, rect: DOMRect, camera: BoardViewport, coalesce?: boolean): BoardPointerEvent;
|
|
7
|
+
//#endregion
|
|
8
|
+
export { toBoardPointerEvent };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { pointToWorld, screenPoint } from "../model/geometry.js";
|
|
2
|
+
//#region src/board/stage/pointer.ts
|
|
3
|
+
function pressureOf(event) {
|
|
4
|
+
return event.pointerType === "pen" && event.pressure > 0 ? event.pressure : .5;
|
|
5
|
+
}
|
|
6
|
+
function screenOf(event, rect) {
|
|
7
|
+
return screenPoint(event.clientX - rect.left, event.clientY - rect.top);
|
|
8
|
+
}
|
|
9
|
+
function toBoardPointerEvent(event, rect, camera, coalesce = false) {
|
|
10
|
+
const screen = screenOf(event, rect);
|
|
11
|
+
const coalesced = coalesce ? event.getCoalescedEvents?.() : void 0;
|
|
12
|
+
const samples = coalesced && coalesced.length > 1 ? coalesced.map((sample) => ({
|
|
13
|
+
world: pointToWorld(screenOf(sample, rect), camera),
|
|
14
|
+
pressure: pressureOf(sample)
|
|
15
|
+
})) : void 0;
|
|
16
|
+
return {
|
|
17
|
+
pointerId: event.pointerId,
|
|
18
|
+
screen,
|
|
19
|
+
world: pointToWorld(screen, camera),
|
|
20
|
+
shiftKey: event.shiftKey,
|
|
21
|
+
metaKey: event.metaKey,
|
|
22
|
+
ctrlKey: event.ctrlKey,
|
|
23
|
+
altKey: event.altKey,
|
|
24
|
+
button: event.button,
|
|
25
|
+
buttons: event.buttons,
|
|
26
|
+
pointerType: event.pointerType,
|
|
27
|
+
cancelled: event.type === "pointercancel" || event.type === "lostpointercapture",
|
|
28
|
+
pressure: pressureOf(event),
|
|
29
|
+
...samples ? { samples } : {}
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
//#endregion
|
|
33
|
+
export { toBoardPointerEvent };
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { BoardDrawPoint } from "../../protocol/dist/board-model.js";
|
|
2
|
+
import { BoardFrame } from "../../protocol/dist/board-layout.js";
|
|
3
|
+
import { Rect, WorldPoint } from "../model/geometry.js";
|
|
4
|
+
import { BoardScene as BoardScene$1, BoardSceneItem } from "../model/scene.js";
|
|
5
|
+
import "../model/index.js";
|
|
6
|
+
import { BoardSelectionTransform, BoardTransformControl } from "../editor/selection-transform.js";
|
|
7
|
+
import "../editor/index.js";
|
|
8
|
+
import { BoardCardRendererResolver, BoardRenderContext, BoardRenderPalette } from "../render/renderers/board-renderer-registry.js";
|
|
9
|
+
import "../render/index.js";
|
|
10
|
+
import { Container, Graphics } from "pixi.js";
|
|
11
|
+
//#region src/board/stage/scene.d.ts
|
|
12
|
+
type BoardLiveStroke = {
|
|
13
|
+
id: string;
|
|
14
|
+
points: BoardDrawPoint[];
|
|
15
|
+
color: string;
|
|
16
|
+
size: number;
|
|
17
|
+
};
|
|
18
|
+
type SceneSyncInput = {
|
|
19
|
+
items: readonly BoardSceneItem[];
|
|
20
|
+
strokes: readonly BoardLiveStroke[];
|
|
21
|
+
scene: BoardScene$1;
|
|
22
|
+
context: BoardRenderContext;
|
|
23
|
+
getItem: (id: string) => BoardSceneItem | null;
|
|
24
|
+
visibleIds: Set<string> | null;
|
|
25
|
+
pinnedIds: Set<string>;
|
|
26
|
+
globalSig: string;
|
|
27
|
+
structureVersion: number;
|
|
28
|
+
geometryVersion: number;
|
|
29
|
+
gestureActive: boolean;
|
|
30
|
+
};
|
|
31
|
+
type SceneOverlayInput = {
|
|
32
|
+
zoom: number;
|
|
33
|
+
pointerType: string;
|
|
34
|
+
marquee: Rect | null;
|
|
35
|
+
selection: string[];
|
|
36
|
+
transform: BoardSelectionTransform | null;
|
|
37
|
+
controls: boolean;
|
|
38
|
+
hoveredControl: BoardTransformControl | null;
|
|
39
|
+
rotationPointer: WorldPoint | null;
|
|
40
|
+
arrowEndpoints?: Array<{
|
|
41
|
+
x: number;
|
|
42
|
+
y: number;
|
|
43
|
+
}>;
|
|
44
|
+
ports?: ReadonlyArray<{
|
|
45
|
+
x: number;
|
|
46
|
+
y: number;
|
|
47
|
+
radius: number;
|
|
48
|
+
}>;
|
|
49
|
+
hoveredPort?: {
|
|
50
|
+
x: number;
|
|
51
|
+
y: number;
|
|
52
|
+
} | null;
|
|
53
|
+
arrowDraft?: {
|
|
54
|
+
from: {
|
|
55
|
+
x: number;
|
|
56
|
+
y: number;
|
|
57
|
+
};
|
|
58
|
+
to: {
|
|
59
|
+
x: number;
|
|
60
|
+
y: number;
|
|
61
|
+
};
|
|
62
|
+
size: number;
|
|
63
|
+
targetFrame: BoardFrame | null;
|
|
64
|
+
} | null;
|
|
65
|
+
bindTarget?: BoardFrame | null;
|
|
66
|
+
};
|
|
67
|
+
type BoardSceneNode = {
|
|
68
|
+
item: BoardSceneItem;
|
|
69
|
+
container: Container;
|
|
70
|
+
};
|
|
71
|
+
type BoardScene = {
|
|
72
|
+
sync: (input: SceneSyncInput) => void;
|
|
73
|
+
getNode: (itemId: string) => BoardSceneNode | null;
|
|
74
|
+
readonly animated: boolean;
|
|
75
|
+
drawOverlay: (input: SceneOverlayInput, palette: BoardRenderPalette) => void;
|
|
76
|
+
destroy: (context: BoardRenderContext) => void;
|
|
77
|
+
};
|
|
78
|
+
declare function createBoardScene(options: {
|
|
79
|
+
world: Container;
|
|
80
|
+
overlay: Graphics;
|
|
81
|
+
getRenderer: BoardCardRendererResolver;
|
|
82
|
+
onFarLayerFrame?: () => void;
|
|
83
|
+
}): BoardScene;
|
|
84
|
+
//#endregion
|
|
85
|
+
export { BoardLiveStroke, BoardScene, BoardSceneNode, SceneOverlayInput, SceneSyncInput, createBoardScene };
|