@neta-art/cohub 12.1.1 → 12.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +85 -8
- package/dist/board/editor/clipboard.js +1 -1
- package/dist/board/editor/creation.js +1 -1
- package/dist/board/editor/document.d.ts +3 -1
- package/dist/board/editor/document.js +8 -6
- package/dist/board/editor/editor.d.ts +8 -3
- package/dist/board/editor/editor.js +8 -2
- package/dist/board/editor/index.d.ts +3 -3
- package/dist/board/editor/index.js +2 -2
- package/dist/board/editor/interaction.d.ts +23 -2
- package/dist/board/editor/interaction.js +93 -4
- package/dist/board/editor/query.d.ts +1 -1
- package/dist/board/editor/query.js +3 -7
- package/dist/board/editor/selection.js +1 -1
- package/dist/board/editor/snapshots.js +5 -57
- package/dist/board/editor/tool.d.ts +4 -1
- package/dist/board/editor/tool.js +6 -1
- package/dist/board/export/index.d.ts +2 -2
- package/dist/board/export/index.js +1 -1
- package/dist/board/export/node/fonts.js +4 -0
- package/dist/board/export/node/index.d.ts +2 -0
- package/dist/board/export/node/index.js +8 -1
- package/dist/board/export/node/scripts.js +1 -1
- package/dist/board/export/scene.d.ts +2 -2
- package/dist/board/export/scene.js +2 -2
- package/dist/board/model/asset-key.d.ts +24 -0
- package/dist/board/model/asset-key.js +104 -0
- package/dist/board/model/export-assets.d.ts +7 -3
- package/dist/board/model/export-assets.js +34 -13
- package/dist/board/model/index.d.ts +9 -7
- package/dist/board/model/index.js +22 -20
- package/dist/board/model/items/create.js +3 -3
- package/dist/board/model/items/definition.d.ts +9 -1
- package/dist/board/model/items/index.d.ts +2 -2
- package/dist/board/model/items/registry.d.ts +2 -1
- package/dist/board/model/items/registry.js +1 -0
- package/dist/board/model/media-sizes.d.ts +28 -0
- package/dist/board/model/media-sizes.js +64 -0
- package/dist/board/model/palette.d.ts +1 -0
- package/dist/board/model/palette.js +20 -130
- package/dist/board/model/sampling.js +1 -1
- package/dist/board/model/text-metrics.d.ts +28 -3
- package/dist/board/model/text-metrics.js +36 -2
- package/dist/board/model/themes.d.ts +495 -0
- package/dist/board/model/themes.js +500 -0
- package/dist/board/render/card-tag.js +2 -2
- package/dist/board/render/index.d.ts +11 -7
- package/dist/board/render/index.js +9 -5
- package/dist/board/render/labels.d.ts +15 -0
- package/dist/board/render/labels.js +14 -0
- package/dist/board/render/lod.d.ts +30 -0
- package/dist/board/render/lod.js +33 -0
- package/dist/board/render/media-interaction.d.ts +5 -2
- package/dist/board/render/media-interaction.js +15 -3
- package/dist/board/render/palette.js +2 -25
- package/dist/board/render/renderers/arrow-card-renderer.js +7 -5
- package/dist/board/render/renderers/audio-card-renderer.js +3 -2
- package/dist/board/render/renderers/board-renderer-registry.d.ts +22 -1
- package/dist/board/render/renderers/board-renderer-registry.js +9 -4
- package/dist/board/render/renderers/custom-card-renderer.js +5 -5
- package/dist/board/render/renderers/draw-card-renderer.js +1 -1
- package/dist/board/render/renderers/far-plate.d.ts +14 -1
- package/dist/board/render/renderers/far-plate.js +31 -8
- package/dist/board/render/renderers/file-card-renderer.js +49 -31
- package/dist/board/render/renderers/frame-card-renderer.js +9 -3
- package/dist/board/render/renderers/image-card-renderer.js +34 -8
- package/dist/board/render/renderers/model-card-renderer.js +6 -6
- package/dist/board/render/renderers/shape-card-renderer.js +25 -24
- package/dist/board/render/renderers/task-card-renderer.js +9 -10
- package/dist/board/render/renderers/text-card-renderer.js +86 -41
- package/dist/board/render/renderers/video-card-renderer.js +5 -4
- package/dist/board/render/script-export.js +1 -1
- package/dist/board/render/script-host.js +3 -3
- package/dist/board/render/style.d.ts +8 -2
- package/dist/board/render/style.js +8 -2
- package/dist/board/render/surface.d.ts +5 -0
- package/dist/board/render/surface.js +13 -3
- package/dist/board/render/text-edit.d.ts +32 -0
- package/dist/board/render/text-edit.js +134 -0
- package/dist/board/render/text-fit.d.ts +1 -3
- package/dist/board/render/text-fit.js +6 -49
- package/dist/board/render/text-measurement.d.ts +25 -1
- package/dist/board/render/text-measurement.js +157 -19
- package/dist/board/render/text-resolution.d.ts +1 -6
- package/dist/board/render/text-resolution.js +1 -10
- package/dist/board/render/theme.d.ts +15 -0
- package/dist/board/render/theme.js +15 -0
- package/dist/board/replica/index.d.ts +4 -2
- package/dist/board/replica/index.js +28 -17
- package/dist/board/replica/indexeddb.d.ts +16 -0
- package/dist/board/replica/indexeddb.js +155 -0
- package/dist/board/stage/assets.d.ts +28 -5
- package/dist/board/stage/assets.js +293 -102
- package/dist/board/stage/bench.d.ts +35 -0
- package/dist/board/stage/bench.js +153 -0
- package/dist/board/stage/device.d.ts +41 -0
- package/dist/board/stage/device.js +62 -0
- package/dist/board/stage/frame-budget.d.ts +19 -0
- package/dist/board/stage/frame-budget.js +69 -0
- package/dist/board/stage/idle.d.ts +4 -0
- package/dist/board/stage/idle.js +26 -0
- package/dist/board/stage/index.d.ts +11 -7
- package/dist/board/stage/index.js +7 -4
- package/dist/board/stage/lru.d.ts +13 -9
- package/dist/board/stage/lru.js +38 -20
- package/dist/board/stage/presses.d.ts +21 -0
- package/dist/board/stage/presses.js +66 -0
- package/dist/board/stage/scene.d.ts +15 -1
- package/dist/board/stage/scene.js +248 -141
- package/dist/board/stage/stage.d.ts +28 -2
- package/dist/board/stage/stage.js +404 -112
- package/dist/board/stage/texture-pool.d.ts +10 -1
- package/dist/board/stage/texture-pool.js +72 -2
- package/dist/chunks/environment.d.ts +6 -0
- package/dist/chunks/http.d.ts +9 -1
- package/dist/chunks/http.js +791 -4
- package/dist/index.d.ts +52 -1
- package/dist/index.js +202 -862
- package/dist/media.d.ts +3 -27
- package/dist/media.js +3 -82
- package/dist/protocol/dist/board-constants.js +3 -3
- package/dist/protocol/dist/board-layout.d.ts +17 -3
- package/dist/protocol/dist/board-layout.js +68 -11
- package/dist/protocol/dist/board-model.d.ts +15 -1
- package/dist/protocol/dist/board-model.js +12 -2
- package/dist/protocol/dist/board-patch.js +1 -1
- package/dist/protocol/dist/board-schema.d.ts +2 -1
- package/dist/protocol/dist/board-schema.js +4 -1
- package/dist/protocol/dist/core/media.d.ts +35 -1
- package/dist/protocol/dist/core/media.js +127 -1
- package/dist/protocol/dist/index.d.ts +3 -3
- package/package.json +4 -2
- package/dist/board/model/image-key.d.ts +0 -9
- package/dist/board/model/image-key.js +0 -33
|
@@ -1,15 +1,14 @@
|
|
|
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, boardLodRatio, boardLodSize } 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 {
|
|
7
|
+
import { fixedBoardFrameBudget } from "./frame-budget.js";
|
|
8
|
+
import { Graphics, Text } from "pixi.js";
|
|
6
9
|
//#region src/board/stage/scene.ts
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
const FAR_PASS_BATCH = 240;
|
|
10
|
-
const FAR_LIVE_LIMIT = 64;
|
|
11
|
-
const RESOLUTION_BATCH = 32;
|
|
12
|
-
const Z_FAR_LAYER = -1;
|
|
10
|
+
const FAR_CHUNK = 256;
|
|
11
|
+
const LOD_ZOOM_STEPS = 8;
|
|
13
12
|
const Z_LIFTED = 1e9;
|
|
14
13
|
const Z_LIVE_STROKE = Number.MAX_SAFE_INTEGER - 1;
|
|
15
14
|
const Z_OVERLAY = Number.MAX_SAFE_INTEGER;
|
|
@@ -24,6 +23,9 @@ function idSetSignature(ids) {
|
|
|
24
23
|
}
|
|
25
24
|
return hash;
|
|
26
25
|
}
|
|
26
|
+
function sameItems(a, b) {
|
|
27
|
+
return a.length === b.length && a.every((item, index) => item === b[index]);
|
|
28
|
+
}
|
|
27
29
|
function createBoardScene(options) {
|
|
28
30
|
const { world, overlay, getRenderer } = options;
|
|
29
31
|
world.sortableChildren = true;
|
|
@@ -36,14 +38,15 @@ function createBoardScene(options) {
|
|
|
36
38
|
const heldKeys = /* @__PURE__ */ new Map();
|
|
37
39
|
const holders = /* @__PURE__ */ new Map();
|
|
38
40
|
let orderById = /* @__PURE__ */ new Map();
|
|
39
|
-
let
|
|
40
|
-
let farActive = false;
|
|
41
|
+
let idsByOrder = [];
|
|
41
42
|
let lastStructureVersion = -1;
|
|
42
43
|
let lastZoom = NaN;
|
|
43
44
|
let lastResolution = "";
|
|
44
45
|
const pendingResolution = /* @__PURE__ */ new Set();
|
|
45
46
|
let liveVersion = 0;
|
|
46
47
|
let lifted = /* @__PURE__ */ new Set();
|
|
48
|
+
const fixedBudget = fixedBoardFrameBudget();
|
|
49
|
+
let budget = fixedBudget;
|
|
47
50
|
const liveStrokes = /* @__PURE__ */ new Map();
|
|
48
51
|
function setHeldKey(context, cardId, item) {
|
|
49
52
|
const desiredKey = item ? context.assetKey(item) : null;
|
|
@@ -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,188 @@ 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 = boardLodSize(renderer, item);
|
|
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 (boardLodRatio(renderer, lodSize(renderer, item), context.zoom, lod) >= (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 allowance = budget.allow("materialize");
|
|
218
|
+
const { focus, view } = input;
|
|
219
|
+
const ranked = focus && (limited || near.length > allowance) ? near.map(({ id, frame }) => ({
|
|
220
|
+
id,
|
|
221
|
+
offscreen: view && !rectsIntersect(itemBounds(frame), view) ? 1 : 0,
|
|
222
|
+
distance: (frame.x + frame.width / 2 - focus.x) ** 2 + (frame.y + frame.height / 2 - focus.y) ** 2
|
|
223
|
+
})).sort((a, b) => a.offscreen - b.offscreen || a.distance - b.distance) : near;
|
|
224
|
+
let admitted = 0;
|
|
225
|
+
let deferred = false;
|
|
226
|
+
for (const { id } of ranked.slice(0, room)) if (!cards.has(id) && admitted++ >= allowance) deferred = true;
|
|
227
|
+
else live.add(id);
|
|
228
|
+
if (deferred) options.requestFrame?.();
|
|
229
|
+
nearIds = live;
|
|
230
|
+
const runs = /* @__PURE__ */ new Map();
|
|
231
|
+
const far = /* @__PURE__ */ new Set();
|
|
232
|
+
for (const id of ids) {
|
|
233
|
+
const item = getItem(id);
|
|
234
|
+
if (!item || live.has(id)) continue;
|
|
235
|
+
const order = orderById.get(id) ?? 0;
|
|
236
|
+
const key = `${Math.floor(order / FAR_CHUNK)}:${clippingAncestor(scene, item) ?? ""}`;
|
|
237
|
+
const run = runs.get(key);
|
|
238
|
+
if (run) run.items.push(item);
|
|
239
|
+
else runs.set(key, {
|
|
240
|
+
key,
|
|
241
|
+
order,
|
|
242
|
+
items: [item]
|
|
243
|
+
});
|
|
244
|
+
far.add(id);
|
|
245
|
+
}
|
|
246
|
+
plan = {
|
|
247
|
+
scene,
|
|
248
|
+
live,
|
|
249
|
+
far,
|
|
250
|
+
runs,
|
|
251
|
+
limited,
|
|
252
|
+
deferred
|
|
253
|
+
};
|
|
254
|
+
return plan;
|
|
255
|
+
}
|
|
256
|
+
const plates = /* @__PURE__ */ new Map();
|
|
257
|
+
let paintedPlan = null;
|
|
258
|
+
let paintedSig = "";
|
|
259
|
+
let platesPending = false;
|
|
260
|
+
let covered = EMPTY_IDS;
|
|
261
|
+
/**
|
|
262
|
+
* Repaint the plates whose cards changed, within a per-frame budget. A card
|
|
263
|
+
* is covered once a plate draws it; until then it stays live if it already is.
|
|
264
|
+
*/
|
|
265
|
+
function paintPlates(input, current) {
|
|
266
|
+
const { context, globalSig, scene } = input;
|
|
267
|
+
if (current === paintedPlan && globalSig === paintedSig) return false;
|
|
268
|
+
const start = performance.now();
|
|
269
|
+
const allowance = budget.allow("plate");
|
|
270
|
+
let painted = 0;
|
|
271
|
+
let pending = false;
|
|
272
|
+
let changed = false;
|
|
273
|
+
for (const run of current.runs.values()) {
|
|
274
|
+
let plate = plates.get(run.key);
|
|
275
|
+
if (plate?.sig === globalSig && sameItems(plate.items, run.items)) continue;
|
|
276
|
+
if (painted >= allowance) {
|
|
277
|
+
pending = true;
|
|
278
|
+
continue;
|
|
190
279
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
280
|
+
painted += run.items.length;
|
|
281
|
+
if (!plate) {
|
|
282
|
+
plate = {
|
|
283
|
+
graphics: new Graphics({ label: "board-far-plate" }),
|
|
284
|
+
items: [],
|
|
285
|
+
sig: ""
|
|
286
|
+
};
|
|
287
|
+
plates.set(run.key, plate);
|
|
288
|
+
changed = true;
|
|
197
289
|
}
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
290
|
+
const { graphics } = plate;
|
|
291
|
+
graphics.clear();
|
|
292
|
+
for (const item of run.items) getRenderer(item, context).renderFar?.(graphics, item, context);
|
|
293
|
+
plate.items = run.items;
|
|
294
|
+
plate.sig = globalSig;
|
|
295
|
+
graphics.zIndex = run.order;
|
|
296
|
+
const host = hostFor(run.items[0], scene);
|
|
297
|
+
if (graphics.parent !== host) host.addChild(graphics);
|
|
298
|
+
}
|
|
299
|
+
const paintMs = performance.now() - start;
|
|
300
|
+
for (const [key, plate] of plates) {
|
|
301
|
+
if (current.runs.has(key)) continue;
|
|
302
|
+
plates.delete(key);
|
|
303
|
+
plate.graphics.destroy();
|
|
304
|
+
changed = true;
|
|
305
|
+
}
|
|
306
|
+
const next = /* @__PURE__ */ new Set();
|
|
307
|
+
for (const plate of plates.values()) for (const item of plate.items) next.add(item.id);
|
|
308
|
+
covered = next;
|
|
309
|
+
budget.spend("plate", painted, paintMs);
|
|
310
|
+
platesPending = pending;
|
|
311
|
+
if (pending) options.requestFrame?.();
|
|
312
|
+
else {
|
|
313
|
+
paintedPlan = current;
|
|
314
|
+
paintedSig = globalSig;
|
|
315
|
+
}
|
|
316
|
+
return changed;
|
|
317
|
+
}
|
|
318
|
+
function repaintFar(keys, assetKey) {
|
|
319
|
+
for (const plate of plates.values()) {
|
|
320
|
+
if (!plate.items.some((item) => keys.has(assetKey(item) ?? ""))) continue;
|
|
321
|
+
plate.sig = "";
|
|
322
|
+
paintedPlan = null;
|
|
323
|
+
}
|
|
205
324
|
}
|
|
206
|
-
function
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
farBack.clear();
|
|
325
|
+
function clearPlates() {
|
|
326
|
+
for (const plate of plates.values()) plate.graphics.destroy();
|
|
327
|
+
plates.clear();
|
|
328
|
+
paintedPlan = null;
|
|
329
|
+
paintedSig = "";
|
|
330
|
+
platesPending = false;
|
|
331
|
+
covered = EMPTY_IDS;
|
|
332
|
+
plan = null;
|
|
333
|
+
planInputs = [];
|
|
334
|
+
nearIds = EMPTY_IDS;
|
|
217
335
|
}
|
|
218
336
|
function syncLiveStrokes(input) {
|
|
219
337
|
const { strokes, context, getItem } = input;
|
|
@@ -274,28 +392,11 @@ function createBoardScene(options) {
|
|
|
274
392
|
world.removeChild(entry.container);
|
|
275
393
|
entry.renderer.destroy?.(entry.container, context);
|
|
276
394
|
}
|
|
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
395
|
function sync(input) {
|
|
297
|
-
const { items, scene, context, getItem,
|
|
298
|
-
|
|
396
|
+
const { items, scene, context, getItem, pinnedIds, globalSig, structureVersion } = input;
|
|
397
|
+
fixedBudget.reset();
|
|
398
|
+
budget = input.budget ?? fixedBudget;
|
|
399
|
+
const resolution = `${textZoomBucket(context.zoom)}:${boardCardDetailed(context.zoom)}:${context.textResolution}`;
|
|
299
400
|
if (resolution !== lastResolution) {
|
|
300
401
|
lastResolution = resolution;
|
|
301
402
|
queueResolution(input.focus);
|
|
@@ -305,45 +406,17 @@ function createBoardScene(options) {
|
|
|
305
406
|
lastStructureVersion = structureVersion;
|
|
306
407
|
const liveIds = new Set(items.map((item) => item.id));
|
|
307
408
|
for (const [id, entry] of [...cards]) if (!liveIds.has(id)) recycle(id, entry, context, false);
|
|
308
|
-
|
|
409
|
+
idsByOrder = items.map((item) => item.id);
|
|
410
|
+
orderById = new Map(idsByOrder.map((id, index) => [id, index]));
|
|
309
411
|
}
|
|
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();
|
|
412
|
+
const current = lodPlan(input);
|
|
413
|
+
const platesChanged = paintPlates(input, current);
|
|
326
414
|
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);
|
|
415
|
+
for (const id of current.live) wanted.add(id);
|
|
416
|
+
for (const id of cards.keys()) if (current.far.has(id) && !covered.has(id)) wanted.add(id);
|
|
346
417
|
let liveSetChanged = false;
|
|
418
|
+
let made = 0;
|
|
419
|
+
let making = 0;
|
|
347
420
|
for (const [id, entry] of [...cards]) if (!wanted.has(id)) {
|
|
348
421
|
recycle(id, entry, context, false);
|
|
349
422
|
liveSetChanged = true;
|
|
@@ -360,7 +433,10 @@ function createBoardScene(options) {
|
|
|
360
433
|
entry = void 0;
|
|
361
434
|
}
|
|
362
435
|
if (!entry) {
|
|
436
|
+
const start = performance.now();
|
|
363
437
|
materialize(item, context, globalSig, scene);
|
|
438
|
+
making += performance.now() - start;
|
|
439
|
+
made += 1;
|
|
364
440
|
liveSetChanged = true;
|
|
365
441
|
continue;
|
|
366
442
|
}
|
|
@@ -384,18 +460,28 @@ function createBoardScene(options) {
|
|
|
384
460
|
pendingResolution.delete(id);
|
|
385
461
|
}
|
|
386
462
|
}
|
|
463
|
+
budget.spend("materialize", made, making);
|
|
464
|
+
if (input.view) cullCards(input.view, pinnedIds);
|
|
387
465
|
refreshResolution(context);
|
|
388
466
|
if (liveSetChanged) liveVersion += 1;
|
|
389
|
-
if (structureChanged || liveSetChanged) syncClips(scene);
|
|
467
|
+
if (structureChanged || liveSetChanged || platesChanged) syncClips(scene);
|
|
390
468
|
if (context.zoom !== lastZoom) {
|
|
391
469
|
lastZoom = context.zoom;
|
|
392
470
|
for (const entry of cards.values()) entry.renderer.zoom?.(entry.container, lastZoom);
|
|
393
471
|
}
|
|
394
472
|
const liftChanged = context.liftedIds !== lifted;
|
|
395
473
|
lifted = context.liftedIds;
|
|
396
|
-
if (structureChanged ||
|
|
474
|
+
if (structureChanged || liveSetChanged || liftChanged) applyChildOrder(scene);
|
|
397
475
|
syncLiveStrokes(input);
|
|
398
476
|
}
|
|
477
|
+
function cullCards(view, pinnedIds) {
|
|
478
|
+
for (const [id, entry] of cards) {
|
|
479
|
+
const culled = !pinnedIds.has(id) && !rectsIntersect(itemBounds(entry.item.frame), view);
|
|
480
|
+
if (entry.container.culled === culled) continue;
|
|
481
|
+
entry.container.culled = culled;
|
|
482
|
+
if (!culled && entry.renderer.tick) tickingIds.add(id);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
399
485
|
function queueResolution(focus) {
|
|
400
486
|
const ids = [...cards].filter(([, entry]) => entry.renderer.resolution);
|
|
401
487
|
if (focus) {
|
|
@@ -406,22 +492,27 @@ function createBoardScene(options) {
|
|
|
406
492
|
for (const [id] of ids) pendingResolution.add(id);
|
|
407
493
|
}
|
|
408
494
|
function refreshResolution(context) {
|
|
409
|
-
|
|
495
|
+
if (pendingResolution.size === 0) return;
|
|
496
|
+
const start = performance.now();
|
|
497
|
+
const allowance = budget.allow("resolution");
|
|
498
|
+
let refreshed = 0;
|
|
410
499
|
for (const id of pendingResolution) {
|
|
411
|
-
if (
|
|
500
|
+
if (refreshed === allowance) break;
|
|
412
501
|
pendingResolution.delete(id);
|
|
413
502
|
const entry = cards.get(id);
|
|
414
503
|
if (!entry) continue;
|
|
415
504
|
entry.renderer.resolution?.(entry.container, entry.item, context);
|
|
416
505
|
if (entry.renderer.tick) tickingIds.add(id);
|
|
506
|
+
refreshed += 1;
|
|
417
507
|
}
|
|
508
|
+
budget.spend("resolution", refreshed, performance.now() - start);
|
|
418
509
|
if (pendingResolution.size) options.requestFrame?.();
|
|
419
510
|
}
|
|
420
511
|
function tick(clock) {
|
|
421
512
|
let next = null;
|
|
422
513
|
for (const id of tickingIds) {
|
|
423
514
|
const entry = cards.get(id);
|
|
424
|
-
const wait = entry?.container.visible ? entry.renderer.tick?.(entry.container, clock) ?? null : null;
|
|
515
|
+
const wait = entry?.container.visible && !entry.container.culled ? entry.renderer.tick?.(entry.container, clock) ?? null : null;
|
|
425
516
|
if (wait === null) tickingIds.delete(id);
|
|
426
517
|
else next = next === null ? wait : Math.min(next, wait);
|
|
427
518
|
}
|
|
@@ -438,6 +529,14 @@ function createBoardScene(options) {
|
|
|
438
529
|
}
|
|
439
530
|
world.sortChildren();
|
|
440
531
|
}
|
|
532
|
+
function redrawText() {
|
|
533
|
+
const redraw = (container) => {
|
|
534
|
+
for (const child of container.children) if (child instanceof Text) child.style.update();
|
|
535
|
+
else redraw(child);
|
|
536
|
+
};
|
|
537
|
+
for (const entry of cards.values()) redraw(entry.container);
|
|
538
|
+
for (const pool of pools.values()) for (const container of pool) redraw(container);
|
|
539
|
+
}
|
|
441
540
|
function destroy(context) {
|
|
442
541
|
for (const entry of cards.values()) entry.renderer.destroy?.(entry.container, context);
|
|
443
542
|
cards.clear();
|
|
@@ -447,14 +546,13 @@ function createBoardScene(options) {
|
|
|
447
546
|
heldKeys.clear();
|
|
448
547
|
holders.clear();
|
|
449
548
|
orderById = /* @__PURE__ */ new Map();
|
|
549
|
+
idsByOrder = [];
|
|
550
|
+
clearPlates();
|
|
450
551
|
for (const entry of clips.values()) entry.group.destroy({ children: true });
|
|
451
552
|
clips.clear();
|
|
452
553
|
animatedIds.clear();
|
|
453
554
|
tickingIds.clear();
|
|
454
555
|
for (const id of [...liveStrokes.keys()]) removeLiveStroke(id, context);
|
|
455
|
-
resetFarLayer();
|
|
456
|
-
farActive = false;
|
|
457
|
-
visibleMemo = null;
|
|
458
556
|
lastStructureVersion = -1;
|
|
459
557
|
lastZoom = NaN;
|
|
460
558
|
lastResolution = "";
|
|
@@ -470,14 +568,23 @@ function createBoardScene(options) {
|
|
|
470
568
|
} : null;
|
|
471
569
|
},
|
|
472
570
|
holders: (key) => holders.get(key) ?? EMPTY_IDS,
|
|
571
|
+
repaintFar,
|
|
473
572
|
get liveVersion() {
|
|
474
573
|
return liveVersion;
|
|
475
574
|
},
|
|
476
575
|
liveIds: () => new Set(cards.keys()),
|
|
576
|
+
counts: () => ({
|
|
577
|
+
live: cards.size,
|
|
578
|
+
far: covered.size
|
|
579
|
+
}),
|
|
580
|
+
get settling() {
|
|
581
|
+
return Boolean(plan?.deferred) || platesPending || pendingResolution.size > 0;
|
|
582
|
+
},
|
|
477
583
|
get animated() {
|
|
478
584
|
return animatedIds.size > 0;
|
|
479
585
|
},
|
|
480
586
|
tick,
|
|
587
|
+
redrawText,
|
|
481
588
|
destroy
|
|
482
589
|
};
|
|
483
590
|
}
|
|
@@ -7,13 +7,15 @@ import { BoardScene, BoardSceneItem } from "../model/scene.js";
|
|
|
7
7
|
import { BoardShapeColors } from "../model/palette.js";
|
|
8
8
|
import { BoardAssetSource } from "../model/media-playback.js";
|
|
9
9
|
import "../model/index.js";
|
|
10
|
-
import { BoardPointerEvent } from "../editor/interaction.js";
|
|
10
|
+
import { BoardActivation, 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";
|
|
18
|
+
import { BoardStagePress } from "./presses.js";
|
|
17
19
|
import { BoardLiveStroke } from "./scene.js";
|
|
18
20
|
import { BoardItemView } from "./views.js";
|
|
19
21
|
import { Graphics, Renderer } from "pixi.js";
|
|
@@ -50,6 +52,10 @@ type BoardStageOptions = {
|
|
|
50
52
|
scene: BoardScene;
|
|
51
53
|
}) => void;
|
|
52
54
|
onPointer?: (phase: BoardStagePointerPhase, event: PointerEvent, input: BoardPointerEvent) => boolean | undefined;
|
|
55
|
+
onActivate?: (item: BoardSceneItem, activation: BoardActivation) => void;
|
|
56
|
+
onLongPress?: (item: BoardSceneItem, press: BoardStagePress) => void;
|
|
57
|
+
/** A press on a card key (`BoardCardRenderer.hitAt`); return false to leave it to the editor. */
|
|
58
|
+
onHit?: (item: BoardSceneItem, action: string, press: BoardStagePress) => boolean | undefined;
|
|
53
59
|
onDoubleClick?: (item: BoardSceneItem | null, point: WorldPoint) => boolean | undefined;
|
|
54
60
|
onSurfaceChange?: (size: {
|
|
55
61
|
width: number;
|
|
@@ -59,6 +65,8 @@ type BoardStageOptions = {
|
|
|
59
65
|
onLiveChange?: (ids: ReadonlySet<string>) => void;
|
|
60
66
|
safeRect?: () => Rect;
|
|
61
67
|
scriptCapabilities?: Record<string, (id: string, input: unknown) => Promise<unknown>>;
|
|
68
|
+
lod?: () => BoardLod | undefined;
|
|
69
|
+
prefetch?: number;
|
|
62
70
|
};
|
|
63
71
|
type BoardStage = {
|
|
64
72
|
readonly ready: Promise<boolean>;
|
|
@@ -80,8 +88,26 @@ type BoardStage = {
|
|
|
80
88
|
}): void;
|
|
81
89
|
isMaterialized(id: string): boolean;
|
|
82
90
|
toWorld(clientX: number, clientY: number): WorldPoint;
|
|
91
|
+
stats(): BoardStageStats;
|
|
83
92
|
destroy(): void;
|
|
84
93
|
};
|
|
94
|
+
type BoardStageStats = {
|
|
95
|
+
live: number;
|
|
96
|
+
far: number;
|
|
97
|
+
textures: number;
|
|
98
|
+
cooling: number;
|
|
99
|
+
textureBytes: number;
|
|
100
|
+
loading: number;
|
|
101
|
+
prefetching: number;
|
|
102
|
+
waiting: number;
|
|
103
|
+
settling: boolean;
|
|
104
|
+
frame: {
|
|
105
|
+
p50: number;
|
|
106
|
+
p95: number;
|
|
107
|
+
max: number;
|
|
108
|
+
};
|
|
109
|
+
interval: number;
|
|
110
|
+
};
|
|
85
111
|
declare function mountBoardStage(host: HTMLElement, options: BoardStageOptions): BoardStage;
|
|
86
112
|
//#endregion
|
|
87
|
-
export { BoardStage, BoardStageOptions, BoardStagePointerPhase, BoardStageTheme, mountBoardStage };
|
|
113
|
+
export { BoardStage, BoardStageOptions, BoardStagePointerPhase, BoardStageStats, BoardStageTheme, mountBoardStage };
|