From 5a2efd399e551b7f1bda91413c1c8c7dcbc08b2a Mon Sep 17 00:00:00 2001 From: i509VCB Date: Sat, 28 Nov 2020 13:47:47 -0600 Subject: [PATCH] Reimplement block entity (un)load events (#1177) * Reimplement block entity (un)load events This is pending testing to verify the tracking is reliable. * Forgot that null check for parity * Everything is implemented. Now for testing * Fix server block entity unload events * Bah indentations broke * Handle world chunks instead of the positions --- .../event/lifecycle/v1/ServerWorldEvents.java | 6 +- .../lifecycle/ClientLifecycleEventsImpl.java | 46 +++++++ .../event/lifecycle/LifecycleEventsImpl.java | 56 +++++++++ .../event/lifecycle/LoadedChunksCache.java | 38 ++++++ .../event/lifecycle/MinecraftServerMixin.java | 12 +- .../mixin/event/lifecycle/WorldMixin.java | 58 ++++----- .../client/ClientPlayNetworkHandlerMixin.java | 37 +++--- .../lifecycle/client/ClientWorldMixin.java | 26 ---- .../lifecycle/client/WorldChunkMixin.java | 112 ++++++++++++++++++ .../lifecycle/server/WorldChunkMixin.java | 102 ++++++++++++++++ .../fabric-lifecycle-events-v1.mixins.json | 8 +- .../src/main/resources/fabric.mod.json | 8 ++ .../ServerBlockEntityLifecycleTests.java | 25 +++- .../lifecycle/ServerEntityLifecycleTests.java | 6 +- .../event/lifecycle/ServerLifecycleTests.java | 2 +- .../lifecycle/ServerResourceReloadTests.java | 2 +- .../test/event/lifecycle/ServerTickTests.java | 4 +- .../ClientBlockEntityLifecycleTests.java | 30 +++-- .../client/ClientEntityLifecycleTests.java | 6 +- .../client/ClientLifecycleTests.java | 2 +- .../lifecycle/client/ClientTickTests.java | 4 +- 21 files changed, 472 insertions(+), 118 deletions(-) create mode 100644 fabric-lifecycle-events-v1/src/main/java/net/fabricmc/fabric/impl/event/lifecycle/ClientLifecycleEventsImpl.java create mode 100644 fabric-lifecycle-events-v1/src/main/java/net/fabricmc/fabric/impl/event/lifecycle/LifecycleEventsImpl.java create mode 100644 fabric-lifecycle-events-v1/src/main/java/net/fabricmc/fabric/impl/event/lifecycle/LoadedChunksCache.java create mode 100644 fabric-lifecycle-events-v1/src/main/java/net/fabricmc/fabric/mixin/event/lifecycle/client/WorldChunkMixin.java create mode 100644 fabric-lifecycle-events-v1/src/main/java/net/fabricmc/fabric/mixin/event/lifecycle/server/WorldChunkMixin.java diff --git a/fabric-lifecycle-events-v1/src/main/java/net/fabricmc/fabric/api/event/lifecycle/v1/ServerWorldEvents.java b/fabric-lifecycle-events-v1/src/main/java/net/fabricmc/fabric/api/event/lifecycle/v1/ServerWorldEvents.java index 29da25965..fb939f62e 100644 --- a/fabric-lifecycle-events-v1/src/main/java/net/fabricmc/fabric/api/event/lifecycle/v1/ServerWorldEvents.java +++ b/fabric-lifecycle-events-v1/src/main/java/net/fabricmc/fabric/api/event/lifecycle/v1/ServerWorldEvents.java @@ -25,9 +25,9 @@ import net.fabricmc.fabric.api.event.EventFactory; public final class ServerWorldEvents { /** - * Called when a world is loaded by a Minecraft server. + * Called just after a world is loaded by a Minecraft server. * - *

For example, this can be used to load world specific metadata or initialize a {@link PersistentState} on a server world. + *

This can be used to load world specific metadata or initialize a {@link PersistentState} on a server world. */ public static final Event LOAD = EventFactory.createArrayBacked(Load.class, callbacks -> (server, world) -> { for (Load callback : callbacks) { @@ -39,7 +39,7 @@ public final class ServerWorldEvents { * Called before a world is unloaded by a Minecraft server. * *

This typically occurs after a server has {@link ServerLifecycleEvents#SERVER_STOPPING started shutting down}. - * Mods which allow dynamic world (un)registration should use this event so mods can let go of world handles when a world is removed. + * Mods which allow dynamic world (un)registration should call this event so mods can let go of world handles when a world is removed. */ public static final Event UNLOAD = EventFactory.createArrayBacked(Unload.class, callbacks -> (server, world) -> { for (Unload callback : callbacks) { diff --git a/fabric-lifecycle-events-v1/src/main/java/net/fabricmc/fabric/impl/event/lifecycle/ClientLifecycleEventsImpl.java b/fabric-lifecycle-events-v1/src/main/java/net/fabricmc/fabric/impl/event/lifecycle/ClientLifecycleEventsImpl.java new file mode 100644 index 000000000..cfe8b04ad --- /dev/null +++ b/fabric-lifecycle-events-v1/src/main/java/net/fabricmc/fabric/impl/event/lifecycle/ClientLifecycleEventsImpl.java @@ -0,0 +1,46 @@ +/* + * 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.impl.event.lifecycle; + +import net.minecraft.block.entity.BlockEntity; + +import net.fabricmc.api.ClientModInitializer; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientBlockEntityEvents; +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientChunkEvents; + +@Environment(EnvType.CLIENT) +public final class ClientLifecycleEventsImpl implements ClientModInitializer { + @Override + public void onInitializeClient() { + // Part of impl for block entity events + ClientChunkEvents.CHUNK_LOAD.register((world, chunk) -> { + ((LoadedChunksCache) world).fabric_markLoaded(chunk); + }); + + ClientChunkEvents.CHUNK_UNLOAD.register((world, chunk) -> { + ((LoadedChunksCache) world).fabric_markUnloaded(chunk); + }); + + ClientChunkEvents.CHUNK_UNLOAD.register((world, chunk) -> { + for (BlockEntity blockEntity : chunk.getBlockEntities().values()) { + ClientBlockEntityEvents.BLOCK_ENTITY_UNLOAD.invoker().onUnload(blockEntity, world); + } + }); + } +} 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 new file mode 100644 index 000000000..ffa5c7005 --- /dev/null +++ b/fabric-lifecycle-events-v1/src/main/java/net/fabricmc/fabric/impl/event/lifecycle/LifecycleEventsImpl.java @@ -0,0 +1,56 @@ +/* + * 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.impl.event.lifecycle; + +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.world.chunk.WorldChunk; + +import net.fabricmc.api.ModInitializer; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerBlockEntityEvents; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerChunkEvents; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerWorldEvents; + +public final class LifecycleEventsImpl implements ModInitializer { + @Override + public void onInitialize() { + // Part of impl for block entity events + ServerChunkEvents.CHUNK_LOAD.register((world, chunk) -> { + ((LoadedChunksCache) world).fabric_markLoaded(chunk); + }); + + ServerChunkEvents.CHUNK_UNLOAD.register((world, chunk) -> { + ((LoadedChunksCache) world).fabric_markUnloaded(chunk); + }); + + // Fire block entity unload events. + // This handles the edge case where going through a portal will cause block entities to unload without warning. + ServerChunkEvents.CHUNK_UNLOAD.register((world, chunk) -> { + for (BlockEntity blockEntity : chunk.getBlockEntities().values()) { + ServerBlockEntityEvents.BLOCK_ENTITY_UNLOAD.invoker().onUnload(blockEntity, world); + } + }); + + // We use the world unload event so worlds that are dynamically hot(un)loaded get block entity unload events fired when shut down. + ServerWorldEvents.UNLOAD.register((server, world) -> { + for (WorldChunk chunk : ((LoadedChunksCache) world).fabric_getLoadedChunks()) { + for (BlockEntity blockEntity : chunk.getBlockEntities().values()) { + ServerBlockEntityEvents.BLOCK_ENTITY_UNLOAD.invoker().onUnload(blockEntity, world); + } + } + }); + } +} diff --git a/fabric-lifecycle-events-v1/src/main/java/net/fabricmc/fabric/impl/event/lifecycle/LoadedChunksCache.java b/fabric-lifecycle-events-v1/src/main/java/net/fabricmc/fabric/impl/event/lifecycle/LoadedChunksCache.java new file mode 100644 index 000000000..79edfc4d9 --- /dev/null +++ b/fabric-lifecycle-events-v1/src/main/java/net/fabricmc/fabric/impl/event/lifecycle/LoadedChunksCache.java @@ -0,0 +1,38 @@ +/* + * 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.impl.event.lifecycle; + +import java.util.Set; + +import net.minecraft.world.chunk.WorldChunk; + +/** + * A simple marker interface which holds references to chunks which block entities may be loaded or unloaded from. + */ +public interface LoadedChunksCache { + Set fabric_getLoadedChunks(); + + /** + * Marks a chunk as loaded in a world. + */ + void fabric_markLoaded(WorldChunk chunk); + + /** + * Marks a chunk as unloaded in a world. + */ + void fabric_markUnloaded(WorldChunk chunk); +} diff --git a/fabric-lifecycle-events-v1/src/main/java/net/fabricmc/fabric/mixin/event/lifecycle/MinecraftServerMixin.java b/fabric-lifecycle-events-v1/src/main/java/net/fabricmc/fabric/mixin/event/lifecycle/MinecraftServerMixin.java index 7ca4bf886..229c20cb8 100644 --- a/fabric-lifecycle-events-v1/src/main/java/net/fabricmc/fabric/mixin/event/lifecycle/MinecraftServerMixin.java +++ b/fabric-lifecycle-events-v1/src/main/java/net/fabricmc/fabric/mixin/event/lifecycle/MinecraftServerMixin.java @@ -74,16 +74,6 @@ public abstract class MinecraftServerMixin { ServerTickEvents.END_SERVER_TICK.invoker().onEndTick((MinecraftServer) (Object) this); } - /** - * When a world is closed, it means the world will be unloaded. - */ - /*@Inject(at = @At(value = "INVOKE", target = "Lnet/minecraft/server/world/ServerWorld;close()V"), method = "shutdown", locals = LocalCapture.CAPTURE_FAILEXCEPTION) - private void closeWorld(CallbackInfo ci, Iterator worlds, ServerWorld serverWorld) { - for (BlockEntity blockEntity : serverWorld.blockEntities) { - ServerBlockEntityEvents.BLOCK_ENTITY_UNLOAD.invoker().onUnload(blockEntity, serverWorld); - } - }*/ - // The locals you have to manage for an inject are insane. And do it twice. A redirect is much cleaner. // Here is what it looks like with an inject: https://gist.github.com/i509VCB/f80077cc536eb4dba62b794eba5611c1 @Redirect(method = "createWorlds", at = @At(value = "INVOKE", target = "Ljava/util/Map;put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;")) @@ -94,7 +84,7 @@ public abstract class MinecraftServerMixin { return result; } - @Inject(method = "shutdown", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/world/ServerWorld;close()V", shift = At.Shift.AFTER), locals = LocalCapture.CAPTURE_FAILEXCEPTION) + @Inject(method = "shutdown", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/world/ServerWorld;close()V"), locals = LocalCapture.CAPTURE_FAILEXCEPTION) private void onUnloadWorldAtShutdown(CallbackInfo ci, Iterator worlds, ServerWorld world) { ServerWorldEvents.UNLOAD.invoker().onWorldUnload((MinecraftServer) (Object) this, world); } diff --git a/fabric-lifecycle-events-v1/src/main/java/net/fabricmc/fabric/mixin/event/lifecycle/WorldMixin.java b/fabric-lifecycle-events-v1/src/main/java/net/fabricmc/fabric/mixin/event/lifecycle/WorldMixin.java index a69a2d477..3ff421f97 100644 --- a/fabric-lifecycle-events-v1/src/main/java/net/fabricmc/fabric/mixin/event/lifecycle/WorldMixin.java +++ b/fabric-lifecycle-events-v1/src/main/java/net/fabricmc/fabric/mixin/event/lifecycle/WorldMixin.java @@ -16,8 +16,12 @@ package net.fabricmc.fabric.mixin.event.lifecycle; +import java.util.HashSet; +import java.util.Set; + import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @@ -25,50 +29,21 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import net.minecraft.server.world.ServerWorld; import net.minecraft.util.profiler.Profiler; import net.minecraft.world.World; +import net.minecraft.world.chunk.WorldChunk; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents; +import net.fabricmc.fabric.impl.event.lifecycle.LoadedChunksCache; @Mixin(World.class) -public abstract class WorldMixin { +public abstract class WorldMixin implements LoadedChunksCache { @Shadow public abstract boolean isClient(); @Shadow public abstract Profiler getProfiler(); - /*@Inject(method = "addBlockEntity", at = @At("TAIL")) - protected void onLoadBlockEntity(BlockEntity blockEntity, CallbackInfoReturnable cir) { - if (!this.isClient()) { // Only fire this event if we are a server world - ServerBlockEntityEvents.BLOCK_ENTITY_LOAD.invoker().onLoad(blockEntity, (ServerWorld) (Object) this); - } - } - - // Mojang what hell, why do you need three ways to unload block entities - @Inject(method = "removeBlockEntity", at = @At(value = "INVOKE", target = "Ljava/util/List;remove(Ljava/lang/Object;)Z", ordinal = 1), locals = LocalCapture.CAPTURE_FAILEXCEPTION) - protected void onUnloadBlockEntity(BlockPos pos, CallbackInfo ci, BlockEntity blockEntity) { - if (!this.isClient()) { // Only fire this event if we are a server world - ServerBlockEntityEvents.BLOCK_ENTITY_UNLOAD.invoker().onUnload(blockEntity, (ServerWorld) (Object) this); - } - } - - @Inject(method = "tickBlockEntities", at = @At(value = "INVOKE", target = "Ljava/util/List;remove(Ljava/lang/Object;)Z"), slice = @Slice(from = @At(value = "INVOKE", target = "Lnet/minecraft/util/profiler/Profiler;pop()V"), to = @At(value = "INVOKE", target = "Lnet/minecraft/world/chunk/WorldChunk;removeBlockEntity(Lnet/minecraft/util/math/BlockPos;)V")), locals = LocalCapture.CAPTURE_FAILEXCEPTION) - protected void onRemoveBlockEntity(CallbackInfo ci, Profiler profiler, Iterator iterator, BlockEntity blockEntity) { - if (!this.isClient()) { - ServerBlockEntityEvents.BLOCK_ENTITY_UNLOAD.invoker().onUnload(blockEntity, (ServerWorld) (Object) this); - } - } - - @Redirect(method = "tickBlockEntities", at = @At(value = "INVOKE", target = "Ljava/util/List;removeAll(Ljava/util/Collection;)Z", ordinal = 1)) - protected boolean onPurgeRemovedBlockEntities(List blockEntityList, Collection removals) { - if (!this.isClient()) { - for (BlockEntity removal : removals) { - ServerBlockEntityEvents.BLOCK_ENTITY_UNLOAD.invoker().onUnload(removal, (ServerWorld) (Object) this); - } - } - - // Mimic vanilla logic - return blockEntityList.removeAll(removals); - }*/ + @Unique + private final Set loadedChunks = new HashSet<>(); @Inject(at = @At("RETURN"), method = "tickBlockEntities") protected void tickWorldAfterBlockEntities(CallbackInfo ci) { @@ -76,4 +51,19 @@ public abstract class WorldMixin { ServerTickEvents.END_WORLD_TICK.invoker().onEndTick((ServerWorld) (Object) this); } } + + @Override + public Set fabric_getLoadedChunks() { + return this.loadedChunks; + } + + @Override + public void fabric_markLoaded(WorldChunk chunk) { + this.loadedChunks.add(chunk); + } + + @Override + public void fabric_markUnloaded(WorldChunk chunk) { + this.loadedChunks.remove(chunk); + } } diff --git a/fabric-lifecycle-events-v1/src/main/java/net/fabricmc/fabric/mixin/event/lifecycle/client/ClientPlayNetworkHandlerMixin.java b/fabric-lifecycle-events-v1/src/main/java/net/fabricmc/fabric/mixin/event/lifecycle/client/ClientPlayNetworkHandlerMixin.java index 9ddf04e17..c9b2a97f4 100644 --- a/fabric-lifecycle-events-v1/src/main/java/net/fabricmc/fabric/mixin/event/lifecycle/client/ClientPlayNetworkHandlerMixin.java +++ b/fabric-lifecycle-events-v1/src/main/java/net/fabricmc/fabric/mixin/event/lifecycle/client/ClientPlayNetworkHandlerMixin.java @@ -22,19 +22,23 @@ 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.client.network.ClientPlayNetworkHandler; import net.minecraft.client.world.ClientWorld; import net.minecraft.entity.Entity; import net.minecraft.network.packet.s2c.play.GameJoinS2CPacket; import net.minecraft.network.packet.s2c.play.PlayerRespawnS2CPacket; +import net.minecraft.world.chunk.WorldChunk; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientBlockEntityEvents; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientEntityEvents; +import net.fabricmc.fabric.impl.event.lifecycle.LoadedChunksCache; @Environment(EnvType.CLIENT) @Mixin(ClientPlayNetworkHandler.class) -public abstract class ClientPlayNetworkHandlerMixin { +abstract class ClientPlayNetworkHandlerMixin { @Shadow private ClientWorld world; @@ -42,14 +46,15 @@ public abstract class ClientPlayNetworkHandlerMixin { private void onPlayerRespawn(PlayerRespawnS2CPacket packet, CallbackInfo ci) { // If a world already exists, we need to unload all (block)entities in the world. if (this.world != null) { - for (Entity entity : world.getEntities()) { + for (Entity entity : this.world.getEntities()) { ClientEntityEvents.ENTITY_UNLOAD.invoker().onUnload(entity, this.world); } - /*for (BlockEntity blockEntity : world.blockEntities) { - ClientBlockEntityEvents.BLOCK_ENTITY_UNLOAD.invoker().onUnload(blockEntity, this.world); - // No need to clear the `tickingBlockEntities` list since it will be null in just an instant - }*/ + for (WorldChunk chunk : ((LoadedChunksCache) this.world).fabric_getLoadedChunks()) { + for (BlockEntity blockEntity : chunk.getBlockEntities().values()) { + ClientBlockEntityEvents.BLOCK_ENTITY_UNLOAD.invoker().onUnload(blockEntity, this.world); + } + } } } @@ -67,10 +72,11 @@ public abstract class ClientPlayNetworkHandlerMixin { ClientEntityEvents.ENTITY_UNLOAD.invoker().onUnload(entity, this.world); } - /*for (BlockEntity blockEntity : world.blockEntities) { - ClientBlockEntityEvents.BLOCK_ENTITY_UNLOAD.invoker().onUnload(blockEntity, this.world); - // No need to clear the `tickingBlockEntities` list since it will be null in just an instant - }*/ + for (WorldChunk chunk : ((LoadedChunksCache) this.world).fabric_getLoadedChunks()) { + for (BlockEntity blockEntity : chunk.getBlockEntities().values()) { + ClientBlockEntityEvents.BLOCK_ENTITY_UNLOAD.invoker().onUnload(blockEntity, this.world); + } + } } } @@ -79,14 +85,15 @@ public abstract class ClientPlayNetworkHandlerMixin { private void onClearWorld(CallbackInfo ci) { // If a world already exists, we need to unload all (block)entities in the world. if (this.world != null) { - for (Entity entity : world.getEntities()) { + for (Entity entity : this.world.getEntities()) { ClientEntityEvents.ENTITY_UNLOAD.invoker().onUnload(entity, this.world); } - /*for (BlockEntity blockEntity : world.blockEntities) { - ClientBlockEntityEvents.BLOCK_ENTITY_UNLOAD.invoker().onUnload(blockEntity, this.world); - // No need to clear the `tickingBlockEntities` list since it will be null in just an instant - }*/ + for (WorldChunk chunk : ((LoadedChunksCache) this.world).fabric_getLoadedChunks()) { + for (BlockEntity blockEntity : chunk.getBlockEntities().values()) { + ClientBlockEntityEvents.BLOCK_ENTITY_UNLOAD.invoker().onUnload(blockEntity, this.world); + } + } } } } diff --git a/fabric-lifecycle-events-v1/src/main/java/net/fabricmc/fabric/mixin/event/lifecycle/client/ClientWorldMixin.java b/fabric-lifecycle-events-v1/src/main/java/net/fabricmc/fabric/mixin/event/lifecycle/client/ClientWorldMixin.java index a155096d1..c9fa08247 100644 --- a/fabric-lifecycle-events-v1/src/main/java/net/fabricmc/fabric/mixin/event/lifecycle/client/ClientWorldMixin.java +++ b/fabric-lifecycle-events-v1/src/main/java/net/fabricmc/fabric/mixin/event/lifecycle/client/ClientWorldMixin.java @@ -31,32 +31,6 @@ import net.fabricmc.fabric.mixin.event.lifecycle.WorldMixin; @Environment(EnvType.CLIENT) @Mixin(ClientWorld.class) public abstract class ClientWorldMixin extends WorldMixin { - // We override our injection on the clientworld so only the client's block entity invocations will run - /*@Override - protected void onLoadBlockEntity(BlockEntity blockEntity, CallbackInfoReturnable cir) { - ClientBlockEntityEvents.BLOCK_ENTITY_LOAD.invoker().onLoad(blockEntity, (ClientWorld) (Object) this); - } - - // We override our injection on the clientworld so only the client's block entity invocations will run - @Override - protected void onUnloadBlockEntity(BlockPos pos, CallbackInfo ci, BlockEntity blockEntity) { - ClientBlockEntityEvents.BLOCK_ENTITY_UNLOAD.invoker().onUnload(blockEntity, (ClientWorld) (Object) this); - } - - @Override - protected void onRemoveBlockEntity(CallbackInfo ci, Profiler profiler, Iterator iterator, BlockEntity blockEntity) { - ClientBlockEntityEvents.BLOCK_ENTITY_UNLOAD.invoker().onUnload(blockEntity, (ClientWorld) (Object) this); - } - - @Override - protected boolean onPurgeRemovedBlockEntities(List blockEntityList, Collection removals) { - for (BlockEntity removal : removals) { - ClientBlockEntityEvents.BLOCK_ENTITY_UNLOAD.invoker().onUnload(removal, (ClientWorld) (Object) this); - } - - return super.onPurgeRemovedBlockEntities(blockEntityList, removals); // Call super - }*/ - // We override our injection on the clientworld so only the client world's tick invocations will run @Override protected void tickWorldAfterBlockEntities(CallbackInfo ci) { diff --git a/fabric-lifecycle-events-v1/src/main/java/net/fabricmc/fabric/mixin/event/lifecycle/client/WorldChunkMixin.java b/fabric-lifecycle-events-v1/src/main/java/net/fabricmc/fabric/mixin/event/lifecycle/client/WorldChunkMixin.java new file mode 100644 index 000000000..19a44719c --- /dev/null +++ b/fabric-lifecycle-events-v1/src/main/java/net/fabricmc/fabric/mixin/event/lifecycle/client/WorldChunkMixin.java @@ -0,0 +1,112 @@ +/* + * 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.client; + +import java.util.Map; + +import org.jetbrains.annotations.Nullable; +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.Redirect; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.LocalCapture; + +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.client.world.ClientWorld; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; +import net.minecraft.world.chunk.WorldChunk; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientBlockEntityEvents; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerBlockEntityEvents; + +@Environment(EnvType.CLIENT) +@Mixin(WorldChunk.class) +abstract class WorldChunkMixin { + @Shadow + public abstract World getWorld(); + + /* + * @Inject(method = "setBlockEntity", at = @At(value = "CONSTANT", args = "nullValue=true"), locals = LocalCapture.CAPTURE_FAILEXCEPTION) + * + * i509VCB: Yes this is very brittle. + * Sadly mixin does not want to cooperate with the Inject annotation commented out above. + * Our goal is to place the inject JUST after the possibly removed block entity is stored onto the stack so we can use local capture: + * + * INVOKEVIRTUAL net/minecraft/util/math/BlockPos.toImmutable ()Lnet/minecraft/util/math/BlockPos; + * ALOAD 1 + * INVOKEINTERFACE java/util/Map.put (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; (itf) + * CHECKCAST net/minecraft/block/entity/BlockEntity + * ASTORE 3 + * <======== HERE + * L6 + */ + @Inject(method = "setBlockEntity", at = @At(value = "INVOKE", target = "Ljava/util/Map;put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", shift = At.Shift.BY, by = 3), locals = LocalCapture.CAPTURE_FAILEXCEPTION) + private void onLoadBlockEntity(BlockEntity blockEntity, CallbackInfo ci, BlockPos blockPos, @Nullable BlockEntity removedBlockEntity) { + // Only fire the load event if the block entity has actually changed + if (blockEntity != null && blockEntity != removedBlockEntity) { + if (this.getWorld() instanceof ServerWorld) { + ServerBlockEntityEvents.BLOCK_ENTITY_LOAD.invoker().onLoad(blockEntity, (ServerWorld) this.getWorld()); + } else if (this.getWorld() instanceof ClientWorld) { + ClientBlockEntityEvents.BLOCK_ENTITY_LOAD.invoker().onLoad(blockEntity, (ClientWorld) this.getWorld()); + } + } + } + + @Inject(method = "setBlockEntity", at = @At(value = "INVOKE", target = "Lnet/minecraft/block/entity/BlockEntity;markRemoved()V", shift = At.Shift.AFTER), locals = LocalCapture.CAPTURE_FAILEXCEPTION) + private void onRemoveBlockEntity(BlockEntity blockEntity, CallbackInfo info, BlockPos blockPos, @Nullable BlockEntity removedBlockEntity) { + if (removedBlockEntity != null) { + if (this.getWorld() instanceof ServerWorld) { + ServerBlockEntityEvents.BLOCK_ENTITY_UNLOAD.invoker().onUnload(removedBlockEntity, (ServerWorld) this.getWorld()); + } else if (this.getWorld() instanceof ClientWorld) { + ClientBlockEntityEvents.BLOCK_ENTITY_UNLOAD.invoker().onUnload(removedBlockEntity, (ClientWorld) this.getWorld()); + } + } + } + + @Redirect(method = "getBlockEntity(Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/world/chunk/WorldChunk$CreationType;)Lnet/minecraft/block/entity/BlockEntity;", at = @At(value = "INVOKE", target = "Ljava/util/Map;remove(Ljava/lang/Object;)Ljava/lang/Object;")) + private Object onRemoveBlockEntity(Map map, K key) { + @Nullable + final V removed = map.remove(key); + + if (removed != null) { + if (this.getWorld() instanceof ServerWorld) { + ServerBlockEntityEvents.BLOCK_ENTITY_UNLOAD.invoker().onUnload((BlockEntity) removed, (ServerWorld) this.getWorld()); + } else if (this.getWorld() instanceof ClientWorld) { + ClientBlockEntityEvents.BLOCK_ENTITY_UNLOAD.invoker().onUnload((BlockEntity) removed, (ClientWorld) this.getWorld()); + } + } + + return removed; + } + + @Inject(method = "removeBlockEntity", at = @At(value = "INVOKE", target = "Lnet/minecraft/block/entity/BlockEntity;markRemoved()V"), locals = LocalCapture.CAPTURE_FAILEXCEPTION) + private void onRemoveBlockEntity(BlockPos pos, CallbackInfo ci, @Nullable BlockEntity removed) { + if (removed != null) { + if (this.getWorld() instanceof ServerWorld) { + ServerBlockEntityEvents.BLOCK_ENTITY_UNLOAD.invoker().onUnload(removed, (ServerWorld) this.getWorld()); + } else if (this.getWorld() instanceof ClientWorld) { + ClientBlockEntityEvents.BLOCK_ENTITY_UNLOAD.invoker().onUnload(removed, (ClientWorld) this.getWorld()); + } + } + } +} diff --git a/fabric-lifecycle-events-v1/src/main/java/net/fabricmc/fabric/mixin/event/lifecycle/server/WorldChunkMixin.java b/fabric-lifecycle-events-v1/src/main/java/net/fabricmc/fabric/mixin/event/lifecycle/server/WorldChunkMixin.java new file mode 100644 index 000000000..fbb1237be --- /dev/null +++ b/fabric-lifecycle-events-v1/src/main/java/net/fabricmc/fabric/mixin/event/lifecycle/server/WorldChunkMixin.java @@ -0,0 +1,102 @@ +/* + * 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.server; + +import java.util.Map; + +import org.jetbrains.annotations.Nullable; +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.Redirect; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.LocalCapture; + +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; +import net.minecraft.world.chunk.WorldChunk; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerBlockEntityEvents; + +/** + * This is a server only mixin for good reason: + * Since all block entity tracking is now on the world chunk, we inject into WorldChunk. + * In order to prevent client logic from being loaded due to the mixin, we have a mixin for the client and this one for the server. + */ +@Environment(EnvType.SERVER) +@Mixin(WorldChunk.class) +abstract class WorldChunkMixin { + @Shadow + public abstract World getWorld(); + + /* + * @Inject(method = "setBlockEntity", at = @At(value = "CONSTANT", args = "nullValue=true"), locals = LocalCapture.CAPTURE_FAILEXCEPTION) + * + * i509VCB: Yes this is very brittle. + * Sadly mixin does not want to cooperate with the Inject annotation commented out above. + * Our goal is to place the inject JUST after the possibly removed block entity is stored onto the stack so we can use local capture: + * + * INVOKEVIRTUAL net/minecraft/util/math/BlockPos.toImmutable ()Lnet/minecraft/util/math/BlockPos; + * ALOAD 1 + * INVOKEINTERFACE java/util/Map.put (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; (itf) + * CHECKCAST net/minecraft/block/entity/BlockEntity + * ASTORE 3 + * <======== HERE + * L6 + */ + @Inject(method = "setBlockEntity", at = @At(value = "INVOKE", target = "Ljava/util/Map;put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", shift = At.Shift.BY, by = 3), locals = LocalCapture.CAPTURE_FAILEXCEPTION) + private void onLoadBlockEntity(BlockEntity blockEntity, CallbackInfo ci, BlockPos blockPos, @Nullable BlockEntity removedBlockEntity) { + // Only fire the load event if the block entity has actually changed + if (blockEntity != null && blockEntity != removedBlockEntity) { + if (this.getWorld() instanceof ServerWorld) { + ServerBlockEntityEvents.BLOCK_ENTITY_LOAD.invoker().onLoad(blockEntity, (ServerWorld) this.getWorld()); + } + } + } + + @Inject(method = "setBlockEntity", at = @At(value = "INVOKE", target = "Lnet/minecraft/block/entity/BlockEntity;markRemoved()V", shift = At.Shift.AFTER), locals = LocalCapture.CAPTURE_FAILEXCEPTION) + private void onRemoveBlockEntity(BlockEntity blockEntity, CallbackInfo info, BlockPos blockPos, @Nullable BlockEntity removedBlockEntity) { + if (removedBlockEntity != null) { + if (this.getWorld() instanceof ServerWorld) { + ServerBlockEntityEvents.BLOCK_ENTITY_UNLOAD.invoker().onUnload(removedBlockEntity, (ServerWorld) this.getWorld()); + } + } + } + + @Redirect(method = "getBlockEntity(Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/world/chunk/WorldChunk$CreationType;)Lnet/minecraft/block/entity/BlockEntity;", at = @At(value = "INVOKE", target = "Ljava/util/Map;remove(Ljava/lang/Object;)Ljava/lang/Object;")) + private Object onRemoveBlockEntity(Map map, K key) { + @Nullable final V removed = map.remove(key); + + if (removed != null && this.getWorld() instanceof ServerWorld) { + ServerBlockEntityEvents.BLOCK_ENTITY_UNLOAD.invoker().onUnload((BlockEntity) removed, (ServerWorld) this.getWorld()); + } + + return removed; + } + + @Inject(method = "removeBlockEntity", at = @At(value = "INVOKE", target = "Lnet/minecraft/block/entity/BlockEntity;markRemoved()V"), locals = LocalCapture.CAPTURE_FAILEXCEPTION) + private void onRemoveBlockEntity(BlockPos pos, CallbackInfo ci, @Nullable BlockEntity removed) { + if (removed != null && this.getWorld() instanceof ServerWorld) { + ServerBlockEntityEvents.BLOCK_ENTITY_UNLOAD.invoker().onUnload(removed, (ServerWorld) this.getWorld()); + } + } +} 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 ffc31a4f9..f51041c23 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 @@ -12,9 +12,13 @@ "client": [ "client.ClientChunkManagerMixin", "client.ClientPlayNetworkHandlerMixin", - "client.ClientWorldMixin", "client.ClientWorldEntityLoaderMixin", - "client.MinecraftClientMixin" + "client.ClientWorldMixin", + "client.MinecraftClientMixin", + "client.WorldChunkMixin" + ], + "server": [ + "server.WorldChunkMixin" ], "injectors": { "defaultRequire": 1 diff --git a/fabric-lifecycle-events-v1/src/main/resources/fabric.mod.json b/fabric-lifecycle-events-v1/src/main/resources/fabric.mod.json index f0c3745e2..724aa33a8 100644 --- a/fabric-lifecycle-events-v1/src/main/resources/fabric.mod.json +++ b/fabric-lifecycle-events-v1/src/main/resources/fabric.mod.json @@ -15,6 +15,14 @@ "authors": [ "FabricMC" ], + "entrypoints": { + "main": [ + "net.fabricmc.fabric.impl.event.lifecycle.LifecycleEventsImpl" + ], + "client": [ + "net.fabricmc.fabric.impl.event.lifecycle.ClientLifecycleEventsImpl" + ] + }, "mixins": [ "fabric-lifecycle-events-v1.mixins.json" ], 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 3da0ba341..431ba778e 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 @@ -19,17 +19,26 @@ package net.fabricmc.fabric.test.event.lifecycle; import java.util.ArrayList; import java.util.List; +import org.apache.logging.log4j.Logger; + import net.minecraft.block.entity.BlockEntity; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.util.registry.Registry; +import net.minecraft.world.chunk.WorldChunk; import net.fabricmc.api.ModInitializer; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerBlockEntityEvents; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents; +import net.fabricmc.fabric.impl.event.lifecycle.LoadedChunksCache; -public class ServerBlockEntityLifecycleTests implements ModInitializer { - private static boolean PRINT_SERVER_BLOCKENTITY_MESSAGES = System.getProperty("fabric-lifecycle-events-testmod.printServerBlockEntityMessages") != null; - private List serverBlockEntities = new ArrayList<>(); +public final class ServerBlockEntityLifecycleTests implements ModInitializer { + private static final boolean PRINT_SERVER_BLOCKENTITY_MESSAGES = System.getProperty("fabric-lifecycle-events-testmod.printServerBlockEntityMessages") != null; + private final List serverBlockEntities = new ArrayList<>(); @Override public void onInitialize() { - /*final Logger logger = ServerLifecycleTests.LOGGER; + final Logger logger = ServerLifecycleTests.LOGGER; ServerBlockEntityEvents.BLOCK_ENTITY_LOAD.register((blockEntity, world) -> { this.serverBlockEntities.add(blockEntity); @@ -56,7 +65,11 @@ public class ServerBlockEntityLifecycleTests implements ModInitializer { } for (ServerWorld world : minecraftServer.getWorlds()) { - int worldEntities = world.blockEntities.size(); + int worldEntities = 0; + + for (WorldChunk chunk : ((LoadedChunksCache) world).fabric_getLoadedChunks()) { + worldEntities += chunk.getBlockEntities().size(); + } if (PRINT_SERVER_BLOCKENTITY_MESSAGES) { logger.info("[SERVER] Tracked BlockEntities in " + world.getRegistryKey().toString() + " - " + worldEntities); @@ -82,6 +95,6 @@ public class ServerBlockEntityLifecycleTests implements ModInitializer { if (this.serverBlockEntities.size() != 0) { logger.error("[SERVER] Mismatch in tracked blockentities, expected 0"); } - });*/ + }); } } 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 02045f985..a4f6adefd 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 @@ -29,9 +29,9 @@ import net.fabricmc.fabric.api.event.lifecycle.v1.ServerEntityEvents; /** * Tests related to the lifecycle of entities. */ -public class ServerEntityLifecycleTests implements ModInitializer { - private static boolean PRINT_SERVER_ENTITY_MESSAGES = System.getProperty("fabric-lifecycle-events-testmod.printServerEntityMessages") != null; - private List serverEntities = new ArrayList<>(); +public final class ServerEntityLifecycleTests implements ModInitializer { + private static final boolean PRINT_SERVER_ENTITY_MESSAGES = System.getProperty("fabric-lifecycle-events-testmod.printServerEntityMessages") != null; + private final List serverEntities = new ArrayList<>(); @Override public void onInitialize() { diff --git a/fabric-lifecycle-events-v1/src/testmod/java/net/fabricmc/fabric/test/event/lifecycle/ServerLifecycleTests.java b/fabric-lifecycle-events-v1/src/testmod/java/net/fabricmc/fabric/test/event/lifecycle/ServerLifecycleTests.java index d773930e7..3440928dd 100644 --- a/fabric-lifecycle-events-v1/src/testmod/java/net/fabricmc/fabric/test/event/lifecycle/ServerLifecycleTests.java +++ b/fabric-lifecycle-events-v1/src/testmod/java/net/fabricmc/fabric/test/event/lifecycle/ServerLifecycleTests.java @@ -26,7 +26,7 @@ import net.fabricmc.fabric.api.event.lifecycle.v1.ServerWorldEvents; /** * Tests related to the lifecycle of a server. */ -public class ServerLifecycleTests implements ModInitializer { +public final class ServerLifecycleTests implements ModInitializer { public static final Logger LOGGER = LogManager.getLogger("LifecycleEventsTest"); @Override diff --git a/fabric-lifecycle-events-v1/src/testmod/java/net/fabricmc/fabric/test/event/lifecycle/ServerResourceReloadTests.java b/fabric-lifecycle-events-v1/src/testmod/java/net/fabricmc/fabric/test/event/lifecycle/ServerResourceReloadTests.java index c7537ba45..149b10d9d 100644 --- a/fabric-lifecycle-events-v1/src/testmod/java/net/fabricmc/fabric/test/event/lifecycle/ServerResourceReloadTests.java +++ b/fabric-lifecycle-events-v1/src/testmod/java/net/fabricmc/fabric/test/event/lifecycle/ServerResourceReloadTests.java @@ -22,7 +22,7 @@ import org.apache.logging.log4j.Logger; import net.fabricmc.api.ModInitializer; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; -public class ServerResourceReloadTests implements ModInitializer { +public final class ServerResourceReloadTests implements ModInitializer { public static final Logger LOGGER = LogManager.getLogger("LifecycleEventsTest"); @Override diff --git a/fabric-lifecycle-events-v1/src/testmod/java/net/fabricmc/fabric/test/event/lifecycle/ServerTickTests.java b/fabric-lifecycle-events-v1/src/testmod/java/net/fabricmc/fabric/test/event/lifecycle/ServerTickTests.java index a0ca7a3f9..19e714655 100644 --- a/fabric-lifecycle-events-v1/src/testmod/java/net/fabricmc/fabric/test/event/lifecycle/ServerTickTests.java +++ b/fabric-lifecycle-events-v1/src/testmod/java/net/fabricmc/fabric/test/event/lifecycle/ServerTickTests.java @@ -28,8 +28,8 @@ import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents; /** * Test related to ticking events on the server. */ -public class ServerTickTests implements ModInitializer { - private Map, Integer> tickTracker = new HashMap<>(); +public final class ServerTickTests implements ModInitializer { + private final Map, Integer> tickTracker = new HashMap<>(); @Override public void onInitialize() { diff --git a/fabric-lifecycle-events-v1/src/testmod/java/net/fabricmc/fabric/test/event/lifecycle/client/ClientBlockEntityLifecycleTests.java b/fabric-lifecycle-events-v1/src/testmod/java/net/fabricmc/fabric/test/event/lifecycle/client/ClientBlockEntityLifecycleTests.java index bf694ff23..99e2f09ac 100644 --- a/fabric-lifecycle-events-v1/src/testmod/java/net/fabricmc/fabric/test/event/lifecycle/client/ClientBlockEntityLifecycleTests.java +++ b/fabric-lifecycle-events-v1/src/testmod/java/net/fabricmc/fabric/test/event/lifecycle/client/ClientBlockEntityLifecycleTests.java @@ -19,18 +19,27 @@ package net.fabricmc.fabric.test.event.lifecycle.client; import java.util.ArrayList; import java.util.List; +import org.apache.logging.log4j.Logger; + import net.minecraft.block.entity.BlockEntity; +import net.minecraft.util.registry.Registry; +import net.minecraft.world.chunk.WorldChunk; import net.fabricmc.api.ClientModInitializer; +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientBlockEntityEvents; +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; +import net.fabricmc.fabric.impl.event.lifecycle.LoadedChunksCache; +import net.fabricmc.fabric.test.event.lifecycle.ServerLifecycleTests; -public class ClientBlockEntityLifecycleTests implements ClientModInitializer { - private static boolean PRINT_CLIENT_BLOCKENTITY_MESSAGES = System.getProperty("fabric-lifecycle-events-testmod.printClientBlockEntityMessages") != null; - private List clientBlockEntities = new ArrayList<>(); +public final class ClientBlockEntityLifecycleTests implements ClientModInitializer { + private static final boolean PRINT_CLIENT_BLOCKENTITY_MESSAGES = System.getProperty("fabric-lifecycle-events-testmod.printClientBlockEntityMessages") != null; + private final List clientBlockEntities = new ArrayList<>(); private int clientTicks; @Override public void onInitializeClient() { - /*final Logger logger = ServerLifecycleTests.LOGGER; + final Logger logger = ServerLifecycleTests.LOGGER; ClientBlockEntityEvents.BLOCK_ENTITY_LOAD.register((blockEntity, world) -> { this.clientBlockEntities.add(blockEntity); @@ -50,11 +59,16 @@ public class ClientBlockEntityLifecycleTests implements ClientModInitializer { ClientTickEvents.END_CLIENT_TICK.register(client -> { if (this.clientTicks++ % 200 == 0 && client.world != null) { - final int blockEntities = client.world.blockEntities.size(); + int blockEntities = 0; if (PRINT_CLIENT_BLOCKENTITY_MESSAGES) { logger.info("[CLIENT] Tracked BlockEntities:" + this.clientBlockEntities.size() + " Ticked at: " + this.clientTicks + "ticks"); - logger.info("[CLIENT] Actual BlockEntities: " + client.world.blockEntities.size()); + + for (WorldChunk chunk : ((LoadedChunksCache) client.world).fabric_getLoadedChunks()) { + blockEntities += chunk.getBlockEntities().size(); + } + + logger.info("[CLIENT] Actual BlockEntities: " + blockEntities); } if (blockEntities != this.clientBlockEntities.size()) { @@ -66,13 +80,13 @@ public class ClientBlockEntityLifecycleTests implements ClientModInitializer { }); ServerLifecycleEvents.SERVER_STOPPED.register(minecraftServer -> { - if (!minecraftServer.isDedicated()) { // fixme: Use ClientNetworking#PLAY_DISCONNECTED instead of the server stop callback for testing. + if (!minecraftServer.isDedicated()) { // fixme: Use ClientPlayConnectionEvents#DISCONNECT instead of the server stop callback for testing. logger.info("[CLIENT] Disconnected. Tracking: " + this.clientBlockEntities.size() + " blockentities"); if (this.clientBlockEntities.size() != 0) { logger.error("[CLIENT] Mismatch in tracked blockentities, expected 0"); } } - });*/ + }); } } diff --git a/fabric-lifecycle-events-v1/src/testmod/java/net/fabricmc/fabric/test/event/lifecycle/client/ClientEntityLifecycleTests.java b/fabric-lifecycle-events-v1/src/testmod/java/net/fabricmc/fabric/test/event/lifecycle/client/ClientEntityLifecycleTests.java index f764e4a64..b5c850fb4 100644 --- a/fabric-lifecycle-events-v1/src/testmod/java/net/fabricmc/fabric/test/event/lifecycle/client/ClientEntityLifecycleTests.java +++ b/fabric-lifecycle-events-v1/src/testmod/java/net/fabricmc/fabric/test/event/lifecycle/client/ClientEntityLifecycleTests.java @@ -37,9 +37,9 @@ import net.fabricmc.fabric.test.event.lifecycle.ServerLifecycleTests; * Tests related to the lifecycle of entities. */ @Environment(EnvType.CLIENT) -public class ClientEntityLifecycleTests implements ClientModInitializer { - private static boolean PRINT_CLIENT_ENTITY_MESSAGES = System.getProperty("fabric-lifecycle-events-testmod.printClientEntityMessages") != null; - private List clientEntities = new ArrayList<>(); +public final class ClientEntityLifecycleTests implements ClientModInitializer { + private static final boolean PRINT_CLIENT_ENTITY_MESSAGES = System.getProperty("fabric-lifecycle-events-testmod.printClientEntityMessages") != null; + private final List clientEntities = new ArrayList<>(); private int clientTicks; @Override diff --git a/fabric-lifecycle-events-v1/src/testmod/java/net/fabricmc/fabric/test/event/lifecycle/client/ClientLifecycleTests.java b/fabric-lifecycle-events-v1/src/testmod/java/net/fabricmc/fabric/test/event/lifecycle/client/ClientLifecycleTests.java index f649a0682..8ee2adba5 100644 --- a/fabric-lifecycle-events-v1/src/testmod/java/net/fabricmc/fabric/test/event/lifecycle/client/ClientLifecycleTests.java +++ b/fabric-lifecycle-events-v1/src/testmod/java/net/fabricmc/fabric/test/event/lifecycle/client/ClientLifecycleTests.java @@ -22,7 +22,7 @@ import net.fabricmc.api.Environment; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents; @Environment(EnvType.CLIENT) -public class ClientLifecycleTests implements ClientModInitializer { +public final class ClientLifecycleTests implements ClientModInitializer { @Override public void onInitializeClient() { ClientLifecycleEvents.CLIENT_STARTED.register(client -> { diff --git a/fabric-lifecycle-events-v1/src/testmod/java/net/fabricmc/fabric/test/event/lifecycle/client/ClientTickTests.java b/fabric-lifecycle-events-v1/src/testmod/java/net/fabricmc/fabric/test/event/lifecycle/client/ClientTickTests.java index b033f992f..74531aeaa 100644 --- a/fabric-lifecycle-events-v1/src/testmod/java/net/fabricmc/fabric/test/event/lifecycle/client/ClientTickTests.java +++ b/fabric-lifecycle-events-v1/src/testmod/java/net/fabricmc/fabric/test/event/lifecycle/client/ClientTickTests.java @@ -29,8 +29,8 @@ import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; import net.fabricmc.fabric.test.event.lifecycle.ServerLifecycleTests; @Environment(EnvType.CLIENT) -public class ClientTickTests implements ClientModInitializer { - private Map, Integer> tickTracker = new HashMap<>(); +public final class ClientTickTests implements ClientModInitializer { + private final Map, Integer> tickTracker = new HashMap<>(); private int ticks; @Override