diff --git a/src/main/java/baritone/bot/Settings.java b/src/main/java/baritone/bot/Settings.java index 32b4920f..f3069051 100644 --- a/src/main/java/baritone/bot/Settings.java +++ b/src/main/java/baritone/bot/Settings.java @@ -163,7 +163,7 @@ public class Settings { /** * The big one. Download all chunks in simplified 2-bit format and save them for better very-long-distance pathing. */ - public Setting chuckCaching = new Setting<>(false); + public Setting chunkCaching = new Setting<>(true); /** * Print all the debug messages to chat diff --git a/src/main/java/baritone/bot/chunk/CachedChunk.java b/src/main/java/baritone/bot/chunk/CachedChunk.java index c85cc88c..8316345f 100644 --- a/src/main/java/baritone/bot/chunk/CachedChunk.java +++ b/src/main/java/baritone/bot/chunk/CachedChunk.java @@ -40,11 +40,6 @@ public final class CachedChunk implements IBlockTypeAccess { */ public static final int SIZE_IN_BYTES = SIZE / 8; - /** - * An array of just 0s with the length of {@link CachedChunk#SIZE_IN_BYTES} - */ - public static final byte[] EMPTY_CHUNK = new byte[SIZE_IN_BYTES]; - /** * The chunk x coordinate */ diff --git a/src/main/java/baritone/bot/chunk/CachedRegion.java b/src/main/java/baritone/bot/chunk/CachedRegion.java index b7bc3580..8f2dfc9b 100644 --- a/src/main/java/baritone/bot/chunk/CachedRegion.java +++ b/src/main/java/baritone/bot/chunk/CachedRegion.java @@ -17,25 +17,30 @@ package baritone.bot.chunk; +import baritone.bot.utils.pathing.IBlockTypeAccess; import baritone.bot.utils.pathing.PathingBlockType; -import baritone.bot.utils.GZIPUtils; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; +import java.io.*; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.Arrays; import java.util.BitSet; import java.util.function.Consumer; +import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; /** * @author Brady * @since 8/3/2018 9:35 PM */ -public final class CachedRegion implements ICachedChunkAccess { +public final class CachedRegion implements IBlockTypeAccess { + private static final byte CHUNK_NOT_PRESENT = 0; + private static final byte CHUNK_PRESENT = 1; + + /** + * Magic value to detect invalid cache files, or incompatible cache files saved in an old version of Baritone + */ + private static final int CACHED_REGION_MAGIC = 456022910; /** * All of the chunks in this region: A 32x32 array of them. @@ -52,9 +57,15 @@ public final class CachedRegion implements ICachedChunkAccess { */ private final int z; + /** + * Has this region been modified since its most recent load or save + */ + private boolean hasUnsavedChanges; + CachedRegion(int x, int z) { this.x = x; this.z = z; + this.hasUnsavedChanges = false; } @Override @@ -66,14 +77,14 @@ public final class CachedRegion implements ICachedChunkAccess { return null; } - @Override - public final void updateCachedChunk(int chunkX, int chunkZ, BitSet data) { + final void updateCachedChunk(int chunkX, int chunkZ, BitSet data) { CachedChunk chunk = this.getChunk(chunkX, chunkZ); if (chunk == null) { this.chunks[chunkX][chunkZ] = new CachedChunk(chunkX, chunkZ, data); } else { chunk.updateContents(data); } + hasUnsavedChanges = true; } private CachedChunk getChunk(int chunkX, int chunkZ) { @@ -92,21 +103,31 @@ public final class CachedRegion implements ICachedChunkAccess { } public final void save(String directory) { + if (!hasUnsavedChanges) { + return; + } try { Path path = Paths.get(directory); if (!Files.exists(path)) Files.createDirectories(path); + System.out.println("Saving region " + x + "," + z + " to disk " + path); Path regionFile = getRegionFile(path, this.x, this.z); if (!Files.exists(regionFile)) Files.createFile(regionFile); - try (FileOutputStream fileOut = new FileOutputStream(regionFile.toFile()); GZIPOutputStream out = new GZIPOutputStream(fileOut)) { + try ( + FileOutputStream fileOut = new FileOutputStream(regionFile.toFile()); + GZIPOutputStream gzipOut = new GZIPOutputStream(fileOut); + DataOutputStream out = new DataOutputStream(gzipOut); + ) { + out.writeInt(CACHED_REGION_MAGIC); for (int z = 0; z < 32; z++) { for (int x = 0; x < 32; x++) { CachedChunk chunk = this.chunks[x][z]; if (chunk == null) { - out.write(CachedChunk.EMPTY_CHUNK); + out.write(CHUNK_NOT_PRESENT); } else { + out.write(CHUNK_PRESENT); byte[] chunkBytes = chunk.toByteArray(); out.write(chunkBytes); // Messy, but fills the empty 0s that should be trailing to fill up the space. @@ -114,8 +135,13 @@ public final class CachedRegion implements ICachedChunkAccess { } } } + out.writeInt(~CACHED_REGION_MAGIC); } - } catch (IOException ignored) {} + hasUnsavedChanges = false; + System.out.println("Saved region successfully"); + } catch (IOException ex) { + ex.printStackTrace(); + } } public void load(String directory) { @@ -128,27 +154,48 @@ public final class CachedRegion implements ICachedChunkAccess { if (!Files.exists(regionFile)) return; - byte[] decompressed; - try (FileInputStream in = new FileInputStream(regionFile.toFile())) { - decompressed = GZIPUtils.decompress(in); - } + System.out.println("Loading region " + x + "," + z + " from disk " + path); - if (decompressed == null) - return; - - for (int z = 0; z < 32; z++) { - for (int x = 0; x < 32; x++) { - int index = (x + (z << 5)) * CachedChunk.SIZE_IN_BYTES; - byte[] bytes = Arrays.copyOfRange(decompressed, index, index + CachedChunk.SIZE_IN_BYTES); - if (isAllZeros(bytes)) { - this.chunks[x][z] = null; - } else { - BitSet bits = BitSet.valueOf(bytes); - updateCachedChunk(x, z, bits); + try ( + FileInputStream fileIn = new FileInputStream(regionFile.toFile()); + GZIPInputStream gzipIn = new GZIPInputStream(fileIn); + DataInputStream in = new DataInputStream(gzipIn); + ) { + int magic = in.readInt(); + if (magic != CACHED_REGION_MAGIC) { + // in the future, if we change the format on disk + // we can keep converters for the old format + // by switching on the magic value, and either loading it normally, or loading through a converter. + throw new IOException("Bad magic value " + magic); + } + for (int z = 0; z < 32; z++) { + for (int x = 0; x < 32; x++) { + int isChunkPresent = in.read(); + switch (isChunkPresent) { + case CHUNK_PRESENT: + byte[] bytes = new byte[CachedChunk.SIZE_IN_BYTES]; + in.readFully(bytes); + BitSet bits = BitSet.valueOf(bytes); + updateCachedChunk(x, z, bits); + break; + case CHUNK_NOT_PRESENT: + this.chunks[x][z] = null; + break; + default: + throw new IOException("Malformed stream"); + } } } + int fileEndMagic = in.readInt(); + if (fileEndMagic != ~magic) { + throw new IOException("Bad end of file magic"); + } } - } catch (IOException ignored) {} + hasUnsavedChanges = false; + System.out.println("Loaded region successfully"); + } catch (IOException ex) { + ex.printStackTrace(); + } } private static boolean isAllZeros(final byte[] array) { diff --git a/src/main/java/baritone/bot/chunk/CachedWorld.java b/src/main/java/baritone/bot/chunk/CachedWorld.java index f8b2c216..d3900f6f 100644 --- a/src/main/java/baritone/bot/chunk/CachedWorld.java +++ b/src/main/java/baritone/bot/chunk/CachedWorld.java @@ -17,18 +17,21 @@ package baritone.bot.chunk; +import baritone.bot.utils.pathing.IBlockTypeAccess; import baritone.bot.utils.pathing.PathingBlockType; import it.unimi.dsi.fastutil.longs.Long2ObjectMap; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; +import net.minecraft.world.chunk.Chunk; import java.util.BitSet; +import java.util.concurrent.LinkedBlockingQueue; import java.util.function.Consumer; /** * @author Brady * @since 8/4/2018 12:02 AM */ -public final class CachedWorld implements ICachedChunkAccess { +public final class CachedWorld implements IBlockTypeAccess { /** * The maximum number of regions in any direction from (0,0) @@ -45,41 +48,42 @@ public final class CachedWorld implements ICachedChunkAccess { */ private final String directory; + private final LinkedBlockingQueue toPack = new LinkedBlockingQueue<>(); + public CachedWorld(String directory) { this.directory = directory; // Insert an invalid region element cachedRegions.put(0, null); + new PackerThread().start(); + } + + public final void queueForPacking(Chunk chunk) { + try { + toPack.put(chunk); + } catch (InterruptedException e) { + e.printStackTrace(); + } } @Override public final PathingBlockType getBlockType(int x, int y, int z) { - CachedRegion region = getRegion(x >> 9, z >> 9); - if (region != null) { - return region.getBlockType(x & 511, y, z & 511); - } - return null; + CachedRegion region = getOrCreateRegion(x >> 9, z >> 9); + return region.getBlockType(x & 511, y, z & 511); } - @Override - public final void updateCachedChunk(int chunkX, int chunkZ, BitSet data) { + private void updateCachedChunk(int chunkX, int chunkZ, BitSet data) { CachedRegion region = getOrCreateRegion(chunkX >> 5, chunkZ >> 5); - if (region != null) { - region.updateCachedChunk(chunkX & 31, chunkZ & 31, data); - } + region.updateCachedChunk(chunkX & 31, chunkZ & 31, data); } public final void save() { + long start = System.currentTimeMillis(); this.cachedRegions.values().forEach(region -> { if (region != null) region.save(this.directory); }); - } - - public final void load() { - this.cachedRegions.values().forEach(region -> { - if (region != null) - region.load(this.directory); - }); + long now = System.currentTimeMillis(); + System.out.println("World save took " + (now - start) + "ms"); } /** @@ -141,4 +145,24 @@ public final class CachedWorld implements ICachedChunkAccess { private boolean isRegionInWorld(int regionX, int regionZ) { return regionX <= REGION_MAX && regionX >= -REGION_MAX && regionZ <= REGION_MAX && regionZ >= -REGION_MAX; } + + private class PackerThread extends Thread { + public void run() { + while (true) { + LinkedBlockingQueue queue = toPack; + if (queue == null) { + break; + } + try { + Chunk chunk = queue.take(); + BitSet packed = ChunkPacker.createPackedChunk(chunk); + CachedWorld.this.updateCachedChunk(chunk.x, chunk.z, packed); + //System.out.println("Processed chunk at " + chunk.x + "," + chunk.z); + } catch (InterruptedException e) { + e.printStackTrace(); + break; + } + } + } + } } diff --git a/src/main/java/baritone/bot/chunk/CachedWorldProvider.java b/src/main/java/baritone/bot/chunk/CachedWorldProvider.java index e5ff7509..b59c3a52 100644 --- a/src/main/java/baritone/bot/chunk/CachedWorldProvider.java +++ b/src/main/java/baritone/bot/chunk/CachedWorldProvider.java @@ -31,8 +31,6 @@ import java.nio.file.Path; import java.util.HashMap; import java.util.Map; import java.util.function.Consumer; -import java.util.regex.Matcher; -import java.util.regex.Pattern; /** * @author Brady @@ -42,8 +40,6 @@ public enum CachedWorldProvider implements Helper { INSTANCE; - private static final Pattern REGION_REGEX = Pattern.compile("r\\.(-?[0-9]+)\\.(-?[0-9]+)\\.bcr"); - private final Map singlePlayerWorldCache = new HashMap<>(); private CachedWorld currentWorld; @@ -68,27 +64,22 @@ public enum CachedWorldProvider implements Helper { } this.currentWorld = this.singlePlayerWorldCache.computeIfAbsent(dir.toString(), CachedWorld::new); - - try { - Files.list(dir).forEach(path -> { - String file = path.getFileName().toString(); - Matcher matcher = REGION_REGEX.matcher(file); - if (matcher.matches()) { - int rx = Integer.parseInt(matcher.group(1)); - int ry = Integer.parseInt(matcher.group(2)); - // Recognize the region for when we load from file - this.currentWorld.getOrCreateRegion(rx, ry); - } - }); - } catch (Exception ignored) {} - - this.currentWorld.load(); } // TODO: Store server worlds } public final void closeWorld() { + CachedWorld world = this.currentWorld; this.currentWorld = null; + if (world == null) { + return; + } + new Thread() { + public void run() { + System.out.println("Started saving the world in a new thread"); + world.save(); + } + }.start(); } public final void ifWorldLoaded(Consumer currentWorldConsumer) { diff --git a/src/main/java/baritone/bot/chunk/ChunkPacker.java b/src/main/java/baritone/bot/chunk/ChunkPacker.java index 7b8fbbe4..de8f3179 100644 --- a/src/main/java/baritone/bot/chunk/ChunkPacker.java +++ b/src/main/java/baritone/bot/chunk/ChunkPacker.java @@ -18,9 +18,9 @@ package baritone.bot.chunk; import baritone.bot.pathing.movement.MovementHelper; -import baritone.bot.utils.pathing.PathingBlockType; import baritone.bot.utils.BlockStateInterface; import baritone.bot.utils.Helper; +import baritone.bot.utils.pathing.PathingBlockType; import net.minecraft.block.Block; import net.minecraft.block.BlockAir; import net.minecraft.block.state.IBlockState; @@ -29,8 +29,6 @@ import net.minecraft.world.chunk.Chunk; import java.util.BitSet; -import static net.minecraft.block.Block.NULL_AABB; - /** * @author Brady * @since 8/3/2018 1:09 AM @@ -40,6 +38,7 @@ public final class ChunkPacker implements Helper { private ChunkPacker() {} public static BitSet createPackedChunk(Chunk chunk) { + long start = System.currentTimeMillis(); BitSet bitSet = new BitSet(CachedChunk.SIZE); try { for (int y = 0; y < 256; y++) { @@ -55,6 +54,8 @@ public final class ChunkPacker implements Helper { } catch (Exception e) { e.printStackTrace(); } + long end = System.currentTimeMillis(); + //System.out.println("Chunk packing took " + (end - start) + "ms for " + chunk.x + "," + chunk.z); return bitSet; } @@ -69,7 +70,7 @@ public final class ChunkPacker implements Helper { return PathingBlockType.AVOID; } - if (block instanceof BlockAir || state.getCollisionBoundingBox(mc.world, pos) == NULL_AABB) { + if (block instanceof BlockAir) { return PathingBlockType.AIR; } diff --git a/src/main/java/baritone/bot/chunk/ICachedChunkAccess.java b/src/main/java/baritone/bot/chunk/ICachedChunkAccess.java deleted file mode 100644 index 9cb4e649..00000000 --- a/src/main/java/baritone/bot/chunk/ICachedChunkAccess.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * This file is part of Baritone. - * - * Baritone is free software: you can redistribute it and/or modify - * it under the terms of the GNU 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Baritone. If not, see . - */ - -package baritone.bot.chunk; - -import baritone.bot.utils.pathing.IBlockTypeAccess; - -import java.util.BitSet; - -/** - * @author Brady - * @since 8/4/2018 1:10 AM - */ -public interface ICachedChunkAccess extends IBlockTypeAccess { - - void updateCachedChunk(int chunkX, int chunkZ, BitSet data); -} diff --git a/src/main/java/baritone/bot/event/GameEventHandler.java b/src/main/java/baritone/bot/event/GameEventHandler.java index b6f365e9..1ffe9861 100644 --- a/src/main/java/baritone/bot/event/GameEventHandler.java +++ b/src/main/java/baritone/bot/event/GameEventHandler.java @@ -35,9 +35,7 @@ package baritone.bot.event; import baritone.bot.Baritone; -import baritone.bot.chunk.CachedWorld; import baritone.bot.chunk.CachedWorldProvider; -import baritone.bot.chunk.ChunkPacker; import baritone.bot.event.events.*; import baritone.bot.event.events.type.EventState; import baritone.bot.event.listener.IGameEventListener; @@ -49,6 +47,7 @@ import net.minecraft.client.renderer.GlStateManager; import net.minecraft.client.renderer.Tessellator; import net.minecraft.client.renderer.vertex.DefaultVertexFormats; import net.minecraft.client.settings.KeyBinding; +import net.minecraft.world.chunk.Chunk; import org.lwjgl.input.Keyboard; import java.util.ArrayList; @@ -112,11 +111,12 @@ public final class GameEventHandler implements IGameEventListener, Helper { && type == ChunkEvent.Type.UNLOAD && mc.world.getChunkProvider().isChunkGeneratedAt(event.getX(), event.getZ()); - if (Baritone.settings().chuckCaching.get()) { + if (Baritone.settings().chunkCaching.get()) { if (isPostPopulate || isPreUnload) { - CachedWorldProvider.INSTANCE.ifWorldLoaded(world -> - world.updateCachedChunk(event.getX(), event.getZ(), - ChunkPacker.createPackedChunk(mc.world.getChunk(event.getX(), event.getZ())))); + CachedWorldProvider.INSTANCE.ifWorldLoaded(world -> { + Chunk chunk = mc.world.getChunk(event.getX(), event.getZ()); + world.queueForPacking(chunk); + }); } } @@ -136,12 +136,12 @@ public final class GameEventHandler implements IGameEventListener, Helper { @Override public final void onWorldEvent(WorldEvent event) { - if (Baritone.settings().chuckCaching.get()) { + if (Baritone.settings().chunkCaching.get()) { CachedWorldProvider cache = CachedWorldProvider.INSTANCE; switch (event.getState()) { case PRE: - cache.ifWorldLoaded(CachedWorld::save); + cache.closeWorld(); break; case POST: cache.closeWorld(); diff --git a/src/main/java/baritone/bot/pathing/calc/AStarPathFinder.java b/src/main/java/baritone/bot/pathing/calc/AStarPathFinder.java index e34a7eff..9f60a80c 100644 --- a/src/main/java/baritone/bot/pathing/calc/AStarPathFinder.java +++ b/src/main/java/baritone/bot/pathing/calc/AStarPathFinder.java @@ -94,7 +94,7 @@ public class AStarPathFinder extends AbstractNodeCostSearch implements Helper { int numNodes = 0; int numEmptyChunk = 0; boolean favoring = favoredPositions.isPresent(); - boolean cache = Baritone.settings().chuckCaching.get(); // grab all settings beforehand so that changing settings during pathing doesn't cause a crash or unpredictable behavior + boolean cache = Baritone.settings().chunkCaching.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(); double favorCoeff = Baritone.settings().backtrackCostFavoringCoefficient.get(); boolean minimumImprovementRepropagation = Baritone.settings().minimumImprovementRepropagation.get(); diff --git a/src/main/java/baritone/bot/utils/BlockStateInterface.java b/src/main/java/baritone/bot/utils/BlockStateInterface.java index cddd7351..591f43cb 100644 --- a/src/main/java/baritone/bot/utils/BlockStateInterface.java +++ b/src/main/java/baritone/bot/utils/BlockStateInterface.java @@ -41,7 +41,7 @@ public class BlockStateInterface implements Helper { if (chunk.isLoaded()) { return chunk.getBlockState(pos); } - if (Baritone.settings().chuckCaching.get()) { + if (Baritone.settings().chunkCaching.get()) { CachedWorld world = CachedWorldProvider.INSTANCE.getCurrentWorld(); if (world != null) { PathingBlockType type = world.getBlockType(pos); diff --git a/src/main/java/baritone/bot/utils/ExampleBaritoneControl.java b/src/main/java/baritone/bot/utils/ExampleBaritoneControl.java index a200789d..0fc62b71 100644 --- a/src/main/java/baritone/bot/utils/ExampleBaritoneControl.java +++ b/src/main/java/baritone/bot/utils/ExampleBaritoneControl.java @@ -23,7 +23,10 @@ import baritone.bot.behavior.Behavior; import baritone.bot.behavior.impl.PathingBehavior; import baritone.bot.event.events.ChatEvent; import baritone.bot.pathing.calc.AStarPathFinder; -import baritone.bot.pathing.goals.*; +import baritone.bot.pathing.goals.Goal; +import baritone.bot.pathing.goals.GoalBlock; +import baritone.bot.pathing.goals.GoalXZ; +import baritone.bot.pathing.goals.GoalYLevel; import baritone.bot.pathing.movement.ActionCosts; import baritone.bot.pathing.movement.CalculationContext; import baritone.bot.pathing.movement.Movement; diff --git a/src/main/java/baritone/bot/utils/GZIPUtils.java b/src/main/java/baritone/bot/utils/GZIPUtils.java deleted file mode 100644 index 7b15e580..00000000 --- a/src/main/java/baritone/bot/utils/GZIPUtils.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * This file is part of Baritone. - * - * Baritone is free software: you can redistribute it and/or modify - * it under the terms of the GNU 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Baritone. If not, see . - */ - -package baritone.bot.utils; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.zip.GZIPInputStream; -import java.util.zip.GZIPOutputStream; - -/** - * @author Brady - * @since 8/3/2018 10:18 PM - */ -public final class GZIPUtils { - - private GZIPUtils() { - } - - public static byte[] compress(byte[] in) throws IOException { - ByteArrayOutputStream byteStream = new ByteArrayOutputStream(in.length); - - try (GZIPOutputStream gzipStream = new GZIPOutputStream(byteStream)) { - gzipStream.write(in); - } - return byteStream.toByteArray(); - } - - public static byte[] decompress(InputStream in) throws IOException { - ByteArrayOutputStream outStream = new ByteArrayOutputStream(); - - try (GZIPInputStream gzipStream = new GZIPInputStream(in)) { - byte[] buffer = new byte[1024]; - int len; - while ((len = gzipStream.read(buffer)) > 0) { - outStream.write(buffer, 0, len); - } - } - return outStream.toByteArray(); - } -}