much needed pathing overhaul

This commit is contained in:
Leijurv 2019-01-30 15:49:37 -08:00
parent 22bd5be5a9
commit 272dd79426
No known key found for this signature in database
GPG Key ID: 44A3EA646EADAC6A
2 changed files with 57 additions and 57 deletions

View File

@ -25,7 +25,6 @@ import baritone.api.utils.BetterBlockPos;
import baritone.pathing.calc.openset.BinaryHeapOpenSet; import baritone.pathing.calc.openset.BinaryHeapOpenSet;
import baritone.pathing.movement.CalculationContext; import baritone.pathing.movement.CalculationContext;
import baritone.pathing.movement.Moves; import baritone.pathing.movement.Moves;
import baritone.utils.Helper;
import baritone.utils.pathing.BetterWorldBorder; import baritone.utils.pathing.BetterWorldBorder;
import baritone.utils.pathing.Favoring; import baritone.utils.pathing.Favoring;
import baritone.utils.pathing.MutableMoveResult; import baritone.utils.pathing.MutableMoveResult;
@ -37,7 +36,7 @@ import java.util.Optional;
* *
* @author leijurv * @author leijurv
*/ */
public final class AStarPathFinder extends AbstractNodeCostSearch implements Helper { public final class AStarPathFinder extends AbstractNodeCostSearch {
private final Favoring favoring; private final Favoring favoring;
private final CalculationContext calcContext; private final CalculationContext calcContext;
@ -55,14 +54,12 @@ public final class AStarPathFinder extends AbstractNodeCostSearch implements Hel
startNode.combinedCost = startNode.estimatedCostToGoal; startNode.combinedCost = startNode.estimatedCostToGoal;
BinaryHeapOpenSet openSet = new BinaryHeapOpenSet(); BinaryHeapOpenSet openSet = new BinaryHeapOpenSet();
openSet.insert(startNode); openSet.insert(startNode);
bestSoFar = new PathNode[COEFFICIENTS.length];//keep track of the best node by the metric of (estimatedCostToGoal + cost / COEFFICIENTS[i]) double[] bestHeuristicSoFar = new double[COEFFICIENTS.length];//keep track of the best node by the metric of (estimatedCostToGoal + cost / COEFFICIENTS[i])
double[] bestHeuristicSoFar = new double[COEFFICIENTS.length];
for (int i = 0; i < bestHeuristicSoFar.length; i++) { for (int i = 0; i < bestHeuristicSoFar.length; i++) {
bestHeuristicSoFar[i] = startNode.estimatedCostToGoal; bestHeuristicSoFar[i] = startNode.estimatedCostToGoal;
bestSoFar[i] = startNode; bestSoFar[i] = startNode;
} }
MutableMoveResult res = new MutableMoveResult(); MutableMoveResult res = new MutableMoveResult();
Favoring favored = favoring;
BetterWorldBorder worldBorder = new BetterWorldBorder(calcContext.world.getWorldBorder()); BetterWorldBorder worldBorder = new BetterWorldBorder(calcContext.world.getWorldBorder());
long startTime = System.currentTimeMillis(); long startTime = System.currentTimeMillis();
boolean slowPath = Baritone.settings().slowPath.get(); boolean slowPath = Baritone.settings().slowPath.get();
@ -75,10 +72,10 @@ public final class AStarPathFinder extends AbstractNodeCostSearch implements Hel
int numNodes = 0; int numNodes = 0;
int numMovementsConsidered = 0; int numMovementsConsidered = 0;
int numEmptyChunk = 0; int numEmptyChunk = 0;
boolean favoring = !favored.isEmpty(); boolean isFavoring = !favoring.isEmpty();
int timeCheckInterval = 1 << 6; int timeCheckInterval = 1 << 6;
int pathingMaxChunkBorderFetch = Baritone.settings().pathingMaxChunkBorderFetch.get(); // grab all settings beforehand so that changing settings during pathing doesn't cause a crash or unpredictable behavior int pathingMaxChunkBorderFetch = Baritone.settings().pathingMaxChunkBorderFetch.get(); // grab all settings beforehand so that changing settings during pathing doesn't cause a crash or unpredictable behavior
boolean minimumImprovementRepropagation = Baritone.settings().minimumImprovementRepropagation.get(); double minimumImprovement = Baritone.settings().minimumImprovementRepropagation.get() ? MIN_IMPROVEMENT : 0;
while (!openSet.isEmpty() && numEmptyChunk < pathingMaxChunkBorderFetch && !cancelRequested) { while (!openSet.isEmpty() && numEmptyChunk < pathingMaxChunkBorderFetch && !cancelRequested) {
if ((numNodes & (timeCheckInterval - 1)) == 0) { // only call this once every 64 nodes (about half a millisecond) if ((numNodes & (timeCheckInterval - 1)) == 0) { // only call this once every 64 nodes (about half a millisecond)
long now = System.currentTimeMillis(); // since nanoTime is slow on windows (takes many microseconds) long now = System.currentTimeMillis(); // since nanoTime is slow on windows (takes many microseconds)
@ -136,21 +133,13 @@ public final class AStarPathFinder extends AbstractNodeCostSearch implements Hel
throw new IllegalStateException(moves + " " + res.y + " " + (currentNode.y + moves.yOffset)); throw new IllegalStateException(moves + " " + res.y + " " + (currentNode.y + moves.yOffset));
} }
long hashCode = BetterBlockPos.longHash(res.x, res.y, res.z); long hashCode = BetterBlockPos.longHash(res.x, res.y, res.z);
if (favoring) { if (isFavoring) {
// see issue #18 // see issue #18
actionCost *= favored.calculate(hashCode); actionCost *= favoring.calculate(hashCode);
} }
PathNode neighbor = getNodeAtPosition(res.x, res.y, res.z, hashCode); PathNode neighbor = getNodeAtPosition(res.x, res.y, res.z, hashCode);
double tentativeCost = currentNode.cost + actionCost; double tentativeCost = currentNode.cost + actionCost;
if (tentativeCost < neighbor.cost) { if (neighbor.cost - tentativeCost > minimumImprovement) {
double improvementBy = neighbor.cost - tentativeCost;
// there are floating point errors caused by random combinations of traverse and diagonal over a flat area
// that means that sometimes there's a cost improvement of like 10 ^ -16
// it's not worth the time to update the costs, decrease-key the heap, potentially repropagate, etc
if (improvementBy < 0.01 && minimumImprovementRepropagation) {
// who cares about a hundredth of a tick? that's half a millisecond for crying out loud!
continue;
}
neighbor.previous = currentNode; neighbor.previous = currentNode;
neighbor.cost = tentativeCost; neighbor.cost = tentativeCost;
neighbor.combinedCost = tentativeCost + neighbor.estimatedCostToGoal; neighbor.combinedCost = tentativeCost + neighbor.estimatedCostToGoal;
@ -159,12 +148,9 @@ public final class AStarPathFinder extends AbstractNodeCostSearch implements Hel
} else { } else {
openSet.insert(neighbor);//dont double count, dont insert into open set if it's already there openSet.insert(neighbor);//dont double count, dont insert into open set if it's already there
} }
for (int i = 0; i < bestSoFar.length; i++) { for (int i = 0; i < COEFFICIENTS.length; i++) {
double heuristic = neighbor.estimatedCostToGoal + neighbor.cost / COEFFICIENTS[i]; double heuristic = neighbor.estimatedCostToGoal + neighbor.cost / COEFFICIENTS[i];
if (heuristic < bestHeuristicSoFar[i]) { if (bestHeuristicSoFar[i] - heuristic > minimumImprovement) {
if (bestHeuristicSoFar[i] - heuristic < 0.01 && minimumImprovementRepropagation) {
continue;
}
bestHeuristicSoFar[i] = heuristic; bestHeuristicSoFar[i] = heuristic;
bestSoFar[i] = neighbor; bestSoFar[i] = neighbor;
if (getDistFromStartSq(neighbor) > MIN_DIST_PATH * MIN_DIST_PATH) { if (getDistFromStartSq(neighbor) > MIN_DIST_PATH * MIN_DIST_PATH) {
@ -182,28 +168,10 @@ public final class AStarPathFinder extends AbstractNodeCostSearch implements Hel
System.out.println("Open set size: " + openSet.size()); System.out.println("Open set size: " + openSet.size());
System.out.println("PathNode map size: " + mapSize()); System.out.println("PathNode map size: " + mapSize());
System.out.println((int) (numNodes * 1.0 / ((System.currentTimeMillis() - startTime) / 1000F)) + " nodes per second"); System.out.println((int) (numNodes * 1.0 / ((System.currentTimeMillis() - startTime) / 1000F)) + " nodes per second");
double bestDist = 0; Optional<IPath> result = bestSoFar(true, numNodes);
for (int i = 0; i < bestSoFar.length; i++) { if (result.isPresent()) {
if (bestSoFar[i] == null) { logDebug("Took " + (System.currentTimeMillis() - startTime) + "ms, " + numMovementsConsidered + " movements considered");
continue;
}
double dist = getDistFromStartSq(bestSoFar[i]);
if (dist > bestDist) {
bestDist = dist;
}
if (dist > MIN_DIST_PATH * MIN_DIST_PATH) { // square the comparison since distFromStartSq is squared
logDebug("Took " + (System.currentTimeMillis() - startTime) + "ms, A* cost coefficient " + COEFFICIENTS[i] + ", " + numMovementsConsidered + " movements considered");
if (COEFFICIENTS[i] >= 3) {
System.out.println("Warning: cost coefficient is greater than three! Probably means that");
System.out.println("the path I found is pretty terrible (like sneak-bridging for dozens of blocks)");
System.out.println("But I'm going to do it anyway, because yolo");
}
System.out.println("Path goes for " + Math.sqrt(dist) + " blocks");
return Optional.of(new Path(startNode, bestSoFar[i], numNodes, goal, calcContext));
}
} }
logDebug("Even with a cost coefficient of " + COEFFICIENTS[COEFFICIENTS.length - 1] + ", I couldn't get more than " + Math.sqrt(bestDist) + " blocks"); return result;
logDebug("No path found =(");
return Optional.empty();
} }
} }

View File

@ -34,7 +34,7 @@ import java.util.Optional;
* *
* @author leijurv * @author leijurv
*/ */
public abstract class AbstractNodeCostSearch implements IPathFinder { public abstract class AbstractNodeCostSearch implements IPathFinder, Helper {
protected final int startX; protected final int startX;
protected final int startY; protected final int startY;
@ -53,7 +53,7 @@ public abstract class AbstractNodeCostSearch implements IPathFinder {
protected PathNode mostRecentConsidered; protected PathNode mostRecentConsidered;
protected PathNode[] bestSoFar; protected final PathNode[] bestSoFar = new PathNode[COEFFICIENTS.length];
private volatile boolean isFinished; private volatile boolean isFinished;
@ -63,13 +63,23 @@ public abstract class AbstractNodeCostSearch implements IPathFinder {
* This is really complicated and hard to explain. I wrote a comment in the old version of MineBot but it was so * This is really complicated and hard to explain. I wrote a comment in the old version of MineBot but it was so
* long it was easier as a Google Doc (because I could insert charts). * long it was easier as a Google Doc (because I could insert charts).
* *
* @see <a href="https://docs.google.com/document/d/1WVHHXKXFdCR1Oz__KtK8sFqyvSwJN_H4lftkHFgmzlc/edit"></a> * @see <a href="https://docs.google.com/document/d/1WVHHXKXFdCR1Oz__KtK8sFqyvSwJN_H4lftkHFgmzlc/edit">here</a>
*/ */
protected static final double[] COEFFICIENTS = {1.5, 2, 2.5, 3, 4, 5, 10}; // big TODO tune protected static final double[] COEFFICIENTS = {1.5, 2, 2.5, 3, 4, 5, 10};
/** /**
* If a path goes less than 5 blocks and doesn't make it to its goal, it's not worth considering. * If a path goes less than 5 blocks and doesn't make it to its goal, it's not worth considering.
*/ */
protected final static double MIN_DIST_PATH = 5; protected static final double MIN_DIST_PATH = 5;
/**
* there are floating point errors caused by random combinations of traverse and diagonal over a flat area
* that means that sometimes there's a cost improvement of like 10 ^ -16
* it's not worth the time to update the costs, decrease-key the heap, potentially repropagate, etc
* <p>
* who cares about a hundredth of a tick? that's half a millisecond for crying out loud!
*/
protected static final double MIN_IMPROVEMENT = 0.01;
AbstractNodeCostSearch(int startX, int startY, int startZ, Goal goal, CalculationContext context) { AbstractNodeCostSearch(int startX, int startY, int startZ, Goal goal, CalculationContext context) {
this.startX = startX; this.startX = startX;
@ -170,25 +180,43 @@ public abstract class AbstractNodeCostSearch implements IPathFinder {
return Optional.ofNullable(mostRecentConsidered).map(node -> new Path(startNode, node, 0, goal, context)); return Optional.ofNullable(mostRecentConsidered).map(node -> new Path(startNode, node, 0, goal, context));
} }
protected int mapSize() { @Override
return map.size(); public Optional<IPath> bestPathSoFar() {
return bestSoFar(false, 0);
} }
@Override protected Optional<IPath> bestSoFar(boolean logInfo, int numNodes) {
public Optional<IPath> bestPathSoFar() { // TODO cleanup code duplication between here and AStarPathFinder
if (startNode == null || bestSoFar == null) { if (startNode == null || bestSoFar == null) {
return Optional.empty(); return Optional.empty();
} }
for (int i = 0; i < bestSoFar.length; i++) { double bestDist = 0;
for (int i = 0; i < COEFFICIENTS.length; i++) {
if (bestSoFar[i] == null) { if (bestSoFar[i] == null) {
continue; continue;
} }
if (getDistFromStartSq(bestSoFar[i]) > MIN_DIST_PATH * MIN_DIST_PATH) { // square the comparison since distFromStartSq is squared double dist = getDistFromStartSq(bestSoFar[i]);
return Optional.of(new Path(startNode, bestSoFar[i], 0, goal, context)); if (dist > bestDist) {
bestDist = dist;
}
if (dist > MIN_DIST_PATH * MIN_DIST_PATH) { // square the comparison since distFromStartSq is squared
if (logInfo) {
if (COEFFICIENTS[i] >= 3) {
System.out.println("Warning: cost coefficient is greater than three! Probably means that");
System.out.println("the path I found is pretty terrible (like sneak-bridging for dozens of blocks)");
System.out.println("But I'm going to do it anyway, because yolo");
}
System.out.println("Path goes for " + Math.sqrt(dist) + " blocks");
logDebug("A* cost coefficient " + COEFFICIENTS[i]);
}
return Optional.of(new Path(startNode, bestSoFar[i], numNodes, goal, context));
} }
} }
// instead of returning bestSoFar[0], be less misleading // instead of returning bestSoFar[0], be less misleading
// if it actually won't find any path, don't make them think it will by rendering a dark blue that will never actually happen // if it actually won't find any path, don't make them think it will by rendering a dark blue that will never actually happen
if (logInfo) {
logDebug("Even with a cost coefficient of " + COEFFICIENTS[COEFFICIENTS.length - 1] + ", I couldn't get more than " + Math.sqrt(bestDist) + " blocks");
logDebug("No path found =(");
}
return Optional.empty(); return Optional.empty();
} }
@ -205,4 +233,8 @@ public abstract class AbstractNodeCostSearch implements IPathFinder {
public BetterBlockPos getStart() { public BetterBlockPos getStart() {
return new BetterBlockPos(startX, startY, startZ); return new BetterBlockPos(startX, startY, startZ);
} }
protected int mapSize() {
return map.size();
}
} }