Skip to content

Commit

Permalink
Make regions generic to support different types of chunks
Browse files Browse the repository at this point in the history
  • Loading branch information
TBlueF committed Nov 4, 2024
1 parent 35fbcff commit 8b1c5ab
Show file tree
Hide file tree
Showing 8 changed files with 108 additions and 63 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -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.");
}

Expand Down
16 changes: 9 additions & 7 deletions core/src/main/java/de/bluecolored/bluemap/core/world/Region.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,23 +26,23 @@

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) {
return x == chunkX && z == chunkZ;
}

@Override
public void accept(int chunkX, int chunkZ, Chunk chunk) {
public void accept(int chunkX, int chunkZ, T chunk) {
this.foundChunk = chunk;
}
}
Expand All @@ -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();

}
Original file line number Diff line number Diff line change
@@ -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();

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
}

Expand Down Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.*;
Expand Down Expand Up @@ -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;

Expand All @@ -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("\\.");
Expand All @@ -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;

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand All @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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$");
Expand All @@ -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("\\.");
Expand All @@ -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);
Expand All @@ -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];

Expand All @@ -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);
Expand Down Expand Up @@ -157,21 +152,26 @@ 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);
}
}
}
}
}

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) {
Expand Down
Loading

0 comments on commit 8b1c5ab

Please sign in to comment.