From 494203ef5af2669230bee9129d97ba4ee4e57a87 Mon Sep 17 00:00:00 2001 From: embeddedt <42941056+embeddedt@users.noreply.github.com> Date: Sun, 24 May 2026 19:45:24 -0400 Subject: [PATCH] Fix potential crash during worldgen with release_protochunks enabled The crash can occur if a protochunk next to a FULL chunk is dropped, and then later re-requested. If it was not persisted to disk for any reason, it starts regeneration from scratch. At FEATURES stage, it may try to place blocks into the adjacent LevelChunk already in the world. The fix is to prevent this situation from even happening by pinning protochunks directly next to FULL chunks, and preventing them from unloading. --- .../mixin/perf/release_protochunks/ChunkHolderMixin.java | 3 +-- .../mixin/perf/release_protochunks/ChunkMapMixin.java | 3 +-- .../duck/release_protochunks/IClearableChunkHolder.java | 8 ++++++++ 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/embeddedt/modernfix/common/mixin/perf/release_protochunks/ChunkHolderMixin.java b/src/main/java/org/embeddedt/modernfix/common/mixin/perf/release_protochunks/ChunkHolderMixin.java index b66ff4dc..56cc5efb 100644 --- a/src/main/java/org/embeddedt/modernfix/common/mixin/perf/release_protochunks/ChunkHolderMixin.java +++ b/src/main/java/org/embeddedt/modernfix/common/mixin/perf/release_protochunks/ChunkHolderMixin.java @@ -4,7 +4,6 @@ import com.mojang.datafixers.util.Either; import net.minecraft.server.level.ChunkHolder; import net.minecraft.server.level.ChunkLevel; import net.minecraft.server.level.ChunkMap; -import net.minecraft.server.level.FullChunkStatus; import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.chunk.ChunkAccess; import org.embeddedt.modernfix.duck.release_protochunks.IClearableChunkHolder; @@ -85,7 +84,7 @@ public class ChunkHolderMixin implements IClearableChunkHolder { } private void mfix$markAsNeedingProtoChunkDrop() { - if (!ChunkLevel.fullStatus(this.ticketLevel).isOrAfter(FullChunkStatus.FULL) + if (this.ticketLevel >= LOWEST_DROPPABLE_TICKET_LEVEL && ChunkLevel.isLoaded(this.ticketLevel)) { // register for suspension check when chain completes var map = ((ISuspendedHolderTrackingChunkMap)this.playerProvider); diff --git a/src/main/java/org/embeddedt/modernfix/common/mixin/perf/release_protochunks/ChunkMapMixin.java b/src/main/java/org/embeddedt/modernfix/common/mixin/perf/release_protochunks/ChunkMapMixin.java index 916f7325..2ec12983 100644 --- a/src/main/java/org/embeddedt/modernfix/common/mixin/perf/release_protochunks/ChunkMapMixin.java +++ b/src/main/java/org/embeddedt/modernfix/common/mixin/perf/release_protochunks/ChunkMapMixin.java @@ -8,7 +8,6 @@ import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap; import net.minecraft.server.level.ChunkHolder; import net.minecraft.server.level.ChunkLevel; import net.minecraft.server.level.ChunkMap; -import net.minecraft.server.level.FullChunkStatus; import net.minecraft.util.thread.BlockableEventLoop; import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.chunk.ChunkAccess; @@ -68,7 +67,7 @@ public abstract class ChunkMapMixin implements ISuspendedHolderTrackingChunkMap long pos = entry.getLongKey(); ChunkHolder holder = this.updatingChunkMap.get(pos); if (holder == null // already removed - || ChunkLevel.fullStatus(holder.getTicketLevel()).isOrAfter(FullChunkStatus.FULL) // promoted to FULL + || holder.getTicketLevel() < IClearableChunkHolder.LOWEST_DROPPABLE_TICKET_LEVEL // promoted to FULL or adjacent to FULL chunk || !ChunkLevel.isLoaded(holder.getTicketLevel()) // is going to be dropped through normal code path ) { dropIterator.remove(); diff --git a/src/main/java/org/embeddedt/modernfix/duck/release_protochunks/IClearableChunkHolder.java b/src/main/java/org/embeddedt/modernfix/duck/release_protochunks/IClearableChunkHolder.java index b6910cf6..5ba3c06e 100644 --- a/src/main/java/org/embeddedt/modernfix/duck/release_protochunks/IClearableChunkHolder.java +++ b/src/main/java/org/embeddedt/modernfix/duck/release_protochunks/IClearableChunkHolder.java @@ -1,8 +1,16 @@ package org.embeddedt.modernfix.duck.release_protochunks; +import net.minecraft.server.level.ChunkLevel; +import net.minecraft.server.level.FullChunkStatus; + import java.util.concurrent.atomic.AtomicInteger; public interface IClearableChunkHolder { + /** + * We don't want to drop FULL chunks, or chunks immediately surrouding FULL. So + 2 is the minimum we can drop. + */ + int LOWEST_DROPPABLE_TICKET_LEVEL = ChunkLevel.byStatus(FullChunkStatus.FULL) + 2; + void mfix$resetProtoChunkFutures(); AtomicInteger mfix$getGenerationRefCount();