diff --git a/fabric-data-generation-api-v1/build.gradle b/fabric-data-generation-api-v1/build.gradle index 60560e7edd..07d4bf621d 100644 --- a/fabric-data-generation-api-v1/build.gradle +++ b/fabric-data-generation-api-v1/build.gradle @@ -7,6 +7,7 @@ moduleDependencies(project, [ 'fabric-networking-api-v1', 'fabric-resource-conditions-api-v1', 'fabric-item-group-api-v1', + 'fabric-tag-api-v1', 'fabric-recipe-api-v1' ]) diff --git a/fabric-data-generation-api-v1/src/main/java/net/fabricmc/fabric/api/datagen/v1/provider/FabricTagProvider.java b/fabric-data-generation-api-v1/src/main/java/net/fabricmc/fabric/api/datagen/v1/provider/FabricTagProvider.java index cac205fc07..bf2b9bac76 100644 --- a/fabric-data-generation-api-v1/src/main/java/net/fabricmc/fabric/api/datagen/v1/provider/FabricTagProvider.java +++ b/fabric-data-generation-api-v1/src/main/java/net/fabricmc/fabric/api/datagen/v1/provider/FabricTagProvider.java @@ -16,6 +16,11 @@ package net.fabricmc.fabric.api.datagen.v1.provider; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.concurrent.CompletableFuture; @@ -70,6 +75,9 @@ * @see EntityTypeTagProvider */ public abstract class FabricTagProvider extends TagProvider { + private final FabricDataOutput output; + private final Map aliasGroupBuilders = new HashMap<>(); + /** * Constructs a new {@link FabricTagProvider} with the default computed path. * @@ -80,6 +88,7 @@ public abstract class FabricTagProvider extends TagProvider { */ public FabricTagProvider(FabricDataOutput output, RegistryKey> registryKey, CompletableFuture registriesFuture) { super(output, registryKey, registriesFuture); + this.output = output; } /** @@ -116,6 +125,34 @@ protected FabricTagBuilder getOrCreateTagBuilder(TagKey tag) { return new FabricTagBuilder(super.getOrCreateTagBuilder(tag)); } + /** + * Gets an {@link AliasGroupBuilder} with the given ID. + * + * @param groupId the group ID + * @return the alias group builder + */ + protected AliasGroupBuilder getOrCreateAliasGroupBuilder(Identifier groupId) { + return aliasGroupBuilders.computeIfAbsent(groupId, key -> new AliasGroupBuilder()); + } + + /** + * Gets an {@link AliasGroupBuilder} with the given ID. + * + * @param group the group name + * @return the alias group builder + */ + protected AliasGroupBuilder getOrCreateAliasGroupBuilder(String group) { + Identifier groupId = Identifier.of(output.getModId(), group); + return aliasGroupBuilders.computeIfAbsent(groupId, key -> new AliasGroupBuilder()); + } + + /** + * {@return a read-only map of alias group builders by the alias group ID} + */ + public Map getAliasGroupBuilders() { + return Collections.unmodifiableMap(aliasGroupBuilders); + } + /** * Extend this class to create {@link Block} tags in the "/blocks" tag directory. */ @@ -396,4 +433,49 @@ public final FabricTagBuilder add(RegistryKey... registryKeys) { return this; } } + + /** + * A builder for tag alias groups. + */ + public final class AliasGroupBuilder { + private final List> tags = new ArrayList<>(); + + /** + * {@return a read-only list of the tags in this alias group} + */ + public List> getTags() { + return Collections.unmodifiableList(tags); + } + + public AliasGroupBuilder add(TagKey tag) { + if (tag.registryRef() != registryRef) { + throw new IllegalArgumentException("Tag " + tag + " isn't from the registry " + registryRef); + } + + this.tags.add(tag); + return this; + } + + @SafeVarargs + public final AliasGroupBuilder add(TagKey... tags) { + for (TagKey tag : tags) { + add(tag); + } + + return this; + } + + public AliasGroupBuilder add(Identifier tag) { + this.tags.add(TagKey.of(registryRef, tag)); + return this; + } + + public AliasGroupBuilder add(Identifier... tags) { + for (Identifier tag : tags) { + this.tags.add(TagKey.of(registryRef, tag)); + } + + return this; + } + } } diff --git a/fabric-data-generation-api-v1/src/main/java/net/fabricmc/fabric/impl/datagen/TagAliasGenerator.java b/fabric-data-generation-api-v1/src/main/java/net/fabricmc/fabric/impl/datagen/TagAliasGenerator.java new file mode 100644 index 0000000000..6d9ce45902 --- /dev/null +++ b/fabric-data-generation-api-v1/src/main/java/net/fabricmc/fabric/impl/datagen/TagAliasGenerator.java @@ -0,0 +1,49 @@ +/* + * 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.datagen; + +import java.nio.file.Path; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +import net.minecraft.data.DataOutput; +import net.minecraft.data.DataProvider; +import net.minecraft.data.DataWriter; +import net.minecraft.registry.Registry; +import net.minecraft.registry.RegistryKey; +import net.minecraft.registry.tag.TagKey; +import net.minecraft.util.Identifier; + +import net.fabricmc.fabric.api.tag.v1.TagAliasGroup; + +public final class TagAliasGenerator { + public static String getDirectory(RegistryKey> registryKey) { + String directory = "fabric/tag_aliases/"; + Identifier registryId = registryKey.getValue(); + + if (!Identifier.DEFAULT_NAMESPACE.equals(registryId.getNamespace())) { + directory += registryId.getNamespace() + '/'; + } + + return directory + registryId.getPath(); + } + + public static CompletableFuture writeTagAlias(DataWriter writer, DataOutput.PathResolver pathResolver, RegistryKey> registryRef, Identifier groupId, List> tags) { + Path path = pathResolver.resolveJson(groupId); + return DataProvider.writeCodecToPath(writer, TagAliasGroup.codec(registryRef), new TagAliasGroup<>(tags), path); + } +} diff --git a/fabric-data-generation-api-v1/src/main/java/net/fabricmc/fabric/mixin/datagen/TagProviderMixin.java b/fabric-data-generation-api-v1/src/main/java/net/fabricmc/fabric/mixin/datagen/TagProviderMixin.java index 3ae2955117..2a1fcd0074 100644 --- a/fabric-data-generation-api-v1/src/main/java/net/fabricmc/fabric/mixin/datagen/TagProviderMixin.java +++ b/fabric-data-generation-api-v1/src/main/java/net/fabricmc/fabric/mixin/datagen/TagProviderMixin.java @@ -16,18 +16,48 @@ package net.fabricmc.fabric.mixin.datagen; +import java.util.Arrays; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; import com.llamalad7.mixinextras.sugar.Local; +import org.spongepowered.asm.mixin.Final; 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.ModifyArg; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import net.minecraft.data.DataOutput; +import net.minecraft.data.DataWriter; import net.minecraft.data.server.tag.TagProvider; +import net.minecraft.registry.Registry; +import net.minecraft.registry.RegistryKey; import net.minecraft.registry.tag.TagBuilder; +import net.minecraft.util.Identifier; +import net.fabricmc.fabric.api.datagen.v1.provider.FabricTagProvider; import net.fabricmc.fabric.impl.datagen.FabricTagBuilder; +import net.fabricmc.fabric.impl.datagen.TagAliasGenerator; @Mixin(TagProvider.class) -public class TagProviderMixin { +public class TagProviderMixin { + @Shadow + @Final + protected RegistryKey> registryRef; + + @Unique + private DataOutput.PathResolver tagAliasPathResolver; + + @Inject(method = "(Lnet/minecraft/data/DataOutput;Lnet/minecraft/registry/RegistryKey;Ljava/util/concurrent/CompletableFuture;Ljava/util/concurrent/CompletableFuture;)V", at = @At("RETURN")) + private void initPathResolver(DataOutput output, RegistryKey> registryRef, CompletableFuture registriesFuture, CompletableFuture parentTagLookupFuture, CallbackInfo info) { + tagAliasPathResolver = output.getResolver(DataOutput.OutputType.DATA_PACK, TagAliasGenerator.getDirectory(registryRef)); + } + @ModifyArg(method = "method_27046", at = @At(value = "INVOKE", target = "Lnet/minecraft/registry/tag/TagFile;(Ljava/util/List;Z)V"), index = 1) private boolean addReplaced(boolean replaced, @Local TagBuilder tagBuilder) { if (tagBuilder instanceof FabricTagBuilder fabricTagBuilder) { @@ -36,4 +66,23 @@ private boolean addReplaced(boolean replaced, @Local TagBuilder tagBuilder) { return replaced; } + + @SuppressWarnings("unchecked") + @WrapOperation(method = "method_49659", at = @At(value = "INVOKE", target = "Ljava/util/concurrent/CompletableFuture;allOf([Ljava/util/concurrent/CompletableFuture;)Ljava/util/concurrent/CompletableFuture;")) + private CompletableFuture addTagAliasGroupBuilders(CompletableFuture[] futures, Operation> original, @Local(argsOnly = true) DataWriter writer) { + if ((Object) this instanceof FabricTagProvider) { + // Note: no pattern matching instanceof so that we can cast directly to FabricTagProvider instead of a wildcard + Map.AliasGroupBuilder> builders = ((FabricTagProvider) (Object) this).getAliasGroupBuilders(); + CompletableFuture[] newFutures = Arrays.copyOf(futures, futures.length + builders.size()); + int index = futures.length; + + for (Map.Entry.AliasGroupBuilder> entry : builders.entrySet()) { + newFutures[index++] = TagAliasGenerator.writeTagAlias(writer, tagAliasPathResolver, registryRef, entry.getKey(), entry.getValue().getTags()); + } + + return original.call((Object) newFutures); + } else { + return original.call((Object) futures); + } + } } diff --git a/fabric-data-generation-api-v1/src/main/resources/fabric.mod.json b/fabric-data-generation-api-v1/src/main/resources/fabric.mod.json index 8f1a7a6ed0..cff1a04de7 100644 --- a/fabric-data-generation-api-v1/src/main/resources/fabric.mod.json +++ b/fabric-data-generation-api-v1/src/main/resources/fabric.mod.json @@ -16,7 +16,8 @@ "FabricMC" ], "depends": { - "fabricloader": ">=0.16.8" + "fabricloader": ">=0.16.8", + "fabric-tag-api-v1": "*" }, "description": "Allows for automatic data generation.", "mixins": [ diff --git a/fabric-tag-api-v1/build.gradle b/fabric-tag-api-v1/build.gradle new file mode 100644 index 0000000000..a9a380ad4a --- /dev/null +++ b/fabric-tag-api-v1/build.gradle @@ -0,0 +1,10 @@ +version = getSubprojectVersion(project) + +loom { + accessWidenerPath = file('src/main/resources/fabric-tag-api-v1.accesswidener') +} + +moduleDependencies(project, [ + 'fabric-api-base', + 'fabric-resource-loader-v0' +]) diff --git a/fabric-tag-api-v1/src/main/java/net/fabricmc/fabric/api/tag/v1/TagAliasGroup.java b/fabric-tag-api-v1/src/main/java/net/fabricmc/fabric/api/tag/v1/TagAliasGroup.java new file mode 100644 index 0000000000..25cfcdb9ff --- /dev/null +++ b/fabric-tag-api-v1/src/main/java/net/fabricmc/fabric/api/tag/v1/TagAliasGroup.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.api.tag.v1; + +import java.util.List; + +import com.mojang.serialization.Codec; + +import net.minecraft.registry.Registry; +import net.minecraft.registry.RegistryKey; +import net.minecraft.registry.tag.TagKey; + +/** + * A group of tags that refer to the same set of registry entries. + * + *

Tag alias groups can be defined in data packs in the {@code data//fabric/tag_aliases/} + * directory. {@code } is the path of the registry's ID, prefixed with {@code /} if it's + * not {@value net.minecraft.util.Identifier#DEFAULT_NAMESPACE}. + * + *

The JSON format of tag alias groups is an object with a {@code tags} list containing plain tag IDs. + * + *

If multiple tag alias groups include a tag, the groups will be combined and each tag will be an alias + * for the same contents. + * + * @param tags the tags in the group, must be from the same registry + * @param the type of registry entries in the tags + */ +public record TagAliasGroup(List> tags) { + /** + * {@return the codec for tag alias groups in the specified registry} + * + * @param registryKey the key of the registry where the tags are from + * @param the entry type + */ + public static Codec> codec(RegistryKey> registryKey) { + return TagKey.unprefixedCodec(registryKey) + .listOf() + .fieldOf("tags") + .xmap(TagAliasGroup::new, TagAliasGroup::tags) + .codec(); + } +} diff --git a/fabric-tag-api-v1/src/main/java/net/fabricmc/fabric/api/tag/v1/package-info.java b/fabric-tag-api-v1/src/main/java/net/fabricmc/fabric/api/tag/v1/package-info.java new file mode 100644 index 0000000000..22d56f5be4 --- /dev/null +++ b/fabric-tag-api-v1/src/main/java/net/fabricmc/fabric/api/tag/v1/package-info.java @@ -0,0 +1,22 @@ +/* + * 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. + */ + +/** + * The Fabric Tag API for working with {@linkplain net.minecraft.registry.tag.TagKey tags}. + * + * @see net.fabricmc.fabric.api.tag.v1.TagAliasGroup + */ +package net.fabricmc.fabric.api.tag.v1; diff --git a/fabric-tag-api-v1/src/main/java/net/fabricmc/fabric/impl/tag/SimpleRegistryExtension.java b/fabric-tag-api-v1/src/main/java/net/fabricmc/fabric/impl/tag/SimpleRegistryExtension.java new file mode 100644 index 0000000000..661beb0c9e --- /dev/null +++ b/fabric-tag-api-v1/src/main/java/net/fabricmc/fabric/impl/tag/SimpleRegistryExtension.java @@ -0,0 +1,21 @@ +/* + * 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; + +public interface SimpleRegistryExtension extends TagAliasEnabledRegistry { + void fabric_applyPendingTagAliases(); +} diff --git a/fabric-tag-api-v1/src/main/java/net/fabricmc/fabric/impl/tag/TagAliasEnabledRegistry.java b/fabric-tag-api-v1/src/main/java/net/fabricmc/fabric/impl/tag/TagAliasEnabledRegistry.java new file mode 100644 index 0000000000..50eb6f576c --- /dev/null +++ b/fabric-tag-api-v1/src/main/java/net/fabricmc/fabric/impl/tag/TagAliasEnabledRegistry.java @@ -0,0 +1,26 @@ +/* + * 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; + +import java.util.Map; +import java.util.Set; + +import net.minecraft.registry.tag.TagKey; + +public interface TagAliasEnabledRegistry { + void fabric_applyTagAliases(Map, Set>> aliasGroups); +} diff --git a/fabric-tag-api-v1/src/main/java/net/fabricmc/fabric/impl/tag/TagAliasLoader.java b/fabric-tag-api-v1/src/main/java/net/fabricmc/fabric/impl/tag/TagAliasLoader.java new file mode 100644 index 0000000000..65b46c8382 --- /dev/null +++ b/fabric-tag-api-v1/src/main/java/net/fabricmc/fabric/impl/tag/TagAliasLoader.java @@ -0,0 +1,156 @@ +/* + * 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; + +import java.io.IOException; +import java.io.Reader; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.google.gson.JsonElement; +import com.google.gson.JsonParseException; +import com.google.gson.JsonParser; +import com.mojang.serialization.Codec; +import com.mojang.serialization.DataResult; +import com.mojang.serialization.JsonOps; +import com.mojang.serialization.Lifecycle; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import net.minecraft.registry.Registry; +import net.minecraft.registry.RegistryKey; +import net.minecraft.registry.RegistryWrapper; +import net.minecraft.registry.tag.TagKey; +import net.minecraft.resource.Resource; +import net.minecraft.resource.ResourceFinder; +import net.minecraft.resource.ResourceManager; +import net.minecraft.resource.SinglePreparationResourceReloader; +import net.minecraft.util.Identifier; +import net.minecraft.util.profiler.Profiler; + +import net.fabricmc.fabric.api.resource.IdentifiableResourceReloadListener; +import net.fabricmc.fabric.api.tag.v1.TagAliasGroup; + +public final class TagAliasLoader extends SinglePreparationResourceReloader>, List>> implements IdentifiableResourceReloadListener { + public static final Identifier ID = Identifier.of("fabric-tag-api-v1", "tag_alias_groups"); + + private static final Logger LOGGER = LoggerFactory.getLogger(TagAliasLoader.class); + private final RegistryWrapper.WrapperLookup registries; + + public TagAliasLoader(RegistryWrapper.WrapperLookup registries) { + this.registries = registries; + } + + @Override + public Identifier getFabricId() { + return ID; + } + + @SuppressWarnings("unchecked") + @Override + protected Map>, List> prepare(ResourceManager manager, Profiler profiler) { + Map>, List> dataByRegistry = new HashMap<>(); + Iterator>> registryIterator = registries.streamAllRegistryKeys().iterator(); + + while (registryIterator.hasNext()) { + RegistryKey> registryKey = registryIterator.next(); + ResourceFinder resourceFinder = ResourceFinder.json(getDirectory(registryKey)); + + for (Map.Entry entry : resourceFinder.findResources(manager).entrySet()) { + Identifier resourcePath = entry.getKey(); + Identifier groupId = resourceFinder.toResourceId(resourcePath); + + try (Reader reader = entry.getValue().getReader()) { + JsonElement json = JsonParser.parseReader(reader); + Codec> codec = TagAliasGroup.codec((RegistryKey>) registryKey); + + switch (codec.parse(JsonOps.INSTANCE, json)) { + case DataResult.Success(TagAliasGroup group, Lifecycle unused) -> { + var data = new Data(groupId, group); + dataByRegistry.computeIfAbsent(registryKey, key -> new ArrayList<>()).add(data); + } + case DataResult.Error> error -> { + LOGGER.error("[Fabric] Couldn't parse tag alias group file '{}' from '{}': {}", groupId, resourcePath, error.message()); + } + } + } catch (IOException | JsonParseException e) { + LOGGER.error("[Fabric] Couldn't parse tag alias group file '{}' from '{}'", groupId, resourcePath, e); + } + } + } + + return dataByRegistry; + } + + private static String getDirectory(RegistryKey> registryKey) { + String directory = "fabric/tag_aliases/"; + Identifier registryId = registryKey.getValue(); + + if (!Identifier.DEFAULT_NAMESPACE.equals(registryId.getNamespace())) { + directory += registryId.getNamespace() + '/'; + } + + return directory + registryId.getPath(); + } + + @Override + protected void apply(Map>, List> prepared, ResourceManager manager, Profiler profiler) { + for (Map.Entry>, List> entry : prepared.entrySet()) { + Map, Set>> groupsByTag = new HashMap<>(); + + for (Data data : entry.getValue()) { + Set> group = new HashSet<>(data.group.tags()); + + for (TagKey tag : data.group.tags()) { + Set> oldGroup = groupsByTag.get(tag); + + // If there's an old group... + if (oldGroup != null) { + // ...merge all of its tags into the current group... + group.addAll(oldGroup); + + // ...and replace the recorded group of each tag in the old group with the new group. + for (TagKey other : oldGroup) { + groupsByTag.put(other, group); + } + } + + groupsByTag.put(tag, group); + } + } + + // Remove any groups of one tag, we don't need to apply them. + groupsByTag.values().removeIf(tags -> tags.size() == 1); + + RegistryWrapper.Impl wrapper = registries.getOrThrow(entry.getKey()); + + if (wrapper instanceof TagAliasEnabledRegistry registry) { + registry.fabric_applyTagAliases(groupsByTag); + } else { + LOGGER.error("[Fabric] Couldn't apply tag aliases to registry wrapper {} ({}), please report this!", wrapper, entry.getKey().getValue()); + } + } + } + + protected record Data(Identifier groupId, TagAliasGroup group) { + } +} diff --git a/fabric-tag-api-v1/src/main/java/net/fabricmc/fabric/impl/tag/TagInit.java b/fabric-tag-api-v1/src/main/java/net/fabricmc/fabric/impl/tag/TagInit.java new file mode 100644 index 0000000000..dbfb528ac8 --- /dev/null +++ b/fabric-tag-api-v1/src/main/java/net/fabricmc/fabric/impl/tag/TagInit.java @@ -0,0 +1,29 @@ +/* + * 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; + +import net.minecraft.resource.ResourceType; + +import net.fabricmc.api.ModInitializer; +import net.fabricmc.fabric.api.resource.ResourceManagerHelper; + +public final class TagInit implements ModInitializer { + @Override + public void onInitialize() { + ResourceManagerHelper.get(ResourceType.SERVER_DATA).registerReloadListener(TagAliasLoader.ID, TagAliasLoader::new); + } +} diff --git a/fabric-tag-api-v1/src/main/java/net/fabricmc/fabric/mixin/tag/SimpleRegistry2Mixin.java b/fabric-tag-api-v1/src/main/java/net/fabricmc/fabric/mixin/tag/SimpleRegistry2Mixin.java new file mode 100644 index 0000000000..aafb14bc87 --- /dev/null +++ b/fabric-tag-api-v1/src/main/java/net/fabricmc/fabric/mixin/tag/SimpleRegistry2Mixin.java @@ -0,0 +1,40 @@ +/* + * 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; + +import java.util.Map; +import java.util.Set; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +import net.minecraft.registry.RegistryWrapper; +import net.minecraft.registry.tag.TagKey; + +import net.fabricmc.fabric.impl.tag.TagAliasEnabledRegistry; + +@Mixin(targets = "net.minecraft.registry.SimpleRegistry$2") +abstract class SimpleRegistry2Mixin implements TagAliasEnabledRegistry { + // returns SimpleRegistry.this, which implements TagAliasEnabledRegistry + @Shadow + public abstract RegistryWrapper.Impl getBase(); + + @Override + public void fabric_applyTagAliases(Map, Set>> aliasGroups) { + ((TagAliasEnabledRegistry) getBase()).fabric_applyTagAliases(aliasGroups); + } +} diff --git a/fabric-tag-api-v1/src/main/java/net/fabricmc/fabric/mixin/tag/SimpleRegistry3Mixin.java b/fabric-tag-api-v1/src/main/java/net/fabricmc/fabric/mixin/tag/SimpleRegistry3Mixin.java new file mode 100644 index 0000000000..cb572fa93c --- /dev/null +++ b/fabric-tag-api-v1/src/main/java/net/fabricmc/fabric/mixin/tag/SimpleRegistry3Mixin.java @@ -0,0 +1,40 @@ +/* + * 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; + +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.registry.SimpleRegistry; + +import net.fabricmc.fabric.impl.tag.SimpleRegistryExtension; + +@Mixin(targets = "net.minecraft.registry.SimpleRegistry$3") +abstract class SimpleRegistry3Mixin { + @Shadow + @Final + SimpleRegistry field_53689; + + @Inject(method = "apply", at = @At(value = "INVOKE", target = "Lnet/minecraft/registry/SimpleRegistry;refreshTags()V")) + private void applyTagAliases(CallbackInfo info) { + ((SimpleRegistryExtension) field_53689).fabric_applyPendingTagAliases(); + } +} diff --git a/fabric-tag-api-v1/src/main/java/net/fabricmc/fabric/mixin/tag/SimpleRegistryMixin.java b/fabric-tag-api-v1/src/main/java/net/fabricmc/fabric/mixin/tag/SimpleRegistryMixin.java new file mode 100644 index 0000000000..34054f4380 --- /dev/null +++ b/fabric-tag-api-v1/src/main/java/net/fabricmc/fabric/mixin/tag/SimpleRegistryMixin.java @@ -0,0 +1,97 @@ +/* + * 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; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.google.common.collect.Sets; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; + +import net.minecraft.registry.Registry; +import net.minecraft.registry.RegistryKey; +import net.minecraft.registry.SimpleRegistry; +import net.minecraft.registry.entry.RegistryEntry; +import net.minecraft.registry.entry.RegistryEntryList; +import net.minecraft.registry.tag.TagKey; + +import net.fabricmc.fabric.impl.tag.SimpleRegistryExtension; + +@Mixin(SimpleRegistry.class) +abstract class SimpleRegistryMixin implements SimpleRegistryExtension { + @Unique + private static final Logger LOGGER = LoggerFactory.getLogger("fabric-tag-api-v1"); + + @Unique + private Map, Set>> fabric_pendingTagAliasGroups; + + @Shadow + @Final + private RegistryKey> key; + + @Shadow + SimpleRegistry.TagLookup tagLookup; + + @Override + public void fabric_applyTagAliases(Map, Set>> aliasGroups) { + fabric_pendingTagAliasGroups = aliasGroups; + } + + @SuppressWarnings("unchecked") + @Override + public void fabric_applyPendingTagAliases() { + if (fabric_pendingTagAliasGroups == null) return; + + Set>> uniqueAliasGroups = Sets.newIdentityHashSet(); + uniqueAliasGroups.addAll(fabric_pendingTagAliasGroups.values()); + + for (Set> aliasGroup : uniqueAliasGroups) { + Set> entries = Sets.newIdentityHashSet(); + + // Fetch all entries from each tag. + for (TagKey tag : aliasGroup) { + RegistryEntryList.Named entryList = tagLookup.getOptional((TagKey) tag).orElse(null); + + if (entryList != null) { + entries.addAll(entryList.entries); + } else { + LOGGER.warn("[Fabric] Cannot apply aliases to tag {} because it doesn't exist!", tag); + } + } + + List> entriesAsList = List.copyOf(entries); + + // Replace the old entry list contents with the merged list. + for (TagKey tag : aliasGroup) { + RegistryEntryList.Named entryList = tagLookup.getOptional((TagKey) tag).orElse(null); + + if (entryList != null) { + entryList.entries = entriesAsList; + } + } + } + + LOGGER.info("[Fabric] Loaded {} tag alias groups for {}", uniqueAliasGroups.size(), key.getValue()); + fabric_pendingTagAliasGroups = null; + } +} diff --git a/fabric-tag-api-v1/src/main/resources/assets/fabric-tag-api-v1/icon.png b/fabric-tag-api-v1/src/main/resources/assets/fabric-tag-api-v1/icon.png new file mode 100644 index 0000000000..2931efbf61 Binary files /dev/null and b/fabric-tag-api-v1/src/main/resources/assets/fabric-tag-api-v1/icon.png differ diff --git a/fabric-tag-api-v1/src/main/resources/fabric-tag-api-v1.accesswidener b/fabric-tag-api-v1/src/main/resources/fabric-tag-api-v1.accesswidener new file mode 100644 index 0000000000..390d96c0a6 --- /dev/null +++ b/fabric-tag-api-v1/src/main/resources/fabric-tag-api-v1.accesswidener @@ -0,0 +1,3 @@ +accessWidener v2 named +accessible class net/minecraft/registry/SimpleRegistry$TagLookup +accessible field net/minecraft/registry/entry/RegistryEntryList$Named entries Ljava/util/List; diff --git a/fabric-tag-api-v1/src/main/resources/fabric-tag-api-v1.mixins.json b/fabric-tag-api-v1/src/main/resources/fabric-tag-api-v1.mixins.json new file mode 100644 index 0000000000..fadeff5b42 --- /dev/null +++ b/fabric-tag-api-v1/src/main/resources/fabric-tag-api-v1.mixins.json @@ -0,0 +1,13 @@ +{ + "required": true, + "package": "net.fabricmc.fabric.mixin.tag", + "compatibilityLevel": "JAVA_21", + "mixins": [ + "SimpleRegistryMixin", + "SimpleRegistry2Mixin", + "SimpleRegistry3Mixin" + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/fabric-tag-api-v1/src/main/resources/fabric.mod.json b/fabric-tag-api-v1/src/main/resources/fabric.mod.json new file mode 100644 index 0000000000..2dc2520294 --- /dev/null +++ b/fabric-tag-api-v1/src/main/resources/fabric.mod.json @@ -0,0 +1,36 @@ +{ + "schemaVersion": 1, + "id": "fabric-tag-api-v1", + "name": "Fabric Tag API (v1)", + "version": "${version}", + "environment": "*", + "license": "Apache-2.0", + "icon": "assets/fabric-tag-api-v1/icon.png", + "contact": { + "homepage": "https://fabricmc.net", + "irc": "irc://irc.esper.net:6667/fabric", + "issues": "https://github.com/FabricMC/fabric/issues", + "sources": "https://github.com/FabricMC/fabric" + }, + "authors": [ + "FabricMC" + ], + "depends": { + "fabricloader": ">=0.16.8", + "fabric-api-base": "*", + "fabric-resource-loader-v0": "*" + }, + "entrypoints": { + "main": [ + "net.fabricmc.fabric.impl.tag.TagInit" + ] + }, + "description": "Hooks for working with tags.", + "mixins": [ + "fabric-tag-api-v1.mixins.json" + ], + "accessWidener": "fabric-tag-api-v1.accesswidener", + "custom": { + "fabric-api:module-lifecycle": "stable" + } +} diff --git a/fabric-tag-api-v1/src/testmod/resources/fabric.mod.json b/fabric-tag-api-v1/src/testmod/resources/fabric.mod.json new file mode 100644 index 0000000000..655e17f28d --- /dev/null +++ b/fabric-tag-api-v1/src/testmod/resources/fabric.mod.json @@ -0,0 +1,15 @@ +{ + "schemaVersion": 1, + "id": "fabric-tag-v1-testmod", + "name": "Fabric Tag API (v1) Test Mod", + "version": "1.0.0", + "environment": "*", + "license": "Apache-2.0", + "depends": { + "fabric-tag-api-v1": "*" + }, + "entrypoints": { + "main": [ + ] + } +} diff --git a/gradle.properties b/gradle.properties index 8c873c60b3..35e4ac560d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -54,6 +54,7 @@ fabric-resource-loader-v0-version=3.0.6 fabric-screen-api-v1-version=2.0.33 fabric-screen-handler-api-v1-version=1.3.99 fabric-sound-api-v1-version=1.0.29 +fabric-tag-api-v1-version=1.0.0 fabric-transfer-api-v1-version=5.4.1 fabric-transitive-access-wideners-v1-version=6.1.8 fabric-convention-tags-v1-version=2.1.1 diff --git a/settings.gradle b/settings.gradle index 68a94ede37..8281f2d524 100644 --- a/settings.gradle +++ b/settings.gradle @@ -63,6 +63,7 @@ include 'fabric-resource-loader-v0' include 'fabric-screen-api-v1' include 'fabric-screen-handler-api-v1' include 'fabric-sound-api-v1' +include 'fabric-tag-api-v1' include 'fabric-transfer-api-v1' include 'fabric-transitive-access-wideners-v1'