From aee0a95cefd8b9d0673010c4f75a1c5f0bf597d1 Mon Sep 17 00:00:00 2001 From: forgetmenot <149003152+forgetmenot13579@users.noreply.github.com> Date: Sat, 21 Sep 2024 19:01:29 -0700 Subject: [PATCH] - Add server tick events for BlockEntity, Chunk, and Entity --- .../lifecycle/v1/ServerBlockEntityEvents.java | 14 +++++ .../event/lifecycle/v1/ServerChunkEvents.java | 14 +++++ .../lifecycle/v1/ServerEntityEvents.java | 14 +++++ .../event/lifecycle/ServerWorldMixin.java | 26 ++++++++ .../event/lifecycle/WorldChunkMixin.java | 45 +++++++++++++ .../fabric-lifecycle-events-v1.mixins.json | 5 +- .../ServerBlockEntityLifecycleTests.java | 6 ++ .../lifecycle/ServerChunkLifecycleTests.java | 63 +++++++++++++++++++ .../lifecycle/ServerEntityLifecycleTests.java | 16 +++-- .../src/testmod/resources/fabric.mod.json | 1 + 10 files changed, 198 insertions(+), 6 deletions(-) create mode 100644 fabric-lifecycle-events-v1/src/main/java/net/fabricmc/fabric/mixin/event/lifecycle/WorldChunkMixin.java create mode 100644 fabric-lifecycle-events-v1/src/testmod/java/net/fabricmc/fabric/test/event/lifecycle/ServerChunkLifecycleTests.java diff --git a/fabric-lifecycle-events-v1/src/main/java/net/fabricmc/fabric/api/event/lifecycle/v1/ServerBlockEntityEvents.java b/fabric-lifecycle-events-v1/src/main/java/net/fabricmc/fabric/api/event/lifecycle/v1/ServerBlockEntityEvents.java index 1e3e1a2520..37bcf4c241 100644 --- a/fabric-lifecycle-events-v1/src/main/java/net/fabricmc/fabric/api/event/lifecycle/v1/ServerBlockEntityEvents.java +++ b/fabric-lifecycle-events-v1/src/main/java/net/fabricmc/fabric/api/event/lifecycle/v1/ServerBlockEntityEvents.java @@ -49,6 +49,15 @@ private ServerBlockEntityEvents() { } }); + /** + * Called when a BlockEntity is ticked by a ServerWorld. + */ + public static final Event BLOCK_ENTITY_TICK = EventFactory.createArrayBacked(ServerBlockEntityEvents.Tick.class, callbacks -> (blockEntity, world) -> { + for (Tick callback : callbacks) { + callback.onTick(blockEntity, world); + } + }); + @FunctionalInterface public interface Load { void onLoad(BlockEntity blockEntity, ServerWorld world); @@ -58,4 +67,9 @@ public interface Load { public interface Unload { void onUnload(BlockEntity blockEntity, ServerWorld world); } + + @FunctionalInterface + public interface Tick { + void onTick(BlockEntity blockEntity, ServerWorld world); + } } diff --git a/fabric-lifecycle-events-v1/src/main/java/net/fabricmc/fabric/api/event/lifecycle/v1/ServerChunkEvents.java b/fabric-lifecycle-events-v1/src/main/java/net/fabricmc/fabric/api/event/lifecycle/v1/ServerChunkEvents.java index f986b7be92..4d05e2566d 100644 --- a/fabric-lifecycle-events-v1/src/main/java/net/fabricmc/fabric/api/event/lifecycle/v1/ServerChunkEvents.java +++ b/fabric-lifecycle-events-v1/src/main/java/net/fabricmc/fabric/api/event/lifecycle/v1/ServerChunkEvents.java @@ -48,6 +48,15 @@ private ServerChunkEvents() { } }); + /** + * Called when a chunk is ticked by a ServerWorld. + */ + public static final Event CHUNK_TICK = EventFactory.createArrayBacked(ServerChunkEvents.Tick.class, callbacks -> (serverWorld, chunk) -> { + for (Tick callback : callbacks) { + callback.onChunkTick(serverWorld, chunk); + } + }); + @FunctionalInterface public interface Load { void onChunkLoad(ServerWorld world, WorldChunk chunk); @@ -57,4 +66,9 @@ public interface Load { public interface Unload { void onChunkUnload(ServerWorld world, WorldChunk chunk); } + + @FunctionalInterface + public interface Tick { + void onChunkTick(ServerWorld world, WorldChunk chunk); + } } diff --git a/fabric-lifecycle-events-v1/src/main/java/net/fabricmc/fabric/api/event/lifecycle/v1/ServerEntityEvents.java b/fabric-lifecycle-events-v1/src/main/java/net/fabricmc/fabric/api/event/lifecycle/v1/ServerEntityEvents.java index dfc1cd7696..5008ee9047 100644 --- a/fabric-lifecycle-events-v1/src/main/java/net/fabricmc/fabric/api/event/lifecycle/v1/ServerEntityEvents.java +++ b/fabric-lifecycle-events-v1/src/main/java/net/fabricmc/fabric/api/event/lifecycle/v1/ServerEntityEvents.java @@ -63,6 +63,15 @@ private ServerEntityEvents() { } }); + /** + * Called when an entity is ticked by a ServerWorld. + */ + public static final Event ENTITY_TICK = EventFactory.createArrayBacked(ServerEntityEvents.Tick.class, callbacks -> (serverWorld, entity) -> { + for (ServerEntityEvents.Tick callback : callbacks) { + callback.onTick(serverWorld, entity); + } + }); + @FunctionalInterface public interface Load { void onLoad(Entity entity, ServerWorld world); @@ -77,4 +86,9 @@ public interface Unload { public interface EquipmentChange { void onChange(LivingEntity livingEntity, EquipmentSlot equipmentSlot, ItemStack previousStack, ItemStack currentStack); } + + @FunctionalInterface + public interface Tick { + void onTick(Entity entity, ServerWorld world); + } } diff --git a/fabric-lifecycle-events-v1/src/main/java/net/fabricmc/fabric/mixin/event/lifecycle/ServerWorldMixin.java b/fabric-lifecycle-events-v1/src/main/java/net/fabricmc/fabric/mixin/event/lifecycle/ServerWorldMixin.java index 454abf4bca..0ec88e43cb 100644 --- a/fabric-lifecycle-events-v1/src/main/java/net/fabricmc/fabric/mixin/event/lifecycle/ServerWorldMixin.java +++ b/fabric-lifecycle-events-v1/src/main/java/net/fabricmc/fabric/mixin/event/lifecycle/ServerWorldMixin.java @@ -19,17 +19,28 @@ import java.util.function.BooleanSupplier; import org.objectweb.asm.Opcodes; +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.entity.Entity; +import net.minecraft.server.MinecraftServer; import net.minecraft.server.world.ServerWorld; +import net.minecraft.world.chunk.WorldChunk; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerChunkEvents; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerEntityEvents; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents; @Mixin(ServerWorld.class) public abstract class ServerWorldMixin { + @Shadow + @Final + private MinecraftServer server; + // Make sure "insideBlockTick" is true before we call the start tick, so inject after it is set @Inject(method = "tick", at = @At(value = "FIELD", target = "Lnet/minecraft/server/world/ServerWorld;inBlockTick:Z", opcode = Opcodes.PUTFIELD, ordinal = 0, shift = At.Shift.AFTER)) private void startWorldTick(BooleanSupplier shouldKeepTicking, CallbackInfo ci) { @@ -40,4 +51,19 @@ private void startWorldTick(BooleanSupplier shouldKeepTicking, CallbackInfo ci) private void endWorldTick(BooleanSupplier shouldKeepTicking, CallbackInfo ci) { ServerTickEvents.END_WORLD_TICK.invoker().onEndTick((ServerWorld) (Object) this); } + + @Inject(method = "tickChunk", at = @At("RETURN")) + private void tick(WorldChunk chunk, int randomTickSpeed, CallbackInfo ci) { + ServerChunkEvents.CHUNK_TICK.invoker().onChunkTick((ServerWorld) (Object) this, chunk); + } + + @Inject(method = "tickEntity", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/Entity;tick()V", shift = At.Shift.AFTER)) + private void tick(Entity entity, CallbackInfo ci) { + ServerEntityEvents.ENTITY_TICK.invoker().onTick(entity, (ServerWorld) (Object) this); + } + + @Inject(method = "tickPassenger", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/Entity;tickRiding()V", shift = At.Shift.AFTER)) + private void tickRiding(Entity vehicle, Entity passenger, CallbackInfo ci) { + ServerEntityEvents.ENTITY_TICK.invoker().onTick(passenger, (ServerWorld) (Object) this); + } } diff --git a/fabric-lifecycle-events-v1/src/main/java/net/fabricmc/fabric/mixin/event/lifecycle/WorldChunkMixin.java b/fabric-lifecycle-events-v1/src/main/java/net/fabricmc/fabric/mixin/event/lifecycle/WorldChunkMixin.java new file mode 100644 index 0000000000..8d09cc81ab --- /dev/null +++ b/fabric-lifecycle-events-v1/src/main/java/net/fabricmc/fabric/mixin/event/lifecycle/WorldChunkMixin.java @@ -0,0 +1,45 @@ +/* + * 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 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.block.entity.BlockEntity; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.world.World; +import net.minecraft.world.chunk.WorldChunk; + +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerBlockEntityEvents; + +@Mixin(WorldChunk.class) +public abstract class WorldChunkMixin { + @Shadow + @Final + World world; + + @Inject(method = "updateTicker", at = @At("HEAD")) + private void getBlockEntityTicker(T blockEntity, CallbackInfo ci) { + if (this.world instanceof ServerWorld serverWorld) { + ServerBlockEntityEvents.BLOCK_ENTITY_TICK.invoker().onTick(blockEntity, serverWorld); + } + } +} 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 f186b59c2d..89191170fc 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 @@ -8,9 +8,10 @@ "LivingEntityMixin", "MinecraftServerMixin", "PlayerManagerMixin", - "ServerWorldServerEntityHandlerMixin", - "ServerWorldMixin", "ServerChunkLoadingManagerMixin", + "ServerWorldMixin", + "ServerWorldServerEntityHandlerMixin", + "WorldChunkMixin", "WorldMixin" ], "server": [ diff --git a/fabric-lifecycle-events-v1/src/testmod/java/net/fabricmc/fabric/test/event/lifecycle/ServerBlockEntityLifecycleTests.java b/fabric-lifecycle-events-v1/src/testmod/java/net/fabricmc/fabric/test/event/lifecycle/ServerBlockEntityLifecycleTests.java index 52091d4268..3f7f9cf42d 100644 --- a/fabric-lifecycle-events-v1/src/testmod/java/net/fabricmc/fabric/test/event/lifecycle/ServerBlockEntityLifecycleTests.java +++ b/fabric-lifecycle-events-v1/src/testmod/java/net/fabricmc/fabric/test/event/lifecycle/ServerBlockEntityLifecycleTests.java @@ -40,6 +40,12 @@ public final class ServerBlockEntityLifecycleTests implements ModInitializer { public void onInitialize() { final Logger logger = ServerLifecycleTests.LOGGER; + ServerBlockEntityEvents.BLOCK_ENTITY_TICK.register((blockEntity, world) -> { + if (PRINT_SERVER_BLOCKENTITY_MESSAGES) { + logger.info("[SERVER] TICKED " + Registries.BLOCK_ENTITY_TYPE.getId(blockEntity.getType()).toString() + " - BlockEntities: " + this.serverBlockEntities.size()); + } + }); + ServerBlockEntityEvents.BLOCK_ENTITY_LOAD.register((blockEntity, world) -> { this.serverBlockEntities.add(blockEntity); diff --git a/fabric-lifecycle-events-v1/src/testmod/java/net/fabricmc/fabric/test/event/lifecycle/ServerChunkLifecycleTests.java b/fabric-lifecycle-events-v1/src/testmod/java/net/fabricmc/fabric/test/event/lifecycle/ServerChunkLifecycleTests.java new file mode 100644 index 0000000000..d6050863a1 --- /dev/null +++ b/fabric-lifecycle-events-v1/src/testmod/java/net/fabricmc/fabric/test/event/lifecycle/ServerChunkLifecycleTests.java @@ -0,0 +1,63 @@ +/* + * 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.test.event.lifecycle; + +import java.util.ArrayList; +import java.util.List; + +import org.slf4j.Logger; + +import net.minecraft.world.chunk.WorldChunk; + +import net.fabricmc.api.ModInitializer; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerChunkEvents; + +/** + * Tests related to the lifecycle of chunks. + */ +public class ServerChunkLifecycleTests implements ModInitializer { + private static final boolean PRINT_SERVER_CHUNK_MESSAGES = System.getProperty("fabric-lifecycle-events-testmod.printServerChunkMessages") != null; + + private final List chunks = new ArrayList<>(); + + @Override + public void onInitialize() { + final Logger logger = ServerLifecycleTests.LOGGER; + + ServerChunkEvents.CHUNK_TICK.register((world, chunk) -> { + if (PRINT_SERVER_CHUNK_MESSAGES) { + logger.info("[SERVER] TICKING CHUNK AT x={} z={} - Tracking {} Chunks", chunk.getPos().x, chunk.getPos().z, this.chunks.size()); + } + }); + + ServerChunkEvents.CHUNK_LOAD.register((world, chunk) -> { + this.chunks.add(chunk); + + if (PRINT_SERVER_CHUNK_MESSAGES) { + logger.info("[SERVER] LOADED CHUNK AT x={} z={} - Tracking {} Chunks", chunk.getPos().x, chunk.getPos().z, this.chunks.size()); + } + }); + + ServerChunkEvents.CHUNK_UNLOAD.register((world, chunk) -> { + this.chunks.remove(chunk); + + if (PRINT_SERVER_CHUNK_MESSAGES) { + logger.info("[SERVER] UNLOADED CHUNK AT x={} z={} - Tracking {} Chunks", chunk.getPos().x, chunk.getPos().z, this.chunks.size()); + } + }); + } +} diff --git a/fabric-lifecycle-events-v1/src/testmod/java/net/fabricmc/fabric/test/event/lifecycle/ServerEntityLifecycleTests.java b/fabric-lifecycle-events-v1/src/testmod/java/net/fabricmc/fabric/test/event/lifecycle/ServerEntityLifecycleTests.java index b03dd7fef1..bf4353e361 100644 --- a/fabric-lifecycle-events-v1/src/testmod/java/net/fabricmc/fabric/test/event/lifecycle/ServerEntityLifecycleTests.java +++ b/fabric-lifecycle-events-v1/src/testmod/java/net/fabricmc/fabric/test/event/lifecycle/ServerEntityLifecycleTests.java @@ -23,6 +23,8 @@ import org.slf4j.Logger; import net.minecraft.entity.Entity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.Items; import net.minecraft.server.world.ServerWorld; import net.fabricmc.api.ModInitializer; @@ -42,11 +44,17 @@ public final class ServerEntityLifecycleTests implements ModInitializer { public void onInitialize() { final Logger logger = ServerLifecycleTests.LOGGER; + ServerEntityEvents.ENTITY_TICK.register((entity, world) -> { + if (PRINT_SERVER_ENTITY_MESSAGES && entity instanceof PlayerEntity player && player.getMainHandStack().isOf(Items.LILY_PAD) && player.isSneaking()) { + logger.info("[SERVER] TICKING {}.", player.getName().getString()); + } + }); + ServerEntityEvents.ENTITY_LOAD.register((entity, world) -> { this.serverEntities.add(entity); if (PRINT_SERVER_ENTITY_MESSAGES) { - logger.info("[SERVER] LOADED " + entity.toString() + " - Entities: " + this.serverEntities.size()); + logger.info("[SERVER] LOADED {} - Entities: {}", entity.toString(), this.serverEntities.size()); } }); @@ -54,7 +62,7 @@ public void onInitialize() { this.serverEntities.remove(entity); if (PRINT_SERVER_ENTITY_MESSAGES) { - logger.info("[SERVER] UNLOADED " + entity.toString() + " - Entities: " + this.serverEntities.size()); + logger.info("[SERVER] UNLOADED {} - Entities: {}", entity.toString(), this.serverEntities.size()); } }); @@ -72,7 +80,7 @@ public void onInitialize() { final int worldEntities = Iterables.size(world.iterateEntities()); if (PRINT_SERVER_ENTITY_MESSAGES) { - logger.info("[SERVER] Tracked Entities in " + world.getRegistryKey().toString() + " - " + worldEntities); + logger.info("[SERVER] Tracked Entities in {} - {}", world.getRegistryKey().toString(), worldEntities); } entities += worldEntities; @@ -92,7 +100,7 @@ public void onInitialize() { ServerLifecycleEvents.SERVER_STOPPED.register(server -> { logger.info("[SERVER] Disconnected. Tracking: " + this.serverEntities.size() + " entities"); - if (this.serverEntities.size() != 0) { + if (!this.serverEntities.isEmpty()) { logger.error("[SERVER] Mismatch in tracked entities, expected 0"); } }); diff --git a/fabric-lifecycle-events-v1/src/testmod/resources/fabric.mod.json b/fabric-lifecycle-events-v1/src/testmod/resources/fabric.mod.json index a6bdc95691..d5a3e74c71 100644 --- a/fabric-lifecycle-events-v1/src/testmod/resources/fabric.mod.json +++ b/fabric-lifecycle-events-v1/src/testmod/resources/fabric.mod.json @@ -12,6 +12,7 @@ "main": [ "net.fabricmc.fabric.test.event.lifecycle.CommonLifecycleTests", "net.fabricmc.fabric.test.event.lifecycle.ServerBlockEntityLifecycleTests", + "net.fabricmc.fabric.test.event.lifecycle.ServerChunkLifecycleTests", "net.fabricmc.fabric.test.event.lifecycle.ServerEntityLifecycleTests", "net.fabricmc.fabric.test.event.lifecycle.ServerLifecycleTests", "net.fabricmc.fabric.test.event.lifecycle.ServerTickTests",