From e986c607c3be10af6ba5b8df29c7b4f796fd7312 Mon Sep 17 00:00:00 2001 From: Sergey Shatunov Date: Wed, 10 Jan 2024 05:05:28 +0800 Subject: [PATCH] Add ChunkWatchEvent (#464) Signed-off-by: Sergey Shatunov Co-authored-by: Max --- .../event/events/common/ChunkWatchEvent.java | 62 +++++++++++++++++++ .../mixin/fabric/MixinChunkMap.java | 14 +++++ .../mixin/fabric/MixinPlayerChunkSender.java | 38 ++++++++++++ .../main/resources/architectury.mixins.json | 1 + .../forge/minecraftforge/MixinChunkMap.java | 43 +++++++++++++ .../MixinPlayerChunkSender.java | 38 ++++++++++++ .../resources/architectury-forge.mixins.json | 2 + .../neoforge/ArchitecturyNeoForge.java | 20 ++++++ .../architectury/test/events/DebugEvents.java | 9 +++ 9 files changed, 227 insertions(+) create mode 100644 common/src/main/java/dev/architectury/event/events/common/ChunkWatchEvent.java create mode 100644 fabric/src/main/java/dev/architectury/mixin/fabric/MixinPlayerChunkSender.java create mode 100644 minecraftforge/src/main/java/dev/architectury/mixin/forge/minecraftforge/MixinChunkMap.java create mode 100644 minecraftforge/src/main/java/dev/architectury/mixin/forge/minecraftforge/MixinPlayerChunkSender.java diff --git a/common/src/main/java/dev/architectury/event/events/common/ChunkWatchEvent.java b/common/src/main/java/dev/architectury/event/events/common/ChunkWatchEvent.java new file mode 100644 index 000000000..124f7e512 --- /dev/null +++ b/common/src/main/java/dev/architectury/event/events/common/ChunkWatchEvent.java @@ -0,0 +1,62 @@ +/* + * This file is part of architectury. + * Copyright (C) 2020, 2021, 2022 architectury + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package dev.architectury.event.events.common; + +import dev.architectury.event.Event; +import dev.architectury.event.EventFactory; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.chunk.LevelChunk; + +public interface ChunkWatchEvent { + /** + * This event is fired whenever a {@link ServerPlayer} begins watching a chunk and the chunk is queued up for + * sending to the client. + *

+ * This event must NOT be used to send additional chunk-related data to the client as the client will not be aware + * of the chunk yet when this event fires. {@link ChunkWatchEvent#SENT} should be used for this purpose instead + */ + Event WATCH = EventFactory.createLoop(); + + /** + * This event is fired whenever a chunk being watched by a {@link ServerPlayer} is transmitted to their client. + *

+ * This event may be used to send additional chunk-related data to the client. + */ + Event SENT = EventFactory.createLoop(); + + /** + * This event is fired whenever a {@link ServerPlayer} stops watching a chunk. The chunk this event fires for + * may never have actually been known to the client if the chunk goes out of range before being sent due to + * slow pacing of chunk sync on slow connections or to slow clients. + */ + Event UNWATCH = EventFactory.createLoop(); + + @FunctionalInterface + interface ChunkListener { + void listen(LevelChunk chunk, ServerLevel level, ServerPlayer player); + } + + @FunctionalInterface + interface ChunkPosListener { + void listen(ChunkPos chunkPos, ServerLevel level, ServerPlayer player); + } +} diff --git a/fabric/src/main/java/dev/architectury/mixin/fabric/MixinChunkMap.java b/fabric/src/main/java/dev/architectury/mixin/fabric/MixinChunkMap.java index 6c17c911c..d3f6e82c4 100644 --- a/fabric/src/main/java/dev/architectury/mixin/fabric/MixinChunkMap.java +++ b/fabric/src/main/java/dev/architectury/mixin/fabric/MixinChunkMap.java @@ -20,17 +20,21 @@ package dev.architectury.mixin.fabric; import dev.architectury.event.events.common.ChunkEvent; +import dev.architectury.event.events.common.ChunkWatchEvent; import net.minecraft.nbt.CompoundTag; import net.minecraft.server.level.ChunkMap; import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.chunk.ChunkAccess; import net.minecraft.world.level.chunk.ChunkStatus; +import net.minecraft.world.level.chunk.LevelChunk; 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; import org.spongepowered.asm.mixin.injection.callback.LocalCapture; @@ -48,4 +52,14 @@ public class MixinChunkMap { private void save(ChunkAccess chunkAccess, CallbackInfoReturnable cir, ChunkPos pos, ChunkStatus chunkStatus, CompoundTag nbt) { ChunkEvent.SAVE_DATA.invoker().save(chunkAccess, this.level, nbt); } + + @Inject(method = "markChunkPendingToSend(Lnet/minecraft/server/level/ServerPlayer;Lnet/minecraft/world/level/chunk/LevelChunk;)V", at = @At("TAIL")) + private static void watch(ServerPlayer player, LevelChunk chunk, CallbackInfo ci) { + ChunkWatchEvent.WATCH.invoker().listen(chunk, player.serverLevel(), player); + } + + @Inject(method = "dropChunk(Lnet/minecraft/server/level/ServerPlayer;Lnet/minecraft/world/level/ChunkPos;)V", at = @At("HEAD")) + private static void unwatch(ServerPlayer player, ChunkPos chunkPos, CallbackInfo ci) { + ChunkWatchEvent.UNWATCH.invoker().listen(chunkPos, player.serverLevel(), player); + } } diff --git a/fabric/src/main/java/dev/architectury/mixin/fabric/MixinPlayerChunkSender.java b/fabric/src/main/java/dev/architectury/mixin/fabric/MixinPlayerChunkSender.java new file mode 100644 index 000000000..cce2fae4a --- /dev/null +++ b/fabric/src/main/java/dev/architectury/mixin/fabric/MixinPlayerChunkSender.java @@ -0,0 +1,38 @@ +/* + * This file is part of architectury. + * Copyright (C) 2020, 2021, 2022 architectury + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package dev.architectury.mixin.fabric; + +import dev.architectury.event.events.common.ChunkWatchEvent; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.network.PlayerChunkSender; +import net.minecraft.server.network.ServerGamePacketListenerImpl; +import net.minecraft.world.level.chunk.LevelChunk; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(PlayerChunkSender.class) +public class MixinPlayerChunkSender { + @Inject(method = "sendChunk", at = @At("TAIL")) + private static void send(ServerGamePacketListenerImpl packetListener, ServerLevel level, LevelChunk chunk, CallbackInfo ci) { + ChunkWatchEvent.SENT.invoker().listen(chunk, level, packetListener.player); + } +} diff --git a/fabric/src/main/resources/architectury.mixins.json b/fabric/src/main/resources/architectury.mixins.json index f8688e509..e2a96df9f 100644 --- a/fabric/src/main/resources/architectury.mixins.json +++ b/fabric/src/main/resources/architectury.mixins.json @@ -52,6 +52,7 @@ "MixinPhantomSpawner", "MixinPlayer", "MixinPlayerAdvancements", + "MixinPlayerChunkSender", "MixinPlayerList", "MixinResultSlot", "MixinServerLevel", diff --git a/minecraftforge/src/main/java/dev/architectury/mixin/forge/minecraftforge/MixinChunkMap.java b/minecraftforge/src/main/java/dev/architectury/mixin/forge/minecraftforge/MixinChunkMap.java new file mode 100644 index 000000000..e68cd47aa --- /dev/null +++ b/minecraftforge/src/main/java/dev/architectury/mixin/forge/minecraftforge/MixinChunkMap.java @@ -0,0 +1,43 @@ +/* + * This file is part of architectury. + * Copyright (C) 2020, 2021, 2022 architectury + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package dev.architectury.mixin.forge.minecraftforge; + +import dev.architectury.event.events.common.ChunkWatchEvent; +import net.minecraft.server.level.ChunkMap; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.chunk.LevelChunk; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(ChunkMap.class) +public abstract class MixinChunkMap { + @Inject(method = "markChunkPendingToSend(Lnet/minecraft/server/level/ServerPlayer;Lnet/minecraft/world/level/chunk/LevelChunk;)V", at = @At("TAIL")) + private static void watch(ServerPlayer player, LevelChunk chunk, CallbackInfo ci) { + ChunkWatchEvent.WATCH.invoker().listen(chunk, player.serverLevel(), player); + } + + @Inject(method = "dropChunk(Lnet/minecraft/server/level/ServerPlayer;Lnet/minecraft/world/level/ChunkPos;)V", at = @At("HEAD")) + private static void unwatch(ServerPlayer player, ChunkPos chunkPos, CallbackInfo ci) { + ChunkWatchEvent.UNWATCH.invoker().listen(chunkPos, player.serverLevel(), player); + } +} diff --git a/minecraftforge/src/main/java/dev/architectury/mixin/forge/minecraftforge/MixinPlayerChunkSender.java b/minecraftforge/src/main/java/dev/architectury/mixin/forge/minecraftforge/MixinPlayerChunkSender.java new file mode 100644 index 000000000..81faaadeb --- /dev/null +++ b/minecraftforge/src/main/java/dev/architectury/mixin/forge/minecraftforge/MixinPlayerChunkSender.java @@ -0,0 +1,38 @@ +/* + * This file is part of architectury. + * Copyright (C) 2020, 2021, 2022 architectury + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package dev.architectury.mixin.forge.minecraftforge; + +import dev.architectury.event.events.common.ChunkWatchEvent; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.network.PlayerChunkSender; +import net.minecraft.server.network.ServerGamePacketListenerImpl; +import net.minecraft.world.level.chunk.LevelChunk; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(PlayerChunkSender.class) +public abstract class MixinPlayerChunkSender { + @Inject(method = "sendChunk", at = @At("TAIL")) + private static void sendChunk(ServerGamePacketListenerImpl packetListener, ServerLevel level, LevelChunk chunk, CallbackInfo ci) { + ChunkWatchEvent.SENT.invoker().listen(chunk, level, packetListener.player); + } +} diff --git a/minecraftforge/src/main/resources/architectury-forge.mixins.json b/minecraftforge/src/main/resources/architectury-forge.mixins.json index a02097dc2..228f6e689 100644 --- a/minecraftforge/src/main/resources/architectury-forge.mixins.json +++ b/minecraftforge/src/main/resources/architectury-forge.mixins.json @@ -6,6 +6,8 @@ "client": [ ], "mixins": [ + "minecraftforge.MixinChunkMap", + "minecraftforge.MixinPlayerChunkSender", "minecraftforge.MixinChunkSerializer", "minecraftforge.MixinEntitySpawnExtension" ], diff --git a/neoforge/src/main/java/dev/architectury/neoforge/ArchitecturyNeoForge.java b/neoforge/src/main/java/dev/architectury/neoforge/ArchitecturyNeoForge.java index 71cbd4b2f..e23d53700 100644 --- a/neoforge/src/main/java/dev/architectury/neoforge/ArchitecturyNeoForge.java +++ b/neoforge/src/main/java/dev/architectury/neoforge/ArchitecturyNeoForge.java @@ -20,9 +20,11 @@ package dev.architectury.neoforge; import dev.architectury.event.EventHandler; +import dev.architectury.event.events.common.ChunkWatchEvent; import dev.architectury.networking.SpawnEntityPacket; import dev.architectury.registry.level.biome.forge.BiomeModificationsImpl; import dev.architectury.utils.ArchitecturyConstants; +import net.neoforged.bus.api.SubscribeEvent; import dev.architectury.utils.Env; import dev.architectury.utils.EnvExecutor; import net.neoforged.fml.common.Mod; @@ -35,4 +37,22 @@ public ArchitecturyNeoForge() { EnvExecutor.runInEnv(Env.CLIENT, () -> SpawnEntityPacket.Client::register); } + + @Mod.EventBusSubscriber(bus = Mod.EventBusSubscriber.Bus.FORGE) + private static class ForgeBusSubscriber { + @SubscribeEvent + private static void event(net.neoforged.neoforge.event.level.ChunkWatchEvent.Watch event) { + ChunkWatchEvent.WATCH.invoker().listen(event.getChunk(), event.getLevel(), event.getPlayer()); + } + + @SubscribeEvent + private static void event(net.neoforged.neoforge.event.level.ChunkWatchEvent.Sent event) { + ChunkWatchEvent.SENT.invoker().listen(event.getChunk(), event.getLevel(), event.getPlayer()); + } + + @SubscribeEvent + private static void event(net.neoforged.neoforge.event.level.ChunkWatchEvent.UnWatch event) { + ChunkWatchEvent.UNWATCH.invoker().listen(event.getPos(), event.getLevel(), event.getPlayer()); + } + } } diff --git a/testmod-common/src/main/java/dev/architectury/test/events/DebugEvents.java b/testmod-common/src/main/java/dev/architectury/test/events/DebugEvents.java index 5423bf98a..14ad5468b 100644 --- a/testmod-common/src/main/java/dev/architectury/test/events/DebugEvents.java +++ b/testmod-common/src/main/java/dev/architectury/test/events/DebugEvents.java @@ -235,6 +235,15 @@ public static void debugEvents() { ChunkEvent.SAVE_DATA.register((chunk, level, nbt) -> { // TestMod.SINK.accept("Chunk saved at x=" + chunk.getPos().x + ", z=" + chunk.getPos().z + " in dimension '" + level.dimension().location() + "'"); }); + ChunkWatchEvent.WATCH.register((chunk, level, player) -> { +// TestMod.SINK.accept("Chunk at x=%d, z=%d in dimension '%s' being watched by %s", chunk.getPos().x, chunk.getPos().z, level.dimension().location(), player.getScoreboardName()); + }); + ChunkWatchEvent.SENT.register((chunk, level, player) -> { +// TestMod.SINK.accept("Chunk at x=%d, z=%d in dimension '%s' sent to %s", chunk.getPos().x, chunk.getPos().z, level.dimension().location(), player.getScoreboardName()); + }); + ChunkWatchEvent.UNWATCH.register((chunkPos, level, player) -> { +// TestMod.SINK.accept("Chunk at x=%d, z=%d in dimension '%s' abandoned by %s", chunkPos.x, chunkPos.z, level.dimension().location(), player.getScoreboardName()); + }); } public static String toShortString(Vec3i pos) {