fast-astar 1.0.5 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Astar.js +1709 -0
- package/Grid.js +585 -0
- package/README.md +272 -36
- package/astar.wasm +0 -0
- package/index.js +282 -0
- package/package.json +23 -21
- package/wasm.d.ts +89 -0
- package/wasm.js +388 -0
- package/dist/Astar.js +0 -2
- package/dist/Grid.js +0 -2
- package/dist/index.js +0 -3
package/Astar.js
ADDED
|
@@ -0,0 +1,1709 @@
|
|
|
1
|
+
// 🚀 智能自适应A*算法 - 最终整合版本
|
|
2
|
+
// 自动选择最优实现:Ultra/Master/WebAssembly
|
|
3
|
+
// 支持所有环境:Node.js、浏览器、WebWorker等
|
|
4
|
+
|
|
5
|
+
import { isNode, isBrowser, getPerformanceNow } from './utils.js';
|
|
6
|
+
|
|
7
|
+
// 高性能二叉堆实现
|
|
8
|
+
class BinaryHeap {
|
|
9
|
+
constructor(scoreFunction = (node) => node.f) {
|
|
10
|
+
this.content = [];
|
|
11
|
+
this.scoreFunction = scoreFunction;
|
|
12
|
+
// element -> index (for decrease-key / update)
|
|
13
|
+
this.indexMap = new Map();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
push(element) {
|
|
17
|
+
this.content.push(element);
|
|
18
|
+
this.indexMap.set(element, this.content.length - 1);
|
|
19
|
+
this.sinkDown(this.content.length - 1);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
pop() {
|
|
23
|
+
const result = this.content[0];
|
|
24
|
+
const end = this.content.pop();
|
|
25
|
+
this.indexMap.delete(result);
|
|
26
|
+
if (this.content.length > 0) {
|
|
27
|
+
this.content[0] = end;
|
|
28
|
+
this.indexMap.set(end, 0);
|
|
29
|
+
this.bubbleUp(0);
|
|
30
|
+
}
|
|
31
|
+
return result;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
size() {
|
|
35
|
+
return this.content.length;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
isEmpty() {
|
|
39
|
+
return this.content.length === 0;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
sinkDown(n) {
|
|
43
|
+
const element = this.content[n];
|
|
44
|
+
const elemScore = this.scoreFunction(element);
|
|
45
|
+
|
|
46
|
+
while (n > 0) {
|
|
47
|
+
const parentN = ((n + 1) >> 1) - 1;
|
|
48
|
+
const parent = this.content[parentN];
|
|
49
|
+
if (elemScore >= this.scoreFunction(parent)) break;
|
|
50
|
+
|
|
51
|
+
this.content[parentN] = element;
|
|
52
|
+
this.content[n] = parent;
|
|
53
|
+
this.indexMap.set(element, parentN);
|
|
54
|
+
this.indexMap.set(parent, n);
|
|
55
|
+
n = parentN;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
bubbleUp(n) {
|
|
60
|
+
const element = this.content[n];
|
|
61
|
+
const elemScore = this.scoreFunction(element);
|
|
62
|
+
|
|
63
|
+
while (true) {
|
|
64
|
+
const child2N = (n + 1) << 1;
|
|
65
|
+
const child1N = child2N - 1;
|
|
66
|
+
let swap = null;
|
|
67
|
+
|
|
68
|
+
if (child1N < this.content.length) {
|
|
69
|
+
const child1 = this.content[child1N];
|
|
70
|
+
if (this.scoreFunction(child1) < elemScore) {
|
|
71
|
+
swap = child1N;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (child2N < this.content.length) {
|
|
76
|
+
const child2 = this.content[child2N];
|
|
77
|
+
const compareScore = swap === null ? elemScore : this.scoreFunction(this.content[swap]);
|
|
78
|
+
if (this.scoreFunction(child2) < compareScore) {
|
|
79
|
+
swap = child2N;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (swap !== null) {
|
|
84
|
+
this.content[n] = this.content[swap];
|
|
85
|
+
this.content[swap] = element;
|
|
86
|
+
this.indexMap.set(this.content[n], n);
|
|
87
|
+
this.indexMap.set(element, swap);
|
|
88
|
+
n = swap;
|
|
89
|
+
} else {
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Re-heapify after element score changed
|
|
96
|
+
update(element) {
|
|
97
|
+
const pos = this.indexMap.get(element);
|
|
98
|
+
if (pos === undefined) return;
|
|
99
|
+
this.sinkDown(pos);
|
|
100
|
+
this.bubbleUp(pos);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Ultra A*实现 - 高性能版本(兼容内存高效Grid)
|
|
105
|
+
class UltraAstar {
|
|
106
|
+
constructor(grid) {
|
|
107
|
+
this.grid = grid;
|
|
108
|
+
this.width = grid.col;
|
|
109
|
+
this.height = grid.row;
|
|
110
|
+
|
|
111
|
+
// 兼容新旧Grid接口
|
|
112
|
+
this.gridData = grid.grid || null; // 传统模式
|
|
113
|
+
this.useMemoryEfficientGrid = grid.useMemoryEfficientMode || false;
|
|
114
|
+
|
|
115
|
+
this.openHeap = new BinaryHeap();
|
|
116
|
+
this.openSet = new Set();
|
|
117
|
+
this.closedSet = new Set();
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// 兼容的节点获取方法
|
|
121
|
+
getNode(x, y) {
|
|
122
|
+
if (this.useMemoryEfficientGrid) {
|
|
123
|
+
return this.grid.getNodeAt(x, y);
|
|
124
|
+
} else {
|
|
125
|
+
return this.gridData[y][x];
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// 兼容的障碍物检查方法
|
|
130
|
+
isWalkable(x, y) {
|
|
131
|
+
if (this.useMemoryEfficientGrid) {
|
|
132
|
+
return this.grid.isWalkableAt(x, y);
|
|
133
|
+
} else {
|
|
134
|
+
return this.gridData[y][x].value < 1;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
search(start, end, options = {}) {
|
|
139
|
+
// 输入验证
|
|
140
|
+
if (!Array.isArray(start) || start.length < 2 ||
|
|
141
|
+
!Array.isArray(end) || end.length < 2) {
|
|
142
|
+
throw new Error('起点和终点必须是包含至少2个元素的数组 [x, y]');
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const startX = Math.floor(start[0]);
|
|
146
|
+
const startY = Math.floor(start[1]);
|
|
147
|
+
const endX = Math.floor(end[0]);
|
|
148
|
+
const endY = Math.floor(end[1]);
|
|
149
|
+
|
|
150
|
+
// 边界检查
|
|
151
|
+
if (startX < 0 || startX >= this.width || startY < 0 || startY >= this.height) {
|
|
152
|
+
throw new Error(`起点坐标 [${startX}, ${startY}] 超出网格范围 [0-${this.width-1}, 0-${this.height-1}]`);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (endX < 0 || endX >= this.width || endY < 0 || endY >= this.height) {
|
|
156
|
+
throw new Error(`终点坐标 [${endX}, ${endY}] 超出网格范围 [0-${this.width-1}, 0-${this.height-1}]`);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const rightAngle = options.rightAngle || false;
|
|
160
|
+
const optimalResult = options.optimalResult !== false; // default true, ensures optimal when true
|
|
161
|
+
const hasRender = !!(this.grid && typeof this.grid.render === 'function');
|
|
162
|
+
|
|
163
|
+
// 重置搜索状态
|
|
164
|
+
this.openHeap = new BinaryHeap();
|
|
165
|
+
this.openSet.clear();
|
|
166
|
+
this.closedSet.clear();
|
|
167
|
+
|
|
168
|
+
const startNode = this.getNode(startX, startY);
|
|
169
|
+
const endNode = this.getNode(endX, endY);
|
|
170
|
+
|
|
171
|
+
if (!startNode || !endNode) {
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// 检查起点和终点是否可通行
|
|
176
|
+
if (!this.isWalkable(startX, startY)) {
|
|
177
|
+
throw new Error(`起点 [${startX}, ${startY}] 是障碍物,无法通行`);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (!this.isWalkable(endX, endY)) {
|
|
181
|
+
throw new Error(`终点 [${endX}, ${endY}] 是障碍物,无法通行`);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// 如果起点和终点相同,直接返回
|
|
185
|
+
if (startX === endX && startY === endY) {
|
|
186
|
+
return [[startX, startY]];
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
startNode.g = 0;
|
|
190
|
+
startNode.h = this.heuristicDistance(startX, startY, endX, endY, rightAngle, optimalResult);
|
|
191
|
+
startNode.f = startNode.h;
|
|
192
|
+
startNode.parent = null;
|
|
193
|
+
|
|
194
|
+
// 仅用于演示:将分数同步回节点对象(只在提供 render 时启用)
|
|
195
|
+
if (hasRender && this.gridData && this.gridData[startY] && this.gridData[startY][startX]) {
|
|
196
|
+
const gridNode = this.gridData[startY][startX];
|
|
197
|
+
gridNode.g = startNode.g;
|
|
198
|
+
gridNode.h = startNode.h;
|
|
199
|
+
gridNode.f = startNode.f;
|
|
200
|
+
gridNode.parent = startNode.parent;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// 触发 render 回调:起点加入开放列表(在设置 g、h、f 之后)
|
|
204
|
+
if (hasRender && this.grid.set) {
|
|
205
|
+
this.grid.set([startX, startY], 'type', 'open');
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
this.openHeap.push(startNode);
|
|
209
|
+
this.openSet.add([startX, startY].toString());
|
|
210
|
+
|
|
211
|
+
while (!this.openHeap.isEmpty()) {
|
|
212
|
+
const current = this.openHeap.pop();
|
|
213
|
+
const currentKey = [current.x, current.y].toString();
|
|
214
|
+
|
|
215
|
+
this.openSet.delete(currentKey);
|
|
216
|
+
this.closedSet.add(currentKey);
|
|
217
|
+
|
|
218
|
+
// 触发 render 回调:高亮当前检查的节点
|
|
219
|
+
if (hasRender && this.grid.set) {
|
|
220
|
+
this.grid.set([current.x, current.y], 'type', 'highlight');
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (current.x === endX && current.y === endY) {
|
|
224
|
+
return this.reconstructPath(current);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// 触发 render 回调:当前节点加入关闭列表
|
|
228
|
+
if (hasRender && this.grid.set) {
|
|
229
|
+
this.grid.set([current.x, current.y], 'type', 'close');
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const neighbors = this.getNeighbors(current, rightAngle);
|
|
233
|
+
|
|
234
|
+
for (const neighbor of neighbors) {
|
|
235
|
+
const neighborKey = [neighbor.x, neighbor.y].toString();
|
|
236
|
+
|
|
237
|
+
if (this.closedSet.has(neighborKey)) continue;
|
|
238
|
+
|
|
239
|
+
const moveCost = this.getMoveCost(current, neighbor, optimalResult);
|
|
240
|
+
const tentativeG = current.g + moveCost;
|
|
241
|
+
|
|
242
|
+
if (!this.openSet.has(neighborKey)) {
|
|
243
|
+
neighbor.parent = [current.x, current.y];
|
|
244
|
+
neighbor.g = tentativeG;
|
|
245
|
+
neighbor.h = this.heuristicDistance(neighbor.x, neighbor.y, endX, endY, rightAngle, optimalResult);
|
|
246
|
+
neighbor.f = neighbor.g + neighbor.h;
|
|
247
|
+
|
|
248
|
+
// 仅用于演示:将分数同步回节点对象(只在提供 render 时启用)
|
|
249
|
+
if (hasRender && this.gridData && this.gridData[neighbor.y] && this.gridData[neighbor.y][neighbor.x]) {
|
|
250
|
+
const gridNode = this.gridData[neighbor.y][neighbor.x];
|
|
251
|
+
gridNode.g = neighbor.g;
|
|
252
|
+
gridNode.h = neighbor.h;
|
|
253
|
+
gridNode.f = neighbor.f;
|
|
254
|
+
gridNode.parent = neighbor.parent;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// 触发 render 回调:新节点加入开放列表(在设置 g、h、f 之后)
|
|
258
|
+
if (hasRender && this.grid.set) {
|
|
259
|
+
this.grid.set([neighbor.x, neighbor.y], 'type', 'open');
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
this.openHeap.push(neighbor);
|
|
263
|
+
this.openSet.add(neighborKey);
|
|
264
|
+
} else if (tentativeG < neighbor.g) {
|
|
265
|
+
neighbor.parent = [current.x, current.y];
|
|
266
|
+
neighbor.g = tentativeG;
|
|
267
|
+
// h stays valid for same end; f must be updated
|
|
268
|
+
neighbor.f = neighbor.g + neighbor.h;
|
|
269
|
+
|
|
270
|
+
// 仅用于演示:将分数同步回节点对象(只在提供 render 时启用)
|
|
271
|
+
if (hasRender && this.gridData && this.gridData[neighbor.y] && this.gridData[neighbor.y][neighbor.x]) {
|
|
272
|
+
const gridNode = this.gridData[neighbor.y][neighbor.x];
|
|
273
|
+
gridNode.g = neighbor.g;
|
|
274
|
+
gridNode.f = neighbor.f;
|
|
275
|
+
gridNode.parent = neighbor.parent;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// 触发 render 回调:更新节点(在设置 g、f 之后)
|
|
279
|
+
if (hasRender && this.grid.set) {
|
|
280
|
+
this.grid.set([neighbor.x, neighbor.y], 'type', 'update');
|
|
281
|
+
}
|
|
282
|
+
// decrease-key: re-heapify (required for optimality)
|
|
283
|
+
this.openHeap.update(neighbor);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return null;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
getNeighbors(node, rightAngle) {
|
|
292
|
+
const neighbors = [];
|
|
293
|
+
const directions = rightAngle ?
|
|
294
|
+
[[0, -1], [1, 0], [0, 1], [-1, 0]] :
|
|
295
|
+
[[0, -1], [1, 0], [0, 1], [-1, 0], [1, -1], [1, 1], [-1, 1], [-1, -1]];
|
|
296
|
+
|
|
297
|
+
for (const [dx, dy] of directions) {
|
|
298
|
+
const x = node.x + dx;
|
|
299
|
+
const y = node.y + dy;
|
|
300
|
+
|
|
301
|
+
if (x >= 0 && x < this.width && y >= 0 && y < this.height) {
|
|
302
|
+
if (this.isWalkable(x, y)) {
|
|
303
|
+
const neighbor = this.getNode(x, y);
|
|
304
|
+
if (neighbor) {
|
|
305
|
+
neighbors.push(neighbor);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
return neighbors;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
getMoveCost(from, to, optimalResult) {
|
|
315
|
+
if (!optimalResult) return 10;
|
|
316
|
+
|
|
317
|
+
const dx = Math.abs(from.x - to.x);
|
|
318
|
+
const dy = Math.abs(from.y - to.y);
|
|
319
|
+
|
|
320
|
+
return (dx === 0 || dy === 0) ? 10 : 14;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
heuristicDistance(x1, y1, x2, y2, rightAngle, optimalResult) {
|
|
324
|
+
const dx = Math.abs(x1 - x2);
|
|
325
|
+
const dy = Math.abs(y1 - y2);
|
|
326
|
+
if (rightAngle) return (dx + dy) * 10; // Manhattan (4-dir)
|
|
327
|
+
const maxD = Math.max(dx, dy);
|
|
328
|
+
const minD = dx + dy - maxD;
|
|
329
|
+
if (!optimalResult) return maxD * 10; // Chebyshev (diag cost==10 in this mode)
|
|
330
|
+
return 14 * minD + 10 * (maxD - minD); // Octile (8-dir, 10/14)
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
reconstructPath(endNode) {
|
|
334
|
+
const path = [];
|
|
335
|
+
let current = [endNode.x, endNode.y];
|
|
336
|
+
|
|
337
|
+
while (current) {
|
|
338
|
+
path.push(current);
|
|
339
|
+
const node = this.getNode(current[0], current[1]);
|
|
340
|
+
current = node ? node.parent : null;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
return path.reverse();
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// 兼容性方法
|
|
347
|
+
getBackPath(xy) {
|
|
348
|
+
const node = this.getNode(xy[0], xy[1]);
|
|
349
|
+
return node ? this.reconstructPath(node) : [];
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
h(start, end) {
|
|
353
|
+
// best-effort: assume optimalResult=true when used as heuristic API
|
|
354
|
+
return this.heuristicDistance(start[0], start[1], end[0], end[1], false, true);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// 内存使用统计
|
|
358
|
+
getMemoryUsage() {
|
|
359
|
+
const openSetSize = this.openSet.size * 20; // 估算Set条目大小
|
|
360
|
+
const closedSetSize = this.closedSet.size * 20;
|
|
361
|
+
const openHeapSize = this.openHeap.size ? (this.openHeap.size() * 4) : (this.openHeap.size() * 50); // TypedArray: 4字节/索引 vs 普通数组: 50字节/节点
|
|
362
|
+
|
|
363
|
+
return {
|
|
364
|
+
openSet: openSetSize,
|
|
365
|
+
closedSet: closedSetSize,
|
|
366
|
+
openHeap: openHeapSize,
|
|
367
|
+
total: openSetSize + closedSetSize + openHeapSize,
|
|
368
|
+
mode: this.useMemoryEfficientGrid ? 'Compatible with Memory Efficient Grid' : 'Standard Grid'
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// Memory-Efficient A*实现 - 专为大地图优化
|
|
374
|
+
class MemoryEfficientAstar {
|
|
375
|
+
constructor(grid) {
|
|
376
|
+
this.grid = grid;
|
|
377
|
+
this.width = grid.col;
|
|
378
|
+
this.height = grid.row;
|
|
379
|
+
|
|
380
|
+
// 如果Grid已经是内存高效模式,直接使用
|
|
381
|
+
if (grid.useMemoryEfficientMode) {
|
|
382
|
+
this.useGridObstacles = true;
|
|
383
|
+
} else {
|
|
384
|
+
// 否则创建自己的位图
|
|
385
|
+
this.useGridObstacles = false;
|
|
386
|
+
this.obstacles = new Uint8Array(Math.ceil(this.width * this.height / 8));
|
|
387
|
+
this.initObstacles();
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// 动态分配的数据结构,只存储访问过的节点
|
|
391
|
+
this.closedSet = new Set(); // 使用数字索引而不是字符串
|
|
392
|
+
this.gScore = new Map(); // 只存储计算过的g值
|
|
393
|
+
this.fScore = new Map(); // 存储f值用于堆排序
|
|
394
|
+
this.parent = new Map(); // 只存储有父节点的节点
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
initObstacles() {
|
|
398
|
+
// 将Grid的障碍物信息压缩到位图中
|
|
399
|
+
for (let y = 0; y < this.height; y++) {
|
|
400
|
+
for (let x = 0; x < this.width; x++) {
|
|
401
|
+
if (this.grid.grid[y][x].value >= 1) {
|
|
402
|
+
this.setObstacle(x, y, true);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
setObstacle(x, y, isObstacle) {
|
|
409
|
+
if (this.useGridObstacles) {
|
|
410
|
+
this.grid.setObstacleBit(x, y, isObstacle);
|
|
411
|
+
} else {
|
|
412
|
+
const index = y * this.width + x;
|
|
413
|
+
const byteIndex = Math.floor(index / 8);
|
|
414
|
+
const bitIndex = index % 8;
|
|
415
|
+
|
|
416
|
+
if (isObstacle) {
|
|
417
|
+
this.obstacles[byteIndex] |= (1 << bitIndex);
|
|
418
|
+
} else {
|
|
419
|
+
this.obstacles[byteIndex] &= ~(1 << bitIndex);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
isObstacle(x, y) {
|
|
425
|
+
if (this.useGridObstacles) {
|
|
426
|
+
return this.grid.getObstacleBit(x, y);
|
|
427
|
+
} else {
|
|
428
|
+
const index = y * this.width + x;
|
|
429
|
+
const byteIndex = Math.floor(index / 8);
|
|
430
|
+
const bitIndex = index % 8;
|
|
431
|
+
return (this.obstacles[byteIndex] & (1 << bitIndex)) !== 0;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
nodeIndex(x, y) {
|
|
436
|
+
return y * this.width + x;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
indexToCoords(index) {
|
|
440
|
+
return [index % this.width, Math.floor(index / this.width)];
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
search(start, end, options = {}) {
|
|
444
|
+
const rightAngle = options.rightAngle || false;
|
|
445
|
+
const optimalResult = true; // always optimal paths
|
|
446
|
+
|
|
447
|
+
// 清理之前的搜索状态
|
|
448
|
+
this.closedSet.clear();
|
|
449
|
+
this.gScore.clear();
|
|
450
|
+
this.fScore.clear();
|
|
451
|
+
this.parent.clear();
|
|
452
|
+
|
|
453
|
+
const startIndex = this.nodeIndex(start[0], start[1]);
|
|
454
|
+
const endIndex = this.nodeIndex(end[0], end[1]);
|
|
455
|
+
|
|
456
|
+
// 创建二叉堆,使用fScore作为评分函数
|
|
457
|
+
const openHeap = new IndexBinaryHeap((index) => this.fScore.get(index) || Infinity);
|
|
458
|
+
|
|
459
|
+
// 初始化起点
|
|
460
|
+
this.gScore.set(startIndex, 0);
|
|
461
|
+
const h = this.heuristicDistance(start[0], start[1], end[0], end[1], rightAngle, optimalResult);
|
|
462
|
+
this.fScore.set(startIndex, h); // f = g + h = 0 + h
|
|
463
|
+
openHeap.push(startIndex);
|
|
464
|
+
|
|
465
|
+
const directions = rightAngle ?
|
|
466
|
+
[[-1, 0], [1, 0], [0, -1], [0, 1]] :
|
|
467
|
+
[[-1, 0], [1, 0], [0, -1], [0, 1], [-1, -1], [-1, 1], [1, -1], [1, 1]];
|
|
468
|
+
|
|
469
|
+
while (!openHeap.isEmpty()) {
|
|
470
|
+
// 从堆中取出f值最小的节点
|
|
471
|
+
const currentIndex = openHeap.pop();
|
|
472
|
+
this.closedSet.add(currentIndex);
|
|
473
|
+
|
|
474
|
+
// 检查是否到达终点
|
|
475
|
+
if (currentIndex === endIndex) {
|
|
476
|
+
return this.reconstructPath(endIndex, start);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
const [currentX, currentY] = this.indexToCoords(currentIndex);
|
|
480
|
+
const currentG = this.gScore.get(currentIndex);
|
|
481
|
+
|
|
482
|
+
// 检查邻居
|
|
483
|
+
for (const [dx, dy] of directions) {
|
|
484
|
+
const neighborX = currentX + dx;
|
|
485
|
+
const neighborY = currentY + dy;
|
|
486
|
+
|
|
487
|
+
// 边界检查
|
|
488
|
+
if (neighborX < 0 || neighborX >= this.width ||
|
|
489
|
+
neighborY < 0 || neighborY >= this.height) continue;
|
|
490
|
+
|
|
491
|
+
const neighborIndex = this.nodeIndex(neighborX, neighborY);
|
|
492
|
+
|
|
493
|
+
// 跳过障碍物和已关闭的节点
|
|
494
|
+
if (this.isObstacle(neighborX, neighborY) ||
|
|
495
|
+
this.closedSet.has(neighborIndex)) continue;
|
|
496
|
+
|
|
497
|
+
// 计算移动成本
|
|
498
|
+
const moveCost = optimalResult && (dx !== 0 && dy !== 0) ? 14 : 10;
|
|
499
|
+
const tentativeG = currentG + moveCost;
|
|
500
|
+
|
|
501
|
+
// 如果找到更好的路径
|
|
502
|
+
const existingG = this.gScore.get(neighborIndex);
|
|
503
|
+
if (existingG === undefined || tentativeG < existingG) {
|
|
504
|
+
this.parent.set(neighborIndex, currentIndex);
|
|
505
|
+
this.gScore.set(neighborIndex, tentativeG);
|
|
506
|
+
|
|
507
|
+
const h = this.heuristicDistance(neighborX, neighborY, end[0], end[1], rightAngle, optimalResult);
|
|
508
|
+
const f = tentativeG + h;
|
|
509
|
+
this.fScore.set(neighborIndex, f);
|
|
510
|
+
|
|
511
|
+
// 如果节点已在堆中则更新,否则 push
|
|
512
|
+
if (openHeap.indexMap && openHeap.indexMap.has(neighborIndex)) {
|
|
513
|
+
openHeap.update(neighborIndex);
|
|
514
|
+
} else {
|
|
515
|
+
openHeap.push(neighborIndex);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
return null; // 无路径
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
reconstructPath(endIndex, start) {
|
|
525
|
+
const path = [];
|
|
526
|
+
let currentIndex = endIndex;
|
|
527
|
+
|
|
528
|
+
while (currentIndex !== undefined) {
|
|
529
|
+
const [x, y] = this.indexToCoords(currentIndex);
|
|
530
|
+
path.push([x, y]);
|
|
531
|
+
currentIndex = this.parent.get(currentIndex);
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
return path.reverse();
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
heuristicDistance(x1, y1, x2, y2, rightAngle, optimalResult) {
|
|
538
|
+
const dx = Math.abs(x1 - x2);
|
|
539
|
+
const dy = Math.abs(y1 - y2);
|
|
540
|
+
if (rightAngle) return (dx + dy) * 10;
|
|
541
|
+
const maxD = Math.max(dx, dy);
|
|
542
|
+
const minD = dx + dy - maxD;
|
|
543
|
+
if (!optimalResult) return maxD * 10;
|
|
544
|
+
return 14 * minD + 10 * (maxD - minD);
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
// 兼容性方法
|
|
548
|
+
getBackPath(xy) {
|
|
549
|
+
const index = this.nodeIndex(xy[0], xy[1]);
|
|
550
|
+
return this.reconstructPath(index, [0, 0]);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
h(start, end) {
|
|
554
|
+
return this.heuristicDistance(start[0], start[1], end[0], end[1], false, true);
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
// 内存使用统计
|
|
558
|
+
getMemoryUsage() {
|
|
559
|
+
const obstacleBytes = this.useGridObstacles ? 0 : this.obstacles.length;
|
|
560
|
+
const closedSetBytes = this.closedSet.size * 8;
|
|
561
|
+
const gScoreBytes = this.gScore.size * 16;
|
|
562
|
+
const fScoreBytes = this.fScore.size * 16;
|
|
563
|
+
const parentBytes = this.parent.size * 16;
|
|
564
|
+
|
|
565
|
+
return {
|
|
566
|
+
obstacles: obstacleBytes,
|
|
567
|
+
closedSet: closedSetBytes,
|
|
568
|
+
gScore: gScoreBytes,
|
|
569
|
+
fScore: fScoreBytes,
|
|
570
|
+
parent: parentBytes,
|
|
571
|
+
total: obstacleBytes + closedSetBytes + gScoreBytes + fScoreBytes + parentBytes
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
// 索引二叉堆 - 使用 TypedArray 优化,减少内存分配
|
|
576
|
+
class IndexBinaryHeap {
|
|
577
|
+
constructor(scoreFunction, initialCapacity = 1024) {
|
|
578
|
+
this.scoreFunction = scoreFunction;
|
|
579
|
+
// 使用 Int32Array 存储索引,减少内存占用和提高性能
|
|
580
|
+
this.capacity = initialCapacity;
|
|
581
|
+
this.content = new Int32Array(this.capacity);
|
|
582
|
+
this._size = 0; // 使用 _size 避免与方法名冲突
|
|
583
|
+
// 索引映射:快速查找元素在堆中的位置
|
|
584
|
+
this.indexMap = new Map();
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// 动态扩容
|
|
588
|
+
_ensureCapacity() {
|
|
589
|
+
if (this._size >= this.capacity) {
|
|
590
|
+
const newCapacity = this.capacity * 2;
|
|
591
|
+
const newContent = new Int32Array(newCapacity);
|
|
592
|
+
newContent.set(this.content);
|
|
593
|
+
this.content = newContent;
|
|
594
|
+
this.capacity = newCapacity;
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
push(index) {
|
|
599
|
+
this._ensureCapacity();
|
|
600
|
+
this.content[this._size] = index;
|
|
601
|
+
this.indexMap.set(index, this._size);
|
|
602
|
+
this.sinkDown(this._size);
|
|
603
|
+
this._size++;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
pop() {
|
|
607
|
+
if (this._size === 0) return null;
|
|
608
|
+
|
|
609
|
+
const result = this.content[0];
|
|
610
|
+
this.indexMap.delete(result);
|
|
611
|
+
|
|
612
|
+
if (this._size === 1) {
|
|
613
|
+
this._size = 0;
|
|
614
|
+
return result;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
const end = this.content[this._size - 1];
|
|
618
|
+
this.content[0] = end;
|
|
619
|
+
this.indexMap.set(end, 0);
|
|
620
|
+
this._size--;
|
|
621
|
+
|
|
622
|
+
if (this._size > 0) {
|
|
623
|
+
this.bubbleUp(0);
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
return result;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
size() {
|
|
630
|
+
return this._size;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
isEmpty() {
|
|
634
|
+
return this._size === 0;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
sinkDown(n) {
|
|
638
|
+
const element = this.content[n];
|
|
639
|
+
const elemScore = this.scoreFunction(element);
|
|
640
|
+
|
|
641
|
+
while (n > 0) {
|
|
642
|
+
const parentN = ((n + 1) >> 1) - 1;
|
|
643
|
+
const parent = this.content[parentN];
|
|
644
|
+
if (elemScore >= this.scoreFunction(parent)) break;
|
|
645
|
+
|
|
646
|
+
this.content[parentN] = element;
|
|
647
|
+
this.content[n] = parent;
|
|
648
|
+
this.indexMap.set(element, parentN);
|
|
649
|
+
this.indexMap.set(parent, n);
|
|
650
|
+
n = parentN;
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
bubbleUp(n) {
|
|
655
|
+
const element = this.content[n];
|
|
656
|
+
const elemScore = this.scoreFunction(element);
|
|
657
|
+
|
|
658
|
+
while (true) {
|
|
659
|
+
const child2N = (n + 1) << 1;
|
|
660
|
+
const child1N = child2N - 1;
|
|
661
|
+
let swap = null;
|
|
662
|
+
|
|
663
|
+
if (child1N < this._size) {
|
|
664
|
+
const child1 = this.content[child1N];
|
|
665
|
+
if (this.scoreFunction(child1) < elemScore) {
|
|
666
|
+
swap = child1N;
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
if (child2N < this._size) {
|
|
671
|
+
const child2 = this.content[child2N];
|
|
672
|
+
const compareScore = swap === null ? elemScore : this.scoreFunction(this.content[swap]);
|
|
673
|
+
if (this.scoreFunction(child2) < compareScore) {
|
|
674
|
+
swap = child2N;
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
if (swap !== null) {
|
|
679
|
+
const swapElement = this.content[swap];
|
|
680
|
+
this.content[n] = swapElement;
|
|
681
|
+
this.content[swap] = element;
|
|
682
|
+
this.indexMap.set(swapElement, n);
|
|
683
|
+
this.indexMap.set(element, swap);
|
|
684
|
+
n = swap;
|
|
685
|
+
} else {
|
|
686
|
+
break;
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
// 更新堆中元素的位置(当f值改变时)- O(log n) 时间复杂度
|
|
692
|
+
update(index) {
|
|
693
|
+
const pos = this.indexMap.get(index);
|
|
694
|
+
if (pos !== undefined && pos < this._size) {
|
|
695
|
+
// 先尝试向上调整(如果值变小)
|
|
696
|
+
this.sinkDown(pos);
|
|
697
|
+
// 再尝试向下调整(如果值变大)
|
|
698
|
+
this.bubbleUp(pos);
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
// 清空堆
|
|
703
|
+
clear() {
|
|
704
|
+
this._size = 0;
|
|
705
|
+
this.indexMap.clear();
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
// Master A*实现 - 内存优化版本(使用二叉堆优化)
|
|
710
|
+
class MasterAstar {
|
|
711
|
+
constructor(grid) {
|
|
712
|
+
this.grid = grid;
|
|
713
|
+
this.gridData = grid.grid;
|
|
714
|
+
this.width = grid.col;
|
|
715
|
+
this.height = grid.row;
|
|
716
|
+
this.gridSize = this.width * this.height;
|
|
717
|
+
|
|
718
|
+
// 根据网格大小动态调整内存分配策略
|
|
719
|
+
if (this.gridSize > 10000000) { // 大于1000万节点时使用内存高效模式
|
|
720
|
+
this.useMemoryEfficientMode = true;
|
|
721
|
+
this.memoryEfficientImpl = new MemoryEfficientAstar(grid);
|
|
722
|
+
} else {
|
|
723
|
+
this.useMemoryEfficientMode = false;
|
|
724
|
+
// 预分配内存池(仅对中小型网格)
|
|
725
|
+
this.closedList = new Uint8Array(this.gridSize);
|
|
726
|
+
this.gScore = new Float32Array(this.gridSize);
|
|
727
|
+
this.fScore = new Float32Array(this.gridSize);
|
|
728
|
+
this.parent = new Int32Array(this.gridSize);
|
|
729
|
+
this.inOpenList = new Uint8Array(this.gridSize); // 标记节点是否在开放列表中
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
search(start, end, options = {}) {
|
|
734
|
+
// 对于超大网格,委托给内存高效实现
|
|
735
|
+
if (this.useMemoryEfficientMode) {
|
|
736
|
+
return this.memoryEfficientImpl.search(start, end, options);
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
// 原有的Master A*实现(适用于中小型网格)- 使用二叉堆优化
|
|
740
|
+
const rightAngle = options.rightAngle || false;
|
|
741
|
+
const optimalResult = options.optimalResult !== false; // default true, ensures optimal when true
|
|
742
|
+
const hasRender = !!(this.grid && typeof this.grid.render === 'function');
|
|
743
|
+
|
|
744
|
+
// 重置状态
|
|
745
|
+
this.closedList.fill(0);
|
|
746
|
+
this.inOpenList.fill(0);
|
|
747
|
+
this.gScore.fill(Infinity);
|
|
748
|
+
this.fScore.fill(Infinity);
|
|
749
|
+
this.parent.fill(-1);
|
|
750
|
+
|
|
751
|
+
// 创建二叉堆,使用fScore作为评分函数
|
|
752
|
+
const openHeap = new IndexBinaryHeap((index) => this.fScore[index]);
|
|
753
|
+
|
|
754
|
+
const startIndex = start[1] * this.width + start[0];
|
|
755
|
+
const endIndex = end[1] * this.width + end[0];
|
|
756
|
+
|
|
757
|
+
this.gScore[startIndex] = 0;
|
|
758
|
+
this.fScore[startIndex] = this.heuristicDistance(start[0], start[1], end[0], end[1], rightAngle, optimalResult);
|
|
759
|
+
|
|
760
|
+
// 触发 render 回调:起点加入开放列表
|
|
761
|
+
if (hasRender && this.grid.set) {
|
|
762
|
+
// 将 TypedArray 中的 g/h/f 同步回节点对象(用于演示显示)
|
|
763
|
+
const startNodeObj = this.gridData && this.gridData[start[1]] ? this.gridData[start[1]][start[0]] : null;
|
|
764
|
+
if (startNodeObj) {
|
|
765
|
+
const g = this.gScore[startIndex];
|
|
766
|
+
const f = this.fScore[startIndex];
|
|
767
|
+
startNodeObj.g = g;
|
|
768
|
+
startNodeObj.f = f;
|
|
769
|
+
startNodeObj.h = f - g;
|
|
770
|
+
startNodeObj.parent = null;
|
|
771
|
+
}
|
|
772
|
+
this.grid.set(start, 'type', 'open');
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
openHeap.push(startIndex);
|
|
776
|
+
this.inOpenList[startIndex] = 1;
|
|
777
|
+
|
|
778
|
+
const directions = rightAngle ?
|
|
779
|
+
[[-1, 0], [1, 0], [0, -1], [0, 1]] :
|
|
780
|
+
[[-1, 0], [1, 0], [0, -1], [0, 1], [-1, -1], [-1, 1], [1, -1], [1, 1]];
|
|
781
|
+
|
|
782
|
+
while (!openHeap.isEmpty()) {
|
|
783
|
+
// 从堆中取出f值最小的节点
|
|
784
|
+
const currentIndex = openHeap.pop();
|
|
785
|
+
this.inOpenList[currentIndex] = 0;
|
|
786
|
+
this.closedList[currentIndex] = 1;
|
|
787
|
+
|
|
788
|
+
const currentX = currentIndex % this.width;
|
|
789
|
+
const currentY = Math.floor(currentIndex / this.width);
|
|
790
|
+
|
|
791
|
+
// 触发 render 回调:高亮当前检查的节点
|
|
792
|
+
if (hasRender && this.grid.set) {
|
|
793
|
+
// 同步当前节点的 g/h/f(用于演示显示)
|
|
794
|
+
const curNodeObj = this.gridData && this.gridData[currentY] ? this.gridData[currentY][currentX] : null;
|
|
795
|
+
if (curNodeObj) {
|
|
796
|
+
const g = this.gScore[currentIndex];
|
|
797
|
+
const f = this.fScore[currentIndex];
|
|
798
|
+
curNodeObj.g = g;
|
|
799
|
+
curNodeObj.f = f;
|
|
800
|
+
curNodeObj.h = f - g;
|
|
801
|
+
const p = this.parent[currentIndex];
|
|
802
|
+
if (p === -1) {
|
|
803
|
+
curNodeObj.parent = null;
|
|
804
|
+
} else {
|
|
805
|
+
curNodeObj.parent = [p % this.width, Math.floor(p / this.width)];
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
this.grid.set([currentX, currentY], 'type', 'highlight');
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
if (currentIndex === endIndex) {
|
|
812
|
+
return this.reconstructPathFromIndex(currentIndex, start, end);
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
// 触发 render 回调:当前节点加入关闭列表
|
|
816
|
+
if (hasRender && this.grid.set) {
|
|
817
|
+
// 关闭节点前也同步一遍(用于演示显示)
|
|
818
|
+
const curNodeObj = this.gridData && this.gridData[currentY] ? this.gridData[currentY][currentX] : null;
|
|
819
|
+
if (curNodeObj) {
|
|
820
|
+
const g = this.gScore[currentIndex];
|
|
821
|
+
const f = this.fScore[currentIndex];
|
|
822
|
+
curNodeObj.g = g;
|
|
823
|
+
curNodeObj.f = f;
|
|
824
|
+
curNodeObj.h = f - g;
|
|
825
|
+
const p = this.parent[currentIndex];
|
|
826
|
+
if (p === -1) {
|
|
827
|
+
curNodeObj.parent = null;
|
|
828
|
+
} else {
|
|
829
|
+
curNodeObj.parent = [p % this.width, Math.floor(p / this.width)];
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
this.grid.set([currentX, currentY], 'type', 'close');
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
// 检查邻居
|
|
836
|
+
for (const [dx, dy] of directions) {
|
|
837
|
+
const neighborX = currentX + dx;
|
|
838
|
+
const neighborY = currentY + dy;
|
|
839
|
+
|
|
840
|
+
if (neighborX < 0 || neighborX >= this.width ||
|
|
841
|
+
neighborY < 0 || neighborY >= this.height) continue;
|
|
842
|
+
|
|
843
|
+
const neighborIndex = neighborY * this.width + neighborX;
|
|
844
|
+
|
|
845
|
+
// 使用Grid的isWalkableAt方法检查障碍物
|
|
846
|
+
if (this.closedList[neighborIndex] ||
|
|
847
|
+
!this.grid.isWalkableAt(neighborX, neighborY)) continue;
|
|
848
|
+
|
|
849
|
+
const moveCost = optimalResult && (dx !== 0 && dy !== 0) ? 14 : 10;
|
|
850
|
+
const tentativeG = this.gScore[currentIndex] + moveCost;
|
|
851
|
+
|
|
852
|
+
if (tentativeG < this.gScore[neighborIndex]) {
|
|
853
|
+
this.parent[neighborIndex] = currentIndex;
|
|
854
|
+
this.gScore[neighborIndex] = tentativeG;
|
|
855
|
+
this.fScore[neighborIndex] = tentativeG +
|
|
856
|
+
this.heuristicDistance(neighborX, neighborY, end[0], end[1], rightAngle, optimalResult);
|
|
857
|
+
|
|
858
|
+
// 添加到开放列表(如果不存在)
|
|
859
|
+
if (!this.inOpenList[neighborIndex]) {
|
|
860
|
+
// 触发 render 回调:新节点加入开放列表
|
|
861
|
+
if (hasRender && this.grid.set) {
|
|
862
|
+
// 同步邻居节点的 g/h/f + parent(用于演示显示)
|
|
863
|
+
const nObj = this.gridData && this.gridData[neighborY] ? this.gridData[neighborY][neighborX] : null;
|
|
864
|
+
if (nObj) {
|
|
865
|
+
const g = this.gScore[neighborIndex];
|
|
866
|
+
const f = this.fScore[neighborIndex];
|
|
867
|
+
nObj.g = g;
|
|
868
|
+
nObj.f = f;
|
|
869
|
+
nObj.h = f - g;
|
|
870
|
+
nObj.parent = [currentX, currentY];
|
|
871
|
+
}
|
|
872
|
+
this.grid.set([neighborX, neighborY], 'type', 'open');
|
|
873
|
+
}
|
|
874
|
+
openHeap.push(neighborIndex);
|
|
875
|
+
this.inOpenList[neighborIndex] = 1;
|
|
876
|
+
} else {
|
|
877
|
+
// 触发 render 回调:更新节点
|
|
878
|
+
if (hasRender && this.grid.set) {
|
|
879
|
+
// 同步邻居节点的 g/h/f + parent(用于演示显示)
|
|
880
|
+
const nObj = this.gridData && this.gridData[neighborY] ? this.gridData[neighborY][neighborX] : null;
|
|
881
|
+
if (nObj) {
|
|
882
|
+
const g = this.gScore[neighborIndex];
|
|
883
|
+
const f = this.fScore[neighborIndex];
|
|
884
|
+
nObj.g = g;
|
|
885
|
+
nObj.f = f;
|
|
886
|
+
nObj.h = f - g;
|
|
887
|
+
nObj.parent = [currentX, currentY];
|
|
888
|
+
}
|
|
889
|
+
this.grid.set([neighborX, neighborY], 'type', 'update');
|
|
890
|
+
}
|
|
891
|
+
// 如果已在开放列表中,更新堆位置
|
|
892
|
+
openHeap.update(neighborIndex);
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
return null;
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
reconstructPathFromIndex(endIndex, start, end) {
|
|
902
|
+
if (this.useMemoryEfficientMode) {
|
|
903
|
+
return this.memoryEfficientImpl.reconstructPath(endIndex, start);
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
const path = [];
|
|
907
|
+
let currentIndex = endIndex;
|
|
908
|
+
|
|
909
|
+
while (currentIndex !== -1) {
|
|
910
|
+
const x = currentIndex % this.width;
|
|
911
|
+
const y = Math.floor(currentIndex / this.width);
|
|
912
|
+
path.push([x, y]);
|
|
913
|
+
currentIndex = this.parent[currentIndex];
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
return path.reverse();
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
heuristicDistance(x1, y1, x2, y2, rightAngle, optimalResult) {
|
|
920
|
+
const dx = Math.abs(x1 - x2);
|
|
921
|
+
const dy = Math.abs(y1 - y2);
|
|
922
|
+
if (rightAngle) return (dx + dy) * 10;
|
|
923
|
+
const maxD = Math.max(dx, dy);
|
|
924
|
+
const minD = dx + dy - maxD;
|
|
925
|
+
if (!optimalResult) return maxD * 10;
|
|
926
|
+
return 14 * minD + 10 * (maxD - minD);
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
// 兼容性方法
|
|
930
|
+
getBackPath(xy) {
|
|
931
|
+
if (this.useMemoryEfficientMode) {
|
|
932
|
+
return this.memoryEfficientImpl.getBackPath(xy);
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
const index = xy[1] * this.width + xy[0];
|
|
936
|
+
return this.reconstructPathFromIndex(index, [0, 0], xy);
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
h(start, end) {
|
|
940
|
+
return this.heuristicDistance(start[0], start[1], end[0], end[1], false, true);
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
// 获取内存使用情况
|
|
944
|
+
getMemoryUsage() {
|
|
945
|
+
if (this.useMemoryEfficientMode) {
|
|
946
|
+
return this.memoryEfficientImpl.getMemoryUsage();
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
return {
|
|
950
|
+
closedList: this.closedList.length,
|
|
951
|
+
inOpenList: this.inOpenList.length,
|
|
952
|
+
gScore: this.gScore.length * 4,
|
|
953
|
+
fScore: this.fScore.length * 4,
|
|
954
|
+
parent: this.parent.length * 4,
|
|
955
|
+
total: (this.gScore.length + this.fScore.length + this.parent.length) * 4 +
|
|
956
|
+
this.closedList.length + this.inOpenList.length
|
|
957
|
+
};
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
// WebAssembly A*实现(兼容内存高效Grid)+ 预热优化
|
|
962
|
+
class WasmAstar {
|
|
963
|
+
constructor(grid) {
|
|
964
|
+
this.grid = grid;
|
|
965
|
+
this.wasmModule = null;
|
|
966
|
+
this.wasmGrid = null;
|
|
967
|
+
this.initialized = false;
|
|
968
|
+
this.fallback = new UltraAstar(grid);
|
|
969
|
+
|
|
970
|
+
// 兼容新旧Grid接口
|
|
971
|
+
this.useMemoryEfficientGrid = grid.useMemoryEfficientMode || false;
|
|
972
|
+
|
|
973
|
+
// 🚀 WebAssembly 优化配置
|
|
974
|
+
this.initStartTime = getPerformanceNow();
|
|
975
|
+
this.syncStrategy = 'auto'; // auto, batch, coords, individual
|
|
976
|
+
this.enablePrewarm = true; // 预热搜索
|
|
977
|
+
|
|
978
|
+
// 异步初始化WebAssembly
|
|
979
|
+
this.initPromise = this.initWasm();
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
async initWasm() {
|
|
983
|
+
try {
|
|
984
|
+
if (typeof WebAssembly === 'undefined') {
|
|
985
|
+
return false;
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
// 动态导入Rust生成的WebAssembly模块
|
|
989
|
+
let wasmModule;
|
|
990
|
+
|
|
991
|
+
if (isNode) {
|
|
992
|
+
// Node.js环境 - 简化的WebAssembly加载
|
|
993
|
+
try {
|
|
994
|
+
// 直接导入根目录的 wasm.js
|
|
995
|
+
wasmModule = await import('./wasm.js');
|
|
996
|
+
|
|
997
|
+
// 检查是否需要初始化
|
|
998
|
+
if (wasmModule.default && typeof wasmModule.default === 'function') {
|
|
999
|
+
// 在Node.js中,需要手动加载wasm文件
|
|
1000
|
+
const { readFileSync } = await import('fs');
|
|
1001
|
+
const { fileURLToPath } = await import('url');
|
|
1002
|
+
const { dirname, join } = await import('path');
|
|
1003
|
+
|
|
1004
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
1005
|
+
const __dirname = dirname(__filename);
|
|
1006
|
+
const wasmPath = join(__dirname, 'astar.wasm');
|
|
1007
|
+
|
|
1008
|
+
const wasmBytes = readFileSync(wasmPath);
|
|
1009
|
+
// 使用同步初始化API
|
|
1010
|
+
wasmModule.initSync({ module: wasmBytes });
|
|
1011
|
+
}
|
|
1012
|
+
} catch (e) {
|
|
1013
|
+
return false;
|
|
1014
|
+
}
|
|
1015
|
+
} else if (isBrowser) {
|
|
1016
|
+
// 浏览器环境 - 使用动态import
|
|
1017
|
+
try {
|
|
1018
|
+
wasmModule = await import('./wasm.js');
|
|
1019
|
+
|
|
1020
|
+
// 检查是否需要初始化 - 使用新的ES模块初始化方式
|
|
1021
|
+
if (wasmModule.default && typeof wasmModule.default === 'function') {
|
|
1022
|
+
// 浏览器中使用默认初始化
|
|
1023
|
+
await wasmModule.default();
|
|
1024
|
+
}
|
|
1025
|
+
} catch (e) {
|
|
1026
|
+
// 静默失败,不显示警告
|
|
1027
|
+
return false;
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
if (wasmModule && wasmModule.AstarGrid) {
|
|
1032
|
+
// 🎯 根据地图大小选择合适的 WASM 实现
|
|
1033
|
+
const totalCells = this.grid.col * this.grid.row;
|
|
1034
|
+
const usesSparseImpl = totalCells > 1_000_000_000; // 超过 10 亿节点使用稀疏实现
|
|
1035
|
+
|
|
1036
|
+
if (usesSparseImpl && wasmModule.AstarGridSparse) {
|
|
1037
|
+
// 超大地图 (>10亿节点) 使用稀疏 HashMap 实现
|
|
1038
|
+
console.log(`🌐 超大地图 (${this.grid.col}x${this.grid.row}) 使用稀疏 WASM 实现`);
|
|
1039
|
+
this.wasmGrid = new wasmModule.AstarGridSparse(BigInt(this.grid.col), BigInt(this.grid.row));
|
|
1040
|
+
this.useSparseImpl = true;
|
|
1041
|
+
} else {
|
|
1042
|
+
// 普通地图使用固定数组实现
|
|
1043
|
+
this.wasmGrid = new wasmModule.AstarGrid(this.grid.col, this.grid.row);
|
|
1044
|
+
this.useSparseImpl = false;
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
// 超智能障碍物同步 - 针对超大地图优化
|
|
1048
|
+
const isLargeGrid = totalCells > 10000000; // 大于1000万节点
|
|
1049
|
+
const isVeryLargeGrid = totalCells > 100000000; // 大于1亿节点
|
|
1050
|
+
|
|
1051
|
+
let shouldSyncObstacles = true;
|
|
1052
|
+
let obstacleCount = 0;
|
|
1053
|
+
let obstacleDensity = 0;
|
|
1054
|
+
|
|
1055
|
+
// 快速障碍物密度评估
|
|
1056
|
+
if (isLargeGrid) {
|
|
1057
|
+
if (this.useMemoryEfficientGrid) {
|
|
1058
|
+
// 内存高效模式:采样统计
|
|
1059
|
+
const sampleSize = isVeryLargeGrid ? 1000 : 10000; // 超大地图用更少采样
|
|
1060
|
+
const step = Math.max(1, Math.floor(Math.sqrt(totalCells / sampleSize)));
|
|
1061
|
+
|
|
1062
|
+
for (let y = 0; y < this.grid.row; y += step) {
|
|
1063
|
+
for (let x = 0; x < this.grid.col; x += step) {
|
|
1064
|
+
if (!this.grid.isWalkableAt(x, y)) {
|
|
1065
|
+
obstacleCount++;
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
// 估算总障碍物数量
|
|
1070
|
+
obstacleCount *= (step * step);
|
|
1071
|
+
} else {
|
|
1072
|
+
// 传统模式:使用Grid统计(如果可用)
|
|
1073
|
+
const stats = this.grid.getStats ? this.grid.getStats() : null;
|
|
1074
|
+
if (stats) {
|
|
1075
|
+
obstacleCount = stats.obstacles;
|
|
1076
|
+
} else {
|
|
1077
|
+
// 快速采样
|
|
1078
|
+
const sampleSize = Math.min(10000, totalCells / 100);
|
|
1079
|
+
for (let i = 0; i < sampleSize; i++) {
|
|
1080
|
+
const x = Math.floor(Math.random() * this.grid.col);
|
|
1081
|
+
const y = Math.floor(Math.random() * this.grid.row);
|
|
1082
|
+
if (this.grid.grid[y][x].value >= 1) {
|
|
1083
|
+
obstacleCount++;
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
obstacleCount = (obstacleCount / sampleSize) * totalCells;
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
obstacleDensity = obstacleCount / totalCells;
|
|
1091
|
+
|
|
1092
|
+
// 超大地图且障碍物密度极低时跳过同步
|
|
1093
|
+
if (isVeryLargeGrid && obstacleDensity < 0.0001) {
|
|
1094
|
+
shouldSyncObstacles = false;
|
|
1095
|
+
console.warn(`⚠️ 超大地图 (${this.grid.col}x${this.grid.row}) 障碍物密度极低 (${(obstacleDensity*100).toFixed(4)}%),跳过障碍物同步以提升性能`);
|
|
1096
|
+
}
|
|
1097
|
+
// 大地图且障碍物密度很低时也跳过
|
|
1098
|
+
else if (isLargeGrid && obstacleDensity < 0.001) {
|
|
1099
|
+
shouldSyncObstacles = false;
|
|
1100
|
+
console.warn(`⚠️ 大地图 (${this.grid.col}x${this.grid.row}) 障碍物密度很低 (${(obstacleDensity*100).toFixed(3)}%),跳过障碍物同步以提升性能`);
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
// 🚀 同步障碍物数据 - 使用最优化的批量方法
|
|
1105
|
+
if (shouldSyncObstacles) {
|
|
1106
|
+
console.log(`🔄 WebAssembly 开始同步障碍物...`);
|
|
1107
|
+
|
|
1108
|
+
// 🎯 优化策略1:优先使用批量坐标方法(最高效)
|
|
1109
|
+
if (this.wasmGrid.setObstaclesFromCoords) {
|
|
1110
|
+
const obstacleCoords = [];
|
|
1111
|
+
const maxObstacles = isVeryLargeGrid ? 1000000 : 10000000; // 限制最大障碍物数量
|
|
1112
|
+
|
|
1113
|
+
if (this.useMemoryEfficientGrid) {
|
|
1114
|
+
// 内存高效模式:批量收集障碍物坐标
|
|
1115
|
+
let count = 0;
|
|
1116
|
+
for (let y = 0; y < this.grid.row && count < maxObstacles; y++) {
|
|
1117
|
+
for (let x = 0; x < this.grid.col && count < maxObstacles; x++) {
|
|
1118
|
+
if (!this.grid.isWalkableAt(x, y)) {
|
|
1119
|
+
obstacleCoords.push(x, y);
|
|
1120
|
+
count++;
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
} else {
|
|
1125
|
+
// 传统模式:批量收集障碍物坐标
|
|
1126
|
+
let count = 0;
|
|
1127
|
+
for (let y = 0; y < this.grid.row && count < maxObstacles; y++) {
|
|
1128
|
+
for (let x = 0; x < this.grid.col && count < maxObstacles; x++) {
|
|
1129
|
+
if (this.grid.grid[y][x].value >= 1) {
|
|
1130
|
+
obstacleCoords.push(x, y);
|
|
1131
|
+
count++;
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
// 🎯 单次批量设置 - 大幅减少跨边界调用
|
|
1138
|
+
if (obstacleCoords.length > 0) {
|
|
1139
|
+
this.wasmGrid.setObstaclesFromCoords(new Int32Array(obstacleCoords));
|
|
1140
|
+
console.log(`✅ WebAssembly 批量同步 ${obstacleCoords.length/2} 个障碍物`);
|
|
1141
|
+
} else {
|
|
1142
|
+
console.log(`✅ WebAssembly 无障碍物需要同步`);
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
// 🚀 优化策略2:使用位图批量传输(如果可用)
|
|
1146
|
+
else if (this.wasmGrid.setObstaclesBatch && this.useMemoryEfficientGrid && this.grid.getObstacleBitmap) {
|
|
1147
|
+
const bitmap = this.grid.getObstacleBitmap();
|
|
1148
|
+
this.wasmGrid.setObstaclesBatch(bitmap);
|
|
1149
|
+
console.log(`✅ WebAssembly 位图同步 ${bitmap.length} 字节`);
|
|
1150
|
+
}
|
|
1151
|
+
// 🚀 优化策略3:创建位图并批量传输
|
|
1152
|
+
else if (this.wasmGrid.setObstaclesBatch) {
|
|
1153
|
+
const totalCells = this.grid.col * this.grid.row;
|
|
1154
|
+
const bitmapSize = Math.ceil(totalCells / 8);
|
|
1155
|
+
const bitmap = new Uint8Array(bitmapSize);
|
|
1156
|
+
|
|
1157
|
+
// 构建位图
|
|
1158
|
+
if (this.useMemoryEfficientGrid) {
|
|
1159
|
+
for (let y = 0; y < this.grid.row; y++) {
|
|
1160
|
+
for (let x = 0; x < this.grid.col; x++) {
|
|
1161
|
+
if (!this.grid.isWalkableAt(x, y)) {
|
|
1162
|
+
const index = y * this.grid.col + x;
|
|
1163
|
+
const byteIndex = Math.floor(index / 8);
|
|
1164
|
+
const bitIndex = index % 8;
|
|
1165
|
+
bitmap[byteIndex] |= (1 << bitIndex);
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
} else {
|
|
1170
|
+
for (let y = 0; y < this.grid.row; y++) {
|
|
1171
|
+
for (let x = 0; x < this.grid.col; x++) {
|
|
1172
|
+
if (this.grid.grid[y][x].value >= 1) {
|
|
1173
|
+
const index = y * this.grid.col + x;
|
|
1174
|
+
const byteIndex = Math.floor(index / 8);
|
|
1175
|
+
const bitIndex = index % 8;
|
|
1176
|
+
bitmap[byteIndex] |= (1 << bitIndex);
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
this.wasmGrid.setObstaclesBatch(bitmap);
|
|
1183
|
+
console.log(`✅ WebAssembly 自建位图同步 ${bitmap.length} 字节`);
|
|
1184
|
+
}
|
|
1185
|
+
// 🐌 回退策略:逐个设置(仅对小地图)
|
|
1186
|
+
else if (!isLargeGrid) {
|
|
1187
|
+
console.warn(`⚠️ WebAssembly 使用低效的逐个同步模式`);
|
|
1188
|
+
let obstacleCount = 0;
|
|
1189
|
+
if (this.useMemoryEfficientGrid) {
|
|
1190
|
+
for (let y = 0; y < this.grid.row; y++) {
|
|
1191
|
+
for (let x = 0; x < this.grid.col; x++) {
|
|
1192
|
+
const isObstacle = !this.grid.isWalkableAt(x, y);
|
|
1193
|
+
if (isObstacle) {
|
|
1194
|
+
this.wasmGrid.setObstacle(x, y, isObstacle);
|
|
1195
|
+
obstacleCount++;
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
} else {
|
|
1200
|
+
for (let y = 0; y < this.grid.row; y++) {
|
|
1201
|
+
for (let x = 0; x < this.grid.col; x++) {
|
|
1202
|
+
const isObstacle = this.grid.grid[y][x].value >= 1;
|
|
1203
|
+
if (isObstacle) {
|
|
1204
|
+
this.wasmGrid.setObstacle(x, y, isObstacle);
|
|
1205
|
+
obstacleCount++;
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
console.log(`✅ WebAssembly 逐个同步 ${obstacleCount} 个障碍物`);
|
|
1211
|
+
}
|
|
1212
|
+
} else {
|
|
1213
|
+
console.log(`⚡ WebAssembly 跳过障碍物同步以提升性能`);
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
this.wasmModule = wasmModule;
|
|
1217
|
+
this.initialized = true;
|
|
1218
|
+
|
|
1219
|
+
// 🚀 WebAssembly 预热 - 执行小规模搜索以优化JIT
|
|
1220
|
+
if (this.enablePrewarm && this.wasmGrid) {
|
|
1221
|
+
try {
|
|
1222
|
+
// 预热搜索:小范围路径查找
|
|
1223
|
+
const prewarmSize = Math.min(10, Math.floor(this.grid.col / 10), Math.floor(this.grid.row / 10));
|
|
1224
|
+
if (prewarmSize >= 2) {
|
|
1225
|
+
this.wasmGrid.findPath(0, 0, prewarmSize, prewarmSize);
|
|
1226
|
+
console.log(`🔥 WebAssembly 预热完成 (${prewarmSize}x${prewarmSize})`);
|
|
1227
|
+
}
|
|
1228
|
+
} catch (prewarmError) {
|
|
1229
|
+
// 预热失败不影响正常使用
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
const initTime = getPerformanceNow() - this.initStartTime;
|
|
1234
|
+
console.log(`✅ WebAssembly 初始化成功 (${initTime.toFixed(1)}ms)`);
|
|
1235
|
+
|
|
1236
|
+
return true;
|
|
1237
|
+
} else {
|
|
1238
|
+
return false;
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
} catch (error) {
|
|
1242
|
+
// 🔍 详细的错误分析和处理
|
|
1243
|
+
const initTime = getPerformanceNow() - this.initStartTime;
|
|
1244
|
+
|
|
1245
|
+
if (error.message && error.message.includes('fetch')) {
|
|
1246
|
+
console.warn(`⚠️ WebAssembly 网络加载失败 (${initTime.toFixed(1)}ms): ${error.message}`);
|
|
1247
|
+
} else if (error.message && error.message.includes('ENOENT')) {
|
|
1248
|
+
console.warn(`⚠️ WebAssembly 文件未找到 (${initTime.toFixed(1)}ms): 请检查 wasm 文件路径`);
|
|
1249
|
+
} else if (error.message && error.message.includes('WebAssembly')) {
|
|
1250
|
+
console.warn(`⚠️ WebAssembly 不支持 (${initTime.toFixed(1)}ms): 环境不兼容`);
|
|
1251
|
+
} else {
|
|
1252
|
+
// 静默处理其他错误,避免干扰用户
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
return false;
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
async search(start, end, options = {}) {
|
|
1260
|
+
// 等待初始化完成
|
|
1261
|
+
await this.initPromise;
|
|
1262
|
+
|
|
1263
|
+
if (this.initialized && this.wasmGrid) {
|
|
1264
|
+
try {
|
|
1265
|
+
// 🚀 使用Rust WebAssembly实现
|
|
1266
|
+
const optimalResult = options.optimalResult !== false; // default true
|
|
1267
|
+
const searchStart = getPerformanceNow();
|
|
1268
|
+
|
|
1269
|
+
let pathData;
|
|
1270
|
+
if (this.useSparseImpl) {
|
|
1271
|
+
// 稀疏实现使用 BigInt 参数
|
|
1272
|
+
pathData = this.wasmGrid.findPath(
|
|
1273
|
+
BigInt(start[0]), BigInt(start[1]),
|
|
1274
|
+
BigInt(end[0]), BigInt(end[1]),
|
|
1275
|
+
optimalResult
|
|
1276
|
+
);
|
|
1277
|
+
} else {
|
|
1278
|
+
// 固定数组实现使用普通整数
|
|
1279
|
+
pathData = this.wasmGrid.findPath(start[0], start[1], end[0], end[1], optimalResult);
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
const searchTime = getPerformanceNow() - searchStart;
|
|
1283
|
+
|
|
1284
|
+
if (pathData && pathData.length > 0) {
|
|
1285
|
+
// 将Int32Array转换为路径数组
|
|
1286
|
+
const path = [];
|
|
1287
|
+
for (let i = 0; i < pathData.length; i += 2) {
|
|
1288
|
+
path.push([pathData[i], pathData[i + 1]]);
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
// 性能监控
|
|
1292
|
+
if (searchTime > 100) { // 超过100ms记录
|
|
1293
|
+
console.log(`🐌 WebAssembly 搜索较慢: ${searchTime.toFixed(1)}ms (${path.length} 步)`);
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
return path;
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
return null;
|
|
1300
|
+
|
|
1301
|
+
} catch (error) {
|
|
1302
|
+
// WebAssembly 搜索失败,降级到 fallback
|
|
1303
|
+
console.warn(`⚠️ WebAssembly 搜索失败,降级到 Ultra A*: ${error.message}`);
|
|
1304
|
+
return this.fallback.search(start, end, options);
|
|
1305
|
+
}
|
|
1306
|
+
} else {
|
|
1307
|
+
// 使用fallback实现
|
|
1308
|
+
return this.fallback.search(start, end, options);
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
// 兼容性方法
|
|
1313
|
+
getBackPath(xy) {
|
|
1314
|
+
if (this.initialized) {
|
|
1315
|
+
// WebAssembly版本暂不支持此方法,使用fallback
|
|
1316
|
+
return this.fallback.getBackPath(xy);
|
|
1317
|
+
}
|
|
1318
|
+
return this.fallback.getBackPath(xy);
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
h(start, end) {
|
|
1322
|
+
return this.fallback.h(start, end);
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
// 内存使用统计
|
|
1326
|
+
getMemoryUsage() {
|
|
1327
|
+
if (this.initialized && this.wasmGrid) {
|
|
1328
|
+
return {
|
|
1329
|
+
wasmGrid: 1024, // 估算WASM内存使用
|
|
1330
|
+
fallback: 0,
|
|
1331
|
+
total: 1024,
|
|
1332
|
+
mode: 'WebAssembly with ' + (this.useMemoryEfficientGrid ? 'Memory Efficient Grid' : 'Standard Grid')
|
|
1333
|
+
};
|
|
1334
|
+
} else {
|
|
1335
|
+
const fallbackMemory = this.fallback.getMemoryUsage ? this.fallback.getMemoryUsage() : { total: 0 };
|
|
1336
|
+
return {
|
|
1337
|
+
wasmGrid: 0,
|
|
1338
|
+
fallback: fallbackMemory.total,
|
|
1339
|
+
total: fallbackMemory.total,
|
|
1340
|
+
mode: 'Fallback to Ultra A*'
|
|
1341
|
+
};
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1345
|
+
// 获取当前实现状态
|
|
1346
|
+
getCurrentImplementationStatus() {
|
|
1347
|
+
if (this.initialized && this.wasmGrid) {
|
|
1348
|
+
return {
|
|
1349
|
+
name: 'WebAssembly A*',
|
|
1350
|
+
usingWasm: true,
|
|
1351
|
+
fallbackUsed: false
|
|
1352
|
+
};
|
|
1353
|
+
} else {
|
|
1354
|
+
return {
|
|
1355
|
+
name: 'Ultra A* (WebAssembly Fallback)',
|
|
1356
|
+
usingWasm: false,
|
|
1357
|
+
fallbackUsed: true,
|
|
1358
|
+
initStatus: this.initialized,
|
|
1359
|
+
wasmGridStatus: !!this.wasmGrid
|
|
1360
|
+
};
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
destroy() {
|
|
1365
|
+
if (this.initialized && this.wasmGrid) {
|
|
1366
|
+
this.wasmGrid.free();
|
|
1367
|
+
this.wasmGrid = null;
|
|
1368
|
+
this.initialized = false;
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1373
|
+
// 环境分析器
|
|
1374
|
+
class EnvironmentAnalyzer {
|
|
1375
|
+
static analyze() {
|
|
1376
|
+
return {
|
|
1377
|
+
platform: this.detectPlatform(),
|
|
1378
|
+
memory: this.estimateMemory(),
|
|
1379
|
+
cores: this.getCPUCores(),
|
|
1380
|
+
jsEngine: this.detectJSEngine(),
|
|
1381
|
+
webAssemblySupport: typeof WebAssembly !== 'undefined',
|
|
1382
|
+
performanceAPI: typeof performance !== 'undefined',
|
|
1383
|
+
isNode,
|
|
1384
|
+
isBrowser
|
|
1385
|
+
};
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
static detectPlatform() {
|
|
1389
|
+
if (isNode) {
|
|
1390
|
+
return `Node.js ${process.platform}`;
|
|
1391
|
+
} else if (isBrowser) {
|
|
1392
|
+
return `Browser ${navigator.platform || 'Unknown'}`;
|
|
1393
|
+
} else {
|
|
1394
|
+
return 'Unknown';
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
static estimateMemory() {
|
|
1399
|
+
if (isBrowser && performance.memory) {
|
|
1400
|
+
return Math.floor(performance.memory.jsHeapSizeLimit / 1024 / 1024);
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
if (isNode && process.memoryUsage) {
|
|
1404
|
+
const usage = process.memoryUsage();
|
|
1405
|
+
return Math.floor((usage.heapTotal + usage.external) / 1024 / 1024);
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
return 2048; // 默认2GB
|
|
1409
|
+
}
|
|
1410
|
+
|
|
1411
|
+
static getCPUCores() {
|
|
1412
|
+
if (isBrowser && navigator.hardwareConcurrency) {
|
|
1413
|
+
return navigator.hardwareConcurrency;
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1416
|
+
if (isNode) {
|
|
1417
|
+
try {
|
|
1418
|
+
// 在 ES Module 中,需要使用动态导入,但这里我们使用同步方式
|
|
1419
|
+
// 为了避免 async/await 问题,我们返回默认值
|
|
1420
|
+
return 4; // 默认值,实际值需要异步获取
|
|
1421
|
+
} catch (e) {
|
|
1422
|
+
// 忽略错误
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
|
|
1426
|
+
return 4; // 默认4核
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1429
|
+
static detectJSEngine() {
|
|
1430
|
+
if (isNode && process.versions && process.versions.v8) {
|
|
1431
|
+
return `V8 ${process.versions.v8}`;
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1434
|
+
if (isBrowser && navigator.userAgent) {
|
|
1435
|
+
const userAgent = navigator.userAgent;
|
|
1436
|
+
if (userAgent.includes('Chrome')) return 'V8 (Chrome)';
|
|
1437
|
+
if (userAgent.includes('Firefox')) return 'SpiderMonkey (Firefox)';
|
|
1438
|
+
if (userAgent.includes('Safari')) return 'JavaScriptCore (Safari)';
|
|
1439
|
+
if (userAgent.includes('Edge')) return 'Chakra (Edge)';
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1442
|
+
return 'Unknown';
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
|
|
1446
|
+
// 智能A*算法 - 主类(移除 WebAssembly 支持,专注 JavaScript 性能)
|
|
1447
|
+
class SmartAstar {
|
|
1448
|
+
constructor(grid, options = {}) {
|
|
1449
|
+
this.grid = grid;
|
|
1450
|
+
this.gridSize = grid.col * grid.row;
|
|
1451
|
+
this.options = this.normalizeOptions(options);
|
|
1452
|
+
|
|
1453
|
+
// 环境分析
|
|
1454
|
+
this.environment = EnvironmentAnalyzer.analyze();
|
|
1455
|
+
|
|
1456
|
+
// 性能历史
|
|
1457
|
+
this.performanceHistory = new Map();
|
|
1458
|
+
|
|
1459
|
+
// 对于超大地图,自动构建四叉树
|
|
1460
|
+
if (this.grid.useQuadTree && !this.grid.quadTreeBuilt) {
|
|
1461
|
+
// 延迟构建,避免阻塞初始化
|
|
1462
|
+
if (this.options.buildQuadTree !== false) {
|
|
1463
|
+
setTimeout(() => {
|
|
1464
|
+
this.grid.buildQuadTree();
|
|
1465
|
+
}, 0);
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1469
|
+
// 选择最优实现
|
|
1470
|
+
this.selectOptimalImplementation();
|
|
1471
|
+
|
|
1472
|
+
if (this.options.debug) {
|
|
1473
|
+
console.log(`🚀 Smart A* 初始化完成,选择: ${this.currentName}`);
|
|
1474
|
+
console.log(`📱 环境: ${this.environment.platform}`);
|
|
1475
|
+
console.log(`💾 内存: ${this.environment.memory}MB`);
|
|
1476
|
+
if (this.grid.useQuadTree) {
|
|
1477
|
+
console.log(`🌳 四叉树优化: ${this.grid.quadTreeBuilt ? '已构建' : '构建中...'}`);
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1481
|
+
|
|
1482
|
+
normalizeOptions(options) {
|
|
1483
|
+
return {
|
|
1484
|
+
strategy: options.strategy || 'auto',
|
|
1485
|
+
appType: options.appType || 'web',
|
|
1486
|
+
performance: options.performance || 'medium',
|
|
1487
|
+
memory: options.memory || 'moderate',
|
|
1488
|
+
debug: options.debug || false,
|
|
1489
|
+
prefer: options.prefer || null,
|
|
1490
|
+
avoid: options.avoid || null,
|
|
1491
|
+
...options
|
|
1492
|
+
};
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
selectOptimalImplementation() {
|
|
1496
|
+
const strategy = this.options.strategy;
|
|
1497
|
+
let selectedImpl = null;
|
|
1498
|
+
let selectedName = null;
|
|
1499
|
+
|
|
1500
|
+
if (strategy === 'auto') {
|
|
1501
|
+
// 基于最新性能测试数据优化的智能选择算法
|
|
1502
|
+
// 测试结果:WebAssembly 在所有地图尺寸上都是最快的,特别是超大地图优势明显
|
|
1503
|
+
const gridSize = this.gridSize;
|
|
1504
|
+
|
|
1505
|
+
if (gridSize <= 2500) {
|
|
1506
|
+
// 微型地图 (≤50x50) - Master A* 表现最佳
|
|
1507
|
+
selectedImpl = MasterAstar;
|
|
1508
|
+
selectedName = 'Master A*';
|
|
1509
|
+
} else if (gridSize <= 10000) {
|
|
1510
|
+
// 小地图 (50x50-100x100) - Master A* 最优
|
|
1511
|
+
selectedImpl = MasterAstar;
|
|
1512
|
+
selectedName = 'Master A*';
|
|
1513
|
+
} else if (gridSize <= 1000000) {
|
|
1514
|
+
// 中等地图 (100x100-1000x1000) - WebAssembly A* 最优
|
|
1515
|
+
// 测试:1000×1000 WebAssembly 9.6ms vs Ultra 167.7ms (快 17 倍)
|
|
1516
|
+
if (this.environment.webAssemblySupport && this.options.avoid !== 'wasm') {
|
|
1517
|
+
selectedImpl = WasmAstar;
|
|
1518
|
+
selectedName = 'WebAssembly A*';
|
|
1519
|
+
} else {
|
|
1520
|
+
selectedImpl = UltraAstar;
|
|
1521
|
+
selectedName = 'Ultra A*';
|
|
1522
|
+
}
|
|
1523
|
+
} else if (gridSize <= 100000000) {
|
|
1524
|
+
// 大地图 (1000x1000-10000x10000) - WebAssembly A* 最优
|
|
1525
|
+
// 测试:5000×5000 WebAssembly 1.0ms vs Ultra 25.7ms (快 25 倍)
|
|
1526
|
+
// 测试:10000×10000 WebAssembly 2.3ms vs Ultra 58.6ms (快 25 倍)
|
|
1527
|
+
if (this.environment.webAssemblySupport && this.options.avoid !== 'wasm') {
|
|
1528
|
+
selectedImpl = WasmAstar;
|
|
1529
|
+
selectedName = 'WebAssembly A*';
|
|
1530
|
+
} else {
|
|
1531
|
+
selectedImpl = UltraAstar;
|
|
1532
|
+
selectedName = 'Ultra A* (Memory Optimized)';
|
|
1533
|
+
}
|
|
1534
|
+
} else {
|
|
1535
|
+
// 超大地图 (≥10000x10000) - WebAssembly 稀疏实现最优
|
|
1536
|
+
// 测试:50000×50000 WebAssembly 20.6ms vs Ultra 369.4ms (快 18 倍)
|
|
1537
|
+
// 测试:100000×100000 WebAssembly 46.9ms vs Ultra 836.0ms (快 18 倍)
|
|
1538
|
+
if (this.environment.webAssemblySupport && this.options.avoid !== 'wasm') {
|
|
1539
|
+
selectedImpl = WasmAstar;
|
|
1540
|
+
selectedName = 'WebAssembly A* (Sparse)';
|
|
1541
|
+
} else {
|
|
1542
|
+
selectedImpl = UltraAstar;
|
|
1543
|
+
selectedName = 'Ultra A* (Memory Optimized)';
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1546
|
+
if (this.options.debug && gridSize > 100000000) {
|
|
1547
|
+
console.log(`🌐 超大地图 (${Math.sqrt(gridSize).toFixed(0)}x${Math.sqrt(gridSize).toFixed(0)}) 使用 WebAssembly 稀疏实现`);
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1550
|
+
} else if (strategy === 'performance') {
|
|
1551
|
+
// 性能优先
|
|
1552
|
+
if (this.environment.webAssemblySupport && this.options.avoid !== 'wasm') {
|
|
1553
|
+
selectedImpl = WasmAstar;
|
|
1554
|
+
selectedName = 'WebAssembly A*';
|
|
1555
|
+
} else {
|
|
1556
|
+
selectedImpl = MasterAstar;
|
|
1557
|
+
selectedName = 'Master A*';
|
|
1558
|
+
}
|
|
1559
|
+
} else if (strategy === 'memory') {
|
|
1560
|
+
// 内存优先
|
|
1561
|
+
selectedImpl = MasterAstar;
|
|
1562
|
+
selectedName = 'Master A*';
|
|
1563
|
+
} else if (strategy === 'compatibility') {
|
|
1564
|
+
// 兼容性优先
|
|
1565
|
+
selectedImpl = UltraAstar;
|
|
1566
|
+
selectedName = 'Ultra A*';
|
|
1567
|
+
} else {
|
|
1568
|
+
// 默认选择
|
|
1569
|
+
selectedImpl = UltraAstar;
|
|
1570
|
+
selectedName = 'Ultra A*';
|
|
1571
|
+
}
|
|
1572
|
+
|
|
1573
|
+
// 应用用户偏好
|
|
1574
|
+
if (this.options.prefer) {
|
|
1575
|
+
const preferMap = {
|
|
1576
|
+
'wasm': { impl: WasmAstar, name: 'WebAssembly A*' },
|
|
1577
|
+
'master': { impl: MasterAstar, name: 'Master A*' },
|
|
1578
|
+
'ultra': { impl: UltraAstar, name: 'Ultra A*' }
|
|
1579
|
+
};
|
|
1580
|
+
|
|
1581
|
+
if (preferMap[this.options.prefer]) {
|
|
1582
|
+
selectedImpl = preferMap[this.options.prefer].impl;
|
|
1583
|
+
selectedName = preferMap[this.options.prefer].name;
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1587
|
+
this.currentImplementation = new selectedImpl(this.grid);
|
|
1588
|
+
this.currentName = selectedName;
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1591
|
+
async search(start, end, options = {}) {
|
|
1592
|
+
// 输入验证(在调用实现之前)
|
|
1593
|
+
if (!Array.isArray(start) || start.length < 2 ||
|
|
1594
|
+
!Array.isArray(end) || end.length < 2) {
|
|
1595
|
+
throw new Error('起点和终点必须是包含至少2个元素的数组 [x, y]');
|
|
1596
|
+
}
|
|
1597
|
+
|
|
1598
|
+
const startX = Math.floor(start[0]);
|
|
1599
|
+
const startY = Math.floor(start[1]);
|
|
1600
|
+
const endX = Math.floor(end[0]);
|
|
1601
|
+
const endY = Math.floor(end[1]);
|
|
1602
|
+
|
|
1603
|
+
// 边界检查
|
|
1604
|
+
if (startX < 0 || startX >= this.grid.col || startY < 0 || startY >= this.grid.row) {
|
|
1605
|
+
throw new Error(`起点坐标 [${startX}, ${startY}] 超出网格范围 [0-${this.grid.col-1}, 0-${this.grid.row-1}]`);
|
|
1606
|
+
}
|
|
1607
|
+
|
|
1608
|
+
if (endX < 0 || endX >= this.grid.col || endY < 0 || endY >= this.grid.row) {
|
|
1609
|
+
throw new Error(`终点坐标 [${endX}, ${endY}] 超出网格范围 [0-${this.grid.col-1}, 0-${this.grid.row-1}]`);
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
const searchStart = this.getPerformanceNow();
|
|
1613
|
+
|
|
1614
|
+
try {
|
|
1615
|
+
let result;
|
|
1616
|
+
|
|
1617
|
+
if (this.currentImplementation.search.constructor.name === 'AsyncFunction') {
|
|
1618
|
+
result = await this.currentImplementation.search(start, end, options);
|
|
1619
|
+
} else {
|
|
1620
|
+
result = this.currentImplementation.search(start, end, options);
|
|
1621
|
+
}
|
|
1622
|
+
|
|
1623
|
+
const searchEnd = this.getPerformanceNow();
|
|
1624
|
+
const searchTime = searchEnd - searchStart;
|
|
1625
|
+
|
|
1626
|
+
// 记录性能
|
|
1627
|
+
this.recordPerformance(searchTime, result ? result.length : 0);
|
|
1628
|
+
|
|
1629
|
+
return result;
|
|
1630
|
+
|
|
1631
|
+
} catch (error) {
|
|
1632
|
+
if (this.options.debug) {
|
|
1633
|
+
console.error(`❌ ${this.currentName} 搜索失败:`, error.message);
|
|
1634
|
+
}
|
|
1635
|
+
throw error;
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
|
|
1639
|
+
getPerformanceNow() {
|
|
1640
|
+
return getPerformanceNow();
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1643
|
+
recordPerformance(searchTime, pathLength) {
|
|
1644
|
+
if (!this.performanceHistory.has(this.currentName)) {
|
|
1645
|
+
this.performanceHistory.set(this.currentName, []);
|
|
1646
|
+
}
|
|
1647
|
+
|
|
1648
|
+
const history = this.performanceHistory.get(this.currentName);
|
|
1649
|
+
history.push({
|
|
1650
|
+
time: searchTime,
|
|
1651
|
+
pathLength,
|
|
1652
|
+
timestamp: Date.now(),
|
|
1653
|
+
gridSize: this.gridSize
|
|
1654
|
+
});
|
|
1655
|
+
|
|
1656
|
+
// 保持最近50次记录
|
|
1657
|
+
if (history.length > 50) {
|
|
1658
|
+
history.shift();
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1661
|
+
|
|
1662
|
+
// 获取当前实现信息
|
|
1663
|
+
getCurrentImplementation() {
|
|
1664
|
+
return {
|
|
1665
|
+
name: this.currentName,
|
|
1666
|
+
gridSize: this.gridSize,
|
|
1667
|
+
environment: this.environment
|
|
1668
|
+
};
|
|
1669
|
+
}
|
|
1670
|
+
|
|
1671
|
+
// 获取性能统计
|
|
1672
|
+
getPerformanceStats() {
|
|
1673
|
+
const stats = {};
|
|
1674
|
+
|
|
1675
|
+
for (const [implName, history] of this.performanceHistory) {
|
|
1676
|
+
if (history.length > 0) {
|
|
1677
|
+
const times = history.map(record => record.time);
|
|
1678
|
+
stats[implName] = {
|
|
1679
|
+
count: history.length,
|
|
1680
|
+
avgTime: times.reduce((a, b) => a + b, 0) / times.length,
|
|
1681
|
+
minTime: Math.min(...times),
|
|
1682
|
+
maxTime: Math.max(...times)
|
|
1683
|
+
};
|
|
1684
|
+
}
|
|
1685
|
+
}
|
|
1686
|
+
|
|
1687
|
+
return stats;
|
|
1688
|
+
}
|
|
1689
|
+
|
|
1690
|
+
// 兼容性方法
|
|
1691
|
+
getBackPath(xy) {
|
|
1692
|
+
return this.currentImplementation ?
|
|
1693
|
+
this.currentImplementation.getBackPath(xy) : [];
|
|
1694
|
+
}
|
|
1695
|
+
|
|
1696
|
+
h(start, end) {
|
|
1697
|
+
return this.currentImplementation ?
|
|
1698
|
+
this.currentImplementation.h(start, end) : 0;
|
|
1699
|
+
}
|
|
1700
|
+
|
|
1701
|
+
destroy() {
|
|
1702
|
+
if (this.currentImplementation && this.currentImplementation.destroy) {
|
|
1703
|
+
this.currentImplementation.destroy();
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1706
|
+
}
|
|
1707
|
+
|
|
1708
|
+
// 导出SmartAstar作为默认的Astar类
|
|
1709
|
+
export default SmartAstar;
|