mirror of
https://github.com/FabricMC/fabric.git
synced 2024-11-15 03:35:07 -05:00
Add support for partially synced client tags (#3118)
* Draft v1.1.0 * Resolve some comments * Add javadoc * Remove old behavior * Minor cleanup * Add test for partially synced tags * Address nitpick * Fix checkstyle * Hard fail when datapack fails to regsiter Co-authored-by: modmuss <modmuss50@gmail.com> * Fix missing import * Refactor Don't recurse through tag hierarchy * Add note for test * Adjustments to logic to handle server-missing nested tags * Restore recursive search, add tracking of checked tags * Cleanup --------- Co-authored-by: modmuss <modmuss50@gmail.com>
This commit is contained in:
parent
6beca848ff
commit
97bb207586
8 changed files with 225 additions and 86 deletions
|
@ -6,4 +6,5 @@ moduleDependencies(project, ['fabric-api-base'])
|
|||
testDependencies(project, [
|
||||
':fabric-convention-tags-v1',
|
||||
':fabric-lifecycle-events-v1',
|
||||
':fabric-resource-loader-v0',
|
||||
])
|
||||
|
|
|
@ -16,21 +16,15 @@
|
|||
|
||||
package net.fabricmc.fabric.api.tag.client.v1;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.registry.RegistryKey;
|
||||
import net.minecraft.registry.entry.RegistryEntry;
|
||||
import net.minecraft.registry.tag.TagKey;
|
||||
import net.minecraft.util.Identifier;
|
||||
import net.minecraft.registry.Registries;
|
||||
import net.minecraft.registry.Registry;
|
||||
import net.minecraft.registry.entry.RegistryEntry;
|
||||
import net.minecraft.registry.RegistryKey;
|
||||
|
||||
import net.fabricmc.fabric.impl.tag.client.ClientTagsLoader;
|
||||
import net.fabricmc.fabric.impl.tag.client.ClientTagsImpl;
|
||||
|
||||
/**
|
||||
* Allows the use of tags by directly loading them from the installed mods.
|
||||
|
@ -45,8 +39,6 @@ import net.fabricmc.fabric.impl.tag.client.ClientTagsLoader;
|
|||
* even when connected to a vanilla server.
|
||||
*/
|
||||
public final class ClientTags {
|
||||
private static final Map<TagKey<?>, Set<Identifier>> LOCAL_TAG_CACHE = new ConcurrentHashMap<>();
|
||||
|
||||
private ClientTags() {
|
||||
}
|
||||
|
||||
|
@ -57,54 +49,25 @@ public final class ClientTags {
|
|||
* @return a set of {@code Identifier}s this tag contains
|
||||
*/
|
||||
public static Set<Identifier> getOrCreateLocalTag(TagKey<?> tagKey) {
|
||||
Set<Identifier> ids = LOCAL_TAG_CACHE.get(tagKey);
|
||||
|
||||
if (ids == null) {
|
||||
ids = ClientTagsLoader.loadTag(tagKey);
|
||||
LOCAL_TAG_CACHE.put(tagKey, ids);
|
||||
}
|
||||
|
||||
return ids;
|
||||
return ClientTagsImpl.getOrCreatePartiallySyncedTag(tagKey).completeIds();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an entry is in a tag.
|
||||
*
|
||||
* <p>If the synced tag does exist, it is queried. If it does not exist,
|
||||
* the tag populated from the available mods is checked.
|
||||
* the tag populated from the available mods is checked, recursively checking the
|
||||
* synced tags and entries contained within.
|
||||
*
|
||||
* @param tagKey the {@code TagKey} to being checked
|
||||
* @param entry the entry to check
|
||||
* @return if the entry is in the given tag
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> boolean isInWithLocalFallback(TagKey<T> tagKey, T entry) {
|
||||
Objects.requireNonNull(tagKey);
|
||||
Objects.requireNonNull(entry);
|
||||
|
||||
Optional<? extends Registry<?>> maybeRegistry = getRegistry(tagKey);
|
||||
|
||||
if (maybeRegistry.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!tagKey.isOf(maybeRegistry.get().getKey())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Registry<T> registry = (Registry<T>) maybeRegistry.get();
|
||||
|
||||
Optional<RegistryKey<T>> maybeKey = registry.getKey(entry);
|
||||
|
||||
// Check synced tag
|
||||
if (registry.getEntryList(tagKey).isPresent()) {
|
||||
return maybeKey.filter(registryKey -> registry.entryOf(registryKey).isIn(tagKey))
|
||||
.isPresent();
|
||||
}
|
||||
|
||||
// Check local tags
|
||||
Set<Identifier> ids = getOrCreateLocalTag(tagKey);
|
||||
return maybeKey.filter(registryKey -> ids.contains(registryKey.getValue())).isPresent();
|
||||
return ClientTagsImpl.getRegistryEntry(tagKey, entry).map(re -> isInWithLocalFallback(tagKey, re)).orElse(false);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -112,7 +75,8 @@ public final class ClientTags {
|
|||
* such as {@link net.minecraft.world.biome.Biome}s.
|
||||
*
|
||||
* <p>If the synced tag does exist, it is queried. If it does not exist,
|
||||
* the tag populated from the available mods is checked.
|
||||
* the tag populated from the available mods is checked, recursively checking the
|
||||
* synced tags and entries contained within.
|
||||
*
|
||||
* @param tagKey the {@code TagKey} to be checked
|
||||
* @param registryEntry the entry to check
|
||||
|
@ -121,21 +85,7 @@ public final class ClientTags {
|
|||
public static <T> boolean isInWithLocalFallback(TagKey<T> tagKey, RegistryEntry<T> registryEntry) {
|
||||
Objects.requireNonNull(tagKey);
|
||||
Objects.requireNonNull(registryEntry);
|
||||
|
||||
// Check if the tag exists in the dynamic registry first
|
||||
Optional<? extends Registry<T>> maybeRegistry = getRegistry(tagKey);
|
||||
|
||||
if (maybeRegistry.isPresent()) {
|
||||
if (maybeRegistry.get().getEntryList(tagKey).isPresent()) {
|
||||
return registryEntry.isIn(tagKey);
|
||||
}
|
||||
}
|
||||
|
||||
if (registryEntry.getKey().isPresent()) {
|
||||
return isInLocal(tagKey, registryEntry.getKey().get());
|
||||
}
|
||||
|
||||
return false;
|
||||
return ClientTagsImpl.isInWithLocalFallback(tagKey, registryEntry);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -157,22 +107,4 @@ public final class ClientTags {
|
|||
|
||||
return false;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static <T> Optional<? extends Registry<T>> getRegistry(TagKey<T> tagKey) {
|
||||
Objects.requireNonNull(tagKey);
|
||||
|
||||
// Check if the tag represents a dynamic registry
|
||||
if (MinecraftClient.getInstance() != null) {
|
||||
if (MinecraftClient.getInstance().world != null) {
|
||||
if (MinecraftClient.getInstance().world.getRegistryManager() != null) {
|
||||
Optional<? extends Registry<T>> maybeRegistry = MinecraftClient.getInstance().world
|
||||
.getRegistryManager().getOptional(tagKey.registry());
|
||||
if (maybeRegistry.isPresent()) return maybeRegistry;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (Optional<? extends Registry<T>>) Registries.REGISTRIES.getOrEmpty(tagKey.registry().getValue());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,124 @@
|
|||
/*
|
||||
* 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.client;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.registry.Registries;
|
||||
import net.minecraft.registry.Registry;
|
||||
import net.minecraft.registry.RegistryKey;
|
||||
import net.minecraft.registry.entry.RegistryEntry;
|
||||
import net.minecraft.registry.tag.TagKey;
|
||||
|
||||
public class ClientTagsImpl {
|
||||
private static final Map<TagKey<?>, ClientTagsLoader.LoadedTag> LOCAL_TAG_HIERARCHY = new ConcurrentHashMap<>();
|
||||
|
||||
public static <T> boolean isInWithLocalFallback(TagKey<T> tagKey, RegistryEntry<T> registryEntry) {
|
||||
return isInWithLocalFallback(tagKey, registryEntry, new HashSet<>());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static <T> boolean isInWithLocalFallback(TagKey<T> tagKey, RegistryEntry<T> registryEntry, Set<TagKey<T>> checked) {
|
||||
if (checked.contains(tagKey)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
checked.add(tagKey);
|
||||
|
||||
// Check if the tag exists in the dynamic registry first
|
||||
Optional<? extends Registry<T>> maybeRegistry = ClientTagsImpl.getRegistry(tagKey);
|
||||
|
||||
if (maybeRegistry.isPresent()) {
|
||||
// Check the synced tag exists and use that
|
||||
if (maybeRegistry.get().getEntryList(tagKey).isPresent()) {
|
||||
return registryEntry.isIn(tagKey);
|
||||
}
|
||||
}
|
||||
|
||||
if (registryEntry.getKey().isEmpty()) {
|
||||
// No key?
|
||||
return false;
|
||||
}
|
||||
|
||||
// Recursively search the entries contained with the tag
|
||||
ClientTagsLoader.LoadedTag wt = ClientTagsImpl.getOrCreatePartiallySyncedTag(tagKey);
|
||||
|
||||
if (wt.immediateChildIds().contains(registryEntry.getKey().get().getValue())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (TagKey<?> key : wt.immediateChildTags()) {
|
||||
if (isInWithLocalFallback((TagKey<T>) key, registryEntry, checked)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
checked.add((TagKey<T>) key);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> Optional<? extends Registry<T>> getRegistry(TagKey<T> tagKey) {
|
||||
Objects.requireNonNull(tagKey);
|
||||
|
||||
// Check if the tag represents a dynamic registry
|
||||
if (MinecraftClient.getInstance() != null) {
|
||||
if (MinecraftClient.getInstance().world != null) {
|
||||
if (MinecraftClient.getInstance().world.getRegistryManager() != null) {
|
||||
Optional<? extends Registry<T>> maybeRegistry = MinecraftClient.getInstance().world
|
||||
.getRegistryManager().getOptional(tagKey.registry());
|
||||
if (maybeRegistry.isPresent()) return maybeRegistry;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (Optional<? extends Registry<T>>) Registries.REGISTRIES.getOrEmpty(tagKey.registry().getValue());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> Optional<RegistryEntry<T>> getRegistryEntry(TagKey<T> tagKey, T entry) {
|
||||
Optional<? extends Registry<?>> maybeRegistry = getRegistry(tagKey);
|
||||
|
||||
if (maybeRegistry.isEmpty() || !tagKey.isOf(maybeRegistry.get().getKey())) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
Registry<T> registry = (Registry<T>) maybeRegistry.get();
|
||||
|
||||
Optional<RegistryKey<T>> maybeKey = registry.getKey(entry);
|
||||
|
||||
return maybeKey.map(registry::entryOf);
|
||||
}
|
||||
|
||||
public static ClientTagsLoader.LoadedTag getOrCreatePartiallySyncedTag(TagKey<?> tagKey) {
|
||||
ClientTagsLoader.LoadedTag loadedTag = LOCAL_TAG_HIERARCHY.get(tagKey);
|
||||
|
||||
if (loadedTag == null) {
|
||||
loadedTag = ClientTagsLoader.loadTag(tagKey);
|
||||
LOCAL_TAG_HIERARCHY.put(tagKey, loadedTag);
|
||||
}
|
||||
|
||||
return loadedTag;
|
||||
}
|
||||
}
|
|
@ -33,15 +33,14 @@ import org.jetbrains.annotations.Nullable;
|
|||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import net.minecraft.registry.Registry;
|
||||
import net.minecraft.registry.RegistryKey;
|
||||
import net.minecraft.registry.tag.TagEntry;
|
||||
import net.minecraft.registry.tag.TagFile;
|
||||
import net.minecraft.registry.tag.TagKey;
|
||||
import net.minecraft.registry.tag.TagManagerLoader;
|
||||
import net.minecraft.util.Identifier;
|
||||
import net.minecraft.registry.Registry;
|
||||
import net.minecraft.registry.RegistryKey;
|
||||
|
||||
import net.fabricmc.fabric.api.tag.client.v1.ClientTags;
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
import net.fabricmc.loader.api.ModContainer;
|
||||
|
||||
|
@ -51,7 +50,7 @@ public class ClientTagsLoader {
|
|||
* Load a given tag from the available mods into a set of {@code Identifier}s.
|
||||
* Parsing based on {@link net.minecraft.registry.tag.TagGroupLoader#loadTags(net.minecraft.resource.ResourceManager)}
|
||||
*/
|
||||
public static Set<Identifier> loadTag(TagKey<?> tagKey) {
|
||||
public static LoadedTag loadTag(TagKey<?> tagKey) {
|
||||
var tags = new HashSet<TagEntry>();
|
||||
HashSet<Path> tagFiles = getTagFiles(tagKey.registry(), tagKey.id());
|
||||
|
||||
|
@ -73,13 +72,16 @@ public class ClientTagsLoader {
|
|||
}
|
||||
}
|
||||
|
||||
HashSet<Identifier> ids = new HashSet<>();
|
||||
HashSet<Identifier> completeIds = new HashSet<>();
|
||||
HashSet<Identifier> immediateChildIds = new HashSet<>();
|
||||
HashSet<TagKey<?>> immediateChildTags = new HashSet<>();
|
||||
|
||||
for (TagEntry tagEntry : tags) {
|
||||
tagEntry.resolve(new TagEntry.ValueGetter<>() {
|
||||
@Nullable
|
||||
@Override
|
||||
public Identifier direct(Identifier id) {
|
||||
immediateChildIds.add(id);
|
||||
return id;
|
||||
}
|
||||
|
||||
|
@ -87,12 +89,20 @@ public class ClientTagsLoader {
|
|||
@Override
|
||||
public Collection<Identifier> tag(Identifier id) {
|
||||
TagKey<?> tag = TagKey.of(tagKey.registry(), id);
|
||||
return ClientTags.getOrCreateLocalTag(tag);
|
||||
immediateChildTags.add(tag);
|
||||
return ClientTagsImpl.getOrCreatePartiallySyncedTag(tag).completeIds;
|
||||
}
|
||||
}, ids::add);
|
||||
}, completeIds::add);
|
||||
}
|
||||
|
||||
return Collections.unmodifiableSet(ids);
|
||||
// Ensure that the tag does not refer to itself
|
||||
immediateChildTags.remove(tagKey);
|
||||
|
||||
return new LoadedTag(Collections.unmodifiableSet(completeIds), Collections.unmodifiableSet(immediateChildTags),
|
||||
Collections.unmodifiableSet(immediateChildIds));
|
||||
}
|
||||
|
||||
public record LoadedTag(Set<Identifier> completeIds, Set<TagKey<?>> immediateChildTags, Set<Identifier> immediateChildIds) {
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -20,20 +20,36 @@ import org.slf4j.Logger;
|
|||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import net.minecraft.block.Blocks;
|
||||
import net.minecraft.registry.Registries;
|
||||
import net.minecraft.registry.tag.TagKey;
|
||||
import net.minecraft.util.Identifier;
|
||||
import net.minecraft.world.biome.BiomeKeys;
|
||||
|
||||
import net.fabricmc.api.ClientModInitializer;
|
||||
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents;
|
||||
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
|
||||
import net.fabricmc.fabric.api.resource.ResourceManagerHelper;
|
||||
import net.fabricmc.fabric.api.resource.ResourcePackActivationType;
|
||||
import net.fabricmc.fabric.api.tag.client.v1.ClientTags;
|
||||
import net.fabricmc.fabric.api.tag.convention.v1.ConventionalBiomeTags;
|
||||
import net.fabricmc.fabric.api.tag.convention.v1.ConventionalBlockTags;
|
||||
import net.fabricmc.fabric.api.tag.convention.v1.ConventionalEnchantmentTags;
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
import net.fabricmc.loader.api.ModContainer;
|
||||
|
||||
public class ClientTagTest implements ClientModInitializer {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(ClientTagTest.class);
|
||||
private static final String MODID = "fabric-clients-tags-api-v1-testmod";
|
||||
|
||||
@Override
|
||||
public void onInitializeClient() {
|
||||
final ModContainer container = FabricLoader.getInstance().getModContainer(MODID).get();
|
||||
|
||||
if (!ResourceManagerHelper.registerBuiltinResourcePack(new Identifier(MODID, "test2"),
|
||||
container, ResourcePackActivationType.ALWAYS_ENABLED)) {
|
||||
throw new IllegalStateException("Could not register built-in resource pack.");
|
||||
}
|
||||
|
||||
ClientLifecycleEvents.CLIENT_STARTED.register(client -> {
|
||||
if (ClientTags.getOrCreateLocalTag(ConventionalEnchantmentTags.INCREASES_BLOCK_DROPS) == null) {
|
||||
throw new AssertionError("Expected to load c:fortune, but it was not found!");
|
||||
|
@ -51,8 +67,23 @@ public class ClientTagTest implements ClientModInitializer {
|
|||
throw new AssertionError("Expected to find forest in c:forest, but it was not found!");
|
||||
}
|
||||
|
||||
if (ClientTags.isInWithLocalFallback(TagKey.of(Registries.BLOCK.getKey(),
|
||||
new Identifier("fabric", "sword_efficient")), Blocks.DIRT)) {
|
||||
throw new AssertionError("Expected not to find dirt in fabric:sword_efficient, but it was found!");
|
||||
}
|
||||
|
||||
// Success!
|
||||
LOGGER.info("The tests for client tags passed!");
|
||||
});
|
||||
|
||||
// This should be tested on a server with the datapack from the builtin resourcepack.
|
||||
// That is, fabric:sword_efficient should NOT exist on the server (can be confirmed with F3 on a dirt block),
|
||||
// but the this test should pass as minecraft:sword_efficient will contain dirt on the server
|
||||
ClientTickEvents.END_WORLD_TICK.register(client -> {
|
||||
if (!ClientTags.isInWithLocalFallback(TagKey.of(Registries.BLOCK.getKey(),
|
||||
new Identifier("fabric", "sword_efficient")), Blocks.DIRT)) {
|
||||
throw new AssertionError("Expected to find dirt in fabric:sword_efficient, but it was not found!");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"replace": false,
|
||||
"values": [
|
||||
{
|
||||
"id": "#fabric:mineable/sword",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"id": "#minecraft:sword_efficient",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"id": "minecraft:bamboo",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"id": "minecraft:cobweb",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"id": "minecraft:bamboo_sapling",
|
||||
"required": false
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"replace": false,
|
||||
"values": [
|
||||
"minecraft:dirt",
|
||||
{
|
||||
"id": "",
|
||||
"required": false
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"pack": {
|
||||
"pack_format": 9,
|
||||
"description": "Test Dirt in SwordEfficient"
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue