From 7490af87a10f24b2a5e4b43dbb34b5da5f34c430 Mon Sep 17 00:00:00 2001
From: shartte <shartte@users.noreply.github.com>
Date: Fri, 18 Sep 2020 19:10:47 +0200
Subject: [PATCH] Make BuiltInRegistries safe for Worldgen Registration during
 Mod Initialization (#1052)

* Adds a synchronisation for entries in BuiltInRegistries to the built-in DynamicRegistryManager, to a void class-loading DynamicRegistryManager during Mod initialization from messing up the Worldgen registrations of subsequently loaded mods.

* Changed to use updated Yarn mappings.
---
 .../registry/sync/DynamicRegistrySync.java    | 66 +++++++++++++++++++
 .../mixin/registry/sync/AccessorRegistry.java |  5 ++
 .../sync/MixinDynamicRegistryManager.java     | 45 +++++++++++++
 .../fabric-registry-sync-v0.mixins.json       |  1 +
 .../test/registry/sync/RegistrySyncTest.java  | 59 ++++++++++++++++-
 5 files changed, 175 insertions(+), 1 deletion(-)
 create mode 100644 fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/impl/registry/sync/DynamicRegistrySync.java
 create mode 100644 fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/mixin/registry/sync/MixinDynamicRegistryManager.java

diff --git a/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/impl/registry/sync/DynamicRegistrySync.java b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/impl/registry/sync/DynamicRegistrySync.java
new file mode 100644
index 000000000..b63a5bd12
--- /dev/null
+++ b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/impl/registry/sync/DynamicRegistrySync.java
@@ -0,0 +1,66 @@
+/*
+ * 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.registry.sync;
+
+import com.mojang.serialization.Lifecycle;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import net.minecraft.util.registry.BuiltinRegistries;
+import net.minecraft.util.registry.DynamicRegistryManager;
+import net.minecraft.util.registry.MutableRegistry;
+import net.minecraft.util.registry.Registry;
+import net.minecraft.util.registry.RegistryKey;
+
+import net.fabricmc.fabric.api.event.registry.RegistryEntryAddedCallback;
+import net.fabricmc.fabric.mixin.registry.sync.AccessorRegistry;
+
+/**
+ * Handles synchronising changes to the built-in registries into the dynamic registry manager's template manager,
+ * in case it gets classloaded early.
+ */
+public class DynamicRegistrySync {
+	private static final Logger LOGGER = LogManager.getLogger();
+
+	/**
+	 * Sets up a synchronisation that will propagate added entries to the given dynamic registry manager, which
+	 * should be the <em>built-in</em> manager. It is never destroyed. We don't ever have to unregister
+	 * the registry events.
+	 */
+	public static void setupSync(DynamicRegistryManager.Impl template) {
+		LOGGER.debug("Setting up synchronisation of new BuiltinRegistries entries to the built-in DynamicRegistryManager");
+		BuiltinRegistries.REGISTRIES.stream().forEach(source -> setupSync(source, template));
+	}
+
+	/**
+	 * Sets up an event registration for the source registy that will ensure all entries added from now on
+	 * are also added to the template for dynamic registry managers.
+	 */
+	private static <T> void setupSync(Registry<T> source, DynamicRegistryManager.Impl template) {
+		@SuppressWarnings("unchecked") AccessorRegistry<T> sourceAccessor = (AccessorRegistry<T>) source;
+		RegistryKey<? extends Registry<T>> sourceKey = source.getKey();
+		MutableRegistry<T> target = template.get(sourceKey);
+
+		RegistryEntryAddedCallback.event(source).register((rawId, id, object) -> {
+			LOGGER.trace("Synchronizing {} from built-in registry {} into built-in dynamic registry manager template.",
+					id, source.getKey());
+			Lifecycle lifecycle = sourceAccessor.callGetEntryLifecycle(object);
+			RegistryKey<T> entryKey = RegistryKey.of(sourceKey, id);
+			target.set(rawId, entryKey, object, lifecycle);
+		});
+	}
+}
diff --git a/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/mixin/registry/sync/AccessorRegistry.java b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/mixin/registry/sync/AccessorRegistry.java
index 2f4d72649..e5a73c141 100644
--- a/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/mixin/registry/sync/AccessorRegistry.java
+++ b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/mixin/registry/sync/AccessorRegistry.java
@@ -16,8 +16,10 @@
 
 package net.fabricmc.fabric.mixin.registry.sync;
 
+import com.mojang.serialization.Lifecycle;
 import org.spongepowered.asm.mixin.Mixin;
 import org.spongepowered.asm.mixin.gen.Accessor;
+import org.spongepowered.asm.mixin.gen.Invoker;
 
 import net.minecraft.util.registry.MutableRegistry;
 import net.minecraft.util.registry.Registry;
@@ -32,4 +34,7 @@ public interface AccessorRegistry<T> {
 
 	@Accessor()
 	RegistryKey<Registry<T>> getRegistryKey();
+
+	@Invoker
+	Lifecycle callGetEntryLifecycle(T object);
 }
diff --git a/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/mixin/registry/sync/MixinDynamicRegistryManager.java b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/mixin/registry/sync/MixinDynamicRegistryManager.java
new file mode 100644
index 000000000..0fcdec8dc
--- /dev/null
+++ b/fabric-registry-sync-v0/src/main/java/net/fabricmc/fabric/mixin/registry/sync/MixinDynamicRegistryManager.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.registry.sync;
+
+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.util.registry.DynamicRegistryManager;
+
+import net.fabricmc.fabric.impl.registry.sync.DynamicRegistrySync;
+
+@Mixin(DynamicRegistryManager.class)
+public class MixinDynamicRegistryManager {
+	// This is the "template" for all subsequent built-in dynamic registry managers,
+	// but it still contains the same objects as BuiltinRegistries, while the subsequent
+	// managers built from this template will contain copies.
+	@Shadow
+	private static DynamicRegistryManager.Impl BUILTIN;
+
+	/**
+	 * Ensures that any registrations made into {@link net.minecraft.util.registry.BuiltinRegistries} after
+	 * {@link DynamicRegistryManager} has been class-loaded are still propagated.
+	 */
+	@Inject(method = "<clinit>", at = @At(value = "TAIL"))
+	private static void setupBuiltInSync(CallbackInfo ci) {
+		DynamicRegistrySync.setupSync(BUILTIN);
+	}
+}
diff --git a/fabric-registry-sync-v0/src/main/resources/fabric-registry-sync-v0.mixins.json b/fabric-registry-sync-v0/src/main/resources/fabric-registry-sync-v0.mixins.json
index f1955d64f..435ba89ee 100644
--- a/fabric-registry-sync-v0/src/main/resources/fabric-registry-sync-v0.mixins.json
+++ b/fabric-registry-sync-v0/src/main/resources/fabric-registry-sync-v0.mixins.json
@@ -5,6 +5,7 @@
   "mixins": [
     "AccessorRegistry",
     "MixinBootstrap",
+    "MixinDynamicRegistryManager",
     "MixinIdList",
     "MixinIdRegistry",
     "MixinPlayerManager",
diff --git a/fabric-registry-sync-v0/src/testmod/java/net/fabricmc/fabric/test/registry/sync/RegistrySyncTest.java b/fabric-registry-sync-v0/src/testmod/java/net/fabricmc/fabric/test/registry/sync/RegistrySyncTest.java
index 63c266d9e..6a687784c 100644
--- a/fabric-registry-sync-v0/src/testmod/java/net/fabricmc/fabric/test/registry/sync/RegistrySyncTest.java
+++ b/fabric-registry-sync-v0/src/testmod/java/net/fabricmc/fabric/test/registry/sync/RegistrySyncTest.java
@@ -24,13 +24,19 @@ import net.minecraft.block.Material;
 import net.minecraft.item.BlockItem;
 import net.minecraft.item.Item;
 import net.minecraft.util.Identifier;
+import net.minecraft.util.registry.BuiltinRegistries;
+import net.minecraft.util.registry.DynamicRegistryManager;
+import net.minecraft.util.registry.MutableRegistry;
 import net.minecraft.util.registry.Registry;
 import net.minecraft.util.registry.SimpleRegistry;
+import net.minecraft.world.gen.feature.ConfiguredFeature;
+import net.minecraft.world.gen.feature.DefaultFeatureConfig;
+import net.minecraft.world.gen.feature.Feature;
 
 import net.fabricmc.api.ModInitializer;
+import net.fabricmc.fabric.api.event.registry.FabricRegistryBuilder;
 import net.fabricmc.fabric.api.event.registry.RegistryAttribute;
 import net.fabricmc.fabric.api.event.registry.RegistryAttributeHolder;
-import net.fabricmc.fabric.api.event.registry.FabricRegistryBuilder;
 
 public class RegistrySyncTest implements ModInitializer {
 	/**
@@ -41,6 +47,8 @@ public class RegistrySyncTest implements ModInitializer {
 
 	@Override
 	public void onInitialize() {
+		testBuiltInRegistrySync();
+
 		if (REGISTER_BLOCKS) {
 			for (int i = 0; i < 5; i++) {
 				Block block = new Block(AbstractBlock.Settings.of(Material.STONE));
@@ -69,4 +77,53 @@ public class RegistrySyncTest implements ModInitializer {
 		Validate.isTrue(RegistryAttributeHolder.get(fabricRegistry).hasAttribute(RegistryAttribute.SYNCED));
 		Validate.isTrue(!RegistryAttributeHolder.get(fabricRegistry).hasAttribute(RegistryAttribute.PERSISTED));
 	}
+
+	/**
+	 * Tests that built-in registries are properly synchronized even after the dynamic reigstry managers have been
+	 * class-loaded.
+	 */
+	private void testBuiltInRegistrySync() {
+		System.out.println("Checking built-in registry sync...");
+
+		// Register a configured feature before force-loading the dynamic registry manager
+		ConfiguredFeature<DefaultFeatureConfig, ?> cf1 = Feature.BASALT_PILLAR.configure(DefaultFeatureConfig.INSTANCE);
+		Identifier f1Id = new Identifier("registry_sync", "f1");
+		Registry.register(BuiltinRegistries.CONFIGURED_FEATURE, f1Id, cf1);
+
+		// Force-Initialize the dynamic registry manager, doing this in a Mod initializer would cause
+		// further registrations into BuiltInRegistries to _NOT_ propagate into DynamicRegistryManager.BUILTIN
+		checkFeature(DynamicRegistryManager.create(), f1Id);
+
+		ConfiguredFeature<DefaultFeatureConfig, ?> cf2 = Feature.DESERT_WELL.configure(DefaultFeatureConfig.INSTANCE);
+		Identifier f2Id = new Identifier("registry_sync", "f2");
+		Registry.register(BuiltinRegistries.CONFIGURED_FEATURE, f2Id, cf2);
+
+		DynamicRegistryManager.Impl impl2 = DynamicRegistryManager.create();
+		checkFeature(impl2, f1Id);
+		checkFeature(impl2, f2Id);
+	}
+
+	private void checkFeature(DynamicRegistryManager manager, Identifier id) {
+		MutableRegistry<ConfiguredFeature<?, ?>> registry = manager.get(Registry.CONFIGURED_FEATURE_WORLDGEN);
+
+		ConfiguredFeature<?, ?> builtInEntry = BuiltinRegistries.CONFIGURED_FEATURE.get(id);
+
+		if (builtInEntry == null) {
+			throw new IllegalStateException("Expected built-in entry to exist for: " + id);
+		}
+
+		ConfiguredFeature<?, ?> entry = registry.get(id);
+
+		if (entry == null) {
+			throw new IllegalStateException("Expected dynamic registry to contain entry " + id);
+		}
+
+		if (builtInEntry == entry) {
+			throw new IllegalStateException("Expected that the built-in entry and dynamic entry don't have object identity because the dynamic entry is created by serializing the built-in entry to JSON and back.");
+		}
+
+		if (builtInEntry.feature != entry.feature) {
+			throw new IllegalStateException("Expected both entries to reference the same feature since it's only in Registry and is never copied");
+		}
+	}
 }