From 4bea9dfb42629529f248e5b4b89b2f2afe8616b1 Mon Sep 17 00:00:00 2001 From: Leijurv Date: Mon, 10 Apr 2023 00:14:13 -0700 Subject: [PATCH] started on simultaneous dijkstra scaffolder, saving progress here for now --- .../DependencyGraphScaffoldingOverlay.java | 4 +- .../baritone/builder/DijkstraScaffolder.java | 6 +- src/main/java/baritone/builder/Main.java | 53 ----- .../builder/PackedBlockStateCuboid.java | 10 + .../java/baritone/builder/Scaffolder.java | 64 +++--- .../SimultaneousDijkstraScaffolder.java | 184 ++++++++++++++++++ .../java/baritone/builder/ScaffolderTest.java | 77 ++++++++ 7 files changed, 312 insertions(+), 86 deletions(-) create mode 100644 src/main/java/baritone/builder/SimultaneousDijkstraScaffolder.java create mode 100644 src/test/java/baritone/builder/ScaffolderTest.java diff --git a/src/main/java/baritone/builder/DependencyGraphScaffoldingOverlay.java b/src/main/java/baritone/builder/DependencyGraphScaffoldingOverlay.java index 1c357aea1..1a47e2e09 100644 --- a/src/main/java/baritone/builder/DependencyGraphScaffoldingOverlay.java +++ b/src/main/java/baritone/builder/DependencyGraphScaffoldingOverlay.java @@ -196,7 +196,7 @@ public class DependencyGraphScaffoldingOverlay { if (child == parent) { throw new IllegalStateException(); } - if (child.positions.size() > parent.positions.size()) { + if (child.positions.size() > parent.positions.size() || (child.positions.size() == parent.positions.size() && child.id < parent.id)) { return mergeInto(parent, child); } if (Main.DEBUG) { @@ -358,7 +358,7 @@ public class DependencyGraphScaffoldingOverlay { if (!deleted()) { return this; } - return deletedInto.deletedIntoRecursive(); + return deletedInto = deletedInto.deletedIntoRecursive(); } public LongSet getPositions() { diff --git a/src/main/java/baritone/builder/DijkstraScaffolder.java b/src/main/java/baritone/builder/DijkstraScaffolder.java index 39722a618..33296d5fc 100644 --- a/src/main/java/baritone/builder/DijkstraScaffolder.java +++ b/src/main/java/baritone/builder/DijkstraScaffolder.java @@ -76,7 +76,7 @@ public enum DijkstraScaffolder implements IScaffolderStrategy { // any position in the initial frontier is clearly in the node map, but also any node that has already been considered // this prevents useless cycling of equivalent paths // this is okay because all paths are equivalent, so there is no possible way to find a better path (because currently it's a fixed value for horizontal / vertical movements) - if (existingNode.costSoFar > newCost && !root.getPositions().contains(node.pos)) { // initialization nodes will have costSoFar = 0 as a base case + if (existingNode.costSoFar > newCost) { // initialization nodes will have costSoFar = 0 as a base case // note that obviously there is a loopback possibility: search one block north then one block south, you'll run into the same node again. that's fine - "costSoFar < newCost" doesn't mean anything // same for diagonals: one block north then one block down, versus one block down then one block north. that's also fine - "costSoFar == newCost" doesn't mean anything System.out.println(BetterBlockPos.fromLong(node.pos) + " to " + BetterBlockPos.fromLong(neighborPos) + " " + existingNode.costSoFar + " " + newCost + " " + root.getPositions().contains(node.pos) + " " + root.getPositions().contains(neighborPos) + " " + reconstructPathTo(node).stream().map(BetterBlockPos::fromLong).collect(Collectors.toList()) + " " + reconstructPathTo(existingNode).stream().map(BetterBlockPos::fromLong).collect(Collectors.toList())); @@ -112,7 +112,7 @@ public enum DijkstraScaffolder implements IScaffolderStrategy { return path; } - private static int edgeCost(Face face) { + public static int edgeCost(Face face) { if (Main.STRICT_Y && face == Face.UP) { throw new IllegalStateException(); } @@ -122,7 +122,7 @@ public enum DijkstraScaffolder implements IScaffolderStrategy { if (face.y == 0) { return 1; } - return 1; + return 2; } private static class ScaffoldingSearchNode { diff --git a/src/main/java/baritone/builder/Main.java b/src/main/java/baritone/builder/Main.java index ff7e5cac8..d212751b6 100644 --- a/src/main/java/baritone/builder/Main.java +++ b/src/main/java/baritone/builder/Main.java @@ -23,11 +23,8 @@ import baritone.builder.mc.DebugStates; import baritone.builder.mc.VanillaBlockStateDataProvider; import it.unimi.dsi.fastutil.doubles.DoubleArrayList; import it.unimi.dsi.fastutil.longs.LongArrayList; -import net.minecraft.block.Block; -import net.minecraft.init.Blocks; import java.util.Random; -import java.util.function.Consumer; import java.util.stream.Collectors; import java.util.stream.IntStream; import java.util.stream.Stream; @@ -344,56 +341,6 @@ Branchy took 124ms System.out.println(SneakPosition.decode(SneakPosition.encode(BetterBlockPos.POST_ADDITION_MASK, sneak))); } } - { - int[][][] test = new int[8][8][8]; - int dirt = Block.BLOCK_STATE_IDS.get(Blocks.DIRT.getDefaultState()); - System.out.println("D " + dirt); - for (int x = 0; x < test.length; x++) { - for (int z = 0; z < test[0][0].length; z++) { - test[x][0][z] = dirt; - test[x][1][z] = dirt; - } - } - test[5][5][5] = dirt; - test[5][5][6] = dirt; - test[0][5][5] = dirt; - Consumer debug = dgso -> { - for (int y = 0; y < test[0].length; y++) { - System.out.println("Layer " + y); - for (int x = 0; x < test.length; x++) { - for (int z = 0; z < test[0][0].length; z++) { - long pos = BetterBlockPos.toLong(x, y, z); - if (dgso.real(pos)) { - System.out.print(dgso.getCollapsedGraph().getComponentLocations().get(pos).deletedIntoRecursive()); - } else { - System.out.print(" "); - } - } - System.out.println(); - } - } - }; - PackedBlockStateCuboid states = new PackedBlockStateCuboid(test, DATA); - PlaceOrderDependencyGraph graph = new PlaceOrderDependencyGraph(states); - System.out.println("N " + Face.NORTH.z); - System.out.println("S " + Face.SOUTH.z); - for (int z = 0; z < test[0][0].length; z++) { - //System.out.println(states.get(states.bounds.toIndex(0, 0, z))); - System.out.println(z + " " + graph.outgoingEdge(BetterBlockPos.toLong(0, 0, z), Face.NORTH) + " " + graph.outgoingEdge(BetterBlockPos.toLong(0, 0, z), Face.SOUTH)); - } - DependencyGraphAnalyzer.prevalidate(graph); - DependencyGraphAnalyzer.prevalidateExternalToInteriorSearch(graph); - DependencyGraphScaffoldingOverlay scaffolding = new DependencyGraphScaffoldingOverlay(graph); - System.out.println("Hewwo"); - scaffolding.getCollapsedGraph().getComponents().forEach((key, value) -> { - System.out.println(key); - System.out.println(value.getPositions().stream().map(BetterBlockPos::fromLong).collect(Collectors.toList())); - }); - System.out.println(); - debug.accept(scaffolding); - Scaffolder.Output out = Scaffolder.run(graph, DijkstraScaffolder.INSTANCE); - debug.accept(out.secretInternalForTesting()); - } System.exit(0); } } diff --git a/src/main/java/baritone/builder/PackedBlockStateCuboid.java b/src/main/java/baritone/builder/PackedBlockStateCuboid.java index 1e13e75db..735c2b4d0 100644 --- a/src/main/java/baritone/builder/PackedBlockStateCuboid.java +++ b/src/main/java/baritone/builder/PackedBlockStateCuboid.java @@ -17,6 +17,8 @@ package baritone.builder; +import java.util.Arrays; + public class PackedBlockStateCuboid { public final Bounds bounds; @@ -41,6 +43,14 @@ public class PackedBlockStateCuboid { genScaffoldVariant(); } + public static void fillWithAir(BlockStateCachedData[][][] states) { + for (BlockStateCachedData[][] layer : states) { + for (BlockStateCachedData[] slice : layer) { + Arrays.fill(slice, FakeStates.AIR); + } + } + } + private void genScaffoldVariant() { for (int i = 0; i < states.length; i++) { if (PlaceOrderDependencyGraph.treatedAsScaffolding(states[i])) { diff --git a/src/main/java/baritone/builder/Scaffolder.java b/src/main/java/baritone/builder/Scaffolder.java index 6bb59c03b..36d50942d 100644 --- a/src/main/java/baritone/builder/Scaffolder.java +++ b/src/main/java/baritone/builder/Scaffolder.java @@ -70,11 +70,7 @@ public class Scaffolder { private List calcRoots() { // since the components form a DAG (because all strongly connected components, and therefore all cycles, have been collapsed) // we can locate all root components by simply finding the ones with no incoming edges - return components - .values() - .stream() - .filter(component -> component.getIncoming().isEmpty()) - .collect(Collectors.toCollection(ArrayList::new)); // ensure arraylist since we will be mutating the list + return components.values().stream().filter(component -> component.getIncoming().isEmpty()).collect(Collectors.toCollection(ArrayList::new)); // ensure arraylist since we will be mutating the list } private void loop() { @@ -96,31 +92,15 @@ public class Scaffolder { if (root.getPositions().contains(path.get(0))) { throw new IllegalStateException(); } - if (!componentLocations.containsKey(path.get(0))) { - throw new IllegalStateException(); - } - for (int i = 1; i < path.size(); i++) { - if (!overlayGraph.hypotheticalScaffoldingIncomingEdge(path.get(i), Face.between(path.get(i), path.get(i - 1)))) { - throw new IllegalStateException(); - } - } - enable(path.subList(1, path.size() - 1)); + internalEnable(path); return; } throw new IllegalStateException("unconnectable"); } - private void enable(LongList positions) { - positions.forEach(pos -> { - if (componentLocations.containsKey(pos)) { - throw new IllegalStateException(); - } - }); - System.out.println("Enabling " + positions.stream().map(BetterBlockPos::fromLong).collect(Collectors.toList())); + private void internalEnable(LongList path) { int cid = collapsedGraph.lastComponentID().getAsInt(); - - positions.forEach(overlayGraph::enable); // TODO more performant to enable in reverse order maybe? - + applyScaffoldingConnection(overlayGraph, path); int newCID = collapsedGraph.lastComponentID().getAsInt(); for (int i = cid + 1; i <= newCID; i++) { if (components.get(i) != null && components.get(i).getIncoming().isEmpty()) { @@ -161,12 +141,40 @@ public class Scaffolder { } } + public static void applyScaffoldingConnection(DependencyGraphScaffoldingOverlay overlayGraph, LongList path) { + CollapsedDependencyGraph collapsedGraph = overlayGraph.getCollapsedGraph(); + Long2ObjectMap componentLocations = collapsedGraph.getComponentLocations(); + if (!componentLocations.containsKey(path.getLong(0))) { + throw new IllegalStateException(); + } + if (!componentLocations.containsKey(path.getLong(path.size() - 1))) { + throw new IllegalStateException(); + } + if (componentLocations.get(path.getLong(0)) == componentLocations.get(path.getLong(path.size() - 1))) { + throw new IllegalStateException(); + } + if (!componentLocations.get(path.getLong(path.size() - 1)).getIncoming().isEmpty()) { + throw new IllegalStateException(); + } + // componentLocations.get(path.getLong(path.size() - 1)).getIncoming() can be either empty or nonempty + for (int i = 1; i < path.size(); i++) { + if (!overlayGraph.hypotheticalScaffoldingIncomingEdge(path.getLong(i), Face.between(path.getLong(i), path.getLong(i - 1)))) { + throw new IllegalStateException(); + } + } + LongList positions = path.subList(1, path.size() - 1); + positions.forEach(pos -> { + if (componentLocations.containsKey(pos)) { + throw new IllegalStateException(); + } + }); + System.out.println("Enabling " + positions.stream().map(BetterBlockPos::fromLong).collect(Collectors.toList())); + positions.forEach(overlayGraph::enable); // TODO more performant to enable in reverse order maybe? + } + public class Output { public void enableAncillaryScaffoldingAndRecomputeRoot(LongList positions) { - getRoot(); - enable(positions); - getRoot(); - throw new UnsupportedOperationException("TODO: should ancillary scaffolding even recompute the components? that scaffolding doesn't NEED to part of any component, and having all components be mutable even after the scaffolder is done is sketchy"); + throw new UnsupportedOperationException("mutable components after scaffolding is not worth it"); } public CollapsedDependencyGraphComponent getRoot() { // TODO this should probably return a new class that is not mutable in-place diff --git a/src/main/java/baritone/builder/SimultaneousDijkstraScaffolder.java b/src/main/java/baritone/builder/SimultaneousDijkstraScaffolder.java new file mode 100644 index 000000000..85b73c697 --- /dev/null +++ b/src/main/java/baritone/builder/SimultaneousDijkstraScaffolder.java @@ -0,0 +1,184 @@ +/* + * 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 . + */ + +package baritone.builder; + +import baritone.api.utils.BetterBlockPos; +import baritone.builder.DependencyGraphScaffoldingOverlay.CollapsedDependencyGraph.CollapsedDependencyGraphComponent; +import it.unimi.dsi.fastutil.HashCommon; +import it.unimi.dsi.fastutil.longs.LongArrayList; +import it.unimi.dsi.fastutil.longs.LongIterator; +import it.unimi.dsi.fastutil.longs.LongList; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; + +import java.util.Comparator; +import java.util.List; +import java.util.PriorityQueue; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.LongStream; + +public class SimultaneousDijkstraScaffolder implements IScaffolderStrategy { + @Override + public LongList scaffoldTo(CollapsedDependencyGraphComponent $ignored$, DependencyGraphScaffoldingOverlay overlayGraph) { + // the use-case that i'm keeping mind from a performance pov is staircased mapart + // O(n^2) across all 128x128=16384 blocks would be cringe + // so let's have a combined priority queue from all cdg provenances without a reset on every scaffolding placement + List roots = overlayGraph.getCollapsedGraph().getComponents().values().stream().filter(component -> component.getIncoming().isEmpty()).collect(Collectors.toList()); + ObjectOpenHashSet remainingRoots = new ObjectOpenHashSet<>(); + remainingRoots.addAll(roots); + Object2IntOpenHashMap componentToId = new Object2IntOpenHashMap<>(); + for (int i = 0; i < roots.size(); i++) { + componentToId.put(roots.get(i), i); + } + PriorityQueue openSet = new PriorityQueue<>(Comparator.comparingInt(node -> node.costSoFar).thenComparingInt(node -> node.key.cdgid)); + Object2ObjectOpenHashMap nodeMap = new Object2ObjectOpenHashMap<>(); + for (CollapsedDependencyGraphComponent component : roots) { + int cdgid = componentToId.getInt(component); + LongIterator it = component.getPositions().iterator(); + while (it.hasNext()) { + long l = it.nextLong(); + ScaffoldingSearchKey key = new ScaffoldingSearchKey(l, cdgid); + nodeMap.put(key, new ScaffoldingSearchNode(key)); + } + } + openSet.addAll(nodeMap.values()); + while (!openSet.isEmpty()) { + ScaffoldingSearchNode node = openSet.poll(); + CollapsedDependencyGraphComponent provenance = roots.get(node.key.cdgid).deletedIntoRecursive(); + // is the deletedIntoRecursive valid? i think so because the costSoFar is a shared key in the priority queue. sure you could get a suboptimal path from an old subsection of a new cdg, but, it would only be considered after the optimal path. so, no issue? i think? + if (!provenance.getIncoming().isEmpty()) { + continue; // node originated from a cdg that has been scaffolded making it no longer a root + } + CollapsedDependencyGraphComponent tentativeComponent = overlayGraph.getCollapsedGraph().getComponentLocations().get(node.key.pos); + if (tentativeComponent != provenance) { + // TODO eventually figure out the situation with exclusiveDescendents like in the sequential dijkstra scaffolder + LongList toActivate = reconstructPathTo(node); + // have to do this bs because the scaffolding route can touch a third component even if only one scaffolding block is added + long[] allNearby = toActivate.stream().flatMapToLong(pos -> LongStream.of(OFFS_INCL_ZERO).map(off -> (off + pos) & BetterBlockPos.POST_ADDITION_MASK)).toArray(); + // have to check before applying scaffolding because getComponentLocations will return the new component and not the deleted root if we did it after + Set toCheck = LongStream.of(allNearby).mapToObj(overlayGraph.getCollapsedGraph().getComponentLocations()::get).collect(Collectors.toCollection(ObjectOpenHashSet::new)); + Scaffolder.applyScaffoldingConnection(overlayGraph, toActivate); + // have to check this again because new scaffolding can make its own collapsed node + // for example if there are two individual blocks separated by a knight's move in strict_y mode, meaning there are two new scaffolding blocks added at a certain y, connecting to one block at that y and another at a higher y, then the two new scaffolding can form a larger collapsed node, causing the previous block to be merged into it + // in short, it's possible for a new root to be created + toCheck.addAll(LongStream.of(allNearby).mapToObj(overlayGraph.getCollapsedGraph().getComponentLocations()::get).collect(Collectors.toSet())); + int sz = remainingRoots.size(); + for (CollapsedDependencyGraphComponent component : toCheck) { + if (component.deleted()) { + remainingRoots.remove(component); + } + } + if (remainingRoots.size() >= sz) { + throw new IllegalStateException(); + } + for (long pos : toActivate) { // im being lazy, this boxes to Long i think + CollapsedDependencyGraphComponent comp = overlayGraph.getCollapsedGraph().getComponentLocations().get(pos); + if (comp.getIncoming().isEmpty()) { + if (remainingRoots.add(comp)) { + int cdgid = roots.size(); + roots.add(comp); + componentToId.put(comp, cdgid); + } + ScaffoldingSearchNode newNode = new ScaffoldingSearchNode(new ScaffoldingSearchKey(pos, componentToId.getInt(comp))); + nodeMap.put(newNode.key, newNode); + openSet.add(newNode); + } + } + if (remainingRoots.size() == 1) { + return null; + } + if (remainingRoots.isEmpty()) { + throw new IllegalStateException(); + } + } + for (Face face : Face.VALUES) { + int newCost = node.costSoFar + DijkstraScaffolder.edgeCost(face); + if (overlayGraph.hypotheticalScaffoldingIncomingEdge(node.key.pos, face)) { + long neighborPos = face.offset(node.key.pos); + ScaffoldingSearchKey neighborKey = new ScaffoldingSearchKey(neighborPos, node.key.cdgid); + ScaffoldingSearchNode existingNode = nodeMap.get(neighborKey); + if (existingNode != null) { + if (existingNode.costSoFar > newCost) { + throw new IllegalStateException(); + } + continue; + } + ScaffoldingSearchNode newNode = new ScaffoldingSearchNode(neighborKey); + newNode.costSoFar = newCost; + newNode.prev = node; + nodeMap.put(newNode.key, newNode); + openSet.add(newNode); + } + } + } + throw new UnsupportedOperationException(); + } + + private static class ScaffoldingSearchKey { + long pos; + int cdgid; + + public ScaffoldingSearchKey(long pos, int cdgid) { + this.pos = pos; + this.cdgid = cdgid; + } + + @Override + public boolean equals(Object o) { + //if (this == o) return true; + //if (!(o instanceof ScaffoldingSearchKey)) return false; + ScaffoldingSearchKey that = (ScaffoldingSearchKey) o; + return pos == that.pos && cdgid == that.cdgid; + } + + @Override + public int hashCode() { + return HashCommon.murmurHash3(cdgid) + (int) HashCommon.murmurHash3(pos); + } + } + + private static class ScaffoldingSearchNode { + + private final ScaffoldingSearchKey key; + private int costSoFar; + private ScaffoldingSearchNode prev; + + private ScaffoldingSearchNode(ScaffoldingSearchKey key) { + this.key = key; + } + } + + private static LongList reconstructPathTo(ScaffoldingSearchNode end) { + LongList path = new LongArrayList(); + while (end != null) { + path.add(end.key.pos); + end = end.prev; + } + return path; + } + + private static final long[] OFFS_INCL_ZERO = new long[Face.NUM_FACES + 1]; + + static { + for (int i = 0; i < Face.NUM_FACES; i++) { + OFFS_INCL_ZERO[i] = Face.VALUES[i].offset; + } + } +} diff --git a/src/test/java/baritone/builder/ScaffolderTest.java b/src/test/java/baritone/builder/ScaffolderTest.java new file mode 100644 index 000000000..83c192541 --- /dev/null +++ b/src/test/java/baritone/builder/ScaffolderTest.java @@ -0,0 +1,77 @@ +/* + * 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 . + */ + +package baritone.builder; + +import baritone.api.utils.BetterBlockPos; +import org.junit.Test; + +import java.util.function.Consumer; +import java.util.stream.Collectors; + +public class ScaffolderTest { + @Test + public void test() { + BlockStateCachedData[][][] test = new BlockStateCachedData[8][8][8]; + PackedBlockStateCuboid.fillWithAir(test); + for (int x = 0; x < test.length; x++) { + for (int z = 0; z < test[0][0].length; z++) { + test[x][0][z] = FakeStates.SOLID; + test[x][1][z] = FakeStates.SOLID; + } + } + test[5][5][5] = FakeStates.SOLID; + test[5][5][6] = FakeStates.SOLID; + test[0][5][5] = FakeStates.SOLID; + Consumer debug = dgso -> { + for (int y = 0; y < test[0].length; y++) { + System.out.println("Layer " + y); + for (int x = 0; x < test.length; x++) { + for (int z = 0; z < test[0][0].length; z++) { + long pos = BetterBlockPos.toLong(x, y, z); + if (dgso.real(pos)) { + System.out.print("A" + dgso.getCollapsedGraph().getComponentLocations().get(pos).deletedIntoRecursive()); + } else { + System.out.print(" "); + } + } + System.out.println(); + } + } + }; + PackedBlockStateCuboid states = new PackedBlockStateCuboid(test); + PlaceOrderDependencyGraph graph = new PlaceOrderDependencyGraph(states); + System.out.println("N " + Face.NORTH.z); + System.out.println("S " + Face.SOUTH.z); + for (int z = 0; z < test[0][0].length; z++) { + //System.out.println(states.get(states.bounds.toIndex(0, 0, z))); + System.out.println(z + " " + graph.outgoingEdge(BetterBlockPos.toLong(0, 0, z), Face.NORTH) + " " + graph.outgoingEdge(BetterBlockPos.toLong(0, 0, z), Face.SOUTH)); + } + DependencyGraphAnalyzer.prevalidate(graph); + DependencyGraphAnalyzer.prevalidateExternalToInteriorSearch(graph); + DependencyGraphScaffoldingOverlay scaffolding = new DependencyGraphScaffoldingOverlay(graph); + System.out.println("Hewwo"); + scaffolding.getCollapsedGraph().getComponents().forEach((key, value) -> { + System.out.println(key); + System.out.println(value.getPositions().stream().map(BetterBlockPos::fromLong).collect(Collectors.toList())); + }); + System.out.println(); + debug.accept(scaffolding); + Scaffolder.Output out = Scaffolder.run(graph, DijkstraScaffolder.INSTANCE); + debug.accept(out.secretInternalForTesting()); + } +}