From 5e85fc0a0928466458ab76dd910779beb45b552f Mon Sep 17 00:00:00 2001
From: deirn <foreirn@gmail.com>
Date: Wed, 18 Aug 2021 01:07:12 +0700
Subject: [PATCH] Tag Factory API (#1562)

* Tag factory

* Add static biome tag factory to the API

* Use SERVER_STARTING event

* Use the ctor directly

* Use the default BIOME factory

* AccessorDynamicRegistryManager -> DynamicRegistryManagerAccess

* Return Tag.Identified

* Load dynamic registry tags right after datapack entries loaded

* DynamicRegistryManagerAccess -> DynamicRegistryManagerAccessor

* Fix grammar
---
 fabric-tag-extensions-v0/build.gradle         |   6 +
 .../fabricmc/fabric/api/tag/TagFactory.java   |  66 ++++++++++
 .../fabricmc/fabric/api/tag/TagRegistry.java  |  33 +++--
 .../tag/extension/FabricTagManagerHooks.java  |  25 ++++
 .../impl/tag/extension/TagFactoryImpl.java    | 119 ++++++++++++++++++
 ...va => DynamicRegistryManagerAccessor.java} |  18 +--
 .../tag/extension/MixinMinecraftServer.java   |  46 +++++++
 .../mixin/tag/extension/MixinRegistryOps.java |  41 ++++++
 .../MixinRequiredTagListRegistry.java         |  41 ++++++
 .../mixin/tag/extension/MixinTagManager.java  |  54 ++++++++
 .../tag/extension/MixinTagManagerLoader.java  |  44 +++++++
 .../fabric-tag-extensions-v0.mixins.json      |   9 +-
 .../test/tag/extension/TagExtensionTest.java  |  61 +++++++++
 .../tag/extension/TagExtensionTestClient.java |  44 +++++++
 .../tags/biomes/factory_test.json             |   8 ++
 .../tags/biomes/json_only_test.json           |   7 ++
 .../worldgen/biome/test.json                  |  20 +++
 .../src/testmod/resources/fabric.mod.json     |  19 +++
 18 files changed, 642 insertions(+), 19 deletions(-)
 create mode 100644 fabric-tag-extensions-v0/src/main/java/net/fabricmc/fabric/api/tag/TagFactory.java
 create mode 100644 fabric-tag-extensions-v0/src/main/java/net/fabricmc/fabric/impl/tag/extension/FabricTagManagerHooks.java
 create mode 100644 fabric-tag-extensions-v0/src/main/java/net/fabricmc/fabric/impl/tag/extension/TagFactoryImpl.java
 rename fabric-tag-extensions-v0/src/main/java/net/fabricmc/fabric/mixin/tag/extension/{AccessorFluidTags.java => DynamicRegistryManagerAccessor.java} (66%)
 create mode 100644 fabric-tag-extensions-v0/src/main/java/net/fabricmc/fabric/mixin/tag/extension/MixinMinecraftServer.java
 create mode 100644 fabric-tag-extensions-v0/src/main/java/net/fabricmc/fabric/mixin/tag/extension/MixinRegistryOps.java
 create mode 100644 fabric-tag-extensions-v0/src/main/java/net/fabricmc/fabric/mixin/tag/extension/MixinRequiredTagListRegistry.java
 create mode 100644 fabric-tag-extensions-v0/src/main/java/net/fabricmc/fabric/mixin/tag/extension/MixinTagManager.java
 create mode 100644 fabric-tag-extensions-v0/src/main/java/net/fabricmc/fabric/mixin/tag/extension/MixinTagManagerLoader.java
 create mode 100644 fabric-tag-extensions-v0/src/testmod/java/net/fabricmc/fabric/test/tag/extension/TagExtensionTest.java
 create mode 100644 fabric-tag-extensions-v0/src/testmod/java/net/fabricmc/fabric/test/tag/extension/TagExtensionTestClient.java
 create mode 100644 fabric-tag-extensions-v0/src/testmod/resources/data/fabric-tag-extensions-v0-testmod/tags/biomes/factory_test.json
 create mode 100644 fabric-tag-extensions-v0/src/testmod/resources/data/fabric-tag-extensions-v0-testmod/tags/biomes/json_only_test.json
 create mode 100644 fabric-tag-extensions-v0/src/testmod/resources/data/fabric-tag-extensions-v0-testmod/worldgen/biome/test.json
 create mode 100644 fabric-tag-extensions-v0/src/testmod/resources/fabric.mod.json

diff --git a/fabric-tag-extensions-v0/build.gradle b/fabric-tag-extensions-v0/build.gradle
index fdcce387c..c96e26c44 100644
--- a/fabric-tag-extensions-v0/build.gradle
+++ b/fabric-tag-extensions-v0/build.gradle
@@ -5,3 +5,9 @@ moduleDependencies(project, [
 		'fabric-api-base',
 		'fabric-resource-loader-v0'
 ])
+
+dependencies {
+	testmodImplementation project(path: ':fabric-command-api-v1', configuration: 'dev')
+	testmodImplementation project(path: ':fabric-key-binding-api-v1', configuration: 'dev')
+	testmodImplementation project(path: ':fabric-lifecycle-events-v1', configuration: 'dev')
+}
diff --git a/fabric-tag-extensions-v0/src/main/java/net/fabricmc/fabric/api/tag/TagFactory.java b/fabric-tag-extensions-v0/src/main/java/net/fabricmc/fabric/api/tag/TagFactory.java
new file mode 100644
index 000000000..1b5f4fffb
--- /dev/null
+++ b/fabric-tag-extensions-v0/src/main/java/net/fabricmc/fabric/api/tag/TagFactory.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.api.tag;
+
+import java.util.function.Supplier;
+
+import net.minecraft.block.Block;
+import net.minecraft.entity.EntityType;
+import net.minecraft.fluid.Fluid;
+import net.minecraft.item.Item;
+import net.minecraft.tag.BlockTags;
+import net.minecraft.tag.EntityTypeTags;
+import net.minecraft.tag.FluidTags;
+import net.minecraft.tag.GameEventTags;
+import net.minecraft.tag.ItemTags;
+import net.minecraft.tag.Tag;
+import net.minecraft.tag.TagGroup;
+import net.minecraft.util.Identifier;
+import net.minecraft.util.registry.Registry;
+import net.minecraft.util.registry.RegistryKey;
+import net.minecraft.world.biome.Biome;
+import net.minecraft.world.event.GameEvent;
+
+import net.fabricmc.fabric.impl.tag.extension.TagFactoryImpl;
+
+/**
+ * A factory for accessing datapack tags.
+ */
+public interface TagFactory<T> {
+	TagFactory<Item> ITEM = of(ItemTags::getTagGroup);
+	TagFactory<Block> BLOCK = of(BlockTags::getTagGroup);
+	TagFactory<Fluid> FLUID = of(FluidTags::getTagGroup);
+	TagFactory<GameEvent> GAME_EVENT = of(GameEventTags::getTagGroup);
+	TagFactory<EntityType<?>> ENTITY_TYPE = of(EntityTypeTags::getTagGroup);
+	TagFactory<Biome> BIOME = of(Registry.BIOME_KEY, "tags/biomes");
+
+	/**
+	 * Create a new tag factory for specified registry.
+	 *
+	 * @param registryKey the key of the registry.
+	 * @param dataType    the data type of this tag group, vanilla uses "tags/[plural]" format for built-in groups.
+	 */
+	static <T> TagFactory<T> of(RegistryKey<? extends Registry<T>> registryKey, String dataType) {
+		return TagFactoryImpl.of(registryKey, dataType);
+	}
+
+	static <T> TagFactory<T> of(Supplier<TagGroup<T>> tagGroupSupplier) {
+		return TagFactoryImpl.of(tagGroupSupplier);
+	}
+
+	Tag.Identified<T> create(Identifier id);
+}
diff --git a/fabric-tag-extensions-v0/src/main/java/net/fabricmc/fabric/api/tag/TagRegistry.java b/fabric-tag-extensions-v0/src/main/java/net/fabricmc/fabric/api/tag/TagRegistry.java
index 2500ae382..9e0dccf7d 100644
--- a/fabric-tag-extensions-v0/src/main/java/net/fabricmc/fabric/api/tag/TagRegistry.java
+++ b/fabric-tag-extensions-v0/src/main/java/net/fabricmc/fabric/api/tag/TagRegistry.java
@@ -19,22 +19,21 @@ package net.fabricmc.fabric.api.tag;
 import java.util.function.Supplier;
 
 import net.minecraft.block.Block;
-import net.minecraft.tag.TagGroup;
 import net.minecraft.entity.EntityType;
 import net.minecraft.fluid.Fluid;
 import net.minecraft.item.Item;
-import net.minecraft.tag.BlockTags;
-import net.minecraft.tag.EntityTypeTags;
-import net.minecraft.tag.ItemTags;
 import net.minecraft.tag.Tag;
+import net.minecraft.tag.TagGroup;
 import net.minecraft.util.Identifier;
 
 import net.fabricmc.fabric.impl.tag.extension.TagDelegate;
-import net.fabricmc.fabric.mixin.tag.extension.AccessorFluidTags;
 
 /**
  * Helper methods for registering Tags.
+ *
+ * @deprecated use {@link TagFactory} instead.
  */
+@Deprecated
 public final class TagRegistry {
 	private TagRegistry() { }
 
@@ -42,19 +41,35 @@ public final class TagRegistry {
 		return new TagDelegate<>(id, containerSupplier);
 	}
 
+	/**
+	 * @deprecated use {@link TagFactory#BLOCK}
+	 */
+	@Deprecated
 	public static Tag<Block> block(Identifier id) {
-		return create(id, BlockTags::getTagGroup);
+		return TagFactory.BLOCK.create(id);
 	}
 
+	/**
+	 * @deprecated use {@link TagFactory#ENTITY_TYPE}
+	 */
+	@Deprecated
 	public static Tag<EntityType<?>> entityType(Identifier id) {
-		return create(id, EntityTypeTags::getTagGroup);
+		return TagFactory.ENTITY_TYPE.create(id);
 	}
 
+	/**
+	 * @deprecated use {@link TagFactory#FLUID}
+	 */
+	@Deprecated
 	public static Tag<Fluid> fluid(Identifier id) {
-		return create(id, () -> AccessorFluidTags.getRequiredTags().getGroup());
+		return TagFactory.FLUID.create(id);
 	}
 
+	/**
+	 * @deprecated use {@link TagFactory#ITEM}
+	 */
+	@Deprecated
 	public static Tag<Item> item(Identifier id) {
-		return create(id, ItemTags::getTagGroup);
+		return TagFactory.ITEM.create(id);
 	}
 }
diff --git a/fabric-tag-extensions-v0/src/main/java/net/fabricmc/fabric/impl/tag/extension/FabricTagManagerHooks.java b/fabric-tag-extensions-v0/src/main/java/net/fabricmc/fabric/impl/tag/extension/FabricTagManagerHooks.java
new file mode 100644
index 000000000..005a473c2
--- /dev/null
+++ b/fabric-tag-extensions-v0/src/main/java/net/fabricmc/fabric/impl/tag/extension/FabricTagManagerHooks.java
@@ -0,0 +1,25 @@
+/*
+ * 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.tag.extension;
+
+import net.minecraft.tag.TagGroup;
+import net.minecraft.util.registry.Registry;
+import net.minecraft.util.registry.RegistryKey;
+
+public interface FabricTagManagerHooks {
+	void fabric_addTagGroup(RegistryKey<? extends Registry<?>> registryKey, TagGroup<?> tagGroup);
+}
diff --git a/fabric-tag-extensions-v0/src/main/java/net/fabricmc/fabric/impl/tag/extension/TagFactoryImpl.java b/fabric-tag-extensions-v0/src/main/java/net/fabricmc/fabric/impl/tag/extension/TagFactoryImpl.java
new file mode 100644
index 000000000..7e055fb5b
--- /dev/null
+++ b/fabric-tag-extensions-v0/src/main/java/net/fabricmc/fabric/impl/tag/extension/TagFactoryImpl.java
@@ -0,0 +1,119 @@
+/*
+ * 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.tag.extension;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Supplier;
+
+import com.google.common.base.Preconditions;
+import com.google.common.base.Stopwatch;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import net.minecraft.resource.ResourceManager;
+import net.minecraft.resource.ServerResourceManager;
+import net.minecraft.server.Main;
+import net.minecraft.tag.RequiredTagList;
+import net.minecraft.tag.RequiredTagListRegistry;
+import net.minecraft.tag.ServerTagManagerHolder;
+import net.minecraft.tag.Tag;
+import net.minecraft.tag.TagGroup;
+import net.minecraft.tag.TagGroupLoader;
+import net.minecraft.util.Identifier;
+import net.minecraft.util.dynamic.RegistryOps;
+import net.minecraft.util.registry.DynamicRegistryManager;
+import net.minecraft.util.registry.Registry;
+import net.minecraft.util.registry.RegistryKey;
+
+import net.fabricmc.fabric.api.tag.TagFactory;
+import net.fabricmc.fabric.mixin.tag.extension.DynamicRegistryManagerAccessor;
+
+@SuppressWarnings("ClassCanBeRecord")
+public final class TagFactoryImpl<T> implements TagFactory<T> {
+	private static final Logger LOGGER = LogManager.getLogger();
+
+	public static final Map<RegistryKey<? extends Registry<?>>, RequiredTagList<?>> TAG_LISTS = new HashMap<>();
+	public static final Set<RequiredTagList<?>> DYNAMICS = new HashSet<>();
+
+	public static <T> TagFactory<T> of(Supplier<TagGroup<T>> tagGroupSupplier) {
+		return new TagFactoryImpl<>(tagGroupSupplier);
+	}
+
+	@SuppressWarnings("unchecked")
+	public static <T> TagFactory<T> of(RegistryKey<? extends Registry<T>> registryKey, String dataType) {
+		RequiredTagList<T> tagList;
+
+		// Use already registered tag list for the registry if it has the same dataType, in case multiple mods tried to do it.
+		if (TAG_LISTS.containsKey(registryKey)) {
+			tagList = (RequiredTagList<T>) TAG_LISTS.get(registryKey);
+			// Throw an exception if the tagList has different dataType.
+			Preconditions.checkArgument(tagList.getDataType().equals(dataType), "Tag list for registry %s is already existed with data type %s", registryKey.getValue(), tagList.getDataType());
+		} else {
+			tagList = RequiredTagListRegistry.register(registryKey, dataType);
+			TAG_LISTS.put(registryKey, tagList);
+
+			// Check whether the registry dynamic.
+			if (DynamicRegistryManagerAccessor.getInfos().containsKey(registryKey)) {
+				DYNAMICS.add(tagList);
+			}
+		}
+
+		return of(tagList::getGroup);
+	}
+
+	/**
+	 * Manually load tags for dynamic registries and add the resulting tag group to the tag list.
+	 *
+	 * <p>Minecraft loads the resource manager before dynamic registries, making tags for them fail to load
+	 * if it mentions datapack entries. The solution is to manually load tags after the registry is loaded.
+	 *
+	 * <p>Look at server's {@link Main#main} function calls for {@link ServerResourceManager#reload} and
+	 * {@link RegistryOps#method_36574} for the relevant code.
+	 */
+	public static void loadDynamicRegistryTags(DynamicRegistryManager registryManager, ResourceManager resourceManager) {
+		Stopwatch stopwatch = Stopwatch.createStarted();
+		int loadedTags = 0;
+
+		for (RequiredTagList<?> tagList : DYNAMICS) {
+			RegistryKey<? extends Registry<?>> registryKey = tagList.getRegistryKey();
+			Registry<?> registry = registryManager.get(registryKey);
+			TagGroupLoader<?> tagGroupLoader = new TagGroupLoader<>(registry::getOrEmpty, tagList.getDataType());
+			TagGroup<?> tagGroup = tagGroupLoader.load(resourceManager);
+			((FabricTagManagerHooks) ServerTagManagerHolder.getTagManager()).fabric_addTagGroup(registryKey, tagGroup);
+			tagList.updateTagManager(ServerTagManagerHolder.getTagManager());
+			loadedTags += tagGroup.getTags().size();
+		}
+
+		if (loadedTags > 0) {
+			LOGGER.info("Loaded {} dynamic registry tags in {}", loadedTags, stopwatch);
+		}
+	}
+
+	private final Supplier<TagGroup<T>> tagGroupSupplier;
+
+	private TagFactoryImpl(Supplier<TagGroup<T>> tagGroupSupplier) {
+		this.tagGroupSupplier = tagGroupSupplier;
+	}
+
+	@Override
+	public Tag.Identified<T> create(Identifier id) {
+		return new TagDelegate<>(id, tagGroupSupplier);
+	}
+}
diff --git a/fabric-tag-extensions-v0/src/main/java/net/fabricmc/fabric/mixin/tag/extension/AccessorFluidTags.java b/fabric-tag-extensions-v0/src/main/java/net/fabricmc/fabric/mixin/tag/extension/DynamicRegistryManagerAccessor.java
similarity index 66%
rename from fabric-tag-extensions-v0/src/main/java/net/fabricmc/fabric/mixin/tag/extension/AccessorFluidTags.java
rename to fabric-tag-extensions-v0/src/main/java/net/fabricmc/fabric/mixin/tag/extension/DynamicRegistryManagerAccessor.java
index 8a594d177..614221590 100644
--- a/fabric-tag-extensions-v0/src/main/java/net/fabricmc/fabric/mixin/tag/extension/AccessorFluidTags.java
+++ b/fabric-tag-extensions-v0/src/main/java/net/fabricmc/fabric/mixin/tag/extension/DynamicRegistryManagerAccessor.java
@@ -16,17 +16,19 @@
 
 package net.fabricmc.fabric.mixin.tag.extension;
 
+import java.util.Map;
+
 import org.spongepowered.asm.mixin.Mixin;
 import org.spongepowered.asm.mixin.gen.Accessor;
 
-import net.minecraft.fluid.Fluid;
-import net.minecraft.tag.FluidTags;
-import net.minecraft.tag.RequiredTagList;
+import net.minecraft.util.registry.DynamicRegistryManager;
+import net.minecraft.util.registry.Registry;
+import net.minecraft.util.registry.RegistryKey;
 
-@Mixin(FluidTags.class)
-public interface AccessorFluidTags {
-	@Accessor("REQUIRED_TAGS")
-	static RequiredTagList<Fluid> getRequiredTags() {
-		throw new UnsupportedOperationException();
+@Mixin(DynamicRegistryManager.class)
+public interface DynamicRegistryManagerAccessor {
+	@Accessor("INFOS")
+	static Map<RegistryKey<? extends Registry<?>>, ?> getInfos() {
+		throw new AssertionError();
 	}
 }
diff --git a/fabric-tag-extensions-v0/src/main/java/net/fabricmc/fabric/mixin/tag/extension/MixinMinecraftServer.java b/fabric-tag-extensions-v0/src/main/java/net/fabricmc/fabric/mixin/tag/extension/MixinMinecraftServer.java
new file mode 100644
index 000000000..4a23e1312
--- /dev/null
+++ b/fabric-tag-extensions-v0/src/main/java/net/fabricmc/fabric/mixin/tag/extension/MixinMinecraftServer.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.mixin.tag.extension;
+
+import java.util.Collection;
+
+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.resource.ServerResourceManager;
+import net.minecraft.server.MinecraftServer;
+import net.minecraft.util.registry.DynamicRegistryManager;
+
+import net.fabricmc.fabric.impl.tag.extension.TagFactoryImpl;
+
+@Mixin(MinecraftServer.class)
+public abstract class MixinMinecraftServer {
+	@Shadow
+	@Final
+	protected DynamicRegistryManager.Impl registryManager;
+
+	@SuppressWarnings("UnresolvedMixinReference")
+	@Inject(method = "method_29440", at = @At(value = "INVOKE", target = "Lnet/minecraft/resource/ServerResourceManager;loadRegistryTags()V", shift = At.Shift.AFTER))
+	private void method_29440(Collection<?> collection, ServerResourceManager serverResourceManager, CallbackInfo ci) {
+		// Load dynamic registry tags on datapack reload.
+		TagFactoryImpl.loadDynamicRegistryTags(registryManager, serverResourceManager.getResourceManager());
+	}
+}
diff --git a/fabric-tag-extensions-v0/src/main/java/net/fabricmc/fabric/mixin/tag/extension/MixinRegistryOps.java b/fabric-tag-extensions-v0/src/main/java/net/fabricmc/fabric/mixin/tag/extension/MixinRegistryOps.java
new file mode 100644
index 000000000..2c73ca40f
--- /dev/null
+++ b/fabric-tag-extensions-v0/src/main/java/net/fabricmc/fabric/mixin/tag/extension/MixinRegistryOps.java
@@ -0,0 +1,41 @@
+/*
+ * 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.tag.extension;
+
+import com.mojang.serialization.DynamicOps;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
+
+import net.minecraft.resource.ResourceManager;
+import net.minecraft.util.dynamic.RegistryOps;
+import net.minecraft.util.registry.DynamicRegistryManager;
+
+import net.fabricmc.fabric.impl.tag.extension.TagFactoryImpl;
+
+/**
+ * This mixin loads dynamic registry tags right after datapack entries loaded.
+ * Needs a higher priority so it will be called before biome modifications.
+ */
+@Mixin(value = RegistryOps.class, priority = 900)
+public class MixinRegistryOps {
+	@Inject(method = "method_36574", at = @At("RETURN"))
+	private static <T> void afterDynamicRegistryLoaded(DynamicOps<T> dynamicOps, ResourceManager resourceManager, DynamicRegistryManager registryManager, CallbackInfoReturnable<RegistryOps<T>> cir) {
+		TagFactoryImpl.loadDynamicRegistryTags(registryManager, resourceManager);
+	}
+}
diff --git a/fabric-tag-extensions-v0/src/main/java/net/fabricmc/fabric/mixin/tag/extension/MixinRequiredTagListRegistry.java b/fabric-tag-extensions-v0/src/main/java/net/fabricmc/fabric/mixin/tag/extension/MixinRequiredTagListRegistry.java
new file mode 100644
index 000000000..eec8f6254
--- /dev/null
+++ b/fabric-tag-extensions-v0/src/main/java/net/fabricmc/fabric/mixin/tag/extension/MixinRequiredTagListRegistry.java
@@ -0,0 +1,41 @@
+/*
+ * 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.tag.extension;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
+
+import net.minecraft.tag.RequiredTagList;
+import net.minecraft.tag.RequiredTagListRegistry;
+
+import net.fabricmc.fabric.impl.tag.extension.TagFactoryImpl;
+
+@Mixin(RequiredTagListRegistry.class)
+public class MixinRequiredTagListRegistry {
+	@Inject(method = "getBuiltinTags", at = @At("TAIL"), cancellable = true)
+	private static void getBuiltinTags(CallbackInfoReturnable<Set<RequiredTagList<?>>> cir) {
+		// Add tag lists registered on fabric to the map.
+		Set<RequiredTagList<?>> set = new HashSet<>(cir.getReturnValue());
+		set.addAll(TagFactoryImpl.TAG_LISTS.values());
+		cir.setReturnValue(set);
+	}
+}
diff --git a/fabric-tag-extensions-v0/src/main/java/net/fabricmc/fabric/mixin/tag/extension/MixinTagManager.java b/fabric-tag-extensions-v0/src/main/java/net/fabricmc/fabric/mixin/tag/extension/MixinTagManager.java
new file mode 100644
index 000000000..e3b6b6497
--- /dev/null
+++ b/fabric-tag-extensions-v0/src/main/java/net/fabricmc/fabric/mixin/tag/extension/MixinTagManager.java
@@ -0,0 +1,54 @@
+/*
+ * 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.tag.extension;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.spongepowered.asm.mixin.Final;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Mutable;
+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.tag.TagGroup;
+import net.minecraft.tag.TagManager;
+import net.minecraft.util.registry.Registry;
+import net.minecraft.util.registry.RegistryKey;
+
+import net.fabricmc.fabric.impl.tag.extension.FabricTagManagerHooks;
+
+@Mixin(TagManager.class)
+public class MixinTagManager implements FabricTagManagerHooks {
+	@Shadow
+	@Mutable
+	@Final
+	private Map<RegistryKey<? extends Registry<?>>, TagGroup<?>> tagGroups;
+
+	@Inject(method = "<init>", at = @At("TAIL"))
+	private void init(Map<RegistryKey<? extends Registry<?>>, TagGroup<?>> tagGroups, CallbackInfo ci) {
+		// Make it mutable so we can add dynamic registry tags later.
+		this.tagGroups = new HashMap<>(tagGroups);
+	}
+
+	@Override
+	public void fabric_addTagGroup(RegistryKey<? extends Registry<?>> registryKey, TagGroup<?> tagGroup) {
+		tagGroups.put(registryKey, tagGroup);
+	}
+}
diff --git a/fabric-tag-extensions-v0/src/main/java/net/fabricmc/fabric/mixin/tag/extension/MixinTagManagerLoader.java b/fabric-tag-extensions-v0/src/main/java/net/fabricmc/fabric/mixin/tag/extension/MixinTagManagerLoader.java
new file mode 100644
index 000000000..5562365ba
--- /dev/null
+++ b/fabric-tag-extensions-v0/src/main/java/net/fabricmc/fabric/mixin/tag/extension/MixinTagManagerLoader.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.mixin.tag.extension;
+
+import java.util.List;
+import java.util.concurrent.Executor;
+
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
+
+import net.minecraft.resource.ResourceManager;
+import net.minecraft.tag.RequiredTagList;
+import net.minecraft.tag.TagManagerLoader;
+
+import net.fabricmc.fabric.impl.tag.extension.TagFactoryImpl;
+
+@Mixin(TagManagerLoader.class)
+public abstract class MixinTagManagerLoader {
+	// RequiredTagListRegistry.forEach in reload.
+	@SuppressWarnings("UnresolvedMixinReference")
+	@Inject(method = "method_33179", at = @At("HEAD"), cancellable = true)
+	private void method_33179(ResourceManager resourceManager, Executor executor, List<?> list, RequiredTagList<?> requiredTagList, CallbackInfo ci) {
+		// Don't load dynamic registry tags now, we need to load them after the dynamic registry.
+		if (TagFactoryImpl.DYNAMICS.contains(requiredTagList)) {
+			ci.cancel();
+		}
+	}
+}
diff --git a/fabric-tag-extensions-v0/src/main/resources/fabric-tag-extensions-v0.mixins.json b/fabric-tag-extensions-v0/src/main/resources/fabric-tag-extensions-v0.mixins.json
index 2b234483f..4822ac834 100644
--- a/fabric-tag-extensions-v0/src/main/resources/fabric-tag-extensions-v0.mixins.json
+++ b/fabric-tag-extensions-v0/src/main/resources/fabric-tag-extensions-v0.mixins.json
@@ -3,10 +3,15 @@
   "package": "net.fabricmc.fabric.mixin.tag.extension",
   "compatibilityLevel": "JAVA_16",
   "mixins": [
-    "AccessorFluidTags",
+    "DynamicRegistryManagerAccessor",
+    "MixinMinecraftServer",
     "MixinObjectBuilder",
+    "MixinRegistryOps",
+    "MixinRequiredTagListRegistry",
+    "MixinTagBuilder",
     "MixinTagImpl",
-    "MixinTagBuilder"
+    "MixinTagManager",
+    "MixinTagManagerLoader"
   ],
   "injectors": {
     "defaultRequire": 1
diff --git a/fabric-tag-extensions-v0/src/testmod/java/net/fabricmc/fabric/test/tag/extension/TagExtensionTest.java b/fabric-tag-extensions-v0/src/testmod/java/net/fabricmc/fabric/test/tag/extension/TagExtensionTest.java
new file mode 100644
index 000000000..b080c794f
--- /dev/null
+++ b/fabric-tag-extensions-v0/src/testmod/java/net/fabricmc/fabric/test/tag/extension/TagExtensionTest.java
@@ -0,0 +1,61 @@
+/*
+ * 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.tag.extension;
+
+import static net.minecraft.server.command.CommandManager.literal;
+
+import java.util.Map;
+import java.util.Optional;
+
+import net.minecraft.tag.Tag;
+import net.minecraft.text.LiteralText;
+import net.minecraft.util.Identifier;
+import net.minecraft.util.registry.Registry;
+import net.minecraft.util.registry.RegistryKey;
+import net.minecraft.world.biome.Biome;
+
+import net.fabricmc.api.ModInitializer;
+import net.fabricmc.fabric.api.command.v1.CommandRegistrationCallback;
+import net.fabricmc.fabric.api.tag.TagFactory;
+
+public class TagExtensionTest implements ModInitializer {
+	static final Tag<Biome> FACTORY_TEST = TagFactory.BIOME.create(new Identifier("fabric-tag-extensions-v0-testmod:factory_test"));
+
+	@Override
+	public void onInitialize() {
+		CommandRegistrationCallback.EVENT.register((dispatcher, dedicated) -> dispatcher.register(literal("biome_tag_test")
+				.then(literal("factory").executes(context -> {
+					FACTORY_TEST.values().forEach(biome -> {
+						Identifier id = context.getSource().getRegistryManager().get(Registry.BIOME_KEY).getId(biome);
+						context.getSource().sendFeedback(new LiteralText(id.toString()), false);
+					});
+					return 1;
+				}))
+				.then(literal("list_all").executes(context -> {
+					Map<Identifier, Tag<Biome>> tags = context.getSource().getServer().getTagManager().getOrCreateTagGroup(Registry.BIOME_KEY).getTags();
+					tags.forEach((tagId, tag) -> {
+						LiteralText text = new LiteralText(tagId.toString() + ":");
+						tag.values().forEach(biome -> {
+							Optional<RegistryKey<Biome>> biomeKey = context.getSource().getRegistryManager().get(Registry.BIOME_KEY).getKey(biome);
+							biomeKey.ifPresent(key -> text.append(" " + key.getValue()));
+						});
+						context.getSource().sendFeedback(text, false);
+					});
+					return 1;
+				}))));
+	}
+}
diff --git a/fabric-tag-extensions-v0/src/testmod/java/net/fabricmc/fabric/test/tag/extension/TagExtensionTestClient.java b/fabric-tag-extensions-v0/src/testmod/java/net/fabricmc/fabric/test/tag/extension/TagExtensionTestClient.java
new file mode 100644
index 000000000..22295a972
--- /dev/null
+++ b/fabric-tag-extensions-v0/src/testmod/java/net/fabricmc/fabric/test/tag/extension/TagExtensionTestClient.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.test.tag.extension;
+
+import org.lwjgl.glfw.GLFW;
+
+import net.minecraft.client.option.KeyBinding;
+import net.minecraft.text.LiteralText;
+import net.minecraft.util.Identifier;
+import net.minecraft.util.registry.Registry;
+
+import net.fabricmc.api.ClientModInitializer;
+import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
+import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper;
+
+public class TagExtensionTestClient implements ClientModInitializer {
+	static final KeyBinding KEY_BINDING = KeyBindingHelper.registerKeyBinding(new KeyBinding("tag_test", GLFW.GLFW_KEY_EQUAL, "tag_test"));
+
+	@Override
+	public void onInitializeClient() {
+		ClientTickEvents.END_CLIENT_TICK.register(client -> {
+			if (KEY_BINDING.isPressed()) {
+				TagExtensionTest.FACTORY_TEST.values().forEach(biome -> {
+					Identifier id = client.getNetworkHandler().getRegistryManager().get(Registry.BIOME_KEY).getId(biome);
+					client.player.sendMessage(new LiteralText(id.toString()), false);
+				});
+			}
+		});
+	}
+}
diff --git a/fabric-tag-extensions-v0/src/testmod/resources/data/fabric-tag-extensions-v0-testmod/tags/biomes/factory_test.json b/fabric-tag-extensions-v0/src/testmod/resources/data/fabric-tag-extensions-v0-testmod/tags/biomes/factory_test.json
new file mode 100644
index 000000000..f6fea9b4f
--- /dev/null
+++ b/fabric-tag-extensions-v0/src/testmod/resources/data/fabric-tag-extensions-v0-testmod/tags/biomes/factory_test.json
@@ -0,0 +1,8 @@
+{
+  "replace": false,
+  "values": [
+    "minecraft:plains",
+    "minecraft:desert",
+    "fabric-tag-extensions-v0-testmod:test"
+  ]
+}
diff --git a/fabric-tag-extensions-v0/src/testmod/resources/data/fabric-tag-extensions-v0-testmod/tags/biomes/json_only_test.json b/fabric-tag-extensions-v0/src/testmod/resources/data/fabric-tag-extensions-v0-testmod/tags/biomes/json_only_test.json
new file mode 100644
index 000000000..06552ac6a
--- /dev/null
+++ b/fabric-tag-extensions-v0/src/testmod/resources/data/fabric-tag-extensions-v0-testmod/tags/biomes/json_only_test.json
@@ -0,0 +1,7 @@
+{
+  "replace": false,
+  "values": [
+    "minecraft:forest",
+    "minecraft:taiga"
+  ]
+}
diff --git a/fabric-tag-extensions-v0/src/testmod/resources/data/fabric-tag-extensions-v0-testmod/worldgen/biome/test.json b/fabric-tag-extensions-v0/src/testmod/resources/data/fabric-tag-extensions-v0-testmod/worldgen/biome/test.json
new file mode 100644
index 000000000..c3aefe3a1
--- /dev/null
+++ b/fabric-tag-extensions-v0/src/testmod/resources/data/fabric-tag-extensions-v0-testmod/worldgen/biome/test.json
@@ -0,0 +1,20 @@
+{
+  "surface_builder": "minecraft:grass",
+  "depth": 0.125,
+  "scale": 0.05,
+  "temperature": 0.8,
+  "downfall": 0.4,
+  "precipitation": "rain",
+  "category": "plains",
+  "effects": {
+    "sky_color": 7907327,
+    "fog_color": 12638463,
+    "water_color": 4159204,
+    "water_fog_color": 329011
+  },
+  "starts": [],
+  "spawners": {},
+  "spawn_costs": {},
+  "carvers": {},
+  "features": []
+}
diff --git a/fabric-tag-extensions-v0/src/testmod/resources/fabric.mod.json b/fabric-tag-extensions-v0/src/testmod/resources/fabric.mod.json
new file mode 100644
index 000000000..4c94b66df
--- /dev/null
+++ b/fabric-tag-extensions-v0/src/testmod/resources/fabric.mod.json
@@ -0,0 +1,19 @@
+{
+  "schemaVersion": 1,
+  "id": "fabric-tag-extensions-v0-testmod",
+  "name": "Fabric Tag Extensions (v0) Test Mod",
+  "version": "1.0.0",
+  "environment": "*",
+  "license": "Apache-2.0",
+  "depends": {
+    "fabric-tag-extensions-v0": "*"
+  },
+  "entrypoints": {
+    "main": [
+      "net.fabricmc.fabric.test.tag.extension.TagExtensionTest"
+    ],
+    "client": [
+      "net.fabricmc.fabric.test.tag.extension.TagExtensionTestClient"
+    ]
+  }
+}