From 9858bb5e9911b759c1a9ec2503feb76b798d55cd Mon Sep 17 00:00:00 2001 From: FalsePattern Date: Wed, 13 Dec 2023 16:34:22 +0100 Subject: [PATCH] add data cloning API --- gradle.properties | 2 +- .../com/falsepattern/chunk/api/ArrayUtil.java | 114 ++++++++++++++++++ .../chunk/api/ChunkDataManager.java | 36 ++++++ .../chunk/api/ChunkDataRegistry.java | 34 +++++- .../falsepattern/chunk/internal/ChunkAPI.java | 2 +- .../chunk/internal/ChunkDataRegistryImpl.java | 12 ++ .../chunk/internal/vanilla/BiomeManager.java | 6 + .../internal/vanilla/BlockIDManager.java | 7 ++ .../internal/vanilla/BlocklightManager.java | 6 + .../internal/vanilla/LightingManager.java | 7 ++ .../internal/vanilla/MetadataManager.java | 6 + .../internal/vanilla/SkylightManager.java | 10 ++ src/main/resources/META-INF/chunkapi_at.cfg | 3 + 13 files changed, 241 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/falsepattern/chunk/api/ArrayUtil.java create mode 100644 src/main/resources/META-INF/chunkapi_at.cfg diff --git a/gradle.properties b/gradle.properties index 56ec6bd..2844fce 100644 --- a/gradle.properties +++ b/gradle.properties @@ -166,7 +166,7 @@ mixinConfigs = # Specify the configuration file for Forge's access transformers here. I must be placed into /src/main/resources/META-INF/ # Example value: mymodid_at.cfg -accessTransformersFile = +accessTransformersFile = chunkapi_at.cfg # Specify the core mod entry class if you use a core mod. This class must implement IFMLLoadingPlugin! # This parameter is for legacy compatibility only diff --git a/src/main/java/com/falsepattern/chunk/api/ArrayUtil.java b/src/main/java/com/falsepattern/chunk/api/ArrayUtil.java new file mode 100644 index 0000000..b78c301 --- /dev/null +++ b/src/main/java/com/falsepattern/chunk/api/ArrayUtil.java @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2023 FalsePattern + * This work is licensed under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. + * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-nd/4.0/ + * or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. + */ + +package com.falsepattern.chunk.api; + +import com.falsepattern.lib.StableAPI; + +import net.minecraft.world.chunk.NibbleArray; + +import java.util.Arrays; + +/** + * Miscellaneous utilities for in-place array transfer (where possible). + */ +@StableAPI(since = "0.5.0") +public class ArrayUtil { + public static boolean[] copyArray(boolean[] src, boolean[] dst) { + if (src == null) + return null; + if (dst == null || src.length != dst.length) + return Arrays.copyOf(src, src.length); + System.arraycopy(src, 0, dst, 0, src.length); + return dst; + } + + public static byte[] copyArray(byte[] src, byte[] dst) { + if (src == null) + return null; + if (dst == null || src.length != dst.length) + return Arrays.copyOf(src, src.length); + System.arraycopy(src, 0, dst, 0, src.length); + return dst; + } + + public static char[] copyArray(char[] src, char[] dst) { + if (src == null) + return null; + if (dst == null || src.length != dst.length) + return Arrays.copyOf(src, src.length); + System.arraycopy(src, 0, dst, 0, src.length); + return dst; + } + + public static short[] copyArray(short[] src, short[] dst) { + if (src == null) + return null; + if (dst == null || src.length != dst.length) + return Arrays.copyOf(src, src.length); + System.arraycopy(src, 0, dst, 0, src.length); + return dst; + } + + public static int[] copyArray(int[] src, int[] dst) { + if (src == null) + return null; + if (dst == null || src.length != dst.length) + return Arrays.copyOf(src, src.length); + System.arraycopy(src, 0, dst, 0, src.length); + return dst; + } + + public static long[] copyArray(long[] src, long[] dst) { + if (src == null) + return null; + if (dst == null || src.length != dst.length) + return Arrays.copyOf(src, src.length); + System.arraycopy(src, 0, dst, 0, src.length); + return dst; + } + + public static float[] copyArray(float[] src, float[] dst) { + if (src == null) + return null; + if (dst == null || src.length != dst.length) + return Arrays.copyOf(src, src.length); + System.arraycopy(src, 0, dst, 0, src.length); + return dst; + } + + public static double[] copyArray(double[] src, double[] dst) { + if (src == null) + return null; + if (dst == null || src.length != dst.length) + return Arrays.copyOf(src, src.length); + System.arraycopy(src, 0, dst, 0, src.length); + return dst; + } + + public static T[] copyArray(T[] src, T[] dst) { + if (src == null) + return null; + if (dst == null || src.length != dst.length) + return Arrays.copyOf(src, src.length); + System.arraycopy(src, 0, dst, 0, src.length); + return dst; + } + + public static NibbleArray copyArray(NibbleArray srcArray, NibbleArray dstArray) { + if (srcArray == null) { + return null; + } + if (dstArray == null) { + return new NibbleArray(Arrays.copyOf(srcArray.data, srcArray.data.length), srcArray.depthBits); + } + dstArray.depthBits = srcArray.depthBits; + dstArray.depthBitsPlusFour = srcArray.depthBitsPlusFour; + dstArray.data = copyArray(srcArray.data, dstArray.data); + return dstArray; + } +} diff --git a/src/main/java/com/falsepattern/chunk/api/ChunkDataManager.java b/src/main/java/com/falsepattern/chunk/api/ChunkDataManager.java index ab5939d..719d3bf 100644 --- a/src/main/java/com/falsepattern/chunk/api/ChunkDataManager.java +++ b/src/main/java/com/falsepattern/chunk/api/ChunkDataManager.java @@ -8,6 +8,7 @@ package com.falsepattern.chunk.api; import com.falsepattern.lib.StableAPI; +import lombok.val; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -72,6 +73,7 @@ public interface ChunkDataManager { * * @param chunk The chunk to serialize. */ + @Contract(mutates = "param1,param4") @StableAPI.Expose void writeToBuffer(Chunk chunk, int ebsMask, boolean forceUpdate, ByteBuffer data); @@ -82,6 +84,7 @@ public interface ChunkDataManager { * @param chunk The chunk to deserialize. * @param buffer The packet buffer to read from. */ + @Contract(mutates = "param1,param4") @StableAPI.Expose void readFromBuffer(Chunk chunk, int ebsMask, boolean forceUpdate, ByteBuffer buffer); } @@ -155,6 +158,7 @@ public interface ChunkDataManager { /** * Serializes your data into an NBT tag. This is used when saving the chunk to disk. */ + @Contract(mutates = "param2") @StableAPI.Expose void writeChunkToNBT(Chunk chunk, NBTTagCompound nbt); @@ -165,8 +169,23 @@ public interface ChunkDataManager { * (e.g., loading save before the mod was added), and the manager is not {@link #chunkPrivilegedAccess() privileged}. * In this case, you should initialize the data to a sane default. */ + @Contract(mutates = "param1") @StableAPI.Expose void readChunkFromNBT(Chunk chunk, NBTTagCompound nbt); + + /** + * Directly copies data from one chunk to another chunk. + * @param from The chunk to copy data from. + * @param to The chunk to copy data to. + * @implNote The default implementation uses a very inefficient method of serialize->deserialize, so this should be overridden if possible. + */ + @Contract(mutates = "param2") + @StableAPI.Expose(since = "0.5.0") + default void cloneChunk(Chunk from, Chunk to) { + val tmp = new NBTTagCompound(); + writeChunkToNBT(from, tmp); + readChunkFromNBT(to, tmp); + } } /** @@ -196,6 +215,7 @@ public interface ChunkDataManager { /** * Serializes your data into an NBT tag. This is used when saving the chunk to disk. */ + @Contract(mutates = "param3") @StableAPI.Expose void writeSectionToNBT(Chunk chunk, ExtendedBlockStorage ebs, NBTTagCompound section); @@ -205,7 +225,23 @@ public interface ChunkDataManager { * (e.g., loading save before the mod was added), and the manager is not {@link #sectionPrivilegedAccess() privileged}. * In this case, you should initialize the data to a sane default. */ + @Contract(mutates = "param2") @StableAPI.Expose void readSectionFromNBT(Chunk chunk, ExtendedBlockStorage ebs, NBTTagCompound section); + + /** + * Directly copies data from one section to another section. + * @param fromChunk The owner of the section to copy data from. + * @param from The section to copy data from. + * @param to The section to copy data to. + * @implNote The default implementation uses a very inefficient method of serialize->deserialize, so this should be overridden if possible. + */ + @Contract(mutates = "param3") + @StableAPI.Expose(since = "0.5.0") + default void cloneSection(Chunk fromChunk, ExtendedBlockStorage from, ExtendedBlockStorage to) { + val tmp = new NBTTagCompound(); + writeSectionToNBT(fromChunk, from, tmp); + readSectionFromNBT(fromChunk, to, tmp); + } } } diff --git a/src/main/java/com/falsepattern/chunk/api/ChunkDataRegistry.java b/src/main/java/com/falsepattern/chunk/api/ChunkDataRegistry.java index 1df27a0..cc0ebad 100644 --- a/src/main/java/com/falsepattern/chunk/api/ChunkDataRegistry.java +++ b/src/main/java/com/falsepattern/chunk/api/ChunkDataRegistry.java @@ -9,11 +9,16 @@ package com.falsepattern.chunk.api; import com.falsepattern.chunk.internal.ChunkDataRegistryImpl; import com.falsepattern.lib.StableAPI; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.Unmodifiable; + +import net.minecraft.world.chunk.Chunk; +import net.minecraft.world.chunk.storage.ExtendedBlockStorage; import java.util.Set; /** - * This class is used to register ChunkDataManagers. + * This class is used to register ChunkDataManagers, as well as dispatch clone requests. * * @since 0.1.0 * @see ChunkDataManager @@ -52,8 +57,33 @@ public class ChunkDataRegistry { * Returns an unmodifiable set of all registered ChunkDataManagers. * The id of a manager is its domain and id separated by a colon. (domain:id) */ + @Contract(pure = true) @StableAPI.Expose - public static Set getRegisteredManagers() { + public static @Unmodifiable Set getRegisteredManagers() { return ChunkDataRegistryImpl.getRegisteredManagers(); } + + /** + * Copies chunk-level data from a source chunk to a target chunk. + * DOES NOT copy data contained inside its ExtendedBlockStorage instances!! + * @param from The chunk to read the data from + * @param to The chunk to write the data to + */ + @Contract(mutates = "param2") + @StableAPI.Expose(since = "0.5.0") + public static void clone(Chunk from, Chunk to) { + ChunkDataRegistryImpl.cloneChunk(from, to); + } + + /** + * Copies data from a source subchunk to a target subchunk. + * @param fromChunk The chunk that owns the *from* subchunk. Used by data managers for getting metadata about the world (skylight presence, etc.) + * @param from The subchunk to read the data from + * @param to The subchunk to write the data to + */ + @Contract(mutates = "param3") + @StableAPI.Expose(since = "0.5.0") + public static void clone(Chunk fromChunk, ExtendedBlockStorage from, ExtendedBlockStorage to) { + ChunkDataRegistryImpl.cloneSection(fromChunk, from, to); + } } diff --git a/src/main/java/com/falsepattern/chunk/internal/ChunkAPI.java b/src/main/java/com/falsepattern/chunk/internal/ChunkAPI.java index 14cd010..d37c8f3 100644 --- a/src/main/java/com/falsepattern/chunk/internal/ChunkAPI.java +++ b/src/main/java/com/falsepattern/chunk/internal/ChunkAPI.java @@ -22,7 +22,7 @@ import cpw.mods.fml.common.event.FMLInitializationEvent; version = Tags.VERSION, name = Tags.MODNAME, acceptedMinecraftVersions = "[1.7.10]", - dependencies = "required-after:falsepatternlib@[0.11,);") + dependencies = "required-after:falsepatternlib@[1.0.0,);") public class ChunkAPI { @Mod.EventHandler diff --git a/src/main/java/com/falsepattern/chunk/internal/ChunkDataRegistryImpl.java b/src/main/java/com/falsepattern/chunk/internal/ChunkDataRegistryImpl.java index 1c2ef73..8815cfd 100644 --- a/src/main/java/com/falsepattern/chunk/internal/ChunkDataRegistryImpl.java +++ b/src/main/java/com/falsepattern/chunk/internal/ChunkDataRegistryImpl.java @@ -238,6 +238,18 @@ public class ChunkDataRegistryImpl { } } + public static void cloneChunk(Chunk from, Chunk to) { + for (val manager: chunkNBTManagers.values()) { + manager.cloneChunk(from, to); + } + } + + public static void cloneSection(Chunk fromChunk, ExtendedBlockStorage from, ExtendedBlockStorage to) { + for (val manager: sectionNBTManagers.values()) { + manager.cloneSection(fromChunk, from, to); + } + } + public static Set getRegisteredManagers() { return Collections.unmodifiableSet(managers); } diff --git a/src/main/java/com/falsepattern/chunk/internal/vanilla/BiomeManager.java b/src/main/java/com/falsepattern/chunk/internal/vanilla/BiomeManager.java index f18f56d..5ca9a33 100644 --- a/src/main/java/com/falsepattern/chunk/internal/vanilla/BiomeManager.java +++ b/src/main/java/com/falsepattern/chunk/internal/vanilla/BiomeManager.java @@ -8,6 +8,7 @@ package com.falsepattern.chunk.internal.vanilla; import com.falsepattern.chunk.api.ChunkDataManager; +import com.falsepattern.chunk.api.ArrayUtil; import org.jetbrains.annotations.NotNull; import net.minecraft.nbt.NBTTagCompound; @@ -60,4 +61,9 @@ public class BiomeManager extends VanillaManager chunk.setBiomeArray(nbt.getByteArray("Biomes")); } } + + @Override + public void cloneChunk(Chunk from, Chunk to) { + to.setBiomeArray(ArrayUtil.copyArray(from.getBiomeArray(), to.getBiomeArray())); + } } 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 ef647b9..55f9b5b 100644 --- a/src/main/java/com/falsepattern/chunk/internal/vanilla/BlockIDManager.java +++ b/src/main/java/com/falsepattern/chunk/internal/vanilla/BlockIDManager.java @@ -8,6 +8,7 @@ package com.falsepattern.chunk.internal.vanilla; import com.falsepattern.chunk.api.ChunkDataManager; +import com.falsepattern.chunk.api.ArrayUtil; import lombok.val; import net.minecraft.nbt.NBTTagCompound; @@ -103,4 +104,10 @@ public class BlockIDManager extends VanillaManager ebs.setBlockMSBArray(new NibbleArray(section.getByteArray("Add"), 4)); } } + + @Override + public void cloneSection(Chunk fromChunk, ExtendedBlockStorage from, ExtendedBlockStorage to) { + to.setBlockLSBArray(ArrayUtil.copyArray(from.getBlockLSBArray(), to.getBlockLSBArray())); + to.setBlockMSBArray(ArrayUtil.copyArray(from.getBlockMSBArray(), to.getBlockMSBArray())); + } } diff --git a/src/main/java/com/falsepattern/chunk/internal/vanilla/BlocklightManager.java b/src/main/java/com/falsepattern/chunk/internal/vanilla/BlocklightManager.java index 95e5c2e..5a53d95 100644 --- a/src/main/java/com/falsepattern/chunk/internal/vanilla/BlocklightManager.java +++ b/src/main/java/com/falsepattern/chunk/internal/vanilla/BlocklightManager.java @@ -8,6 +8,7 @@ package com.falsepattern.chunk.internal.vanilla; import com.falsepattern.chunk.api.ChunkDataManager; +import com.falsepattern.chunk.api.ArrayUtil; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.world.chunk.Chunk; @@ -39,4 +40,9 @@ public class BlocklightManager extends NibbleManager implements ChunkDataManager public void readSectionFromNBT(Chunk chunk, ExtendedBlockStorage ebs, NBTTagCompound section) { ebs.setBlocklightArray(new NibbleArray(section.getByteArray("BlockLight"), 4)); } + + @Override + public void cloneSection(Chunk fromChunk, ExtendedBlockStorage from, ExtendedBlockStorage to) { + from.setBlocklightArray(ArrayUtil.copyArray(from.getBlocklightArray(), to.getBlocklightArray())); + } } diff --git a/src/main/java/com/falsepattern/chunk/internal/vanilla/LightingManager.java b/src/main/java/com/falsepattern/chunk/internal/vanilla/LightingManager.java index d39cad2..f9a54ea 100644 --- a/src/main/java/com/falsepattern/chunk/internal/vanilla/LightingManager.java +++ b/src/main/java/com/falsepattern/chunk/internal/vanilla/LightingManager.java @@ -8,6 +8,7 @@ package com.falsepattern.chunk.internal.vanilla; import com.falsepattern.chunk.api.ChunkDataManager; +import com.falsepattern.chunk.api.ArrayUtil; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.world.chunk.Chunk; @@ -34,4 +35,10 @@ public class LightingManager extends VanillaManager implements ChunkDataManager. public boolean chunkPrivilegedAccess() { return true; } + + @Override + public void cloneChunk(Chunk from, Chunk to) { + to.heightMap = ArrayUtil.copyArray(from.heightMap, to.heightMap); + to.isLightPopulated = from.isLightPopulated; + } } diff --git a/src/main/java/com/falsepattern/chunk/internal/vanilla/MetadataManager.java b/src/main/java/com/falsepattern/chunk/internal/vanilla/MetadataManager.java index 4b42cee..9723aa5 100644 --- a/src/main/java/com/falsepattern/chunk/internal/vanilla/MetadataManager.java +++ b/src/main/java/com/falsepattern/chunk/internal/vanilla/MetadataManager.java @@ -8,6 +8,7 @@ package com.falsepattern.chunk.internal.vanilla; import com.falsepattern.chunk.api.ChunkDataManager; +import com.falsepattern.chunk.api.ArrayUtil; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.world.chunk.Chunk; @@ -39,4 +40,9 @@ public class MetadataManager extends NibbleManager implements ChunkDataManager.S public void readSectionFromNBT(Chunk chunk, ExtendedBlockStorage ebs, NBTTagCompound section) { ebs.setBlockMetadataArray(new NibbleArray(section.getByteArray("Data"), 4)); } + + @Override + public void cloneSection(Chunk fromChunk, ExtendedBlockStorage from, ExtendedBlockStorage to) { + to.setBlockMetadataArray(ArrayUtil.copyArray(from.getMetadataArray(), to.getMetadataArray())); + } } diff --git a/src/main/java/com/falsepattern/chunk/internal/vanilla/SkylightManager.java b/src/main/java/com/falsepattern/chunk/internal/vanilla/SkylightManager.java index fb2874a..2e14572 100644 --- a/src/main/java/com/falsepattern/chunk/internal/vanilla/SkylightManager.java +++ b/src/main/java/com/falsepattern/chunk/internal/vanilla/SkylightManager.java @@ -8,6 +8,7 @@ package com.falsepattern.chunk.internal.vanilla; import com.falsepattern.chunk.api.ChunkDataManager; +import com.falsepattern.chunk.api.ArrayUtil; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.world.chunk.Chunk; @@ -61,4 +62,13 @@ public class SkylightManager extends NibbleManager implements ChunkDataManager.S ebs.setSkylightArray(new NibbleArray(section.getByteArray("SkyLight"), 4)); } } + + @Override + public void cloneSection(Chunk fromChunk, ExtendedBlockStorage from, ExtendedBlockStorage to) { + if (!fromChunk.worldObj.provider.hasNoSky) { + to.setSkylightArray(ArrayUtil.copyArray(from.getSkylightArray(), to.getSkylightArray())); + } else { + to.setSkylightArray(null); + } + } } diff --git a/src/main/resources/META-INF/chunkapi_at.cfg b/src/main/resources/META-INF/chunkapi_at.cfg new file mode 100644 index 0000000..e6a2269 --- /dev/null +++ b/src/main/resources/META-INF/chunkapi_at.cfg @@ -0,0 +1,3 @@ +public-f net.minecraft.world.chunk.NibbleArray field_76585_a # data +public-f net.minecraft.world.chunk.NibbleArray field_76583_b # depthBits +public-f net.minecraft.world.chunk.NibbleArray field_76584_c # depthBitsPlusFour