Skip to content

Commit

Permalink
feat: world user data, allow biome loading to be overridden
Browse files Browse the repository at this point in the history
  • Loading branch information
mworzala committed Dec 18, 2023
1 parent b36043a commit add30b0
Show file tree
Hide file tree
Showing 10 changed files with 89 additions and 22 deletions.
1 change: 1 addition & 0 deletions src/main/java/net/hollowcube/polar/AnvilPolar.java
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ public class AnvilPolar {
PolarWorld.LATEST_VERSION,
PolarWorld.DEFAULT_COMPRESSION,
(byte) minSection, (byte) maxSection,
new byte[0],
chunks
);
}
Expand Down
29 changes: 11 additions & 18 deletions src/main/java/net/hollowcube/polar/PolarLoader.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,10 @@
import net.minestom.server.instance.block.Block;
import net.minestom.server.instance.block.BlockManager;
import net.minestom.server.network.NetworkBuffer;
import net.minestom.server.utils.NamespaceID;
import net.minestom.server.world.biomes.Biome;
import net.minestom.server.world.biomes.BiomeManager;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jglrxavpok.hephaistos.nbt.NBTCompound;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand All @@ -36,20 +33,20 @@
@SuppressWarnings("UnstableApiUsage")
public class PolarLoader implements IChunkLoader {
private static final BlockManager BLOCK_MANAGER = MinecraftServer.getBlockManager();
private static final BiomeManager BIOME_MANAGER = MinecraftServer.getBiomeManager();
private static final ExceptionManager EXCEPTION_HANDLER = MinecraftServer.getExceptionManager();
private static final Logger logger = LoggerFactory.getLogger(PolarLoader.class);
static final Logger logger = LoggerFactory.getLogger(PolarLoader.class);

// Account for changes between main Minestom and minestom-ce.
private static final ChunkSupplierShim CHUNK_SUPPLIER = ChunkSupplierShim.select();

private static final Map<String, Biome> biomeCache = new ConcurrentHashMap<>();
private final Map<String, Biome> biomeReadCache = new ConcurrentHashMap<>();
private final Map<Integer, String> biomeWriteCache = new ConcurrentHashMap<>();

private final Path savePath;
private final ReentrantReadWriteLock worldDataLock = new ReentrantReadWriteLock();
private final PolarWorld worldData;

private PolarWorldAccess worldAccess = null;
private PolarWorldAccess worldAccess = PolarWorldAccess.DEFAULT;
private boolean parallel = false;

public PolarLoader(@NotNull Path path) throws IOException {
Expand Down Expand Up @@ -109,7 +106,10 @@ public boolean supportsParallelLoading() {

@Override
public void loadInstance(@NotNull Instance instance) {
//todo validate that the chunk is loadable in this world
var userData = worldData.userData();
if (userData.length > 0) {
worldAccess.loadWorldData(instance, new NetworkBuffer(ByteBuffer.wrap(userData)));
}
}

@Override
Expand Down Expand Up @@ -147,7 +147,7 @@ public void loadInstance(@NotNull Instance instance) {
}

var userData = chunkData.userData();
if (userData.length > 0 && worldAccess != null) {
if (userData.length > 0) {
worldAccess.loadChunkData(chunk, new NetworkBuffer(ByteBuffer.wrap(userData)));
}
}
Expand Down Expand Up @@ -184,14 +184,7 @@ private void loadSection(@NotNull PolarSection sectionData, @NotNull Section sec
var rawBiomePalette = sectionData.biomePalette();
var biomePalette = new Biome[rawBiomePalette.length];
for (int i = 0; i < rawBiomePalette.length; i++) {
biomePalette[i] = biomeCache.computeIfAbsent(rawBiomePalette[i], id -> {
var biome = BIOME_MANAGER.getByName(NamespaceID.from(id));
if (biome == null) {
logger.error("Failed to find biome: {}", id);
biome = Biome.PLAINS;
}
return biome;
});
biomePalette[i] = biomeReadCache.computeIfAbsent(rawBiomePalette[i], worldAccess::getBiome);
}
if (biomePalette.length == 1) {
section.biomePalette().fill(biomePalette[0].id());
Expand Down Expand Up @@ -333,7 +326,7 @@ private void updateChunkData(@NotNull Short2ObjectMap<String> blockCache, @NotNu
var biomeData = new int[PolarSection.BIOME_PALETTE_SIZE];

section.biomePalette().getAll((x, y, z, id) -> {
var biomeId = BIOME_MANAGER.getById(id).name().asString();
var biomeId = biomeWriteCache.computeIfAbsent(id, worldAccess::getBiomeName);

var paletteId = biomePalette.indexOf(biomeId);
if (paletteId == -1) {
Expand Down
7 changes: 6 additions & 1 deletion src/main/java/net/hollowcube/polar/PolarReader.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,14 @@ private PolarReader() {}
byte minSection = buffer.read(BYTE), maxSection = buffer.read(BYTE);
assertThat(minSection < maxSection, "Invalid section range");

// User (world) data
byte[] userData = new byte[0];
if (version > PolarWorld.VERSION_WORLD_USERDATA)
userData = buffer.read(BYTE_ARRAY);

var chunks = buffer.readCollection(b -> readChunk(version, b, maxSection - minSection + 1));

return new PolarWorld(version, compression, minSection, maxSection, chunks);
return new PolarWorld(version, compression, minSection, maxSection, userData, chunks);
}

private static @NotNull PolarChunk readChunk(short version, @NotNull NetworkBuffer buffer, int sectionCount) {
Expand Down
12 changes: 10 additions & 2 deletions src/main/java/net/hollowcube/polar/PolarWorld.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,12 @@
@SuppressWarnings("UnstableApiUsage")
public class PolarWorld {
public static final int MAGIC_NUMBER = 0x506F6C72; // `Polr`
public static final short LATEST_VERSION = 4;
public static final short LATEST_VERSION = 5;

static final short VERSION_UNIFIED_LIGHT = 1;
static final short VERSION_USERDATA_OPT_BLOCK_ENT_NBT = 2;
static final short VERSION_MINESTOM_NBT_READ_BREAK = 3;
static final short VERSION_WORLD_USERDATA = 4;

public static CompressionType DEFAULT_COMPRESSION = CompressionType.ZSTD;

Expand All @@ -30,25 +31,28 @@ public class PolarWorld {
// World metadata
private final byte minSection;
private final byte maxSection;
private byte @NotNull [] userData;

// Chunk data
private final Long2ObjectMap<PolarChunk> chunks = new Long2ObjectOpenHashMap<>();

public PolarWorld() {
this(LATEST_VERSION, DEFAULT_COMPRESSION, (byte) -4, (byte) 19, List.of());
this(LATEST_VERSION, DEFAULT_COMPRESSION, (byte) -4, (byte) 19, new byte[0], List.of());
}

public PolarWorld(
short version,
@NotNull CompressionType compression,
byte minSection, byte maxSection,
byte @NotNull [] userData,
@NotNull List<PolarChunk> chunks
) {
this.version = version;
this.compression = compression;

this.minSection = minSection;
this.maxSection = maxSection;
this.userData = userData;

for (var chunk : chunks) {
var index = ChunkUtils.getChunkIndex(chunk.x(), chunk.z());
Expand All @@ -75,6 +79,10 @@ public byte maxSection() {
return maxSection;
}

public byte @NotNull [] userData() {
return userData;
}

public @Nullable PolarChunk chunkAt(int x, int z) {
return chunks.getOrDefault(ChunkUtils.getChunkIndex(x, z), null);
}
Expand Down
54 changes: 54 additions & 0 deletions src/main/java/net/hollowcube/polar/PolarWorldAccess.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
package net.hollowcube.polar;

import net.minestom.server.MinecraftServer;
import net.minestom.server.instance.Chunk;
import net.minestom.server.instance.Instance;
import net.minestom.server.network.NetworkBuffer;
import net.minestom.server.utils.NamespaceID;
import net.minestom.server.world.biomes.Biome;
import net.minestom.server.world.biomes.BiomeManager;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

Expand All @@ -14,6 +19,27 @@
*/
@SuppressWarnings("UnstableApiUsage")
public interface PolarWorldAccess {
PolarWorldAccess DEFAULT = new PolarWorldAccess() {};

/**
* Called when an instance is created from this chunk loader.
* <br/><br/>
* Can be used to initialize the world based on saved user data in the world.
*
* @param instance The Minestom instance being created
* @param userData The saved user data, or null if none is present.
*/
default void loadWorldData(@NotNull Instance instance, @Nullable NetworkBuffer userData) {}

/**
* Called when an instance is being saved.
* <br/><br/>
* Can be used to save user data in the world by writing it to the buffer.
*
* @param instance The Minestom instance being saved
* @param userData A buffer to write user data to save
*/
default void saveWorldData(@NotNull Instance instance, @NotNull NetworkBuffer userData) {}

/**
* Called when a chunk is created, just before it is added to the world.
Expand All @@ -35,4 +61,32 @@ default void loadChunkData(@NotNull Chunk chunk, @Nullable NetworkBuffer userDat
*/
default void saveChunkData(@NotNull Chunk chunk, @NotNull NetworkBuffer userData) {}

/**
* Called when a chunk is being loaded by a {@link PolarLoader} to convert biome ids back to instances.
* <br/><br/>
* It is valid to change the behavior as long as a biome is returned in all cases (i.e. have a default).
* <br/><br/>
* Biomes are cached by the loader per loader instance, so there will only be a single call per loader, even over many chunks.
*
* @param name The namespace ID of the biome, eg minecraft:plains
* @return The biome instance
*/
default @NotNull Biome getBiome(@NotNull String name) {
var biome = MinecraftServer.getBiomeManager().getByName(NamespaceID.from(name));
if (biome == null) {
PolarLoader.logger.error("Failed to find biome: {}", name);
biome = Biome.PLAINS;
}
return biome;
}

default @NotNull String getBiomeName(int id) {
var biome = MinecraftServer.getBiomeManager().getById(id);
if (biome == null) {
PolarLoader.logger.error("Failed to find biome: {}", id);
biome = Biome.PLAINS;
}
return biome.name().asString();
}

}
1 change: 1 addition & 0 deletions src/main/java/net/hollowcube/polar/PolarWriter.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public static byte[] write(@NotNull PolarWorld world) {
var content = new NetworkBuffer(ByteBuffer.allocate(1024));
content.write(BYTE, world.minSection());
content.write(BYTE, world.maxSection());
content.write(BYTE_ARRAY, world.userData());
content.writeCollection(world.chunks(), PolarWriter::writeChunk);

// Create final buffer
Expand Down
2 changes: 1 addition & 1 deletion src/test/java/net/hollowcube/polar/TestAnvilPolar.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ void testConvertAnvilWorld() throws Exception {

var result = PolarWriter.write(world);
System.out.println(result.length);
Files.write(Path.of("./src/test/resources/4.polar"), result);
Files.write(Path.of("./src/test/resources/5.polar"), result);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ void testVersion4() {
runTest(4);
}

@Test
void testVersion5() {
runTest(5);
}

private static void runTest(int version) {
var is = TestReaderBackwardsCompatibility.class.getResourceAsStream("/backward/" + version + ".polar");
assertNotNull(is);
Expand Down
Binary file removed src/test/resources/3.polar
Binary file not shown.
Binary file added src/test/resources/backward/5.polar
Binary file not shown.

0 comments on commit add30b0

Please sign in to comment.