refactor out zobrist world state cache
This commit is contained in:
@@ -170,10 +170,6 @@ public final class BetterBlockPos extends BlockPos {
|
||||
return murmur64(HASHCODE_MURMUR_MASK ^ packed);
|
||||
}
|
||||
|
||||
public static long zobrist(long packed) {
|
||||
return murmur64(ZOBRIST_MURMUR_MASK ^ packed);
|
||||
}
|
||||
|
||||
public static long murmur64(long h) {
|
||||
return HashCommon.murmurHash3(h);
|
||||
}
|
||||
|
||||
@@ -20,12 +20,16 @@ package baritone.builder;
|
||||
import baritone.api.utils.BetterBlockPos;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class GreedySolver {
|
||||
|
||||
private final SolverEngineInput engineInput;
|
||||
private final NodeBinaryHeap heap = new NodeBinaryHeap();
|
||||
private final Long2ObjectOpenHashMap<Node> nodes = new Long2ObjectOpenHashMap<>();
|
||||
final Long2ObjectOpenHashMap<WorldState> zobristWorldStateCache = new Long2ObjectOpenHashMap<>();
|
||||
private final ZobristWorldStateCache zobristCache;
|
||||
private final long allCompleted;
|
||||
private final Bounds bounds;
|
||||
private Column scratchpadExpandNode1 = new Column();
|
||||
private Column scratchpadExpandNode2 = new Column();
|
||||
@@ -34,19 +38,41 @@ public class GreedySolver {
|
||||
public GreedySolver(SolverEngineInput input) {
|
||||
this.engineInput = input;
|
||||
this.bounds = engineInput.graph.bounds();
|
||||
this.zobristCache = new ZobristWorldStateCache(new WorldState.WorldStateWrappedSubstrate(engineInput));
|
||||
Node root = new Node(engineInput.player, null, 0L, -1L, 0);
|
||||
nodes.put(root.nodeMapKey(), root);
|
||||
heap.insert(root);
|
||||
this.allCompleted = WorldState.predetermineGoalZobrist(engineInput.allToPlaceNow);
|
||||
}
|
||||
|
||||
synchronized SolverEngineOutput search() {
|
||||
Node root = new Node(engineInput.player, null, 0L, -1L, this, 0);
|
||||
nodes.put(root.nodeMapKey(), root);
|
||||
heap.insert(root);
|
||||
zobristWorldStateCache.put(0L, new WorldState.WorldStateWrappedSubstrate(engineInput));
|
||||
while (!heap.isEmpty()) {
|
||||
expandNode(heap.removeLowest());
|
||||
Node node = heap.removeLowest();
|
||||
if (!node.sneaking() && node.worldStateZobristHash == allCompleted) {
|
||||
return backwards(node);
|
||||
}
|
||||
expandNode(node);
|
||||
}
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
private SolverEngineOutput backwards(Node node) {
|
||||
ArrayDeque<SolvedActionStep> steps = new ArrayDeque<>();
|
||||
while (node.previous != null) {
|
||||
steps.addFirst(step(node, node.previous));
|
||||
node = node.previous;
|
||||
}
|
||||
return new SolverEngineOutput(new ArrayList<>(steps));
|
||||
}
|
||||
|
||||
private SolvedActionStep step(Node next, Node prev) {
|
||||
if (next.worldStateZobristHash == prev.worldStateZobristHash) {
|
||||
return new SolvedActionStep(next.pos());
|
||||
} else {
|
||||
return new SolvedActionStep(next.pos(), WorldState.unzobrist(prev.worldStateZobristHash ^ next.worldStateZobristHash));
|
||||
}
|
||||
}
|
||||
|
||||
private boolean wantToPlaceAt(long blockGoesAt, Node vantage, int blipsWithinVoxel, WorldState worldState) {
|
||||
if (worldState.blockExists(blockGoesAt)) {
|
||||
return false;
|
||||
@@ -80,7 +106,7 @@ public class GreedySolver {
|
||||
}
|
||||
|
||||
private void expandNode(Node node) {
|
||||
WorldState worldState = node.coalesceState(this);
|
||||
WorldState worldState = zobristCache.coalesceState(node);
|
||||
long pos = node.pos();
|
||||
Column within = scratchpadExpandNode1;
|
||||
within.initFrom(pos, worldState, engineInput);
|
||||
@@ -186,7 +212,10 @@ public class GreedySolver {
|
||||
|
||||
private void upsertEdge(Node node, WorldState worldState, long newPlayerPosition, Face sneakingTowards, long blockPlacement, int edgeCost) {
|
||||
Node neighbor = getNode(newPlayerPosition, sneakingTowards, node, worldState, blockPlacement);
|
||||
if (Main.SLOW_DEBUG && blockPlacement != -1 && !neighbor.coalesceState(this).blockExists(blockPlacement)) { // only in slow_debug because this force-allocates a WorldState for every neighbor of every node!
|
||||
if (Main.SLOW_DEBUG && blockPlacement != -1 && !zobristCache.coalesceState(neighbor).blockExists(blockPlacement)) { // only in slow_debug because this force-allocates a WorldState for every neighbor of every node!
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (Main.DEBUG && node == neighbor) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
updateNeighbor(node, neighbor, edgeCost);
|
||||
@@ -250,7 +279,7 @@ public class GreedySolver {
|
||||
if (blockPlacement != -1) {
|
||||
newHeuristic += calculateHeuristicModifier(prevWorld, blockPlacement);
|
||||
}
|
||||
Node node = new Node(playerPosition, null, worldStateZobristHash, blockPlacement, this, newHeuristic);
|
||||
Node node = new Node(playerPosition, null, worldStateZobristHash, blockPlacement, newHeuristic);
|
||||
if (Main.DEBUG && node.nodeMapKey() != code) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
@@ -28,45 +28,20 @@ public class Node {
|
||||
public int cost;
|
||||
public int combinedCost;
|
||||
public Node previous;
|
||||
public int heapPosition;
|
||||
int heapPosition;
|
||||
|
||||
// boolean unrealizedZobristBlockChange; // no longer needed since presence in the overall GreedySolver zobristWorldStateCache indicates if this is a yet-unrealized branch of the zobrist space
|
||||
private long packedUnrealizedCoordinate;
|
||||
long packedUnrealizedCoordinate;
|
||||
// int unrealizedState; // no longer needed now that world state is binarized with scaffolding/build versus air
|
||||
// long unrealizedZobristParentHash; // no longer needed since we can compute it backwards with XOR
|
||||
|
||||
public Node(long pos, Face sneakingTowards, long zobristState, long unrealizedBlockPlacement, GreedySolver solver, int heuristic) {
|
||||
public Node(long pos, Face sneakingTowards, long zobristState, long unrealizedBlockPlacement, int heuristic) {
|
||||
this.posAndSneak = encode(pos, sneakingTowards);
|
||||
this.heapPosition = -1;
|
||||
this.cost = Integer.MAX_VALUE;
|
||||
this.heuristic = heuristic;
|
||||
this.worldStateZobristHash = zobristState;
|
||||
this.packedUnrealizedCoordinate = unrealizedBlockPlacement;
|
||||
if (Main.DEBUG && (solver.zobristWorldStateCache.containsKey(worldStateZobristHash) ^ (unrealizedBlockPlacement == -1))) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
|
||||
public WorldState coalesceState(GreedySolver solver) {
|
||||
WorldState alr = solver.zobristWorldStateCache.get(worldStateZobristHash);
|
||||
if (alr != null) {
|
||||
if (Main.DEBUG && alr.zobristHash != worldStateZobristHash) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
// don't check packedUnrealizedCoordinate here because it could exist (not -1) if a different route was taken to a zobrist-equivalent node (such as at a different player position) which was then expanded and coalesced
|
||||
return alr;
|
||||
}
|
||||
if (Main.DEBUG && packedUnrealizedCoordinate == -1) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
long parent = WorldState.updateZobrist(worldStateZobristHash, packedUnrealizedCoordinate); // updateZobrist is symmetric because XOR, so the same operation can do child->parent as parent->child
|
||||
WorldState myState = solver.zobristWorldStateCache.get(parent).withChild(packedUnrealizedCoordinate);
|
||||
if (Main.DEBUG && myState.zobristHash != worldStateZobristHash) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
solver.zobristWorldStateCache.put(worldStateZobristHash, myState);
|
||||
packedUnrealizedCoordinate = -1;
|
||||
return myState;
|
||||
}
|
||||
|
||||
public static long encode(long pos, Face sneakingTowards) {
|
||||
|
||||
@@ -31,6 +31,9 @@ public class SolvedActionStep {
|
||||
public SolvedActionStep(long playerMovesTo, long blockPlacedAt) {
|
||||
this.playerEndPosition = playerMovesTo;
|
||||
this.placePosition = blockPlacedAt;
|
||||
if (Main.DEBUG && blockPlacedAt < -1) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
|
||||
public OptionalLong placeAt() {
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
package baritone.builder;
|
||||
|
||||
import baritone.api.utils.BetterBlockPos;
|
||||
import it.unimi.dsi.fastutil.HashCommon;
|
||||
import it.unimi.dsi.fastutil.longs.LongCollection;
|
||||
import it.unimi.dsi.fastutil.longs.LongIterator;
|
||||
|
||||
import java.util.BitSet;
|
||||
@@ -40,7 +42,24 @@ public abstract class WorldState {
|
||||
}
|
||||
|
||||
public static long updateZobrist(long worldStateZobristHash, long changedPosition) {
|
||||
return BetterBlockPos.zobrist(changedPosition) ^ worldStateZobristHash;
|
||||
return zobrist(changedPosition) ^ worldStateZobristHash;
|
||||
}
|
||||
|
||||
public static long predetermineGoalZobrist(LongCollection goal) {
|
||||
LongIterator it = goal.iterator();
|
||||
long ret = 0;
|
||||
while (it.hasNext()) {
|
||||
ret ^= zobrist(it.nextLong());
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static long zobrist(long packed) {
|
||||
return HashCommon.mix(BetterBlockPos.ZOBRIST_MURMUR_MASK ^ packed);
|
||||
}
|
||||
|
||||
public static long unzobrist(long zobrist) {
|
||||
return BetterBlockPos.ZOBRIST_MURMUR_MASK ^ HashCommon.invMix(zobrist);
|
||||
}
|
||||
|
||||
public static class WorldStateWrappedSubstrate extends WorldState {
|
||||
|
||||
52
src/main/java/baritone/builder/ZobristWorldStateCache.java
Normal file
52
src/main/java/baritone/builder/ZobristWorldStateCache.java
Normal file
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* This file is part of Baritone.
|
||||
*
|
||||
* Baritone is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Baritone is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Baritone. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package baritone.builder;
|
||||
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
||||
|
||||
public class ZobristWorldStateCache {
|
||||
|
||||
private final Long2ObjectOpenHashMap<WorldState> zobristWorldStateCache;
|
||||
|
||||
public ZobristWorldStateCache(WorldState zeroEntry) {
|
||||
this.zobristWorldStateCache = new Long2ObjectOpenHashMap<>();
|
||||
this.zobristWorldStateCache.put(0L, zeroEntry);
|
||||
}
|
||||
|
||||
public WorldState coalesceState(Node node) {
|
||||
WorldState alr = zobristWorldStateCache.get(node.worldStateZobristHash);
|
||||
if (alr != null) {
|
||||
if (Main.DEBUG && alr.zobristHash != node.worldStateZobristHash) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
// don't check packedUnrealizedCoordinate here because it could exist (not -1) if a different route was taken to a zobrist-equivalent node (such as at a different player position) which was then expanded and coalesced
|
||||
return alr;
|
||||
}
|
||||
if (Main.DEBUG && node.packedUnrealizedCoordinate == -1) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
long parent = WorldState.updateZobrist(node.worldStateZobristHash, node.packedUnrealizedCoordinate); // updateZobrist is symmetric because XOR, so the same operation can do child->parent as parent->child
|
||||
WorldState myState = zobristWorldStateCache.get(parent).withChild(node.packedUnrealizedCoordinate);
|
||||
if (Main.DEBUG && myState.zobristHash != node.worldStateZobristHash) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
zobristWorldStateCache.put(node.worldStateZobristHash, myState);
|
||||
node.packedUnrealizedCoordinate = -1;
|
||||
return myState;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user