diff --git a/src/api/java/baritone/api/Settings.java b/src/api/java/baritone/api/Settings.java index fdbaaf45..97337d2a 100644 --- a/src/api/java/baritone/api/Settings.java +++ b/src/api/java/baritone/api/Settings.java @@ -404,6 +404,12 @@ public class Settings { */ public Setting followRadius = new Setting<>(3); + /** + * Cached chunks (regardless of if they're in RAM or saved to disk) expire and are deleted after this number of seconds + * -1 to disable + */ + public Setting cachedChunksExpirySeconds = new Setting<>(-1L); + /** * The function that is called when Baritone will log to chat. This function can be added to * via {@link Consumer#andThen(Consumer)} or it can completely be overriden via setting diff --git a/src/main/java/baritone/cache/CachedChunk.java b/src/main/java/baritone/cache/CachedChunk.java index b8d88a49..317bf377 100644 --- a/src/main/java/baritone/cache/CachedChunk.java +++ b/src/main/java/baritone/cache/CachedChunk.java @@ -106,7 +106,9 @@ public final class CachedChunk implements IBlockTypeAccess, Helper { private final Map> specialBlockLocations; - CachedChunk(int x, int z, BitSet data, IBlockState[] overview, Map> specialBlockLocations) { + public final long cacheTimestamp; + + CachedChunk(int x, int z, BitSet data, IBlockState[] overview, Map> specialBlockLocations, long cacheTimestamp) { validateSize(data); this.x = x; @@ -115,6 +117,7 @@ public final class CachedChunk implements IBlockTypeAccess, Helper { this.overview = overview; this.heightMap = new int[256]; this.specialBlockLocations = specialBlockLocations; + this.cacheTimestamp = cacheTimestamp; calculateHeightMap(); } diff --git a/src/main/java/baritone/cache/CachedRegion.java b/src/main/java/baritone/cache/CachedRegion.java index e9402bd5..200e0f16 100644 --- a/src/main/java/baritone/cache/CachedRegion.java +++ b/src/main/java/baritone/cache/CachedRegion.java @@ -17,6 +17,7 @@ package baritone.cache; +import baritone.Baritone; import baritone.api.cache.ICachedRegion; import net.minecraft.block.state.IBlockState; import net.minecraft.util.math.BlockPos; @@ -112,6 +113,7 @@ public final class CachedRegion implements ICachedRegion { if (!hasUnsavedChanges) { return; } + removeExpired(); try { Path path = Paths.get(directory); if (!Files.exists(path)) { @@ -129,8 +131,8 @@ public final class CachedRegion implements ICachedRegion { DataOutputStream out = new DataOutputStream(gzipOut) ) { out.writeInt(CACHED_REGION_MAGIC); - for (int z = 0; z < 32; z++) { - for (int x = 0; x < 32; x++) { + for (int x = 0; x < 32; x++) { + for (int z = 0; z < 32; z++) { CachedChunk chunk = this.chunks[x][z]; if (chunk == null) { out.write(CHUNK_NOT_PRESENT); @@ -143,8 +145,8 @@ public final class CachedRegion implements ICachedRegion { } } } - for (int z = 0; z < 32; z++) { - for (int x = 0; x < 32; x++) { + for (int x = 0; x < 32; x++) { + for (int z = 0; z < 32; z++) { if (chunks[x][z] != null) { for (int i = 0; i < 256; i++) { out.writeUTF(ChunkPacker.blockToString(chunks[x][z].getOverview()[i].getBlock())); @@ -152,8 +154,8 @@ public final class CachedRegion implements ICachedRegion { } } } - for (int z = 0; z < 32; z++) { - for (int x = 0; x < 32; x++) { + for (int x = 0; x < 32; x++) { + for (int z = 0; z < 32; z++) { if (chunks[x][z] != null) { Map> locs = chunks[x][z].getRelativeBlocks(); out.writeShort(locs.entrySet().size()); @@ -168,10 +170,17 @@ public final class CachedRegion implements ICachedRegion { } } } + for (int x = 0; x < 32; x++) { + for (int z = 0; z < 32; z++) { + if (chunks[x][z] != null) { + out.writeLong(chunks[x][z].cacheTimestamp); + } + } + } } hasUnsavedChanges = false; System.out.println("Saved region successfully"); - } catch (IOException ex) { + } catch (Exception ex) { ex.printStackTrace(); } } @@ -203,42 +212,42 @@ public final class CachedRegion implements ICachedRegion { // by switching on the magic value, and either loading it normally, or loading through a converter. throw new IOException("Bad magic value " + magic); } - CachedChunk[][] tmpCached = new CachedChunk[32][32]; + boolean[][] present = new boolean[32][32]; + BitSet[][] bitSets = new BitSet[32][32]; Map>[][] location = new Map[32][32]; - for (int z = 0; z < 32; z++) { - for (int x = 0; x < 32; x++) { + IBlockState[][][] overview = new IBlockState[32][32][]; + long[][] cacheTimestamp = new long[32][32]; + for (int x = 0; x < 32; x++) { + for (int z = 0; z < 32; z++) { int isChunkPresent = in.read(); switch (isChunkPresent) { case CHUNK_PRESENT: byte[] bytes = new byte[CachedChunk.SIZE_IN_BYTES]; in.readFully(bytes); + bitSets[x][z] = BitSet.valueOf(bytes); location[x][z] = new HashMap<>(); - int regionX = this.x; - int regionZ = this.z; - int chunkX = x + 32 * regionX; - int chunkZ = z + 32 * regionZ; - tmpCached[x][z] = new CachedChunk(chunkX, chunkZ, BitSet.valueOf(bytes), new IBlockState[256], location[x][z]); + overview[x][z] = new IBlockState[256]; + present[x][z] = true; break; case CHUNK_NOT_PRESENT: - tmpCached[x][z] = null; break; default: throw new IOException("Malformed stream"); } } } - for (int z = 0; z < 32; z++) { - for (int x = 0; x < 32; x++) { - if (tmpCached[x][z] != null) { + for (int x = 0; x < 32; x++) { + for (int z = 0; z < 32; z++) { + if (present[x][z]) { for (int i = 0; i < 256; i++) { - tmpCached[x][z].getOverview()[i] = ChunkPacker.stringToBlock(in.readUTF()).getDefaultState(); + overview[x][z][i] = ChunkPacker.stringToBlock(in.readUTF()).getDefaultState(); } } } } - for (int z = 0; z < 32; z++) { - for (int x = 0; x < 32; x++) { - if (tmpCached[x][z] != null) { + for (int x = 0; x < 32; x++) { + for (int z = 0; z < 32; z++) { + if (present[x][z]) { // 16 * 16 * 256 = 65536 so a short is enough // ^ haha jokes on leijurv, java doesn't have unsigned types so that isn't correct // also why would you have more than 32767 special blocks in a chunk @@ -264,21 +273,52 @@ public final class CachedRegion implements ICachedRegion { } } } + for (int x = 0; x < 32; x++) { + for (int z = 0; z < 32; z++) { + if (present[x][z]) { + cacheTimestamp[x][z] = in.readLong(); + } + } + } // only if the entire file was uncorrupted do we actually set the chunks for (int x = 0; x < 32; x++) { for (int z = 0; z < 32; z++) { - this.chunks[x][z] = tmpCached[x][z]; + if (present[x][z]) { + int regionX = this.x; + int regionZ = this.z; + int chunkX = x + 32 * regionX; + int chunkZ = z + 32 * regionZ; + this.chunks[x][z] = new CachedChunk(chunkX, chunkZ, bitSets[x][z], overview[x][z], location[x][z], cacheTimestamp[x][z]); + } } } } + removeExpired(); hasUnsavedChanges = false; long end = System.nanoTime() / 1000000L; System.out.println("Loaded region successfully in " + (end - start) + "ms"); - } catch (IOException ex) { + } catch (Exception ex) { // corrupted files can cause NullPointerExceptions as well as IOExceptions ex.printStackTrace(); } } + public synchronized final void removeExpired() { + long expiry = Baritone.settings().cachedChunksExpirySeconds.get(); + if (expiry < 0) { + return; + } + long now = System.currentTimeMillis(); + long oldestAcceptableAge = now - expiry * 1000L; + for (int x = 0; x < 32; x++) { + for (int z = 0; z < 32; z++) { + if (this.chunks[x][z] != null && this.chunks[x][z].cacheTimestamp < oldestAcceptableAge) { + System.out.println("Removing chunk " + (x + 32 * this.x) + "," + (z + 32 * this.z) + " because it was cached " + (now - this.chunks[x][z].cacheTimestamp) / 1000L + " seconds ago, and max age is " + expiry); + this.chunks[x][z] = null; + } + } + } + } + /** * @return The region x coordinate */ diff --git a/src/main/java/baritone/cache/CachedWorld.java b/src/main/java/baritone/cache/CachedWorld.java index 3901303d..db626aca 100644 --- a/src/main/java/baritone/cache/CachedWorld.java +++ b/src/main/java/baritone/cache/CachedWorld.java @@ -142,6 +142,7 @@ public final class CachedWorld implements ICachedWorld, Helper { public final void save() { if (!Baritone.settings().chunkCaching.get()) { System.out.println("Not saving to disk; chunk caching is disabled."); + allRegions().forEach(CachedRegion::removeExpired); // even if we aren't saving to disk, still delete expired old chunks from RAM return; } long start = System.nanoTime() / 1000000L; diff --git a/src/main/java/baritone/cache/ChunkPacker.java b/src/main/java/baritone/cache/ChunkPacker.java index 4164be3c..e29bde00 100644 --- a/src/main/java/baritone/cache/ChunkPacker.java +++ b/src/main/java/baritone/cache/ChunkPacker.java @@ -106,7 +106,7 @@ public final class ChunkPacker implements Helper { blocks[z << 4 | x] = Blocks.AIR.getDefaultState(); } } - return new CachedChunk(chunk.x, chunk.z, bitSet, blocks, specialBlocks); + return new CachedChunk(chunk.x, chunk.z, bitSet, blocks, specialBlocks, System.currentTimeMillis()); } public static String blockToString(Block block) {