mirror of
https://github.com/BlueMap-Minecraft/BlueMap.git
synced 2024-11-24 19:46:29 +01:00
Make regions generic to support different types of chunks
This commit is contained in:
parent
35fbcff370
commit
8b1c5ab3c2
@ -25,16 +25,16 @@
|
||||
package de.bluecolored.bluemap.core.world;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface ChunkConsumer {
|
||||
public interface ChunkConsumer<T> {
|
||||
|
||||
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<T> extends ChunkConsumer<T> {
|
||||
|
||||
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.");
|
||||
}
|
||||
|
||||
|
@ -26,15 +26,15 @@
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public interface Region {
|
||||
public interface Region<T> {
|
||||
|
||||
/**
|
||||
* Directly loads and returns the specified chunk.<br>
|
||||
* (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<T> {
|
||||
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)}.<br>
|
||||
* And if (any only if) that method returned <code>true</code>, the chunk will be loaded and {@link ChunkConsumer#accept(int, int, Chunk)}
|
||||
* And if (any only if) that method returned <code>true</code>, 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<T> consumer) throws IOException;
|
||||
|
||||
T emptyChunk();
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,37 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* 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> {
|
||||
|
||||
T load(byte[] data, int offset, int length, Compression compression) throws IOException;
|
||||
|
||||
T emptyChunk();
|
||||
|
||||
}
|
@ -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<Vector2i, Region> regionCache = Caffeine.newBuilder()
|
||||
private final MCAChunkLoader chunkLoader = new MCAChunkLoader(this);
|
||||
private final LoadingCache<Vector2i, Region<Chunk>> 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<Chunk> getRegion(int x, int z) {
|
||||
return getRegion(VECTOR_2_I_CACHE.get(x, z));
|
||||
}
|
||||
|
||||
private Region getRegion(Vector2i pos) {
|
||||
private Region<Chunk> getRegion(Vector2i pos) {
|
||||
return regionCache.get(pos);
|
||||
}
|
||||
|
||||
@ -191,7 +191,7 @@ public WatchService<Vector2i> createRegionWatchService() throws IOException {
|
||||
@Override
|
||||
public void preloadRegionChunks(int x, int z, Predicate<Vector2i> 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<Chunk> 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<Chunk> loadRegion(int x, int z) {
|
||||
return RegionType.loadRegion(chunkLoader, getRegionFolder(), x, z);
|
||||
}
|
||||
|
||||
private Chunk loadChunk(Vector2i chunkPos) {
|
||||
|
@ -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<Chunk> {
|
||||
|
||||
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;
|
@ -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<T> implements Region<T> {
|
||||
|
||||
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<T> 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<T> 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<T> 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;
|
||||
}
|
||||
|
@ -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<T> implements Region<T> {
|
||||
|
||||
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<T> chunkLoader;
|
||||
private final Vector2i regionPos;
|
||||
|
||||
public MCARegion(MCAWorld world, Path regionFile) throws IllegalArgumentException {
|
||||
this.world = world;
|
||||
public MCARegion(ChunkLoader<T> 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<T> 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) {
|
||||
|
@ -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);
|
||||
<T> Region<T> createRegion(ChunkLoader<T> 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 <T> Region<T> loadRegion(ChunkLoader<T> 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 <T> Region<T> createRegion(ChunkLoader<T> 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);
|
||||
<T> Region<T> create(ChunkLoader<T> chunkLoader, Path regionFile);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
|
Loading…
Reference in New Issue
Block a user