Pathfinding Improvements: Replace nextNodes array with PriorityQueueSet (#169)
This commit is contained in:
@@ -0,0 +1,82 @@
|
|||||||
|
/**
|
||||||
|
* A combination queue/set where the elements are ordered (in ascending order, according to the given priority function)
|
||||||
|
* and unique (according to the given elementMatcher).
|
||||||
|
*
|
||||||
|
* If an element is added to the set and an equivalent element already exists, the lower-priority one is discarded.
|
||||||
|
*/
|
||||||
|
export class PriorityQueueSet {
|
||||||
|
constructor(elementMatcher, priorityFunction) {
|
||||||
|
this.first = null;
|
||||||
|
this.elementMatcher = elementMatcher;
|
||||||
|
this.priorityFunction = priorityFunction;
|
||||||
|
}
|
||||||
|
|
||||||
|
pushWithPriority(value) {
|
||||||
|
const newNode = {value, priority: this.priorityFunction(value), next: null};
|
||||||
|
|
||||||
|
// If the queue is currently empty, we can just set this new node as the first and we're done
|
||||||
|
if (!this.first) {
|
||||||
|
this.first = newNode;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let inserted = false;
|
||||||
|
let previous;
|
||||||
|
let current = this.first;
|
||||||
|
|
||||||
|
// Loop through the existing elements
|
||||||
|
while (current) {
|
||||||
|
if (this.elementMatcher(current.value, value)) {
|
||||||
|
// We've found an equivalent element before one with a lower priority. This one has at least
|
||||||
|
// the same priority as the new one, so don't bother inserting
|
||||||
|
return;
|
||||||
|
} else if (newNode.priority <= current.priority) {
|
||||||
|
// We've found some element with lower priority than the new one, so insert the new one just before it
|
||||||
|
newNode.next = current;
|
||||||
|
if (previous) {
|
||||||
|
previous.next = newNode;
|
||||||
|
} else {
|
||||||
|
this.first = newNode;
|
||||||
|
}
|
||||||
|
inserted = true;
|
||||||
|
|
||||||
|
previous = current;
|
||||||
|
current = current.next
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
previous = current;
|
||||||
|
current = current.next;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inserted) {
|
||||||
|
// Go through the rest of the list and try to find an equivalent element to the new one.
|
||||||
|
// We know it has higher priority than the new one, so remove it.
|
||||||
|
while (current) {
|
||||||
|
if (this.elementMatcher(current.value, value)) {
|
||||||
|
if (previous) {
|
||||||
|
previous.next = current.next;
|
||||||
|
} else {
|
||||||
|
this.first = current.next
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
previous = current;
|
||||||
|
current = current.next;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// We reached the end of the queue without finding a lower-priority or existing element, so
|
||||||
|
// insert the new one at the end
|
||||||
|
previous.next = newNode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hasNext() {
|
||||||
|
return !!this.first;
|
||||||
|
}
|
||||||
|
|
||||||
|
pop() {
|
||||||
|
const first = this.first;
|
||||||
|
this.first = first?.next;
|
||||||
|
return first?.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
+25
-17
@@ -5,6 +5,7 @@ import {settingsKey} from "./settings.js";
|
|||||||
import {getSnapPointForTokenObj, iterPairs} from "./util.js";
|
import {getSnapPointForTokenObj, iterPairs} from "./util.js";
|
||||||
|
|
||||||
import * as GridlessPathfinding from "../wasm/gridless_pathfinding.js"
|
import * as GridlessPathfinding from "../wasm/gridless_pathfinding.js"
|
||||||
|
import {PriorityQueueSet} from "./data_structures.js";
|
||||||
|
|
||||||
let cachedNodes = undefined;
|
let cachedNodes = undefined;
|
||||||
let use5105 = false;
|
let use5105 = false;
|
||||||
@@ -103,32 +104,39 @@ function calculatePath(from, to, token, previousWaypoints) {
|
|||||||
previousWaypoints = previousWaypoints.map(w => getGridPositionFromPixelsObj(w));
|
previousWaypoints = previousWaypoints.map(w => getGridPositionFromPixelsObj(w));
|
||||||
startLayer = calcNoDiagonals(previousWaypoints) % 2;
|
startLayer = calcNoDiagonals(previousWaypoints) % 2;
|
||||||
}
|
}
|
||||||
const nextNodes = [{node: getNode({...to, layer: startLayer}, token), cost: 0, estimated: estimateCost(to, from), previous: null}];
|
|
||||||
|
const nextNodes = new PriorityQueueSet((node1, node2) => node1.node === node2.node, node => node.estimated);
|
||||||
const previousNodes = new Set();
|
const previousNodes = new Set();
|
||||||
while (nextNodes.length > 0) {
|
|
||||||
// Sort by estimated cost, high to low
|
nextNodes.pushWithPriority(
|
||||||
// TODO Re-sorting every iteration is expensive. Think of something better
|
{
|
||||||
nextNodes.sort((a, b) => b.estimated - a.estimated);
|
node: getNode({...to, layer: startLayer}, token),
|
||||||
|
cost: 0,
|
||||||
|
estimated: estimateCost(to, from),
|
||||||
|
previous: null
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
while (nextNodes.hasNext()) {
|
||||||
// Get node with cheapest estimate
|
// Get node with cheapest estimate
|
||||||
const currentNode = nextNodes.pop();
|
const currentNode = nextNodes.pop();
|
||||||
if (currentNode.node.x === from.x && currentNode.node.y === from.y)
|
if (currentNode.node.x === from.x && currentNode.node.y === from.y) {
|
||||||
return currentNode;
|
return currentNode;
|
||||||
|
}
|
||||||
previousNodes.add(currentNode.node);
|
previousNodes.add(currentNode.node);
|
||||||
for (const edge of currentNode.node.edges) {
|
for (const edge of currentNode.node.edges) {
|
||||||
const neighborNode = getNode(edge.target, token);
|
const neighborNode = getNode(edge.target, token);
|
||||||
if (previousNodes.has(neighborNode))
|
if (previousNodes.has(neighborNode)) {
|
||||||
continue;
|
continue;
|
||||||
const neighbor = {node: neighborNode, cost: currentNode.cost + edge.cost, estimated: currentNode.cost + edge.cost + estimateCost(neighborNode, from), previous: currentNode};
|
|
||||||
const neighborIndex = nextNodes.findIndex(node => node.node === neighbor.node);
|
|
||||||
if (neighborIndex >= 0) {
|
|
||||||
// If the neighbor is cheaper to reach via the current route than through previously discovered routes, replace it
|
|
||||||
if (nextNodes[neighborIndex].cost > neighbor.cost) {
|
|
||||||
nextNodes[neighborIndex] = neighbor;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
nextNodes.push(neighbor);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const neighbor = {
|
||||||
|
node: neighborNode,
|
||||||
|
cost: currentNode.cost + edge.cost,
|
||||||
|
estimated: currentNode.cost + edge.cost + estimateCost(neighborNode, from),
|
||||||
|
previous: currentNode
|
||||||
|
};
|
||||||
|
nextNodes.pushWithPriority(neighbor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user