From 7ef5e90bf9dedd6a99ff1615828f8a508bf3e934 Mon Sep 17 00:00:00 2001 From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> Date: Sat, 31 Oct 2026 17:40:34 -0700 Subject: [PATCH] Fire Fabric chunk load/unload events from NeoForge's chunk events --- .../event/lifecycle/LifecycleEventsImpl.java | 22 ++++++++ .../mixin/event/lifecycle/ChunkMapMixin.java | 53 ------------------- .../lifecycle/ChunkStatusTasksMixin.java | 11 +--- .../fabric-lifecycle-events-v1.mixins.json | 1 - 4 files changed, 23 insertions(+), 64 deletions(-) delete mode 100644 fabric-lifecycle-events-v1/src/main/java/net/fabricmc/fabric/mixin/event/lifecycle/ChunkMapMixin.java diff --git a/fabric-lifecycle-events-v1/src/main/java/net/fabricmc/fabric/impl/event/lifecycle/LifecycleEventsImpl.java b/fabric-lifecycle-events-v1/src/main/java/net/fabricmc/fabric/impl/event/lifecycle/LifecycleEventsImpl.java index 96fc2700a3..3e60a70cc3 100644 --- a/fabric-lifecycle-events-v1/src/main/java/net/fabricmc/fabric/impl/event/lifecycle/LifecycleEventsImpl.java +++ b/fabric-lifecycle-events-v1/src/main/java/net/fabricmc/fabric/impl/event/lifecycle/LifecycleEventsImpl.java @@ -16,6 +16,10 @@ package net.fabricmc.fabric.impl.event.lifecycle; +import net.neoforged.neoforge.common.NeoForge; +import net.neoforged.neoforge.event.level.ChunkEvent; + +import net.minecraft.server.level.ServerLevel; import net.minecraft.world.entity.Entity; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.chunk.LevelChunk; @@ -29,6 +33,24 @@ public final class LifecycleEventsImpl implements ModInitializer { @Override public void onInitialize() { + // Neo: fire chunk load/unload from NeoForge's chunk events instead of vanilla injections, + // so they also fire when a mod replaces vanilla's chunk system and posts NeoForge's events itself. + NeoForge.EVENT_BUS.addListener(ChunkEvent.Load.class, event -> { + if (event.getLevel() instanceof ServerLevel level) { + ServerChunkEvents.CHUNK_LOAD.invoker().onChunkLoad(level, event.getChunk(), event.isNewChunk()); + + if (event.isNewChunk()) { + ServerChunkEvents.CHUNK_GENERATE.invoker().onChunkGenerate(level, event.getChunk()); + } + } + }); + + NeoForge.EVENT_BUS.addListener(ChunkEvent.Unload.class, event -> { + if (event.getLevel() instanceof ServerLevel level) { + ServerChunkEvents.CHUNK_UNLOAD.invoker().onChunkUnload(level, event.getChunk()); + } + }); + // Part of impl for block entity events ServerChunkEvents.CHUNK_LOAD.register((level, chunk, generated) -> { ((LoadedChunksCache) level).fabric_markLoaded(chunk); diff --git a/fabric-lifecycle-events-v1/src/main/java/net/fabricmc/fabric/mixin/event/lifecycle/ChunkMapMixin.java b/fabric-lifecycle-events-v1/src/main/java/net/fabricmc/fabric/mixin/event/lifecycle/ChunkMapMixin.java deleted file mode 100644 index 02abade4e7..0000000000 --- a/fabric-lifecycle-events-v1/src/main/java/net/fabricmc/fabric/mixin/event/lifecycle/ChunkMapMixin.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (c) 2016, 2017, 2018, 2019 FabricMC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.fabricmc.fabric.mixin.event.lifecycle; - -import java.util.concurrent.CompletableFuture; - -import com.llamalad7.mixinextras.sugar.Local; -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 net.minecraft.server.level.ChunkHolder; -import net.minecraft.server.level.ChunkMap; -import net.minecraft.server.level.ServerLevel; -import net.minecraft.world.level.chunk.ChunkAccess; -import net.minecraft.world.level.chunk.LevelChunk; - -import net.fabricmc.fabric.api.event.lifecycle.v1.ServerChunkEvents; - -@Mixin(ChunkMap.class) -public abstract class ChunkMapMixin { - @Shadow - @Final - private ServerLevel level; - - /** - * Injection is inside of scheduleUnload. - * We inject just after "setLoaded" is made false, since here the LevelChunk is guaranteed to be unloaded. - */ - @Inject(method = "lambda$scheduleUnload$0", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/level/ChunkMap;save(Lnet/minecraft/world/level/chunk/ChunkAccess;)Z")) - private void onChunkUnload(ChunkHolder chunkHolder, CompletableFuture completableFuture, long l, CallbackInfo ci, @Local(name = "chunk") ChunkAccess chunk) { - if (chunk instanceof LevelChunk levelChunk) { - ServerChunkEvents.CHUNK_UNLOAD.invoker().onChunkUnload(this.level, levelChunk); - } - } -} diff --git a/fabric-lifecycle-events-v1/src/main/java/net/fabricmc/fabric/mixin/event/lifecycle/ChunkStatusTasksMixin.java b/fabric-lifecycle-events-v1/src/main/java/net/fabricmc/fabric/mixin/event/lifecycle/ChunkStatusTasksMixin.java index c8c597a0ee..2c62c8f60d 100644 --- a/fabric-lifecycle-events-v1/src/main/java/net/fabricmc/fabric/mixin/event/lifecycle/ChunkStatusTasksMixin.java +++ b/fabric-lifecycle-events-v1/src/main/java/net/fabricmc/fabric/mixin/event/lifecycle/ChunkStatusTasksMixin.java @@ -25,7 +25,6 @@ import net.minecraft.server.level.FullChunkStatus; import net.minecraft.server.level.GenerationChunkHolder; import net.minecraft.world.level.chunk.ChunkAccess; -import net.minecraft.world.level.chunk.ImposterProtoChunk; import net.minecraft.world.level.chunk.LevelChunk; import net.minecraft.world.level.chunk.status.ChunkStatusTasks; import net.minecraft.world.level.chunk.status.WorldGenContext; @@ -38,19 +37,11 @@ abstract class ChunkStatusTasksMixin { @Unique private static final FullChunkStatus[] fabric_FULL_CHUNK_STATUSES = FullChunkStatus.values(); // values() clones the internal array each call, so cache the return + // Neo: CHUNK_LOAD and CHUNK_GENERATE are fired from NeoForge's ChunkEvent.Load (see LifecycleEventsImpl), which is posted at the end of this lambda. @Inject(method = "lambda$full$0", at = @At("TAIL")) private static void onChunkLoad(ChunkAccess chunk, WorldGenContext worldGenContext, GenerationChunkHolder chunkHolder, CallbackInfoReturnable callbackInfoReturnable) { LevelChunk levelChunk = (LevelChunk) callbackInfoReturnable.getReturnValue(); - boolean generated = !(chunk instanceof ImposterProtoChunk); - - // We fire the event at TAIL since the chunk is guaranteed to be a LevelChunk then. - ServerChunkEvents.CHUNK_LOAD.invoker().onChunkLoad(worldGenContext.level(), levelChunk, generated); - - if (generated) { - ServerChunkEvents.CHUNK_GENERATE.invoker().onChunkGenerate(worldGenContext.level(), levelChunk); - } - // Handles the case where the chunk becomes accessible from being completely unloaded, only fires if chunkHolder has been set to at least that full chunk status FullChunkStatusEventTracker chunkStatusTracker = (FullChunkStatusEventTracker) chunkHolder; diff --git a/fabric-lifecycle-events-v1/src/main/resources/fabric-lifecycle-events-v1.mixins.json b/fabric-lifecycle-events-v1/src/main/resources/fabric-lifecycle-events-v1.mixins.json index ccbcc12d99..5e1846a78c 100644 --- a/fabric-lifecycle-events-v1/src/main/resources/fabric-lifecycle-events-v1.mixins.json +++ b/fabric-lifecycle-events-v1/src/main/resources/fabric-lifecycle-events-v1.mixins.json @@ -4,7 +4,6 @@ "compatibilityLevel": "JAVA_25", "mixins": [ "ChunkHolderMixin", - "ChunkMapMixin", "ChunkStatusTasksMixin", "EntityMixin", "EntityTypeMixin",