diff --git a/BENCHMARK.md b/BENCHMARK.md index 0a52b4a..795f1de 100644 --- a/BENCHMARK.md +++ b/BENCHMARK.md @@ -17,7 +17,7 @@ public class ScuffedBenchmark { System.out.println("Starting iteration " + iter); // TNTLoader loader = new TNTLoader(new FileTNTSource(Path.of("src/test/resources/bench/bench.tnt"))); // AnvilLoader loader = new AnvilLoader(Path.of("src/test/resources/bench")); - PolarLoader loader = new PolarLoader(PolarReader.read(Files.readAllBytes(Path.of("src/test/resources/bench.polar")))); + PolarLoader loader = new PolarLoader(MinestomPolarReader.read(Files.readAllBytes(Path.of("src/test/resources/bench.polar")))); for (int x = 0; x < 32; x++) { for (int z = 0; z < 32; z++) { loader.loadChunk(instance, 0, 0).join(); diff --git a/build.gradle.kts b/build.gradle.kts deleted file mode 100644 index 06a2a8c..0000000 --- a/build.gradle.kts +++ /dev/null @@ -1,117 +0,0 @@ -plugins { - `java-library` - - `maven-publish` - signing - alias(libs.plugins.nexuspublish) -} - -group = "dev.hollowcube" -version = System.getenv("TAG_VERSION") ?: "dev" -description = "Fast and small world format for Minestom" - -repositories { - mavenLocal() - mavenCentral() - maven(url = "https://jitpack.io") -} - -dependencies { - val minestom = libs.minestom - - compileOnly(minestom) - implementation(libs.zstd) - // Fastutil is only included because minestom already uses it, otherwise it is a crazy dependency - // for how it is used in this project. - implementation(libs.fastutil) - - testImplementation("ch.qos.logback:logback-core:1.4.7") - testImplementation("ch.qos.logback:logback-classic:1.4.7") - - testImplementation(platform("org.junit:junit-bom:5.9.1")) - testImplementation("org.junit.jupiter:junit-jupiter") - testImplementation(minestom) -} - -java { - withSourcesJar() - withJavadocJar() - - sourceCompatibility = JavaVersion.VERSION_21 - targetCompatibility = JavaVersion.VERSION_21 -} - -tasks.test { - maxHeapSize = "2g" - useJUnitPlatform() -} - -nexusPublishing { - this.packageGroup.set("dev.hollowcube") - - repositories.sonatype { - nexusUrl.set(uri("https://s01.oss.sonatype.org/service/local/")) - snapshotRepositoryUrl.set(uri("https://s01.oss.sonatype.org/content/repositories/snapshots/")) - - if (System.getenv("SONATYPE_USERNAME") != null) { - username.set(System.getenv("SONATYPE_USERNAME")) - password.set(System.getenv("SONATYPE_PASSWORD")) - } - } -} - -publishing.publications.create("maven") { - groupId = "dev.hollowcube" - artifactId = "polar" - version = project.version.toString() - - from(project.components["java"]) - - pom { - name.set(artifactId) - description.set(project.description) - url.set("https://github.com/hollow-cube/polar") - - licenses { - license { - name.set("MIT") - url.set("https://github.com/hollow-cube/polar/blob/main/LICENSE") - } - } - - developers { - developer { - id.set("mworzala") - name.set("Matt Worzala") - email.set("matt@hollowcube.dev") - } - } - - issueManagement { - system.set("GitHub") - url.set("https://github.com/hollow-cube/polar/issues") - } - - scm { - connection.set("scm:git:git://github.com/hollow-cube/polar.git") - developerConnection.set("scm:git:git@github.com:hollow-cube/polar.git") - url.set("https://github.com/hollow-cube/polar") - tag.set(System.getenv("TAG_VERSION") ?: "HEAD") - } - - ciManagement { - system.set("Github Actions") - url.set("https://github.com/hollow-cube/polar/actions") - } - } -} - -signing { - isRequired = System.getenv("CI") != null - - val privateKey = System.getenv("GPG_PRIVATE_KEY") - val keyPassphrase = System.getenv()["GPG_PASSPHRASE"] - useInMemoryPgpKeys(privateKey, keyPassphrase) - - sign(publishing.publications) -} diff --git a/common/build.gradle.kts b/common/build.gradle.kts new file mode 100644 index 0000000..7d0713d --- /dev/null +++ b/common/build.gradle.kts @@ -0,0 +1,44 @@ +plugins { + `java-library` + kotlin("jvm") +} + +group = "dev.hollowcube" +version = System.getenv("TAG_VERSION") ?: "dev" +description = "Fast and small world format for Minestom" + +repositories { + mavenLocal() + mavenCentral() + maven(url = "https://jitpack.io") +} + +dependencies { + compileOnly("org.jetbrains:annotations:26.0.2") + api("net.kyori:adventure-api:4.21.0") + api("net.kyori:adventure-nbt:4.21.0") + api(libs.zstd) + // Fastutil is only included because minestom already uses it, otherwise it is a crazy dependency + // for how it is used in this project. + api(libs.fastutil) + + testImplementation("ch.qos.logback:logback-core:1.4.7") + testImplementation("ch.qos.logback:logback-classic:1.4.7") + + testImplementation(platform("org.junit:junit-bom:5.9.1")) + testImplementation("org.junit.jupiter:junit-jupiter") + implementation(kotlin("stdlib-jdk8")) +} + +java { + withSourcesJar() + withJavadocJar() +} + +tasks.test { + maxHeapSize = "2g" + useJUnitPlatform() +} +kotlin { + jvmToolchain(21) +} \ No newline at end of file diff --git a/src/main/java/net/hollowcube/polar/ChunkSelector.java b/common/src/main/java/net/hollowcube/polar/ChunkSelector.java similarity index 100% rename from src/main/java/net/hollowcube/polar/ChunkSelector.java rename to common/src/main/java/net/hollowcube/polar/ChunkSelector.java diff --git a/common/src/main/java/net/hollowcube/polar/DimensionTypeWrapper.java b/common/src/main/java/net/hollowcube/polar/DimensionTypeWrapper.java new file mode 100644 index 0000000..de8a064 --- /dev/null +++ b/common/src/main/java/net/hollowcube/polar/DimensionTypeWrapper.java @@ -0,0 +1,6 @@ +package net.hollowcube.polar; + +public interface DimensionTypeWrapper { + int minY(); + int maxY(); +} diff --git a/src/main/java/net/hollowcube/polar/PaletteUtil.java b/common/src/main/java/net/hollowcube/polar/PaletteUtil.java similarity index 92% rename from src/main/java/net/hollowcube/polar/PaletteUtil.java rename to common/src/main/java/net/hollowcube/polar/PaletteUtil.java index d5e5b83..ec2fd1e 100644 --- a/src/main/java/net/hollowcube/polar/PaletteUtil.java +++ b/common/src/main/java/net/hollowcube/polar/PaletteUtil.java @@ -1,12 +1,10 @@ package net.hollowcube.polar; -import net.minestom.server.utils.validate.Check; - final class PaletteUtil { private PaletteUtil() {} public static int bitsToRepresent(int n) { - Check.argCondition(n < 1, "n must be greater than 0"); + assert n > 0: "n must be greater than 0"; return Integer.SIZE - Integer.numberOfLeadingZeros(n); } diff --git a/src/main/java/net/hollowcube/polar/PolarChunk.java b/common/src/main/java/net/hollowcube/polar/PolarChunk.java similarity index 92% rename from src/main/java/net/hollowcube/polar/PolarChunk.java rename to common/src/main/java/net/hollowcube/polar/PolarChunk.java index 0e8405f..740ffee 100644 --- a/src/main/java/net/hollowcube/polar/PolarChunk.java +++ b/common/src/main/java/net/hollowcube/polar/PolarChunk.java @@ -2,7 +2,6 @@ import net.kyori.adventure.nbt.CompoundBinaryTag; -import net.minestom.server.instance.Chunk; import org.jetbrains.annotations.Nullable; import java.util.List; @@ -35,7 +34,7 @@ public record PolarChunk( HEIGHTMAP_WORLD_SURFACE, HEIGHTMAP_WORLD_SURFACE_WG, }; - static final int HEIGHTMAP_SIZE = Chunk.CHUNK_SIZE_X * Chunk.CHUNK_SIZE_Z; + static final int HEIGHTMAP_SIZE = 16 * 16; static final int MAX_HEIGHTMAPS = 32; public int @Nullable [] heightmap(int type) { diff --git a/src/main/java/net/hollowcube/polar/PolarDataConverter.java b/common/src/main/java/net/hollowcube/polar/PolarDataConverter.java similarity index 91% rename from src/main/java/net/hollowcube/polar/PolarDataConverter.java rename to common/src/main/java/net/hollowcube/polar/PolarDataConverter.java index 6bc6fe5..9a9c45f 100644 --- a/src/main/java/net/hollowcube/polar/PolarDataConverter.java +++ b/common/src/main/java/net/hollowcube/polar/PolarDataConverter.java @@ -1,7 +1,6 @@ package net.hollowcube.polar; import net.kyori.adventure.nbt.CompoundBinaryTag; -import net.minestom.server.MinecraftServer; import org.jetbrains.annotations.NotNull; import java.util.Map; @@ -19,14 +18,14 @@ public interface PolarDataConverter { * do not store a data version. Defaults to the current Minestom data version. */ default int defaultDataVersion() { - return MinecraftServer.DATA_VERSION; + return -1; } /** * Returns the current data version of the world. */ default int dataVersion() { - return MinecraftServer.DATA_VERSION; + return -1; } /** diff --git a/src/main/java/net/hollowcube/polar/PolarReader.java b/common/src/main/java/net/hollowcube/polar/PolarReader.java similarity index 65% rename from src/main/java/net/hollowcube/polar/PolarReader.java rename to common/src/main/java/net/hollowcube/polar/PolarReader.java index a871703..f480ff7 100644 --- a/src/main/java/net/hollowcube/polar/PolarReader.java +++ b/common/src/main/java/net/hollowcube/polar/PolarReader.java @@ -2,24 +2,16 @@ import com.github.luben.zstd.Zstd; import net.hollowcube.polar.PolarSection.LightContent; +import net.hollowcube.polar.buffer.BufferFactory; +import net.hollowcube.polar.buffer.BufferWrapper; import net.kyori.adventure.key.Key; -import net.kyori.adventure.nbt.BinaryTag; import net.kyori.adventure.nbt.CompoundBinaryTag; -import net.minestom.server.coordinate.CoordConversion; -import net.minestom.server.network.NetworkBuffer; -import net.minestom.server.utils.nbt.BinaryTagReader; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; -import java.io.DataInputStream; -import java.io.InputStream; import java.util.ArrayList; -import static net.minestom.server.network.NetworkBuffer.*; - public class PolarReader { - static final NetworkBuffer.Type LIGHT_DATA = NetworkBuffer.FixedRawBytes(2048); - private static final boolean FORCE_LEGACY_NBT = Boolean.getBoolean("polar.debug.force-legacy-nbt"); static final int MAX_BLOCK_PALETTE_SIZE = 16 * 16 * 16; static final int MAX_BIOME_PALETTE_SIZE = 8 * 8 * 8; @@ -27,40 +19,40 @@ public class PolarReader { private PolarReader() { } - public static @NotNull PolarWorld read(byte @NotNull [] data) { - return read(data, PolarDataConverter.NOOP); + public static @NotNull PolarWorld read(byte @NotNull [] data, @NotNull BufferFactory bufferFactory) { + return read(data, bufferFactory, PolarDataConverter.NOOP); } - public static @NotNull PolarWorld read(byte @NotNull [] data, @NotNull PolarDataConverter dataConverter) { - var buffer = NetworkBuffer.wrap(data, 0, data.length); + public static @NotNull PolarWorld read(byte @NotNull [] data, @NotNull BufferFactory bufferFactory, @NotNull PolarDataConverter dataConverter) { + var buffer = bufferFactory.wrap(data); buffer.writeIndex(data.length); // Set write index to end so readableBytes returns remaining bytes - var magicNumber = buffer.read(INT); + var magicNumber = buffer.readInt(); assertThat(magicNumber == PolarWorld.MAGIC_NUMBER, "Invalid magic number"); - short version = buffer.read(SHORT); + short version = buffer.readShort(); validateVersion(version); int dataVersion = version >= PolarWorld.VERSION_DATA_CONVERTER - ? buffer.read(VAR_INT) + ? buffer.readVarInt() : dataConverter.defaultDataVersion(); - var compression = PolarWorld.CompressionType.fromId(buffer.read(BYTE)); + var compression = PolarWorld.CompressionType.fromId(buffer.readByte()); assertThat(compression != null, "Invalid compression type"); - var compressedDataLength = buffer.read(VAR_INT); + var compressedDataLength = buffer.readVarInt(); // Replace the buffer with a "decompressed" version. This is a no-op if compression is NONE. - buffer = decompressBuffer(buffer, compression, compressedDataLength); + buffer = decompressBuffer(bufferFactory, buffer, compression, compressedDataLength); - byte minSection = buffer.read(BYTE), maxSection = buffer.read(BYTE); + byte minSection = buffer.readByte(), maxSection = buffer.readByte(); 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); + userData = buffer.readByteArray(); - int chunkCount = buffer.read(VAR_INT); + int chunkCount = buffer.readVarInt(); var chunks = new ArrayList(chunkCount); for (int i = 0; i < chunkCount; i++) { chunks.add(readChunk(dataConverter, version, dataVersion, buffer, maxSection - minSection + 1)); @@ -69,16 +61,16 @@ private PolarReader() { return new PolarWorld(version, dataVersion, compression, minSection, maxSection, userData, chunks); } - private static @NotNull PolarChunk readChunk(@NotNull PolarDataConverter dataConverter, short version, int dataVersion, @NotNull NetworkBuffer buffer, int sectionCount) { - var chunkX = buffer.read(VAR_INT); - var chunkZ = buffer.read(VAR_INT); + private static @NotNull PolarChunk readChunk(@NotNull PolarDataConverter dataConverter, short version, int dataVersion, @NotNull BufferWrapper buffer, int sectionCount) { + var chunkX = buffer.readVarInt(); + var chunkZ = buffer.readVarInt(); var sections = new PolarSection[sectionCount]; for (int i = 0; i < sectionCount; i++) { sections[i] = readSection(dataConverter, version, dataVersion, buffer); } - int blockEntityCount = buffer.read(VAR_INT); + int blockEntityCount = buffer.readVarInt(); var blockEntities = new ArrayList(blockEntityCount); for (int i = 0; i < blockEntityCount; i++) { blockEntities.add(readBlockEntity(dataConverter, version, dataVersion, buffer)); @@ -89,7 +81,7 @@ private PolarReader() { // Objects byte[] userData = new byte[0]; if (version > PolarWorld.VERSION_USERDATA_OPT_BLOCK_ENT_NBT) - userData = buffer.read(BYTE_ARRAY); + userData = buffer.readByteArray(); return new PolarChunk( chunkX, chunkZ, @@ -100,11 +92,11 @@ private PolarReader() { ); } - private static @NotNull PolarSection readSection(@NotNull PolarDataConverter dataConverter, short version, int dataVersion, @NotNull NetworkBuffer buffer) { + private static @NotNull PolarSection readSection(@NotNull PolarDataConverter dataConverter, short version, int dataVersion, @NotNull BufferWrapper buffer) { // If section is empty exit immediately - if (buffer.read(BOOLEAN)) return new PolarSection(); + if (buffer.readBoolean()) return new PolarSection(); - var blockPalette = buffer.read(STRING.list(MAX_BLOCK_PALETTE_SIZE)).toArray(String[]::new); + var blockPalette = buffer.readStringArray(MAX_BLOCK_PALETTE_SIZE); if (dataVersion < dataConverter.dataVersion()) { dataConverter.convertBlockPalette(blockPalette, dataVersion, dataConverter.dataVersion()); } @@ -113,17 +105,17 @@ private PolarReader() { if (blockPalette.length > 1) { blockData = new int[PolarSection.BLOCK_PALETTE_SIZE]; - var rawBlockData = buffer.read(LONG_ARRAY); + var rawBlockData = buffer.readLongArray(); var bitsPerEntry = (int) Math.ceil(Math.log(blockPalette.length) / Math.log(2)); PaletteUtil.unpack(blockData, rawBlockData, bitsPerEntry); } - var biomePalette = buffer.read(STRING.list(MAX_BIOME_PALETTE_SIZE)).toArray(String[]::new); + var biomePalette = buffer.readStringArray(MAX_BIOME_PALETTE_SIZE); int[] biomeData = null; if (biomePalette.length > 1) { biomeData = new int[PolarSection.BIOME_PALETTE_SIZE]; - var rawBiomeData = buffer.read(LONG_ARRAY); + var rawBiomeData = buffer.readLongArray(); var bitsPerEntry = (int) Math.ceil(Math.log(biomePalette.length) / Math.log(2)); PaletteUtil.unpack(biomeData, rawBiomeData, bitsPerEntry); } @@ -132,20 +124,20 @@ private PolarReader() { byte[] blockLight = null, skyLight = null; if (version > PolarWorld.VERSION_UNIFIED_LIGHT) { blockLightContent = version >= PolarWorld.VERSION_IMPROVED_LIGHT - ? LightContent.VALUES[buffer.read(BYTE)] - : (buffer.read(BOOLEAN) ? LightContent.PRESENT : LightContent.MISSING); + ? LightContent.VALUES[buffer.readByte()] + : (buffer.readBoolean() ? LightContent.PRESENT : LightContent.MISSING); if (blockLightContent == LightContent.PRESENT) - blockLight = buffer.read(LIGHT_DATA); + blockLight = buffer.readLightData(); skyLightContent = version >= PolarWorld.VERSION_IMPROVED_LIGHT - ? LightContent.VALUES[buffer.read(BYTE)] - : (buffer.read(BOOLEAN) ? LightContent.PRESENT : LightContent.MISSING); + ? LightContent.VALUES[buffer.readByte()] + : (buffer.readBoolean() ? LightContent.PRESENT : LightContent.MISSING); if (skyLightContent == LightContent.PRESENT) - skyLight = buffer.read(LIGHT_DATA); - } else if (buffer.read(BOOLEAN)) { + skyLight = buffer.readLightData(); + } else if (buffer.readBoolean()) { blockLightContent = LightContent.PRESENT; - blockLight = buffer.read(LIGHT_DATA); + blockLight = buffer.readLightData(); skyLightContent = LightContent.PRESENT; - skyLight = buffer.read(LIGHT_DATA); + skyLight = buffer.readLightData(); } return new PolarSection( @@ -169,15 +161,15 @@ static void upgradeGrassInPalette(String[] blockPalette, int version) { } } - static int[][] readHeightmapData(@NotNull NetworkBuffer buffer, boolean skip) { + static int[][] readHeightmapData(@NotNull BufferWrapper buffer, boolean skip) { var heightmaps = !skip ? new int[PolarChunk.MAX_HEIGHTMAPS][] : null; - int heightmapMask = buffer.read(INT); + int heightmapMask = buffer.readInt(); for (int i = 0; i < PolarChunk.MAX_HEIGHTMAPS; i++) { if ((heightmapMask & (1 << i)) == 0) continue; if (!skip) { - var packed = buffer.read(LONG_ARRAY); + var packed = buffer.readLongArray(); if (packed.length == 0) { heightmaps[i] = new int[0]; } else { @@ -186,22 +178,22 @@ static int[][] readHeightmapData(@NotNull NetworkBuffer buffer, boolean skip) { PaletteUtil.unpack(heightmaps[i], packed, bitsPerEntry); } } else { - buffer.advanceRead(buffer.read(VAR_INT) * 8); // Skip a long array + buffer.advanceRead(buffer.readVarInt() * 8); // Skip a long array } } return heightmaps; } - static @NotNull PolarChunk.BlockEntity readBlockEntity(@NotNull PolarDataConverter dataConverter, int version, int dataVersion, @NotNull NetworkBuffer buffer) { - int posIndex = buffer.read(INT); - var id = buffer.read(STRING.optional()); + static @NotNull PolarChunk.BlockEntity readBlockEntity(@NotNull PolarDataConverter dataConverter, int version, int dataVersion, @NotNull BufferWrapper buffer) { + int posIndex = buffer.readInt(); + var id = buffer.readOptString(); CompoundBinaryTag nbt = CompoundBinaryTag.empty(); - if (version <= PolarWorld.VERSION_USERDATA_OPT_BLOCK_ENT_NBT || buffer.read(BOOLEAN)) { + if (version <= PolarWorld.VERSION_USERDATA_OPT_BLOCK_ENT_NBT || buffer.readBoolean()) { if (version <= PolarWorld.VERSION_MINESTOM_NBT_READ_BREAK || FORCE_LEGACY_NBT) { - nbt = (CompoundBinaryTag) legacyReadNBT(buffer); + nbt = (CompoundBinaryTag) buffer.readLegacyNbt(); } else { - nbt = (CompoundBinaryTag) buffer.read(NBT); + nbt = (CompoundBinaryTag) buffer.readNbt(); } } @@ -214,9 +206,9 @@ static int[][] readHeightmapData(@NotNull NetworkBuffer buffer, boolean skip) { } return new PolarChunk.BlockEntity( - CoordConversion.chunkBlockIndexGetX(posIndex), - CoordConversion.chunkBlockIndexGetY(posIndex), - CoordConversion.chunkBlockIndexGetZ(posIndex), + Utils.chunkBlockIndexGetX(posIndex), + Utils.chunkBlockIndexGetY(posIndex), + Utils.chunkBlockIndexGetZ(posIndex), id, nbt ); } @@ -227,41 +219,18 @@ static void validateVersion(int version) { assertThat(version <= PolarWorld.LATEST_VERSION, invalidVersionError); } - private static @NotNull NetworkBuffer decompressBuffer(@NotNull NetworkBuffer buffer, @NotNull PolarWorld.CompressionType compression, int length) { + private static @NotNull BufferWrapper decompressBuffer(@NotNull BufferFactory bufferFactory, @NotNull BufferWrapper buffer, @NotNull PolarWorld.CompressionType compression, int length) { return switch (compression) { case NONE -> buffer; case ZSTD -> { - var bytes = Zstd.decompress(buffer.read(RAW_BYTES), length); - var newBuffer = NetworkBuffer.wrap(bytes, 0, 0); + var bytes = Zstd.decompress(buffer.readRawBytes(), length); + var newBuffer = bufferFactory.wrap(bytes); newBuffer.writeIndex(bytes.length); yield newBuffer; } }; } - /** - * Minecraft (so Minestom) had a breaking change in NBT reading in 1.20.2. This method replicates the old - * behavior which we use for any Polar version less than {@link PolarWorld#VERSION_MINESTOM_NBT_READ_BREAK}. - * - * @see NetworkBuffer#NBT - */ - private static BinaryTag legacyReadNBT(@NotNull NetworkBuffer buffer) { - try { - var nbtReader = new BinaryTagReader(new DataInputStream(new InputStream() { - public int read() { - return buffer.read(NetworkBuffer.BYTE) & 255; - } - - public int available() { - return (int) buffer.readableBytes(); - } - })); - return nbtReader.readNamed().getValue(); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - @Contract("false, _ -> fail") static void assertThat(boolean condition, @NotNull String message) { if (!condition) throw new Error(message); diff --git a/src/main/java/net/hollowcube/polar/PolarSection.java b/common/src/main/java/net/hollowcube/polar/PolarSection.java similarity index 100% rename from src/main/java/net/hollowcube/polar/PolarSection.java rename to common/src/main/java/net/hollowcube/polar/PolarSection.java diff --git a/src/main/java/net/hollowcube/polar/PolarWorld.java b/common/src/main/java/net/hollowcube/polar/PolarWorld.java similarity index 84% rename from src/main/java/net/hollowcube/polar/PolarWorld.java rename to common/src/main/java/net/hollowcube/polar/PolarWorld.java index 4eae355..f7835be 100644 --- a/src/main/java/net/hollowcube/polar/PolarWorld.java +++ b/common/src/main/java/net/hollowcube/polar/PolarWorld.java @@ -2,17 +2,12 @@ import it.unimi.dsi.fastutil.longs.Long2ObjectMap; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; -import net.minestom.server.MinecraftServer; -import net.minestom.server.coordinate.CoordConversion; -import net.minestom.server.world.DimensionType; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Collection; import java.util.List; -import static net.minestom.server.instance.Chunk.CHUNK_SECTION_SIZE; - /** * A Java type representing the latest version of the world format. */ @@ -20,6 +15,7 @@ public class PolarWorld { public static final int MAGIC_NUMBER = 0x506F6C72; // `Polr` public static final short LATEST_VERSION = 7; + public static final int CHUNK_SECTION_SIZE = 16; static final short VERSION_UNIFIED_LIGHT = 1; static final short VERSION_USERDATA_OPT_BLOCK_ENT_NBT = 2; @@ -45,13 +41,13 @@ public class PolarWorld { private final Long2ObjectMap chunks = new Long2ObjectOpenHashMap<>(); public PolarWorld() { - this(LATEST_VERSION, MinecraftServer.DATA_VERSION, DEFAULT_COMPRESSION, (byte) -4, (byte) 19, new byte[0], List.of()); + this(LATEST_VERSION, -1, DEFAULT_COMPRESSION, (byte) -4, (byte) 19, new byte[0], List.of()); } - public PolarWorld(@NotNull DimensionType dimensionType) { + public PolarWorld(@NotNull DimensionTypeWrapper dimensionType) { this( LATEST_VERSION, - MinecraftServer.DATA_VERSION, + -1, DEFAULT_COMPRESSION, (byte) (dimensionType.minY() / CHUNK_SECTION_SIZE), (byte) (dimensionType.maxY() / CHUNK_SECTION_SIZE - 1), @@ -77,7 +73,7 @@ public PolarWorld( this.userData = userData; for (var chunk : chunks) { - var index = CoordConversion.chunkIndex(chunk.x(), chunk.z()); + var index = Utils.chunkIndex(chunk.x(), chunk.z()); this.chunks.put(index, chunk); } } @@ -124,11 +120,11 @@ public void userData(byte @NotNull [] userData) { } public @Nullable PolarChunk chunkAt(int x, int z) { - return chunks.getOrDefault(CoordConversion.chunkIndex(x, z), null); + return chunks.getOrDefault(Utils.chunkIndex(x, z), null); } public void updateChunkAt(int x, int z, @NotNull PolarChunk chunk) { - chunks.put(CoordConversion.chunkIndex(x, z), chunk); + chunks.put(Utils.chunkIndex(x, z), chunk); } public @NotNull Collection chunks() { diff --git a/src/main/java/net/hollowcube/polar/PolarWriter.java b/common/src/main/java/net/hollowcube/polar/PolarWriter.java similarity index 50% rename from src/main/java/net/hollowcube/polar/PolarWriter.java rename to common/src/main/java/net/hollowcube/polar/PolarWriter.java index a961fd9..de4e400 100644 --- a/src/main/java/net/hollowcube/polar/PolarWriter.java +++ b/common/src/main/java/net/hollowcube/polar/PolarWriter.java @@ -1,16 +1,10 @@ package net.hollowcube.polar; import com.github.luben.zstd.Zstd; -import net.minestom.server.coordinate.CoordConversion; -import net.minestom.server.instance.Chunk; -import net.minestom.server.network.NetworkBuffer; +import net.hollowcube.polar.buffer.BufferFactory; +import net.hollowcube.polar.buffer.BufferWrapper; import org.jetbrains.annotations.NotNull; -import java.util.Arrays; - -import static net.minestom.server.network.NetworkBuffer.*; - -@SuppressWarnings("UnstableApiUsage") public class PolarWriter { private PolarWriter() { } @@ -20,40 +14,41 @@ public static byte[] write(@NotNull PolarWorld world) { } public static byte[] write(@NotNull PolarWorld world, @NotNull PolarDataConverter dataConverter) { + BufferFactory bufferFactory = BufferFactory.INSTANCE; // Write the compressed content first - var contentBytes = NetworkBuffer.makeArray(content -> { - content.write(BYTE, world.minSection()); - content.write(BYTE, world.maxSection()); - content.write(BYTE_ARRAY, world.userData()); + var contentBytes = bufferFactory.makeArray(content -> { + content.writeByte(world.minSection()); + content.writeByte(world.maxSection()); + content.writeByteArray(world.userData()); - content.write(VAR_INT, world.chunks().size()); + content.writeVarInt(world.chunks().size()); for (var chunk : world.chunks()) { writeChunk(content, chunk, world.maxSection() - world.minSection() + 1); } }); // Create final buffer - return NetworkBuffer.makeArray(buffer -> { - buffer.write(INT, PolarWorld.MAGIC_NUMBER); - buffer.write(SHORT, PolarWorld.VERSION_IMPROVED_LIGHT); - buffer.write(VAR_INT, dataConverter.dataVersion()); - buffer.write(BYTE, (byte) world.compression().ordinal()); + return bufferFactory.makeArray(buffer -> { + buffer.writeInt(PolarWorld.MAGIC_NUMBER); + buffer.writeShort(PolarWorld.VERSION_IMPROVED_LIGHT); + buffer.writeVarInt(dataConverter.dataVersion()); + buffer.writeByte((byte) world.compression().ordinal()); switch (world.compression()) { case NONE -> { - buffer.write(VAR_INT, contentBytes.length); - buffer.write(RAW_BYTES, contentBytes); + buffer.writeVarInt(contentBytes.length); + buffer.writeRawBytes(contentBytes); } case ZSTD -> { - buffer.write(VAR_INT, contentBytes.length); - buffer.write(RAW_BYTES, Zstd.compress(contentBytes)); + buffer.writeVarInt(contentBytes.length); + buffer.writeRawBytes(Zstd.compress(contentBytes)); } } }); } - private static void writeChunk(@NotNull NetworkBuffer buffer, @NotNull PolarChunk chunk, int sectionCount) { - buffer.write(VAR_INT, chunk.x()); - buffer.write(VAR_INT, chunk.z()); + private static void writeChunk(@NotNull BufferWrapper buffer, @NotNull PolarChunk chunk, int sectionCount) { + buffer.writeVarInt(chunk.x()); + buffer.writeVarInt(chunk.z()); assert sectionCount == chunk.sections().length : "section count and chunk section length mismatch"; @@ -61,7 +56,7 @@ private static void writeChunk(@NotNull NetworkBuffer buffer, @NotNull PolarChun writeSection(buffer, section); } - buffer.write(VAR_INT, chunk.blockEntities().size()); + buffer.writeVarInt(chunk.blockEntities().size()); for (var blockEntity : chunk.blockEntities()) { writeBlockEntity(buffer, blockEntity); } @@ -72,57 +67,57 @@ private static void writeChunk(@NotNull NetworkBuffer buffer, @NotNull PolarChun if (chunk.heightmap(i) != null) heightmapBits |= 1 << i; } - buffer.write(INT, heightmapBits); + buffer.writeInt(heightmapBits); - int bitsPerEntry = PaletteUtil.bitsToRepresent(sectionCount * Chunk.CHUNK_SECTION_SIZE); + int bitsPerEntry = PaletteUtil.bitsToRepresent(sectionCount * 16); for (int i = 0; i < PolarChunk.MAX_HEIGHTMAPS; i++) { var heightmap = chunk.heightmap(i); if (heightmap == null) continue; - if (heightmap.length == 0) buffer.write(LONG_ARRAY, new long[0]); - else buffer.write(LONG_ARRAY, PaletteUtil.pack(heightmap, bitsPerEntry)); + if (heightmap.length == 0) buffer.writeLongArray(new long[0]); + else buffer.writeLongArray(PaletteUtil.pack(heightmap, bitsPerEntry)); } } - buffer.write(BYTE_ARRAY, chunk.userData()); + buffer.writeByteArray(chunk.userData()); } - private static void writeSection(@NotNull NetworkBuffer buffer, @NotNull PolarSection section) { - buffer.write(BOOLEAN, section.isEmpty()); + private static void writeSection(@NotNull BufferWrapper buffer, @NotNull PolarSection section) { + buffer.writeBoolean(section.isEmpty()); if (section.isEmpty()) return; // Blocks var blockPalette = section.blockPalette(); - buffer.write(STRING.list(), Arrays.asList(blockPalette)); + buffer.writeStringArray(blockPalette); if (blockPalette.length > 1) { var blockData = section.blockData(); var bitsPerEntry = (int) Math.ceil(Math.log(blockPalette.length) / Math.log(2)); if (bitsPerEntry < 1) bitsPerEntry = 1; - buffer.write(LONG_ARRAY, PaletteUtil.pack(blockData, bitsPerEntry)); + buffer.writeLongArray(PaletteUtil.pack(blockData, bitsPerEntry)); } // Biomes var biomePalette = section.biomePalette(); - buffer.write(STRING.list(), Arrays.asList(biomePalette)); + buffer.writeStringArray(biomePalette); if (biomePalette.length > 1) { var biomeData = section.biomeData(); var bitsPerEntry = (int) Math.ceil(Math.log(biomePalette.length) / Math.log(2)); if (bitsPerEntry < 1) bitsPerEntry = 1; - buffer.write(LONG_ARRAY, PaletteUtil.pack(biomeData, bitsPerEntry)); + buffer.writeLongArray(PaletteUtil.pack(biomeData, bitsPerEntry)); } // Light - buffer.write(BYTE, (byte) section.blockLightContent().ordinal()); + buffer.writeByte((byte) section.blockLightContent().ordinal()); if (section.blockLightContent() == PolarSection.LightContent.PRESENT) - buffer.write(RAW_BYTES, section.blockLight()); - buffer.write(BYTE, (byte) section.skyLightContent().ordinal()); + buffer.writeRawBytes(section.blockLight()); + buffer.writeByte((byte) section.skyLightContent().ordinal()); if (section.skyLightContent() == PolarSection.LightContent.PRESENT) - buffer.write(RAW_BYTES, section.skyLight()); + buffer.writeRawBytes(section.skyLight()); } - private static void writeBlockEntity(@NotNull NetworkBuffer buffer, @NotNull PolarChunk.BlockEntity blockEntity) { - var index = CoordConversion.chunkBlockIndex(blockEntity.x(), blockEntity.y(), blockEntity.z()); - buffer.write(INT, index); - buffer.write(STRING.optional(), blockEntity.id()); - buffer.write(NBT.optional(), blockEntity.data()); + private static void writeBlockEntity(@NotNull BufferWrapper buffer, @NotNull PolarChunk.BlockEntity blockEntity) { + var index = Utils.chunkBlockIndex(blockEntity.x(), blockEntity.y(), blockEntity.z()); + buffer.writeInt(index); + buffer.writeOptString(blockEntity.id()); + buffer.writeOptNbt(blockEntity.data()); } } diff --git a/common/src/main/java/net/hollowcube/polar/Utils.java b/common/src/main/java/net/hollowcube/polar/Utils.java new file mode 100644 index 0000000..07ff008 --- /dev/null +++ b/common/src/main/java/net/hollowcube/polar/Utils.java @@ -0,0 +1,41 @@ +package net.hollowcube.polar; + +public class Utils { + public static int chunkBlockIndexGetX(int index) { + return index & 0xF; // bits 0-3 + } + + public static int chunkBlockIndexGetY(int index) { + int y = (index & 0x07FFFFF0) >>> 4; + if ((index & 0x08000000) != 0) y = -y; // Sign bit set, invert sign + return y; // 4-28 bits + } + + public static int chunkBlockIndexGetZ(int index) { + return (index >>> 28) & 0xF; // bits 28-31 + } + + public static long chunkIndex(int chunkX, int chunkZ) { + return (((long) chunkX) << 32) | (chunkZ & 0xffffffffL); + } + + public static int globalToSectionRelative(int xyz) { + return xyz & 0xF; + } + + public static int chunkBlockIndex(int x, int y, int z) { + // Mask x and z to ensure only the lower 4 bits are used. + x = globalToSectionRelative(x); + z = globalToSectionRelative(z); + + // Bits layout: + // bits 0-3: x (4 bits) + // bits 4-26: absolute value of y (23 bits) + // bit 27: sign bit of y + // bits 28-31: z (4 bits) + return (z << 28) // Z component (shifted to the upper 4 bits) + | (y & 0x80000000) >>> 4 // Y sign bit if y was negative + | (Math.abs(y) & 0x007FFFFF) << 4 // Y component (23 bits for Y, sign encoded in the 24th) + | (x); // X component (4 bits for X) + } +} diff --git a/src/main/java/net/hollowcube/polar/WorldHeightUtil.java b/common/src/main/java/net/hollowcube/polar/WorldHeightUtil.java similarity index 100% rename from src/main/java/net/hollowcube/polar/WorldHeightUtil.java rename to common/src/main/java/net/hollowcube/polar/WorldHeightUtil.java diff --git a/common/src/main/java/net/hollowcube/polar/buffer/BufferFactory.java b/common/src/main/java/net/hollowcube/polar/buffer/BufferFactory.java new file mode 100644 index 0000000..cbafdf3 --- /dev/null +++ b/common/src/main/java/net/hollowcube/polar/buffer/BufferFactory.java @@ -0,0 +1,9 @@ +package net.hollowcube.polar.buffer; + +import org.jetbrains.annotations.NotNull; +import java.util.function.Consumer; + +public interface BufferFactory { + BufferWrapper wrap(byte[] data); + byte[] makeArray(@NotNull Consumer<@NotNull BufferWrapper> writing); +} diff --git a/common/src/main/java/net/hollowcube/polar/buffer/BufferWrapper.java b/common/src/main/java/net/hollowcube/polar/buffer/BufferWrapper.java new file mode 100644 index 0000000..6bd9af0 --- /dev/null +++ b/common/src/main/java/net/hollowcube/polar/buffer/BufferWrapper.java @@ -0,0 +1,36 @@ +package net.hollowcube.polar.buffer; + +import net.kyori.adventure.nbt.BinaryTag; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +public interface BufferWrapper { + void writeIndex(int index); + void advanceRead(int length); + + boolean readBoolean(); + void writeBoolean(boolean value); + byte readByte(); + void writeByte(byte value); + short readShort(); + void writeShort(short value); + int readInt(); + void writeInt(int value); + int readVarInt(); + void writeVarInt(int value); + byte[] readByteArray(); + void writeByteArray(byte[] value); + byte[] readRawBytes(); + void writeRawBytes(byte[] value); + String[] readStringArray(int maxSize); + void writeStringArray(String[] value); + long[] readLongArray(); + void writeLongArray(long[] value); + @Nullable String readOptString(); + void writeOptString(@Nullable String value); + BinaryTag readNbt(); + BinaryTag readLegacyNbt(); + void writeOptNbt(@Nullable BinaryTag value); + byte[] readLightData(); +} diff --git a/minestom/build.gradle.kts b/minestom/build.gradle.kts new file mode 100644 index 0000000..58151b1 --- /dev/null +++ b/minestom/build.gradle.kts @@ -0,0 +1,22 @@ +plugins { + `java-library` +} + +repositories { + mavenLocal() + mavenCentral() + maven(url = "https://jitpack.io") +} + +dependencies { + implementation(project(":common")) + val minestom = libs.minestom + compileOnly(minestom) + + testImplementation("ch.qos.logback:logback-core:1.4.7") + testImplementation("ch.qos.logback:logback-classic:1.4.7") + + testImplementation(platform("org.junit:junit-bom:5.9.1")) + testImplementation("org.junit.jupiter:junit-jupiter") + testImplementation(minestom) +} \ No newline at end of file diff --git a/src/main/java/net/hollowcube/polar/AnvilPolar.java b/minestom/src/main/java/net/hollowcube/polar/AnvilPolar.java similarity index 100% rename from src/main/java/net/hollowcube/polar/AnvilPolar.java rename to minestom/src/main/java/net/hollowcube/polar/AnvilPolar.java diff --git a/minestom/src/main/java/net/hollowcube/polar/MinestomPolarReader.java b/minestom/src/main/java/net/hollowcube/polar/MinestomPolarReader.java new file mode 100644 index 0000000..6c9f911 --- /dev/null +++ b/minestom/src/main/java/net/hollowcube/polar/MinestomPolarReader.java @@ -0,0 +1,14 @@ +package net.hollowcube.polar; + +import net.hollowcube.polar.buffer.MinestomBufferFactory; +import org.jetbrains.annotations.NotNull; + +public class MinestomPolarReader { + public static @NotNull PolarWorld read(byte @NotNull [] data) { + return read(data, PolarDataConverter.NOOP); + } + + public static @NotNull PolarWorld read(byte @NotNull [] data, @NotNull PolarDataConverter dataConverter) { + return PolarReader.read(data, MinestomBufferFactory.INSTANCE, dataConverter); + } +} diff --git a/src/main/java/net/hollowcube/polar/PolarLoader.java b/minestom/src/main/java/net/hollowcube/polar/PolarLoader.java similarity index 99% rename from src/main/java/net/hollowcube/polar/PolarLoader.java rename to minestom/src/main/java/net/hollowcube/polar/PolarLoader.java index 588fada..889957d 100644 --- a/src/main/java/net/hollowcube/polar/PolarLoader.java +++ b/minestom/src/main/java/net/hollowcube/polar/PolarLoader.java @@ -86,7 +86,7 @@ public class PolarLoader implements IChunkLoader { private int plainsBiomeId = 0; // Always 0 in minestom public PolarLoader(@NotNull Path path) throws IOException { - this(path, Files.exists(path) ? PolarReader.read(Files.readAllBytes(path)) : new PolarWorld()); + this(path, Files.exists(path) ? MinestomPolarReader.read(Files.readAllBytes(path)) : new PolarWorld()); } public PolarLoader(@NotNull Path savePath, @NotNull PolarWorld worldData) { @@ -96,7 +96,7 @@ public PolarLoader(@NotNull Path savePath, @NotNull PolarWorld worldData) { public PolarLoader(@NotNull InputStream inputStream) throws IOException { try (inputStream) { - this.worldData = PolarReader.read(inputStream.readAllBytes()); + this.worldData = MinestomPolarReader.read(inputStream.readAllBytes()); this.savePath = null; } } diff --git a/src/main/java/net/hollowcube/polar/PolarWorldAccess.java b/minestom/src/main/java/net/hollowcube/polar/PolarWorldAccess.java similarity index 100% rename from src/main/java/net/hollowcube/polar/PolarWorldAccess.java rename to minestom/src/main/java/net/hollowcube/polar/PolarWorldAccess.java diff --git a/src/main/java/net/hollowcube/polar/StreamingPolarLoader.java b/minestom/src/main/java/net/hollowcube/polar/StreamingPolarLoader.java similarity index 98% rename from src/main/java/net/hollowcube/polar/StreamingPolarLoader.java rename to minestom/src/main/java/net/hollowcube/polar/StreamingPolarLoader.java index e859f87..5caaae0 100644 --- a/src/main/java/net/hollowcube/polar/StreamingPolarLoader.java +++ b/minestom/src/main/java/net/hollowcube/polar/StreamingPolarLoader.java @@ -3,6 +3,7 @@ import com.github.luben.zstd.Zstd; import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import net.hollowcube.polar.buffer.MinestomBufferWrapper; import net.minestom.server.MinecraftServer; import net.minestom.server.command.builder.arguments.minecraft.ArgumentBlockState; import net.minestom.server.command.builder.exception.ArgumentSyntaxException; @@ -22,6 +23,7 @@ import static net.hollowcube.polar.PolarLoader.*; import static net.hollowcube.polar.PolarReader.*; import static net.hollowcube.polar.UnsafeOps.*; +import static net.hollowcube.polar.buffer.MinestomBufferWrapper.LIGHT_DATA; import static net.minestom.server.instance.Chunk.CHUNK_SECTION_SIZE; import static net.minestom.server.network.NetworkBuffer.*; import static net.minestom.server.network.PolarBufferAccessWidener.networkBufferAddress; @@ -137,7 +139,7 @@ private void readChunk(@NotNull NetworkBuffer buffer, int minSection, int maxSec // Load block entities final int blockEntityCount = buffer.read(VAR_INT); for (int i = 0; i < blockEntityCount; i++) { - final var blockEntity = readBlockEntity(dataConverter, version, dataVersion, buffer); + final var blockEntity = readBlockEntity(dataConverter, version, dataVersion, new MinestomBufferWrapper(buffer)); if (chunkEntries != null && chunkTickables != null) { final var block = createBlockEntity(chunk, blockEntity); final int index = CoordConversion.chunkBlockIndex(blockEntity.x(), blockEntity.y(), blockEntity.z()); @@ -150,7 +152,7 @@ private void readChunk(@NotNull NetworkBuffer buffer, int minSection, int maxSec } // Load heightmaps (if we have world data, otherwise we can skip) - int[][] heightmaps = readHeightmapData(buffer, worldAccess == null); + int[][] heightmaps = readHeightmapData(new MinestomBufferWrapper(buffer), worldAccess == null); if (worldAccess != null) worldAccess.loadHeightmaps(chunk, heightmaps); else unsafeSetNeedsCompleteHeightmapRefresh(chunk, true); } diff --git a/src/main/java/net/hollowcube/polar/UnsafeOps.java b/minestom/src/main/java/net/hollowcube/polar/UnsafeOps.java similarity index 100% rename from src/main/java/net/hollowcube/polar/UnsafeOps.java rename to minestom/src/main/java/net/hollowcube/polar/UnsafeOps.java diff --git a/minestom/src/main/java/net/hollowcube/polar/buffer/MinestomBufferFactory.java b/minestom/src/main/java/net/hollowcube/polar/buffer/MinestomBufferFactory.java new file mode 100644 index 0000000..0f64a87 --- /dev/null +++ b/minestom/src/main/java/net/hollowcube/polar/buffer/MinestomBufferFactory.java @@ -0,0 +1,26 @@ +package net.hollowcube.polar.buffer; + +import net.minestom.server.network.NetworkBuffer; +import org.jetbrains.annotations.NotNull; + +import java.util.function.Consumer; + +public class MinestomBufferFactory implements BufferFactory { + public static final MinestomBufferFactory INSTANCE = new MinestomBufferFactory(); + + private MinestomBufferFactory() { + } + + @Override + public BufferWrapper wrap(byte[] data) { + NetworkBuffer buffer = NetworkBuffer.wrap(data, 0, 0); + return new MinestomBufferWrapper(buffer); + } + + @Override + public byte[] makeArray(@NotNull Consumer<@NotNull BufferWrapper> writing) { + return NetworkBuffer.makeArray((buffer) -> { + writing.accept(new MinestomBufferWrapper(buffer)); + }); + } +} diff --git a/minestom/src/main/java/net/hollowcube/polar/buffer/MinestomBufferWrapper.java b/minestom/src/main/java/net/hollowcube/polar/buffer/MinestomBufferWrapper.java new file mode 100644 index 0000000..520864a --- /dev/null +++ b/minestom/src/main/java/net/hollowcube/polar/buffer/MinestomBufferWrapper.java @@ -0,0 +1,170 @@ +package net.hollowcube.polar.buffer; + +import net.kyori.adventure.nbt.BinaryTag; +import net.minestom.server.network.NetworkBuffer; +import net.minestom.server.utils.nbt.BinaryTagReader; +import org.jetbrains.annotations.Nullable; + +import java.io.DataInputStream; +import java.io.InputStream; +import java.util.Arrays; + +public class MinestomBufferWrapper implements BufferWrapper { + private final NetworkBuffer buffer; + + public MinestomBufferWrapper(NetworkBuffer buffer) { + this.buffer = buffer; + } + + @Override + public void writeIndex(int index) { + buffer.writeIndex(index); + } + + @Override + public int readInt() { + return buffer.read(NetworkBuffer.INT); + } + + @Override + public void writeInt(int value) { + buffer.write(NetworkBuffer.INT, value); + } + + @Override + public short readShort() { + return buffer.read(NetworkBuffer.SHORT); + } + + @Override + public void writeShort(short value) { + buffer.write(NetworkBuffer.SHORT, value); + } + + @Override + public int readVarInt() { + return buffer.read(NetworkBuffer.VAR_INT); + } + + @Override + public void writeVarInt(int value) { + buffer.write(NetworkBuffer.VAR_INT, value); + } + + @Override + public byte readByte() { + return buffer.read(NetworkBuffer.BYTE); + } + + @Override + public void writeByte(byte value) { + buffer.write(NetworkBuffer.BYTE, value); + } + + @Override + public boolean readBoolean() { + return buffer.read(NetworkBuffer.BOOLEAN); + } + + @Override + public void writeBoolean(boolean value) { + buffer.write(NetworkBuffer.BOOLEAN, value); + } + + @Override + public byte[] readByteArray() { + return buffer.read(NetworkBuffer.BYTE_ARRAY); + } + + @Override + public void writeByteArray(byte[] value) { + buffer.write(NetworkBuffer.BYTE_ARRAY, value); + } + + @Override + public byte[] readRawBytes() { + return buffer.read(NetworkBuffer.RAW_BYTES); + } + + @Override + public void writeRawBytes(byte[] value) { + buffer.write(NetworkBuffer.RAW_BYTES, value); + } + + @Override + public String[] readStringArray(int maxSize) { + return buffer.read(NetworkBuffer.STRING.list(maxSize)).toArray(String[]::new); + } + + @Override + public void writeStringArray(String[] value) { + buffer.write(NetworkBuffer.STRING.list(), Arrays.asList(value)); + } + + @Override + public long[] readLongArray() { + return buffer.read(NetworkBuffer.LONG_ARRAY); + } + + @Override + public void writeLongArray(long[] value) { + buffer.write(NetworkBuffer.LONG_ARRAY, value); + } + + @Override + public void advanceRead(int length) { + buffer.advanceRead(length); + } + + @Override + public @Nullable String readOptString() { + return buffer.read(NetworkBuffer.STRING.optional()); + } + + @Override + public void writeOptString(@Nullable String value) { + buffer.write(NetworkBuffer.STRING.optional(), value); + } + + @Override + public BinaryTag readNbt() { + return buffer.read(NetworkBuffer.NBT); + } + + /** + * Minecraft (so Minestom) had a breaking change in NBT reading in 1.20.2. This method replicates the old + * behavior which we use for any Polar version less than VERSION_MINESTOM_NBT_READ_BREAK. + * + * @see NetworkBuffer#NBT + */ + @Override + public BinaryTag readLegacyNbt() { + try { + var nbtReader = new BinaryTagReader(new DataInputStream(new InputStream() { + public int read() { + return buffer.read(NetworkBuffer.BYTE) & 255; + } + + public int available() { + return (int) buffer.readableBytes(); + } + })); + return nbtReader.readNamed().getValue(); + } catch (Exception e) { + throw new RuntimeException(e); + } + + } + + @Override + public void writeOptNbt(@Nullable BinaryTag value) { + buffer.write(NetworkBuffer.NBT.optional(), value); + } + + public static final NetworkBuffer.Type LIGHT_DATA = NetworkBuffer.FixedRawBytes(2048); + + @Override + public byte[] readLightData() { + return buffer.read(LIGHT_DATA); + } +} diff --git a/src/main/java/net/minestom/server/instance/palette/PolarPaletteAccessWidener.java b/minestom/src/main/java/net/minestom/server/instance/palette/PolarPaletteAccessWidener.java similarity index 100% rename from src/main/java/net/minestom/server/instance/palette/PolarPaletteAccessWidener.java rename to minestom/src/main/java/net/minestom/server/instance/palette/PolarPaletteAccessWidener.java diff --git a/src/main/java/net/minestom/server/network/PolarBufferAccessWidener.java b/minestom/src/main/java/net/minestom/server/network/PolarBufferAccessWidener.java similarity index 100% rename from src/main/java/net/minestom/server/network/PolarBufferAccessWidener.java rename to minestom/src/main/java/net/minestom/server/network/PolarBufferAccessWidener.java diff --git a/src/test/java/net/hollowcube/polar/TestAnvilPolar.java b/minestom/src/test/java/net/hollowcube/polar/TestAnvilPolar.java similarity index 100% rename from src/test/java/net/hollowcube/polar/TestAnvilPolar.java rename to minestom/src/test/java/net/hollowcube/polar/TestAnvilPolar.java diff --git a/src/test/java/net/hollowcube/polar/TestBigPaletteReadWrite.java b/minestom/src/test/java/net/hollowcube/polar/TestBigPaletteReadWrite.java similarity index 97% rename from src/test/java/net/hollowcube/polar/TestBigPaletteReadWrite.java rename to minestom/src/test/java/net/hollowcube/polar/TestBigPaletteReadWrite.java index 735c7ad..40446d2 100644 --- a/src/test/java/net/hollowcube/polar/TestBigPaletteReadWrite.java +++ b/minestom/src/test/java/net/hollowcube/polar/TestBigPaletteReadWrite.java @@ -93,7 +93,7 @@ void testLegacyLoader() { var worldBytes = PolarWriter.write(loader.world()); var loadInstance = new InstanceContainer(UUID.randomUUID(), DimensionType.OVERWORLD); - loadInstance.setChunkLoader(new PolarLoader(PolarReader.read(worldBytes))); + loadInstance.setChunkLoader(new PolarLoader(MinestomPolarReader.read(worldBytes))); loadInstance.loadChunk(0, 0).join(); random = new Random(22); diff --git a/src/test/java/net/hollowcube/polar/TestCustomBiomes.java b/minestom/src/test/java/net/hollowcube/polar/TestCustomBiomes.java similarity index 100% rename from src/test/java/net/hollowcube/polar/TestCustomBiomes.java rename to minestom/src/test/java/net/hollowcube/polar/TestCustomBiomes.java diff --git a/src/test/java/net/hollowcube/polar/TestNonStandardHeight.java b/minestom/src/test/java/net/hollowcube/polar/TestNonStandardHeight.java similarity index 97% rename from src/test/java/net/hollowcube/polar/TestNonStandardHeight.java rename to minestom/src/test/java/net/hollowcube/polar/TestNonStandardHeight.java index a96139e..ea47485 100644 --- a/src/test/java/net/hollowcube/polar/TestNonStandardHeight.java +++ b/minestom/src/test/java/net/hollowcube/polar/TestNonStandardHeight.java @@ -52,6 +52,6 @@ void testNonStandardDimensionHeight() { byte[] bytes = PolarWriter.write(loader.world()); - PolarReader.read(bytes); + MinestomPolarReader.read(bytes); } } diff --git a/src/test/java/net/hollowcube/polar/TestPolarReader.java b/minestom/src/test/java/net/hollowcube/polar/TestPolarReader.java similarity index 89% rename from src/test/java/net/hollowcube/polar/TestPolarReader.java rename to minestom/src/test/java/net/hollowcube/polar/TestPolarReader.java index e71e490..e412822 100644 --- a/src/test/java/net/hollowcube/polar/TestPolarReader.java +++ b/minestom/src/test/java/net/hollowcube/polar/TestPolarReader.java @@ -13,7 +13,7 @@ class TestPolarReader { @Test void testReadInvalidMagic() { var e = assertThrows(PolarReader.Error.class, () -> { - PolarReader.read(new byte[]{0x00, 0x00, 0x00, 0x00, 0x00, 0x00}); + MinestomPolarReader.read(new byte[]{0x00, 0x00, 0x00, 0x00, 0x00, 0x00}); }); assertEquals("Invalid magic number", e.getMessage()); } @@ -21,7 +21,7 @@ void testReadInvalidMagic() { @Test void testNewerVersionFail() { var e = assertThrows(PolarReader.Error.class, () -> { - PolarReader.read(new byte[]{ + MinestomPolarReader.read(new byte[]{ 0x50, 0x6F, 0x6C, 0x72, // magic number 0x50, 0x50, // version }); @@ -42,7 +42,7 @@ void testHeightmapReadWrite() { world.updateChunkAt(0, 0, new PolarChunk(0, 0, emptySections, List.of(), heightmaps, new byte[0])); var raw = PolarWriter.write(world); - var newWorld = PolarReader.read(raw); + var newWorld = MinestomPolarReader.read(raw); var newChunk = newWorld.chunkAt(0, 0); var newHeightmap = newChunk.heightmap(0); for (int i = 0; i < PolarChunk.HEIGHTMAP_SIZE; i++) { diff --git a/src/test/java/net/hollowcube/polar/TestReaderBackwardsCompatibility.java b/minestom/src/test/java/net/hollowcube/polar/TestReaderBackwardsCompatibility.java similarity index 93% rename from src/test/java/net/hollowcube/polar/TestReaderBackwardsCompatibility.java rename to minestom/src/test/java/net/hollowcube/polar/TestReaderBackwardsCompatibility.java index 9889eda..a6f5b20 100644 --- a/src/test/java/net/hollowcube/polar/TestReaderBackwardsCompatibility.java +++ b/minestom/src/test/java/net/hollowcube/polar/TestReaderBackwardsCompatibility.java @@ -37,7 +37,7 @@ private static void runTest(int version) { assertNotNull(is); var worldData = assertDoesNotThrow(is::readAllBytes); - var world = assertDoesNotThrow(() -> PolarReader.read(worldData)); + var world = assertDoesNotThrow(() -> MinestomPolarReader.read(worldData)); assertNotNull(world); assertEquals(32 * 32, world.chunks().size()); diff --git a/src/test/java/net/hollowcube/polar/TestUserDataWriteRead.java b/minestom/src/test/java/net/hollowcube/polar/TestUserDataWriteRead.java similarity index 100% rename from src/test/java/net/hollowcube/polar/TestUserDataWriteRead.java rename to minestom/src/test/java/net/hollowcube/polar/TestUserDataWriteRead.java diff --git a/src/test/java/net/hollowcube/polar/UpdateTimeWorldAccess.java b/minestom/src/test/java/net/hollowcube/polar/UpdateTimeWorldAccess.java similarity index 100% rename from src/test/java/net/hollowcube/polar/UpdateTimeWorldAccess.java rename to minestom/src/test/java/net/hollowcube/polar/UpdateTimeWorldAccess.java diff --git a/src/test/java/net/hollowcube/polar/demo/DemoServer.java b/minestom/src/test/java/net/hollowcube/polar/demo/DemoServer.java similarity index 100% rename from src/test/java/net/hollowcube/polar/demo/DemoServer.java rename to minestom/src/test/java/net/hollowcube/polar/demo/DemoServer.java diff --git a/src/test/java/net/hollowcube/polar/regression/TestIssue17.java b/minestom/src/test/java/net/hollowcube/polar/regression/TestIssue17.java similarity index 100% rename from src/test/java/net/hollowcube/polar/regression/TestIssue17.java rename to minestom/src/test/java/net/hollowcube/polar/regression/TestIssue17.java diff --git a/src/test/resources/backward/1.polar b/minestom/src/test/resources/backward/1.polar similarity index 100% rename from src/test/resources/backward/1.polar rename to minestom/src/test/resources/backward/1.polar diff --git a/src/test/resources/backward/2.polar b/minestom/src/test/resources/backward/2.polar similarity index 100% rename from src/test/resources/backward/2.polar rename to minestom/src/test/resources/backward/2.polar diff --git a/src/test/resources/backward/3.polar b/minestom/src/test/resources/backward/3.polar similarity index 100% rename from src/test/resources/backward/3.polar rename to minestom/src/test/resources/backward/3.polar diff --git a/src/test/resources/backward/4.polar b/minestom/src/test/resources/backward/4.polar similarity index 100% rename from src/test/resources/backward/4.polar rename to minestom/src/test/resources/backward/4.polar diff --git a/src/test/resources/backward/5.polar b/minestom/src/test/resources/backward/5.polar similarity index 100% rename from src/test/resources/backward/5.polar rename to minestom/src/test/resources/backward/5.polar diff --git a/src/test/resources/bench/bench-nozstd.polar b/minestom/src/test/resources/bench/bench-nozstd.polar similarity index 100% rename from src/test/resources/bench/bench-nozstd.polar rename to minestom/src/test/resources/bench/bench-nozstd.polar diff --git a/src/test/resources/bench/bench.polar b/minestom/src/test/resources/bench/bench.polar similarity index 100% rename from src/test/resources/bench/bench.polar rename to minestom/src/test/resources/bench/bench.polar diff --git a/src/test/resources/bench/region/r.0.0.mca b/minestom/src/test/resources/bench/region/r.0.0.mca similarity index 100% rename from src/test/resources/bench/region/r.0.0.mca rename to minestom/src/test/resources/bench/region/r.0.0.mca diff --git a/src/test/resources/bench_1205.polar b/minestom/src/test/resources/bench_1205.polar similarity index 100% rename from src/test/resources/bench_1205.polar rename to minestom/src/test/resources/bench_1205.polar diff --git a/src/test/resources/emclobby.polar b/minestom/src/test/resources/emclobby.polar similarity index 100% rename from src/test/resources/emclobby.polar rename to minestom/src/test/resources/emclobby.polar diff --git a/netty/build.gradle.kts b/netty/build.gradle.kts new file mode 100644 index 0000000..455dbad --- /dev/null +++ b/netty/build.gradle.kts @@ -0,0 +1,14 @@ +plugins { + `java-library` +} + +repositories { + mavenLocal() + mavenCentral() + maven(url = "https://jitpack.io") +} + +dependencies { + implementation(project(":common")) + implementation("io.netty:netty-all:4.2.1.Final") +} \ No newline at end of file diff --git a/netty/src/main/java/net/hollowcube/polar/buffer/ByteBufUtils.java b/netty/src/main/java/net/hollowcube/polar/buffer/ByteBufUtils.java new file mode 100644 index 0000000..b890a50 --- /dev/null +++ b/netty/src/main/java/net/hollowcube/polar/buffer/ByteBufUtils.java @@ -0,0 +1,71 @@ +package net.hollowcube.polar.buffer; + +import io.netty.buffer.ByteBuf; + +import java.nio.charset.StandardCharsets; + +public class ByteBufUtils { + + private static final int SEGMENT_BITS = 0x7F; + private static final int CONTINUE_BIT = 0x80; + + public static int readVarInt(ByteBuf buffer) { + int value = 0; + int position = 0; + int currentByte; + + while (buffer.isReadable()) { + currentByte = buffer.readByte() & 0xFF; + value |= (currentByte & SEGMENT_BITS) << position; + + if ((currentByte & CONTINUE_BIT) == 0) { + break; + } + + position += 7; + if (position >= 32) { + throw new RuntimeException("VarInt is too big"); + } + } + return value; + } + + public static void writeVarInt(ByteBuf buffer, int value) { + while (true) { + if ((value & ~SEGMENT_BITS) == 0) { + buffer.writeByte(value); + return; + } + buffer.writeByte((value & SEGMENT_BITS) | CONTINUE_BIT); + value >>>= 7; + } + } + + public static String readString(ByteBuf buffer, int maxLength) { + final int maxSize = maxLength * 3; + final int size = readVarInt(buffer); + + if (size > maxSize) { + throw new IllegalStateException("The received string was longer than the allowed " + maxSize + " (" + size + " > " + maxSize + ")"); + } + if (size < 0) { + throw new IllegalStateException("The received string's length was smaller than 0"); + } + + byte[] bytes = new byte[size]; + buffer.readBytes(bytes); + String str = new String(bytes, StandardCharsets.UTF_8); + + if (str.length() > maxLength) { + throw new IllegalStateException("The received string was longer than the allowed (" + str.length() + " > " + maxLength + ")"); + } + + return str; + } + + public static void writeString(ByteBuf buffer, String text) { + byte[] utf8Bytes = text.getBytes(StandardCharsets.UTF_8); + writeVarInt(buffer, utf8Bytes.length); + buffer.writeBytes(utf8Bytes); + } +} diff --git a/netty/src/main/java/net/hollowcube/polar/buffer/NettyBufferFactory.java b/netty/src/main/java/net/hollowcube/polar/buffer/NettyBufferFactory.java new file mode 100644 index 0000000..d3564bd --- /dev/null +++ b/netty/src/main/java/net/hollowcube/polar/buffer/NettyBufferFactory.java @@ -0,0 +1,20 @@ +package net.hollowcube.polar.buffer; + +import io.netty.buffer.Unpooled; +import org.jetbrains.annotations.NotNull; + +import java.util.function.Consumer; + +public class NettyBufferFactory implements BufferFactory { + @Override + public BufferWrapper wrap(byte[] data) { + return new NettyBufferWrapper(Unpooled.copiedBuffer(data)); + } + + @Override + public byte[] makeArray(@NotNull Consumer<@NotNull BufferWrapper> writing) { + NettyBufferWrapper buffer = new NettyBufferWrapper(Unpooled.buffer()); + writing.accept(buffer); + return buffer.readRawBytes(); + } +} diff --git a/netty/src/main/java/net/hollowcube/polar/buffer/NettyBufferWrapper.java b/netty/src/main/java/net/hollowcube/polar/buffer/NettyBufferWrapper.java new file mode 100644 index 0000000..00119f2 --- /dev/null +++ b/netty/src/main/java/net/hollowcube/polar/buffer/NettyBufferWrapper.java @@ -0,0 +1,174 @@ +package net.hollowcube.polar.buffer; + +import io.netty.buffer.ByteBuf; +import net.kyori.adventure.nbt.BinaryTag; +import org.jetbrains.annotations.Nullable; + +public class NettyBufferWrapper implements BufferWrapper { + private final ByteBuf buffer; + + public NettyBufferWrapper(ByteBuf buffer) { + this.buffer = buffer; + } + + @Override + public void writeIndex(int index) { + buffer.writerIndex(index); + } + + @Override + public void advanceRead(int length) { + buffer.readBytes(length); + } + + @Override + public boolean readBoolean() { + return buffer.readBoolean(); + } + + @Override + public void writeBoolean(boolean value) { + buffer.writeBoolean(value); + } + + @Override + public byte readByte() { + return buffer.readByte(); + } + + @Override + public void writeByte(byte value) { + buffer.writeByte(value); + } + + @Override + public short readShort() { + return buffer.readShort(); + } + + @Override + public void writeShort(short value) { + buffer.writeShort(value); + } + + @Override + public int readInt() { + return buffer.readInt(); + } + + @Override + public void writeInt(int value) { + buffer.writeInt(value); + } + + @Override + public int readVarInt() { + return ByteBufUtils.readVarInt(buffer); + } + + @Override + public void writeVarInt(int value) { + ByteBufUtils.writeVarInt(buffer, value); + } + + @Override + public byte[] readByteArray() { + int len = readVarInt(); + var byteArray = new byte[len]; + buffer.readBytes(byteArray); + return byteArray; + } + + @Override + public void writeByteArray(byte[] value) { + writeVarInt(value.length); + buffer.writeBytes(value); + } + + @Override + public byte[] readRawBytes() { + int len = buffer.readableBytes(); + var byteArray = new byte[len]; + buffer.readBytes(byteArray); + return byteArray; + } + + @Override + public void writeRawBytes(byte[] value) { + buffer.writeBytes(value); + } + + @Override + public String[] readStringArray(int maxSize) { + int len = readVarInt(); + var array = new String[len]; + for (int i = 0; i < len; i++) { + array[i] = ByteBufUtils.readString(buffer, maxSize); + } + return array; + } + + @Override + public void writeStringArray(String[] value) { + writeVarInt(value.length); + for (String s : value) { + ByteBufUtils.writeString(buffer, s); + } + } + + @Override + public long[] readLongArray() { + int len = readVarInt(); + var array = new long[len]; + for (int i = 0; i < len; i++) { + array[i] = buffer.readLong(); + } + return array; + } + + @Override + public void writeLongArray(long[] value) { + writeVarInt(value.length); + for (long l : value) { + buffer.writeLong(l); + } + } + + @Override + public @Nullable String readOptString() { + boolean hasValue = readBoolean(); + if (!hasValue) return null; + return ByteBufUtils.readString(buffer, Short.MAX_VALUE); + } + + @Override + public void writeOptString(@Nullable String value) { + boolean hasValue = value != null; + writeBoolean(hasValue); + if (hasValue) { + ByteBufUtils.writeString(buffer, value); + } + } + + @Override + public BinaryTag readNbt() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public BinaryTag readLegacyNbt() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public void writeOptNbt(@Nullable BinaryTag value) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public byte[] readLightData() { + byte[] bytes = new byte[2048]; + buffer.readBytes(bytes); + return bytes; + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 1d94c4e..c34bfa3 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,2 +1,15 @@ +pluginManagement { + plugins { + kotlin("jvm") version "2.1.20" + } +} +plugins { + id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0" +} rootProject.name = "polar" +include( + "common", + "minestom", + "netty" +) \ No newline at end of file