refactor out zobrist world state cache

This commit is contained in:
Leijurv
2021-09-03 13:29:29 -07:00
parent 0fd91edb97
commit 217f6ecf28
6 changed files with 116 additions and 42 deletions

View File

@@ -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);
}

View File

@@ -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();
}

View File

@@ -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) {

View File

@@ -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() {

View File

@@ -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 {

View 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;
}
}