From e55772b5f33eba362632fe7f3b391d74ef4564bf Mon Sep 17 00:00:00 2001 From: CursedFlames <18627001+CursedFlames@users.noreply.github.com> Date: Mon, 22 Jan 2024 17:28:51 +1300 Subject: [PATCH 1/2] Implement ProtoCube and ImposterProtoCube --- .../level/cube/MixinImposterProtoCube.java | 21 ++ .../world/level/cube/MixinProtoCube.java | 22 ++ .../world/level/cube/CubeAccess.java | 7 + .../world/level/cube/ImposterProtoCube.java | 23 ++- .../world/level/cube/ProtoCube.java | 189 +++++++++++++++++- .../resources/cubicchunks.mixins.core.json | 2 + src/main/resources/dasm/sets/sets.dasm | 10 +- ...asses.java => TestVanillaCubicParity.java} | 67 ++++++- .../test/world/level/cube/TestLevelCube.java | 3 +- .../test/world/level/cube/TestProtoCube.java | 90 +++++++++ 10 files changed, 416 insertions(+), 18 deletions(-) create mode 100644 src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/world/level/cube/MixinImposterProtoCube.java create mode 100644 src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/world/level/cube/MixinProtoCube.java rename src/test/java/io/github/opencubicchunks/cubicchunks/test/misc/{TestInterfacesMatchVanillaClasses.java => TestVanillaCubicParity.java} (64%) create mode 100644 src/test/java/io/github/opencubicchunks/cubicchunks/test/world/level/cube/TestProtoCube.java diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/world/level/cube/MixinImposterProtoCube.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/world/level/cube/MixinImposterProtoCube.java new file mode 100644 index 00000000..2d85876d --- /dev/null +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/world/level/cube/MixinImposterProtoCube.java @@ -0,0 +1,21 @@ +package io.github.opencubicchunks.cubicchunks.mixin.core.common.world.level.cube; + +import io.github.opencubicchunks.cubicchunks.world.level.chunklike.ImposterProtoClo; +import io.github.opencubicchunks.cubicchunks.world.level.chunklike.LevelClo; +import io.github.opencubicchunks.cubicchunks.world.level.cube.ImposterProtoCube; +import io.github.opencubicchunks.cubicchunks.world.level.cube.LevelCube; +import org.spongepowered.asm.mixin.Dynamic; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +// Needed for DASM to apply +@Mixin(ImposterProtoCube.class) +public abstract class MixinImposterProtoCube extends MixinProtoCube implements ImposterProtoClo { + // Field generated by DASM + @Dynamic @Shadow @Final private LevelCube wrapped; + + @Override public LevelClo cc_getWrappedClo() { + return this.wrapped; + } +} diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/world/level/cube/MixinProtoCube.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/world/level/cube/MixinProtoCube.java new file mode 100644 index 00000000..52afa05e --- /dev/null +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/world/level/cube/MixinProtoCube.java @@ -0,0 +1,22 @@ +package io.github.opencubicchunks.cubicchunks.mixin.core.common.world.level.cube; + +import io.github.opencubicchunks.cc_core.utils.Coords; +import io.github.opencubicchunks.cubicchunks.world.level.cube.ProtoCube; +import net.minecraft.core.BlockPos; +import org.spongepowered.asm.mixin.Dynamic; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +// Needed for DASM to apply +@Mixin(ProtoCube.class) +public abstract class MixinProtoCube extends MixinCubeAccess { + /** + * Redirect to use cube section indexing instead of chunk section indexing + */ + @Dynamic @Redirect(method = {"markPosForPostprocessing", "cc_dasm$getBlockState", "cc_dasm$getFluidState"}, at = @At(value = "INVOKE", target = "Lio/github/opencubicchunks/cubicchunks" + + "/world/level/cube/ProtoCube;getSectionIndex(I)I")) + private int cc_onGetBlockState_SectionIndex(ProtoCube instance, int i, BlockPos pos) { + return Coords.blockToIndex(pos); + } +} diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/cube/CubeAccess.java b/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/cube/CubeAccess.java index b3d2808c..bb7c5194 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/cube/CubeAccess.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/cube/CubeAccess.java @@ -38,6 +38,7 @@ import net.minecraft.world.level.chunk.ChunkStatus; import net.minecraft.world.level.chunk.LevelChunkSection; import net.minecraft.world.level.chunk.UpgradeData; +import net.minecraft.world.level.gameevent.GameEventListenerRegistry; import net.minecraft.world.level.levelgen.BelowZeroRetrogen; import net.minecraft.world.level.levelgen.Heightmap; import net.minecraft.world.level.levelgen.NoiseChunk; @@ -112,6 +113,9 @@ private static void replaceMissingSections(Registry biomeRegistry, LevelC } } + @TransformFrom(copyFrom = @CopyFrom(clazz = ChunkAccess.class), value = "getListenerRegistry(I)Lnet/minecraft/world/level/gameevent/GameEventListenerRegistry;") + @Override public native GameEventListenerRegistry getListenerRegistry(int sectionY); + @Override @Nullable public abstract BlockState setBlockState(BlockPos pos, BlockState state, boolean isMoving); @Override public abstract void setBlockEntity(BlockEntity blockEntity); @@ -287,6 +291,9 @@ private static void replaceMissingSections(Registry biomeRegistry, LevelC @TransformFrom(copyFrom = @CopyFrom(clazz = ChunkAccess.class), value = "setInhabitedTime(J)V") @Override public native void setInhabitedTime(long inhabitedTime); + @TransformFrom(copyFrom = @CopyFrom(clazz = ChunkAccess.class), value = "getOrCreateOffsetList([Lit/unimi/dsi/fastutil/shorts/ShortList;I)Lit/unimi/dsi/fastutil/shorts/ShortList;") + public static native ShortList getOrCreateOffsetList(ShortList[] packedPositions, int index); + @TransformFrom(copyFrom = @CopyFrom(clazz = ChunkAccess.class), value = "isLightCorrect()Z") @Override public native boolean isLightCorrect(); diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/cube/ImposterProtoCube.java b/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/cube/ImposterProtoCube.java index cc8988d9..e721e976 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/cube/ImposterProtoCube.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/cube/ImposterProtoCube.java @@ -1,7 +1,26 @@ package io.github.opencubicchunks.cubicchunks.world.level.cube; +import io.github.opencubicchunks.cubicchunks.mixin.CopyFrom; +import io.github.opencubicchunks.cubicchunks.mixin.DasmRedirect; +import io.github.opencubicchunks.cubicchunks.mixin.TransformFromClass; import io.github.opencubicchunks.cubicchunks.world.level.chunklike.ImposterProtoClo; +import io.github.opencubicchunks.cubicchunks.world.level.chunklike.LevelClo; +import net.minecraft.world.level.chunk.ImposterProtoChunk; -// not yet implemented - stub class -public abstract class ImposterProtoCube extends ProtoCube implements ImposterProtoClo { +// Whole class redirect +@DasmRedirect({ "cubeAccessAndDescendants" }) +@TransformFromClass(@CopyFrom(clazz = ImposterProtoChunk.class)) +public class ImposterProtoCube extends ProtoCube implements ImposterProtoClo { + // Field cleared and re-generated by DASM; we just need it here because otherwise mixin does not detect that the field exists. + private LevelCube wrapped; + + public ImposterProtoCube(LevelCube wrapped, boolean allowWrites) { + super(null, null, null, null, null); + throw new IllegalStateException("DASM failed to apply"); + } + + // Method is implemented in MixinImposterProtoCube instead, since DASM clears everything in this class. + @Override public LevelClo cc_getWrappedClo() { + throw new IllegalStateException("DASM failed to apply"); + } } diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/cube/ProtoCube.java b/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/cube/ProtoCube.java index fa5c8382..dc5aa4e0 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/cube/ProtoCube.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/cube/ProtoCube.java @@ -1,10 +1,191 @@ package io.github.opencubicchunks.cubicchunks.world.level.cube; +import java.util.List; +import java.util.Map; + +import javax.annotation.Nullable; + +import com.google.common.collect.Lists; +import io.github.opencubicchunks.cc_core.utils.Coords; +import io.github.opencubicchunks.cubicchunks.mixin.CopyFrom; +import io.github.opencubicchunks.cubicchunks.mixin.DasmRedirect; +import io.github.opencubicchunks.cubicchunks.mixin.TransformFrom; +import io.github.opencubicchunks.cubicchunks.world.level.chunklike.CloPos; import io.github.opencubicchunks.cubicchunks.world.level.chunklike.ProtoClo; +import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Holder; +import net.minecraft.core.Registry; +import net.minecraft.core.SectionPos; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.LevelHeightAccessor; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.chunk.CarvingMask; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.ChunkStatus; +import net.minecraft.world.level.chunk.LevelChunkSection; +import net.minecraft.world.level.chunk.ProtoChunk; +import net.minecraft.world.level.chunk.UpgradeData; +import net.minecraft.world.level.levelgen.BelowZeroRetrogen; +import net.minecraft.world.level.levelgen.GenerationStep; +import net.minecraft.world.level.levelgen.blending.BlendingData; +import net.minecraft.world.level.lighting.LevelLightEngine; +import net.minecraft.world.level.material.Fluid; +import net.minecraft.world.level.material.FluidState; +import net.minecraft.world.ticks.LevelChunkTicks; +import net.minecraft.world.ticks.ProtoChunkTicks; +import net.minecraft.world.ticks.TickContainerAccess; + +@DasmRedirect({ "cubeAccessAndDescendants" }) +public class ProtoCube extends CubeAccess implements ProtoClo { + // Fields matching ProtoChunk + @Nullable + private volatile LevelLightEngine lightEngine; + private volatile ChunkStatus status; + private final List entities; + private final Map carvingMasks; + @Nullable + private BelowZeroRetrogen belowZeroRetrogen; + private final ProtoChunkTicks blockTicks; + private final ProtoChunkTicks fluidTicks; + + // Constructors mirroring vanilla signatures + public ProtoCube(CloPos cloPos, UpgradeData upgradeData, LevelHeightAccessor levelHeightAccessor, Registry biomeRegistry, @Nullable BlendingData blendingData) { + this(cloPos, upgradeData, null, new ProtoChunkTicks(), new ProtoChunkTicks(), levelHeightAccessor, biomeRegistry, blendingData); + } + + public ProtoCube(CloPos cloPos, UpgradeData upgradeData, @Nullable LevelChunkSection[] sections, ProtoChunkTicks blockTicks, ProtoChunkTicks liquidTicks, + LevelHeightAccessor levelHeightAccessor, Registry biomeRegistry, @Nullable BlendingData blendingData) { + super(cloPos, upgradeData, levelHeightAccessor, biomeRegistry, 0L, sections, blendingData); + this.status = ChunkStatus.EMPTY; + this.entities = Lists.newArrayList(); + this.carvingMasks = new Object2ObjectArrayMap(); + this.blockTicks = blockTicks; + this.fluidTicks = liquidTicks; + } + + @TransformFrom(copyFrom = @CopyFrom(clazz = ProtoChunk.class), value = "getBlockTicks()Lnet/minecraft/world/ticks/TickContainerAccess;") + @Override public native TickContainerAccess getBlockTicks(); + + @TransformFrom(copyFrom = @CopyFrom(clazz = ProtoChunk.class), value = "getFluidTicks()Lnet/minecraft/world/ticks/TickContainerAccess;") + @Override public native TickContainerAccess getFluidTicks(); + + @TransformFrom(copyFrom = @CopyFrom(clazz = ProtoChunk.class), value = "getTicksForSerialization()Lnet/minecraft/world/level/chunk/ChunkAccess$TicksToSave;") + @Override public native ChunkAccess.TicksToSave getTicksForSerialization(); + + // dasm + mixin + @TransformFrom(copyFrom = @CopyFrom(clazz = ProtoChunk.class), value = "getBlockState(Lnet/minecraft/core/BlockPos;)Lnet/minecraft/world/level/block/state/BlockState;") + @Override public native BlockState getBlockState(BlockPos pos); + + // dasm + mixin + @TransformFrom(copyFrom = @CopyFrom(clazz = ProtoChunk.class), value = "getFluidState(Lnet/minecraft/core/BlockPos;)Lnet/minecraft/world/level/material/FluidState;") + @Override public native FluidState getFluidState(BlockPos pos); + + @Nullable + @Override public BlockState setBlockState(BlockPos pos, BlockState state, boolean isMoving) { + int x = pos.getX(); + int y = pos.getY(); + int z = pos.getZ(); + LevelChunkSection section = this.getSection(Coords.blockToIndex(pos)); + boolean emptySection = section.hasOnlyAir(); + if (emptySection && state.is(Blocks.AIR)) { + return state; + } else { + int sectionLocalX = SectionPos.sectionRelative(x); + int sectionLocalY = SectionPos.sectionRelative(y); + int sectionLocalZ = SectionPos.sectionRelative(z); + BlockState blockstate = section.setBlockState(sectionLocalX, sectionLocalY, sectionLocalZ, state); + // TODO (P2) lighting and heightmaps - see vanilla method - might be dasm-able once we do? + + return blockstate; + } + } + + @TransformFrom(copyFrom = @CopyFrom(clazz = ProtoChunk.class), value = "setBlockEntity(Lnet/minecraft/world/level/block/entity/BlockEntity;)V") + @Override public native void setBlockEntity(BlockEntity pBlockEntity); + + @TransformFrom(copyFrom = @CopyFrom(clazz = ProtoChunk.class), value = "getBlockEntity(Lnet/minecraft/core/BlockPos;)Lnet/minecraft/world/level/block/entity/BlockEntity;") + @Override @Nullable public native BlockEntity getBlockEntity(BlockPos pPos); + + @TransformFrom(copyFrom = @CopyFrom(clazz = ProtoChunk.class), value = "getBlockEntities()Ljava/util/Map;") + @Override public native Map getBlockEntities(); + + @TransformFrom(copyFrom = @CopyFrom(clazz = ProtoChunk.class), value = "addEntity(Lnet/minecraft/nbt/CompoundTag;)V") + @Override public native void addEntity(CompoundTag pTag); + + @TransformFrom(copyFrom = @CopyFrom(clazz = ProtoChunk.class), value = "addEntity(Lnet/minecraft/world/entity/Entity;)V") + @Override public native void addEntity(Entity pEntity); + + // setStartForStructure: ProtoChunk logic handles below-zero retrogen then calls super, so we don't need to override + + @TransformFrom(copyFrom = @CopyFrom(clazz = ProtoChunk.class), value = "getEntities()Ljava/util/List;") + @Override public native List getEntities(); + + @TransformFrom(copyFrom = @CopyFrom(clazz = ProtoChunk.class), value = "getStatus()Lnet/minecraft/world/level/chunk/ChunkStatus;") + @Override public native ChunkStatus getStatus(); + + @TransformFrom(copyFrom = @CopyFrom(clazz = ProtoChunk.class), value = "setStatus(Lnet/minecraft/world/level/chunk/ChunkStatus;)V") + @Override public native void setStatus(ChunkStatus pStatus); + + @TransformFrom(copyFrom = @CopyFrom(clazz = ProtoChunk.class), value = "getNoiseBiome(III)Lnet/minecraft/core/Holder;") + @Override public native Holder getNoiseBiome(int pX, int pY, int pZ); + + @TransformFrom(copyFrom = @CopyFrom(clazz = ProtoChunk.class), value = "packOffsetCoordinates(Lnet/minecraft/core/BlockPos;)S") + public native static short packOffsetCoordinates(BlockPos pPos); + + @TransformFrom(copyFrom = @CopyFrom(clazz = ProtoChunk.class), value = "unpackOffsetCoordinates(SILnet/minecraft/world/level/ChunkPos;)Lnet/minecraft/core/BlockPos;") + public native static BlockPos unpackOffsetCoordinates(short pPackedPos, int pYOffset, ChunkPos pChunkPos); + + // dasm + mixin + @TransformFrom(copyFrom = @CopyFrom(clazz = ProtoChunk.class), value = "markPosForPostprocessing(Lnet/minecraft/core/BlockPos;)V") + @Override public native void markPosForPostprocessing(BlockPos pPos); + + @TransformFrom(copyFrom = @CopyFrom(clazz = ProtoChunk.class), value = "addPackedPostProcess(SI)V") + @Override public native void addPackedPostProcess(short pPackedPosition, int pIndex); + + @TransformFrom(copyFrom = @CopyFrom(clazz = ProtoChunk.class), value = "getBlockEntityNbts()Ljava/util/Map;") + @Override public native Map getBlockEntityNbts(); + + @TransformFrom(copyFrom = @CopyFrom(clazz = ProtoChunk.class), value = "getBlockEntityNbtForSaving(Lnet/minecraft/core/BlockPos;)Lnet/minecraft/nbt/CompoundTag;") + @Override @Nullable public native CompoundTag getBlockEntityNbtForSaving(BlockPos pPos); + + @TransformFrom(copyFrom = @CopyFrom(clazz = ProtoChunk.class), value = "removeBlockEntity(Lnet/minecraft/core/BlockPos;)V") + @Override public native void removeBlockEntity(BlockPos pPos); + + @TransformFrom(copyFrom = @CopyFrom(clazz = ProtoChunk.class), value = "getCarvingMask(Lnet/minecraft/world/level/levelgen/GenerationStep$Carving;)Lnet/minecraft/world/level/chunk/CarvingMask;") + @Override @Nullable public native CarvingMask getCarvingMask(GenerationStep.Carving pStep); + + @TransformFrom(copyFrom = @CopyFrom(clazz = ProtoChunk.class), value = "getOrCreateCarvingMask(Lnet/minecraft/world/level/levelgen/GenerationStep$Carving;)Lnet/minecraft/world/level/chunk/CarvingMask;") + @Override public native CarvingMask getOrCreateCarvingMask(GenerationStep.Carving pStep); + + @TransformFrom(copyFrom = @CopyFrom(clazz = ProtoChunk.class), value = "setCarvingMask(Lnet/minecraft/world/level/levelgen/GenerationStep$Carving;Lnet/minecraft/world/level/chunk/CarvingMask;)V") + @Override public native void setCarvingMask(GenerationStep.Carving pStep, CarvingMask pCarvingMask); + + @TransformFrom(copyFrom = @CopyFrom(clazz = ProtoChunk.class), value = "setLightEngine(Lnet/minecraft/world/level/lighting/LevelLightEngine;)V") + @Override public native void setLightEngine(LevelLightEngine pLightEngine); + + @Override public void setBelowZeroRetrogen(@Nullable BelowZeroRetrogen pBelowZeroRetrogen) { + // Unused + } + + @TransformFrom(copyFrom = @CopyFrom(clazz = ProtoChunk.class), value = "unpackTicks(Lnet/minecraft/world/ticks/ProtoChunkTicks;)Lnet/minecraft/world/ticks/LevelChunkTicks;") + private static LevelChunkTicks unpackTicks(ProtoChunkTicks pTicks) { + return new LevelChunkTicks(pTicks.scheduledTicks()); + } + + @TransformFrom(copyFrom = @CopyFrom(clazz = ProtoChunk.class), value = "unpackBlockTicks()Lnet/minecraft/world/ticks/LevelChunkTicks;") + @Override public native LevelChunkTicks unpackBlockTicks(); + + @TransformFrom(copyFrom = @CopyFrom(clazz = ProtoChunk.class), value = "unpackFluidTicks()Lnet/minecraft/world/ticks/LevelChunkTicks;") + @Override public native LevelChunkTicks unpackFluidTicks(); -// not yet implemented - stub class -public abstract class ProtoCube extends CubeAccess implements ProtoClo { - public ProtoCube() { - super(null, null, null, null, 0L, null, null); + @Override public LevelHeightAccessor getHeightAccessorForGeneration() { + return this; // Vanilla has logic for below-zero retrogen here } } diff --git a/src/main/resources/cubicchunks.mixins.core.json b/src/main/resources/cubicchunks.mixins.core.json index b9626519..e68790c2 100644 --- a/src/main/resources/cubicchunks.mixins.core.json +++ b/src/main/resources/cubicchunks.mixins.core.json @@ -29,9 +29,11 @@ "common.world.level.chunk.MixinImposterProtoChunk", "common.world.level.chunk.MixinLevelChunk", "common.world.level.cube.MixinCubeAccess", + "common.world.level.cube.MixinImposterProtoCube", "common.world.level.cube.MixinLevelCube", "common.world.level.cube.MixinLevelCube$BoundTickingBlockEntity", "common.world.level.cube.MixinLevelCube$RebindableTickingBlockEntityWrapper", + "common.world.level.cube.MixinProtoCube", "common.world.level.MixinLevel" ], "client": [], diff --git a/src/main/resources/dasm/sets/sets.dasm b/src/main/resources/dasm/sets/sets.dasm index bbb3bd25..6c94eb27 100644 --- a/src/main/resources/dasm/sets/sets.dasm +++ b/src/main/resources/dasm/sets/sets.dasm @@ -8,13 +8,17 @@ "net.minecraft.world.level.chunk.LevelChunk$PostLoadProcessor", "net.minecraft.world.level.chunk.LevelChunk$RebindableTickingBlockEntityWrapper", "net.minecraft.world.level.chunk.ChunkAccess", + "net.minecraft.world.level.chunk.ProtoChunk", + "net.minecraft.world.level.chunk.ImposterProtoChunk", "io.github.opencubicchunks.cubicchunks.world.level.chunklike.CloAccess", "io.github.opencubicchunks.cubicchunks.world.level.chunklike.LevelClo", "io.github.opencubicchunks.cubicchunks.world.level.cube.CubeAccess", "io.github.opencubicchunks.cubicchunks.world.level.cube.LevelCube", "io.github.opencubicchunks.cubicchunks.world.level.cube.LevelCube$BoundTickingBlockEntity", "io.github.opencubicchunks.cubicchunks.world.level.cube.LevelCube$PostLoadProcessor", - "io.github.opencubicchunks.cubicchunks.world.level.cube.LevelCube$RebindableTickingBlockEntityWrapper" + "io.github.opencubicchunks.cubicchunks.world.level.cube.LevelCube$RebindableTickingBlockEntityWrapper", + "io.github.opencubicchunks.cubicchunks.world.level.cube.ProtoCube", + "io.github.opencubicchunks.cubicchunks.world.level.cube.ImposterProtoCube" ], "sets": { "general": { @@ -36,7 +40,9 @@ "LevelChunk": "LevelCube", "LevelChunk$BoundTickingBlockEntity": "LevelCube$BoundTickingBlockEntity", "LevelChunk$PostLoadProcessor": "LevelCube$PostLoadProcessor", - "LevelChunk$RebindableTickingBlockEntityWrapper": "LevelCube$RebindableTickingBlockEntityWrapper" + "LevelChunk$RebindableTickingBlockEntityWrapper": "LevelCube$RebindableTickingBlockEntityWrapper", + "ProtoChunk": "ProtoCube", + "ImposterProtoChunk": "ImposterProtoCube" }, "fieldRedirects": { "ChunkAccess | ChunkPos chunkPos": "cloPos" diff --git a/src/test/java/io/github/opencubicchunks/cubicchunks/test/misc/TestInterfacesMatchVanillaClasses.java b/src/test/java/io/github/opencubicchunks/cubicchunks/test/misc/TestVanillaCubicParity.java similarity index 64% rename from src/test/java/io/github/opencubicchunks/cubicchunks/test/misc/TestInterfacesMatchVanillaClasses.java rename to src/test/java/io/github/opencubicchunks/cubicchunks/test/misc/TestVanillaCubicParity.java index 874596ae..d2e9a9a7 100644 --- a/src/test/java/io/github/opencubicchunks/cubicchunks/test/misc/TestInterfacesMatchVanillaClasses.java +++ b/src/test/java/io/github/opencubicchunks/cubicchunks/test/misc/TestVanillaCubicParity.java @@ -13,6 +13,10 @@ import io.github.opencubicchunks.cubicchunks.world.level.chunklike.ImposterProtoClo; import io.github.opencubicchunks.cubicchunks.world.level.chunklike.LevelClo; import io.github.opencubicchunks.cubicchunks.world.level.chunklike.ProtoClo; +import io.github.opencubicchunks.cubicchunks.world.level.cube.CubeAccess; +import io.github.opencubicchunks.cubicchunks.world.level.cube.ImposterProtoCube; +import io.github.opencubicchunks.cubicchunks.world.level.cube.LevelCube; +import io.github.opencubicchunks.cubicchunks.world.level.cube.ProtoCube; import net.minecraft.nbt.CompoundTag; import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.chunk.ChunkAccess; @@ -25,12 +29,13 @@ import org.junit.jupiter.api.TestInstance; @TestInstance(TestInstance.Lifecycle.PER_CLASS) -public class TestInterfacesMatchVanillaClasses { +public class TestVanillaCubicParity { @BeforeAll public static void setup() { setupTests(); } + private static String stringifyMethod(Method method) { return method.getName() + "(" + Arrays.stream(method.getParameterTypes()).map(Class::getName).collect(Collectors.joining(", ")) + ") -> " + method.getReturnType().getName(); } @@ -39,22 +44,50 @@ private static boolean isStatic(Method method) { return Modifier.isStatic(method.getModifiers()); } + private void testStaticParity(Class vanillaClass, Class cubicClass, Method... excludes) { + // TODO we currently only compare method names; need to be able to apply DASM translation since static methods may have differing signatures + var excludesSet = Arrays.stream(excludes) + .map(Method::getName) + .collect(Collectors.toSet()); + var vanillaMethods = Arrays.stream(vanillaClass.getMethods()) + .filter(method -> method.getDeclaringClass() == vanillaClass && isStatic(method)) + .map(Method::getName) + .filter(s -> !excludesSet.contains(s)) + .collect(Collectors.toSet()); + Arrays.stream(cubicClass.getMethods()) + .filter(method -> method.getDeclaringClass() == cubicClass && isStatic(method)) + .map(Method::getName) + .forEach(vanillaMethods::remove); // We don't care about static methods that exist on the CC class but not the vanilla class. + assertTrue(vanillaMethods.isEmpty(), () -> String.format(""" + Expected parity in static methods between %s %s and %s %s. + Extra methods in %s: + %s + + """, + vanillaClass.isInterface() ? "interface" : "class", + vanillaClass.getName(), + cubicClass.isInterface() ? "interface" : "class", + cubicClass.getName(), + vanillaClass.getSimpleName(), + vanillaMethods.isEmpty() ? "[none]" : String.join("\n ", vanillaMethods))); + } + private void testParityIncludingAncestors(Class vanillaClass, Class cubicClass, Method... excludes) { var excludesSet = Arrays.stream(excludes) - .map(TestInterfacesMatchVanillaClasses::stringifyMethod) + .map(TestVanillaCubicParity::stringifyMethod) .collect(Collectors.toSet()); var vanillaMethods = Arrays.stream(vanillaClass.getMethods()) .filter(method -> method.getDeclaringClass() != Object.class && !isStatic(method)) - .map(TestInterfacesMatchVanillaClasses::stringifyMethod) + .map(TestVanillaCubicParity::stringifyMethod) .filter(s -> !excludesSet.contains(s)) .collect(Collectors.toSet()); var cubicMethods = Arrays.stream(cubicClass.getMethods()) .filter(method -> method.getDeclaringClass() != Object.class && !isStatic(method)) - .map(TestInterfacesMatchVanillaClasses::stringifyMethod) + .map(TestVanillaCubicParity::stringifyMethod) .filter(methodString -> !vanillaMethods.remove(methodString)) // Filter methodStrings that are NOT in vanillaMethods .toList(); assertTrue(vanillaMethods.isEmpty() && cubicMethods.isEmpty(), () -> String.format(""" - Expected parity between %s %s and %s %s. + Expected parity in non-static methods between %s %s and %s %s. Extra methods in %s: %s Extra methods in %s: @@ -71,16 +104,20 @@ private void testParityIncludingAncestors(Class vanillaClass, Class cubicC cubicMethods.isEmpty() ? "[none]" : String.join("\n ", cubicMethods))); } - @Test public void testChunkAccessCloAccessParity() throws NoSuchMethodException { + @Test public void testChunkAccessParity() throws NoSuchMethodException { testParityIncludingAncestors( ChunkAccess.class, CloAccess.class, ChunkAccess.class.getMethod("getPos"), ChunkAccess.class.getMethod("getWorldForge") // TODO need to check existence; this would fail on Fabric ); + testStaticParity( + ChunkAccess.class, + CubeAccess.class + ); } - @Test public void testLevelChunkLevelCloParity() throws NoSuchMethodException { + @Test public void testLevelChunkParity() throws NoSuchMethodException { testParityIncludingAncestors( LevelChunk.class, LevelClo.class, @@ -98,18 +135,26 @@ private void testParityIncludingAncestors(Class vanillaClass, Class cubicC LevelChunk.class.getMethod("setData", Supplier.class, Object.class), LevelChunk.class.getMethod("getAuxLightManager", ChunkPos.class) ); + testStaticParity( + LevelChunk.class, + LevelCube.class + ); } - @Test public void testProtoChunkProtoCloParity() throws NoSuchMethodException { + @Test public void testProtoChunkParity() throws NoSuchMethodException { testParityIncludingAncestors( ProtoChunk.class, ProtoClo.class, ChunkAccess.class.getMethod("getPos"), ChunkAccess.class.getMethod("getWorldForge") // TODO need to check existence; this would fail on Fabric ); + testStaticParity( + ProtoChunk.class, + ProtoCube.class + ); } - @Test public void testImposterProtoChunkImposterProtoCloParity() throws NoSuchMethodException { + @Test public void testImposterProtoChunkParity() throws NoSuchMethodException { testParityIncludingAncestors( ImposterProtoChunk.class, ImposterProtoClo.class, @@ -117,5 +162,9 @@ private void testParityIncludingAncestors(Class vanillaClass, Class cubicC ImposterProtoChunk.class.getMethod("getWrapped"), ChunkAccess.class.getMethod("getWorldForge") // TODO need to check existence; this would fail on Fabric ); + testStaticParity( + ImposterProtoChunk.class, + ImposterProtoCube.class + ); } } diff --git a/src/test/java/io/github/opencubicchunks/cubicchunks/test/world/level/cube/TestLevelCube.java b/src/test/java/io/github/opencubicchunks/cubicchunks/test/world/level/cube/TestLevelCube.java index 5094587f..fb2b8f4b 100644 --- a/src/test/java/io/github/opencubicchunks/cubicchunks/test/world/level/cube/TestLevelCube.java +++ b/src/test/java/io/github/opencubicchunks/cubicchunks/test/world/level/cube/TestLevelCube.java @@ -36,6 +36,8 @@ public static void setup() { setupTests(); } + // TODO integration test for constructing from a ProtoCube + // TODO replaceWithPacketData - probably needs to be an integration test // TODO (P2 or P3) postProcessGeneration - currently a method stub @@ -80,7 +82,6 @@ private void methodCallsAndBlockEntities(Random random) { var pos = cubePos.cubePos() .asBlockPos(random.nextInt(CubicConstants.DIAMETER_IN_BLOCKS), random.nextInt(CubicConstants.DIAMETER_IN_BLOCKS), random.nextInt(CubicConstants.DIAMETER_IN_BLOCKS)); var cube = new LevelCube(mock(Answers.RETURNS_DEEP_STUBS), cubePos); - cube.setLoaded(true); // required for removing block entities to work BlockState state1 = spy(Blocks.FURNACE.defaultBlockState()); BlockState state2 = spy(Blocks.STONE.defaultBlockState()); diff --git a/src/test/java/io/github/opencubicchunks/cubicchunks/test/world/level/cube/TestProtoCube.java b/src/test/java/io/github/opencubicchunks/cubicchunks/test/world/level/cube/TestProtoCube.java new file mode 100644 index 00000000..1da9a444 --- /dev/null +++ b/src/test/java/io/github/opencubicchunks/cubicchunks/test/world/level/cube/TestProtoCube.java @@ -0,0 +1,90 @@ +package io.github.opencubicchunks.cubicchunks.test.world.level.cube; + +import static io.github.opencubicchunks.cubicchunks.testutils.Setup.setupTests; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Random; +import java.util.Set; + +import io.github.opencubicchunks.cc_core.api.CubicConstants; +import io.github.opencubicchunks.cubicchunks.world.level.chunklike.CloPos; +import io.github.opencubicchunks.cubicchunks.world.level.cube.ProtoCube; +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.LevelHeightAccessor; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.properties.BlockStateProperties; +import net.minecraft.world.level.material.Fluids; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.mockito.Answers; + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +public class TestProtoCube { + @BeforeAll + public static void setup() { + setupTests(); + } + + private ProtoCube makeProtoCube(CloPos cubePos) { + LevelHeightAccessor heightAccessor = mock(Answers.RETURNS_DEEP_STUBS); + when(heightAccessor.getMinBuildHeight()).thenReturn(-(1 << 24)); + when(heightAccessor.getMaxBuildHeight()).thenReturn(1 << 24); + when(heightAccessor.getHeight()).thenReturn(1 << 25); + when(heightAccessor.isOutsideBuildHeight(any())).thenReturn(false); + return new ProtoCube(cubePos, mock(Answers.RETURNS_DEEP_STUBS), heightAccessor, mock(Answers.RETURNS_DEEP_STUBS), mock(Answers.RETURNS_DEEP_STUBS)); + } + + // TODO markPosForPostprocessing - need to figure out what it actually does in order to test it + + private void simpleGetSetBlockState(Random random) { + CloPos cubePos = CloPos.cube(random.nextInt(20000)-10000, random.nextInt(20000)-10000, random.nextInt(20000)-10000); + var cube = makeProtoCube(cubePos); + Map states = new HashMap<>(); + for (int i = 0; i < 1000; i++) { + var pos = cubePos.cubePos() + .asBlockPos(random.nextInt(CubicConstants.DIAMETER_IN_BLOCKS), random.nextInt(CubicConstants.DIAMETER_IN_BLOCKS), random.nextInt(CubicConstants.DIAMETER_IN_BLOCKS)); + var state = random.nextBoolean() ? Blocks.STONE.defaultBlockState() : Blocks.DIRT.defaultBlockState(); + states.put(pos, state); + cube.setBlockState(pos, state, false); + } + + for (var pos : states.keySet()) { + assertEquals(states.get(pos), cube.getBlockState(pos)); + } + } + + // Mojang's fluid stuff is so jank and half-implemented + private void fluidState(Random random) { + CloPos cubePos = CloPos.cube(random.nextInt(20000)-10000, random.nextInt(20000)-10000, random.nextInt(20000)-10000); + var cube = makeProtoCube(cubePos); + Set positions = new HashSet<>(); + var state = Blocks.ANDESITE_SLAB.defaultBlockState().setValue(BlockStateProperties.WATERLOGGED, true); + for (int i = 0; i < 100; i++) { + var pos = cubePos.cubePos() + .asBlockPos(random.nextInt(CubicConstants.DIAMETER_IN_BLOCKS), random.nextInt(CubicConstants.DIAMETER_IN_BLOCKS), random.nextInt(CubicConstants.DIAMETER_IN_BLOCKS)); + positions.add(pos); + cube.setBlockState(pos, state, false); + } + + for (var pos : positions) { + assertEquals(state, cube.getBlockState(pos)); + assertEquals(Fluids.WATER.getSource(false), cube.getFluidState(pos)); + } + } + + @Test public void testGetSetBlockStateAndFluidState() { + var random = new Random(-102); + for (int i = 0; i < 100; i++) { + simpleGetSetBlockState(random); + fluidState(random); + } + } +} \ No newline at end of file From 60dcbd26663c6fe3739001ad8e9f163d9dae7993 Mon Sep 17 00:00:00 2001 From: CursedFlames <18627001+CursedFlames@users.noreply.github.com> Date: Wed, 24 Jan 2024 13:45:26 +1300 Subject: [PATCH 2/2] address review comments --- .../cubicchunks/world/level/cube/ProtoCube.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/cube/ProtoCube.java b/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/cube/ProtoCube.java index dc5aa4e0..a7a68bdd 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/cube/ProtoCube.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/world/level/cube/ProtoCube.java @@ -171,13 +171,11 @@ public ProtoCube(CloPos cloPos, UpgradeData upgradeData, @Nullable LevelChunkSec @Override public native void setLightEngine(LevelLightEngine pLightEngine); @Override public void setBelowZeroRetrogen(@Nullable BelowZeroRetrogen pBelowZeroRetrogen) { - // Unused + // Below-zero retrogen is unused in CC, hence empty method body } @TransformFrom(copyFrom = @CopyFrom(clazz = ProtoChunk.class), value = "unpackTicks(Lnet/minecraft/world/ticks/ProtoChunkTicks;)Lnet/minecraft/world/ticks/LevelChunkTicks;") - private static LevelChunkTicks unpackTicks(ProtoChunkTicks pTicks) { - return new LevelChunkTicks(pTicks.scheduledTicks()); - } + private static native LevelChunkTicks unpackTicks(ProtoChunkTicks pTicks); @TransformFrom(copyFrom = @CopyFrom(clazz = ProtoChunk.class), value = "unpackBlockTicks()Lnet/minecraft/world/ticks/LevelChunkTicks;") @Override public native LevelChunkTicks unpackBlockTicks();