@mindexec/cli 0.2.476 → 0.2.478
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 +323 -323
- package/codex-model-catalog.js +202 -202
- package/codex-runtime.js +1608 -1608
- package/desktop-workspace-state.cjs +120 -120
- package/electron/main.cjs +517 -517
- package/electron/preload.cjs +17 -17
- package/electron/recurring-update-owner-smoke.mjs +55 -55
- package/electron/recurring-update-owner.cjs +97 -97
- package/electron/source-smoke.mjs +108 -108
- package/electron/update-feed-resolver.cjs +79 -79
- package/electron/update-feed.json +10 -10
- package/electron/update-manager-smoke.mjs +146 -146
- package/electron/update-manager.cjs +297 -297
- package/electron/windows-package-smoke.mjs +156 -156
- package/launch-bridge.cjs +236 -236
- package/lzo1x.js +110 -110
- package/mode4-atlas-worker.js +440 -440
- package/mode4-atlas.js +130 -130
- package/package.json +1 -1
- package/port-guard.cjs +285 -285
- package/remote-fast/osx-arm64/MacScreenCaptureKitH264.swift +339 -339
- package/remote-fast/osx-arm64/MacSystemAudioCapture.swift +214 -214
- package/remote-fast/osx-arm64/UnixRawCaptureHelper.mjs +80 -80
- package/remote-fast/osx-arm64/livedesk-raw-capture-resampler.mjs +73 -73
- package/remote-fast/osx-arm64/mindexec-remote-fast.dll +0 -0
- package/remote-fast/osx-x64/MacScreenCaptureKitH264.swift +339 -339
- package/remote-fast/osx-x64/MacSystemAudioCapture.swift +214 -214
- package/remote-fast/osx-x64/UnixRawCaptureHelper.mjs +80 -80
- package/remote-fast/osx-x64/livedesk-raw-capture-resampler.mjs +73 -73
- package/remote-fast/osx-x64/mindexec-remote-fast.dll +0 -0
- package/remote-fast/win-x64/mindexec-remote-fast.dll +0 -0
- package/remote-hub.js +3698 -3698
- package/scripts/auth-session-smoke.mjs +262 -262
- package/scripts/board-render-ownership-smoke.mjs +85 -85
- package/scripts/codex-model-catalog-smoke.mjs +150 -150
- package/scripts/codex-runtime-home-smoke.mjs +58 -58
- package/scripts/codex-sdk-image-live-smoke.mjs +43 -43
- package/scripts/codex-sdk-image-runtime-smoke.mjs +122 -122
- package/scripts/codex-sdk-runtime-smoke.mjs +89 -89
- package/scripts/desktop-update-publisher-smoke.mjs +89 -89
- package/scripts/desktop-update-worker-smoke.mjs +72 -72
- package/scripts/desktop-workspace-state-smoke.mjs +81 -81
- package/scripts/drive-installed-update-smoke.mjs +67 -67
- package/scripts/npm-fresh-install-smoke.mjs +216 -216
- package/scripts/publish-mindexec-desktop-updates.mjs +224 -224
- package/scripts/remote-agent-managed-smoke.mjs +318 -318
- package/scripts/remote-agent-package-smoke.mjs +64 -64
- package/scripts/remote-agent-ws-smoke.mjs +269 -269
- package/scripts/remote-fast-live-rate-smoke.mjs +343 -343
- package/scripts/remote-fast-mdm-browser-smoke.mjs +453 -453
- package/scripts/remote-fleet-render-smoke.mjs +1350 -1350
- package/scripts/remote-frame-ws-smoke.mjs +234 -234
- package/scripts/remote-http-smoke.mjs +592 -592
- package/scripts/remote-hub-identity-smoke.mjs +146 -146
- package/scripts/remote-hub-scale-smoke.mjs +124 -124
- package/scripts/remote-hub-smoke.mjs +699 -699
- package/scripts/remote-input-ws-smoke.mjs +263 -263
- package/scripts/remote-registry-follower-smoke.mjs +815 -815
- package/scripts/setup-tree-sitter-grammars.mjs +59 -59
- package/server.js +7690 -7690
- package/start-bridge.bat +32 -32
- package/start-bridge.sh +81 -81
- package/wwwroot/_content/MindExecution.Plugins.Admin/css/admin-dashboard.css +546 -546
- package/wwwroot/_content/MindExecution.Plugins.YouTube/css/youtube-search.css +251 -251
- package/wwwroot/_content/MindExecution.Shared/css/app.css +1 -1
- package/wwwroot/_content/MindExecution.Shared/css/mind-map-overrides.css +3789 -3789
- package/wwwroot/_content/MindExecution.Shared/js/agent-visualization.js +27 -27
- package/wwwroot/_content/MindExecution.Shared/js/background-themes.js +1449 -1449
- package/wwwroot/_content/MindExecution.Shared/js/code-master.js +8316 -8316
- package/wwwroot/_content/MindExecution.Shared/js/file-system-helper.js +338 -338
- package/wwwroot/_content/MindExecution.Shared/js/marked.min.js +69 -69
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-animated-image-preview.js +270 -270
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-automation-edge-motion.js +170 -170
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-board-render-ownership.js +81 -81
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-core.js +10506 -10517
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-css3d-hotpath.js +902 -902
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-css3d-manager.js +24637 -24637
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-dev-guards.js +325 -325
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-dnd.js +1491 -1491
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-glow-shader.js +70 -70
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-image-policy.js +218 -218
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-interactions.js +7065 -7065
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-lod-plan-worker.js +160 -160
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-lod-renderer.js +9956 -9956
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-lod-video-placeholder.js +107 -107
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-logic-workers.js +1004 -1004
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-menu-manager.js +1351 -1351
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-multi-select.js +865 -865
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-node-search-worker.js +554 -554
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-nodes.js +4744 -4744
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-normal-boundary-optimizer.js +624 -624
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-object-manager.js +241 -241
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-pipeline.js +2290 -2290
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-remote-codecs.js +202 -202
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-render-plan.js +1971 -1971
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-text-lod-system.js +93 -93
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-texture-factory.js +2391 -2391
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-video-playback-controls.js +95 -95
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-visibility-worker.js +895 -895
- package/wwwroot/_content/MindExecution.Shared/js/mindmap-toolbar.js +636 -636
- package/wwwroot/_content/MindExecution.Shared/js/native-drop-handler.js +45 -45
- package/wwwroot/_content/MindExecution.Shared/js/plan-master.js +117 -117
- package/wwwroot/_content/MindExecution.Shared/js/renderers/CSS3DRenderer.js +114 -114
- package/wwwroot/_content/MindExecution.Shared/js/texture-worker-manager.js +10 -10
- package/wwwroot/_content/MindExecution.Shared/js/three.min.js +6 -6
- package/wwwroot/_content/MindExecution.Shared/js/token-manager.js +7 -7
- package/wwwroot/_content/MindExecution.Shared/js/troika-bundle.js +6 -6
- package/wwwroot/_content/MindExecution.Shared/js/troika-bundle.js.map +2 -2
- package/wwwroot/_content/MindExecution.Shared/lib/font-awesome/css/all.min.css +8 -8
- package/wwwroot/_content/MindExecution.Shared/models/vocab.txt +30522 -30522
- package/wwwroot/_framework/{Google.Protobuf.9h59ukbel7.dll → Google.Protobuf.33g2gb5is0.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Core.cx2pre7qz2.dll → MindExecution.Core.z100xo4mg0.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Kernel.engfih12kr.dll → MindExecution.Kernel.i9cldrx8yc.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Plugins.Admin.xfunemn9zi.dll → MindExecution.Plugins.Admin.48lzqddb0a.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Plugins.Business.ja94a5ze3i.dll → MindExecution.Plugins.Business.7m8u1q142p.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Plugins.Concept.3q05vzviqb.dll → MindExecution.Plugins.Concept.cl5wmr53od.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Plugins.Directory.re4jjs91d1.dll → MindExecution.Plugins.Directory.slipi39041.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Plugins.PlanMaster.89etwfquz1.dll → MindExecution.Plugins.PlanMaster.qfge05l671.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Plugins.YouTube.aqf4vztjji.dll → MindExecution.Plugins.YouTube.tn7v29ut77.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Shared.9b1rucdxvv.dll → MindExecution.Shared.rjqlf0umjn.dll} +0 -0
- package/wwwroot/_framework/{MindExecution.Web.ssp0n0xf1e.dll → MindExecution.Web.rwawrxxk38.dll} +0 -0
- package/wwwroot/_framework/blazor.boot.json +23 -23
- package/wwwroot/_headers +40 -40
- package/wwwroot/_redirects +1 -1
- package/wwwroot/index.html +772 -772
- package/wwwroot/js/marketing-tool.js +180 -180
- package/wwwroot/manifest.webmanifest +23 -23
- package/wwwroot/service-worker-assets.js +78 -106
- package/wwwroot/service-worker.js +33 -33
- package/tree-sitter-grammars/README.md +0 -18
- package/tree-sitter-grammars/tree-sitter-c_sharp.wasm +0 -0
- package/tree-sitter-grammars/tree-sitter-go.wasm +0 -0
- package/tree-sitter-grammars/tree-sitter-java.wasm +0 -0
- package/tree-sitter-grammars/tree-sitter-javascript.wasm +0 -0
- package/tree-sitter-grammars/tree-sitter-python.wasm +0 -0
- package/tree-sitter-grammars/tree-sitter-rust.wasm +0 -0
- package/tree-sitter-grammars/tree-sitter-tsx.wasm +0 -0
- package/tree-sitter-grammars/tree-sitter-typescript.wasm +0 -0
- package/wwwroot/_content/MindExecution.Plugins.Directory/background.png +0 -0
- package/wwwroot/_content/MindExecution.Shared/fonts/NotoSansKR-Bold.ttf +0 -0
- package/wwwroot/_content/MindExecution.Shared/fonts/NotoSansKR-Regular.ttf +0 -0
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-core.js.backup +0 -1059
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-dnd.js.bak +0 -434
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-object-manager.js.backup +0 -372
- package/wwwroot/_content/MindExecution.Shared/js/mind-map-texture-factory.js.backup +0 -1258
|
@@ -1,895 +1,895 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const FULL_RES_VISIBILITY_EXIT_MARGIN_SCALE = 1.8;
|
|
4
|
-
const MAX_DIRECT_GRID_LOOKUPS = 4096;
|
|
5
|
-
const DEFAULT_HIT_TEST_CELL_RADIUS = 1;
|
|
6
|
-
const MAX_HIT_TEST_CELL_RADIUS = 3;
|
|
7
|
-
|
|
8
|
-
function createState() {
|
|
9
|
-
return {
|
|
10
|
-
boardId: '',
|
|
11
|
-
cellSize: 500,
|
|
12
|
-
nodes: new Map(),
|
|
13
|
-
spatialGrid: new Map(),
|
|
14
|
-
lastVisibleIds: new Set(),
|
|
15
|
-
lastPriorityIds: [],
|
|
16
|
-
lastVisibilityRect: null,
|
|
17
|
-
lastNodeCount: 0,
|
|
18
|
-
lastResultSeq: 0
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
let runtime = createState();
|
|
23
|
-
|
|
24
|
-
function normalizeBoardId(boardId) {
|
|
25
|
-
return String(boardId ?? '').trim();
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function normalizeNodeSnapshot(node) {
|
|
29
|
-
if (!node || typeof node !== 'object') {
|
|
30
|
-
return null;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const id = String(node.id || '').trim();
|
|
34
|
-
if (!id) {
|
|
35
|
-
return null;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const minX = Number(node.minX || 0);
|
|
39
|
-
const maxX = Number(node.maxX || minX);
|
|
40
|
-
const minY = Number(node.minY || 0);
|
|
41
|
-
const maxY = Number(node.maxY || minY);
|
|
42
|
-
|
|
43
|
-
return {
|
|
44
|
-
id,
|
|
45
|
-
contentType: String(node.contentType || '').trim().toLowerCase(),
|
|
46
|
-
textLike: node.textLike === true,
|
|
47
|
-
x: Number(node.x || minX),
|
|
48
|
-
y: Number(node.y || maxY),
|
|
49
|
-
z: Number(node.z || 0),
|
|
50
|
-
width: Math.max(1, Number(node.width || (maxX - minX) || 1)),
|
|
51
|
-
height: Math.max(1, Number(node.height || (maxY - minY) || 1)),
|
|
52
|
-
minX,
|
|
53
|
-
maxX,
|
|
54
|
-
minY,
|
|
55
|
-
maxY,
|
|
56
|
-
cellKeys: Array.isArray(node.cellKeys)
|
|
57
|
-
? node.cellKeys.map((key) => String(key || '').trim()).filter(Boolean)
|
|
58
|
-
: []
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function clearState(boardId, cellSize) {
|
|
63
|
-
runtime = createState();
|
|
64
|
-
runtime.boardId = normalizeBoardId(boardId);
|
|
65
|
-
runtime.cellSize = Math.max(1, Number(cellSize || 500));
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function removeNodeFromGrid(nodeId, node) {
|
|
69
|
-
const normalizedId = String(nodeId || '').trim();
|
|
70
|
-
if (!normalizedId || !node?.cellKeys) {
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
for (let i = 0; i < node.cellKeys.length; i++) {
|
|
75
|
-
const key = node.cellKeys[i];
|
|
76
|
-
const cell = runtime.spatialGrid.get(key);
|
|
77
|
-
if (!cell) {
|
|
78
|
-
continue;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
cell.delete(normalizedId);
|
|
82
|
-
if (cell.size === 0) {
|
|
83
|
-
runtime.spatialGrid.delete(key);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
function upsertNode(node) {
|
|
89
|
-
const normalizedNode = normalizeNodeSnapshot(node);
|
|
90
|
-
if (!normalizedNode) {
|
|
91
|
-
return;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
const existing = runtime.nodes.get(normalizedNode.id);
|
|
95
|
-
if (existing) {
|
|
96
|
-
removeNodeFromGrid(normalizedNode.id, existing);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
runtime.nodes.set(normalizedNode.id, normalizedNode);
|
|
100
|
-
for (let i = 0; i < normalizedNode.cellKeys.length; i++) {
|
|
101
|
-
const key = normalizedNode.cellKeys[i];
|
|
102
|
-
let cell = runtime.spatialGrid.get(key);
|
|
103
|
-
if (!cell) {
|
|
104
|
-
cell = new Set();
|
|
105
|
-
runtime.spatialGrid.set(key, cell);
|
|
106
|
-
}
|
|
107
|
-
cell.add(normalizedNode.id);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
function removeNode(nodeId) {
|
|
112
|
-
const normalizedId = String(nodeId || '').trim();
|
|
113
|
-
if (!normalizedId) {
|
|
114
|
-
return;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
const existing = runtime.nodes.get(normalizedId);
|
|
118
|
-
if (!existing) {
|
|
119
|
-
return;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
removeNodeFromGrid(normalizedId, existing);
|
|
123
|
-
runtime.nodes.delete(normalizedId);
|
|
124
|
-
runtime.lastVisibleIds.delete(normalizedId);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
function rebuildBoard(boardId, cellSize, nodes) {
|
|
128
|
-
clearState(boardId, cellSize);
|
|
129
|
-
const sourceNodes = Array.isArray(nodes) ? nodes : [];
|
|
130
|
-
for (let i = 0; i < sourceNodes.length; i++) {
|
|
131
|
-
upsertNode(sourceNodes[i]);
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
function getVisibilityMarginPolicy(cameraZ, isPanning = false, isCtrlWheelVerticalScrolling = false) {
|
|
136
|
-
let cullingMarginPx = 3000;
|
|
137
|
-
let maxMarginCells = 6;
|
|
138
|
-
let useCoarseVisibility = false;
|
|
139
|
-
|
|
140
|
-
if (cameraZ < 4500) {
|
|
141
|
-
cullingMarginPx = 1000;
|
|
142
|
-
maxMarginCells = 4;
|
|
143
|
-
} else if (cameraZ < 12000) {
|
|
144
|
-
cullingMarginPx = 2200;
|
|
145
|
-
maxMarginCells = 5;
|
|
146
|
-
} else if (cameraZ < 30000) {
|
|
147
|
-
cullingMarginPx = 900;
|
|
148
|
-
maxMarginCells = 2;
|
|
149
|
-
} else {
|
|
150
|
-
cullingMarginPx = 240;
|
|
151
|
-
maxMarginCells = 1;
|
|
152
|
-
useCoarseVisibility = isPanning !== true;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
if (isPanning === true) {
|
|
156
|
-
if (cameraZ < 4500) {
|
|
157
|
-
cullingMarginPx += 500;
|
|
158
|
-
maxMarginCells += 1;
|
|
159
|
-
} else if (cameraZ < 12000) {
|
|
160
|
-
cullingMarginPx += 700;
|
|
161
|
-
maxMarginCells += 1;
|
|
162
|
-
} else if (cameraZ < 30000) {
|
|
163
|
-
cullingMarginPx += 600;
|
|
164
|
-
maxMarginCells += 1;
|
|
165
|
-
} else {
|
|
166
|
-
cullingMarginPx += 320;
|
|
167
|
-
maxMarginCells += 1;
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
if (isCtrlWheelVerticalScrolling && cameraZ < 12000) {
|
|
172
|
-
cullingMarginPx += (cameraZ < 4500) ? 700 : 900;
|
|
173
|
-
maxMarginCells += 2;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
return {
|
|
177
|
-
cullingMarginPx,
|
|
178
|
-
maxMarginCells,
|
|
179
|
-
useCoarseVisibility
|
|
180
|
-
};
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
function describeCellRange(rect, cellSize) {
|
|
184
|
-
if (!rect) {
|
|
185
|
-
return null;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
const left = Number(rect.left);
|
|
189
|
-
const right = Number(rect.right);
|
|
190
|
-
const bottom = Number(rect.bottom);
|
|
191
|
-
const top = Number(rect.top);
|
|
192
|
-
if (!Number.isFinite(left) || !Number.isFinite(right) || !Number.isFinite(bottom) || !Number.isFinite(top)) {
|
|
193
|
-
return null;
|
|
194
|
-
}
|
|
195
|
-
if (right < left || top < bottom) {
|
|
196
|
-
return null;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
const startCellX = Math.floor(left / cellSize);
|
|
200
|
-
const endCellX = Math.floor(right / cellSize);
|
|
201
|
-
const startCellY = Math.floor(bottom / cellSize);
|
|
202
|
-
const endCellY = Math.floor(top / cellSize);
|
|
203
|
-
const spanX = Math.max(0, endCellX - startCellX + 1);
|
|
204
|
-
const spanY = Math.max(0, endCellY - startCellY + 1);
|
|
205
|
-
|
|
206
|
-
return {
|
|
207
|
-
startCellX,
|
|
208
|
-
endCellX,
|
|
209
|
-
startCellY,
|
|
210
|
-
endCellY,
|
|
211
|
-
spanX,
|
|
212
|
-
spanY,
|
|
213
|
-
directGridLookups: spanX * spanY
|
|
214
|
-
};
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
function intersectsRect(node, rect) {
|
|
218
|
-
if (!node || !rect) {
|
|
219
|
-
return false;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
return !(
|
|
223
|
-
node.maxX < rect.left ||
|
|
224
|
-
node.minX > rect.right ||
|
|
225
|
-
node.maxY < rect.bottom ||
|
|
226
|
-
node.minY > rect.top
|
|
227
|
-
);
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
function collectCandidates(range) {
|
|
231
|
-
const candidateIds = new Set();
|
|
232
|
-
let usedPopulatedCellScan = false;
|
|
233
|
-
|
|
234
|
-
if (!range) {
|
|
235
|
-
runtime.nodes.forEach((_, nodeId) => candidateIds.add(nodeId));
|
|
236
|
-
return {
|
|
237
|
-
candidateIds,
|
|
238
|
-
usedPopulatedCellScan,
|
|
239
|
-
directGridLookups: 0,
|
|
240
|
-
gridSpanX: 0,
|
|
241
|
-
gridSpanY: 0
|
|
242
|
-
};
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
if (range.directGridLookups > MAX_DIRECT_GRID_LOOKUPS) {
|
|
246
|
-
usedPopulatedCellScan = true;
|
|
247
|
-
runtime.spatialGrid.forEach((nodeIds, key) => {
|
|
248
|
-
const separatorIndex = key.indexOf(':');
|
|
249
|
-
if (separatorIndex <= 0) {
|
|
250
|
-
return;
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
const cx = Number(key.slice(0, separatorIndex));
|
|
254
|
-
const cy = Number(key.slice(separatorIndex + 1));
|
|
255
|
-
if (!Number.isFinite(cx) || !Number.isFinite(cy)) {
|
|
256
|
-
return;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
if (cx >= range.startCellX && cx <= range.endCellX && cy >= range.startCellY && cy <= range.endCellY) {
|
|
260
|
-
nodeIds.forEach((nodeId) => candidateIds.add(nodeId));
|
|
261
|
-
}
|
|
262
|
-
});
|
|
263
|
-
} else {
|
|
264
|
-
for (let cx = range.startCellX; cx <= range.endCellX; cx++) {
|
|
265
|
-
for (let cy = range.startCellY; cy <= range.endCellY; cy++) {
|
|
266
|
-
const cell = runtime.spatialGrid.get(`${cx}:${cy}`);
|
|
267
|
-
if (!cell) {
|
|
268
|
-
continue;
|
|
269
|
-
}
|
|
270
|
-
cell.forEach((nodeId) => candidateIds.add(nodeId));
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
runtime.lastVisibleIds.forEach((nodeId) => {
|
|
276
|
-
if (runtime.nodes.has(nodeId)) {
|
|
277
|
-
candidateIds.add(nodeId);
|
|
278
|
-
}
|
|
279
|
-
});
|
|
280
|
-
|
|
281
|
-
return {
|
|
282
|
-
candidateIds,
|
|
283
|
-
usedPopulatedCellScan,
|
|
284
|
-
directGridLookups: range.directGridLookups,
|
|
285
|
-
gridSpanX: range.spanX,
|
|
286
|
-
gridSpanY: range.spanY
|
|
287
|
-
};
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
function buildCellKeySetFromRange(range, targetSet) {
|
|
291
|
-
if (!range || !targetSet) {
|
|
292
|
-
return;
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
for (let cx = range.startCellX; cx <= range.endCellX; cx++) {
|
|
296
|
-
for (let cy = range.startCellY; cy <= range.endCellY; cy++) {
|
|
297
|
-
targetSet.add(`${cx}:${cy}`);
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
function addCandidatesFromCellKeySet(cellKeys, candidateIds, skipPreviouslyVisible = false) {
|
|
303
|
-
if (!cellKeys || !candidateIds || cellKeys.size === 0) {
|
|
304
|
-
return;
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
cellKeys.forEach((key) => {
|
|
308
|
-
const nodeIds = runtime.spatialGrid.get(key);
|
|
309
|
-
if (!nodeIds) {
|
|
310
|
-
return;
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
nodeIds.forEach((nodeId) => {
|
|
314
|
-
if (skipPreviouslyVisible && runtime.lastVisibleIds.has(nodeId)) {
|
|
315
|
-
return;
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
candidateIds.add(nodeId);
|
|
319
|
-
});
|
|
320
|
-
});
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
function normalizeHitTestCellRadius(cellRadius) {
|
|
324
|
-
const normalized = Math.round(Number(cellRadius || DEFAULT_HIT_TEST_CELL_RADIUS));
|
|
325
|
-
if (!Number.isFinite(normalized)) {
|
|
326
|
-
return DEFAULT_HIT_TEST_CELL_RADIUS;
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
return Math.max(0, Math.min(MAX_HIT_TEST_CELL_RADIUS, normalized));
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
function collectHitTestCandidateIds(centerCellX, centerCellY, cellRadius) {
|
|
333
|
-
const candidateIds = new Set();
|
|
334
|
-
const normalizedCellRadius = normalizeHitTestCellRadius(cellRadius);
|
|
335
|
-
let cellLookups = 0;
|
|
336
|
-
|
|
337
|
-
for (let offsetX = -normalizedCellRadius; offsetX <= normalizedCellRadius; offsetX++) {
|
|
338
|
-
for (let offsetY = -normalizedCellRadius; offsetY <= normalizedCellRadius; offsetY++) {
|
|
339
|
-
cellLookups++;
|
|
340
|
-
const cell = runtime.spatialGrid.get(`${centerCellX + offsetX}:${centerCellY + offsetY}`);
|
|
341
|
-
if (!cell) {
|
|
342
|
-
continue;
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
cell.forEach((nodeId) => candidateIds.add(nodeId));
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
return {
|
|
350
|
-
candidateIds,
|
|
351
|
-
cellLookups,
|
|
352
|
-
cellRadius: normalizedCellRadius
|
|
353
|
-
};
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
function normalizeSelectionRect(rect) {
|
|
357
|
-
if (!rect || typeof rect !== 'object') {
|
|
358
|
-
return null;
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
const left = Number(rect.left);
|
|
362
|
-
const right = Number(rect.right);
|
|
363
|
-
const bottom = Number(rect.bottom);
|
|
364
|
-
const top = Number(rect.top);
|
|
365
|
-
if (!Number.isFinite(left) || !Number.isFinite(right) || !Number.isFinite(bottom) || !Number.isFinite(top)) {
|
|
366
|
-
return null;
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
return {
|
|
370
|
-
left: Math.min(left, right),
|
|
371
|
-
right: Math.max(left, right),
|
|
372
|
-
bottom: Math.min(bottom, top),
|
|
373
|
-
top: Math.max(bottom, top)
|
|
374
|
-
};
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
function buildPriorityIds(visibleIds, camX, camY) {
|
|
378
|
-
const buffer = [];
|
|
379
|
-
visibleIds.forEach((nodeId) => {
|
|
380
|
-
const node = runtime.nodes.get(nodeId);
|
|
381
|
-
if (!node) {
|
|
382
|
-
return;
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
const dx = Number(node.x || 0) - camX;
|
|
386
|
-
const dy = Number(node.y || 0) - camY;
|
|
387
|
-
const distSq = (dx * dx) + (dy * dy);
|
|
388
|
-
const textPriority = node.textLike === true ? 0 : 1;
|
|
389
|
-
const mediaPriority = node.contentType === 'image' ? 0 : 1;
|
|
390
|
-
buffer.push({ nodeId, textPriority, mediaPriority, distSq });
|
|
391
|
-
});
|
|
392
|
-
|
|
393
|
-
buffer.sort((a, b) => {
|
|
394
|
-
if (a.textPriority !== b.textPriority) {
|
|
395
|
-
return a.textPriority - b.textPriority;
|
|
396
|
-
}
|
|
397
|
-
if (a.mediaPriority !== b.mediaPriority) {
|
|
398
|
-
return a.mediaPriority - b.mediaPriority;
|
|
399
|
-
}
|
|
400
|
-
return a.distSq - b.distSq;
|
|
401
|
-
});
|
|
402
|
-
|
|
403
|
-
return buffer.map((entry) => entry.nodeId);
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
function buildIncrementalPriorityIds(nextVisibleIds, enteredIds, camX, camY) {
|
|
407
|
-
const priorityIds = [];
|
|
408
|
-
const used = new Set();
|
|
409
|
-
|
|
410
|
-
if (Array.isArray(runtime.lastPriorityIds)) {
|
|
411
|
-
for (let i = 0; i < runtime.lastPriorityIds.length; i++) {
|
|
412
|
-
const nodeId = String(runtime.lastPriorityIds[i] || '').trim();
|
|
413
|
-
if (!nodeId || used.has(nodeId) || !nextVisibleIds.has(nodeId)) {
|
|
414
|
-
continue;
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
priorityIds.push(nodeId);
|
|
418
|
-
used.add(nodeId);
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
const enteredSet = new Set();
|
|
423
|
-
if (Array.isArray(enteredIds)) {
|
|
424
|
-
for (let i = 0; i < enteredIds.length; i++) {
|
|
425
|
-
const nodeId = String(enteredIds[i] || '').trim();
|
|
426
|
-
if (nodeId && nextVisibleIds.has(nodeId) && !used.has(nodeId)) {
|
|
427
|
-
enteredSet.add(nodeId);
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
if (enteredSet.size > 0) {
|
|
433
|
-
const enteredPriorityIds = buildPriorityIds(enteredSet, camX, camY);
|
|
434
|
-
for (let i = 0; i < enteredPriorityIds.length; i++) {
|
|
435
|
-
const nodeId = enteredPriorityIds[i];
|
|
436
|
-
if (!used.has(nodeId)) {
|
|
437
|
-
priorityIds.push(nodeId);
|
|
438
|
-
used.add(nodeId);
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
if (priorityIds.length !== nextVisibleIds.size) {
|
|
444
|
-
return buildPriorityIds(nextVisibleIds, camX, camY);
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
return priorityIds;
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
function handleVisibilityQuery(message) {
|
|
451
|
-
const boardId = normalizeBoardId(message.boardId);
|
|
452
|
-
if (!boardId || boardId !== runtime.boardId) {
|
|
453
|
-
return;
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
const t0 = (typeof performance !== 'undefined' && typeof performance.now === 'function')
|
|
457
|
-
? performance.now()
|
|
458
|
-
: Date.now();
|
|
459
|
-
|
|
460
|
-
const camera = message.camera || {};
|
|
461
|
-
const state = message.state || {};
|
|
462
|
-
const seq = Number(message.seq || 0);
|
|
463
|
-
const previousResultSeq = Math.max(0, Math.floor(Number(runtime.lastResultSeq || 0)));
|
|
464
|
-
const mainLastAppliedSeq = Math.max(0, Math.floor(Number(message.mainLastAppliedSeq || 0)));
|
|
465
|
-
const cellSize = Math.max(1, Number(message.cellSize || runtime.cellSize || 500));
|
|
466
|
-
const camX = Number(camera.x || 0);
|
|
467
|
-
const camY = Number(camera.y || 0);
|
|
468
|
-
const camZ = Number(camera.z || 0);
|
|
469
|
-
const camNear = Number(camera.near || 0);
|
|
470
|
-
const camFar = Number(camera.far || Number.MAX_SAFE_INTEGER);
|
|
471
|
-
const viewportW = Math.max(1, Number(camera.viewportW || 1));
|
|
472
|
-
const viewportH = Math.max(1, Number(camera.viewportH || 1));
|
|
473
|
-
const aspect = Math.max(0.1, Number(camera.aspect || 1));
|
|
474
|
-
const fov = Number(camera.fov || 45);
|
|
475
|
-
const isPanning = state.isPanning === true;
|
|
476
|
-
const isZooming = state.isZooming === true;
|
|
477
|
-
const isFullResMode = state.isFullResMode === true;
|
|
478
|
-
const isCtrlWheelVerticalScrolling = state.isCtrlWheelVerticalScrolling === true;
|
|
479
|
-
|
|
480
|
-
const marginPolicy = getVisibilityMarginPolicy(camZ, isPanning, isCtrlWheelVerticalScrolling);
|
|
481
|
-
let marginWorld = 0;
|
|
482
|
-
let verticalLeadMarginTopWorld = 0;
|
|
483
|
-
let verticalLeadMarginBottomWorld = 0;
|
|
484
|
-
let horizontalLeadMarginLeftWorld = 0;
|
|
485
|
-
let horizontalLeadMarginRightWorld = 0;
|
|
486
|
-
|
|
487
|
-
const vfov = (fov * Math.PI) / 180;
|
|
488
|
-
const viewHeight = 2 * Math.tan(vfov / 2) * Math.max(1, camZ);
|
|
489
|
-
const viewWidth = viewHeight * aspect;
|
|
490
|
-
const worldPerPxX = viewWidth / viewportW;
|
|
491
|
-
const worldPerPxY = viewHeight / viewportH;
|
|
492
|
-
const rawMarginWorld = marginPolicy.cullingMarginPx * Math.max(worldPerPxX, worldPerPxY);
|
|
493
|
-
marginWorld = Math.min(rawMarginWorld, cellSize * marginPolicy.maxMarginCells);
|
|
494
|
-
|
|
495
|
-
if (isCtrlWheelVerticalScrolling && camZ < 12000) {
|
|
496
|
-
const directionalLeadWorld = Math.min(
|
|
497
|
-
rawMarginWorld * 0.8,
|
|
498
|
-
cellSize * Math.max(1, marginPolicy.maxMarginCells - 1)
|
|
499
|
-
);
|
|
500
|
-
const lastCtrlWheelDeltaY = Number(state.lastCtrlWheelDeltaY || 0);
|
|
501
|
-
if (lastCtrlWheelDeltaY < -0.01) {
|
|
502
|
-
verticalLeadMarginTopWorld = directionalLeadWorld;
|
|
503
|
-
} else if (lastCtrlWheelDeltaY > 0.01) {
|
|
504
|
-
verticalLeadMarginBottomWorld = directionalLeadWorld;
|
|
505
|
-
} else {
|
|
506
|
-
verticalLeadMarginTopWorld = directionalLeadWorld * 0.5;
|
|
507
|
-
verticalLeadMarginBottomWorld = directionalLeadWorld * 0.5;
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
if (isPanning && camZ < 12000) {
|
|
512
|
-
const panLeadWorld = Math.min(
|
|
513
|
-
rawMarginWorld * (camZ < 4500 ? 1.35 : 1.1),
|
|
514
|
-
cellSize * Math.max(1, marginPolicy.maxMarginCells)
|
|
515
|
-
);
|
|
516
|
-
const panDx = Number(state.lastPanWorldDeltaX || 0);
|
|
517
|
-
const panDy = Number(state.lastPanWorldDeltaY || 0);
|
|
518
|
-
if (panDx > 0.01) {
|
|
519
|
-
horizontalLeadMarginRightWorld = panLeadWorld;
|
|
520
|
-
} else if (panDx < -0.01) {
|
|
521
|
-
horizontalLeadMarginLeftWorld = panLeadWorld;
|
|
522
|
-
}
|
|
523
|
-
if (panDy > 0.01) {
|
|
524
|
-
verticalLeadMarginTopWorld = Math.max(verticalLeadMarginTopWorld, panLeadWorld);
|
|
525
|
-
} else if (panDy < -0.01) {
|
|
526
|
-
verticalLeadMarginBottomWorld = Math.max(verticalLeadMarginBottomWorld, panLeadWorld);
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
const halfVisibleW = viewWidth / 2;
|
|
531
|
-
const halfVisibleH = viewHeight / 2;
|
|
532
|
-
const viewportLeft = camX - halfVisibleW;
|
|
533
|
-
const viewportRight = camX + halfVisibleW;
|
|
534
|
-
const viewportBottom = camY - halfVisibleH;
|
|
535
|
-
const viewportTop = camY + halfVisibleH;
|
|
536
|
-
const enterMarginWorld = marginWorld;
|
|
537
|
-
const exitMarginWorld = marginWorld * FULL_RES_VISIBILITY_EXIT_MARGIN_SCALE;
|
|
538
|
-
|
|
539
|
-
const currentEnterRect = {
|
|
540
|
-
left: viewportLeft - (enterMarginWorld + horizontalLeadMarginLeftWorld),
|
|
541
|
-
right: viewportRight + (enterMarginWorld + horizontalLeadMarginRightWorld),
|
|
542
|
-
bottom: viewportBottom - (enterMarginWorld + verticalLeadMarginBottomWorld),
|
|
543
|
-
top: viewportTop + (enterMarginWorld + verticalLeadMarginTopWorld)
|
|
544
|
-
};
|
|
545
|
-
const currentPreserveRect = {
|
|
546
|
-
left: viewportLeft - (exitMarginWorld + horizontalLeadMarginLeftWorld),
|
|
547
|
-
right: viewportRight + (exitMarginWorld + horizontalLeadMarginRightWorld),
|
|
548
|
-
bottom: viewportBottom - (exitMarginWorld + verticalLeadMarginBottomWorld),
|
|
549
|
-
top: viewportTop + (exitMarginWorld + verticalLeadMarginTopWorld)
|
|
550
|
-
};
|
|
551
|
-
|
|
552
|
-
const previousVisibilityRect = runtime.lastVisibilityRect || null;
|
|
553
|
-
const currentRange = describeCellRange(currentEnterRect, cellSize);
|
|
554
|
-
let candidateIds = new Set();
|
|
555
|
-
let usedPopulatedCellScan = false;
|
|
556
|
-
let directGridLookups = Number(currentRange?.directGridLookups || 0);
|
|
557
|
-
let gridSpanX = Number(currentRange?.spanX || 0);
|
|
558
|
-
let gridSpanY = Number(currentRange?.spanY || 0);
|
|
559
|
-
const previousVisibleCount = runtime.lastVisibleIds.size;
|
|
560
|
-
const nextVisibleIds = new Set();
|
|
561
|
-
let usedIncrementalPanVisibility = false;
|
|
562
|
-
let incrementalPanBandCount = 0;
|
|
563
|
-
let incrementalPanCellLookups = 0;
|
|
564
|
-
const canAttemptIncrementalFullResPan =
|
|
565
|
-
isFullResMode === true &&
|
|
566
|
-
isPanning === true &&
|
|
567
|
-
isZooming !== true &&
|
|
568
|
-
state.isWindowResizing !== true &&
|
|
569
|
-
state.isResizing !== true &&
|
|
570
|
-
previousVisibilityRect?.mode === 'full-res' &&
|
|
571
|
-
runtime.lastNodeCount === runtime.nodes.size &&
|
|
572
|
-
Math.abs(Number(previousVisibilityRect?.cameraZ || 0) - camZ) < 0.01 &&
|
|
573
|
-
runtime.lastVisibleIds.size > 0;
|
|
574
|
-
|
|
575
|
-
if (canAttemptIncrementalFullResPan && currentRange) {
|
|
576
|
-
const prevRect = previousVisibilityRect;
|
|
577
|
-
const epsilon = 0.01;
|
|
578
|
-
const incrementalBands = [];
|
|
579
|
-
|
|
580
|
-
if (currentEnterRect.left < Number(prevRect.left) - epsilon) {
|
|
581
|
-
incrementalBands.push({
|
|
582
|
-
left: currentEnterRect.left,
|
|
583
|
-
right: Math.min(currentEnterRect.right, Number(prevRect.left)),
|
|
584
|
-
bottom: currentEnterRect.bottom,
|
|
585
|
-
top: currentEnterRect.top
|
|
586
|
-
});
|
|
587
|
-
}
|
|
588
|
-
if (currentEnterRect.right > Number(prevRect.right) + epsilon) {
|
|
589
|
-
incrementalBands.push({
|
|
590
|
-
left: Math.max(currentEnterRect.left, Number(prevRect.right)),
|
|
591
|
-
right: currentEnterRect.right,
|
|
592
|
-
bottom: currentEnterRect.bottom,
|
|
593
|
-
top: currentEnterRect.top
|
|
594
|
-
});
|
|
595
|
-
}
|
|
596
|
-
if (currentEnterRect.bottom < Number(prevRect.bottom) - epsilon) {
|
|
597
|
-
incrementalBands.push({
|
|
598
|
-
left: currentEnterRect.left,
|
|
599
|
-
right: currentEnterRect.right,
|
|
600
|
-
bottom: currentEnterRect.bottom,
|
|
601
|
-
top: Math.min(currentEnterRect.top, Number(prevRect.bottom))
|
|
602
|
-
});
|
|
603
|
-
}
|
|
604
|
-
if (currentEnterRect.top > Number(prevRect.top) + epsilon) {
|
|
605
|
-
incrementalBands.push({
|
|
606
|
-
left: currentEnterRect.left,
|
|
607
|
-
right: currentEnterRect.right,
|
|
608
|
-
bottom: Math.max(currentEnterRect.bottom, Number(prevRect.top)),
|
|
609
|
-
top: currentEnterRect.top
|
|
610
|
-
});
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
const incrementalCellKeys = new Set();
|
|
614
|
-
for (let i = 0; i < incrementalBands.length; i++) {
|
|
615
|
-
const bandRange = describeCellRange(incrementalBands[i], cellSize);
|
|
616
|
-
if (!bandRange || bandRange.directGridLookups <= 0) {
|
|
617
|
-
continue;
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
incrementalPanBandCount++;
|
|
621
|
-
buildCellKeySetFromRange(bandRange, incrementalCellKeys);
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
if (incrementalPanBandCount > 0 &&
|
|
625
|
-
incrementalCellKeys.size > 0 &&
|
|
626
|
-
incrementalCellKeys.size < directGridLookups) {
|
|
627
|
-
usedIncrementalPanVisibility = true;
|
|
628
|
-
incrementalPanCellLookups = incrementalCellKeys.size;
|
|
629
|
-
|
|
630
|
-
runtime.lastVisibleIds.forEach((nodeId) => {
|
|
631
|
-
if (runtime.nodes.has(nodeId)) {
|
|
632
|
-
nextVisibleIds.add(nodeId);
|
|
633
|
-
}
|
|
634
|
-
});
|
|
635
|
-
addCandidatesFromCellKeySet(incrementalCellKeys, candidateIds, true);
|
|
636
|
-
}
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
if (!usedIncrementalPanVisibility) {
|
|
640
|
-
const result = collectCandidates(currentRange);
|
|
641
|
-
candidateIds = result.candidateIds;
|
|
642
|
-
usedPopulatedCellScan = result.usedPopulatedCellScan;
|
|
643
|
-
directGridLookups = result.directGridLookups;
|
|
644
|
-
gridSpanX = result.gridSpanX;
|
|
645
|
-
gridSpanY = result.gridSpanY;
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
candidateIds.forEach((nodeId) => {
|
|
649
|
-
const node = runtime.nodes.get(nodeId);
|
|
650
|
-
if (!node) {
|
|
651
|
-
return;
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
const depth = camZ - node.z;
|
|
655
|
-
if (depth <= camNear || depth >= camFar) {
|
|
656
|
-
return;
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
const baseMarginWorld = runtime.lastVisibleIds.has(nodeId)
|
|
660
|
-
? exitMarginWorld
|
|
661
|
-
: enterMarginWorld;
|
|
662
|
-
const leftBound = viewportLeft - (baseMarginWorld + horizontalLeadMarginLeftWorld);
|
|
663
|
-
const rightBound = viewportRight + (baseMarginWorld + horizontalLeadMarginRightWorld);
|
|
664
|
-
const bottomBound = viewportBottom - (baseMarginWorld + verticalLeadMarginBottomWorld);
|
|
665
|
-
const topBound = viewportTop + (baseMarginWorld + verticalLeadMarginTopWorld);
|
|
666
|
-
|
|
667
|
-
const isVisible = !(
|
|
668
|
-
node.maxX < leftBound ||
|
|
669
|
-
node.minX > rightBound ||
|
|
670
|
-
node.maxY < bottomBound ||
|
|
671
|
-
node.minY > topBound
|
|
672
|
-
);
|
|
673
|
-
|
|
674
|
-
if (isVisible) {
|
|
675
|
-
nextVisibleIds.add(nodeId);
|
|
676
|
-
}
|
|
677
|
-
});
|
|
678
|
-
|
|
679
|
-
const enteredIds = [];
|
|
680
|
-
nextVisibleIds.forEach((nodeId) => {
|
|
681
|
-
if (!runtime.lastVisibleIds.has(nodeId)) {
|
|
682
|
-
enteredIds.push(nodeId);
|
|
683
|
-
}
|
|
684
|
-
});
|
|
685
|
-
|
|
686
|
-
const exitedIds = [];
|
|
687
|
-
runtime.lastVisibleIds.forEach((nodeId) => {
|
|
688
|
-
if (!usedIncrementalPanVisibility && !nextVisibleIds.has(nodeId)) {
|
|
689
|
-
exitedIds.push(nodeId);
|
|
690
|
-
}
|
|
691
|
-
});
|
|
692
|
-
|
|
693
|
-
const visibilityChanged = enteredIds.length > 0 || exitedIds.length > 0;
|
|
694
|
-
const canReusePriorityIds =
|
|
695
|
-
visibilityChanged !== true &&
|
|
696
|
-
Array.isArray(runtime.lastPriorityIds) &&
|
|
697
|
-
runtime.lastPriorityIds.length === nextVisibleIds.size;
|
|
698
|
-
const priorityIds = canReusePriorityIds
|
|
699
|
-
? runtime.lastPriorityIds
|
|
700
|
-
: (usedIncrementalPanVisibility
|
|
701
|
-
? buildIncrementalPriorityIds(nextVisibleIds, enteredIds, camX, camY)
|
|
702
|
-
: buildPriorityIds(nextVisibleIds, camX, camY));
|
|
703
|
-
|
|
704
|
-
runtime.lastVisibleIds = nextVisibleIds;
|
|
705
|
-
runtime.lastPriorityIds = priorityIds;
|
|
706
|
-
runtime.lastVisibilityRect = {
|
|
707
|
-
left: currentEnterRect.left,
|
|
708
|
-
right: currentEnterRect.right,
|
|
709
|
-
bottom: currentEnterRect.bottom,
|
|
710
|
-
top: currentEnterRect.top,
|
|
711
|
-
mode: isFullResMode === true ? 'full-res' : 'lod',
|
|
712
|
-
cameraZ: camZ
|
|
713
|
-
};
|
|
714
|
-
runtime.lastNodeCount = runtime.nodes.size;
|
|
715
|
-
runtime.lastResultSeq = seq;
|
|
716
|
-
|
|
717
|
-
const t1 = (typeof performance !== 'undefined' && typeof performance.now === 'function')
|
|
718
|
-
? performance.now()
|
|
719
|
-
: Date.now();
|
|
720
|
-
|
|
721
|
-
const canOmitFullVisibleIds =
|
|
722
|
-
mainLastAppliedSeq > 0 &&
|
|
723
|
-
mainLastAppliedSeq === previousResultSeq &&
|
|
724
|
-
seq === previousResultSeq + 1;
|
|
725
|
-
|
|
726
|
-
self.postMessage({
|
|
727
|
-
type: 'VISIBILITY_RESULT',
|
|
728
|
-
boardId,
|
|
729
|
-
seq,
|
|
730
|
-
visibleIds: canOmitFullVisibleIds ? undefined : Array.from(nextVisibleIds),
|
|
731
|
-
visibleCount: Number(nextVisibleIds.size || 0),
|
|
732
|
-
enteredIds,
|
|
733
|
-
exitedIds,
|
|
734
|
-
priorityIds: canReusePriorityIds ? undefined : priorityIds,
|
|
735
|
-
visibilityChanged,
|
|
736
|
-
viewportRect: runtime.lastVisibilityRect,
|
|
737
|
-
lastVisibilityNodeCount: runtime.lastNodeCount,
|
|
738
|
-
fullResVisibilityReconcilePending: usedIncrementalPanVisibility === true,
|
|
739
|
-
profile: {
|
|
740
|
-
durationMs: Number(Math.max(0, t1 - t0).toFixed(2)),
|
|
741
|
-
cameraZ: Number((camZ || 0).toFixed(2)),
|
|
742
|
-
useCoarseVisibility: marginPolicy.useCoarseVisibility === true,
|
|
743
|
-
marginWorld: Number((marginWorld || 0).toFixed(2)),
|
|
744
|
-
gridSpanX: Number(gridSpanX || 0),
|
|
745
|
-
gridSpanY: Number(gridSpanY || 0),
|
|
746
|
-
directGridLookups: Number(directGridLookups || 0),
|
|
747
|
-
usedPopulatedCellScan: usedPopulatedCellScan === true,
|
|
748
|
-
incrementalPanMode: usedIncrementalPanVisibility === true,
|
|
749
|
-
incrementalPanBandCount,
|
|
750
|
-
incrementalPanCellLookups,
|
|
751
|
-
omittedFullVisibleIds: canOmitFullVisibleIds === true,
|
|
752
|
-
candidateCount: Number(candidateIds.size || 0),
|
|
753
|
-
processedCount: Number(candidateIds.size || 0),
|
|
754
|
-
prevVisibleCount: Number(previousVisibleCount || 0),
|
|
755
|
-
nextVisibleCount: Number(nextVisibleIds.size || 0),
|
|
756
|
-
visibilityChanged: visibilityChanged === true
|
|
757
|
-
}
|
|
758
|
-
});
|
|
759
|
-
}
|
|
760
|
-
|
|
761
|
-
function handleHitTestQuery(message) {
|
|
762
|
-
const boardId = normalizeBoardId(message.boardId);
|
|
763
|
-
if (!boardId || boardId !== runtime.boardId) {
|
|
764
|
-
return;
|
|
765
|
-
}
|
|
766
|
-
|
|
767
|
-
const seq = Number(message.seq || 0);
|
|
768
|
-
const cellX = Math.floor(Number(message.cellX || 0));
|
|
769
|
-
const cellY = Math.floor(Number(message.cellY || 0));
|
|
770
|
-
const t0 = (typeof performance !== 'undefined' && typeof performance.now === 'function')
|
|
771
|
-
? performance.now()
|
|
772
|
-
: Date.now();
|
|
773
|
-
const { candidateIds, cellLookups, cellRadius } = collectHitTestCandidateIds(
|
|
774
|
-
cellX,
|
|
775
|
-
cellY,
|
|
776
|
-
message.cellRadius
|
|
777
|
-
);
|
|
778
|
-
const t1 = (typeof performance !== 'undefined' && typeof performance.now === 'function')
|
|
779
|
-
? performance.now()
|
|
780
|
-
: Date.now();
|
|
781
|
-
|
|
782
|
-
self.postMessage({
|
|
783
|
-
type: 'HIT_TEST_RESULT',
|
|
784
|
-
boardId,
|
|
785
|
-
seq,
|
|
786
|
-
cellX,
|
|
787
|
-
cellY,
|
|
788
|
-
cellRadius,
|
|
789
|
-
rangeKey: `${cellX}:${cellY}:${cellRadius}`,
|
|
790
|
-
candidateIds: Array.from(candidateIds),
|
|
791
|
-
profile: {
|
|
792
|
-
durationMs: Number(Math.max(0, t1 - t0).toFixed(2)),
|
|
793
|
-
cellLookups,
|
|
794
|
-
candidateCount: Number(candidateIds.size || 0),
|
|
795
|
-
spatialGridCellCount: Number(runtime.spatialGrid.size || 0)
|
|
796
|
-
}
|
|
797
|
-
});
|
|
798
|
-
}
|
|
799
|
-
|
|
800
|
-
function handleSelectionQuery(message) {
|
|
801
|
-
const boardId = normalizeBoardId(message.boardId);
|
|
802
|
-
if (!boardId || boardId !== runtime.boardId) {
|
|
803
|
-
return;
|
|
804
|
-
}
|
|
805
|
-
|
|
806
|
-
const rect = normalizeSelectionRect(message.selectionRect);
|
|
807
|
-
if (!rect) {
|
|
808
|
-
return;
|
|
809
|
-
}
|
|
810
|
-
|
|
811
|
-
const seq = Number(message.seq || 0);
|
|
812
|
-
const t0 = (typeof performance !== 'undefined' && typeof performance.now === 'function')
|
|
813
|
-
? performance.now()
|
|
814
|
-
: Date.now();
|
|
815
|
-
const range = describeCellRange(rect, Math.max(1, Number(message.cellSize || runtime.cellSize || 500)));
|
|
816
|
-
const { candidateIds, usedPopulatedCellScan, directGridLookups, gridSpanX, gridSpanY } = collectCandidates(range);
|
|
817
|
-
const selectedIds = [];
|
|
818
|
-
|
|
819
|
-
candidateIds.forEach((nodeId) => {
|
|
820
|
-
const node = runtime.nodes.get(nodeId);
|
|
821
|
-
if (!node) {
|
|
822
|
-
return;
|
|
823
|
-
}
|
|
824
|
-
|
|
825
|
-
if (node.minX >= rect.left &&
|
|
826
|
-
node.maxX <= rect.right &&
|
|
827
|
-
node.minY >= rect.bottom &&
|
|
828
|
-
node.maxY <= rect.top) {
|
|
829
|
-
selectedIds.push(nodeId);
|
|
830
|
-
}
|
|
831
|
-
});
|
|
832
|
-
|
|
833
|
-
const t1 = (typeof performance !== 'undefined' && typeof performance.now === 'function')
|
|
834
|
-
? performance.now()
|
|
835
|
-
: Date.now();
|
|
836
|
-
|
|
837
|
-
self.postMessage({
|
|
838
|
-
type: 'SELECTION_QUERY_RESULT',
|
|
839
|
-
boardId,
|
|
840
|
-
seq,
|
|
841
|
-
selectedIds,
|
|
842
|
-
selectionRect: rect,
|
|
843
|
-
profile: {
|
|
844
|
-
durationMs: Number(Math.max(0, t1 - t0).toFixed(2)),
|
|
845
|
-
candidateCount: Number(candidateIds.size || 0),
|
|
846
|
-
selectedCount: Number(selectedIds.length || 0),
|
|
847
|
-
gridSpanX: Number(gridSpanX || 0),
|
|
848
|
-
gridSpanY: Number(gridSpanY || 0),
|
|
849
|
-
directGridLookups: Number(directGridLookups || 0),
|
|
850
|
-
usedPopulatedCellScan: usedPopulatedCellScan === true
|
|
851
|
-
}
|
|
852
|
-
});
|
|
853
|
-
}
|
|
854
|
-
|
|
855
|
-
self.onmessage = function (event) {
|
|
856
|
-
const message = event?.data || null;
|
|
857
|
-
if (!message || typeof message !== 'object') {
|
|
858
|
-
return;
|
|
859
|
-
}
|
|
860
|
-
|
|
861
|
-
switch (message.type) {
|
|
862
|
-
case 'RESET_BOARD':
|
|
863
|
-
clearState(message.boardId, message.cellSize);
|
|
864
|
-
break;
|
|
865
|
-
case 'REBUILD_BOARD':
|
|
866
|
-
rebuildBoard(message.boardId, message.cellSize, message.nodes);
|
|
867
|
-
break;
|
|
868
|
-
case 'UPSERT_NODE':
|
|
869
|
-
if (normalizeBoardId(message.boardId) === runtime.boardId) {
|
|
870
|
-
upsertNode(message.node);
|
|
871
|
-
}
|
|
872
|
-
break;
|
|
873
|
-
case 'REMOVE_NODE':
|
|
874
|
-
if (normalizeBoardId(message.boardId) === runtime.boardId) {
|
|
875
|
-
removeNode(message.nodeId);
|
|
876
|
-
}
|
|
877
|
-
break;
|
|
878
|
-
case 'VISIBILITY_QUERY':
|
|
879
|
-
handleVisibilityQuery(message);
|
|
880
|
-
break;
|
|
881
|
-
case 'HIT_TEST_QUERY':
|
|
882
|
-
handleHitTestQuery(message);
|
|
883
|
-
break;
|
|
884
|
-
case 'SELECTION_QUERY':
|
|
885
|
-
handleSelectionQuery(message);
|
|
886
|
-
break;
|
|
887
|
-
default:
|
|
888
|
-
break;
|
|
889
|
-
}
|
|
890
|
-
};
|
|
891
|
-
|
|
892
|
-
self.postMessage({
|
|
893
|
-
type: 'READY',
|
|
894
|
-
worker: 'visibility'
|
|
895
|
-
});
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const FULL_RES_VISIBILITY_EXIT_MARGIN_SCALE = 1.8;
|
|
4
|
+
const MAX_DIRECT_GRID_LOOKUPS = 4096;
|
|
5
|
+
const DEFAULT_HIT_TEST_CELL_RADIUS = 1;
|
|
6
|
+
const MAX_HIT_TEST_CELL_RADIUS = 3;
|
|
7
|
+
|
|
8
|
+
function createState() {
|
|
9
|
+
return {
|
|
10
|
+
boardId: '',
|
|
11
|
+
cellSize: 500,
|
|
12
|
+
nodes: new Map(),
|
|
13
|
+
spatialGrid: new Map(),
|
|
14
|
+
lastVisibleIds: new Set(),
|
|
15
|
+
lastPriorityIds: [],
|
|
16
|
+
lastVisibilityRect: null,
|
|
17
|
+
lastNodeCount: 0,
|
|
18
|
+
lastResultSeq: 0
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
let runtime = createState();
|
|
23
|
+
|
|
24
|
+
function normalizeBoardId(boardId) {
|
|
25
|
+
return String(boardId ?? '').trim();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function normalizeNodeSnapshot(node) {
|
|
29
|
+
if (!node || typeof node !== 'object') {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const id = String(node.id || '').trim();
|
|
34
|
+
if (!id) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const minX = Number(node.minX || 0);
|
|
39
|
+
const maxX = Number(node.maxX || minX);
|
|
40
|
+
const minY = Number(node.minY || 0);
|
|
41
|
+
const maxY = Number(node.maxY || minY);
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
id,
|
|
45
|
+
contentType: String(node.contentType || '').trim().toLowerCase(),
|
|
46
|
+
textLike: node.textLike === true,
|
|
47
|
+
x: Number(node.x || minX),
|
|
48
|
+
y: Number(node.y || maxY),
|
|
49
|
+
z: Number(node.z || 0),
|
|
50
|
+
width: Math.max(1, Number(node.width || (maxX - minX) || 1)),
|
|
51
|
+
height: Math.max(1, Number(node.height || (maxY - minY) || 1)),
|
|
52
|
+
minX,
|
|
53
|
+
maxX,
|
|
54
|
+
minY,
|
|
55
|
+
maxY,
|
|
56
|
+
cellKeys: Array.isArray(node.cellKeys)
|
|
57
|
+
? node.cellKeys.map((key) => String(key || '').trim()).filter(Boolean)
|
|
58
|
+
: []
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function clearState(boardId, cellSize) {
|
|
63
|
+
runtime = createState();
|
|
64
|
+
runtime.boardId = normalizeBoardId(boardId);
|
|
65
|
+
runtime.cellSize = Math.max(1, Number(cellSize || 500));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function removeNodeFromGrid(nodeId, node) {
|
|
69
|
+
const normalizedId = String(nodeId || '').trim();
|
|
70
|
+
if (!normalizedId || !node?.cellKeys) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
for (let i = 0; i < node.cellKeys.length; i++) {
|
|
75
|
+
const key = node.cellKeys[i];
|
|
76
|
+
const cell = runtime.spatialGrid.get(key);
|
|
77
|
+
if (!cell) {
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
cell.delete(normalizedId);
|
|
82
|
+
if (cell.size === 0) {
|
|
83
|
+
runtime.spatialGrid.delete(key);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function upsertNode(node) {
|
|
89
|
+
const normalizedNode = normalizeNodeSnapshot(node);
|
|
90
|
+
if (!normalizedNode) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const existing = runtime.nodes.get(normalizedNode.id);
|
|
95
|
+
if (existing) {
|
|
96
|
+
removeNodeFromGrid(normalizedNode.id, existing);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
runtime.nodes.set(normalizedNode.id, normalizedNode);
|
|
100
|
+
for (let i = 0; i < normalizedNode.cellKeys.length; i++) {
|
|
101
|
+
const key = normalizedNode.cellKeys[i];
|
|
102
|
+
let cell = runtime.spatialGrid.get(key);
|
|
103
|
+
if (!cell) {
|
|
104
|
+
cell = new Set();
|
|
105
|
+
runtime.spatialGrid.set(key, cell);
|
|
106
|
+
}
|
|
107
|
+
cell.add(normalizedNode.id);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function removeNode(nodeId) {
|
|
112
|
+
const normalizedId = String(nodeId || '').trim();
|
|
113
|
+
if (!normalizedId) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const existing = runtime.nodes.get(normalizedId);
|
|
118
|
+
if (!existing) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
removeNodeFromGrid(normalizedId, existing);
|
|
123
|
+
runtime.nodes.delete(normalizedId);
|
|
124
|
+
runtime.lastVisibleIds.delete(normalizedId);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function rebuildBoard(boardId, cellSize, nodes) {
|
|
128
|
+
clearState(boardId, cellSize);
|
|
129
|
+
const sourceNodes = Array.isArray(nodes) ? nodes : [];
|
|
130
|
+
for (let i = 0; i < sourceNodes.length; i++) {
|
|
131
|
+
upsertNode(sourceNodes[i]);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function getVisibilityMarginPolicy(cameraZ, isPanning = false, isCtrlWheelVerticalScrolling = false) {
|
|
136
|
+
let cullingMarginPx = 3000;
|
|
137
|
+
let maxMarginCells = 6;
|
|
138
|
+
let useCoarseVisibility = false;
|
|
139
|
+
|
|
140
|
+
if (cameraZ < 4500) {
|
|
141
|
+
cullingMarginPx = 1000;
|
|
142
|
+
maxMarginCells = 4;
|
|
143
|
+
} else if (cameraZ < 12000) {
|
|
144
|
+
cullingMarginPx = 2200;
|
|
145
|
+
maxMarginCells = 5;
|
|
146
|
+
} else if (cameraZ < 30000) {
|
|
147
|
+
cullingMarginPx = 900;
|
|
148
|
+
maxMarginCells = 2;
|
|
149
|
+
} else {
|
|
150
|
+
cullingMarginPx = 240;
|
|
151
|
+
maxMarginCells = 1;
|
|
152
|
+
useCoarseVisibility = isPanning !== true;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (isPanning === true) {
|
|
156
|
+
if (cameraZ < 4500) {
|
|
157
|
+
cullingMarginPx += 500;
|
|
158
|
+
maxMarginCells += 1;
|
|
159
|
+
} else if (cameraZ < 12000) {
|
|
160
|
+
cullingMarginPx += 700;
|
|
161
|
+
maxMarginCells += 1;
|
|
162
|
+
} else if (cameraZ < 30000) {
|
|
163
|
+
cullingMarginPx += 600;
|
|
164
|
+
maxMarginCells += 1;
|
|
165
|
+
} else {
|
|
166
|
+
cullingMarginPx += 320;
|
|
167
|
+
maxMarginCells += 1;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (isCtrlWheelVerticalScrolling && cameraZ < 12000) {
|
|
172
|
+
cullingMarginPx += (cameraZ < 4500) ? 700 : 900;
|
|
173
|
+
maxMarginCells += 2;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return {
|
|
177
|
+
cullingMarginPx,
|
|
178
|
+
maxMarginCells,
|
|
179
|
+
useCoarseVisibility
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function describeCellRange(rect, cellSize) {
|
|
184
|
+
if (!rect) {
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const left = Number(rect.left);
|
|
189
|
+
const right = Number(rect.right);
|
|
190
|
+
const bottom = Number(rect.bottom);
|
|
191
|
+
const top = Number(rect.top);
|
|
192
|
+
if (!Number.isFinite(left) || !Number.isFinite(right) || !Number.isFinite(bottom) || !Number.isFinite(top)) {
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
if (right < left || top < bottom) {
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const startCellX = Math.floor(left / cellSize);
|
|
200
|
+
const endCellX = Math.floor(right / cellSize);
|
|
201
|
+
const startCellY = Math.floor(bottom / cellSize);
|
|
202
|
+
const endCellY = Math.floor(top / cellSize);
|
|
203
|
+
const spanX = Math.max(0, endCellX - startCellX + 1);
|
|
204
|
+
const spanY = Math.max(0, endCellY - startCellY + 1);
|
|
205
|
+
|
|
206
|
+
return {
|
|
207
|
+
startCellX,
|
|
208
|
+
endCellX,
|
|
209
|
+
startCellY,
|
|
210
|
+
endCellY,
|
|
211
|
+
spanX,
|
|
212
|
+
spanY,
|
|
213
|
+
directGridLookups: spanX * spanY
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function intersectsRect(node, rect) {
|
|
218
|
+
if (!node || !rect) {
|
|
219
|
+
return false;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return !(
|
|
223
|
+
node.maxX < rect.left ||
|
|
224
|
+
node.minX > rect.right ||
|
|
225
|
+
node.maxY < rect.bottom ||
|
|
226
|
+
node.minY > rect.top
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function collectCandidates(range) {
|
|
231
|
+
const candidateIds = new Set();
|
|
232
|
+
let usedPopulatedCellScan = false;
|
|
233
|
+
|
|
234
|
+
if (!range) {
|
|
235
|
+
runtime.nodes.forEach((_, nodeId) => candidateIds.add(nodeId));
|
|
236
|
+
return {
|
|
237
|
+
candidateIds,
|
|
238
|
+
usedPopulatedCellScan,
|
|
239
|
+
directGridLookups: 0,
|
|
240
|
+
gridSpanX: 0,
|
|
241
|
+
gridSpanY: 0
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (range.directGridLookups > MAX_DIRECT_GRID_LOOKUPS) {
|
|
246
|
+
usedPopulatedCellScan = true;
|
|
247
|
+
runtime.spatialGrid.forEach((nodeIds, key) => {
|
|
248
|
+
const separatorIndex = key.indexOf(':');
|
|
249
|
+
if (separatorIndex <= 0) {
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const cx = Number(key.slice(0, separatorIndex));
|
|
254
|
+
const cy = Number(key.slice(separatorIndex + 1));
|
|
255
|
+
if (!Number.isFinite(cx) || !Number.isFinite(cy)) {
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (cx >= range.startCellX && cx <= range.endCellX && cy >= range.startCellY && cy <= range.endCellY) {
|
|
260
|
+
nodeIds.forEach((nodeId) => candidateIds.add(nodeId));
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
} else {
|
|
264
|
+
for (let cx = range.startCellX; cx <= range.endCellX; cx++) {
|
|
265
|
+
for (let cy = range.startCellY; cy <= range.endCellY; cy++) {
|
|
266
|
+
const cell = runtime.spatialGrid.get(`${cx}:${cy}`);
|
|
267
|
+
if (!cell) {
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
cell.forEach((nodeId) => candidateIds.add(nodeId));
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
runtime.lastVisibleIds.forEach((nodeId) => {
|
|
276
|
+
if (runtime.nodes.has(nodeId)) {
|
|
277
|
+
candidateIds.add(nodeId);
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
return {
|
|
282
|
+
candidateIds,
|
|
283
|
+
usedPopulatedCellScan,
|
|
284
|
+
directGridLookups: range.directGridLookups,
|
|
285
|
+
gridSpanX: range.spanX,
|
|
286
|
+
gridSpanY: range.spanY
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function buildCellKeySetFromRange(range, targetSet) {
|
|
291
|
+
if (!range || !targetSet) {
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
for (let cx = range.startCellX; cx <= range.endCellX; cx++) {
|
|
296
|
+
for (let cy = range.startCellY; cy <= range.endCellY; cy++) {
|
|
297
|
+
targetSet.add(`${cx}:${cy}`);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function addCandidatesFromCellKeySet(cellKeys, candidateIds, skipPreviouslyVisible = false) {
|
|
303
|
+
if (!cellKeys || !candidateIds || cellKeys.size === 0) {
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
cellKeys.forEach((key) => {
|
|
308
|
+
const nodeIds = runtime.spatialGrid.get(key);
|
|
309
|
+
if (!nodeIds) {
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
nodeIds.forEach((nodeId) => {
|
|
314
|
+
if (skipPreviouslyVisible && runtime.lastVisibleIds.has(nodeId)) {
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
candidateIds.add(nodeId);
|
|
319
|
+
});
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function normalizeHitTestCellRadius(cellRadius) {
|
|
324
|
+
const normalized = Math.round(Number(cellRadius || DEFAULT_HIT_TEST_CELL_RADIUS));
|
|
325
|
+
if (!Number.isFinite(normalized)) {
|
|
326
|
+
return DEFAULT_HIT_TEST_CELL_RADIUS;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
return Math.max(0, Math.min(MAX_HIT_TEST_CELL_RADIUS, normalized));
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function collectHitTestCandidateIds(centerCellX, centerCellY, cellRadius) {
|
|
333
|
+
const candidateIds = new Set();
|
|
334
|
+
const normalizedCellRadius = normalizeHitTestCellRadius(cellRadius);
|
|
335
|
+
let cellLookups = 0;
|
|
336
|
+
|
|
337
|
+
for (let offsetX = -normalizedCellRadius; offsetX <= normalizedCellRadius; offsetX++) {
|
|
338
|
+
for (let offsetY = -normalizedCellRadius; offsetY <= normalizedCellRadius; offsetY++) {
|
|
339
|
+
cellLookups++;
|
|
340
|
+
const cell = runtime.spatialGrid.get(`${centerCellX + offsetX}:${centerCellY + offsetY}`);
|
|
341
|
+
if (!cell) {
|
|
342
|
+
continue;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
cell.forEach((nodeId) => candidateIds.add(nodeId));
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
return {
|
|
350
|
+
candidateIds,
|
|
351
|
+
cellLookups,
|
|
352
|
+
cellRadius: normalizedCellRadius
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
function normalizeSelectionRect(rect) {
|
|
357
|
+
if (!rect || typeof rect !== 'object') {
|
|
358
|
+
return null;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const left = Number(rect.left);
|
|
362
|
+
const right = Number(rect.right);
|
|
363
|
+
const bottom = Number(rect.bottom);
|
|
364
|
+
const top = Number(rect.top);
|
|
365
|
+
if (!Number.isFinite(left) || !Number.isFinite(right) || !Number.isFinite(bottom) || !Number.isFinite(top)) {
|
|
366
|
+
return null;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
return {
|
|
370
|
+
left: Math.min(left, right),
|
|
371
|
+
right: Math.max(left, right),
|
|
372
|
+
bottom: Math.min(bottom, top),
|
|
373
|
+
top: Math.max(bottom, top)
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function buildPriorityIds(visibleIds, camX, camY) {
|
|
378
|
+
const buffer = [];
|
|
379
|
+
visibleIds.forEach((nodeId) => {
|
|
380
|
+
const node = runtime.nodes.get(nodeId);
|
|
381
|
+
if (!node) {
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
const dx = Number(node.x || 0) - camX;
|
|
386
|
+
const dy = Number(node.y || 0) - camY;
|
|
387
|
+
const distSq = (dx * dx) + (dy * dy);
|
|
388
|
+
const textPriority = node.textLike === true ? 0 : 1;
|
|
389
|
+
const mediaPriority = node.contentType === 'image' ? 0 : 1;
|
|
390
|
+
buffer.push({ nodeId, textPriority, mediaPriority, distSq });
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
buffer.sort((a, b) => {
|
|
394
|
+
if (a.textPriority !== b.textPriority) {
|
|
395
|
+
return a.textPriority - b.textPriority;
|
|
396
|
+
}
|
|
397
|
+
if (a.mediaPriority !== b.mediaPriority) {
|
|
398
|
+
return a.mediaPriority - b.mediaPriority;
|
|
399
|
+
}
|
|
400
|
+
return a.distSq - b.distSq;
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
return buffer.map((entry) => entry.nodeId);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
function buildIncrementalPriorityIds(nextVisibleIds, enteredIds, camX, camY) {
|
|
407
|
+
const priorityIds = [];
|
|
408
|
+
const used = new Set();
|
|
409
|
+
|
|
410
|
+
if (Array.isArray(runtime.lastPriorityIds)) {
|
|
411
|
+
for (let i = 0; i < runtime.lastPriorityIds.length; i++) {
|
|
412
|
+
const nodeId = String(runtime.lastPriorityIds[i] || '').trim();
|
|
413
|
+
if (!nodeId || used.has(nodeId) || !nextVisibleIds.has(nodeId)) {
|
|
414
|
+
continue;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
priorityIds.push(nodeId);
|
|
418
|
+
used.add(nodeId);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
const enteredSet = new Set();
|
|
423
|
+
if (Array.isArray(enteredIds)) {
|
|
424
|
+
for (let i = 0; i < enteredIds.length; i++) {
|
|
425
|
+
const nodeId = String(enteredIds[i] || '').trim();
|
|
426
|
+
if (nodeId && nextVisibleIds.has(nodeId) && !used.has(nodeId)) {
|
|
427
|
+
enteredSet.add(nodeId);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
if (enteredSet.size > 0) {
|
|
433
|
+
const enteredPriorityIds = buildPriorityIds(enteredSet, camX, camY);
|
|
434
|
+
for (let i = 0; i < enteredPriorityIds.length; i++) {
|
|
435
|
+
const nodeId = enteredPriorityIds[i];
|
|
436
|
+
if (!used.has(nodeId)) {
|
|
437
|
+
priorityIds.push(nodeId);
|
|
438
|
+
used.add(nodeId);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
if (priorityIds.length !== nextVisibleIds.size) {
|
|
444
|
+
return buildPriorityIds(nextVisibleIds, camX, camY);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
return priorityIds;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
function handleVisibilityQuery(message) {
|
|
451
|
+
const boardId = normalizeBoardId(message.boardId);
|
|
452
|
+
if (!boardId || boardId !== runtime.boardId) {
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
const t0 = (typeof performance !== 'undefined' && typeof performance.now === 'function')
|
|
457
|
+
? performance.now()
|
|
458
|
+
: Date.now();
|
|
459
|
+
|
|
460
|
+
const camera = message.camera || {};
|
|
461
|
+
const state = message.state || {};
|
|
462
|
+
const seq = Number(message.seq || 0);
|
|
463
|
+
const previousResultSeq = Math.max(0, Math.floor(Number(runtime.lastResultSeq || 0)));
|
|
464
|
+
const mainLastAppliedSeq = Math.max(0, Math.floor(Number(message.mainLastAppliedSeq || 0)));
|
|
465
|
+
const cellSize = Math.max(1, Number(message.cellSize || runtime.cellSize || 500));
|
|
466
|
+
const camX = Number(camera.x || 0);
|
|
467
|
+
const camY = Number(camera.y || 0);
|
|
468
|
+
const camZ = Number(camera.z || 0);
|
|
469
|
+
const camNear = Number(camera.near || 0);
|
|
470
|
+
const camFar = Number(camera.far || Number.MAX_SAFE_INTEGER);
|
|
471
|
+
const viewportW = Math.max(1, Number(camera.viewportW || 1));
|
|
472
|
+
const viewportH = Math.max(1, Number(camera.viewportH || 1));
|
|
473
|
+
const aspect = Math.max(0.1, Number(camera.aspect || 1));
|
|
474
|
+
const fov = Number(camera.fov || 45);
|
|
475
|
+
const isPanning = state.isPanning === true;
|
|
476
|
+
const isZooming = state.isZooming === true;
|
|
477
|
+
const isFullResMode = state.isFullResMode === true;
|
|
478
|
+
const isCtrlWheelVerticalScrolling = state.isCtrlWheelVerticalScrolling === true;
|
|
479
|
+
|
|
480
|
+
const marginPolicy = getVisibilityMarginPolicy(camZ, isPanning, isCtrlWheelVerticalScrolling);
|
|
481
|
+
let marginWorld = 0;
|
|
482
|
+
let verticalLeadMarginTopWorld = 0;
|
|
483
|
+
let verticalLeadMarginBottomWorld = 0;
|
|
484
|
+
let horizontalLeadMarginLeftWorld = 0;
|
|
485
|
+
let horizontalLeadMarginRightWorld = 0;
|
|
486
|
+
|
|
487
|
+
const vfov = (fov * Math.PI) / 180;
|
|
488
|
+
const viewHeight = 2 * Math.tan(vfov / 2) * Math.max(1, camZ);
|
|
489
|
+
const viewWidth = viewHeight * aspect;
|
|
490
|
+
const worldPerPxX = viewWidth / viewportW;
|
|
491
|
+
const worldPerPxY = viewHeight / viewportH;
|
|
492
|
+
const rawMarginWorld = marginPolicy.cullingMarginPx * Math.max(worldPerPxX, worldPerPxY);
|
|
493
|
+
marginWorld = Math.min(rawMarginWorld, cellSize * marginPolicy.maxMarginCells);
|
|
494
|
+
|
|
495
|
+
if (isCtrlWheelVerticalScrolling && camZ < 12000) {
|
|
496
|
+
const directionalLeadWorld = Math.min(
|
|
497
|
+
rawMarginWorld * 0.8,
|
|
498
|
+
cellSize * Math.max(1, marginPolicy.maxMarginCells - 1)
|
|
499
|
+
);
|
|
500
|
+
const lastCtrlWheelDeltaY = Number(state.lastCtrlWheelDeltaY || 0);
|
|
501
|
+
if (lastCtrlWheelDeltaY < -0.01) {
|
|
502
|
+
verticalLeadMarginTopWorld = directionalLeadWorld;
|
|
503
|
+
} else if (lastCtrlWheelDeltaY > 0.01) {
|
|
504
|
+
verticalLeadMarginBottomWorld = directionalLeadWorld;
|
|
505
|
+
} else {
|
|
506
|
+
verticalLeadMarginTopWorld = directionalLeadWorld * 0.5;
|
|
507
|
+
verticalLeadMarginBottomWorld = directionalLeadWorld * 0.5;
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
if (isPanning && camZ < 12000) {
|
|
512
|
+
const panLeadWorld = Math.min(
|
|
513
|
+
rawMarginWorld * (camZ < 4500 ? 1.35 : 1.1),
|
|
514
|
+
cellSize * Math.max(1, marginPolicy.maxMarginCells)
|
|
515
|
+
);
|
|
516
|
+
const panDx = Number(state.lastPanWorldDeltaX || 0);
|
|
517
|
+
const panDy = Number(state.lastPanWorldDeltaY || 0);
|
|
518
|
+
if (panDx > 0.01) {
|
|
519
|
+
horizontalLeadMarginRightWorld = panLeadWorld;
|
|
520
|
+
} else if (panDx < -0.01) {
|
|
521
|
+
horizontalLeadMarginLeftWorld = panLeadWorld;
|
|
522
|
+
}
|
|
523
|
+
if (panDy > 0.01) {
|
|
524
|
+
verticalLeadMarginTopWorld = Math.max(verticalLeadMarginTopWorld, panLeadWorld);
|
|
525
|
+
} else if (panDy < -0.01) {
|
|
526
|
+
verticalLeadMarginBottomWorld = Math.max(verticalLeadMarginBottomWorld, panLeadWorld);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
const halfVisibleW = viewWidth / 2;
|
|
531
|
+
const halfVisibleH = viewHeight / 2;
|
|
532
|
+
const viewportLeft = camX - halfVisibleW;
|
|
533
|
+
const viewportRight = camX + halfVisibleW;
|
|
534
|
+
const viewportBottom = camY - halfVisibleH;
|
|
535
|
+
const viewportTop = camY + halfVisibleH;
|
|
536
|
+
const enterMarginWorld = marginWorld;
|
|
537
|
+
const exitMarginWorld = marginWorld * FULL_RES_VISIBILITY_EXIT_MARGIN_SCALE;
|
|
538
|
+
|
|
539
|
+
const currentEnterRect = {
|
|
540
|
+
left: viewportLeft - (enterMarginWorld + horizontalLeadMarginLeftWorld),
|
|
541
|
+
right: viewportRight + (enterMarginWorld + horizontalLeadMarginRightWorld),
|
|
542
|
+
bottom: viewportBottom - (enterMarginWorld + verticalLeadMarginBottomWorld),
|
|
543
|
+
top: viewportTop + (enterMarginWorld + verticalLeadMarginTopWorld)
|
|
544
|
+
};
|
|
545
|
+
const currentPreserveRect = {
|
|
546
|
+
left: viewportLeft - (exitMarginWorld + horizontalLeadMarginLeftWorld),
|
|
547
|
+
right: viewportRight + (exitMarginWorld + horizontalLeadMarginRightWorld),
|
|
548
|
+
bottom: viewportBottom - (exitMarginWorld + verticalLeadMarginBottomWorld),
|
|
549
|
+
top: viewportTop + (exitMarginWorld + verticalLeadMarginTopWorld)
|
|
550
|
+
};
|
|
551
|
+
|
|
552
|
+
const previousVisibilityRect = runtime.lastVisibilityRect || null;
|
|
553
|
+
const currentRange = describeCellRange(currentEnterRect, cellSize);
|
|
554
|
+
let candidateIds = new Set();
|
|
555
|
+
let usedPopulatedCellScan = false;
|
|
556
|
+
let directGridLookups = Number(currentRange?.directGridLookups || 0);
|
|
557
|
+
let gridSpanX = Number(currentRange?.spanX || 0);
|
|
558
|
+
let gridSpanY = Number(currentRange?.spanY || 0);
|
|
559
|
+
const previousVisibleCount = runtime.lastVisibleIds.size;
|
|
560
|
+
const nextVisibleIds = new Set();
|
|
561
|
+
let usedIncrementalPanVisibility = false;
|
|
562
|
+
let incrementalPanBandCount = 0;
|
|
563
|
+
let incrementalPanCellLookups = 0;
|
|
564
|
+
const canAttemptIncrementalFullResPan =
|
|
565
|
+
isFullResMode === true &&
|
|
566
|
+
isPanning === true &&
|
|
567
|
+
isZooming !== true &&
|
|
568
|
+
state.isWindowResizing !== true &&
|
|
569
|
+
state.isResizing !== true &&
|
|
570
|
+
previousVisibilityRect?.mode === 'full-res' &&
|
|
571
|
+
runtime.lastNodeCount === runtime.nodes.size &&
|
|
572
|
+
Math.abs(Number(previousVisibilityRect?.cameraZ || 0) - camZ) < 0.01 &&
|
|
573
|
+
runtime.lastVisibleIds.size > 0;
|
|
574
|
+
|
|
575
|
+
if (canAttemptIncrementalFullResPan && currentRange) {
|
|
576
|
+
const prevRect = previousVisibilityRect;
|
|
577
|
+
const epsilon = 0.01;
|
|
578
|
+
const incrementalBands = [];
|
|
579
|
+
|
|
580
|
+
if (currentEnterRect.left < Number(prevRect.left) - epsilon) {
|
|
581
|
+
incrementalBands.push({
|
|
582
|
+
left: currentEnterRect.left,
|
|
583
|
+
right: Math.min(currentEnterRect.right, Number(prevRect.left)),
|
|
584
|
+
bottom: currentEnterRect.bottom,
|
|
585
|
+
top: currentEnterRect.top
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
if (currentEnterRect.right > Number(prevRect.right) + epsilon) {
|
|
589
|
+
incrementalBands.push({
|
|
590
|
+
left: Math.max(currentEnterRect.left, Number(prevRect.right)),
|
|
591
|
+
right: currentEnterRect.right,
|
|
592
|
+
bottom: currentEnterRect.bottom,
|
|
593
|
+
top: currentEnterRect.top
|
|
594
|
+
});
|
|
595
|
+
}
|
|
596
|
+
if (currentEnterRect.bottom < Number(prevRect.bottom) - epsilon) {
|
|
597
|
+
incrementalBands.push({
|
|
598
|
+
left: currentEnterRect.left,
|
|
599
|
+
right: currentEnterRect.right,
|
|
600
|
+
bottom: currentEnterRect.bottom,
|
|
601
|
+
top: Math.min(currentEnterRect.top, Number(prevRect.bottom))
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
if (currentEnterRect.top > Number(prevRect.top) + epsilon) {
|
|
605
|
+
incrementalBands.push({
|
|
606
|
+
left: currentEnterRect.left,
|
|
607
|
+
right: currentEnterRect.right,
|
|
608
|
+
bottom: Math.max(currentEnterRect.bottom, Number(prevRect.top)),
|
|
609
|
+
top: currentEnterRect.top
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
const incrementalCellKeys = new Set();
|
|
614
|
+
for (let i = 0; i < incrementalBands.length; i++) {
|
|
615
|
+
const bandRange = describeCellRange(incrementalBands[i], cellSize);
|
|
616
|
+
if (!bandRange || bandRange.directGridLookups <= 0) {
|
|
617
|
+
continue;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
incrementalPanBandCount++;
|
|
621
|
+
buildCellKeySetFromRange(bandRange, incrementalCellKeys);
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
if (incrementalPanBandCount > 0 &&
|
|
625
|
+
incrementalCellKeys.size > 0 &&
|
|
626
|
+
incrementalCellKeys.size < directGridLookups) {
|
|
627
|
+
usedIncrementalPanVisibility = true;
|
|
628
|
+
incrementalPanCellLookups = incrementalCellKeys.size;
|
|
629
|
+
|
|
630
|
+
runtime.lastVisibleIds.forEach((nodeId) => {
|
|
631
|
+
if (runtime.nodes.has(nodeId)) {
|
|
632
|
+
nextVisibleIds.add(nodeId);
|
|
633
|
+
}
|
|
634
|
+
});
|
|
635
|
+
addCandidatesFromCellKeySet(incrementalCellKeys, candidateIds, true);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
if (!usedIncrementalPanVisibility) {
|
|
640
|
+
const result = collectCandidates(currentRange);
|
|
641
|
+
candidateIds = result.candidateIds;
|
|
642
|
+
usedPopulatedCellScan = result.usedPopulatedCellScan;
|
|
643
|
+
directGridLookups = result.directGridLookups;
|
|
644
|
+
gridSpanX = result.gridSpanX;
|
|
645
|
+
gridSpanY = result.gridSpanY;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
candidateIds.forEach((nodeId) => {
|
|
649
|
+
const node = runtime.nodes.get(nodeId);
|
|
650
|
+
if (!node) {
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
const depth = camZ - node.z;
|
|
655
|
+
if (depth <= camNear || depth >= camFar) {
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
const baseMarginWorld = runtime.lastVisibleIds.has(nodeId)
|
|
660
|
+
? exitMarginWorld
|
|
661
|
+
: enterMarginWorld;
|
|
662
|
+
const leftBound = viewportLeft - (baseMarginWorld + horizontalLeadMarginLeftWorld);
|
|
663
|
+
const rightBound = viewportRight + (baseMarginWorld + horizontalLeadMarginRightWorld);
|
|
664
|
+
const bottomBound = viewportBottom - (baseMarginWorld + verticalLeadMarginBottomWorld);
|
|
665
|
+
const topBound = viewportTop + (baseMarginWorld + verticalLeadMarginTopWorld);
|
|
666
|
+
|
|
667
|
+
const isVisible = !(
|
|
668
|
+
node.maxX < leftBound ||
|
|
669
|
+
node.minX > rightBound ||
|
|
670
|
+
node.maxY < bottomBound ||
|
|
671
|
+
node.minY > topBound
|
|
672
|
+
);
|
|
673
|
+
|
|
674
|
+
if (isVisible) {
|
|
675
|
+
nextVisibleIds.add(nodeId);
|
|
676
|
+
}
|
|
677
|
+
});
|
|
678
|
+
|
|
679
|
+
const enteredIds = [];
|
|
680
|
+
nextVisibleIds.forEach((nodeId) => {
|
|
681
|
+
if (!runtime.lastVisibleIds.has(nodeId)) {
|
|
682
|
+
enteredIds.push(nodeId);
|
|
683
|
+
}
|
|
684
|
+
});
|
|
685
|
+
|
|
686
|
+
const exitedIds = [];
|
|
687
|
+
runtime.lastVisibleIds.forEach((nodeId) => {
|
|
688
|
+
if (!usedIncrementalPanVisibility && !nextVisibleIds.has(nodeId)) {
|
|
689
|
+
exitedIds.push(nodeId);
|
|
690
|
+
}
|
|
691
|
+
});
|
|
692
|
+
|
|
693
|
+
const visibilityChanged = enteredIds.length > 0 || exitedIds.length > 0;
|
|
694
|
+
const canReusePriorityIds =
|
|
695
|
+
visibilityChanged !== true &&
|
|
696
|
+
Array.isArray(runtime.lastPriorityIds) &&
|
|
697
|
+
runtime.lastPriorityIds.length === nextVisibleIds.size;
|
|
698
|
+
const priorityIds = canReusePriorityIds
|
|
699
|
+
? runtime.lastPriorityIds
|
|
700
|
+
: (usedIncrementalPanVisibility
|
|
701
|
+
? buildIncrementalPriorityIds(nextVisibleIds, enteredIds, camX, camY)
|
|
702
|
+
: buildPriorityIds(nextVisibleIds, camX, camY));
|
|
703
|
+
|
|
704
|
+
runtime.lastVisibleIds = nextVisibleIds;
|
|
705
|
+
runtime.lastPriorityIds = priorityIds;
|
|
706
|
+
runtime.lastVisibilityRect = {
|
|
707
|
+
left: currentEnterRect.left,
|
|
708
|
+
right: currentEnterRect.right,
|
|
709
|
+
bottom: currentEnterRect.bottom,
|
|
710
|
+
top: currentEnterRect.top,
|
|
711
|
+
mode: isFullResMode === true ? 'full-res' : 'lod',
|
|
712
|
+
cameraZ: camZ
|
|
713
|
+
};
|
|
714
|
+
runtime.lastNodeCount = runtime.nodes.size;
|
|
715
|
+
runtime.lastResultSeq = seq;
|
|
716
|
+
|
|
717
|
+
const t1 = (typeof performance !== 'undefined' && typeof performance.now === 'function')
|
|
718
|
+
? performance.now()
|
|
719
|
+
: Date.now();
|
|
720
|
+
|
|
721
|
+
const canOmitFullVisibleIds =
|
|
722
|
+
mainLastAppliedSeq > 0 &&
|
|
723
|
+
mainLastAppliedSeq === previousResultSeq &&
|
|
724
|
+
seq === previousResultSeq + 1;
|
|
725
|
+
|
|
726
|
+
self.postMessage({
|
|
727
|
+
type: 'VISIBILITY_RESULT',
|
|
728
|
+
boardId,
|
|
729
|
+
seq,
|
|
730
|
+
visibleIds: canOmitFullVisibleIds ? undefined : Array.from(nextVisibleIds),
|
|
731
|
+
visibleCount: Number(nextVisibleIds.size || 0),
|
|
732
|
+
enteredIds,
|
|
733
|
+
exitedIds,
|
|
734
|
+
priorityIds: canReusePriorityIds ? undefined : priorityIds,
|
|
735
|
+
visibilityChanged,
|
|
736
|
+
viewportRect: runtime.lastVisibilityRect,
|
|
737
|
+
lastVisibilityNodeCount: runtime.lastNodeCount,
|
|
738
|
+
fullResVisibilityReconcilePending: usedIncrementalPanVisibility === true,
|
|
739
|
+
profile: {
|
|
740
|
+
durationMs: Number(Math.max(0, t1 - t0).toFixed(2)),
|
|
741
|
+
cameraZ: Number((camZ || 0).toFixed(2)),
|
|
742
|
+
useCoarseVisibility: marginPolicy.useCoarseVisibility === true,
|
|
743
|
+
marginWorld: Number((marginWorld || 0).toFixed(2)),
|
|
744
|
+
gridSpanX: Number(gridSpanX || 0),
|
|
745
|
+
gridSpanY: Number(gridSpanY || 0),
|
|
746
|
+
directGridLookups: Number(directGridLookups || 0),
|
|
747
|
+
usedPopulatedCellScan: usedPopulatedCellScan === true,
|
|
748
|
+
incrementalPanMode: usedIncrementalPanVisibility === true,
|
|
749
|
+
incrementalPanBandCount,
|
|
750
|
+
incrementalPanCellLookups,
|
|
751
|
+
omittedFullVisibleIds: canOmitFullVisibleIds === true,
|
|
752
|
+
candidateCount: Number(candidateIds.size || 0),
|
|
753
|
+
processedCount: Number(candidateIds.size || 0),
|
|
754
|
+
prevVisibleCount: Number(previousVisibleCount || 0),
|
|
755
|
+
nextVisibleCount: Number(nextVisibleIds.size || 0),
|
|
756
|
+
visibilityChanged: visibilityChanged === true
|
|
757
|
+
}
|
|
758
|
+
});
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
function handleHitTestQuery(message) {
|
|
762
|
+
const boardId = normalizeBoardId(message.boardId);
|
|
763
|
+
if (!boardId || boardId !== runtime.boardId) {
|
|
764
|
+
return;
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
const seq = Number(message.seq || 0);
|
|
768
|
+
const cellX = Math.floor(Number(message.cellX || 0));
|
|
769
|
+
const cellY = Math.floor(Number(message.cellY || 0));
|
|
770
|
+
const t0 = (typeof performance !== 'undefined' && typeof performance.now === 'function')
|
|
771
|
+
? performance.now()
|
|
772
|
+
: Date.now();
|
|
773
|
+
const { candidateIds, cellLookups, cellRadius } = collectHitTestCandidateIds(
|
|
774
|
+
cellX,
|
|
775
|
+
cellY,
|
|
776
|
+
message.cellRadius
|
|
777
|
+
);
|
|
778
|
+
const t1 = (typeof performance !== 'undefined' && typeof performance.now === 'function')
|
|
779
|
+
? performance.now()
|
|
780
|
+
: Date.now();
|
|
781
|
+
|
|
782
|
+
self.postMessage({
|
|
783
|
+
type: 'HIT_TEST_RESULT',
|
|
784
|
+
boardId,
|
|
785
|
+
seq,
|
|
786
|
+
cellX,
|
|
787
|
+
cellY,
|
|
788
|
+
cellRadius,
|
|
789
|
+
rangeKey: `${cellX}:${cellY}:${cellRadius}`,
|
|
790
|
+
candidateIds: Array.from(candidateIds),
|
|
791
|
+
profile: {
|
|
792
|
+
durationMs: Number(Math.max(0, t1 - t0).toFixed(2)),
|
|
793
|
+
cellLookups,
|
|
794
|
+
candidateCount: Number(candidateIds.size || 0),
|
|
795
|
+
spatialGridCellCount: Number(runtime.spatialGrid.size || 0)
|
|
796
|
+
}
|
|
797
|
+
});
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
function handleSelectionQuery(message) {
|
|
801
|
+
const boardId = normalizeBoardId(message.boardId);
|
|
802
|
+
if (!boardId || boardId !== runtime.boardId) {
|
|
803
|
+
return;
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
const rect = normalizeSelectionRect(message.selectionRect);
|
|
807
|
+
if (!rect) {
|
|
808
|
+
return;
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
const seq = Number(message.seq || 0);
|
|
812
|
+
const t0 = (typeof performance !== 'undefined' && typeof performance.now === 'function')
|
|
813
|
+
? performance.now()
|
|
814
|
+
: Date.now();
|
|
815
|
+
const range = describeCellRange(rect, Math.max(1, Number(message.cellSize || runtime.cellSize || 500)));
|
|
816
|
+
const { candidateIds, usedPopulatedCellScan, directGridLookups, gridSpanX, gridSpanY } = collectCandidates(range);
|
|
817
|
+
const selectedIds = [];
|
|
818
|
+
|
|
819
|
+
candidateIds.forEach((nodeId) => {
|
|
820
|
+
const node = runtime.nodes.get(nodeId);
|
|
821
|
+
if (!node) {
|
|
822
|
+
return;
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
if (node.minX >= rect.left &&
|
|
826
|
+
node.maxX <= rect.right &&
|
|
827
|
+
node.minY >= rect.bottom &&
|
|
828
|
+
node.maxY <= rect.top) {
|
|
829
|
+
selectedIds.push(nodeId);
|
|
830
|
+
}
|
|
831
|
+
});
|
|
832
|
+
|
|
833
|
+
const t1 = (typeof performance !== 'undefined' && typeof performance.now === 'function')
|
|
834
|
+
? performance.now()
|
|
835
|
+
: Date.now();
|
|
836
|
+
|
|
837
|
+
self.postMessage({
|
|
838
|
+
type: 'SELECTION_QUERY_RESULT',
|
|
839
|
+
boardId,
|
|
840
|
+
seq,
|
|
841
|
+
selectedIds,
|
|
842
|
+
selectionRect: rect,
|
|
843
|
+
profile: {
|
|
844
|
+
durationMs: Number(Math.max(0, t1 - t0).toFixed(2)),
|
|
845
|
+
candidateCount: Number(candidateIds.size || 0),
|
|
846
|
+
selectedCount: Number(selectedIds.length || 0),
|
|
847
|
+
gridSpanX: Number(gridSpanX || 0),
|
|
848
|
+
gridSpanY: Number(gridSpanY || 0),
|
|
849
|
+
directGridLookups: Number(directGridLookups || 0),
|
|
850
|
+
usedPopulatedCellScan: usedPopulatedCellScan === true
|
|
851
|
+
}
|
|
852
|
+
});
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
self.onmessage = function (event) {
|
|
856
|
+
const message = event?.data || null;
|
|
857
|
+
if (!message || typeof message !== 'object') {
|
|
858
|
+
return;
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
switch (message.type) {
|
|
862
|
+
case 'RESET_BOARD':
|
|
863
|
+
clearState(message.boardId, message.cellSize);
|
|
864
|
+
break;
|
|
865
|
+
case 'REBUILD_BOARD':
|
|
866
|
+
rebuildBoard(message.boardId, message.cellSize, message.nodes);
|
|
867
|
+
break;
|
|
868
|
+
case 'UPSERT_NODE':
|
|
869
|
+
if (normalizeBoardId(message.boardId) === runtime.boardId) {
|
|
870
|
+
upsertNode(message.node);
|
|
871
|
+
}
|
|
872
|
+
break;
|
|
873
|
+
case 'REMOVE_NODE':
|
|
874
|
+
if (normalizeBoardId(message.boardId) === runtime.boardId) {
|
|
875
|
+
removeNode(message.nodeId);
|
|
876
|
+
}
|
|
877
|
+
break;
|
|
878
|
+
case 'VISIBILITY_QUERY':
|
|
879
|
+
handleVisibilityQuery(message);
|
|
880
|
+
break;
|
|
881
|
+
case 'HIT_TEST_QUERY':
|
|
882
|
+
handleHitTestQuery(message);
|
|
883
|
+
break;
|
|
884
|
+
case 'SELECTION_QUERY':
|
|
885
|
+
handleSelectionQuery(message);
|
|
886
|
+
break;
|
|
887
|
+
default:
|
|
888
|
+
break;
|
|
889
|
+
}
|
|
890
|
+
};
|
|
891
|
+
|
|
892
|
+
self.postMessage({
|
|
893
|
+
type: 'READY',
|
|
894
|
+
worker: 'visibility'
|
|
895
|
+
});
|