diff --git a/core/src/main/java/de/bluecolored/bluemap/core/world/ChunkConsumer.java b/core/src/main/java/de/bluecolored/bluemap/core/world/ChunkConsumer.java index 9f893a19..d231d12a 100644 --- a/core/src/main/java/de/bluecolored/bluemap/core/world/ChunkConsumer.java +++ b/core/src/main/java/de/bluecolored/bluemap/core/world/ChunkConsumer.java @@ -25,16 +25,16 @@ package de.bluecolored.bluemap.core.world; @FunctionalInterface -public interface ChunkConsumer { +public interface ChunkConsumer { default boolean filter(int chunkX, int chunkZ, int lastModified) { return true; } - void accept(int chunkX, int chunkZ, Chunk chunk); + void accept(int chunkX, int chunkZ, T chunk); @FunctionalInterface - interface ListOnly extends ChunkConsumer { + interface ListOnly extends ChunkConsumer { void accept(int chunkX, int chunkZ, int lastModified); @@ -45,7 +45,7 @@ default boolean filter(int chunkX, int chunkZ, int lastModified) { } @Override - default void accept(int chunkX, int chunkZ, Chunk chunk) { + default void accept(int chunkX, int chunkZ, T chunk) { throw new IllegalStateException("Should never be called."); } diff --git a/core/src/main/java/de/bluecolored/bluemap/core/world/Region.java b/core/src/main/java/de/bluecolored/bluemap/core/world/Region.java index d5ac390f..998d5cd9 100644 --- a/core/src/main/java/de/bluecolored/bluemap/core/world/Region.java +++ b/core/src/main/java/de/bluecolored/bluemap/core/world/Region.java @@ -26,15 +26,15 @@ import java.io.IOException; -public interface Region { +public interface Region { /** * Directly loads and returns the specified chunk.
* (implementations should consider overriding this method for a faster implementation) */ - default Chunk loadChunk(int chunkX, int chunkZ) throws IOException { - class SingleChunkConsumer implements ChunkConsumer { - private Chunk foundChunk = Chunk.EMPTY_CHUNK; + default T loadChunk(int chunkX, int chunkZ) throws IOException { + class SingleChunkConsumer implements ChunkConsumer { + private T foundChunk = emptyChunk(); @Override public boolean filter(int x, int z, int lastModified) { @@ -42,7 +42,7 @@ public boolean filter(int x, int z, int lastModified) { } @Override - public void accept(int chunkX, int chunkZ, Chunk chunk) { + public void accept(int chunkX, int chunkZ, T chunk) { this.foundChunk = chunk; } } @@ -54,11 +54,13 @@ public void accept(int chunkX, int chunkZ, Chunk chunk) { /** * Iterates over all chunks in this region and first calls {@link ChunkConsumer#filter(int, int, int)}.
- * And if (any only if) that method returned true, the chunk will be loaded and {@link ChunkConsumer#accept(int, int, Chunk)} + * And if (any only if) that method returned true, the chunk will be loaded and {@link ChunkConsumer#accept(int, int, T)} * will be called with the loaded chunk. * @param consumer the consumer choosing which chunks to load and accepting them * @throws IOException if an IOException occurred trying to read the region */ - void iterateAllChunks(ChunkConsumer consumer) throws IOException; + void iterateAllChunks(ChunkConsumer consumer) throws IOException; + + T emptyChunk(); } diff --git a/core/src/main/java/de/bluecolored/bluemap/core/world/mca/ChunkLoader.java b/core/src/main/java/de/bluecolored/bluemap/core/world/mca/ChunkLoader.java new file mode 100644 index 00000000..17e4b105 --- /dev/null +++ b/core/src/main/java/de/bluecolored/bluemap/core/world/mca/ChunkLoader.java @@ -0,0 +1,37 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package de.bluecolored.bluemap.core.world.mca; + +import de.bluecolored.bluemap.core.storage.compression.Compression; + +import java.io.IOException; + +public interface ChunkLoader { + + T load(byte[] data, int offset, int length, Compression compression) throws IOException; + + T emptyChunk(); + +} diff --git a/core/src/main/java/de/bluecolored/bluemap/core/world/mca/MCAWorld.java b/core/src/main/java/de/bluecolored/bluemap/core/world/mca/MCAWorld.java index 7754b7f7..258f0b98 100644 --- a/core/src/main/java/de/bluecolored/bluemap/core/world/mca/MCAWorld.java +++ b/core/src/main/java/de/bluecolored/bluemap/core/world/mca/MCAWorld.java @@ -37,7 +37,7 @@ import de.bluecolored.bluemap.core.util.Vector2iCache; import de.bluecolored.bluemap.core.util.WatchService; import de.bluecolored.bluemap.core.world.*; -import de.bluecolored.bluemap.core.world.mca.chunk.ChunkLoader; +import de.bluecolored.bluemap.core.world.mca.chunk.MCAChunkLoader; import de.bluecolored.bluemap.core.world.mca.data.DimensionTypeDeserializer; import de.bluecolored.bluemap.core.world.mca.data.LevelData; import de.bluecolored.bluemap.core.world.mca.region.RegionType; @@ -78,8 +78,8 @@ public class MCAWorld implements World { private final Path dimensionFolder; private final Path regionFolder; - private final ChunkLoader chunkLoader = new ChunkLoader(this); - private final LoadingCache regionCache = Caffeine.newBuilder() + private final MCAChunkLoader chunkLoader = new MCAChunkLoader(this); + private final LoadingCache> regionCache = Caffeine.newBuilder() .executor(BlueMap.THREAD_POOL) .softValues() .maximumSize(32) @@ -153,11 +153,11 @@ private Chunk getChunk(Vector2i pos) { } @Override - public Region getRegion(int x, int z) { + public Region getRegion(int x, int z) { return getRegion(VECTOR_2_I_CACHE.get(x, z)); } - private Region getRegion(Vector2i pos) { + private Region getRegion(Vector2i pos) { return regionCache.get(pos); } @@ -191,7 +191,7 @@ public WatchService createRegionWatchService() throws IOException { @Override public void preloadRegionChunks(int x, int z, Predicate chunkFilter) { try { - getRegion(x, z).iterateAllChunks(new ChunkConsumer() { + getRegion(x, z).iterateAllChunks(new ChunkConsumer<>() { @Override public boolean filter(int chunkX, int chunkZ, int lastModified) { Vector2i chunkPos = VECTOR_2_I_CACHE.get(chunkX, chunkZ); @@ -221,12 +221,12 @@ public void invalidateChunkCache(int x, int z) { chunkCache.invalidate(VECTOR_2_I_CACHE.get(x, z)); } - private Region loadRegion(Vector2i regionPos) { + private Region loadRegion(Vector2i regionPos) { return loadRegion(regionPos.getX(), regionPos.getY()); } - private Region loadRegion(int x, int z) { - return RegionType.loadRegion(this, getRegionFolder(), x, z); + private Region loadRegion(int x, int z) { + return RegionType.loadRegion(chunkLoader, getRegionFolder(), x, z); } private Chunk loadChunk(Vector2i chunkPos) { diff --git a/core/src/main/java/de/bluecolored/bluemap/core/world/mca/chunk/ChunkLoader.java b/core/src/main/java/de/bluecolored/bluemap/core/world/mca/chunk/MCAChunkLoader.java similarity index 93% rename from core/src/main/java/de/bluecolored/bluemap/core/world/mca/chunk/ChunkLoader.java rename to core/src/main/java/de/bluecolored/bluemap/core/world/mca/chunk/MCAChunkLoader.java index 450832c2..bb49d7a9 100644 --- a/core/src/main/java/de/bluecolored/bluemap/core/world/mca/chunk/ChunkLoader.java +++ b/core/src/main/java/de/bluecolored/bluemap/core/world/mca/chunk/MCAChunkLoader.java @@ -25,6 +25,8 @@ package de.bluecolored.bluemap.core.world.mca.chunk; import de.bluecolored.bluemap.core.storage.compression.Compression; +import de.bluecolored.bluemap.core.world.Chunk; +import de.bluecolored.bluemap.core.world.mca.ChunkLoader; import de.bluecolored.bluemap.core.world.mca.MCAUtil; import de.bluecolored.bluemap.core.world.mca.MCAWorld; import lombok.Getter; @@ -37,11 +39,11 @@ import java.util.List; import java.util.function.BiFunction; -public class ChunkLoader { +public class MCAChunkLoader implements ChunkLoader { private final MCAWorld world; - public ChunkLoader(MCAWorld world) { + public MCAChunkLoader(MCAWorld world) { this.world = world; } @@ -79,6 +81,11 @@ public MCAChunk load(byte[] data, int offset, int length, Compression compressio return chunk; } + @Override + public Chunk emptyChunk() { + return Chunk.EMPTY_CHUNK; + } + private @Nullable ChunkVersionLoader findBestLoaderForVersion(int version) { for (ChunkVersionLoader loader : CHUNK_VERSION_LOADERS) { if (loader.mightSupport(version)) return loader; diff --git a/core/src/main/java/de/bluecolored/bluemap/core/world/mca/region/LinearRegion.java b/core/src/main/java/de/bluecolored/bluemap/core/world/mca/region/LinearRegion.java index 24cf5e98..ab97a4ac 100644 --- a/core/src/main/java/de/bluecolored/bluemap/core/world/mca/region/LinearRegion.java +++ b/core/src/main/java/de/bluecolored/bluemap/core/world/mca/region/LinearRegion.java @@ -28,8 +28,7 @@ import de.bluecolored.bluemap.core.storage.compression.Compression; import de.bluecolored.bluemap.core.world.ChunkConsumer; import de.bluecolored.bluemap.core.world.Region; -import de.bluecolored.bluemap.core.world.mca.MCAWorld; -import de.bluecolored.bluemap.core.world.mca.chunk.MCAChunk; +import de.bluecolored.bluemap.core.world.mca.ChunkLoader; import lombok.Getter; import java.io.*; @@ -61,14 +60,14 @@ */ @Getter -public class LinearRegion implements Region { +public class LinearRegion implements Region { public static final String FILE_SUFFIX = ".linear"; public static final Pattern FILE_PATTERN = Pattern.compile("^r\\.(-?\\d+)\\.(-?\\d+)\\.linear$"); private static final long MAGIC = 0xc3ff13183cca9d9aL; - private final MCAWorld world; + private final ChunkLoader chunkLoader; private final Path regionFile; private final Vector2i regionPos; @@ -82,8 +81,8 @@ public class LinearRegion implements Region { private long dataHash; private byte[] compressedData; - public LinearRegion(MCAWorld world, Path regionFile) throws IllegalArgumentException { - this.world = world; + public LinearRegion(ChunkLoader chunkLoader, Path regionFile) throws IllegalArgumentException { + this.chunkLoader = chunkLoader; this.regionFile = regionFile; String[] filenameParts = regionFile.getFileName().toString().split("\\."); @@ -93,12 +92,6 @@ public LinearRegion(MCAWorld world, Path regionFile) throws IllegalArgumentExcep this.regionPos = new Vector2i(rX, rZ); } - public LinearRegion(MCAWorld world, Vector2i regionPos) throws IllegalArgumentException { - this.world = world; - this.regionPos = regionPos; - this.regionFile = world.getRegionFolder().resolve(getRegionFileName(regionPos.getX(), regionPos.getY())); - } - private synchronized void init() throws IOException { if (initialized) return; @@ -141,7 +134,7 @@ private synchronized void init() throws IOException { } @Override - public void iterateAllChunks(ChunkConsumer consumer) throws IOException { + public void iterateAllChunks(ChunkConsumer consumer) throws IOException { if (!initialized) init(); int chunkStartX = regionPos.getX() * 32; @@ -177,7 +170,7 @@ public void iterateAllChunks(ChunkConsumer consumer) throws IOException { chunkDataBuffer = new byte[length]; dIn.readFully(chunkDataBuffer, 0, length); - MCAChunk chunk = world.getChunkLoader().load(chunkDataBuffer, 0, length, Compression.NONE); + T chunk = chunkLoader.load(chunkDataBuffer, 0, length, Compression.NONE); consumer.accept(chunkX, chunkZ, chunk); } else { // skip before reading the next chunk, but only if there is a next chunk @@ -193,6 +186,11 @@ public void iterateAllChunks(ChunkConsumer consumer) throws IOException { } } + @Override + public T emptyChunk() { + return chunkLoader.emptyChunk(); + } + public static String getRegionFileName(int regionX, int regionZ) { return "r." + regionX + "." + regionZ + FILE_SUFFIX; } diff --git a/core/src/main/java/de/bluecolored/bluemap/core/world/mca/region/MCARegion.java b/core/src/main/java/de/bluecolored/bluemap/core/world/mca/region/MCARegion.java index f1cc273b..b20608b4 100644 --- a/core/src/main/java/de/bluecolored/bluemap/core/world/mca/region/MCARegion.java +++ b/core/src/main/java/de/bluecolored/bluemap/core/world/mca/region/MCARegion.java @@ -29,7 +29,8 @@ import de.bluecolored.bluemap.core.world.Chunk; import de.bluecolored.bluemap.core.world.ChunkConsumer; import de.bluecolored.bluemap.core.world.Region; -import de.bluecolored.bluemap.core.world.mca.MCAWorld; +import de.bluecolored.bluemap.core.world.mca.ChunkLoader; +import de.bluecolored.bluemap.core.world.mca.chunk.MCAChunkLoader; import de.bluecolored.bluemap.core.world.mca.chunk.MCAChunk; import lombok.Getter; @@ -43,7 +44,7 @@ import java.util.regex.Pattern; @Getter -public class MCARegion implements Region { +public class MCARegion implements Region { public static final String FILE_SUFFIX = ".mca"; public static final Pattern FILE_PATTERN = Pattern.compile("^r\\.(-?\\d+)\\.(-?\\d+)\\.mca$"); @@ -57,12 +58,12 @@ public class MCARegion implements Region { CHUNK_COMPRESSION_MAP[4] = Compression.LZ4; } - private final MCAWorld world; private final Path regionFile; + private final ChunkLoader chunkLoader; private final Vector2i regionPos; - public MCARegion(MCAWorld world, Path regionFile) throws IllegalArgumentException { - this.world = world; + public MCARegion(ChunkLoader chunkLoader, Path regionFile) throws IllegalArgumentException { + this.chunkLoader = chunkLoader; this.regionFile = regionFile; String[] filenameParts = regionFile.getFileName().toString().split("\\."); @@ -72,18 +73,12 @@ public MCARegion(MCAWorld world, Path regionFile) throws IllegalArgumentExceptio this.regionPos = new Vector2i(rX, rZ); } - public MCARegion(MCAWorld world, Vector2i regionPos) throws IllegalArgumentException { - this.world = world; - this.regionPos = regionPos; - this.regionFile = world.getRegionFolder().resolve(getRegionFileName(regionPos.getX(), regionPos.getY())); - } - @Override - public Chunk loadChunk(int chunkX, int chunkZ) throws IOException { - if (Files.notExists(regionFile)) return Chunk.EMPTY_CHUNK; + public T loadChunk(int chunkX, int chunkZ) throws IOException { + if (Files.notExists(regionFile)) return chunkLoader.emptyChunk(); long fileLength = Files.size(regionFile); - if (fileLength == 0) return Chunk.EMPTY_CHUNK; + if (fileLength == 0) return chunkLoader.emptyChunk(); try (FileChannel channel = FileChannel.open(regionFile, StandardOpenOption.READ)) { int xzChunk = (chunkZ & 0b11111) << 5 | (chunkX & 0b11111); @@ -98,7 +93,7 @@ public Chunk loadChunk(int chunkX, int chunkZ) throws IOException { offset *= 4096; int size = (header[3] & 0xFF) * 4096; - if (size == 0) return Chunk.EMPTY_CHUNK; + if (size == 0) return chunkLoader.emptyChunk(); byte[] chunkDataBuffer = new byte[size]; @@ -110,7 +105,7 @@ public Chunk loadChunk(int chunkX, int chunkZ) throws IOException { } @Override - public void iterateAllChunks(ChunkConsumer consumer) throws IOException { + public void iterateAllChunks(ChunkConsumer consumer) throws IOException { if (Files.notExists(regionFile)) return; long fileLength = Files.size(regionFile); @@ -157,7 +152,7 @@ public void iterateAllChunks(ChunkConsumer consumer) throws IOException { channel.position(offset); readFully(channel, chunkDataBuffer, 0, size); - MCAChunk chunk = loadChunk(chunkDataBuffer, size); + T chunk = loadChunk(chunkDataBuffer, size); consumer.accept(chunkX, chunkZ, chunk); } } @@ -165,13 +160,18 @@ public void iterateAllChunks(ChunkConsumer consumer) throws IOException { } } - private MCAChunk loadChunk(byte[] data, int size) throws IOException { + @Override + public T emptyChunk() { + return chunkLoader.emptyChunk(); + } + + private T loadChunk(byte[] data, int size) throws IOException { int compressionTypeId = Byte.toUnsignedInt(data[4]); Compression compression = CHUNK_COMPRESSION_MAP[compressionTypeId]; if (compression == null) throw new IOException("Unknown chunk compression-id: " + compressionTypeId); - return world.getChunkLoader().load(data, 5, size - 5, compression); + return chunkLoader.load(data, 5, size - 5, compression); } public static String getRegionFileName(int regionX, int regionZ) { diff --git a/core/src/main/java/de/bluecolored/bluemap/core/world/mca/region/RegionType.java b/core/src/main/java/de/bluecolored/bluemap/core/world/mca/region/RegionType.java index 00394346..4c68b1f7 100644 --- a/core/src/main/java/de/bluecolored/bluemap/core/world/mca/region/RegionType.java +++ b/core/src/main/java/de/bluecolored/bluemap/core/world/mca/region/RegionType.java @@ -29,7 +29,8 @@ import de.bluecolored.bluemap.core.util.Keyed; import de.bluecolored.bluemap.core.util.Registry; import de.bluecolored.bluemap.core.world.Region; -import de.bluecolored.bluemap.core.world.mca.MCAWorld; +import de.bluecolored.bluemap.core.world.mca.ChunkLoader; +import de.bluecolored.bluemap.core.world.mca.chunk.MCAChunkLoader; import lombok.Getter; import lombok.RequiredArgsConstructor; import org.jetbrains.annotations.Nullable; @@ -53,7 +54,7 @@ public interface RegionType extends Keyed { /** * Creates a new {@link Region} from the given world and region-file */ - Region createRegion(MCAWorld world, Path regionFile); + Region createRegion(ChunkLoader chunkLoader, Path regionFile); /** * Converts region coordinates into the region-file name. @@ -84,12 +85,12 @@ public interface RegionType extends Keyed { return null; } - static Region loadRegion(MCAWorld world, Path regionFolder, int regionX, int regionZ) { + static Region loadRegion(ChunkLoader chunkLoader, Path regionFolder, int regionX, int regionZ) { for (RegionType regionType : REGISTRY.values()) { Path regionFile = regionFolder.resolve(regionType.getRegionFileName(regionX, regionZ)); - if (Files.exists(regionFile)) return regionType.createRegion(world, regionFile); + if (Files.exists(regionFile)) return regionType.createRegion(chunkLoader, regionFile); } - return DEFAULT.createRegion(world, regionFolder.resolve(DEFAULT.getRegionFileName(regionX, regionZ))); + return DEFAULT.createRegion(chunkLoader, regionFolder.resolve(DEFAULT.getRegionFileName(regionX, regionZ))); } @RequiredArgsConstructor @@ -100,8 +101,8 @@ class Impl implements RegionType { private final RegionFileNameFunction regionFileNameFunction; private final Pattern regionFileNamePattern; - public Region createRegion(MCAWorld world, Path regionFile) { - return this.regionFactory.create(world, regionFile); + public Region createRegion(ChunkLoader chunkLoader, Path regionFile) { + return this.regionFactory.create(chunkLoader, regionFile); } public String getRegionFileName(int regionX, int regionZ) { @@ -122,7 +123,7 @@ public String getRegionFileName(int regionX, int regionZ) { @FunctionalInterface interface RegionFactory { - Region create(MCAWorld world, Path regionFile); + Region create(ChunkLoader chunkLoader, Path regionFile); } @FunctionalInterface