@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.
Files changed (134) hide show
  1. package/README.md +85 -8
  2. package/dist/board/editor/clipboard.js +1 -1
  3. package/dist/board/editor/creation.js +1 -1
  4. package/dist/board/editor/document.d.ts +3 -1
  5. package/dist/board/editor/document.js +8 -6
  6. package/dist/board/editor/editor.d.ts +8 -3
  7. package/dist/board/editor/editor.js +8 -2
  8. package/dist/board/editor/index.d.ts +3 -3
  9. package/dist/board/editor/index.js +2 -2
  10. package/dist/board/editor/interaction.d.ts +23 -2
  11. package/dist/board/editor/interaction.js +93 -4
  12. package/dist/board/editor/query.d.ts +1 -1
  13. package/dist/board/editor/query.js +3 -7
  14. package/dist/board/editor/selection.js +1 -1
  15. package/dist/board/editor/snapshots.js +5 -57
  16. package/dist/board/editor/tool.d.ts +4 -1
  17. package/dist/board/editor/tool.js +6 -1
  18. package/dist/board/export/index.d.ts +2 -2
  19. package/dist/board/export/index.js +1 -1
  20. package/dist/board/export/node/fonts.js +4 -0
  21. package/dist/board/export/node/index.d.ts +2 -0
  22. package/dist/board/export/node/index.js +8 -1
  23. package/dist/board/export/node/scripts.js +1 -1
  24. package/dist/board/export/scene.d.ts +2 -2
  25. package/dist/board/export/scene.js +2 -2
  26. package/dist/board/model/asset-key.d.ts +24 -0
  27. package/dist/board/model/asset-key.js +104 -0
  28. package/dist/board/model/export-assets.d.ts +7 -3
  29. package/dist/board/model/export-assets.js +34 -13
  30. package/dist/board/model/index.d.ts +9 -7
  31. package/dist/board/model/index.js +22 -20
  32. package/dist/board/model/items/create.js +3 -3
  33. package/dist/board/model/items/definition.d.ts +9 -1
  34. package/dist/board/model/items/index.d.ts +2 -2
  35. package/dist/board/model/items/registry.d.ts +2 -1
  36. package/dist/board/model/items/registry.js +1 -0
  37. package/dist/board/model/media-sizes.d.ts +28 -0
  38. package/dist/board/model/media-sizes.js +64 -0
  39. package/dist/board/model/palette.d.ts +1 -0
  40. package/dist/board/model/palette.js +20 -130
  41. package/dist/board/model/sampling.js +1 -1
  42. package/dist/board/model/text-metrics.d.ts +28 -3
  43. package/dist/board/model/text-metrics.js +36 -2
  44. package/dist/board/model/themes.d.ts +495 -0
  45. package/dist/board/model/themes.js +500 -0
  46. package/dist/board/render/card-tag.js +2 -2
  47. package/dist/board/render/index.d.ts +11 -7
  48. package/dist/board/render/index.js +9 -5
  49. package/dist/board/render/labels.d.ts +15 -0
  50. package/dist/board/render/labels.js +14 -0
  51. package/dist/board/render/lod.d.ts +30 -0
  52. package/dist/board/render/lod.js +33 -0
  53. package/dist/board/render/media-interaction.d.ts +5 -2
  54. package/dist/board/render/media-interaction.js +15 -3
  55. package/dist/board/render/palette.js +2 -25
  56. package/dist/board/render/renderers/arrow-card-renderer.js +7 -5
  57. package/dist/board/render/renderers/audio-card-renderer.js +3 -2
  58. package/dist/board/render/renderers/board-renderer-registry.d.ts +22 -1
  59. package/dist/board/render/renderers/board-renderer-registry.js +9 -4
  60. package/dist/board/render/renderers/custom-card-renderer.js +5 -5
  61. package/dist/board/render/renderers/draw-card-renderer.js +1 -1
  62. package/dist/board/render/renderers/far-plate.d.ts +14 -1
  63. package/dist/board/render/renderers/far-plate.js +31 -8
  64. package/dist/board/render/renderers/file-card-renderer.js +49 -31
  65. package/dist/board/render/renderers/frame-card-renderer.js +9 -3
  66. package/dist/board/render/renderers/image-card-renderer.js +34 -8
  67. package/dist/board/render/renderers/model-card-renderer.js +6 -6
  68. package/dist/board/render/renderers/shape-card-renderer.js +25 -24
  69. package/dist/board/render/renderers/task-card-renderer.js +9 -10
  70. package/dist/board/render/renderers/text-card-renderer.js +86 -41
  71. package/dist/board/render/renderers/video-card-renderer.js +5 -4
  72. package/dist/board/render/script-export.js +1 -1
  73. package/dist/board/render/script-host.js +3 -3
  74. package/dist/board/render/style.d.ts +8 -2
  75. package/dist/board/render/style.js +8 -2
  76. package/dist/board/render/surface.d.ts +5 -0
  77. package/dist/board/render/surface.js +13 -3
  78. package/dist/board/render/text-edit.d.ts +32 -0
  79. package/dist/board/render/text-edit.js +134 -0
  80. package/dist/board/render/text-fit.d.ts +1 -3
  81. package/dist/board/render/text-fit.js +6 -49
  82. package/dist/board/render/text-measurement.d.ts +25 -1
  83. package/dist/board/render/text-measurement.js +157 -19
  84. package/dist/board/render/text-resolution.d.ts +1 -6
  85. package/dist/board/render/text-resolution.js +1 -10
  86. package/dist/board/render/theme.d.ts +15 -0
  87. package/dist/board/render/theme.js +15 -0
  88. package/dist/board/replica/index.d.ts +4 -2
  89. package/dist/board/replica/index.js +28 -17
  90. package/dist/board/replica/indexeddb.d.ts +16 -0
  91. package/dist/board/replica/indexeddb.js +155 -0
  92. package/dist/board/stage/assets.d.ts +28 -5
  93. package/dist/board/stage/assets.js +293 -102
  94. package/dist/board/stage/bench.d.ts +35 -0
  95. package/dist/board/stage/bench.js +153 -0
  96. package/dist/board/stage/device.d.ts +41 -0
  97. package/dist/board/stage/device.js +62 -0
  98. package/dist/board/stage/frame-budget.d.ts +19 -0
  99. package/dist/board/stage/frame-budget.js +69 -0
  100. package/dist/board/stage/idle.d.ts +4 -0
  101. package/dist/board/stage/idle.js +26 -0
  102. package/dist/board/stage/index.d.ts +11 -7
  103. package/dist/board/stage/index.js +7 -4
  104. package/dist/board/stage/lru.d.ts +13 -9
  105. package/dist/board/stage/lru.js +38 -20
  106. package/dist/board/stage/presses.d.ts +21 -0
  107. package/dist/board/stage/presses.js +66 -0
  108. package/dist/board/stage/scene.d.ts +15 -1
  109. package/dist/board/stage/scene.js +248 -141
  110. package/dist/board/stage/stage.d.ts +28 -2
  111. package/dist/board/stage/stage.js +404 -112
  112. package/dist/board/stage/texture-pool.d.ts +10 -1
  113. package/dist/board/stage/texture-pool.js +72 -2
  114. package/dist/chunks/environment.d.ts +6 -0
  115. package/dist/chunks/http.d.ts +9 -1
  116. package/dist/chunks/http.js +791 -4
  117. package/dist/index.d.ts +52 -1
  118. package/dist/index.js +202 -862
  119. package/dist/media.d.ts +3 -27
  120. package/dist/media.js +3 -82
  121. package/dist/protocol/dist/board-constants.js +3 -3
  122. package/dist/protocol/dist/board-layout.d.ts +17 -3
  123. package/dist/protocol/dist/board-layout.js +68 -11
  124. package/dist/protocol/dist/board-model.d.ts +15 -1
  125. package/dist/protocol/dist/board-model.js +12 -2
  126. package/dist/protocol/dist/board-patch.js +1 -1
  127. package/dist/protocol/dist/board-schema.d.ts +2 -1
  128. package/dist/protocol/dist/board-schema.js +4 -1
  129. package/dist/protocol/dist/core/media.d.ts +35 -1
  130. package/dist/protocol/dist/core/media.js +127 -1
  131. package/dist/protocol/dist/index.d.ts +3 -3
  132. package/package.json +4 -2
  133. package/dist/board/model/image-key.d.ts +0 -9
  134. package/dist/board/model/image-key.js +0 -33
@@ -0,0 +1,153 @@
1
+ //#region src/board/stage/bench.ts
2
+ const STEP = {
3
+ name: "step",
4
+ frames: 1,
5
+ rest: 3e3,
6
+ wheel: { deltaY: 960 }
7
+ };
8
+ const BOARD_BENCH_PHASES = [
9
+ {
10
+ name: "zoom in",
11
+ frames: 15,
12
+ wheel: {
13
+ deltaY: -10,
14
+ ctrlKey: true
15
+ }
16
+ },
17
+ {
18
+ name: "close",
19
+ frames: 12,
20
+ wheel: {
21
+ deltaY: -10,
22
+ ctrlKey: true
23
+ }
24
+ },
25
+ STEP,
26
+ STEP,
27
+ STEP,
28
+ {
29
+ name: "pan close",
30
+ frames: 60,
31
+ wheel: {
32
+ deltaX: -24,
33
+ deltaY: 16
34
+ }
35
+ },
36
+ {
37
+ name: "zoom out",
38
+ frames: 12,
39
+ wheel: {
40
+ deltaY: 10,
41
+ ctrlKey: true
42
+ }
43
+ },
44
+ {
45
+ name: "pan",
46
+ frames: 120,
47
+ wheel: { deltaX: 24 }
48
+ },
49
+ {
50
+ name: "overview",
51
+ frames: 16,
52
+ wheel: {
53
+ deltaY: 10,
54
+ ctrlKey: true
55
+ }
56
+ }
57
+ ];
58
+ const SETTLE_LIMIT_MS = 2e4;
59
+ const LONG_FRAME_MS = 50;
60
+ function percentiles(values) {
61
+ const sorted = [...values].sort((a, b) => a - b);
62
+ const at = (ratio) => sorted[Math.min(sorted.length - 1, Math.floor(sorted.length * ratio))] ?? 0;
63
+ return {
64
+ p50: at(.5),
65
+ p95: at(.95),
66
+ max: sorted[sorted.length - 1] ?? 0
67
+ };
68
+ }
69
+ const nextFrame = () => new Promise((resolve) => requestAnimationFrame(resolve));
70
+ async function settle(stage, since) {
71
+ const times = {};
72
+ for (;;) {
73
+ const now = await nextFrame();
74
+ const { waiting, settling, loading } = stage.stats();
75
+ const at = now - since;
76
+ if (times.shown === void 0 && waiting === 0) times.shown = at;
77
+ if (times.ready === void 0 && !settling) times.ready = at;
78
+ if (times.loaded === void 0 && !settling && loading === 0) times.loaded = at;
79
+ if (times.loaded !== void 0 || at >= SETTLE_LIMIT_MS) return {
80
+ shown: times.shown ?? SETTLE_LIMIT_MS,
81
+ ready: times.ready ?? SETTLE_LIMIT_MS,
82
+ loaded: times.loaded ?? SETTLE_LIMIT_MS
83
+ };
84
+ }
85
+ }
86
+ function measureFrameWork() {
87
+ const original = globalThis.requestAnimationFrame;
88
+ let work = 0;
89
+ globalThis.requestAnimationFrame = (callback) => original((time) => {
90
+ const start = performance.now();
91
+ try {
92
+ callback(time);
93
+ } finally {
94
+ work += performance.now() - start;
95
+ }
96
+ });
97
+ return {
98
+ take: () => {
99
+ const taken = work;
100
+ work = 0;
101
+ return taken;
102
+ },
103
+ stop: () => {
104
+ globalThis.requestAnimationFrame = original;
105
+ }
106
+ };
107
+ }
108
+ async function runBoardStageBench(stage, { since = performance.now(), phases = BOARD_BENCH_PHASES } = {}) {
109
+ await stage.ready;
110
+ const canvas = stage.renderer?.canvas;
111
+ if (!canvas) throw new Error("Board stage has no canvas.");
112
+ const open = await settle(stage, since);
113
+ const rect = canvas.getBoundingClientRect();
114
+ const work = measureFrameWork();
115
+ try {
116
+ const reports = [];
117
+ for (const phase of phases) {
118
+ if (phase.rest) await new Promise((resolve) => setTimeout(resolve, phase.rest));
119
+ const intervals = [];
120
+ const costs = [];
121
+ let previous = await nextFrame();
122
+ for (let frame = 0; frame < phase.frames; frame += 1) {
123
+ canvas.dispatchEvent(new WheelEvent("wheel", {
124
+ bubbles: true,
125
+ cancelable: true,
126
+ clientX: rect.left + rect.width / 2,
127
+ clientY: rect.top + rect.height / 2,
128
+ ...phase.wheel
129
+ }));
130
+ work.take();
131
+ const now = await nextFrame();
132
+ costs.push(work.take());
133
+ intervals.push(now - previous);
134
+ previous = now;
135
+ }
136
+ reports.push({
137
+ name: phase.name,
138
+ frame: percentiles(intervals),
139
+ work: percentiles(costs),
140
+ long: intervals.filter((interval) => interval > LONG_FRAME_MS).length,
141
+ ...await settle(stage, previous)
142
+ });
143
+ }
144
+ return {
145
+ open,
146
+ phases: reports
147
+ };
148
+ } finally {
149
+ work.stop();
150
+ }
151
+ }
152
+ //#endregion
153
+ export { BOARD_BENCH_PHASES, runBoardStageBench };
@@ -0,0 +1,41 @@
1
+ import { BoardLod } from "../render/lod.js";
2
+ import { LruBudget } from "./lru.js";
3
+ //#region src/board/stage/device.d.ts
4
+ type BoardDeviceTier = "low" | "standard" | "high";
5
+ type BoardDeviceSignals = {
6
+ phone?: boolean;
7
+ memory?: number;
8
+ cores?: number;
9
+ saveData?: boolean;
10
+ slowNetwork?: boolean;
11
+ };
12
+ type BoardDeviceBudget = {
13
+ tier: BoardDeviceTier;
14
+ lod: BoardLod;
15
+ lru: LruBudget;
16
+ concurrency: number;
17
+ prefetch: number;
18
+ };
19
+ declare const BOARD_DEVICE_TIERS: readonly BoardDeviceTier[];
20
+ type BoardDeviceEnvironment = {
21
+ navigator?: Navigator & {
22
+ deviceMemory?: number;
23
+ connection?: {
24
+ saveData?: boolean;
25
+ effectiveType?: string;
26
+ };
27
+ };
28
+ matchMedia?: (query: string) => {
29
+ matches: boolean;
30
+ };
31
+ screen?: {
32
+ width: number;
33
+ height: number;
34
+ };
35
+ };
36
+ declare function readBoardDeviceSignals(environment?: BoardDeviceEnvironment): BoardDeviceSignals;
37
+ declare function boardDeviceTier({ phone, memory, cores }: BoardDeviceSignals): BoardDeviceTier;
38
+ declare function boardDeviceBudget(signals: BoardDeviceSignals, tier?: BoardDeviceTier): BoardDeviceBudget;
39
+ declare function defaultBoardDeviceBudget(): BoardDeviceBudget;
40
+ //#endregion
41
+ export { BOARD_DEVICE_TIERS, BoardDeviceBudget, BoardDeviceEnvironment, BoardDeviceSignals, BoardDeviceTier, boardDeviceBudget, boardDeviceTier, defaultBoardDeviceBudget, readBoardDeviceSignals };
@@ -0,0 +1,62 @@
1
+ import { COMPACT_BOARD_LOD, DEFAULT_BOARD_LOD } from "../render/lod.js";
2
+ import { COMPACT_LRU_BUDGET, DEFAULT_LRU_BUDGET } from "./lru.js";
3
+ //#region src/board/stage/device.ts
4
+ const BUDGETS = {
5
+ low: {
6
+ lod: COMPACT_BOARD_LOD,
7
+ lru: COMPACT_LRU_BUDGET,
8
+ concurrency: 4,
9
+ prefetch: 0
10
+ },
11
+ standard: {
12
+ lod: DEFAULT_BOARD_LOD,
13
+ lru: DEFAULT_LRU_BUDGET,
14
+ concurrency: 6,
15
+ prefetch: .5
16
+ },
17
+ high: {
18
+ lod: DEFAULT_BOARD_LOD,
19
+ lru: {
20
+ maxCount: 4096,
21
+ maxBytes: 402653184
22
+ },
23
+ concurrency: 8,
24
+ prefetch: 1
25
+ }
26
+ };
27
+ const BOARD_DEVICE_TIERS = Object.keys(BUDGETS);
28
+ const PHONE_SHORT_SIDE = 600;
29
+ function readBoardDeviceSignals(environment = globalThis) {
30
+ const { navigator: device, matchMedia, screen } = environment;
31
+ if (!device || !matchMedia || !screen) return {};
32
+ const connection = device.connection;
33
+ return {
34
+ phone: matchMedia("(pointer: coarse)").matches && !matchMedia("(any-pointer: fine)").matches && Math.min(screen.width, screen.height) < PHONE_SHORT_SIDE,
35
+ memory: device.deviceMemory,
36
+ cores: device.hardwareConcurrency,
37
+ saveData: Boolean(connection?.saveData),
38
+ slowNetwork: /^(slow-2g|2g|3g)$/.test(connection?.effectiveType ?? "")
39
+ };
40
+ }
41
+ function boardDeviceTier({ phone, memory, cores }) {
42
+ if (memory !== void 0 && memory <= 2 || cores !== void 0 && cores <= 2) return "low";
43
+ if (phone) return memory !== void 0 && memory >= 8 ? "standard" : "low";
44
+ if (memory !== void 0 && memory <= 4 && cores !== void 0 && cores <= 4) return "low";
45
+ if (memory !== void 0 && memory >= 8 && cores !== void 0 && cores >= 8) return "high";
46
+ return "standard";
47
+ }
48
+ function boardDeviceBudget(signals, tier = boardDeviceTier(signals)) {
49
+ const budget = BUDGETS[tier];
50
+ return {
51
+ tier,
52
+ ...budget,
53
+ prefetch: signals.saveData || signals.slowNetwork ? 0 : budget.prefetch
54
+ };
55
+ }
56
+ let detected;
57
+ function defaultBoardDeviceBudget() {
58
+ detected ??= boardDeviceBudget(readBoardDeviceSignals());
59
+ return detected;
60
+ }
61
+ //#endregion
62
+ export { BOARD_DEVICE_TIERS, boardDeviceBudget, boardDeviceTier, defaultBoardDeviceBudget, readBoardDeviceSignals };
@@ -0,0 +1,19 @@
1
+ //#region src/board/stage/frame-budget.d.ts
2
+ type BoardFrameWork = "materialize" | "plate" | "resolution";
3
+ type BoardFrameBudget = {
4
+ allow(kind: BoardFrameWork): number;
5
+ spend(kind: BoardFrameWork, units: number, ms: number): void;
6
+ };
7
+ type BoardFrameClock = BoardFrameBudget & {
8
+ begin(frameTime: number, options: {
9
+ moving: boolean;
10
+ chained: boolean;
11
+ }): void;
12
+ readonly interval: number;
13
+ };
14
+ declare function fixedBoardFrameBudget(): BoardFrameBudget & {
15
+ reset(): void;
16
+ };
17
+ declare function createBoardFrameClock(now?: () => number): BoardFrameClock;
18
+ //#endregion
19
+ export { BoardFrameBudget, BoardFrameClock, BoardFrameWork, createBoardFrameClock, fixedBoardFrameBudget };
@@ -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,4 @@
1
+ //#region src/board/stage/idle.d.ts
2
+ declare function scheduleBoardIdle(task: () => void, delay: number): () => void;
3
+ //#endregion
4
+ export { scheduleBoardIdle };
@@ -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
- import { COMPACT_LRU_BUDGET, DEFAULT_LRU_BUDGET, LruBudget, LruEntry, selectLruEvictions } from "./lru.js";
2
- import { BoardImageTexturePool, boardImageTexturePool, createBoardImageTexturePool, loadBoardImageTexture, unloadBoardImageTexture } from "./texture-pool.js";
3
- import { BoardAssetManager, BoardAssetManagerOptions, BoardModelPreviewRequest, BoardModelPreviews, boardAssetKey, boardTextureEdge, createBoardAssetManager } from "./assets.js";
4
- import { toBoardPointerEvent } from "./pointer.js";
5
- import { createBoardPointerGestures } from "./pointer-gestures.js";
1
+ import { COMPACT_LRU_BUDGET, DEFAULT_LRU_BUDGET, LruBudget, LruEntry, LruTrim, planLruTrim, selectLruEvictions } from "./lru.js";
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
- import { BoardStage, BoardStageOptions, BoardStagePointerPhase, BoardStageTheme, mountBoardStage } from "./stage.js";
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, BoardStageTheme, COMPACT_LRU_BUDGET, DEFAULT_LRU_BUDGET, LruBudget, LruEntry, SceneSyncInput, boardAssetKey, boardImageTexturePool, boardTextureEdge, createBoardAssetManager, createBoardImageTexturePool, createBoardItemViewLayer, createBoardPointerGestures, createBoardScene, drawBoardOverlay, expandFrame, loadBoardImageTexture, mountBoardStage, selectLruEvictions, toBoardPointerEvent, unloadBoardImageTexture };
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
- import { COMPACT_LRU_BUDGET, DEFAULT_LRU_BUDGET, selectLruEvictions } from "./lru.js";
2
- import { boardImageTexturePool, createBoardImageTexturePool, loadBoardImageTexture, unloadBoardImageTexture } from "./texture-pool.js";
3
- import { boardAssetKey, boardTextureEdge, createBoardAssetManager } from "./assets.js";
1
+ import { COMPACT_LRU_BUDGET, DEFAULT_LRU_BUDGET, planLruTrim, selectLruEvictions } from "./lru.js";
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";
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, boardAssetKey, boardImageTexturePool, boardTextureEdge, createBoardAssetManager, createBoardImageTexturePool, createBoardItemViewLayer, createBoardPointerGestures, createBoardScene, drawBoardOverlay, expandFrame, loadBoardImageTexture, mountBoardStage, selectLruEvictions, 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 };
@@ -6,8 +6,8 @@
6
6
  * they enter a "cooling" pool (reference count zero but still on the GPU) so a
7
7
  * quick pan back is instant instead of re-fetching. The pool is bounded by
8
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.
9
+ * recently used entries shrink or leave first. Keeping this decision pure makes
10
+ * the release-vs-churn trade-off trivial to test.
11
11
  */
12
12
  type LruEntry = {
13
13
  key: string;
@@ -15,19 +15,23 @@ type LruEntry = {
15
15
  lastUsedAt: number;
16
16
  /** Approximate GPU footprint in bytes. */
17
17
  bytes: number;
18
+ shrunkBytes?: number;
18
19
  };
19
20
  type LruBudget = {
20
21
  maxCount: number;
21
22
  maxBytes: number;
22
23
  };
23
- /** Default cooling-pool ceiling: 512 images or ~192 MB of GPU memory. */
24
- declare const DEFAULT_LRU_BUDGET: LruBudget;
25
- declare const COMPACT_LRU_BUDGET: LruBudget;
26
24
  /**
27
- * Given the unreferenced (cooling) entries and a budget, return the keys to
28
- * evict — oldest first — so the remaining pool fits within both limits. Returns
29
- * an empty array when the pool already fits.
25
+ * Default cooling-pool ceiling: 2048 images or ~192 MB of GPU memory. Bytes
26
+ * bind first for full-size textures; shrunk copies let many more stay warm.
30
27
  */
28
+ declare const DEFAULT_LRU_BUDGET: LruBudget;
29
+ declare const COMPACT_LRU_BUDGET: LruBudget;
30
+ type LruTrim = {
31
+ shrink: string[];
32
+ evict: string[];
33
+ };
34
+ declare function planLruTrim(entries: LruEntry[], budget: LruBudget): LruTrim;
31
35
  declare function selectLruEvictions(entries: LruEntry[], budget: LruBudget): string[];
32
36
  //#endregion
33
- export { COMPACT_LRU_BUDGET, DEFAULT_LRU_BUDGET, LruBudget, LruEntry, selectLruEvictions };
37
+ export { COMPACT_LRU_BUDGET, DEFAULT_LRU_BUDGET, LruBudget, LruEntry, LruTrim, planLruTrim, selectLruEvictions };
@@ -1,33 +1,51 @@
1
1
  //#region src/board/stage/lru.ts
2
- /** Default cooling-pool ceiling: 512 images or ~192 MB of GPU memory. */
2
+ /**
3
+ * Default cooling-pool ceiling: 2048 images or ~192 MB of GPU memory. Bytes
4
+ * bind first for full-size textures; shrunk copies let many more stay warm.
5
+ */
3
6
  const DEFAULT_LRU_BUDGET = {
4
- maxCount: 512,
7
+ maxCount: 2048,
5
8
  maxBytes: 201326592
6
9
  };
7
10
  const COMPACT_LRU_BUDGET = {
8
- maxCount: 256,
11
+ maxCount: 1024,
9
12
  maxBytes: 100663296
10
13
  };
11
- /**
12
- * Given the unreferenced (cooling) entries and a budget, return the keys to
13
- * evict — oldest first — so the remaining pool fits within both limits. Returns
14
- * an empty array when the pool already fits.
15
- */
16
- function selectLruEvictions(entries, budget) {
17
- if (entries.length === 0) return [];
18
- let count = entries.length;
14
+ function planLruTrim(entries, budget) {
19
15
  let bytes = 0;
20
16
  for (const entry of entries) bytes += entry.bytes;
21
- if (count <= budget.maxCount && bytes <= budget.maxBytes) return [];
17
+ const over = entries.length - budget.maxCount;
18
+ if (over <= 0 && bytes <= budget.maxBytes) return {
19
+ shrink: [],
20
+ evict: []
21
+ };
22
22
  const sorted = [...entries].sort((a, b) => a.lastUsedAt - b.lastUsedAt);
23
- const evict = [];
24
- for (const entry of sorted) {
25
- if (count <= budget.maxCount && bytes <= budget.maxBytes) break;
26
- evict.push(entry.key);
27
- count -= 1;
28
- bytes -= entry.bytes;
23
+ const evict = sorted.slice(0, Math.max(0, over));
24
+ const kept = sorted.slice(evict.length);
25
+ for (const entry of evict) bytes -= entry.bytes;
26
+ const shrink = /* @__PURE__ */ new Set();
27
+ for (const entry of kept) {
28
+ if (bytes <= budget.maxBytes) break;
29
+ if (entry.shrunkBytes === void 0 || entry.shrunkBytes >= entry.bytes) continue;
30
+ shrink.add(entry);
31
+ bytes -= entry.bytes - entry.shrunkBytes;
32
+ }
33
+ for (const entry of kept) {
34
+ if (bytes <= budget.maxBytes) break;
35
+ bytes -= shrink.delete(entry) ? entry.shrunkBytes : entry.bytes;
36
+ evict.push(entry);
29
37
  }
30
- return evict;
38
+ return {
39
+ shrink: [...shrink].map(({ key }) => key),
40
+ evict: evict.map(({ key }) => key)
41
+ };
42
+ }
43
+ function selectLruEvictions(entries, budget) {
44
+ return planLruTrim(entries.map(({ key, lastUsedAt, bytes }) => ({
45
+ key,
46
+ lastUsedAt,
47
+ bytes
48
+ })), budget).evict;
31
49
  }
32
50
  //#endregion
33
- export { COMPACT_LRU_BUDGET, DEFAULT_LRU_BUDGET, selectLruEvictions };
51
+ export { COMPACT_LRU_BUDGET, DEFAULT_LRU_BUDGET, planLruTrim, selectLruEvictions };
@@ -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 };
@@ -1,8 +1,11 @@
1
1
  import { BoardDrawPoint } from "../../protocol/dist/board-model.js";
2
+ import { Rect } from "../model/geometry.js";
2
3
  import { BoardScene as BoardScene$1, BoardSceneItem } from "../model/scene.js";
3
4
  import "../model/index.js";
5
+ import { BoardLod } from "../render/lod.js";
4
6
  import { BoardCardRendererResolver, BoardRenderContext } from "../render/renderers/board-renderer-registry.js";
5
7
  import "../render/index.js";
8
+ import { BoardFrameBudget } from "./frame-budget.js";
6
9
  import { Container, Graphics } from "pixi.js";
7
10
  //#region src/board/stage/scene.d.ts
8
11
  type BoardLiveStroke = {
@@ -22,12 +25,15 @@ type SceneSyncInput = {
22
25
  globalSig: string;
23
26
  staleIds?: ReadonlySet<string>;
24
27
  structureVersion: number;
25
- geometryVersion: number;
26
28
  gestureActive: boolean;
27
29
  focus?: {
28
30
  x: number;
29
31
  y: number;
30
32
  };
33
+ /** The world area on screen; live cards outside it skip rendering. */
34
+ view?: Rect;
35
+ lod?: BoardLod;
36
+ budget?: BoardFrameBudget;
31
37
  };
32
38
  type BoardSceneNode = {
33
39
  item: BoardSceneItem;
@@ -37,10 +43,18 @@ type BoardScene = {
37
43
  sync: (input: SceneSyncInput) => void;
38
44
  getNode: (itemId: string) => BoardSceneNode | null;
39
45
  holders: (key: string) => ReadonlySet<string>;
46
+ repaintFar: (keys: ReadonlySet<string>, assetKey: (item: BoardSceneItem) => string | null) => void;
40
47
  readonly liveVersion: number;
41
48
  liveIds: () => ReadonlySet<string>;
49
+ /** Live cards and cards drawn on far plates. */
50
+ counts: () => {
51
+ live: number;
52
+ far: number;
53
+ };
54
+ readonly settling: boolean;
42
55
  readonly animated: boolean;
43
56
  tick: (clock: number) => number | null;
57
+ redrawText: () => void;
44
58
  destroy: (context: BoardRenderContext) => void;
45
59
  };
46
60
  declare function createBoardScene(options: {