From 144d3847c09bd5e1c5ac4fde3e60ce3043181799 Mon Sep 17 00:00:00 2001
From: i509VCB <git@i509.me>
Date: Fri, 17 Jul 2020 06:51:05 -0700
Subject: [PATCH] Lifecycle Events: Part 2, Electric Boogaloo (#848)

* Add a few new server lifecycle events

Server starting, Load World, Before server resource reload, After server resource reload

* Save event

* Split world load into ServerWorldEvents, add failure event for data pack reload

* cause

* Merge fail and regular end.

* Present tense lol

* So we can just plainly specify the system properties

* use handleAsync instead
---
 .../lifecycle/v1/ServerLifecycleEvents.java   | 54 +++++++++++++++++++
 .../event/lifecycle/v1/ServerWorldEvents.java | 44 +++++++++++++++
 .../event/lifecycle/MinecraftServerMixin.java | 40 ++++++++++++++
 .../ServerBlockEntityLifecycleTests.java      | 27 ++++++++--
 .../lifecycle/ServerEntityLifecycleTests.java |  6 ++-
 .../event/lifecycle/ServerLifecycleTests.java |  5 ++
 .../lifecycle/ServerResourceReloadTests.java  | 43 +++++++++++++++
 .../ClientBlockEntityLifecycleTests.java      | 22 ++++++--
 .../client/ClientEntityLifecycleTests.java    | 19 +++++--
 .../src/testmod/resources/fabric.mod.json     |  3 +-
 10 files changed, 247 insertions(+), 16 deletions(-)
 create mode 100644 fabric-lifecycle-events-v1/src/main/java/net/fabricmc/fabric/api/event/lifecycle/v1/ServerWorldEvents.java
 create mode 100644 fabric-lifecycle-events-v1/src/testmod/java/net/fabricmc/fabric/test/event/lifecycle/ServerResourceReloadTests.java

diff --git a/fabric-lifecycle-events-v1/src/main/java/net/fabricmc/fabric/api/event/lifecycle/v1/ServerLifecycleEvents.java b/fabric-lifecycle-events-v1/src/main/java/net/fabricmc/fabric/api/event/lifecycle/v1/ServerLifecycleEvents.java
index ce5fceba8..50bf8a507 100644
--- a/fabric-lifecycle-events-v1/src/main/java/net/fabricmc/fabric/api/event/lifecycle/v1/ServerLifecycleEvents.java
+++ b/fabric-lifecycle-events-v1/src/main/java/net/fabricmc/fabric/api/event/lifecycle/v1/ServerLifecycleEvents.java
@@ -16,7 +16,9 @@
 
 package net.fabricmc.fabric.api.event.lifecycle.v1;
 
+import net.minecraft.resource.ServerResourceManager;
 import net.minecraft.server.MinecraftServer;
+import net.minecraft.server.PlayerManager;
 
 import net.fabricmc.fabric.api.event.Event;
 import net.fabricmc.fabric.api.event.EventFactory;
@@ -25,6 +27,17 @@ public final class ServerLifecycleEvents {
 	private ServerLifecycleEvents() {
 	}
 
+	/**
+	 * Called when a Minecraft server is starting.
+	 *
+	 * <p>This occurs before the {@link PlayerManager player manager} and any worlds are loaded.
+	 */
+	public static final Event<ServerStarting> SERVER_STARTING = EventFactory.createArrayBacked(ServerStarting.class, callbacks -> server -> {
+		for (ServerStarting callback : callbacks) {
+			callback.onServerStarting(server);
+		}
+	});
+
 	/**
 	 * Called when a Minecraft server has started and is about to tick for the first time.
 	 *
@@ -63,6 +76,30 @@ public final class ServerLifecycleEvents {
 		}
 	});
 
+	/**
+	 * Called before a Minecraft server reloads data packs.
+	 */
+	public static final Event<StartDataPackReload> START_DATA_PACK_RELOAD = EventFactory.createArrayBacked(StartDataPackReload.class, callbacks -> (server, serverResourceManager) -> {
+		for (StartDataPackReload callback : callbacks) {
+			callback.startDataPackReload(server, serverResourceManager);
+		}
+	});
+
+	/**
+	 * Called after a Minecraft server has reloaded data packs.
+	 *
+	 * <p>If reloading data packs was unsuccessful, the current data packs will be kept.
+	 */
+	public static final Event<EndDataPackReload> END_DATA_PACK_RELOAD = EventFactory.createArrayBacked(EndDataPackReload.class, callbacks -> (server, serverResourceManager, success) -> {
+		for (EndDataPackReload callback : callbacks) {
+			callback.endDataPackReload(server, serverResourceManager, success);
+		}
+	});
+
+	public interface ServerStarting {
+		void onServerStarting(MinecraftServer server);
+	}
+
 	public interface ServerStarted {
 		void onServerStarted(MinecraftServer server);
 	}
@@ -74,4 +111,21 @@ public final class ServerLifecycleEvents {
 	public interface ServerStopped {
 		void onServerStopped(MinecraftServer server);
 	}
+
+	public interface StartDataPackReload {
+		void startDataPackReload(MinecraftServer server, ServerResourceManager serverResourceManager);
+	}
+
+	public interface EndDataPackReload {
+		/**
+		 * Called after data packs on a Minecraft server have been reloaded.
+		 *
+		 * <p>If the reload was not successful, the old data packs will be kept.
+		 *
+		 * @param server the server
+		 * @param serverResourceManager the server resource manager
+		 * @param success if the reload was successful
+		 */
+		void endDataPackReload(MinecraftServer server, ServerResourceManager serverResourceManager, boolean success);
+	}
 }
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
new file mode 100644
index 000000000..ca47bed62
--- /dev/null
+++ b/fabric-lifecycle-events-v1/src/main/java/net/fabricmc/fabric/api/event/lifecycle/v1/ServerWorldEvents.java
@@ -0,0 +1,44 @@
+/*
+ * 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.api.event.lifecycle.v1;
+
+import net.minecraft.server.MinecraftServer;
+import net.minecraft.server.world.ServerWorld;
+import net.minecraft.world.PersistentState;
+
+import net.fabricmc.fabric.api.event.Event;
+import net.fabricmc.fabric.api.event.EventFactory;
+
+public final class ServerWorldEvents {
+	/**
+	 * Called when a world is loaded by a Minecraft server.
+	 *
+	 * <p>For example, this can be used to load world specific metadata or initialize a {@link PersistentState} on a server world.
+	 */
+	public static final Event<Load> LOAD = EventFactory.createArrayBacked(Load.class, callbacks -> (server, world) -> {
+		for (Load callback : callbacks) {
+			callback.onWorldLoad(server, world);
+		}
+	});
+
+	public interface Load {
+		void onWorldLoad(MinecraftServer server, ServerWorld world);
+	}
+
+	private ServerWorldEvents() {
+	}
+}
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 b67db9177..61811585b 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
@@ -16,27 +16,43 @@
 
 package net.fabricmc.fabric.mixin.event.lifecycle;
 
+import java.util.Collection;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
 import java.util.function.BooleanSupplier;
 
 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.CallbackInfoReturnable;
 import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
 
 import net.minecraft.block.entity.BlockEntity;
 import net.minecraft.entity.Entity;
+import net.minecraft.resource.ServerResourceManager;
 import net.minecraft.server.MinecraftServer;
 import net.minecraft.server.world.ServerWorld;
 
 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.api.event.lifecycle.v1.ServerWorldEvents;
 
 @Mixin(MinecraftServer.class)
 public abstract class MinecraftServerMixin {
+	@Shadow
+	private ServerResourceManager serverResourceManager;
+
+	@Inject(at = @At(value = "INVOKE", target = "Lnet/minecraft/server/MinecraftServer;setupServer()Z"), method = "runServer")
+	private void beforeSetupServer(CallbackInfo info) {
+		ServerLifecycleEvents.SERVER_STARTING.invoker().onServerStarting((MinecraftServer) (Object) this);
+	}
+
 	@Inject(at = @At(value = "INVOKE", target = "Lnet/minecraft/server/MinecraftServer;setFavicon(Lnet/minecraft/server/ServerMetadata;)V", ordinal = 0), method = "runServer")
 	private void afterSetupServer(CallbackInfo info) {
 		ServerLifecycleEvents.SERVER_STARTED.invoker().onServerStarted((MinecraftServer) (Object) this);
@@ -73,4 +89,28 @@ public abstract class MinecraftServerMixin {
 			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;"))
+	private <K, V> V onLoadWorld(Map<K, V> worlds, K registryKey, V serverWorld) {
+		final V result = worlds.put(registryKey, serverWorld);
+		ServerWorldEvents.LOAD.invoker().onWorldLoad((MinecraftServer) (Object) this, (ServerWorld) serverWorld);
+
+		return result;
+	}
+
+	@Inject(method = "reloadResources", at = @At("HEAD"))
+	private void startResourceReload(Collection<String> collection, CallbackInfoReturnable<CompletableFuture<Void>> cir) {
+		ServerLifecycleEvents.START_DATA_PACK_RELOAD.invoker().startDataPackReload((MinecraftServer) (Object) this, this.serverResourceManager);
+	}
+
+	@Inject(method = "reloadResources", at = @At("TAIL"))
+	private void endResourceReload(Collection<String> collection, CallbackInfoReturnable<CompletableFuture<Void>> cir) {
+		cir.getReturnValue().handleAsync((value, throwable) -> {
+			// Hook into fail
+			ServerLifecycleEvents.END_DATA_PACK_RELOAD.invoker().endDataPackReload((MinecraftServer) (Object) this, this.serverResourceManager, throwable == null);
+			return value;
+		}, (MinecraftServer) (Object) this);
+	}
 }
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 d9e0d6b5f..c3210b597 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
@@ -31,6 +31,7 @@ import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
 import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents;
 
 public class ServerBlockEntityLifecycleTests implements ModInitializer {
+	private static boolean PRINT_SERVER_BLOCKENTITY_MESSAGES = System.getProperty("fabric-lifecycle-events-testmod.printServerBlockEntityMessages") != null;
 	private List<BlockEntity> serverBlockEntities = new ArrayList<>();
 
 	@Override
@@ -39,28 +40,44 @@ public class ServerBlockEntityLifecycleTests implements ModInitializer {
 
 		ServerBlockEntityEvents.BLOCK_ENTITY_LOAD.register((blockEntity, world) -> {
 			this.serverBlockEntities.add(blockEntity);
-			logger.info("[SERVER] LOADED " + Registry.BLOCK_ENTITY_TYPE.getId(blockEntity.getType()).toString() + " - BlockEntities: " + this.serverBlockEntities.size());
+
+			if (PRINT_SERVER_BLOCKENTITY_MESSAGES) {
+				logger.info("[SERVER] LOADED " + Registry.BLOCK_ENTITY_TYPE.getId(blockEntity.getType()).toString() + " - BlockEntities: " + this.serverBlockEntities.size());
+			}
 		});
 
 		ServerBlockEntityEvents.BLOCK_ENTITY_UNLOAD.register((blockEntity, world) -> {
 			this.serverBlockEntities.remove(blockEntity);
-			logger.info("[SERVER] UNLOADED " + Registry.BLOCK_ENTITY_TYPE.getId(blockEntity.getType()).toString() + " - BlockEntities: " + this.serverBlockEntities.size());
+
+			if (PRINT_SERVER_BLOCKENTITY_MESSAGES) {
+				logger.info("[SERVER] UNLOADED " + Registry.BLOCK_ENTITY_TYPE.getId(blockEntity.getType()).toString() + " - BlockEntities: " + this.serverBlockEntities.size());
+			}
 		});
 
 		ServerTickEvents.END_SERVER_TICK.register(minecraftServer -> {
 			if (minecraftServer.getTicks() % 200 == 0) {
 				int entities = 0;
-				logger.info("[SERVER] Tracked BlockEntities:" + this.serverBlockEntities.size() + " Ticked at: " + minecraftServer.getTicks() + "ticks");
+
+				if (PRINT_SERVER_BLOCKENTITY_MESSAGES) {
+					logger.info("[SERVER] Tracked BlockEntities:" + this.serverBlockEntities.size() + " Ticked at: " + minecraftServer.getTicks() + "ticks");
+				}
 
 				for (ServerWorld world : minecraftServer.getWorlds()) {
 					int worldEntities = world.blockEntities.size();
-					logger.info("[SERVER] Tracked BlockEntities in " + world.getRegistryKey().toString() + " - " + worldEntities);
+
+					if (PRINT_SERVER_BLOCKENTITY_MESSAGES) {
+						logger.info("[SERVER] Tracked BlockEntities in " + world.getRegistryKey().toString() + " - " + worldEntities);
+					}
+
 					entities += worldEntities;
 				}
 
-				logger.info("[SERVER] Actual Total BlockEntities: " + entities);
+				if (PRINT_SERVER_BLOCKENTITY_MESSAGES) {
+					logger.info("[SERVER] Actual Total BlockEntities: " + entities);
+				}
 
 				if (entities != this.serverBlockEntities.size()) {
+					// Always print mismatches
 					logger.error("[SERVER] Mismatch in tracked blockentities and actual blockentities");
 				}
 			}
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 4bf9c0e47..02045f985 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
@@ -30,6 +30,7 @@ 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<Entity> serverEntities = new ArrayList<>();
 
 	@Override
@@ -38,7 +39,10 @@ public class ServerEntityLifecycleTests implements ModInitializer {
 
 		ServerEntityEvents.ENTITY_LOAD.register((entity, world) -> {
 			this.serverEntities.add(entity);
-			logger.info("[SERVER] LOADED " + entity.toString() + " - Entities: " + this.serverEntities.size());
+
+			if (PRINT_SERVER_ENTITY_MESSAGES) {
+				logger.info("[SERVER] LOADED " + entity.toString() + " - Entities: " + this.serverEntities.size());
+			}
 		});
 	}
 }
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 0e0888cce..59f5dfb3a 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
@@ -21,6 +21,7 @@ import org.apache.logging.log4j.Logger;
 
 import net.fabricmc.api.ModInitializer;
 import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
+import net.fabricmc.fabric.api.event.lifecycle.v1.ServerWorldEvents;
 
 /**
  * Tests related to the lifecycle of a server.
@@ -41,5 +42,9 @@ public class ServerLifecycleTests implements ModInitializer {
 		ServerLifecycleEvents.SERVER_STOPPED.register(server -> {
 			LOGGER.info("Stopped Server!");
 		});
+
+		ServerWorldEvents.LOAD.register((server, world) -> {
+			LOGGER.info("Loaded world " + world.getRegistryKey().getValue().toString());
+		});
 	}
 }
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
new file mode 100644
index 000000000..c7537ba45
--- /dev/null
+++ b/fabric-lifecycle-events-v1/src/testmod/java/net/fabricmc/fabric/test/event/lifecycle/ServerResourceReloadTests.java
@@ -0,0 +1,43 @@
+/*
+ * 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 org.apache.logging.log4j.LogManager;
+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 static final Logger LOGGER = LogManager.getLogger("LifecycleEventsTest");
+
+	@Override
+	public void onInitialize() {
+		ServerLifecycleEvents.START_DATA_PACK_RELOAD.register((server, serverResourceManager) -> {
+			LOGGER.info("PREPARING FOR RELOAD");
+		});
+
+		ServerLifecycleEvents.END_DATA_PACK_RELOAD.register((server, serverResourceManager, success) -> {
+			if (success) {
+				LOGGER.info("FINISHED RELOAD on {}", Thread.currentThread());
+			} else {
+				// Failure can be tested by trying to disable the vanilla datapack
+				LOGGER.error("FAILED TO RELOAD on {}", Thread.currentThread());
+			}
+		});
+	}
+}
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 51424dc60..94202d6a9 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
@@ -31,6 +31,7 @@ import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
 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<BlockEntity> clientBlockEntities = new ArrayList<>();
 	private int clientTicks;
 
@@ -40,22 +41,33 @@ public class ClientBlockEntityLifecycleTests implements ClientModInitializer {
 
 		ClientBlockEntityEvents.BLOCK_ENTITY_LOAD.register((blockEntity, world) -> {
 			this.clientBlockEntities.add(blockEntity);
-			logger.info("[CLIENT]" + " LOADED " + Registry.BLOCK_ENTITY_TYPE.getId(blockEntity.getType()).toString() + " - BlockEntities: " + this.clientBlockEntities.size());
+
+			if (PRINT_CLIENT_BLOCKENTITY_MESSAGES) {
+				logger.info("[CLIENT]" + " LOADED " + Registry.BLOCK_ENTITY_TYPE.getId(blockEntity.getType()).toString() + " - BlockEntities: " + this.clientBlockEntities.size());
+			}
 		});
 
 		ClientBlockEntityEvents.BLOCK_ENTITY_UNLOAD.register((blockEntity, world) -> {
 			this.clientBlockEntities.remove(blockEntity);
-			logger.info("[CLIENT]" + " UNLOADED " + Registry.BLOCK_ENTITY_TYPE.getId(blockEntity.getType()).toString() + " - BlockEntities: " + this.clientBlockEntities.size());
+
+			if (PRINT_CLIENT_BLOCKENTITY_MESSAGES) {
+				logger.info("[CLIENT]" + " UNLOADED " + Registry.BLOCK_ENTITY_TYPE.getId(blockEntity.getType()).toString() + " - BlockEntities: " + this.clientBlockEntities.size());
+			}
 		});
 
 		ClientTickEvents.END_CLIENT_TICK.register(client -> {
 			if (this.clientTicks++ % 200 == 0 && client.world != null) {
 				final int blockEntities = client.world.blockEntities.size();
-				logger.info("[CLIENT] Tracked BlockEntities:" + this.clientBlockEntities.size() + " Ticked at: " + this.clientTicks + "ticks");
-				logger.info("[CLIENT] Actual BlockEntities: " + client.world.blockEntities.size());
+
+				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());
+				}
 
 				if (blockEntities != this.clientBlockEntities.size()) {
-					logger.error("[CLIENT] Mismatch in tracked blockentities and actual blockentities");
+					if (PRINT_CLIENT_BLOCKENTITY_MESSAGES) {
+						logger.error("[CLIENT] Mismatch in tracked blockentities and actual blockentities");
+					}
 				}
 			}
 		});
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 350ba0709..f764e4a64 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
@@ -38,6 +38,7 @@ import net.fabricmc.fabric.test.event.lifecycle.ServerLifecycleTests;
  */
 @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<Entity> clientEntities = new ArrayList<>();
 	private int clientTicks;
 
@@ -47,21 +48,31 @@ public class ClientEntityLifecycleTests implements ClientModInitializer {
 
 		ClientEntityEvents.ENTITY_LOAD.register((entity, world) -> {
 			this.clientEntities.add(entity);
-			logger.info("[CLIENT]" + " LOADED " + Registry.ENTITY_TYPE.getId(entity.getType()).toString() + " - Entities: " + this.clientEntities.size());
+
+			if (PRINT_CLIENT_ENTITY_MESSAGES) {
+				logger.info("[CLIENT]" + " LOADED " + Registry.ENTITY_TYPE.getId(entity.getType()).toString() + " - Entities: " + this.clientEntities.size());
+			}
 		});
 
 		ClientEntityEvents.ENTITY_UNLOAD.register((entity, world) -> {
 			this.clientEntities.remove(entity);
-			logger.info("[CLIENT]" + " UNLOADED " + Registry.ENTITY_TYPE.getId(entity.getType()).toString() + " - Entities: " + this.clientEntities.size());
+
+			if (PRINT_CLIENT_ENTITY_MESSAGES) {
+				logger.info("[CLIENT]" + " UNLOADED " + Registry.ENTITY_TYPE.getId(entity.getType()).toString() + " - Entities: " + this.clientEntities.size());
+			}
 		});
 
 		ClientTickEvents.END_CLIENT_TICK.register(client -> {
 			if (this.clientTicks++ % 200 == 0 && client.world != null) {
 				final int entities = Iterables.toArray(client.world.getEntities(), Entity.class).length;
-				logger.info("[CLIENT] Tracked Entities:" + this.clientEntities.size() + " Ticked at: " + this.clientTicks + "ticks");
-				logger.info("[CLIENT] Actual Entities: " + entities);
+
+				if (PRINT_CLIENT_ENTITY_MESSAGES) {
+					logger.info("[CLIENT] Tracked Entities:" + this.clientEntities.size() + " Ticked at: " + this.clientTicks + "ticks");
+					logger.info("[CLIENT] Actual Entities: " + entities);
+				}
 
 				if (entities != this.clientEntities.size()) {
+					// Always print mismatches
 					logger.error("[CLIENT] Mismatch in tracked entities and actual entities");
 				}
 			}
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 8fe00cbdf..46fb08365 100644
--- a/fabric-lifecycle-events-v1/src/testmod/resources/fabric.mod.json
+++ b/fabric-lifecycle-events-v1/src/testmod/resources/fabric.mod.json
@@ -13,7 +13,8 @@
       "net.fabricmc.fabric.test.event.lifecycle.ServerBlockEntityLifecycleTests",
       "net.fabricmc.fabric.test.event.lifecycle.ServerEntityLifecycleTests",
       "net.fabricmc.fabric.test.event.lifecycle.ServerLifecycleTests",
-      "net.fabricmc.fabric.test.event.lifecycle.ServerTickTests"
+      "net.fabricmc.fabric.test.event.lifecycle.ServerTickTests",
+      "net.fabricmc.fabric.test.event.lifecycle.ServerResourceReloadTests"
     ],
     "client": [
       "net.fabricmc.fabric.test.event.lifecycle.client.ClientBlockEntityLifecycleTests",