@neta-art/cohub 12.2.0 → 12.4.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 +87 -3
- package/dist/board/editor/context.d.ts +2 -0
- package/dist/board/editor/document.d.ts +3 -1
- package/dist/board/editor/document.js +8 -6
- package/dist/board/editor/editor.d.ts +7 -3
- package/dist/board/editor/editor.js +8 -1
- 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 +99 -5
- package/dist/board/editor/query.d.ts +1 -1
- package/dist/board/editor/query.js +3 -7
- package/dist/board/editor/selection.js +13 -1
- package/dist/board/editor/tool.d.ts +4 -1
- package/dist/board/editor/tool.js +6 -1
- package/dist/board/model/asset-key.d.ts +9 -1
- package/dist/board/model/asset-key.js +27 -7
- package/dist/board/model/index.d.ts +5 -5
- package/dist/board/model/index.js +4 -4
- package/dist/board/model/items/create.js +1 -1
- 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/text-metrics.d.ts +21 -1
- package/dist/board/model/text-metrics.js +24 -1
- package/dist/board/model/themes.d.ts +49 -42
- package/dist/board/model/themes.js +49 -44
- package/dist/board/render/index.d.ts +8 -6
- package/dist/board/render/index.js +7 -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 +9 -1
- package/dist/board/render/lod.js +7 -1
- package/dist/board/render/media-interaction.d.ts +5 -2
- package/dist/board/render/media-interaction.js +14 -2
- package/dist/board/render/renderers/arrow-card-renderer.js +7 -5
- package/dist/board/render/renderers/board-renderer-registry.d.ts +12 -1
- package/dist/board/render/renderers/board-renderer-registry.js +9 -4
- package/dist/board/render/renderers/far-plate.d.ts +4 -1
- package/dist/board/render/renderers/far-plate.js +5 -1
- package/dist/board/render/renderers/file-card-renderer.js +44 -24
- package/dist/board/render/renderers/frame-card-renderer.js +9 -3
- package/dist/board/render/renderers/image-card-renderer.js +2 -2
- package/dist/board/render/renderers/model-card-renderer.js +2 -2
- package/dist/board/render/renderers/task-card-renderer.js +2 -2
- package/dist/board/render/renderers/video-card-renderer.js +2 -2
- package/dist/board/render/style.d.ts +1 -0
- package/dist/board/render/style.js +3 -1
- 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 +4 -43
- package/dist/board/render/text-measurement.d.ts +12 -2
- package/dist/board/render/text-measurement.js +55 -10
- package/dist/board/replica/index.d.ts +4 -2
- package/dist/board/replica/index.js +34 -21
- package/dist/board/replica/indexeddb.d.ts +16 -0
- package/dist/board/replica/indexeddb.js +155 -0
- package/dist/board/stage/assets.d.ts +16 -4
- package/dist/board/stage/assets.js +155 -30
- 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 +9 -5
- package/dist/board/stage/index.js +5 -2
- package/dist/board/stage/presses.d.ts +21 -0
- package/dist/board/stage/presses.js +66 -0
- package/dist/board/stage/scene.d.ts +4 -0
- package/dist/board/stage/scene.js +45 -13
- package/dist/board/stage/stage.d.ts +21 -2
- package/dist/board/stage/stage.js +309 -89
- package/dist/board/stage/texture-pool.d.ts +6 -4
- package/dist/board/stage/texture-pool.js +42 -8
- package/dist/board/stage/views.d.ts +1 -1
- package/dist/board/stage/views.js +2 -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 +65 -733
- package/dist/protocol/dist/board-model.d.ts +15 -1
- package/dist/protocol/dist/board-model.js +12 -2
- 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.js +4 -0
- package/dist/protocol/dist/index.d.ts +2 -2
- package/package.json +2 -1
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
//#region src/board/stage/frame-budget.ts
|
|
2
|
+
const INITIAL = {
|
|
3
|
+
materialize: 48,
|
|
4
|
+
plate: 2400,
|
|
5
|
+
resolution: 32
|
|
6
|
+
};
|
|
7
|
+
const KINDS = Object.keys(INITIAL);
|
|
8
|
+
const MIN_SHARE = 1 / 8;
|
|
9
|
+
const LATE_FRAMES = 1.5;
|
|
10
|
+
const MOVING_SHARE = .25;
|
|
11
|
+
const RESTING_SHARE = .6;
|
|
12
|
+
const COST_SMOOTHING = .3;
|
|
13
|
+
const DEFAULT_INTERVAL = 1e3 / 60;
|
|
14
|
+
const MIN_INTERVAL = 4;
|
|
15
|
+
const INTERVAL_DRIFT = 1.01;
|
|
16
|
+
const perKind = (value) => ({
|
|
17
|
+
materialize: value,
|
|
18
|
+
plate: value,
|
|
19
|
+
resolution: value
|
|
20
|
+
});
|
|
21
|
+
function fixedBoardFrameBudget() {
|
|
22
|
+
let done = perKind(0);
|
|
23
|
+
return {
|
|
24
|
+
allow: (kind) => Math.max(0, INITIAL[kind] - done[kind]),
|
|
25
|
+
spend(kind, units) {
|
|
26
|
+
done[kind] += units;
|
|
27
|
+
},
|
|
28
|
+
reset() {
|
|
29
|
+
done = perKind(0);
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
function createBoardFrameClock(now = () => performance.now()) {
|
|
34
|
+
let interval = DEFAULT_INTERVAL;
|
|
35
|
+
let previous = NaN;
|
|
36
|
+
let deadline = Number.POSITIVE_INFINITY;
|
|
37
|
+
let late = false;
|
|
38
|
+
const cost = perKind(0);
|
|
39
|
+
for (const kind of KINDS) cost[kind] = DEFAULT_INTERVAL * MOVING_SHARE / INITIAL[kind];
|
|
40
|
+
const last = perKind(0);
|
|
41
|
+
let done = perKind(0);
|
|
42
|
+
return {
|
|
43
|
+
get interval() {
|
|
44
|
+
return interval;
|
|
45
|
+
},
|
|
46
|
+
begin(frameTime, { moving, chained }) {
|
|
47
|
+
const delta = frameTime - previous;
|
|
48
|
+
previous = frameTime;
|
|
49
|
+
late = moving && chained && delta > interval * LATE_FRAMES;
|
|
50
|
+
if (chained && delta >= MIN_INTERVAL) interval = Math.min(interval * INTERVAL_DRIFT, delta);
|
|
51
|
+
deadline = now() + interval * (moving ? MOVING_SHARE : RESTING_SHARE);
|
|
52
|
+
for (const kind of KINDS) if (done[kind] > 0) last[kind] = done[kind];
|
|
53
|
+
done = perKind(0);
|
|
54
|
+
},
|
|
55
|
+
allow(kind) {
|
|
56
|
+
const floor = Math.ceil(INITIAL[kind] * MIN_SHARE);
|
|
57
|
+
const fit = Math.floor((deadline - now()) / cost[kind]);
|
|
58
|
+
const ceiling = late ? Math.floor(last[kind] / 2) : Math.max(INITIAL[kind], last[kind] * 2);
|
|
59
|
+
return Math.max(done[kind] === 0 ? floor : 0, Math.min(fit, ceiling - done[kind]));
|
|
60
|
+
},
|
|
61
|
+
spend(kind, units, ms) {
|
|
62
|
+
if (units <= 0) return;
|
|
63
|
+
done[kind] += units;
|
|
64
|
+
cost[kind] += (Math.max(ms, .001) / units - cost[kind]) * COST_SMOOTHING;
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
//#endregion
|
|
69
|
+
export { createBoardFrameClock, fixedBoardFrameBudget };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
//#region src/board/stage/idle.ts
|
|
2
|
+
function scheduleBoardIdle(task, delay) {
|
|
3
|
+
const scheduler = globalThis.scheduler;
|
|
4
|
+
if (scheduler?.postTask) {
|
|
5
|
+
const controller = new AbortController();
|
|
6
|
+
scheduler.postTask(task, {
|
|
7
|
+
priority: "background",
|
|
8
|
+
delay,
|
|
9
|
+
signal: controller.signal
|
|
10
|
+
}).catch((error) => {
|
|
11
|
+
if (!controller.signal.aborted) throw error;
|
|
12
|
+
});
|
|
13
|
+
return () => controller.abort();
|
|
14
|
+
}
|
|
15
|
+
let idle = 0;
|
|
16
|
+
const timer = setTimeout(() => {
|
|
17
|
+
if (typeof requestIdleCallback === "function") idle = requestIdleCallback(task, { timeout: 1e3 });
|
|
18
|
+
else task();
|
|
19
|
+
}, delay);
|
|
20
|
+
return () => {
|
|
21
|
+
clearTimeout(timer);
|
|
22
|
+
if (idle) cancelIdleCallback(idle);
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
//#endregion
|
|
26
|
+
export { scheduleBoardIdle };
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import { COMPACT_LRU_BUDGET, DEFAULT_LRU_BUDGET, LruBudget, LruEntry, LruTrim, planLruTrim, selectLruEvictions } from "./lru.js";
|
|
2
|
-
import { BoardImageTexturePool, boardImageTexturePool, createBoardImageTexturePool, loadBoardImageTexture, loadFittedImageTexture, shrinkBoardTexture, unloadBoardImageTexture } from "./texture-pool.js";
|
|
3
|
-
import { BoardAssetManager, BoardAssetManagerOptions, BoardModelPreviewRequest, BoardModelPreviews, createBoardAssetManager } from "./assets.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
2
|
+
import { BoardImageTexturePool, boardImageTexturePool, createBoardImageTexturePool, loadBoardImageTexture, loadFittedImageTexture, readTextureTone, shrinkBoardTexture, unloadBoardImageTexture } from "./texture-pool.js";
|
|
3
|
+
import { BoardAssetChange, BoardAssetManager, BoardAssetManagerOptions, BoardAssetPrefetch, BoardModelPreviewRequest, BoardModelPreviews, createBoardAssetManager } from "./assets.js";
|
|
4
|
+
import { BOARD_LONG_PRESS_MS, BoardStagePress } from "./presses.js";
|
|
5
|
+
import { BoardFrameBudget, BoardFrameWork } from "./frame-budget.js";
|
|
6
6
|
import { BoardLiveStroke, BoardScene, BoardSceneNode, SceneSyncInput, createBoardScene } from "./scene.js";
|
|
7
7
|
import { BoardItemView, BoardItemViewContext, BoardItemViewInstance, BoardItemViewLayer, createBoardItemViewLayer } from "./views.js";
|
|
8
8
|
import { BoardStage, BoardStageOptions, BoardStagePointerPhase, BoardStageStats, BoardStageTheme, mountBoardStage } from "./stage.js";
|
|
9
|
+
import { BOARD_BENCH_PHASES, BoardBenchPercentiles, BoardBenchPhase, BoardBenchPhaseReport, BoardBenchReport, BoardBenchSettle, runBoardStageBench } from "./bench.js";
|
|
10
|
+
import { BOARD_DEVICE_TIERS, BoardDeviceBudget, BoardDeviceEnvironment, BoardDeviceSignals, BoardDeviceTier, boardDeviceBudget, boardDeviceTier, defaultBoardDeviceBudget, readBoardDeviceSignals } from "./device.js";
|
|
11
|
+
import { toBoardPointerEvent } from "./pointer.js";
|
|
12
|
+
import { createBoardPointerGestures } from "./pointer-gestures.js";
|
|
9
13
|
import { BoardOverlayInput, drawBoardOverlay, expandFrame } from "./overlay.js";
|
|
10
|
-
export { BoardAssetManager, BoardAssetManagerOptions, BoardImageTexturePool, BoardItemView, BoardItemViewContext, BoardItemViewInstance, BoardItemViewLayer, BoardLiveStroke, BoardModelPreviewRequest, BoardModelPreviews, BoardOverlayInput, BoardScene, BoardSceneNode, BoardStage, BoardStageOptions, BoardStagePointerPhase, BoardStageStats, BoardStageTheme, COMPACT_LRU_BUDGET, DEFAULT_LRU_BUDGET, LruBudget, LruEntry, LruTrim, SceneSyncInput, boardImageTexturePool, createBoardAssetManager, createBoardImageTexturePool, createBoardItemViewLayer, createBoardPointerGestures, createBoardScene, drawBoardOverlay, expandFrame, loadBoardImageTexture, loadFittedImageTexture, mountBoardStage, planLruTrim, selectLruEvictions, shrinkBoardTexture, toBoardPointerEvent, unloadBoardImageTexture };
|
|
14
|
+
export { BOARD_BENCH_PHASES, BOARD_DEVICE_TIERS, BOARD_LONG_PRESS_MS, BoardAssetChange, BoardAssetManager, BoardAssetManagerOptions, BoardAssetPrefetch, BoardBenchPercentiles, BoardBenchPhase, BoardBenchPhaseReport, BoardBenchReport, BoardBenchSettle, BoardDeviceBudget, BoardDeviceEnvironment, BoardDeviceSignals, BoardDeviceTier, type BoardFrameBudget, type BoardFrameWork, BoardImageTexturePool, BoardItemView, BoardItemViewContext, BoardItemViewInstance, BoardItemViewLayer, BoardLiveStroke, BoardModelPreviewRequest, BoardModelPreviews, BoardOverlayInput, BoardScene, BoardSceneNode, BoardStage, BoardStageOptions, BoardStagePointerPhase, type BoardStagePress, BoardStageStats, BoardStageTheme, COMPACT_LRU_BUDGET, DEFAULT_LRU_BUDGET, LruBudget, LruEntry, LruTrim, SceneSyncInput, boardDeviceBudget, boardDeviceTier, boardImageTexturePool, createBoardAssetManager, createBoardImageTexturePool, createBoardItemViewLayer, createBoardPointerGestures, createBoardScene, defaultBoardDeviceBudget, drawBoardOverlay, expandFrame, loadBoardImageTexture, loadFittedImageTexture, mountBoardStage, planLruTrim, readBoardDeviceSignals, readTextureTone, runBoardStageBench, selectLruEvictions, shrinkBoardTexture, toBoardPointerEvent, unloadBoardImageTexture };
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { COMPACT_LRU_BUDGET, DEFAULT_LRU_BUDGET, planLruTrim, selectLruEvictions } from "./lru.js";
|
|
2
|
-
import {
|
|
2
|
+
import { BOARD_DEVICE_TIERS, boardDeviceBudget, boardDeviceTier, defaultBoardDeviceBudget, readBoardDeviceSignals } from "./device.js";
|
|
3
|
+
import { boardImageTexturePool, createBoardImageTexturePool, loadBoardImageTexture, loadFittedImageTexture, readTextureTone, shrinkBoardTexture, unloadBoardImageTexture } from "./texture-pool.js";
|
|
3
4
|
import { createBoardAssetManager } from "./assets.js";
|
|
5
|
+
import { BOARD_BENCH_PHASES, runBoardStageBench } from "./bench.js";
|
|
4
6
|
import { toBoardPointerEvent } from "./pointer.js";
|
|
5
7
|
import { createBoardPointerGestures } from "./pointer-gestures.js";
|
|
6
8
|
import { createBoardScene } from "./scene.js";
|
|
9
|
+
import { BOARD_LONG_PRESS_MS } from "./presses.js";
|
|
7
10
|
import { drawBoardOverlay, expandFrame } from "./overlay.js";
|
|
8
11
|
import { createBoardItemViewLayer } from "./views.js";
|
|
9
12
|
import { mountBoardStage } from "./stage.js";
|
|
10
|
-
export { COMPACT_LRU_BUDGET, DEFAULT_LRU_BUDGET, boardImageTexturePool, createBoardAssetManager, createBoardImageTexturePool, createBoardItemViewLayer, createBoardPointerGestures, createBoardScene, drawBoardOverlay, expandFrame, loadBoardImageTexture, loadFittedImageTexture, mountBoardStage, planLruTrim, selectLruEvictions, shrinkBoardTexture, toBoardPointerEvent, unloadBoardImageTexture };
|
|
13
|
+
export { BOARD_BENCH_PHASES, BOARD_DEVICE_TIERS, BOARD_LONG_PRESS_MS, COMPACT_LRU_BUDGET, DEFAULT_LRU_BUDGET, boardDeviceBudget, boardDeviceTier, boardImageTexturePool, createBoardAssetManager, createBoardImageTexturePool, createBoardItemViewLayer, createBoardPointerGestures, createBoardScene, defaultBoardDeviceBudget, drawBoardOverlay, expandFrame, loadBoardImageTexture, loadFittedImageTexture, mountBoardStage, planLruTrim, readBoardDeviceSignals, readTextureTone, runBoardStageBench, selectLruEvictions, shrinkBoardTexture, toBoardPointerEvent, unloadBoardImageTexture };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
//#region src/board/stage/presses.d.ts
|
|
2
|
+
declare const BOARD_LONG_PRESS_MS = 450;
|
|
3
|
+
type BoardStagePress = {
|
|
4
|
+
pointerId: number;
|
|
5
|
+
pointerType: string;
|
|
6
|
+
clientX: number;
|
|
7
|
+
clientY: number;
|
|
8
|
+
};
|
|
9
|
+
declare function boardStagePress(event: PointerEvent): BoardStagePress;
|
|
10
|
+
declare function createBoardLongPress(fire: (press: BoardStagePress) => void): {
|
|
11
|
+
start(event: PointerEvent): void;
|
|
12
|
+
move(event: PointerEvent): void;
|
|
13
|
+
end(event: PointerEvent): void;
|
|
14
|
+
cancel: () => void;
|
|
15
|
+
};
|
|
16
|
+
declare function createGhostClickGuard(view: Window | null): {
|
|
17
|
+
arm(): void;
|
|
18
|
+
dispose: () => void | undefined;
|
|
19
|
+
};
|
|
20
|
+
//#endregion
|
|
21
|
+
export { BOARD_LONG_PRESS_MS, BoardStagePress, boardStagePress, createBoardLongPress, createGhostClickGuard };
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import "../editor/tool.js";
|
|
2
|
+
//#region src/board/stage/presses.ts
|
|
3
|
+
const BOARD_LONG_PRESS_MS = 450;
|
|
4
|
+
const GHOST_CLICK_MS = 700;
|
|
5
|
+
function boardStagePress(event) {
|
|
6
|
+
return {
|
|
7
|
+
pointerId: event.pointerId,
|
|
8
|
+
pointerType: event.pointerType,
|
|
9
|
+
clientX: event.clientX,
|
|
10
|
+
clientY: event.clientY
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
function createBoardLongPress(fire) {
|
|
14
|
+
let pending = null;
|
|
15
|
+
const cancel = () => {
|
|
16
|
+
if (pending) clearTimeout(pending.timer);
|
|
17
|
+
pending = null;
|
|
18
|
+
};
|
|
19
|
+
return {
|
|
20
|
+
start(event) {
|
|
21
|
+
if (pending) return cancel();
|
|
22
|
+
if (event.pointerType === "mouse" || event.button !== 0) return;
|
|
23
|
+
const press = boardStagePress(event);
|
|
24
|
+
pending = {
|
|
25
|
+
press,
|
|
26
|
+
timer: setTimeout(() => {
|
|
27
|
+
pending = null;
|
|
28
|
+
fire(press);
|
|
29
|
+
}, 450)
|
|
30
|
+
};
|
|
31
|
+
},
|
|
32
|
+
move(event) {
|
|
33
|
+
if (!pending || event.pointerId !== pending.press.pointerId) return;
|
|
34
|
+
const { clientX, clientY } = pending.press;
|
|
35
|
+
if (Math.hypot(event.clientX - clientX, event.clientY - clientY) > 8) cancel();
|
|
36
|
+
},
|
|
37
|
+
end(event) {
|
|
38
|
+
if (pending?.press.pointerId === event.pointerId) cancel();
|
|
39
|
+
},
|
|
40
|
+
cancel
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
function createGhostClickGuard(view) {
|
|
44
|
+
let disarm = null;
|
|
45
|
+
return {
|
|
46
|
+
arm() {
|
|
47
|
+
disarm?.();
|
|
48
|
+
if (!view) return;
|
|
49
|
+
const drop = (event) => {
|
|
50
|
+
event.preventDefault();
|
|
51
|
+
event.stopImmediatePropagation();
|
|
52
|
+
disarm?.();
|
|
53
|
+
};
|
|
54
|
+
const timer = setTimeout(() => disarm?.(), GHOST_CLICK_MS);
|
|
55
|
+
view.addEventListener("click", drop, true);
|
|
56
|
+
disarm = () => {
|
|
57
|
+
clearTimeout(timer);
|
|
58
|
+
view.removeEventListener("click", drop, true);
|
|
59
|
+
disarm = null;
|
|
60
|
+
};
|
|
61
|
+
},
|
|
62
|
+
dispose: () => disarm?.()
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
//#endregion
|
|
66
|
+
export { BOARD_LONG_PRESS_MS, boardStagePress, createBoardLongPress, createGhostClickGuard };
|
|
@@ -5,6 +5,7 @@ import "../model/index.js";
|
|
|
5
5
|
import { BoardLod } from "../render/lod.js";
|
|
6
6
|
import { BoardCardRendererResolver, BoardRenderContext } from "../render/renderers/board-renderer-registry.js";
|
|
7
7
|
import "../render/index.js";
|
|
8
|
+
import { BoardFrameBudget } from "./frame-budget.js";
|
|
8
9
|
import { Container, Graphics } from "pixi.js";
|
|
9
10
|
//#region src/board/stage/scene.d.ts
|
|
10
11
|
type BoardLiveStroke = {
|
|
@@ -32,6 +33,7 @@ type SceneSyncInput = {
|
|
|
32
33
|
/** The world area on screen; live cards outside it skip rendering. */
|
|
33
34
|
view?: Rect;
|
|
34
35
|
lod?: BoardLod;
|
|
36
|
+
budget?: BoardFrameBudget;
|
|
35
37
|
};
|
|
36
38
|
type BoardSceneNode = {
|
|
37
39
|
item: BoardSceneItem;
|
|
@@ -41,6 +43,7 @@ type BoardScene = {
|
|
|
41
43
|
sync: (input: SceneSyncInput) => void;
|
|
42
44
|
getNode: (itemId: string) => BoardSceneNode | null;
|
|
43
45
|
holders: (key: string) => ReadonlySet<string>;
|
|
46
|
+
repaintFar: (keys: ReadonlySet<string>, assetKey: (item: BoardSceneItem) => string | null) => void;
|
|
44
47
|
readonly liveVersion: number;
|
|
45
48
|
liveIds: () => ReadonlySet<string>;
|
|
46
49
|
/** Live cards and cards drawn on far plates. */
|
|
@@ -48,6 +51,7 @@ type BoardScene = {
|
|
|
48
51
|
live: number;
|
|
49
52
|
far: number;
|
|
50
53
|
};
|
|
54
|
+
readonly settling: boolean;
|
|
51
55
|
readonly animated: boolean;
|
|
52
56
|
tick: (clock: number) => number | null;
|
|
53
57
|
redrawText: () => void;
|
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
import { parseBoardItem } from "../../protocol/dist/board-model.js";
|
|
2
2
|
import { drawPointsBounds } from "../../protocol/dist/board-layout.js";
|
|
3
3
|
import { itemBounds, rectsIntersect } from "../model/geometry.js";
|
|
4
|
-
import { DEFAULT_BOARD_LOD, boardCardDetailed } from "../render/lod.js";
|
|
4
|
+
import { DEFAULT_BOARD_LOD, boardCardDetailed, boardLodRatio, boardLodSize } from "../render/lod.js";
|
|
5
5
|
import { textZoomBucket } from "../render/text-resolution.js";
|
|
6
6
|
import { clippingAncestor, isClippingFrame, syncClipGroup } from "../render/clip.js";
|
|
7
|
+
import { fixedBoardFrameBudget } from "./frame-budget.js";
|
|
7
8
|
import { Graphics, Text } from "pixi.js";
|
|
8
9
|
//#region src/board/stage/scene.ts
|
|
9
10
|
const FAR_CHUNK = 256;
|
|
10
|
-
const FAR_PAINT_BUDGET = 2400;
|
|
11
|
-
const MATERIALIZE_BATCH = 48;
|
|
12
11
|
const LOD_ZOOM_STEPS = 8;
|
|
13
|
-
const RESOLUTION_BATCH = 32;
|
|
14
12
|
const Z_LIFTED = 1e9;
|
|
15
13
|
const Z_LIVE_STROKE = Number.MAX_SAFE_INTEGER - 1;
|
|
16
14
|
const Z_OVERLAY = Number.MAX_SAFE_INTEGER;
|
|
@@ -47,6 +45,8 @@ function createBoardScene(options) {
|
|
|
47
45
|
const pendingResolution = /* @__PURE__ */ new Set();
|
|
48
46
|
let liveVersion = 0;
|
|
49
47
|
let lifted = /* @__PURE__ */ new Set();
|
|
48
|
+
const fixedBudget = fixedBoardFrameBudget();
|
|
49
|
+
let budget = fixedBudget;
|
|
50
50
|
const liveStrokes = /* @__PURE__ */ new Map();
|
|
51
51
|
function setHeldKey(context, cardId, item) {
|
|
52
52
|
const desiredKey = item ? context.assetKey(item) : null;
|
|
@@ -168,7 +168,7 @@ function createBoardScene(options) {
|
|
|
168
168
|
function lodSize(renderer, item) {
|
|
169
169
|
let size = lodSizes.get(item);
|
|
170
170
|
if (size === void 0) {
|
|
171
|
-
size = renderer
|
|
171
|
+
size = boardLodSize(renderer, item);
|
|
172
172
|
lodSizes.set(item, size);
|
|
173
173
|
}
|
|
174
174
|
return size;
|
|
@@ -210,19 +210,20 @@ function createBoardScene(options) {
|
|
|
210
210
|
live.add(id);
|
|
211
211
|
continue;
|
|
212
212
|
}
|
|
213
|
-
if (lodSize(renderer, item)
|
|
213
|
+
if (boardLodRatio(renderer, lodSize(renderer, item), context.zoom, lod) >= (nearIds.has(id) ? .8 : 1)) near.push(item);
|
|
214
214
|
}
|
|
215
215
|
const room = Math.max(0, lod.live - live.size);
|
|
216
216
|
const limited = near.length > room;
|
|
217
|
+
const allowance = budget.allow("materialize");
|
|
217
218
|
const { focus, view } = input;
|
|
218
|
-
const ranked = focus && (limited || near.length >
|
|
219
|
+
const ranked = focus && (limited || near.length > allowance) ? near.map(({ id, frame }) => ({
|
|
219
220
|
id,
|
|
220
221
|
offscreen: view && !rectsIntersect(itemBounds(frame), view) ? 1 : 0,
|
|
221
222
|
distance: (frame.x + frame.width / 2 - focus.x) ** 2 + (frame.y + frame.height / 2 - focus.y) ** 2
|
|
222
223
|
})).sort((a, b) => a.offscreen - b.offscreen || a.distance - b.distance) : near;
|
|
223
224
|
let admitted = 0;
|
|
224
225
|
let deferred = false;
|
|
225
|
-
for (const { id } of ranked.slice(0, room)) if (!cards.has(id) && admitted++ >=
|
|
226
|
+
for (const { id } of ranked.slice(0, room)) if (!cards.has(id) && admitted++ >= allowance) deferred = true;
|
|
226
227
|
else live.add(id);
|
|
227
228
|
if (deferred) options.requestFrame?.();
|
|
228
229
|
nearIds = live;
|
|
@@ -255,6 +256,7 @@ function createBoardScene(options) {
|
|
|
255
256
|
const plates = /* @__PURE__ */ new Map();
|
|
256
257
|
let paintedPlan = null;
|
|
257
258
|
let paintedSig = "";
|
|
259
|
+
let platesPending = false;
|
|
258
260
|
let covered = EMPTY_IDS;
|
|
259
261
|
/**
|
|
260
262
|
* Repaint the plates whose cards changed, within a per-frame budget. A card
|
|
@@ -263,17 +265,19 @@ function createBoardScene(options) {
|
|
|
263
265
|
function paintPlates(input, current) {
|
|
264
266
|
const { context, globalSig, scene } = input;
|
|
265
267
|
if (current === paintedPlan && globalSig === paintedSig) return false;
|
|
266
|
-
|
|
268
|
+
const start = performance.now();
|
|
269
|
+
const allowance = budget.allow("plate");
|
|
270
|
+
let painted = 0;
|
|
267
271
|
let pending = false;
|
|
268
272
|
let changed = false;
|
|
269
273
|
for (const run of current.runs.values()) {
|
|
270
274
|
let plate = plates.get(run.key);
|
|
271
275
|
if (plate?.sig === globalSig && sameItems(plate.items, run.items)) continue;
|
|
272
|
-
if (
|
|
276
|
+
if (painted >= allowance) {
|
|
273
277
|
pending = true;
|
|
274
278
|
continue;
|
|
275
279
|
}
|
|
276
|
-
|
|
280
|
+
painted += run.items.length;
|
|
277
281
|
if (!plate) {
|
|
278
282
|
plate = {
|
|
279
283
|
graphics: new Graphics({ label: "board-far-plate" }),
|
|
@@ -292,6 +296,7 @@ function createBoardScene(options) {
|
|
|
292
296
|
const host = hostFor(run.items[0], scene);
|
|
293
297
|
if (graphics.parent !== host) host.addChild(graphics);
|
|
294
298
|
}
|
|
299
|
+
const paintMs = performance.now() - start;
|
|
295
300
|
for (const [key, plate] of plates) {
|
|
296
301
|
if (current.runs.has(key)) continue;
|
|
297
302
|
plates.delete(key);
|
|
@@ -301,6 +306,8 @@ function createBoardScene(options) {
|
|
|
301
306
|
const next = /* @__PURE__ */ new Set();
|
|
302
307
|
for (const plate of plates.values()) for (const item of plate.items) next.add(item.id);
|
|
303
308
|
covered = next;
|
|
309
|
+
budget.spend("plate", painted, paintMs);
|
|
310
|
+
platesPending = pending;
|
|
304
311
|
if (pending) options.requestFrame?.();
|
|
305
312
|
else {
|
|
306
313
|
paintedPlan = current;
|
|
@@ -308,11 +315,19 @@ function createBoardScene(options) {
|
|
|
308
315
|
}
|
|
309
316
|
return changed;
|
|
310
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
|
+
}
|
|
324
|
+
}
|
|
311
325
|
function clearPlates() {
|
|
312
326
|
for (const plate of plates.values()) plate.graphics.destroy();
|
|
313
327
|
plates.clear();
|
|
314
328
|
paintedPlan = null;
|
|
315
329
|
paintedSig = "";
|
|
330
|
+
platesPending = false;
|
|
316
331
|
covered = EMPTY_IDS;
|
|
317
332
|
plan = null;
|
|
318
333
|
planInputs = [];
|
|
@@ -379,6 +394,8 @@ function createBoardScene(options) {
|
|
|
379
394
|
}
|
|
380
395
|
function sync(input) {
|
|
381
396
|
const { items, scene, context, getItem, pinnedIds, globalSig, structureVersion } = input;
|
|
397
|
+
fixedBudget.reset();
|
|
398
|
+
budget = input.budget ?? fixedBudget;
|
|
382
399
|
const resolution = `${textZoomBucket(context.zoom)}:${boardCardDetailed(context.zoom)}:${context.textResolution}`;
|
|
383
400
|
if (resolution !== lastResolution) {
|
|
384
401
|
lastResolution = resolution;
|
|
@@ -398,6 +415,8 @@ function createBoardScene(options) {
|
|
|
398
415
|
for (const id of current.live) wanted.add(id);
|
|
399
416
|
for (const id of cards.keys()) if (current.far.has(id) && !covered.has(id)) wanted.add(id);
|
|
400
417
|
let liveSetChanged = false;
|
|
418
|
+
let made = 0;
|
|
419
|
+
let making = 0;
|
|
401
420
|
for (const [id, entry] of [...cards]) if (!wanted.has(id)) {
|
|
402
421
|
recycle(id, entry, context, false);
|
|
403
422
|
liveSetChanged = true;
|
|
@@ -414,7 +433,10 @@ function createBoardScene(options) {
|
|
|
414
433
|
entry = void 0;
|
|
415
434
|
}
|
|
416
435
|
if (!entry) {
|
|
436
|
+
const start = performance.now();
|
|
417
437
|
materialize(item, context, globalSig, scene);
|
|
438
|
+
making += performance.now() - start;
|
|
439
|
+
made += 1;
|
|
418
440
|
liveSetChanged = true;
|
|
419
441
|
continue;
|
|
420
442
|
}
|
|
@@ -438,6 +460,7 @@ function createBoardScene(options) {
|
|
|
438
460
|
pendingResolution.delete(id);
|
|
439
461
|
}
|
|
440
462
|
}
|
|
463
|
+
budget.spend("materialize", made, making);
|
|
441
464
|
if (input.view) cullCards(input.view, pinnedIds);
|
|
442
465
|
refreshResolution(context);
|
|
443
466
|
if (liveSetChanged) liveVersion += 1;
|
|
@@ -469,15 +492,20 @@ function createBoardScene(options) {
|
|
|
469
492
|
for (const [id] of ids) pendingResolution.add(id);
|
|
470
493
|
}
|
|
471
494
|
function refreshResolution(context) {
|
|
472
|
-
|
|
495
|
+
if (pendingResolution.size === 0) return;
|
|
496
|
+
const start = performance.now();
|
|
497
|
+
const allowance = budget.allow("resolution");
|
|
498
|
+
let refreshed = 0;
|
|
473
499
|
for (const id of pendingResolution) {
|
|
474
|
-
if (
|
|
500
|
+
if (refreshed === allowance) break;
|
|
475
501
|
pendingResolution.delete(id);
|
|
476
502
|
const entry = cards.get(id);
|
|
477
503
|
if (!entry) continue;
|
|
478
504
|
entry.renderer.resolution?.(entry.container, entry.item, context);
|
|
479
505
|
if (entry.renderer.tick) tickingIds.add(id);
|
|
506
|
+
refreshed += 1;
|
|
480
507
|
}
|
|
508
|
+
budget.spend("resolution", refreshed, performance.now() - start);
|
|
481
509
|
if (pendingResolution.size) options.requestFrame?.();
|
|
482
510
|
}
|
|
483
511
|
function tick(clock) {
|
|
@@ -540,6 +568,7 @@ function createBoardScene(options) {
|
|
|
540
568
|
} : null;
|
|
541
569
|
},
|
|
542
570
|
holders: (key) => holders.get(key) ?? EMPTY_IDS,
|
|
571
|
+
repaintFar,
|
|
543
572
|
get liveVersion() {
|
|
544
573
|
return liveVersion;
|
|
545
574
|
},
|
|
@@ -548,6 +577,9 @@ function createBoardScene(options) {
|
|
|
548
577
|
live: cards.size,
|
|
549
578
|
far: covered.size
|
|
550
579
|
}),
|
|
580
|
+
get settling() {
|
|
581
|
+
return Boolean(plan?.deferred) || platesPending || pendingResolution.size > 0;
|
|
582
|
+
},
|
|
551
583
|
get animated() {
|
|
552
584
|
return animatedIds.size > 0;
|
|
553
585
|
},
|
|
@@ -7,7 +7,7 @@ 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";
|
|
@@ -15,6 +15,7 @@ import { BoardLod } from "../render/lod.js";
|
|
|
15
15
|
import { BoardCardRendererResolver, BoardRenderLabels, BoardRenderPalette } from "../render/renderers/board-renderer-registry.js";
|
|
16
16
|
import "../render/index.js";
|
|
17
17
|
import { BoardAssetManager } from "./assets.js";
|
|
18
|
+
import { BoardStagePress } from "./presses.js";
|
|
18
19
|
import { BoardLiveStroke } from "./scene.js";
|
|
19
20
|
import { BoardItemView } from "./views.js";
|
|
20
21
|
import { Graphics, Renderer } from "pixi.js";
|
|
@@ -34,6 +35,8 @@ type BoardStageOptions = {
|
|
|
34
35
|
source: BoardAssetSource;
|
|
35
36
|
renderers?: BoardCardRendererResolver;
|
|
36
37
|
views?: readonly BoardItemView[];
|
|
38
|
+
/** `below` needs a theme with `imageBackground`; views there get no input. */
|
|
39
|
+
viewLayer?: "above" | "below";
|
|
37
40
|
theme?: () => BoardStageTheme;
|
|
38
41
|
labels?: () => BoardRenderLabels;
|
|
39
42
|
taskRun?: (taskRunId: string) => {
|
|
@@ -51,6 +54,12 @@ type BoardStageOptions = {
|
|
|
51
54
|
scene: BoardScene;
|
|
52
55
|
}) => void;
|
|
53
56
|
onPointer?: (phase: BoardStagePointerPhase, event: PointerEvent, input: BoardPointerEvent) => boolean | undefined;
|
|
57
|
+
onActivate?: (item: BoardSceneItem, activation: BoardActivation) => void;
|
|
58
|
+
onLongPress?: (item: BoardSceneItem, press: BoardStagePress) => void;
|
|
59
|
+
/** A press on a card key (`BoardCardRenderer.hitAt`); return false to leave it to the editor. */
|
|
60
|
+
onHit?: (item: BoardSceneItem, action: string, press: BoardStagePress) => boolean | undefined;
|
|
61
|
+
/** Card keys `onHit` acts on; others go to the editor. */
|
|
62
|
+
handlesHit?: (item: BoardSceneItem, action: string) => boolean;
|
|
54
63
|
onDoubleClick?: (item: BoardSceneItem | null, point: WorldPoint) => boolean | undefined;
|
|
55
64
|
onSurfaceChange?: (size: {
|
|
56
65
|
width: number;
|
|
@@ -60,8 +69,8 @@ type BoardStageOptions = {
|
|
|
60
69
|
onLiveChange?: (ids: ReadonlySet<string>) => void;
|
|
61
70
|
safeRect?: () => Rect;
|
|
62
71
|
scriptCapabilities?: Record<string, (id: string, input: unknown) => Promise<unknown>>;
|
|
63
|
-
/** Level-of-detail thresholds and live budget; read every frame. */
|
|
64
72
|
lod?: () => BoardLod | undefined;
|
|
73
|
+
prefetch?: number;
|
|
65
74
|
};
|
|
66
75
|
type BoardStage = {
|
|
67
76
|
readonly ready: Promise<boolean>;
|
|
@@ -92,6 +101,16 @@ type BoardStageStats = {
|
|
|
92
101
|
textures: number;
|
|
93
102
|
cooling: number;
|
|
94
103
|
textureBytes: number;
|
|
104
|
+
loading: number;
|
|
105
|
+
prefetching: number;
|
|
106
|
+
waiting: number;
|
|
107
|
+
settling: boolean;
|
|
108
|
+
frame: {
|
|
109
|
+
p50: number;
|
|
110
|
+
p95: number;
|
|
111
|
+
max: number;
|
|
112
|
+
};
|
|
113
|
+
interval: number;
|
|
95
114
|
};
|
|
96
115
|
declare function mountBoardStage(host: HTMLElement, options: BoardStageOptions): BoardStage;
|
|
97
116
|
//#endregion
|