From 9b4649acd976eda9c5fc25f50d20833c4a698de4 Mon Sep 17 00:00:00 2001 From: ishland Date: Thu, 31 Oct 2024 22:39:08 +0800 Subject: [PATCH 1/8] docs: MinGW is no longer needed to build --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8d01e7a0f..c56221445 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ Our issue tracker: [link](https://github.com/RelativityMC/C2ME-fabric/issues) Our discord server: [link](https://discord.gg/Kdy8NM5HW4) ## Building and setting up -JDK 22+, Clang 18+ and MinGW are required to build C2ME +JDK 22+, Clang 18+ are required to build C2ME Run the following commands in the root directory: ```shell From 2a57d4dbf07d65e984d647c7790eee61320dd108 Mon Sep 17 00:00:00 2001 From: ishland Date: Fri, 1 Nov 2024 13:29:40 +0800 Subject: [PATCH 2/8] fix: suppress ghost mushrooms outside of simulation distance Fixes #336 Squashed commit of the following: commit db66b242e229a2cf6ccd9240ec1fee36a728ce46 Author: ishland Date: Sun Sep 22 11:19:46 2024 +0800 change: allow disabling mushroom mitigation commit 1839d3c91203891662091f843e8c97db50b743ff Author: ishland Date: Sun Sep 22 10:44:27 2024 +0800 fix: ghost mushroom attempt 5 Related to #336 commit 3eb1b5e9654dfc6e2b24f0af4fafaf4ba2e6ddb3 Author: ishland Date: Sun Sep 22 10:37:23 2024 +0800 Revert "fix: ghost mushroom attempt 3" This reverts commit ebd3b85d77bebecda82e33253a45877f4cc88e4f. commit 669847490a6ce0818a03e406234db84c7ec8b1d0 Author: ishland Date: Sun Sep 22 10:37:18 2024 +0800 Revert "fix: ghost mushroom attempt 4" This reverts commit 48d9f3bc0683b44ef8453a854c11b3ab68324789. commit 48d9f3bc0683b44ef8453a854c11b3ab68324789 Author: ishland Date: Sat Sep 21 16:45:25 2024 +0800 fix: ghost mushroom attempt 4 Related to #336 commit ebd3b85d77bebecda82e33253a45877f4cc88e4f Author: ishland Date: Sat Sep 21 16:34:30 2024 +0800 fix: ghost mushroom attempt 3 Related to #336 commit d33bd29f7392a0657055ecbce61bfdccd001604e Author: ishland Date: Sat Sep 21 15:53:19 2024 +0800 Revert "fix: ghost mushroom attempt 1" This reverts commit 5054bef74cea15ab9acbeb652652c492265f502e. commit 50710be04d9bffd8f653c998f190ddb7d68eb130 Author: ishland Date: Sat Sep 21 15:53:14 2024 +0800 Revert "fix: ghost mushroom attempt 2: ensure post-processed chunk is open" This reverts commit 4f74581533e2ee062b5e05b67f8125ef7db3ac4e. commit cc0e89c7c3ffa0b96e537e227a80533407192b4a Merge: 4f745815 716d21cb Author: ishland Date: Thu Sep 19 16:53:35 2024 +0800 Merge branch 'ver/1.21.1' into fix/336-ghost-mushroom commit 4f74581533e2ee062b5e05b67f8125ef7db3ac4e Author: ishland Date: Thu Sep 19 16:47:00 2024 +0800 fix: ghost mushroom attempt 2: ensure post-processed chunk is open Related to #336 commit 5054bef74cea15ab9acbeb652652c492265f502e Author: ishland Date: Wed Sep 18 23:18:53 2024 +0800 fix: ghost mushroom attempt 1 Related to #336 --- .../rewrites/chunksystem/common/Config.java | 10 ++++++ .../common/statuses/ServerAccessible.java | 33 +++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/c2me-rewrites-chunk-system/src/main/java/com/ishland/c2me/rewrites/chunksystem/common/Config.java b/c2me-rewrites-chunk-system/src/main/java/com/ishland/c2me/rewrites/chunksystem/common/Config.java index 4d3c2e13a..d36a387b5 100644 --- a/c2me-rewrites-chunk-system/src/main/java/com/ishland/c2me/rewrites/chunksystem/common/Config.java +++ b/c2me-rewrites-chunk-system/src/main/java/com/ishland/c2me/rewrites/chunksystem/common/Config.java @@ -34,6 +34,16 @@ Whether to allow POIs (Point of Interest) to be unloaded """) .getBoolean(true, false); + public static final boolean suppressGhostMushrooms = new ConfigSystem.ConfigAccessor() + .key("chunkSystem.suppressGhostMushrooms") + .comment(""" + This option workarounds MC-276863, a bug that makes mushrooms appear in non-postprocessed chunks + This bug is amplified with notickvd as it exposes non-postprocessed chunks to players + + This should not affect other worldgen behavior and game mechanics in general + """) + .getBoolean(true, false); + public static void init() { // intentionally empty } diff --git a/c2me-rewrites-chunk-system/src/main/java/com/ishland/c2me/rewrites/chunksystem/common/statuses/ServerAccessible.java b/c2me-rewrites-chunk-system/src/main/java/com/ishland/c2me/rewrites/chunksystem/common/statuses/ServerAccessible.java index 0765ba30d..44e3aea6b 100644 --- a/c2me-rewrites-chunk-system/src/main/java/com/ishland/c2me/rewrites/chunksystem/common/statuses/ServerAccessible.java +++ b/c2me-rewrites-chunk-system/src/main/java/com/ishland/c2me/rewrites/chunksystem/common/statuses/ServerAccessible.java @@ -12,14 +12,22 @@ import com.ishland.c2me.rewrites.chunksystem.common.fapi.LifecycleEventInvoker; import com.ishland.flowsched.scheduler.ItemHolder; import com.ishland.flowsched.scheduler.KeyStatusPair; +import it.unimi.dsi.fastutil.shorts.ShortList; +import it.unimi.dsi.fastutil.shorts.ShortListIterator; +import net.minecraft.block.BlockState; +import net.minecraft.block.Blocks; import net.minecraft.entity.EntityType; import net.minecraft.entity.SpawnReason; import net.minecraft.nbt.NbtCompound; import net.minecraft.server.world.ChunkLevels; import net.minecraft.server.world.ServerChunkLoadingManager; import net.minecraft.server.world.ServerWorld; +import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.ChunkPos; +import net.minecraft.world.ChunkRegion; import net.minecraft.world.chunk.Chunk; +import net.minecraft.world.chunk.ChunkGenerationStep; +import net.minecraft.world.chunk.ChunkGenerationSteps; import net.minecraft.world.chunk.ChunkStatus; import net.minecraft.world.chunk.ProtoChunk; import net.minecraft.world.chunk.WorldChunk; @@ -56,6 +64,31 @@ public CompletionStage upgradeToThis(ChunkLoadingContext context) { final Chunk chunk = context.holder().getItem().get().chunk(); Preconditions.checkState(chunk instanceof ProtoChunk, "Chunk must be a proto chunk"); ProtoChunk protoChunk = (ProtoChunk) chunk; + + if (Config.suppressGhostMushrooms) { + ServerWorld serverWorld = ((IThreadedAnvilChunkStorage) context.tacs()).getWorld(); + ChunkRegion chunkRegion = new ChunkRegion(serverWorld, context.chunks(), ChunkGenerationSteps.GENERATION.get(ChunkStatus.FULL), chunk); + + ChunkPos chunkPos = context.holder().getKey(); + + ShortList[] postProcessingLists = protoChunk.getPostProcessingLists(); + for (int i = 0; i < postProcessingLists.length; i++) { + if (postProcessingLists[i] != null) { + for (ShortListIterator iterator = postProcessingLists[i].iterator(); iterator.hasNext(); ) { + short short_ = iterator.nextShort(); + BlockPos blockPos = ProtoChunk.joinBlockPos(short_, protoChunk.sectionIndexToCoord(i), chunkPos); + BlockState blockState = protoChunk.getBlockState(blockPos); + + if (blockState.getBlock() == Blocks.BROWN_MUSHROOM || blockState.getBlock() == Blocks.RED_MUSHROOM) { + if (!blockState.canPlaceAt(chunkRegion, blockPos)) { + protoChunk.setBlockState(blockPos, Blocks.AIR.getDefaultState(), false); // TODO depends on the fact that the chunk system always locks the current chunk + } + } + } + } + } + } + return CompletableFuture.runAsync(() -> { ServerWorld serverWorld = ((IThreadedAnvilChunkStorage) context.tacs()).getWorld(); final WorldChunk worldChunk = toFullChunk(protoChunk, serverWorld); From a9a15f815a8201688c9414f63820b5866f9a2dbb Mon Sep 17 00:00:00 2001 From: ishland Date: Sat, 2 Nov 2024 16:58:14 +0800 Subject: [PATCH 3/8] fix: improve player ticket consistency Related to #374 --- .../c2me/rewrites/chunksystem/common/Config.java | 14 ++++++++++++++ .../mixin/MixinChunkTicketManager.java | 16 ++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/c2me-rewrites-chunk-system/src/main/java/com/ishland/c2me/rewrites/chunksystem/common/Config.java b/c2me-rewrites-chunk-system/src/main/java/com/ishland/c2me/rewrites/chunksystem/common/Config.java index d36a387b5..3c314cf8c 100644 --- a/c2me-rewrites-chunk-system/src/main/java/com/ishland/c2me/rewrites/chunksystem/common/Config.java +++ b/c2me-rewrites-chunk-system/src/main/java/com/ishland/c2me/rewrites/chunksystem/common/Config.java @@ -44,6 +44,20 @@ Whether to allow POIs (Point of Interest) to be unloaded """) .getBoolean(true, false); + public static final boolean syncPlayerTickets = new ConfigSystem.ConfigAccessor() + .key("chunkSystem.syncPlayerTickets") + .comment(""" + Whether to synchronize the management of player tickets + + In vanilla Minecraft, player tickets are not always removed immediately when players leave an area. + The delay in removal increases with the chunk system’s throughput, but due to vanilla’s typically + slow chunk loading, tickets are almost always removed immediately. However, some contraptions rely + on this immediate removal behavior and tend to be broken with the increased chunk throughput. + Enabling this option synchronizes player ticket handling, making it more predictable and + thus improving compatibility with these contraptions. + """) + .getBoolean(true, false); + public static void init() { // intentionally empty } diff --git a/c2me-rewrites-chunk-system/src/main/java/com/ishland/c2me/rewrites/chunksystem/mixin/MixinChunkTicketManager.java b/c2me-rewrites-chunk-system/src/main/java/com/ishland/c2me/rewrites/chunksystem/mixin/MixinChunkTicketManager.java index a422a05e7..5709f5eee 100644 --- a/c2me-rewrites-chunk-system/src/main/java/com/ishland/c2me/rewrites/chunksystem/mixin/MixinChunkTicketManager.java +++ b/c2me-rewrites-chunk-system/src/main/java/com/ishland/c2me/rewrites/chunksystem/mixin/MixinChunkTicketManager.java @@ -1,13 +1,20 @@ package com.ishland.c2me.rewrites.chunksystem.mixin; import com.bawnorton.mixinsquared.TargetHandler; +import com.ishland.c2me.rewrites.chunksystem.common.Config; +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; import net.minecraft.server.world.ChunkHolder; import net.minecraft.server.world.ChunkTicketManager; +import net.minecraft.server.world.ThrottledChunkTaskScheduler; +import net.minecraft.util.thread.TaskExecutor; 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; +import java.util.concurrent.Executor; + @Mixin(value = ChunkTicketManager.class, priority = 1051) public class MixinChunkTicketManager { @@ -21,4 +28,13 @@ private int fakeLevel(ChunkHolder instance) { return Integer.MAX_VALUE; } + @WrapOperation(method = "", at = @At(value = "NEW", target = "(Lnet/minecraft/util/thread/TaskExecutor;Ljava/util/concurrent/Executor;I)Lnet/minecraft/server/world/ThrottledChunkTaskScheduler;")) + private ThrottledChunkTaskScheduler syncPlayerTickets(TaskExecutor executor, Executor dispatchExecutor, int maxConcurrentChunks, Operation original, Executor workerExecutor, Executor mainThreadExecutor) { + if (Config.syncPlayerTickets) { + return original.call(executor, mainThreadExecutor, maxConcurrentChunks); // improve player ticket consistency + } else { + return original.call(executor, dispatchExecutor, maxConcurrentChunks); + } + } + } From e9524ac3cfb2d0330eb500329f4e5730bfd72040 Mon Sep 17 00:00:00 2001 From: ishland Date: Sat, 2 Nov 2024 22:17:41 +0800 Subject: [PATCH 4/8] fix: properly map NEW to level 45 --- .../c2me/rewrites/chunksystem/common/NewChunkStatus.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/c2me-rewrites-chunk-system/src/main/java/com/ishland/c2me/rewrites/chunksystem/common/NewChunkStatus.java b/c2me-rewrites-chunk-system/src/main/java/com/ishland/c2me/rewrites/chunksystem/common/NewChunkStatus.java index f86fdfb25..5e4f2c232 100644 --- a/c2me-rewrites-chunk-system/src/main/java/com/ishland/c2me/rewrites/chunksystem/common/NewChunkStatus.java +++ b/c2me-rewrites-chunk-system/src/main/java/com/ishland/c2me/rewrites/chunksystem/common/NewChunkStatus.java @@ -48,6 +48,11 @@ public CompletionStage upgradeToThis(ChunkLoadingContext context) { public CompletionStage downgradeFromThis(ChunkLoadingContext context) { throw new UnsupportedOperationException(); } + + @Override + public int toVanillaLevel() { + return ChunkLevels.INACCESSIBLE + 1; + } }; statuses.add(NEW); DISK = Config.asyncSerialization ? new ReadFromDiskAsync(statuses.size()) : new ReadFromDisk(statuses.size()); From 3e18e75cafa5c065d9b01f6d6b6f2eb180042d02 Mon Sep 17 00:00:00 2001 From: ishland Date: Thu, 7 Nov 2024 17:09:32 +0800 Subject: [PATCH 5/8] docs: update README.md [ci skip] --- README.md | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index c56221445..8126725bb 100644 --- a/README.md +++ b/README.md @@ -14,14 +14,33 @@ C^2M-Engine, or C2ME for short, is a Fabric mod designed to improve the performa ## What does C2ME stand for? Concurrent chunk management engine, it's about making the game better threaded and more scalable in regard to world gen and chunk io performance. -## So what is C2ME not? -**C2ME is currently in alpha stage and pretty experimental.** -Although it is usable in most cases and tested during build time, it doesn't mean that it is fully stable for a production server. -So backup your worlds and practice good game modding skills. +## Vanilla parity +C2ME does not sacrifice vanilla functionality or behavior, or alter the vanilla world generation in the name of raw speed by default. +However, due to the [non-determinism of vanilla world generation](https://bugs.mojang.com/browse/MC-55596), worlds will vary +significantly run-to-run even with the same seed. This is not a bug on our side. + +While we carefully check that we do not modify any vanilla behavior, bugs are unavoidable after all. +So, if you do encounter an issue where C2ME deviates from the intended vanilla behavior, don't hesitate to open an issue. + +## Mod and Datapack compatibility +World generation datapacks that can run on vanilla Minecraft are fully supported. +Custom world generators implemented in mods usually runs well, but *may* cause compatibility issues due to certain +design assumption used by mod authors being broken for further speedups of world generation. +As a world generation mod author, if you find your mod broken, don't hesitate to look for help in our discord server (linked below). +We are willing to help mod authors to embrace scalable world generation. + +### Undefined behavior sanitization +C2ME includes `CheckedThreadLocalRandom` for world random (included in [UWRAD](https://modrinth.com/mod/uwrad)) plus a few others. +These detections exist to prevent mods from screwing up Minecraft internals and causing undebuggable problems. +The detection should almost **never** produce false positives, and should be taken seriously and reported +to corresponding mod authors instead. + +## Usage notice +**Backup your worlds and practice good game modding skills.** ## Downloads -Modrinth: https://modrinth.com/mod/c2me-fabric -CurseForge: https://www.curseforge.com/minecraft/mc-mods/c2me-fabric +Modrinth: https://modrinth.com/mod/c2me-fabric +CurseForge: https://www.curseforge.com/minecraft/mc-mods/c2me ## Support status for Minecraft versions Only the latest Minecraft release and the latest Minecraft snapshot are fully supported. From 63faedc1ea71c9be850b07ffe188b29a7484c611 Mon Sep 17 00:00:00 2001 From: ishland Date: Thu, 7 Nov 2024 19:42:41 +0800 Subject: [PATCH 6/8] tag: 0.3.1+alpha.0 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 2c516476a..66fc23138 100644 --- a/gradle.properties +++ b/gradle.properties @@ -10,7 +10,7 @@ yarn_mappings=1.21.3+build.2 loader_version=0.16.7 fabric_version=0.107.0+1.21.3 # Mod Properties -mod_version=0.3.0+alpha.0 +mod_version=0.3.1+alpha.0 maven_group=com.ishland.c2me archives_base_name=c2me-fabric # Java Dependencies From d0803e6124274f39c3d8346001f17a35ef7aefc4 Mon Sep 17 00:00:00 2001 From: ishland Date: Thu, 7 Nov 2024 23:07:56 +0800 Subject: [PATCH 7/8] fix: GHA publishing --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1a4f2a3f4..63b3a49a9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -46,7 +46,7 @@ jobs: - name: upload to modrinth and curseforge run: ./gradlew modrinth curseforge - if: github.ref == 'refs/heads/ver/1.21.3' + if: github.ref == 'refs/heads/dev/1.21.3' env: MODRINTH_TOKEN: ${{ secrets.MODRINTH_UPLOAD_TOKEN }} CURSEFORGE_TOKEN: ${{ secrets.CURSEFORGE_API_TOKEN }} From 54bd5356db34fc75a57ed6306292a8231b174dbf Mon Sep 17 00:00:00 2001 From: ishland Date: Fri, 8 Nov 2024 23:21:35 +0800 Subject: [PATCH 8/8] fix: shortcut existing chunks check --- .../mixin/MixinServerChunkManager.java | 58 ++++++++++++++++--- 1 file changed, 51 insertions(+), 7 deletions(-) diff --git a/c2me-rewrites-chunk-system/src/main/java/com/ishland/c2me/rewrites/chunksystem/mixin/MixinServerChunkManager.java b/c2me-rewrites-chunk-system/src/main/java/com/ishland/c2me/rewrites/chunksystem/mixin/MixinServerChunkManager.java index c2968495c..8531e65f1 100644 --- a/c2me-rewrites-chunk-system/src/main/java/com/ishland/c2me/rewrites/chunksystem/mixin/MixinServerChunkManager.java +++ b/c2me-rewrites-chunk-system/src/main/java/com/ishland/c2me/rewrites/chunksystem/mixin/MixinServerChunkManager.java @@ -36,20 +36,64 @@ public abstract class MixinServerChunkManager { @Shadow protected abstract @Nullable ChunkHolder getChunkHolder(long pos); + @Shadow @Final private long[] chunkPosCache; + + @Shadow @Final private ChunkStatus[] chunkStatusCache; + + @Shadow @Final private Chunk[] chunkCache; + @Inject(method = "getChunk(IILnet/minecraft/world/chunk/ChunkStatus;Z)Lnet/minecraft/world/chunk/Chunk;", at = @At("HEAD"), cancellable = true) private void shortcutGetChunk(int x, int z, ChunkStatus leastStatus, boolean create, CallbackInfoReturnable cir) { if (Thread.currentThread() != this.serverThread) { - final ChunkHolder holder = this.getChunkHolder(ChunkPos.toLong(x, z)); - if (holder != null) { - final CompletableFuture> future = holder.load(leastStatus, this.chunkLoadingManager); // thread-safe in new system - Chunk chunk = future.getNow(ChunkHolder.UNLOADED).orElse(null); - if (chunk instanceof WrapperProtoChunk readOnlyChunk) chunk = readOnlyChunk.getWrappedChunk(); + Chunk chunk = this.c2me$fastpathExistingChunks(x, z, leastStatus); + if (chunk != null) { + cir.setReturnValue(chunk); + return; + } else if (!create) { + cir.setReturnValue(null); + return; + } + } else { // on server thread + if (!create) { // no create required + Chunk chunk = this.c2me$getFromCahe(x, z, leastStatus, false); if (chunk != null) { - cir.setReturnValue(chunk); // also cancels - return; + cir.setReturnValue(chunk); + } else { + cir.setReturnValue(this.c2me$fastpathExistingChunks(x, z, leastStatus)); + } + return; + } + } + } + + @Unique + private Chunk c2me$getFromCahe(int x, int z, ChunkStatus leastStatus, boolean create) { + long l = ChunkPos.toLong(x, z); + + if (this.chunkPosCache == null || this.chunkStatusCache == null || this.chunkCache == null) { + return null; // no cache + } + + for (int i = 0; i < 4; i++) { + if (l == this.chunkPosCache[i] && leastStatus == this.chunkStatusCache[i]) { + Chunk chunk = this.chunkCache[i]; + if (chunk != null || !create) { + return chunk; } } } + + return null; + } + + @Unique + private Chunk c2me$fastpathExistingChunks(int x, int z, ChunkStatus leastStatus) { + final ChunkHolder holder = this.getChunkHolder(ChunkPos.toLong(x, z)); + if (holder != null) { + final CompletableFuture> future = holder.load(leastStatus, this.chunkLoadingManager); // thread-safe in new system + return future.getNow(ChunkHolder.UNLOADED).orElse(null); + } + return null; } @Inject(method = "tick", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/world/ServerChunkManager;updateChunks()Z", shift = At.Shift.AFTER))