From 6a485b9d0f03bc333badbd3bb93413fb0d72e2ee Mon Sep 17 00:00:00 2001 From: CursedFlames <18627001+CursedFlames@users.noreply.github.com> Date: Tue, 20 Feb 2024 23:39:35 +1300 Subject: [PATCH] ChunkHolder and partial implementation for ChunkMap --- .../resources/cubicchunks.mixins.forge.json | 4 +- .../cubicchunks/mixin/GlobalSet.java | 28 ++ .../common/server/level/MixinChunkHolder.java | 215 ++++++++++ .../server/level/MixinChunkHolder_Forge.java | 12 + .../common/server/level/MixinChunkMap.java | 378 ++++++++++++++++++ .../MixinChunkTaskPriorityQueueSorter.java | 2 + .../world/level/chunk/MixinChunkStatus.java | 47 +++ .../chunk/storage/MixinChunkStorage.java | 39 ++ .../server/level/CubicChunkHolder.java | 32 ++ .../server/level/CubicTicketType.java | 1 + .../resources/cubicchunks.mixins.core.json | 17 +- .../level/IntegrationTestCubicChunkMap.java | 302 ++++++++++++++ .../server/level/ChunkHolderTestAccess.java | 17 + .../server/level/ChunkMapTestAccess.java | 30 ++ .../level/ServerChunkCacheTestAccess.java | 11 + .../server/level/TestCubicChunkHolder.java | 15 + .../test/server/level/TestCubicChunkMap.java | 15 + .../level/TestCubicServerChunkCache.java | 13 + .../resources/cubicchunks.mixins.test.json | 5 +- 19 files changed, 1176 insertions(+), 7 deletions(-) create mode 100644 src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinChunkHolder.java create mode 100644 src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinChunkHolder_Forge.java create mode 100644 src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinChunkMap.java create mode 100644 src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/world/level/chunk/MixinChunkStatus.java create mode 100644 src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/world/level/chunk/storage/MixinChunkStorage.java create mode 100644 src/main/java/io/github/opencubicchunks/cubicchunks/server/level/CubicChunkHolder.java create mode 100644 src/test/java/io/github/opencubicchunks/cubicchunks/integrationtest/server/level/IntegrationTestCubicChunkMap.java create mode 100644 src/test/java/io/github/opencubicchunks/cubicchunks/mixin/test/common/server/level/ChunkHolderTestAccess.java create mode 100644 src/test/java/io/github/opencubicchunks/cubicchunks/mixin/test/common/server/level/ChunkMapTestAccess.java create mode 100644 src/test/java/io/github/opencubicchunks/cubicchunks/mixin/test/common/server/level/ServerChunkCacheTestAccess.java create mode 100644 src/test/java/io/github/opencubicchunks/cubicchunks/test/server/level/TestCubicChunkHolder.java create mode 100644 src/test/java/io/github/opencubicchunks/cubicchunks/test/server/level/TestCubicChunkMap.java create mode 100644 src/test/java/io/github/opencubicchunks/cubicchunks/test/server/level/TestCubicServerChunkCache.java diff --git a/src/forge/resources/cubicchunks.mixins.forge.json b/src/forge/resources/cubicchunks.mixins.forge.json index 4be79bcd..89a19328 100644 --- a/src/forge/resources/cubicchunks.mixins.forge.json +++ b/src/forge/resources/cubicchunks.mixins.forge.json @@ -2,6 +2,7 @@ "required": true, "package": "io.github.opencubicchunks.cubicchunks.mixin.forge", "refmap": "CubicChunks-refmap.json", + "plugin": "io.github.opencubicchunks.cubicchunks.mixin.ASMConfigPlugin", "compatibilityLevel": "JAVA_17", "minVersion": "0.8", "injectors": { @@ -10,8 +11,7 @@ "overwrites": { "conformVisibility": true }, - "mixins": [ - ], + "mixins": [], "client": [], "server": [] } \ No newline at end of file diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/GlobalSet.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/GlobalSet.java index 4b4e32f4..b70d7a2b 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/GlobalSet.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/GlobalSet.java @@ -1,22 +1,36 @@ package io.github.opencubicchunks.cubicchunks.mixin; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import java.util.function.Function; + +import com.mojang.datafixers.util.Either; import io.github.notstirred.dasm.api.annotations.redirect.redirects.ConstructorToFactoryRedirect; import io.github.notstirred.dasm.api.annotations.redirect.redirects.FieldRedirect; import io.github.notstirred.dasm.api.annotations.redirect.redirects.FieldToMethodRedirect; +import io.github.notstirred.dasm.api.annotations.redirect.redirects.MethodRedirect; import io.github.notstirred.dasm.api.annotations.redirect.redirects.TypeRedirect; import io.github.notstirred.dasm.api.annotations.redirect.sets.RedirectContainer; import io.github.notstirred.dasm.api.annotations.redirect.sets.RedirectSet; import io.github.notstirred.dasm.api.annotations.selector.ConstructorMethodSig; import io.github.notstirred.dasm.api.annotations.selector.FieldSig; +import io.github.notstirred.dasm.api.annotations.selector.MethodSig; import io.github.notstirred.dasm.api.annotations.selector.Ref; import io.github.opencubicchunks.cubicchunks.server.level.CubicChunkHolder; import io.github.opencubicchunks.cubicchunks.server.level.CubicTicketType; import io.github.opencubicchunks.cubicchunks.server.level.progress.CubicChunkProgressListener; +import io.github.opencubicchunks.cubicchunks.world.level.chunklike.CloAccess; import io.github.opencubicchunks.cubicchunks.world.level.chunklike.CloPos; import net.minecraft.server.level.ChunkHolder; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ThreadedLevelLightEngine; import net.minecraft.server.level.TicketType; import net.minecraft.server.level.progress.ChunkProgressListener; import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.chunk.ChunkGenerator; +import net.minecraft.world.level.chunk.ChunkStatus; +import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager; /** * Generally should not be used directly for DASM transforms; prefer using {@link GeneralSet} instead. @@ -59,6 +73,20 @@ abstract class ChunkTicketType_to_CloTicketType_redirects { public static TicketType UNKNOWN; } + @RedirectContainer(owner = @Ref(ChunkStatus.class)) + abstract class ChunkStatus_redirects { + @MethodRedirect(@MethodSig("generate(Ljava/util/concurrent/Executor;Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/world/level/chunk/ChunkGenerator;Lnet/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplateManager;Lnet/minecraft/server/level/ThreadedLevelLightEngine;Ljava/util/function/Function;Ljava/util/List;)Ljava/util/concurrent/CompletableFuture;")) + public abstract CompletableFuture> cc_generate( + Executor pExectutor, + ServerLevel pLevel, + ChunkGenerator pChunkGenerator, + StructureTemplateManager pStructureTemplateManager, + ThreadedLevelLightEngine pLightEngine, + Function>> pTask, + List pCache + ); + } + @TypeRedirect(from = @Ref(ChunkProgressListener.class), to = @Ref(CubicChunkProgressListener.class)) interface ChunkProgressListener_to_CubicChunkProgressListener_redirects { } } diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinChunkHolder.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinChunkHolder.java new file mode 100644 index 00000000..b5affc41 --- /dev/null +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinChunkHolder.java @@ -0,0 +1,215 @@ +package io.github.opencubicchunks.cubicchunks.mixin.core.common.server.level; + +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; + +import javax.annotation.Nullable; + +import com.llamalad7.mixinextras.sugar.Local; +import com.mojang.datafixers.util.Either; +import io.github.notstirred.dasm.api.annotations.Dasm; +import io.github.notstirred.dasm.api.annotations.redirect.redirects.AddFieldToSets; +import io.github.notstirred.dasm.api.annotations.redirect.redirects.AddMethodToSets; +import io.github.notstirred.dasm.api.annotations.redirect.redirects.AddTransformToSets; +import io.github.notstirred.dasm.api.annotations.selector.FieldSig; +import io.github.notstirred.dasm.api.annotations.selector.MethodSig; +import io.github.notstirred.dasm.api.annotations.selector.Ref; +import io.github.notstirred.dasm.api.annotations.transform.TransformFromMethod; +import io.github.opencubicchunks.cc_core.utils.Coords; +import io.github.opencubicchunks.cubicchunks.mixin.GeneralSet; +import io.github.opencubicchunks.cubicchunks.server.level.CubicChunkHolder; +import io.github.opencubicchunks.cubicchunks.world.level.chunklike.CloAccess; +import io.github.opencubicchunks.cubicchunks.world.level.chunklike.CloPos; +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.LevelCube; +import it.unimi.dsi.fastutil.shorts.ShortSet; +import net.minecraft.core.BlockPos; +import net.minecraft.core.SectionPos; +import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.game.ClientboundBlockUpdatePacket; +import net.minecraft.network.protocol.game.ClientboundSectionBlocksUpdatePacket; +import net.minecraft.server.level.ChunkHolder; +import net.minecraft.server.level.ChunkMap; +import net.minecraft.server.level.FullChunkStatus; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.LevelHeightAccessor; +import net.minecraft.world.level.LightLayer; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.ChunkStatus; +import net.minecraft.world.level.chunk.LevelChunkSection; +import org.spongepowered.asm.mixin.Dynamic; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.Redirect; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Dasm(GeneralSet.class) +@Mixin(ChunkHolder.class) +public abstract class MixinChunkHolder implements CubicChunkHolder { + private boolean cc_isCubic; + + @AddFieldToSets(sets = GeneralSet.class, owner = @Ref(ChunkHolder.class), field = @FieldSig(name = "pos", type = @Ref(ChunkPos.class))) + private CloPos cc_cloPos; + + @AddFieldToSets(sets = GeneralSet.class, owner = @Ref(ChunkHolder.class), field = @FieldSig(name = "onLevelChange", type = @Ref(ChunkHolder.LevelChangeListener.class))) + private final CubicChunkHolder.LevelChangeListener cc_onLevelChange; + @AddFieldToSets(sets = GeneralSet.class, owner = @Ref(ChunkHolder.class), field = @FieldSig(name = "playerProvider", type = @Ref(ChunkHolder.PlayerProvider.class))) + private final CubicChunkHolder.PlayerProvider cc_playerProvider; + + @AddMethodToSets(sets = GeneralSet.class, owner = @Ref(ChunkHolder.class), method = @MethodSig("getPos()Lnet/minecraft/world/level/ChunkPos;")) + @Override public CloPos cc_getPos() { + return cc_cloPos; + } + + @Shadow private boolean hasChangedSections; + @Shadow @Final private final ShortSet[] changedBlocksPerSection; + + @Shadow protected abstract void broadcastBlockEntityIfNeeded(List pPlayers, Level pLevel, BlockPos pPos, BlockState pState); + + @Shadow protected abstract void broadcast(List pPlayers, Packet pPacket); + + @AddTransformToSets(GeneralSet.class) @TransformFromMethod( + value = @MethodSig("(Lnet/minecraft/world/level/ChunkPos;ILnet/minecraft/world/level/LevelHeightAccessor;Lnet/minecraft/world/level/lighting/LevelLightEngine;" + + "Lnet/minecraft/server/level/ChunkHolder$LevelChangeListener;Lnet/minecraft/server/level/ChunkHolder$PlayerProvider;)V")) + public MixinChunkHolder() { + throw new IllegalStateException("dasm failed to apply"); + } + + @Dynamic @Inject(at = @At("RETURN"), method = "cc_dasm$__init__(Lio/github/opencubicchunks/cubicchunks/world/level/chunklike/CloPos;ILnet/minecraft/world/level/LevelHeightAccessor;" + + "Lnet/minecraft/world/level/lighting/LevelLightEngine;Lio/github/opencubicchunks/cubicchunks/server/level/CubicChunkHolder$LevelChangeListener;" + + "Lio/github/opencubicchunks/cubicchunks/server/level/CubicChunkHolder$PlayerProvider;)V") + private void onCcInit(CallbackInfo ci) { + // TODO redirect changedBlocksPerSection construction for chunks + cc_isCubic = true; + } + + @AddTransformToSets(GeneralSet.class) @TransformFromMethod( + value = @MethodSig("getTickingChunk()Lnet/minecraft/world/level/chunk/LevelChunk;")) + @Nullable public native LevelClo cc_getTickingChunk(); + + @AddTransformToSets(GeneralSet.class) @TransformFromMethod( + value = @MethodSig("getChunkToSend()Lnet/minecraft/world/level/chunk/LevelChunk;")) + @Nullable public native LevelClo cc_getChunkToSend(); + + @AddTransformToSets(GeneralSet.class) @TransformFromMethod( + value = @MethodSig("getFullChunk()Lnet/minecraft/world/level/chunk/LevelChunk;")) + @Nullable public native LevelClo cc_getFullChunk(); + + @AddTransformToSets(GeneralSet.class) @TransformFromMethod( + value = @MethodSig("getLastAvailable()Lnet/minecraft/world/level/chunk/ChunkAccess;")) + @Nullable public native CloAccess cc_getLastAvailable(); + + // dasm + mixin + @AddTransformToSets(GeneralSet.class) @TransformFromMethod( + value = @MethodSig("blockChanged(Lnet/minecraft/core/BlockPos;)V")) + public native void cc_blockChanged(BlockPos pos); + + /** + * Only handle block changes for cubes, as this should not be tracked on columns + */ + @Dynamic @Inject(method = "cc_dasm$cc_blockChanged", + at = @At(value = "INVOKE_ASSIGN", shift = At.Shift.AFTER, + target = "Lnet/minecraft/server/level/ChunkHolder;cc_getTickingChunk()Lio/github/opencubicchunks/cubicchunks/world/level/chunklike/LevelClo;" + ), + cancellable = true) + private void cc_blockChanged_checkCubic(BlockPos pos, CallbackInfo ci, @Local LevelClo clo) { + if (clo instanceof ChunkAccess) ci.cancel(); + } + + /** + * Redirect to use cube section indexing instead of chunk section indexing + */ + @Dynamic @Redirect(method = "cc_dasm$cc_blockChanged", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/LevelHeightAccessor;getSectionIndex(I)I")) + private int cc_blockChanged_sectionIndex(LevelHeightAccessor instance, int y, BlockPos pos) { + return Coords.blockToIndex(pos); + } + + // We want a different signature (see below); can't automatically redirect this one + @Inject(method = "sectionLightChanged", at = @At("HEAD")) + private void onSectionLightChanged(LightLayer pType, int pSectionY, CallbackInfo ci) { + // We should be calling the cubic signature instead + assert !cc_isCubic; + } + + @AddMethodToSets(sets = GeneralSet.class, owner = @Ref(ChunkHolder.class), method = @MethodSig("sectionLightChanged(Lnet/minecraft/world/level/LightLayer;I)V")) + public void cc_sectionLightChanged(LightLayer pType, SectionPos pos) { + // TODO (P2) lighting + } + + @AddMethodToSets(sets = GeneralSet.class, owner = @Ref(ChunkHolder.class), method = @MethodSig("broadcastChanges(Lnet/minecraft/world/level/chunk/LevelChunk;)V")) + public void cc_broadcastChanges(LevelClo clo) { + // TODO (P2) also handle lighting - see vanilla method + // TODO seems like this should only run for cubes; is that correct? + if (this.hasChangedSections && clo instanceof LevelCube cube) { + Level level = cube.getLevel(); + + List list1 = this.cc_playerProvider.getPlayers(this.cc_cloPos, false); + + for(int j = 0; j < this.changedBlocksPerSection.length; ++j) { + ShortSet shortset = this.changedBlocksPerSection[j]; + if (shortset != null) { + this.changedBlocksPerSection[j] = null; + if (!list1.isEmpty()) { + SectionPos sectionpos = Coords.sectionPosByIndex(cube.cc_getCloPos().cubePos(), j); + if (shortset.size() == 1) { + BlockPos blockpos = sectionpos.relativeToBlockPos(shortset.iterator().nextShort()); + BlockState blockstate = level.getBlockState(blockpos); + this.broadcast(list1, new ClientboundBlockUpdatePacket(blockpos, blockstate)); + this.broadcastBlockEntityIfNeeded(list1, level, blockpos, blockstate); + } else { + LevelChunkSection levelchunksection = cube.getSection(j); + ClientboundSectionBlocksUpdatePacket clientboundsectionblocksupdatepacket = new ClientboundSectionBlocksUpdatePacket( + sectionpos, shortset, levelchunksection + ); + this.broadcast(list1, clientboundsectionblocksupdatepacket); + clientboundsectionblocksupdatepacket.runUpdates( + (p_288761_, p_288762_) -> this.broadcastBlockEntityIfNeeded(list1, level, p_288761_, p_288762_) + ); + } + } + } + } + + this.hasChangedSections = false; + } + } + + @AddTransformToSets(GeneralSet.class) @TransformFromMethod( + value = @MethodSig("getOrScheduleFuture(Lnet/minecraft/world/level/chunk/ChunkStatus;Lnet/minecraft/server/level/ChunkMap;)Ljava/util/concurrent/CompletableFuture;")) + @Override public native CompletableFuture> cc_getOrScheduleFuture(ChunkStatus status, ChunkMap map); + + @AddTransformToSets(GeneralSet.class) @TransformFromMethod( + value = @MethodSig("addSaveDependency(Ljava/lang/String;Ljava/util/concurrent/CompletableFuture;)V")) + protected native void cc_addSaveDependency(String source, CompletableFuture future); + + @AddTransformToSets(GeneralSet.class) @TransformFromMethod( + value = @MethodSig("updateChunkToSave(Ljava/util/concurrent/CompletableFuture;Ljava/lang/String;)V")) + private native void cc_updateChunkToSave(CompletableFuture> future, String source); + + @AddTransformToSets(GeneralSet.class) @TransformFromMethod( + value = @MethodSig("scheduleFullChunkPromotion(Lnet/minecraft/server/level/ChunkMap;Ljava/util/concurrent/CompletableFuture;Ljava/util/concurrent/Executor;Lnet/minecraft/server/level/FullChunkStatus;)V")) + private native void cc_scheduleFullChunkPromotion( + ChunkMap chunkMap, CompletableFuture> future, Executor executor, FullChunkStatus fullChunkStatus + ); + + @AddTransformToSets(GeneralSet.class) @TransformFromMethod( + value = @MethodSig("demoteFullChunk(Lnet/minecraft/server/level/ChunkMap;Lnet/minecraft/server/level/FullChunkStatus;)V")) + private native void cc_demoteFullChunk(ChunkMap chunkMap, FullChunkStatus fullChunkStatus); + + @AddTransformToSets(GeneralSet.class) @TransformFromMethod( + value = @MethodSig("updateFutures(Lnet/minecraft/server/level/ChunkMap;Ljava/util/concurrent/Executor;)V")) + protected native void cc_updateFutures(ChunkMap chunkMap, Executor executor); + + @AddTransformToSets(GeneralSet.class) @TransformFromMethod( + value = @MethodSig("replaceProtoChunk(Lnet/minecraft/world/level/chunk/ImposterProtoChunk;)V")) + public native void cc_replaceProtoChunk(ImposterProtoClo imposter); + +} diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinChunkHolder_Forge.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinChunkHolder_Forge.java new file mode 100644 index 00000000..1aae655d --- /dev/null +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinChunkHolder_Forge.java @@ -0,0 +1,12 @@ +package io.github.opencubicchunks.cubicchunks.mixin.core.common.server.level; + +import io.github.opencubicchunks.cubicchunks.world.level.chunklike.LevelClo; +import net.minecraft.server.level.ChunkHolder; +import org.spongepowered.asm.mixin.Mixin; + +// FIXME should be in forge sourceset once tests run against forge +@Mixin(ChunkHolder.class) +public class MixinChunkHolder_Forge { + // Field added by Forge + LevelClo currentlyLoading; +} diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinChunkMap.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinChunkMap.java new file mode 100644 index 00000000..a2964efc --- /dev/null +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinChunkMap.java @@ -0,0 +1,378 @@ +package io.github.opencubicchunks.cubicchunks.mixin.core.common.server.level; + +import static java.util.Collections.swap; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import java.util.function.IntFunction; +import java.util.function.Supplier; + +import javax.annotation.Nullable; + +import com.google.common.collect.Lists; +import com.llamalad7.mixinextras.injector.v2.WrapWithCondition; +import com.llamalad7.mixinextras.sugar.Local; +import com.mojang.datafixers.DataFixer; +import com.mojang.datafixers.util.Either; +import io.github.notstirred.dasm.api.annotations.Dasm; +import io.github.notstirred.dasm.api.annotations.redirect.redirects.AddFieldToSets; +import io.github.notstirred.dasm.api.annotations.redirect.redirects.AddMethodToSets; +import io.github.notstirred.dasm.api.annotations.redirect.redirects.AddTransformToSets; +import io.github.notstirred.dasm.api.annotations.selector.FieldSig; +import io.github.notstirred.dasm.api.annotations.selector.MethodSig; +import io.github.notstirred.dasm.api.annotations.selector.Ref; +import io.github.notstirred.dasm.api.annotations.transform.TransformFromMethod; +import io.github.opencubicchunks.cc_core.api.CubicConstants; +import io.github.opencubicchunks.cc_core.utils.Coords; +import io.github.opencubicchunks.cubicchunks.mixin.GeneralSet; +import io.github.opencubicchunks.cubicchunks.mixin.core.common.world.level.chunk.storage.MixinChunkStorage; +import io.github.opencubicchunks.cubicchunks.server.level.CubicChunkHolder; +import io.github.opencubicchunks.cubicchunks.server.level.progress.CubicChunkProgressListener; +import io.github.opencubicchunks.cubicchunks.world.level.chunklike.CloAccess; +import io.github.opencubicchunks.cubicchunks.world.level.chunklike.CloPos; +import io.github.opencubicchunks.cubicchunks.world.level.chunklike.LevelClo; +import net.minecraft.Util; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.server.level.ChunkHolder; +import net.minecraft.server.level.ChunkMap; +import net.minecraft.server.level.FullChunkStatus; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.level.progress.ChunkProgressListener; +import net.minecraft.util.thread.BlockableEventLoop; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.chunk.ChunkGenerator; +import net.minecraft.world.level.chunk.ChunkStatus; +import net.minecraft.world.level.chunk.LightChunkGetter; +import net.minecraft.world.level.entity.ChunkStatusUpdateListener; +import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager; +import net.minecraft.world.level.storage.LevelStorageSource; +import org.slf4j.Logger; +import org.spongepowered.asm.mixin.Dynamic; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Dasm(GeneralSet.class) +@Mixin(ChunkMap.class) +public abstract class MixinChunkMap extends MixinChunkStorage { + // TODO maybe don't shadow logger; use our own? + @Shadow @Final private static Logger LOGGER; + + @AddFieldToSets(sets = GeneralSet.class, owner = @Ref(ChunkMap.class), field = @FieldSig(type = @Ref(ChunkProgressListener.class), name = "progressListener")) + private CubicChunkProgressListener cc_progressListener; + + @Inject(method = "", at = @At("RETURN")) + private void onInit(ServerLevel pLevel, LevelStorageSource.LevelStorageAccess pLevelStorageAccess, DataFixer pFixerUpper, StructureTemplateManager pStructureManager, + Executor pDispatcher, BlockableEventLoop pMainThreadExecutor, LightChunkGetter pLightChunk, ChunkGenerator pGenerator, ChunkProgressListener pProgressListener, + ChunkStatusUpdateListener pChunkStatusListener, Supplier pOverworldDataStorage, int pViewDistance, boolean pSync, CallbackInfo ci) { + cc_progressListener = ((CubicChunkProgressListener) pProgressListener); + } + + /** + * Returns the squared distance to the center of the cube. + */ + @AddMethodToSets(sets = GeneralSet.class, owner = @Ref(ChunkMap.class), method = @MethodSig("euclideanDistanceSquared(Lnet/minecraft/world/level/ChunkPos;" + + "Lnet/minecraft/world/entity/Entity;)D")) + private static double cc_euclideanDistanceSquared(CloPos cloPos, Entity entity) { + if (cloPos.isChunk()) { + throw new UnsupportedOperationException("Should not call euclideanDistanceSquared with a chunk position"); + } + double cubeCenterX = Coords.cubeToCenterBlock(cloPos.getX()); + double cubeCenterY = Coords.cubeToCenterBlock(cloPos.getX()); + double cubeCenterZ = Coords.cubeToCenterBlock(cloPos.getX()); + double dx = cubeCenterX - entity.getX(); + double dy = cubeCenterY - entity.getY(); + double dz = cubeCenterZ - entity.getZ(); + return dx * dx + dy * dy + dz * dz; + } + + // isChunkTracked - requires CubeTrackingView + + // isChunkOnTrackedBorder - requires CubeTrackingView + + // getChunkDebugData - low prio + + // dasm + mixin + @AddTransformToSets(GeneralSet.class) @TransformFromMethod(@MethodSig("getChunkRangeFuture(Lnet/minecraft/server/level/ChunkHolder;ILjava/util/function/IntFunction;)Ljava/util/concurrent/CompletableFuture;")) + private native CompletableFuture, ChunkHolder.ChunkLoadingFailure>> cc_getChunkRangeFuture(ChunkHolder cloHolder, int radius, + IntFunction statusByRadius); + + // TODO this could be substantially improved probably hopefully + /** + * Cubes require different adjacency logic compared to Chunks + */ + @Dynamic @Inject(method = "cc_dasm$cc_getChunkRangeFuture", at = @At("HEAD"), cancellable = true) + private void cc_onGetChunkRangeFuture(ChunkHolder cloHolder, int radius, IntFunction statusByRadius, + CallbackInfoReturnable, ChunkHolder.ChunkLoadingFailure>>> cir) { + CloPos pos = ((CubicChunkHolder) cloHolder).cc_getPos(); + if (!pos.isCube()) return; + if (radius == 0) { + ChunkStatus chunkstatus1 = statusByRadius.apply(0); + var cm = ((ChunkMap) (Object) this); + CompletableFuture> future = ((CubicChunkHolder) cloHolder).cc_getOrScheduleFuture(chunkstatus1, cm); + cir.setReturnValue(future.thenApply(p_214893_ -> p_214893_.mapLeft(List::of))); + return; + } + List>> dependencyFutures = new ArrayList<>(); + List cloHolders = new ArrayList<>(); + int middleCubeIndex = -1; + for (int dz = -radius; dz <= radius; dz++) { + for (int dx = -radius; dx <= radius; dx++) { + // We want the chunks intersecting this column of cubes to be loaded at the maximum level of any of those cubes; + // this occurs when dy=0, so we only consider x/z distance + int chunkDistance = Math.max(Math.abs(dz), Math.abs(dx)); + for (int sectionZ = 0; sectionZ < CubicConstants.DIAMETER_IN_SECTIONS; sectionZ++) { + for (int sectionX = 0; sectionX < CubicConstants.DIAMETER_IN_SECTIONS; sectionX++) { + ChunkHolder holder = this.getUpdatingChunkIfPresent(CloPos.asLong(Coords.cubeToSection(pos.getX()+dx, sectionX), Coords.cubeToSection(pos.getZ()+dz, sectionZ))); + // TODO do we really want Mojang's janky error handling? can we just crash instead? + if (holder == null) { + var pos1 = new ChunkPos(Coords.cubeToSection(pos.getX()+dx, sectionX), Coords.cubeToSection(pos.getZ()+dz, sectionZ)); + cir.setReturnValue(CompletableFuture.completedFuture(Either.right(new ChunkHolder.ChunkLoadingFailure() { + @Override + public String toString() { + return "Unloaded " + pos1; + } + }))); + return; + } + ChunkStatus expectedStatus = statusByRadius.apply(chunkDistance); + var future = ((CubicChunkHolder) holder).cc_getOrScheduleFuture(expectedStatus, (ChunkMap) (Object) this); + cloHolders.add(holder); + dependencyFutures.add(future); + } + } + for (int dy = -radius; dy <= radius; dy++) { + if (dx == 0 && dy == 0 && dz == 0) { + middleCubeIndex = cloHolders.size(); + } + ChunkHolder holder = this.getUpdatingChunkIfPresent(CloPos.asLong(pos.getX()+dx, pos.getY()+dy, pos.getZ()+dz)); + // TODO do we really want Mojang's janky error handling? can we just crash instead? + if (holder == null) { + var pos1 = CloPos.cube(pos.getX()+dx, pos.getY()+dy, pos.getZ()+dz); + cir.setReturnValue(CompletableFuture.completedFuture(Either.right(new ChunkHolder.ChunkLoadingFailure() { + @Override + public String toString() { + return "Unloaded " + pos1; + } + }))); + return; + } + ChunkStatus expectedStatus = statusByRadius.apply(Math.max(chunkDistance, Math.abs(dy))); + var future = ((CubicChunkHolder) holder).cc_getOrScheduleFuture(expectedStatus, (ChunkMap) (Object) this); + cloHolders.add(holder); + dependencyFutures.add(future); + } + } + } + + // Vanilla expects that the center chunk is in the middle of the list; this is not the case for cubes, so we manually swap the center cube to the middle. + swap(cloHolders, middleCubeIndex, cloHolders.size() / 2); + swap(dependencyFutures, middleCubeIndex, cloHolders.size() / 2); + + var sequencedFuture = Util.sequence(dependencyFutures); + CompletableFuture, ChunkHolder.ChunkLoadingFailure>> combinedFuture = sequencedFuture.thenApply(p_183730_ -> { + List list2 = Lists.newArrayList(); + int k1 = 0; + + for(final Either either : p_183730_) { + if (either == null) { +// throw this.debugFuturesAndCreateReportedException(new IllegalStateException("At least one of the chunk futures were null"), "n/a"); + } + + Optional optional = either.left(); + if (optional.isEmpty()) { + return Either.right(new ChunkHolder.ChunkLoadingFailure() { + @Override + public String toString() { + // TODO + return "Unloaded ";// + new ChunkPos(i + k1 % (p_282030_ * 2 + 1), j + k1 / (p_282030_ * 2 + 1)) + " " + either.right().get(); + } + }); + } + + list2.add(optional.get()); + ++k1; + } + + return Either.left(list2); + } + ); + + for (ChunkHolder holder : cloHolders) { +// holder.addSaveDependency("getChunkRangeFuture " + pos + " " + radius, combinedFuture); + } + + cir.setReturnValue(combinedFuture); + } + + // dasm + mixin + @AddTransformToSets(GeneralSet.class) @TransformFromMethod(@MethodSig("updateChunkScheduling(JILnet/minecraft/server/level/ChunkHolder;I)Lnet/minecraft/server/level/ChunkHolder;")) + @Nullable native ChunkHolder cc_updateChunkScheduling(long pChunkPos, int pNewLevel, @Nullable ChunkHolder pHolder, int pOldLevel); + + // TODO move to forge sourceset + /** + * Only call Forge hook for chunks, not cubes + */ + @Dynamic @WrapWithCondition(method = "cc_dasm$cc_updateChunkScheduling", at = @At(value = "INVOKE", target = "Lnet/neoforged/neoforge/event/EventHooks;fireChunkTicketLevelUpdated" + + "(Lnet/minecraft/server/level/ServerLevel;JIILnet/minecraft/server/level/ChunkHolder;)V")) + private boolean cc_updateChunkScheduling_onForgeHook(ServerLevel level, long pos, int oldLevel, int newLevel, ChunkHolder holder) { + return CloPos.isChunk(pos); + } + + @AddTransformToSets(GeneralSet.class) @TransformFromMethod(@MethodSig("saveAllChunks(Z)V")) + protected native void cc_saveAllChunks(boolean flush); + + // P4: scheduleUnload lambda we'll want to mirror the forge API for cubes + // FIXME remove call to forge hook in copied method + @AddTransformToSets(GeneralSet.class) @TransformFromMethod(@MethodSig("scheduleUnload(JLnet/minecraft/server/level/ChunkHolder;)V")) + private native void cc_scheduleUnload(long pChunkPos, ChunkHolder pChunkHolder); + + @AddTransformToSets(GeneralSet.class) @TransformFromMethod(@MethodSig("schedule(Lnet/minecraft/server/level/ChunkHolder;Lnet/minecraft/world/level/chunk/ChunkStatus;)Ljava/util/concurrent/CompletableFuture;")) + public native CompletableFuture> cc_schedule(ChunkHolder pHolder, ChunkStatus pStatus); + + @AddTransformToSets(GeneralSet.class) @TransformFromMethod(@MethodSig("scheduleChunkLoad(Lnet/minecraft/world/level/ChunkPos;)Ljava/util/concurrent/CompletableFuture;")) + private native CompletableFuture> cc_scheduleChunkLoad(CloPos cloPos); + + @AddTransformToSets(GeneralSet.class) @TransformFromMethod(@MethodSig("handleChunkLoadFailure(Ljava/lang/Throwable;Lnet/minecraft/world/level/ChunkPos;)Lcom/mojang/datafixers/util/Either;")) + private native Either cc_handleChunkLoadFailure(Throwable exception, CloPos cloPos); + + @AddTransformToSets(GeneralSet.class) @TransformFromMethod( + value = @MethodSig("createEmptyChunk(Lnet/minecraft/world/level/ChunkPos;)Lnet/minecraft/world/level/chunk/ChunkAccess;")) + private native CloAccess cc_createEmptyChunk(CloPos cloPos); + + @AddTransformToSets(GeneralSet.class) @TransformFromMethod(@MethodSig("markPositionReplaceable(Lnet/minecraft/world/level/ChunkPos;)V")) + private native void cc_markPositionReplaceable(CloPos cloPos); + + @AddTransformToSets(GeneralSet.class) @TransformFromMethod(@MethodSig("markPosition(Lnet/minecraft/world/level/ChunkPos;Lnet/minecraft/world/level/chunk/ChunkStatus$ChunkType;)B")) + private native byte cc_markPosition(CloPos cloPos, ChunkStatus.ChunkType chunkType); + + // TODO does this work properly with dasm? + @AddTransformToSets(GeneralSet.class) @TransformFromMethod(@MethodSig("scheduleChunkGeneration(Lnet/minecraft/server/level/ChunkHolder;Lnet/minecraft/world/level/chunk/ChunkStatus;)Ljava/util/concurrent/CompletableFuture;")) + private native CompletableFuture> cc_scheduleChunkGeneration(ChunkHolder pChunkHolder, ChunkStatus pChunkStatus); + + // FIXME requires redirect from ticket type to cubic ticket type + @AddTransformToSets(GeneralSet.class) @TransformFromMethod(@MethodSig("releaseLightTicket(Lnet/minecraft/world/level/ChunkPos;)V")) + protected native void cc_releaseLightTicket(ChunkPos pChunkPos); + + // TODO getDependencyStatus - might need changes? + + // FIXME remove call to forge hook in copied method + // FIXME include currentlyLoading on ChunkHolder in forge sourceset + @AddTransformToSets(GeneralSet.class) @TransformFromMethod(@MethodSig("protoChunkToFullChunk(Lnet/minecraft/server/level/ChunkHolder;)Ljava/util/concurrent/CompletableFuture;")) + private native CompletableFuture> cc_protoChunkToFullChunk(ChunkHolder pHolder); + + @AddTransformToSets(GeneralSet.class) @TransformFromMethod(@MethodSig("prepareTickingChunk(Lnet/minecraft/server/level/ChunkHolder;)Ljava/util/concurrent/CompletableFuture;")) + public native CompletableFuture> cc_prepareTickingChunk(ChunkHolder pHolder); + + @AddTransformToSets(GeneralSet.class) @TransformFromMethod(@MethodSig("onChunkReadyToSend(Lnet/minecraft/world/level/chunk/LevelChunk;)V")) + private native void cc_onChunkReadyToSend(LevelClo cloPos); + + @AddTransformToSets(GeneralSet.class) @TransformFromMethod(@MethodSig("prepareAccessibleChunk(Lnet/minecraft/server/level/ChunkHolder;)Ljava/util/concurrent/CompletableFuture;")) + public native CompletableFuture> cc_prepareAccessibleChunk(ChunkHolder pHolder); + + @AddTransformToSets(GeneralSet.class) @TransformFromMethod(@MethodSig("saveChunkIfNeeded(Lnet/minecraft/server/level/ChunkHolder;)Z")) + private native boolean cc_saveChunkIfNeeded(ChunkHolder holder); + + // FIXME remove call to forge hook in copied method + // dasm + mixin + @AddTransformToSets(GeneralSet.class) @TransformFromMethod(@MethodSig("save(Lnet/minecraft/world/level/chunk/ChunkAccess;)Z")) + private native boolean cc_save(CloAccess cloAccess); + + /** + * Redirect error logging to log with CloPos + */ + @Dynamic @Inject(method = "cc_dasm$cc_save", at = @At(value = "INVOKE", target = "Lio/github/opencubicchunks/cubicchunks/world/level/chunklike/CloPos;getX()I"), cancellable = true) + private void cc_onSave_errorLog(CloAccess cloAccess, CallbackInfoReturnable cir, @Local Exception exception) { + LOGGER.error("Failed to save chunk or cube {}", cloAccess.cc_getCloPos().toString(), exception); + cir.setReturnValue(false); + } + + // This calls ChunkSerializer.getChunkTypeFromTag, which could be an issue? + @AddTransformToSets(GeneralSet.class) @TransformFromMethod(@MethodSig("isExistingChunkFull(Lnet/minecraft/world/level/ChunkPos;)Z")) + private native boolean cc_isExistingChunkFull(CloPos cloPos); + + @AddTransformToSets(GeneralSet.class) @TransformFromMethod(@MethodSig("markChunkPendingToSend(Lnet/minecraft/server/level/ServerPlayer;Lnet/minecraft/world/level/ChunkPos;)V")) + private native void cc_markChunkPendingToSend(ServerPlayer player, CloPos cloPos); + + @AddTransformToSets(GeneralSet.class) @TransformFromMethod(@MethodSig("markChunkPendingToSend(Lnet/minecraft/server/level/ServerPlayer;Lnet/minecraft/world/level/chunk/LevelChunk;)V")) + private static native void cc_markChunkPendingToSend(ServerPlayer player, LevelClo clo); + + @AddTransformToSets(GeneralSet.class) @TransformFromMethod(@MethodSig("dropChunk(Lnet/minecraft/server/level/ServerPlayer;Lnet/minecraft/world/level/ChunkPos;)V")) + private static native void cc_dropChunk(ServerPlayer player, CloPos cloPos); + + @AddTransformToSets(GeneralSet.class) @TransformFromMethod(@MethodSig("getChunkToSend(J)Lnet/minecraft/world/level/chunk/LevelChunk;")) + public native LevelClo cc_getChunkToSend(long cloPos); + + // dumpChunks (low prio) + + // printFuture - only ever called in dumpChunks + + // TODO (P2) readChunk: this.upgradeChunkTag might need a dasm redirect? + + @AddTransformToSets(GeneralSet.class) @TransformFromMethod(@MethodSig("readChunk(Lnet/minecraft/world/level/ChunkPos;)Ljava/util/concurrent/CompletableFuture;")) + private native CompletableFuture> cc_readChunk(CloPos cloPos); + + @AddTransformToSets(GeneralSet.class) @TransformFromMethod(@MethodSig("anyPlayerCloseEnoughForSpawning(Lnet/minecraft/world/level/ChunkPos;)Z")) + native boolean cc_anyPlayerCloseEnoughForSpawning(CloPos cloPos); + + @AddTransformToSets(GeneralSet.class) @TransformFromMethod(@MethodSig("getPlayersCloseForSpawning(Lnet/minecraft/world/level/ChunkPos;)Ljava/util/List;")) + public native List cc_getPlayersCloseForSpawning(CloPos cloPos); + + @AddTransformToSets(GeneralSet.class) @TransformFromMethod(@MethodSig("playerIsCloseEnoughForSpawning(Lnet/minecraft/server/level/ServerPlayer;Lnet/minecraft/world/level/ChunkPos;)Z")) + private native boolean cc_playerIsCloseEnoughForSpawning(ServerPlayer player, CloPos cloPos); + + // updatePlayerStatus - DASM due to calling DASM-duplicated methods? not sure + + // move - DASM due to calling DASM-duplicated methods? + + // updateChunkTracking - DASM; possibly conditional redirect original method to copy + + // applyChunkTrackingView - complex + + // getPlayers - complex + + // tick - DASM or mixin, there's a single `.chunk()` call in there on a sectionpos + + // TODO resendBiomesForChunks - only used for FillBiomeCommand + + @AddTransformToSets(GeneralSet.class) @TransformFromMethod(@MethodSig("onFullChunkStatusChange(Lnet/minecraft/world/level/ChunkPos;Lnet/minecraft/server/level/FullChunkStatus;)V")) + native void cc_onFullChunkStatusChange(CloPos cloPos, FullChunkStatus fullChunkStatus); + + @AddTransformToSets(GeneralSet.class) @TransformFromMethod(@MethodSig("waitForLightBeforeSending(Lnet/minecraft/world/level/ChunkPos;I)V")) + public native void cc_waitForLightBeforeSending(CloPos cloPos, int p_301130_); + + // TrackedEntity.updatePlayer - in its own mixin class bc inner class - complex + + @Shadow + protected abstract ChunkHolder getUpdatingChunkIfPresent(long aLong); + + + // TEMPORARY - needs dasm subclass method redirect inheritance for non-overriden methods + + @AddMethodToSets(sets = GeneralSet.class, owner = @Ref(ChunkMap.class), + method = @MethodSig("isOldChunkAround(Lnet/minecraft/world/level/ChunkPos;I)Z")) + public boolean cc_isOldChunkAround(CloPos pPos, int pRadius) { + return super.cc_isOldChunkAround(pPos, pRadius); + } + + @AddMethodToSets(sets = GeneralSet.class, owner = @Ref(ChunkMap.class), + method = @MethodSig("read(Lnet/minecraft/world/level/ChunkPos;)Ljava/util/concurrent/CompletableFuture;")) + public CompletableFuture> cc_read(CloPos cloPos) { + return super.cc_read(cloPos); + } + + @AddMethodToSets(sets = GeneralSet.class, owner = @Ref(ChunkMap.class), + method = @MethodSig("write(Lnet/minecraft/world/level/ChunkPos;Lnet/minecraft/nbt/CompoundTag;)V")) + public void cc_write(CloPos cloPos, CompoundTag chunkData) { + super.cc_write(cloPos, chunkData); + } +} diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinChunkTaskPriorityQueueSorter.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinChunkTaskPriorityQueueSorter.java index 860d0627..5a3c6193 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinChunkTaskPriorityQueueSorter.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/server/level/MixinChunkTaskPriorityQueueSorter.java @@ -19,6 +19,8 @@ import io.github.opencubicchunks.cubicchunks.world.level.chunklike.CloPos; import net.minecraft.server.level.ChunkHolder; import net.minecraft.server.level.ChunkTaskPriorityQueueSorter; +import net.minecraft.util.Unit; +import net.minecraft.util.thread.ProcessorHandle; import net.minecraft.world.level.ChunkPos; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/world/level/chunk/MixinChunkStatus.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/world/level/chunk/MixinChunkStatus.java new file mode 100644 index 00000000..4a145b9f --- /dev/null +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/world/level/chunk/MixinChunkStatus.java @@ -0,0 +1,47 @@ +package io.github.opencubicchunks.cubicchunks.mixin.core.common.world.level.chunk; + +import static io.github.opencubicchunks.cc_core.utils.Utils.unsafeCast; + +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import java.util.function.Function; + +import com.mojang.datafixers.util.Either; +import io.github.opencubicchunks.cubicchunks.world.level.chunklike.CloAccess; +import io.github.opencubicchunks.cubicchunks.world.level.chunklike.ProtoClo; +import net.minecraft.server.level.ChunkHolder; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ThreadedLevelLightEngine; +import net.minecraft.world.level.chunk.ChunkGenerator; +import net.minecraft.world.level.chunk.ChunkStatus; +import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager; +import org.spongepowered.asm.mixin.Mixin; + +@Mixin(ChunkStatus.class) +public class MixinChunkStatus { + // TODO (P2) proper generation logic; this currently ignores everything and only handles promotion from ProtoClo to LevelClo + public CompletableFuture> cc_generate( + Executor pExectutor, + ServerLevel pLevel, + ChunkGenerator pChunkGenerator, + StructureTemplateManager pStructureTemplateManager, + ThreadedLevelLightEngine pLightEngine, + Function>> pTask, + List pCache + ) { + CloAccess chunkaccess = pCache.get(pCache.size() / 2); + return ((Object) this == ChunkStatus.FULL ? pTask.apply(chunkaccess) : CompletableFuture.completedFuture(Either.left(chunkaccess))) + .thenApply( + p_281217_ -> { + p_281217_.ifLeft(p_290029_ -> { + if (p_290029_ instanceof ProtoClo protochunk && !protochunk.getStatus().isOrAfter((ChunkStatus) (Object) this)) { + protochunk.setStatus((ChunkStatus) (Object) this); + } + }); + + return unsafeCast(p_281217_); + } + ); + } +} diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/world/level/chunk/storage/MixinChunkStorage.java b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/world/level/chunk/storage/MixinChunkStorage.java new file mode 100644 index 00000000..faec29bb --- /dev/null +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/mixin/core/common/world/level/chunk/storage/MixinChunkStorage.java @@ -0,0 +1,39 @@ +package io.github.opencubicchunks.cubicchunks.mixin.core.common.world.level.chunk.storage; + +import java.util.Optional; +import java.util.concurrent.CompletableFuture; + +import io.github.notstirred.dasm.api.annotations.Dasm; +import io.github.notstirred.dasm.api.annotations.redirect.redirects.AddMethodToSets; +import io.github.notstirred.dasm.api.annotations.selector.MethodSig; +import io.github.notstirred.dasm.api.annotations.selector.Ref; +import io.github.opencubicchunks.cubicchunks.mixin.GeneralSet; +import io.github.opencubicchunks.cubicchunks.world.level.chunklike.CloPos; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.world.level.chunk.storage.ChunkStorage; +import org.spongepowered.asm.mixin.Mixin; + +@Dasm(GeneralSet.class) +@Mixin(ChunkStorage.class) +public abstract class MixinChunkStorage { + @AddMethodToSets(sets = GeneralSet.class, owner = @Ref(ChunkStorage.class), + method = @MethodSig("isOldChunkAround(Lnet/minecraft/world/level/ChunkPos;I)Z")) + public boolean cc_isOldChunkAround(CloPos pPos, int pRadius) { + return false; // TODO (P2) should be dasm'd once IOWorker is done + } + + @AddMethodToSets(sets = GeneralSet.class, owner = @Ref(ChunkStorage.class), + method = @MethodSig("read(Lnet/minecraft/world/level/ChunkPos;)Ljava/util/concurrent/CompletableFuture;")) + public CompletableFuture> cc_read(CloPos cloPos) { + // TODO (P2) loading - this method should be dasm'd + return CompletableFuture.completedFuture(Optional.empty()); + } + + @AddMethodToSets(sets = GeneralSet.class, owner = @Ref(ChunkStorage.class), + method = @MethodSig("write(Lnet/minecraft/world/level/ChunkPos;Lnet/minecraft/nbt/CompoundTag;)V")) + public void cc_write(CloPos cloPos, CompoundTag chunkData) { + // TODO (P2) loading/unloading + } + + // TODO (P2) chunkScanner() cubic version once IOWorker is done +} diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/server/level/CubicChunkHolder.java b/src/main/java/io/github/opencubicchunks/cubicchunks/server/level/CubicChunkHolder.java new file mode 100644 index 00000000..c6275846 --- /dev/null +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/server/level/CubicChunkHolder.java @@ -0,0 +1,32 @@ +package io.github.opencubicchunks.cubicchunks.server.level; + +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.function.IntConsumer; +import java.util.function.IntSupplier; + +import com.mojang.datafixers.util.Either; +import io.github.opencubicchunks.cubicchunks.world.level.chunklike.CloAccess; +import io.github.opencubicchunks.cubicchunks.world.level.chunklike.CloPos; +import net.minecraft.server.level.ChunkHolder; +import net.minecraft.server.level.ChunkMap; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.level.chunk.ChunkStatus; + +public interface CubicChunkHolder { + CloPos cc_getPos(); + + CompletableFuture> cc_getOrScheduleFuture(ChunkStatus status, ChunkMap map); + + @FunctionalInterface + interface LevelChangeListener { + void onLevelChange(CloPos cloPos, IntSupplier p_140120_, int p_140121_, IntConsumer p_140122_); + } + + interface PlayerProvider { + /** + * Returns the players tracking the given chunk. + */ + List getPlayers(CloPos pos, boolean boundaryOnly); + } +} diff --git a/src/main/java/io/github/opencubicchunks/cubicchunks/server/level/CubicTicketType.java b/src/main/java/io/github/opencubicchunks/cubicchunks/server/level/CubicTicketType.java index aa23794f..73a96e6f 100644 --- a/src/main/java/io/github/opencubicchunks/cubicchunks/server/level/CubicTicketType.java +++ b/src/main/java/io/github/opencubicchunks/cubicchunks/server/level/CubicTicketType.java @@ -11,6 +11,7 @@ public class CubicTicketType { public static final TicketType FORCED = create("forced", Comparator.comparingLong(CloPos::asLong)); public static final TicketType LIGHT = create("light", Comparator.comparingLong(CloPos::asLong)); public static final TicketType UNKNOWN = create("unknown", Comparator.comparingLong(CloPos::asLong), 1); + // TODO we don't actually need this; remove it and use TicketType.START public static final TicketType START = create("start", (a, b) -> 0); public static TicketType create(String nameIn, Comparator comparator) { diff --git a/src/main/resources/cubicchunks.mixins.core.json b/src/main/resources/cubicchunks.mixins.core.json index 02d4c3a1..7bc2f8af 100644 --- a/src/main/resources/cubicchunks.mixins.core.json +++ b/src/main/resources/cubicchunks.mixins.core.json @@ -13,7 +13,9 @@ }, "mixins": [ "common.client.multiplayer.MixinClientLevel", - "common.server.MixinMinecraftServer", + "common.server.level.MixinChunkHolder", + "common.server.level.MixinChunkHolder_Forge", + "common.server.level.MixinChunkMap", "common.server.level.MixinChunkTaskPriorityQueue", "common.server.level.MixinChunkTaskPriorityQueueSorter", "common.server.level.MixinChunkTicketTracker", @@ -25,18 +27,25 @@ "common.server.level.MixinServerLevel", "common.server.level.MixinServerPlayer", "common.server.level.MixinTickingTracker", - "common.world.level.MixinLevel", - "common.world.level.MixinLevelReader", + "common.server.level.progress.MixinLoggerChunkProgressListener", + "common.server.level.progress.MixinProcessorChunkProgressListener", + "common.server.level.progress.MixinStoringChunkProgressListener", + "common.server.MixinMinecraftServer", "common.world.level.chunk.MixinChunkAccess", "common.world.level.chunk.MixinChunkSource", + "common.world.level.chunk.MixinChunkStatus", "common.world.level.chunk.MixinImposterProtoChunk", "common.world.level.chunk.MixinLevelChunk", + "common.world.level.chunk.MixinProtoChunk", + "common.world.level.chunk.storage.MixinChunkStorage", "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.cube.MixinProtoCube", + "common.world.level.MixinLevel", + "common.world.level.MixinLevelReader" ], "client": [], "server": [] diff --git a/src/test/java/io/github/opencubicchunks/cubicchunks/integrationtest/server/level/IntegrationTestCubicChunkMap.java b/src/test/java/io/github/opencubicchunks/cubicchunks/integrationtest/server/level/IntegrationTestCubicChunkMap.java new file mode 100644 index 00000000..45cfe613 --- /dev/null +++ b/src/test/java/io/github/opencubicchunks/cubicchunks/integrationtest/server/level/IntegrationTestCubicChunkMap.java @@ -0,0 +1,302 @@ +package io.github.opencubicchunks.cubicchunks.integrationtest.server.level; + +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.mockito.Mockito.withSettings; + +import java.io.IOException; +import java.nio.file.Files; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Stream; + +import io.github.opencubicchunks.cc_core.api.CubicConstants; +import io.github.opencubicchunks.cc_core.utils.Coords; +import io.github.opencubicchunks.cubicchunks.mixin.test.common.server.level.ChunkHolderTestAccess; +import io.github.opencubicchunks.cubicchunks.mixin.test.common.server.level.ChunkMapTestAccess; +import io.github.opencubicchunks.cubicchunks.mixin.test.common.server.level.ServerChunkCacheTestAccess; +import io.github.opencubicchunks.cubicchunks.testutils.BaseTest; +import io.github.opencubicchunks.cubicchunks.testutils.CloseableReference; +import io.github.opencubicchunks.cubicchunks.world.level.chunklike.CloPos; +import io.github.opencubicchunks.cubicchunks.world.level.cube.LevelCube; +import net.minecraft.Util; +import net.minecraft.server.level.ChunkHolder; +import net.minecraft.server.level.ChunkLevel; +import net.minecraft.server.level.ChunkMap; +import net.minecraft.server.level.ServerChunkCache; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.progress.ProcessorChunkProgressListener; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.chunk.ChunkStatus; +import net.minecraft.world.level.chunk.LevelChunk; +import net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator; +import net.minecraft.world.level.levelgen.RandomState; +import net.minecraft.world.level.storage.LevelStorageSource; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.Answers; +import org.mockito.Mockito; + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +public class IntegrationTestCubicChunkMap extends BaseTest { + private CloseableReference createServerChunkCache() throws IOException { + // Worldgen internals + var randomStateMockedStatic = Mockito.mockStatic(RandomState.class, withSettings().defaultAnswer(Answers.RETURNS_DEEP_STUBS)); + NoiseBasedChunkGenerator noiseBasedChunkGeneratorMock = mock(Mockito.RETURNS_DEEP_STUBS); + when(noiseBasedChunkGeneratorMock.createBiomes(any(),any(),any(),any(),any())).thenAnswer(i -> CompletableFuture.completedFuture(i.getArguments()[4])); + when(noiseBasedChunkGeneratorMock.fillFromNoise(any(),any(),any(),any(),any())).thenAnswer(i -> CompletableFuture.completedFuture(i.getArguments()[4])); + // Distance manager is responsible for updating chunk levels; we do this manually for testing + var distanceManagerMockedConstruction = Mockito.mockConstruction(ChunkMap.DistanceManager.class, withSettings().defaultAnswer(Answers.RETURNS_DEEP_STUBS)); + // Server level + ServerLevel serverLevelMock = mock(Mockito.RETURNS_DEEP_STUBS); + when(serverLevelMock.getHeight()).thenReturn(384); + when(serverLevelMock.getSectionsCount()).thenReturn(24); + // This call MAGICALLY makes things not break, and we have no idea why + // Probably due to threading issues? + serverLevelMock.getServer().getWorldData().worldGenOptions().generateStructures(); + // We seem to need an actual directory, not a mock + LevelStorageSource.LevelStorageAccess levelStorageAccessMock = mock(Mockito.RETURNS_DEEP_STUBS); + when(levelStorageAccessMock.getDimensionPath(any())).thenReturn(Files.createTempDirectory("cc_test")); + // This executor is what vanilla uses + var executor = Util.backgroundExecutor(); + var serverChunkCache = new ServerChunkCache( + serverLevelMock, + levelStorageAccessMock, + mock(Mockito.RETURNS_DEEP_STUBS), + mock(Mockito.RETURNS_DEEP_STUBS), + executor, + noiseBasedChunkGeneratorMock, + 10, // server view distance + 10, // simulation distance + false, // sync - not relevant for tests; false should be faster + // Need to mock an implementation of the interface, so that it also implements CubicChunkProgressListener + Mockito.mock(Mockito.RETURNS_DEEP_STUBS), + mock(Mockito.RETURNS_DEEP_STUBS), + mock(Mockito.RETURNS_DEEP_STUBS) + ); + when(serverLevelMock.getChunkSource()).thenReturn(serverChunkCache); + return new CloseableReference<>(serverChunkCache, randomStateMockedStatic, distanceManagerMockedConstruction); + } + + /** + * Load all dependencies for a single chunk at a given status (note that that chunk will only reach the status below) + */ + public void singleChunkAllDependenciesForStatusVanilla(ChunkStatus status) throws Exception { + try(var serverChunkCacheRef = createServerChunkCache()) { + var serverChunkCache = serverChunkCacheRef.value(); + var chunkMap = serverChunkCache.chunkMap; + + var centerLevel = ChunkLevel.byStatus(status); + + var radius = ChunkLevel.MAX_LEVEL - centerLevel; + + ChunkHolder centerHolder = null; + + for (int x = -radius; x <= radius; x++) { + for (int z = -radius; z <= radius; z++) { + var holder = ((ChunkMapTestAccess) chunkMap).invokeUpdateChunkScheduling( + ChunkPos.asLong(x, z), + centerLevel + Math.max(Math.abs(x), Math.abs(z)), + null, + ChunkLevel.MAX_LEVEL + 1 + ); + if (x == 0 && z == 0) centerHolder = holder; + } + } + + var future = ((ChunkMapTestAccess) chunkMap).invokeGetChunkRangeFuture(centerHolder, status.getRange(), + n -> ((ChunkMapTestAccess) chunkMap).invokeGetDependencyStatus(status, n) + ); + + while (!(future.isDone() || future.isCompletedExceptionally())) { + ((ServerChunkCacheTestAccess) serverChunkCache).getMainThreadProcessor().pollTask(); + } + var either = future.get(); + assertTrue(either.left().isPresent(), () -> status + " chunk dependency future Either should be successful, but was " + either.right().get()); + } + } + + private Stream chunkStatuses() { + return ChunkStatus.getStatusList().stream(); + } + + @ParameterizedTest @MethodSource("chunkStatuses") + public void testSingleChunkAllDependenciesForStatusVanilla(ChunkStatus status) throws Exception { + singleChunkAllDependenciesForStatusVanilla(status); + } + + /** + * Load a single chunk at full status + */ + @Test public void singleFullChunkVanilla() throws Exception { + try(var serverChunkCacheRef = createServerChunkCache()) { + var serverChunkCache = serverChunkCacheRef.value(); + var chunkMap = serverChunkCache.chunkMap; + + var centerLevel = ChunkLevel.byStatus(ChunkStatus.FULL); + + var radius = ChunkLevel.MAX_LEVEL - centerLevel; + + ChunkHolder centerHolder = null; + + for (int x = -radius; x <= radius; x++) { + for (int z = -radius; z <= radius; z++) { + var holder = ((ChunkMapTestAccess) chunkMap).invokeUpdateChunkScheduling( + ChunkPos.asLong(x, z), + centerLevel + Math.max(Math.abs(x), Math.abs(z)), + null, + ChunkLevel.MAX_LEVEL + 1 + ); + if (x == 0 && z == 0) centerHolder = holder; + } + } + + var future = centerHolder.getOrScheduleFuture(ChunkStatus.FULL, chunkMap); + + while (!(future.isDone() || future.isCompletedExceptionally())) { + ((ServerChunkCacheTestAccess) serverChunkCache).getMainThreadProcessor().pollTask(); + } + var either = future.get(); + assertTrue(either.left().isPresent(), () -> "Full chunk future Either should be successful, but was " + either.right().get()); + assertInstanceOf(LevelChunk.class, either.left().get()); + } + } + + /** + * Load all dependencies for a single chunk at a given status (note that that chunk will only reach the status below) + */ + public void singleChunkAllDependenciesForStatus(ChunkStatus status) throws Exception { + try(var serverChunkCacheRef = createServerChunkCache()) { + var serverChunkCache = serverChunkCacheRef.value(); + var chunkMap = serverChunkCache.chunkMap; + + var centerLevel = ChunkLevel.byStatus(status); + + var radius = ChunkLevel.MAX_LEVEL - centerLevel; + + ChunkHolder centerHolder = null; + + for (int x = -radius; x <= radius; x++) { + for (int z = -radius; z <= radius; z++) { + var holder = ((ChunkMapTestAccess) chunkMap).invokeCc_UpdateChunkScheduling( + ChunkPos.asLong(x, z), + centerLevel + Math.max(Math.abs(x), Math.abs(z)), + null, + ChunkLevel.MAX_LEVEL + 1 + ); + if (x == 0 && z == 0) centerHolder = holder; + } + } + + var future = ((ChunkMapTestAccess) chunkMap).invokeCc_GetChunkRangeFuture(centerHolder, status.getRange(), + n -> ((ChunkMapTestAccess) chunkMap).invokeGetDependencyStatus(status, n) + ); + + while (!(future.isDone() || future.isCompletedExceptionally())) { + ((ServerChunkCacheTestAccess) serverChunkCache).getMainThreadProcessor().pollTask(); + } + var either = future.get(); + assertTrue(either.left().isPresent(), () -> status + " chunk dependency future Either should be successful, but was " + either.right().get()); + } + } + + @ParameterizedTest @MethodSource("chunkStatuses") + public void testSingleChunkAllDependenciesForStatus(ChunkStatus status) throws Exception { + singleChunkAllDependenciesForStatus(status); + } + + /** + * Load a single chunk at full status + */ + @Test public void singleFullChunk() throws Exception { + try(var serverChunkCacheRef = createServerChunkCache()) { + var serverChunkCache = serverChunkCacheRef.value(); + var chunkMap = serverChunkCache.chunkMap; + + var centerLevel = ChunkLevel.byStatus(ChunkStatus.FULL); + + var radius = ChunkLevel.MAX_LEVEL - centerLevel; + + ChunkHolder centerHolder = null; + + for (int x = -radius; x <= radius; x++) { + for (int z = -radius; z <= radius; z++) { + var holder = ((ChunkMapTestAccess) chunkMap).invokeCc_UpdateChunkScheduling( + ChunkPos.asLong(x, z), + centerLevel + Math.max(Math.abs(x), Math.abs(z)), + null, + ChunkLevel.MAX_LEVEL + 1 + ); + if (x == 0 && z == 0) centerHolder = holder; + } + } + + var future = ((ChunkHolderTestAccess) centerHolder).invokeCc_GetOrScheduleFuture(ChunkStatus.FULL, chunkMap); + + while (!(future.isDone() || future.isCompletedExceptionally())) { + ((ServerChunkCacheTestAccess) serverChunkCache).getMainThreadProcessor().pollTask(); + } + var either = future.get(); + assertTrue(either.left().isPresent(), () -> "Full chunk future Either should be successful, but was " + either.right().get()); + assertInstanceOf(LevelChunk.class, either.left().get()); + } + } + + // TODO might want to test 'dependencies for status' for cubes too + + /** + * Load a single cube at full status + */ + @Test public void singleFullCube() throws Exception { + try(var serverChunkCacheRef = createServerChunkCache()) { + var serverChunkCache = serverChunkCacheRef.value(); + var chunkMap = serverChunkCache.chunkMap; + + var centerLevel = ChunkLevel.byStatus(ChunkStatus.FULL); + + var radius = ChunkLevel.MAX_LEVEL - centerLevel; + + ChunkHolder centerHolder = null; + + for (int x = -radius; x <= radius; x++) { + for (int z = -radius; z <= radius; z++) { + // We want the chunks intersecting this column of cubes to be loaded at the maximum level of any of those cubes; + // this occurs when dy=0, so we only consider x/z distance + int chunkDistance = Math.max(Math.abs(z), Math.abs(x)); + for (int sectionZ = 0; sectionZ < CubicConstants.DIAMETER_IN_SECTIONS; sectionZ++) { + for (int sectionX = 0; sectionX < CubicConstants.DIAMETER_IN_SECTIONS; sectionX++) { + ((ChunkMapTestAccess) chunkMap).invokeCc_UpdateChunkScheduling( + CloPos.asLong(Coords.cubeToSection(x, sectionX), Coords.cubeToSection(z, sectionZ)), + centerLevel + chunkDistance, + null, + ChunkLevel.MAX_LEVEL + 1 + ); + } + } + for (int y = -radius; y <= radius; y++) { + var holder = ((ChunkMapTestAccess) chunkMap).invokeCc_UpdateChunkScheduling( + CloPos.asLong(x, y, z), + centerLevel + Math.max(Math.abs(y), chunkDistance), + null, + ChunkLevel.MAX_LEVEL + 1 + ); + if (x == 0 && z == 0 && y == 0) centerHolder = holder; + } + } + } + + var future = ((ChunkHolderTestAccess) centerHolder).invokeCc_GetOrScheduleFuture(ChunkStatus.FULL, chunkMap); + + while (!(future.isDone() || future.isCompletedExceptionally())) { + ((ServerChunkCacheTestAccess) serverChunkCache).getMainThreadProcessor().pollTask(); + } + var either = future.get(); + assertTrue(either.left().isPresent(), () -> "Full cube future Either should be successful, but was " + either.right().get()); + assertInstanceOf(LevelCube.class, either.left().get()); + } + } +} diff --git a/src/test/java/io/github/opencubicchunks/cubicchunks/mixin/test/common/server/level/ChunkHolderTestAccess.java b/src/test/java/io/github/opencubicchunks/cubicchunks/mixin/test/common/server/level/ChunkHolderTestAccess.java new file mode 100644 index 00000000..b8ef3906 --- /dev/null +++ b/src/test/java/io/github/opencubicchunks/cubicchunks/mixin/test/common/server/level/ChunkHolderTestAccess.java @@ -0,0 +1,17 @@ +package io.github.opencubicchunks.cubicchunks.mixin.test.common.server.level; + +import java.util.concurrent.CompletableFuture; + +import com.mojang.datafixers.util.Either; +import io.github.opencubicchunks.cubicchunks.world.level.chunklike.CloAccess; +import net.minecraft.server.level.ChunkHolder; +import net.minecraft.server.level.ChunkMap; +import net.minecraft.world.level.chunk.ChunkStatus; +import org.spongepowered.asm.mixin.Dynamic; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Invoker; + +@Mixin(ChunkHolder.class) +public interface ChunkHolderTestAccess { + @Dynamic @Invoker CompletableFuture> invokeCc_GetOrScheduleFuture(ChunkStatus status, ChunkMap map); +} diff --git a/src/test/java/io/github/opencubicchunks/cubicchunks/mixin/test/common/server/level/ChunkMapTestAccess.java b/src/test/java/io/github/opencubicchunks/cubicchunks/mixin/test/common/server/level/ChunkMapTestAccess.java new file mode 100644 index 00000000..fe85ff0c --- /dev/null +++ b/src/test/java/io/github/opencubicchunks/cubicchunks/mixin/test/common/server/level/ChunkMapTestAccess.java @@ -0,0 +1,30 @@ +package io.github.opencubicchunks.cubicchunks.mixin.test.common.server.level; + +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.function.IntFunction; + +import javax.annotation.Nullable; + +import com.mojang.datafixers.util.Either; +import io.github.opencubicchunks.cubicchunks.world.level.chunklike.CloAccess; +import net.minecraft.server.level.ChunkHolder; +import net.minecraft.server.level.ChunkMap; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.ChunkStatus; +import org.spongepowered.asm.mixin.Dynamic; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Invoker; + +@Mixin(ChunkMap.class) +public interface ChunkMapTestAccess { + @Invoker @Nullable ChunkHolder invokeUpdateChunkScheduling(long chunkPos, int newLevel, @Nullable ChunkHolder holder, int oldLevel); + + @Invoker CompletableFuture, ChunkHolder.ChunkLoadingFailure>> invokeGetChunkRangeFuture(ChunkHolder chunkHolder, int range, IntFunction statusGetter); + + @Invoker ChunkStatus invokeGetDependencyStatus(ChunkStatus chunkStatus, int p_140264_); + + @Dynamic @Invoker @Nullable ChunkHolder invokeCc_UpdateChunkScheduling(long cloPos, int newLevel, @Nullable ChunkHolder holder, int oldLevel); + + @Dynamic @Invoker CompletableFuture, ChunkHolder.ChunkLoadingFailure>> invokeCc_GetChunkRangeFuture(ChunkHolder chunkHolder, int range, IntFunction statusGetter); +} diff --git a/src/test/java/io/github/opencubicchunks/cubicchunks/mixin/test/common/server/level/ServerChunkCacheTestAccess.java b/src/test/java/io/github/opencubicchunks/cubicchunks/mixin/test/common/server/level/ServerChunkCacheTestAccess.java new file mode 100644 index 00000000..f78db39e --- /dev/null +++ b/src/test/java/io/github/opencubicchunks/cubicchunks/mixin/test/common/server/level/ServerChunkCacheTestAccess.java @@ -0,0 +1,11 @@ +package io.github.opencubicchunks.cubicchunks.mixin.test.common.server.level; + +import net.minecraft.server.level.ServerChunkCache; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(ServerChunkCache.class) +public interface ServerChunkCacheTestAccess { + @Accessor + ServerChunkCache.MainThreadExecutor getMainThreadProcessor(); +} diff --git a/src/test/java/io/github/opencubicchunks/cubicchunks/test/server/level/TestCubicChunkHolder.java b/src/test/java/io/github/opencubicchunks/cubicchunks/test/server/level/TestCubicChunkHolder.java new file mode 100644 index 00000000..fb1a7c0a --- /dev/null +++ b/src/test/java/io/github/opencubicchunks/cubicchunks/test/server/level/TestCubicChunkHolder.java @@ -0,0 +1,15 @@ +package io.github.opencubicchunks.cubicchunks.test.server.level; + +import io.github.opencubicchunks.cubicchunks.integrationtest.server.level.IntegrationTestCubicChunkMap; +import net.minecraft.server.level.ChunkHolder; +import net.minecraft.server.level.ChunkMap; +import net.minecraft.server.level.ServerChunkCache; + +/** + * We do not unit test {@link ChunkHolder} as it is very tightly coupled with {@link ChunkMap} and {@link ServerChunkCache}. + * + * @see IntegrationTestCubicChunkMap integration tests + */ +public class TestCubicChunkHolder { + +} diff --git a/src/test/java/io/github/opencubicchunks/cubicchunks/test/server/level/TestCubicChunkMap.java b/src/test/java/io/github/opencubicchunks/cubicchunks/test/server/level/TestCubicChunkMap.java new file mode 100644 index 00000000..eaeda237 --- /dev/null +++ b/src/test/java/io/github/opencubicchunks/cubicchunks/test/server/level/TestCubicChunkMap.java @@ -0,0 +1,15 @@ +package io.github.opencubicchunks.cubicchunks.test.server.level; + +import io.github.opencubicchunks.cubicchunks.integrationtest.server.level.IntegrationTestCubicChunkMap; +import net.minecraft.server.level.ChunkHolder; +import net.minecraft.server.level.ChunkMap; +import net.minecraft.server.level.ServerChunkCache; + +/** + * We do not unit test {@link ChunkMap} as it is very tightly coupled with {@link ChunkHolder} and {@link ServerChunkCache}. + * + * @see IntegrationTestCubicChunkMap integration tests + */ +public class TestCubicChunkMap { + +} diff --git a/src/test/java/io/github/opencubicchunks/cubicchunks/test/server/level/TestCubicServerChunkCache.java b/src/test/java/io/github/opencubicchunks/cubicchunks/test/server/level/TestCubicServerChunkCache.java new file mode 100644 index 00000000..ef82948b --- /dev/null +++ b/src/test/java/io/github/opencubicchunks/cubicchunks/test/server/level/TestCubicServerChunkCache.java @@ -0,0 +1,13 @@ +package io.github.opencubicchunks.cubicchunks.test.server.level; + +import net.minecraft.server.level.ChunkHolder; +import net.minecraft.server.level.ChunkMap; +import net.minecraft.server.level.ServerChunkCache; + +/** + * We do not unit test {@link ServerChunkCache} as it is very tightly coupled with {@link ChunkMap} and {@link ChunkHolder}. + * TODO integration tests + */ +public class TestCubicServerChunkCache { + +} diff --git a/src/test/resources/cubicchunks.mixins.test.json b/src/test/resources/cubicchunks.mixins.test.json index d9661ff7..2d04ce5b 100644 --- a/src/test/resources/cubicchunks.mixins.test.json +++ b/src/test/resources/cubicchunks.mixins.test.json @@ -11,9 +11,12 @@ "conformVisibility": true }, "mixins": [ + "common.server.level.ChunkHolderTestAccess", + "common.server.level.ChunkMapTestAccess", "common.server.level.ChunkTrackerTestAccess", "common.server.level.CubicDistanceManagerTestAccess", - "common.server.level.MinecraftServerTestAccess" + "common.server.level.MinecraftServerTestAccess", + "common.server.level.ServerChunkCacheTestAccess" ], "client": [], "server": []