From 023d9c96ee180b5d5b46acce9a3e1c35ee8113ed Mon Sep 17 00:00:00 2001 From: RecursivePineapple Date: Wed, 26 Nov 2025 10:08:20 -0500 Subject: [PATCH] Cubic Chunks Compatibility --- .../falsepattern/chunk/api/DataManager.java | 50 ++++++++++++++- .../chunk/internal/DataRegistryImpl.java | 63 +++++++++++++++++++ .../internal/vanilla/BlockIDManager.java | 32 +++++++++- .../chunk/internal/vanilla/NibbleManager.java | 17 ++++- 4 files changed, 158 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/falsepattern/chunk/api/DataManager.java b/src/main/java/com/falsepattern/chunk/api/DataManager.java index 45c75b6..fdecad7 100644 --- a/src/main/java/com/falsepattern/chunk/api/DataManager.java +++ b/src/main/java/com/falsepattern/chunk/api/DataManager.java @@ -22,6 +22,7 @@ package com.falsepattern.chunk.api; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -84,6 +85,9 @@ interface PacketDataManager extends DataManager { * Serializes your data into a packet. * * @param chunk The chunk to serialize. + * @param subChunkMask The mask that controls which subchunks need to be serialized. This will be 0 when cubic chunks is + * present - only chunk-specific information (like biomes) should be sent in this case. + * @param forceUpdate True when the chunk is first synced. */ @Contract(mutates = "param4") void writeToBuffer(Chunk chunk, int subChunkMask, boolean forceUpdate, ByteBuffer buffer); @@ -91,13 +95,55 @@ interface PacketDataManager extends DataManager { /** * Deserializes your data from a packet. * - * @param chunk The chunk to deserialize. - * @param buffer The packet buffer to read from. + * @param chunk The chunk to deserialize. + * @param subChunkMask The mask that controls which subchunks need to be deserialized. This will be 0 when cubic chunks is + * present - only chunk-specific information (like biomes) should be sent in this case. + * @param forceUpdate True when the chunk is first synced. */ @Contract(mutates = "param1,param4") void readFromBuffer(Chunk chunk, int subChunkMask, boolean forceUpdate, ByteBuffer buffer); } + /** + * A cube-specific variant of {@link PacketDataManager}. This is only used by cubic chunks. + * + * @author RecursivePineapple + * @since 0.7.1 + * @apiNote Cubes, subchunks, and ExtendedBlockStorages are equivalent. A cube is a subchunk and a cube contains an EBS. + * To keep dependencies simple, cube-specific information is not available to this interface, but a cube's location can be + * retrieved by combining the chunk location and the EBS Y level. Note that the EBS's Y level can be <0 and >= 16. + */ + @ApiStatus.Experimental + interface CubicPacketDataManager extends DataManager { + + /** + * @return The maximum amount of bytes your data can take up in a packet. + * + * @implSpec This is used to determine the size of the packet compression/decompression buffer. + * Only called ONCE, during registration! + */ + @Contract(pure = true) + int maxPacketSizeCubic(); + + /** + * Serializes your data into a packet. + * + * @param chunk The chunk that contains the subchunk. + * @param blockStorage The subchunk (cube) that was updated. + */ + @Contract(mutates = "param3") + void writeToBuffer(Chunk chunk, ExtendedBlockStorage blockStorage, ByteBuffer buffer); + + /** + * Deserializes your data from a packet. + * + * @param chunk The chunk that contains the subchunk. + * @param blockStorage The subchunk (cube) that was updated. + */ + @Contract(mutates = "param1,param2,param3") + void readFromBuffer(Chunk chunk, ExtendedBlockStorage blockStorage, ByteBuffer buffer); + } + /** * Implement this interface if you additionally want to synchronize your data on single and multi-block updates, * not just chunk updates. diff --git a/src/main/java/com/falsepattern/chunk/internal/DataRegistryImpl.java b/src/main/java/com/falsepattern/chunk/internal/DataRegistryImpl.java index 936bfcc..00d3399 100644 --- a/src/main/java/com/falsepattern/chunk/internal/DataRegistryImpl.java +++ b/src/main/java/com/falsepattern/chunk/internal/DataRegistryImpl.java @@ -59,12 +59,14 @@ public class DataRegistryImpl { private static final Map managersUnordered = new HashMap<>(); private static final SortedSet managers = new TreeSet<>(); private static final DualMap packetManagers = new DualMap<>(); + private static final DualMap cubicPacketManagers = new DualMap<>(); private static final DualMap blockPacketManagers = new DualMap<>(); private static final DualMap NBTManagers = new DualMap<>(); private static final SortedMap chunkNBTManagers = new TreeMap<>(); private static final SortedMap subChunkNBTManagers = new TreeMap<>(); private static final Set disabledManagers = new HashSet<>(); private static int maxPacketSize = 4; + private static int maxPacketSizeCubic = 4; @Data private static class PacketManagerInfo { @@ -72,6 +74,12 @@ private static class PacketManagerInfo { public final DataManager.PacketDataManager manager; } + @Data + private static class CubicPacketManagerInfo { + public final int maxPacketSize; + public final DataManager.CubicPacketDataManager manager; + } + public static void registerDataManager(DataManager manager, int ordering) throws IllegalStateException, IllegalArgumentException { if (Loader.instance().getLoaderState() != LoaderState.INITIALIZATION) { throw new IllegalStateException("ChunkDataManager registration is not allowed at this time! Please register your ChunkDataManager in the init phase."); @@ -100,6 +108,12 @@ public static void registerDataManager(DataManager manager, int ordering) throws val blockPacketManager = (DataManager.BlockPacketDataManager) manager; blockPacketManagers.put(ord, blockPacketManager); } + if (manager instanceof DataManager.CubicPacketDataManager) { + val cubicPacketManager = (DataManager.CubicPacketDataManager) manager; + val maxSize = cubicPacketManager.maxPacketSizeCubic(); + maxPacketSizeCubic += 4 + id.getBytes(StandardCharsets.UTF_8).length + 4 + maxSize; + cubicPacketManagers.put(ord, new CubicPacketManagerInfo(maxSize, cubicPacketManager)); + } if (manager instanceof DataManager.StorageDataManager) { NBTManagers.put(ord, (DataManager.StorageDataManager) manager); if (manager instanceof DataManager.ChunkDataManager) { @@ -127,6 +141,10 @@ public static void disableDataManager(String domain, String id) { val removed = packetManagers.remove(ord); maxPacketSize -= 4 + id.getBytes(StandardCharsets.UTF_8).length + 4 + removed.maxPacketSize; } + if (cubicPacketManagers.containsKey(ord)) { + val removed = cubicPacketManagers.remove(ord); + maxPacketSizeCubic -= 4 + id.getBytes(StandardCharsets.UTF_8).length + 4 + removed.maxPacketSize; + } blockPacketManagers.remove(ord); chunkNBTManagers.remove(ord); subChunkNBTManagers.remove(ord); @@ -141,6 +159,10 @@ public static int maxPacketSize() { return maxPacketSize; } + public static int maxPacketSizeCubic() { + return maxPacketSizeCubic; + } + private static void writeString(ByteBuffer buffer, String string) { val bytes = string.getBytes(); buffer.putInt(bytes.length); @@ -177,6 +199,29 @@ public static void readFromBuffer(Chunk chunk, int subChunkMask, boolean forceUp } } + public static void readFromBufferCubic(Chunk chunk, ExtendedBlockStorage blockStorage, byte[] data) { + val buf = ByteBuffer.wrap(data); + buf.order(ByteOrder.LITTLE_ENDIAN); + int count = buf.getInt(); + for (int i = 0; i < count; i++) { + val id = readString(buf); + val length = buf.getInt(); + val managerInfo = cubicPacketManagers.get(id); + if (managerInfo == null) { + Common.LOG.error("Received data for unknown CubicPacketDataManager " + id + ". Skipping."); + buf.position(buf.position() + length); + continue; + } + if (length > managerInfo.maxPacketSize) { + Common.LOG.error("Received packet larger than max size for CubicPacketDataManager " + id + "! Continuing anyways, things might break!"); + } + int start = buf.position(); + val slice = createSlice(buf, start, length); + managerInfo.manager.readFromBuffer(chunk, blockStorage, slice); + buf.position(start + length); + } + } + public static int writeToBuffer(Chunk chunk, int subChunkMask, boolean forceUpdate, byte[] data) { val buf = ByteBuffer.wrap(data); buf.order(ByteOrder.LITTLE_ENDIAN); @@ -195,6 +240,24 @@ public static int writeToBuffer(Chunk chunk, int subChunkMask, boolean forceUpda return buf.position(); } + public static int writeToBufferCubic(Chunk chunk, ExtendedBlockStorage blockStorage, byte[] data) { + val buf = ByteBuffer.wrap(data); + buf.order(ByteOrder.LITTLE_ENDIAN); + buf.putInt(cubicPacketManagers.size()); + for (val pair : cubicPacketManagers.entrySet()) { + val ord = pair.getKey(); + val managerInfo = pair.getValue(); + writeString(buf, ord.id); + int start = buf.position() + 4; + val slice = createSlice(buf, start, managerInfo.maxPacketSize); + managerInfo.manager.writeToBuffer(chunk, blockStorage, slice); + int length = slice.position(); + buf.putInt(length); + buf.position(start + length); + } + return buf.position(); + } + public static void writeBlockToPacket(Chunk chunk, int x, int y, int z, S23PacketBlockChange packet) { for (val manager : blockPacketManagers.values()) { manager.writeBlockToPacket(chunk, x, y, z, packet); diff --git a/src/main/java/com/falsepattern/chunk/internal/vanilla/BlockIDManager.java b/src/main/java/com/falsepattern/chunk/internal/vanilla/BlockIDManager.java index c2c0f03..b3a28ba 100644 --- a/src/main/java/com/falsepattern/chunk/internal/vanilla/BlockIDManager.java +++ b/src/main/java/com/falsepattern/chunk/internal/vanilla/BlockIDManager.java @@ -40,7 +40,7 @@ import static com.falsepattern.chunk.internal.Common.BLOCKS_PER_SUBCHUNK; import static com.falsepattern.chunk.internal.Common.SUBCHUNKS_PER_CHUNK; -public class BlockIDManager extends VanillaManager implements DataManager.PacketDataManager, DataManager.BlockPacketDataManager, DataManager.SubChunkDataManager { +public class BlockIDManager extends VanillaManager implements DataManager.PacketDataManager, DataManager.CubicPacketDataManager, DataManager.BlockPacketDataManager, DataManager.SubChunkDataManager { private static final int LSB_BYTES_PER_SUBCHUNK = BLOCKS_PER_SUBCHUNK; private static final int MSB_BYTES_PER_SUBCHUNK = BLOCKS_PER_SUBCHUNK / 2; private static final int HEADER_SIZE = 2; @@ -100,6 +100,36 @@ public void readFromBuffer(Chunk chunk, int subChunkMask, boolean forceUpdate, B } } + @Override + public int maxPacketSizeCubic() { + return HEADER_SIZE + LSB_BYTES_PER_SUBCHUNK + MSB_BYTES_PER_SUBCHUNK; + } + + @Override + public void writeToBuffer(Chunk chunk, ExtendedBlockStorage blockStorage, ByteBuffer buffer) { + buffer.put(blockStorage.getBlockLSBArray()); + + if (blockStorage.getBlockMSBArray() != null) { + buffer.put((byte) 1); + buffer.put(blockStorage.getBlockMSBArray().data); + } else { + buffer.put((byte) 0); + } + } + + @Override + public void readFromBuffer(Chunk chunk, ExtendedBlockStorage blockStorage, ByteBuffer buffer) { + buffer.get(blockStorage.getBlockLSBArray()); + + if (buffer.get() != 0) { + if (blockStorage.getBlockMSBArray() == null) { + blockStorage.createBlockMSBArray(); + } + + buffer.get(blockStorage.getBlockMSBArray().data); + } + } + @Override public boolean subChunkPrivilegedAccess() { return true; diff --git a/src/main/java/com/falsepattern/chunk/internal/vanilla/NibbleManager.java b/src/main/java/com/falsepattern/chunk/internal/vanilla/NibbleManager.java index d93c6a6..e291802 100644 --- a/src/main/java/com/falsepattern/chunk/internal/vanilla/NibbleManager.java +++ b/src/main/java/com/falsepattern/chunk/internal/vanilla/NibbleManager.java @@ -32,7 +32,7 @@ import java.nio.ByteBuffer; -public abstract class NibbleManager extends VanillaManager implements DataManager.PacketDataManager { +public abstract class NibbleManager extends VanillaManager implements DataManager.PacketDataManager, DataManager.CubicPacketDataManager { public static final int BYTES_PER_SUBCHUNK = Common.BLOCKS_PER_SUBCHUNK / 2; protected abstract NibbleArray getNibbleArray(ExtendedBlockStorage subChunk); @@ -67,4 +67,19 @@ public void readFromBuffer(Chunk chunk, int subChunkMask, boolean forceUpdate, B } } } + + @Override + public int maxPacketSizeCubic() { + return BYTES_PER_SUBCHUNK; + } + + @Override + public void writeToBuffer(Chunk chunk, ExtendedBlockStorage blockStorage, ByteBuffer buffer) { + buffer.put(getNibbleArray(blockStorage).data, 0, BYTES_PER_SUBCHUNK); + } + + @Override + public void readFromBuffer(Chunk chunk, ExtendedBlockStorage blockStorage, ByteBuffer buffer) { + buffer.get(getNibbleArray(blockStorage).data, 0, BYTES_PER_SUBCHUNK); + } }