@neta-art/cohub 12.1.0 → 12.2.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 +8 -5
- package/dist/board/editor/editor.d.ts +1 -0
- package/dist/board/editor/editor.js +4 -0
- package/dist/board/editor/snapshots.js +5 -57
- package/dist/board/export/index.d.ts +2 -2
- package/dist/board/export/node/fonts.js +4 -0
- package/dist/board/export/node/index.d.ts +3 -0
- package/dist/board/export/node/index.js +13 -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 +16 -0
- package/dist/board/model/asset-key.js +92 -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 +7 -5
- package/dist/board/model/index.js +9 -7
- package/dist/board/model/items/create.js +1 -1
- 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/text-metrics.d.ts +8 -3
- package/dist/board/model/text-metrics.js +13 -2
- package/dist/board/model/themes.d.ts +488 -0
- package/dist/board/model/themes.js +495 -0
- package/dist/board/render/card-tag.js +2 -2
- package/dist/board/render/index.d.ts +6 -4
- package/dist/board/render/index.js +5 -3
- package/dist/board/render/lod.d.ts +22 -0
- package/dist/board/render/lod.js +27 -0
- package/dist/board/render/media-interaction.js +1 -1
- package/dist/board/render/palette.js +2 -25
- package/dist/board/render/renderers/audio-card-renderer.js +2 -1
- package/dist/board/render/renderers/board-renderer-registry.d.ts +10 -0
- package/dist/board/render/renderers/custom-card-renderer.js +5 -5
- package/dist/board/render/renderers/far-plate.d.ts +11 -1
- package/dist/board/render/renderers/far-plate.js +27 -8
- package/dist/board/render/renderers/file-card-renderer.js +3 -5
- package/dist/board/render/renderers/image-card-renderer.js +32 -6
- package/dist/board/render/renderers/model-card-renderer.js +1 -1
- package/dist/board/render/renderers/shape-card-renderer.js +25 -24
- package/dist/board/render/renderers/task-card-renderer.js +7 -8
- package/dist/board/render/renderers/text-card-renderer.js +86 -41
- package/dist/board/render/renderers/video-card-renderer.js +2 -1
- package/dist/board/render/style.d.ts +7 -2
- package/dist/board/render/style.js +5 -1
- package/dist/board/render/surface.d.ts +5 -0
- package/dist/board/render/surface.js +13 -3
- package/dist/board/render/text-fit.js +8 -12
- package/dist/board/render/text-measurement.d.ts +15 -1
- package/dist/board/render/text-measurement.js +113 -20
- 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/stage/assets.d.ts +15 -4
- package/dist/board/stage/assets.js +155 -89
- package/dist/board/stage/index.d.ts +5 -5
- package/dist/board/stage/index.js +4 -4
- package/dist/board/stage/lru.d.ts +13 -9
- package/dist/board/stage/lru.js +38 -20
- package/dist/board/stage/scene.d.ts +11 -1
- package/dist/board/stage/scene.js +213 -138
- package/dist/board/stage/stage.d.ts +12 -1
- package/dist/board/stage/stage.js +97 -24
- package/dist/board/stage/texture-pool.d.ts +8 -1
- package/dist/board/stage/texture-pool.js +37 -1
- package/dist/chunks/app-realtime.js +1 -1
- package/dist/chunks/{transport.js → http-error.js} +37 -1
- package/dist/chunks/http.js +30 -10
- package/dist/http.js +1 -1
- package/dist/index.js +139 -131
- 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/core/media.d.ts +35 -1
- package/dist/protocol/dist/core/media.js +123 -1
- package/dist/protocol/dist/index.d.ts +2 -2
- package/dist/websocket.js +951 -1
- package/package.json +3 -2
- package/dist/board/model/image-key.d.ts +0 -9
- package/dist/board/model/image-key.js +0 -33
- package/dist/chunks/websocket.js +0 -988
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import { parseBoardItem } from "../../protocol/dist/board-model.js";
|
|
2
2
|
import { drawPointsBounds } from "../../protocol/dist/board-layout.js";
|
|
3
|
+
import { itemBounds, rectsIntersect } from "../model/geometry.js";
|
|
4
|
+
import { DEFAULT_BOARD_LOD, boardCardDetailed } from "../render/lod.js";
|
|
3
5
|
import { textZoomBucket } from "../render/text-resolution.js";
|
|
4
6
|
import { clippingAncestor, isClippingFrame, syncClipGroup } from "../render/clip.js";
|
|
5
|
-
import { Graphics } from "pixi.js";
|
|
7
|
+
import { Graphics, Text } from "pixi.js";
|
|
6
8
|
//#region src/board/stage/scene.ts
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
const
|
|
9
|
+
const FAR_CHUNK = 256;
|
|
10
|
+
const FAR_PAINT_BUDGET = 2400;
|
|
11
|
+
const MATERIALIZE_BATCH = 48;
|
|
12
|
+
const LOD_ZOOM_STEPS = 8;
|
|
11
13
|
const RESOLUTION_BATCH = 32;
|
|
12
|
-
const Z_FAR_LAYER = -1;
|
|
13
14
|
const Z_LIFTED = 1e9;
|
|
14
15
|
const Z_LIVE_STROKE = Number.MAX_SAFE_INTEGER - 1;
|
|
15
16
|
const Z_OVERLAY = Number.MAX_SAFE_INTEGER;
|
|
@@ -24,6 +25,9 @@ function idSetSignature(ids) {
|
|
|
24
25
|
}
|
|
25
26
|
return hash;
|
|
26
27
|
}
|
|
28
|
+
function sameItems(a, b) {
|
|
29
|
+
return a.length === b.length && a.every((item, index) => item === b[index]);
|
|
30
|
+
}
|
|
27
31
|
function createBoardScene(options) {
|
|
28
32
|
const { world, overlay, getRenderer } = options;
|
|
29
33
|
world.sortableChildren = true;
|
|
@@ -36,8 +40,7 @@ function createBoardScene(options) {
|
|
|
36
40
|
const heldKeys = /* @__PURE__ */ new Map();
|
|
37
41
|
const holders = /* @__PURE__ */ new Map();
|
|
38
42
|
let orderById = /* @__PURE__ */ new Map();
|
|
39
|
-
let
|
|
40
|
-
let farActive = false;
|
|
43
|
+
let idsByOrder = [];
|
|
41
44
|
let lastStructureVersion = -1;
|
|
42
45
|
let lastZoom = NaN;
|
|
43
46
|
let lastResolution = "";
|
|
@@ -105,6 +108,11 @@ function createBoardScene(options) {
|
|
|
105
108
|
const host = hostFor(entry.item, scene);
|
|
106
109
|
if (entry.container.parent !== host) host.addChild(entry.container);
|
|
107
110
|
}
|
|
111
|
+
for (const plate of plates.values()) {
|
|
112
|
+
const first = plate.items[0];
|
|
113
|
+
const host = first && hostFor(first, scene);
|
|
114
|
+
if (host && plate.graphics.parent !== host) host.addChild(plate.graphics);
|
|
115
|
+
}
|
|
108
116
|
for (const [id, entry] of clips) {
|
|
109
117
|
const frame = scene.get(id);
|
|
110
118
|
if (!frame || !isClippingFrame(frame) || entry.group.children.length <= 1) {
|
|
@@ -121,6 +129,7 @@ function createBoardScene(options) {
|
|
|
121
129
|
const pooled = takeFromPool(renderer.id);
|
|
122
130
|
const container = pooled ?? renderer.create(item, context);
|
|
123
131
|
container.visible = true;
|
|
132
|
+
container.culled = false;
|
|
124
133
|
if (pooled) renderer.update(container, item, context);
|
|
125
134
|
container.alpha = scene.opacity(item.id);
|
|
126
135
|
hostFor(item, scene).addChild(container);
|
|
@@ -141,79 +150,173 @@ function createBoardScene(options) {
|
|
|
141
150
|
renderer.zoom?.(container, context.zoom);
|
|
142
151
|
return entry;
|
|
143
152
|
}
|
|
144
|
-
|
|
145
|
-
let
|
|
146
|
-
let
|
|
147
|
-
let
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
const
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
return
|
|
153
|
+
const lodSizes = /* @__PURE__ */ new WeakMap();
|
|
154
|
+
let nearIds = EMPTY_IDS;
|
|
155
|
+
let plan = null;
|
|
156
|
+
let planInputs = [];
|
|
157
|
+
/** Visible ids in z-order; sorting indices beats comparing ids through the map. */
|
|
158
|
+
function orderedIds(visibleIds) {
|
|
159
|
+
if (visibleIds === null) return idsByOrder;
|
|
160
|
+
const order = new Int32Array(visibleIds.size);
|
|
161
|
+
let length = 0;
|
|
162
|
+
for (const id of visibleIds) {
|
|
163
|
+
const index = orderById.get(id);
|
|
164
|
+
if (index !== void 0) order[length++] = index;
|
|
165
|
+
}
|
|
166
|
+
return Array.from(order.subarray(0, length).sort(), (index) => idsByOrder[index]);
|
|
158
167
|
}
|
|
159
|
-
function
|
|
160
|
-
|
|
161
|
-
if (
|
|
162
|
-
|
|
168
|
+
function lodSize(renderer, item) {
|
|
169
|
+
let size = lodSizes.get(item);
|
|
170
|
+
if (size === void 0) {
|
|
171
|
+
size = renderer.lod?.size?.(item) ?? Math.max(item.frame.width, item.frame.height);
|
|
172
|
+
lodSizes.set(item, size);
|
|
173
|
+
}
|
|
174
|
+
return size;
|
|
163
175
|
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
const
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
176
|
+
/**
|
|
177
|
+
* Split the visible cards into live ones and z-ordered chunks of far stand-ins.
|
|
178
|
+
* A card is live while it is legible on screen, until the live budget keeps
|
|
179
|
+
* the on-screen ones nearest the focus; pinned cards and cards without a far
|
|
180
|
+
* stand-in stay live.
|
|
181
|
+
*/
|
|
182
|
+
function lodPlan(input) {
|
|
183
|
+
const { visibleIds, context, getItem, pinnedIds, scene } = input;
|
|
184
|
+
const lod = input.lod ?? DEFAULT_BOARD_LOD;
|
|
185
|
+
const step = Math.round(Math.log2(Math.max(context.zoom, 1e-4)) * LOD_ZOOM_STEPS);
|
|
186
|
+
const cell = input.view && input.focus && plan?.limited ? input.view.width / 8 : 0;
|
|
187
|
+
const at = cell ? `${Math.round((input.focus?.x ?? 0) / cell)},${Math.round((input.focus?.y ?? 0) / cell)}` : "";
|
|
188
|
+
const inputs = [
|
|
189
|
+
visibleIds,
|
|
190
|
+
input.structureVersion,
|
|
191
|
+
idSetSignature(pinnedIds),
|
|
192
|
+
step,
|
|
193
|
+
at,
|
|
194
|
+
lod.media,
|
|
195
|
+
lod.text,
|
|
196
|
+
lod.card,
|
|
197
|
+
lod.live
|
|
198
|
+
];
|
|
199
|
+
const reusable = plan && inputs.every((value, index) => value === planInputs[index]);
|
|
200
|
+
if (plan && reusable && !plan.deferred && (input.gestureActive || plan.scene === scene)) return plan;
|
|
201
|
+
planInputs = inputs;
|
|
202
|
+
const ids = orderedIds(visibleIds);
|
|
203
|
+
const live = /* @__PURE__ */ new Set();
|
|
204
|
+
const near = [];
|
|
205
|
+
for (const id of ids) {
|
|
206
|
+
const item = getItem(id);
|
|
207
|
+
if (!item) continue;
|
|
208
|
+
const renderer = getRenderer(item, context);
|
|
209
|
+
if (pinnedIds.has(id) || !renderer.renderFar) {
|
|
210
|
+
live.add(id);
|
|
211
|
+
continue;
|
|
185
212
|
}
|
|
186
|
-
if (
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
213
|
+
if (lodSize(renderer, item) * context.zoom / lod[renderer.lod?.kind ?? "card"] >= (nearIds.has(id) ? .8 : 1)) near.push(item);
|
|
214
|
+
}
|
|
215
|
+
const room = Math.max(0, lod.live - live.size);
|
|
216
|
+
const limited = near.length > room;
|
|
217
|
+
const { focus, view } = input;
|
|
218
|
+
const ranked = focus && (limited || near.length > MATERIALIZE_BATCH) ? near.map(({ id, frame }) => ({
|
|
219
|
+
id,
|
|
220
|
+
offscreen: view && !rectsIntersect(itemBounds(frame), view) ? 1 : 0,
|
|
221
|
+
distance: (frame.x + frame.width / 2 - focus.x) ** 2 + (frame.y + frame.height / 2 - focus.y) ** 2
|
|
222
|
+
})).sort((a, b) => a.offscreen - b.offscreen || a.distance - b.distance) : near;
|
|
223
|
+
let admitted = 0;
|
|
224
|
+
let deferred = false;
|
|
225
|
+
for (const { id } of ranked.slice(0, room)) if (!cards.has(id) && admitted++ >= MATERIALIZE_BATCH) deferred = true;
|
|
226
|
+
else live.add(id);
|
|
227
|
+
if (deferred) options.requestFrame?.();
|
|
228
|
+
nearIds = live;
|
|
229
|
+
const runs = /* @__PURE__ */ new Map();
|
|
230
|
+
const far = /* @__PURE__ */ new Set();
|
|
231
|
+
for (const id of ids) {
|
|
232
|
+
const item = getItem(id);
|
|
233
|
+
if (!item || live.has(id)) continue;
|
|
234
|
+
const order = orderById.get(id) ?? 0;
|
|
235
|
+
const key = `${Math.floor(order / FAR_CHUNK)}:${clippingAncestor(scene, item) ?? ""}`;
|
|
236
|
+
const run = runs.get(key);
|
|
237
|
+
if (run) run.items.push(item);
|
|
238
|
+
else runs.set(key, {
|
|
239
|
+
key,
|
|
240
|
+
order,
|
|
241
|
+
items: [item]
|
|
242
|
+
});
|
|
243
|
+
far.add(id);
|
|
244
|
+
}
|
|
245
|
+
plan = {
|
|
246
|
+
scene,
|
|
247
|
+
live,
|
|
248
|
+
far,
|
|
249
|
+
runs,
|
|
250
|
+
limited,
|
|
251
|
+
deferred
|
|
252
|
+
};
|
|
253
|
+
return plan;
|
|
254
|
+
}
|
|
255
|
+
const plates = /* @__PURE__ */ new Map();
|
|
256
|
+
let paintedPlan = null;
|
|
257
|
+
let paintedSig = "";
|
|
258
|
+
let covered = EMPTY_IDS;
|
|
259
|
+
/**
|
|
260
|
+
* Repaint the plates whose cards changed, within a per-frame budget. A card
|
|
261
|
+
* is covered once a plate draws it; until then it stays live if it already is.
|
|
262
|
+
*/
|
|
263
|
+
function paintPlates(input, current) {
|
|
264
|
+
const { context, globalSig, scene } = input;
|
|
265
|
+
if (current === paintedPlan && globalSig === paintedSig) return false;
|
|
266
|
+
let budget = FAR_PAINT_BUDGET;
|
|
267
|
+
let pending = false;
|
|
268
|
+
let changed = false;
|
|
269
|
+
for (const run of current.runs.values()) {
|
|
270
|
+
let plate = plates.get(run.key);
|
|
271
|
+
if (plate?.sig === globalSig && sameItems(plate.items, run.items)) continue;
|
|
272
|
+
if (budget <= 0) {
|
|
273
|
+
pending = true;
|
|
274
|
+
continue;
|
|
190
275
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
276
|
+
budget -= run.items.length;
|
|
277
|
+
if (!plate) {
|
|
278
|
+
plate = {
|
|
279
|
+
graphics: new Graphics({ label: "board-far-plate" }),
|
|
280
|
+
items: [],
|
|
281
|
+
sig: ""
|
|
282
|
+
};
|
|
283
|
+
plates.set(run.key, plate);
|
|
284
|
+
changed = true;
|
|
197
285
|
}
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
286
|
+
const { graphics } = plate;
|
|
287
|
+
graphics.clear();
|
|
288
|
+
for (const item of run.items) getRenderer(item, context).renderFar?.(graphics, item, context);
|
|
289
|
+
plate.items = run.items;
|
|
290
|
+
plate.sig = globalSig;
|
|
291
|
+
graphics.zIndex = run.order;
|
|
292
|
+
const host = hostFor(run.items[0], scene);
|
|
293
|
+
if (graphics.parent !== host) host.addChild(graphics);
|
|
294
|
+
}
|
|
295
|
+
for (const [key, plate] of plates) {
|
|
296
|
+
if (current.runs.has(key)) continue;
|
|
297
|
+
plates.delete(key);
|
|
298
|
+
plate.graphics.destroy();
|
|
299
|
+
changed = true;
|
|
300
|
+
}
|
|
301
|
+
const next = /* @__PURE__ */ new Set();
|
|
302
|
+
for (const plate of plates.values()) for (const item of plate.items) next.add(item.id);
|
|
303
|
+
covered = next;
|
|
304
|
+
if (pending) options.requestFrame?.();
|
|
305
|
+
else {
|
|
306
|
+
paintedPlan = current;
|
|
307
|
+
paintedSig = globalSig;
|
|
308
|
+
}
|
|
309
|
+
return changed;
|
|
205
310
|
}
|
|
206
|
-
function
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
farFront.clear();
|
|
216
|
-
farBack.clear();
|
|
311
|
+
function clearPlates() {
|
|
312
|
+
for (const plate of plates.values()) plate.graphics.destroy();
|
|
313
|
+
plates.clear();
|
|
314
|
+
paintedPlan = null;
|
|
315
|
+
paintedSig = "";
|
|
316
|
+
covered = EMPTY_IDS;
|
|
317
|
+
plan = null;
|
|
318
|
+
planInputs = [];
|
|
319
|
+
nearIds = EMPTY_IDS;
|
|
217
320
|
}
|
|
218
321
|
function syncLiveStrokes(input) {
|
|
219
322
|
const { strokes, context, getItem } = input;
|
|
@@ -274,28 +377,9 @@ function createBoardScene(options) {
|
|
|
274
377
|
world.removeChild(entry.container);
|
|
275
378
|
entry.renderer.destroy?.(entry.container, context);
|
|
276
379
|
}
|
|
277
|
-
let visibleMemo = null;
|
|
278
|
-
function visibleFacts(input) {
|
|
279
|
-
const { items, visibleIds, structureVersion, context, getItem } = input;
|
|
280
|
-
if (visibleMemo?.ids === visibleIds && visibleMemo.structureVersion === structureVersion) return visibleMemo;
|
|
281
|
-
const orderedIds = visibleIds === null ? items.map((item) => item.id) : [...visibleIds].sort((a, b) => (orderById.get(a) ?? 0) - (orderById.get(b) ?? 0));
|
|
282
|
-
const unbatched = /* @__PURE__ */ new Set();
|
|
283
|
-
for (const id of orderedIds) {
|
|
284
|
-
const item = getItem(id);
|
|
285
|
-
if (item && !getRenderer(item, context).renderFar) unbatched.add(id);
|
|
286
|
-
}
|
|
287
|
-
visibleMemo = {
|
|
288
|
-
ids: visibleIds,
|
|
289
|
-
structureVersion,
|
|
290
|
-
signature: visibleIds === null ? 0 : idSetSignature(visibleIds),
|
|
291
|
-
orderedIds,
|
|
292
|
-
unbatched
|
|
293
|
-
};
|
|
294
|
-
return visibleMemo;
|
|
295
|
-
}
|
|
296
380
|
function sync(input) {
|
|
297
|
-
const { items, scene, context, getItem,
|
|
298
|
-
const resolution = `${textZoomBucket(context.zoom)}:${context.textResolution}`;
|
|
381
|
+
const { items, scene, context, getItem, pinnedIds, globalSig, structureVersion } = input;
|
|
382
|
+
const resolution = `${textZoomBucket(context.zoom)}:${boardCardDetailed(context.zoom)}:${context.textResolution}`;
|
|
299
383
|
if (resolution !== lastResolution) {
|
|
300
384
|
lastResolution = resolution;
|
|
301
385
|
queueResolution(input.focus);
|
|
@@ -305,44 +389,14 @@ function createBoardScene(options) {
|
|
|
305
389
|
lastStructureVersion = structureVersion;
|
|
306
390
|
const liveIds = new Set(items.map((item) => item.id));
|
|
307
391
|
for (const [id, entry] of [...cards]) if (!liveIds.has(id)) recycle(id, entry, context, false);
|
|
308
|
-
|
|
392
|
+
idsByOrder = items.map((item) => item.id);
|
|
393
|
+
orderById = new Map(idsByOrder.map((id, index) => [id, index]));
|
|
309
394
|
}
|
|
310
|
-
const
|
|
311
|
-
const
|
|
312
|
-
const farModeChanged = nextFarActive !== farActive;
|
|
313
|
-
farActive = nextFarActive;
|
|
314
|
-
farFront.visible = farActive;
|
|
315
|
-
if (farActive) {
|
|
316
|
-
farInput = input;
|
|
317
|
-
const nextFarSig = [
|
|
318
|
-
structureVersion,
|
|
319
|
-
geometryVersion,
|
|
320
|
-
globalSig,
|
|
321
|
-
idSetSignature(pinnedIds),
|
|
322
|
-
visibleIds === null ? "all" : visibleFacts(input).signature
|
|
323
|
-
].join("|");
|
|
324
|
-
if (farSig === null || nextFarSig !== farSig && !gestureActive) requestFarPass(nextFarSig);
|
|
325
|
-
} else if (farModeChanged) resetFarLayer();
|
|
395
|
+
const current = lodPlan(input);
|
|
396
|
+
const platesChanged = paintPlates(input, current);
|
|
326
397
|
const wanted = new Set(pinnedIds);
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
for (const id of unbatched) wanted.add(id);
|
|
330
|
-
let retained = 0;
|
|
331
|
-
if (!farPainted) {
|
|
332
|
-
for (const id of cards.keys()) if ((!visibleIds || visibleIds.has(id)) && !farCovered.has(id) && !wanted.has(id)) {
|
|
333
|
-
wanted.add(id);
|
|
334
|
-
retained++;
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
let budget = Math.max(0, FAR_LIVE_LIMIT - retained);
|
|
338
|
-
for (const id of orderedIds) {
|
|
339
|
-
if (budget === 0) break;
|
|
340
|
-
if (farCovered.has(id) || wanted.has(id)) continue;
|
|
341
|
-
wanted.add(id);
|
|
342
|
-
budget -= 1;
|
|
343
|
-
}
|
|
344
|
-
} else if (visibleIds === null) for (const item of items) wanted.add(item.id);
|
|
345
|
-
else for (const id of visibleIds) wanted.add(id);
|
|
398
|
+
for (const id of current.live) wanted.add(id);
|
|
399
|
+
for (const id of cards.keys()) if (current.far.has(id) && !covered.has(id)) wanted.add(id);
|
|
346
400
|
let liveSetChanged = false;
|
|
347
401
|
for (const [id, entry] of [...cards]) if (!wanted.has(id)) {
|
|
348
402
|
recycle(id, entry, context, false);
|
|
@@ -384,18 +438,27 @@ function createBoardScene(options) {
|
|
|
384
438
|
pendingResolution.delete(id);
|
|
385
439
|
}
|
|
386
440
|
}
|
|
441
|
+
if (input.view) cullCards(input.view, pinnedIds);
|
|
387
442
|
refreshResolution(context);
|
|
388
443
|
if (liveSetChanged) liveVersion += 1;
|
|
389
|
-
if (structureChanged || liveSetChanged) syncClips(scene);
|
|
444
|
+
if (structureChanged || liveSetChanged || platesChanged) syncClips(scene);
|
|
390
445
|
if (context.zoom !== lastZoom) {
|
|
391
446
|
lastZoom = context.zoom;
|
|
392
447
|
for (const entry of cards.values()) entry.renderer.zoom?.(entry.container, lastZoom);
|
|
393
448
|
}
|
|
394
449
|
const liftChanged = context.liftedIds !== lifted;
|
|
395
450
|
lifted = context.liftedIds;
|
|
396
|
-
if (structureChanged ||
|
|
451
|
+
if (structureChanged || liveSetChanged || liftChanged) applyChildOrder(scene);
|
|
397
452
|
syncLiveStrokes(input);
|
|
398
453
|
}
|
|
454
|
+
function cullCards(view, pinnedIds) {
|
|
455
|
+
for (const [id, entry] of cards) {
|
|
456
|
+
const culled = !pinnedIds.has(id) && !rectsIntersect(itemBounds(entry.item.frame), view);
|
|
457
|
+
if (entry.container.culled === culled) continue;
|
|
458
|
+
entry.container.culled = culled;
|
|
459
|
+
if (!culled && entry.renderer.tick) tickingIds.add(id);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
399
462
|
function queueResolution(focus) {
|
|
400
463
|
const ids = [...cards].filter(([, entry]) => entry.renderer.resolution);
|
|
401
464
|
if (focus) {
|
|
@@ -421,7 +484,7 @@ function createBoardScene(options) {
|
|
|
421
484
|
let next = null;
|
|
422
485
|
for (const id of tickingIds) {
|
|
423
486
|
const entry = cards.get(id);
|
|
424
|
-
const wait = entry?.container.visible ? entry.renderer.tick?.(entry.container, clock) ?? null : null;
|
|
487
|
+
const wait = entry?.container.visible && !entry.container.culled ? entry.renderer.tick?.(entry.container, clock) ?? null : null;
|
|
425
488
|
if (wait === null) tickingIds.delete(id);
|
|
426
489
|
else next = next === null ? wait : Math.min(next, wait);
|
|
427
490
|
}
|
|
@@ -438,6 +501,14 @@ function createBoardScene(options) {
|
|
|
438
501
|
}
|
|
439
502
|
world.sortChildren();
|
|
440
503
|
}
|
|
504
|
+
function redrawText() {
|
|
505
|
+
const redraw = (container) => {
|
|
506
|
+
for (const child of container.children) if (child instanceof Text) child.style.update();
|
|
507
|
+
else redraw(child);
|
|
508
|
+
};
|
|
509
|
+
for (const entry of cards.values()) redraw(entry.container);
|
|
510
|
+
for (const pool of pools.values()) for (const container of pool) redraw(container);
|
|
511
|
+
}
|
|
441
512
|
function destroy(context) {
|
|
442
513
|
for (const entry of cards.values()) entry.renderer.destroy?.(entry.container, context);
|
|
443
514
|
cards.clear();
|
|
@@ -447,14 +518,13 @@ function createBoardScene(options) {
|
|
|
447
518
|
heldKeys.clear();
|
|
448
519
|
holders.clear();
|
|
449
520
|
orderById = /* @__PURE__ */ new Map();
|
|
521
|
+
idsByOrder = [];
|
|
522
|
+
clearPlates();
|
|
450
523
|
for (const entry of clips.values()) entry.group.destroy({ children: true });
|
|
451
524
|
clips.clear();
|
|
452
525
|
animatedIds.clear();
|
|
453
526
|
tickingIds.clear();
|
|
454
527
|
for (const id of [...liveStrokes.keys()]) removeLiveStroke(id, context);
|
|
455
|
-
resetFarLayer();
|
|
456
|
-
farActive = false;
|
|
457
|
-
visibleMemo = null;
|
|
458
528
|
lastStructureVersion = -1;
|
|
459
529
|
lastZoom = NaN;
|
|
460
530
|
lastResolution = "";
|
|
@@ -474,10 +544,15 @@ function createBoardScene(options) {
|
|
|
474
544
|
return liveVersion;
|
|
475
545
|
},
|
|
476
546
|
liveIds: () => new Set(cards.keys()),
|
|
547
|
+
counts: () => ({
|
|
548
|
+
live: cards.size,
|
|
549
|
+
far: covered.size
|
|
550
|
+
}),
|
|
477
551
|
get animated() {
|
|
478
552
|
return animatedIds.size > 0;
|
|
479
553
|
},
|
|
480
554
|
tick,
|
|
555
|
+
redrawText,
|
|
481
556
|
destroy
|
|
482
557
|
};
|
|
483
558
|
}
|
|
@@ -11,6 +11,7 @@ import { BoardPointerEvent } from "../editor/interaction.js";
|
|
|
11
11
|
import { BoardEditor } from "../editor/editor.js";
|
|
12
12
|
import "../editor/index.js";
|
|
13
13
|
import { BoardRenderStyle } from "../render/style.js";
|
|
14
|
+
import { BoardLod } from "../render/lod.js";
|
|
14
15
|
import { BoardCardRendererResolver, BoardRenderLabels, BoardRenderPalette } from "../render/renderers/board-renderer-registry.js";
|
|
15
16
|
import "../render/index.js";
|
|
16
17
|
import { BoardAssetManager } from "./assets.js";
|
|
@@ -59,6 +60,8 @@ type BoardStageOptions = {
|
|
|
59
60
|
onLiveChange?: (ids: ReadonlySet<string>) => void;
|
|
60
61
|
safeRect?: () => Rect;
|
|
61
62
|
scriptCapabilities?: Record<string, (id: string, input: unknown) => Promise<unknown>>;
|
|
63
|
+
/** Level-of-detail thresholds and live budget; read every frame. */
|
|
64
|
+
lod?: () => BoardLod | undefined;
|
|
62
65
|
};
|
|
63
66
|
type BoardStage = {
|
|
64
67
|
readonly ready: Promise<boolean>;
|
|
@@ -80,8 +83,16 @@ type BoardStage = {
|
|
|
80
83
|
}): void;
|
|
81
84
|
isMaterialized(id: string): boolean;
|
|
82
85
|
toWorld(clientX: number, clientY: number): WorldPoint;
|
|
86
|
+
stats(): BoardStageStats;
|
|
83
87
|
destroy(): void;
|
|
84
88
|
};
|
|
89
|
+
type BoardStageStats = {
|
|
90
|
+
live: number;
|
|
91
|
+
far: number;
|
|
92
|
+
textures: number;
|
|
93
|
+
cooling: number;
|
|
94
|
+
textureBytes: number;
|
|
95
|
+
};
|
|
85
96
|
declare function mountBoardStage(host: HTMLElement, options: BoardStageOptions): BoardStage;
|
|
86
97
|
//#endregion
|
|
87
|
-
export { BoardStage, BoardStageOptions, BoardStagePointerPhase, BoardStageTheme, mountBoardStage };
|
|
98
|
+
export { BoardStage, BoardStageOptions, BoardStagePointerPhase, BoardStageStats, BoardStageTheme, mountBoardStage };
|