Skip to content

Commit

Permalink
Add support for partially synced client tags (#3118)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
dexman545 and modmuss50 committed Jul 3, 2023
1 parent d63b52e commit 0eee125
Show file tree
Hide file tree
Showing 8 changed files with 225 additions and 86 deletions.
1 change: 1 addition & 0 deletions fabric-client-tags-api-v1/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ moduleDependencies(project, ['fabric-api-base'])
testDependencies(project, [
':fabric-convention-tags-v1',
':fabric-lifecycle-events-v1',
':fabric-resource-loader-v0',
])
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -45,8 +39,6 @@
* 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() {
}

Expand All @@ -57,62 +49,34 @@ private 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);
}

/**
* Checks if an entry is in a tag, for use with entries from a dynamic registry,
* 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
Expand All @@ -121,21 +85,7 @@ public static <T> boolean isInWithLocalFallback(TagKey<T> tagKey, T entry) {
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);
}

/**
Expand All @@ -157,22 +107,4 @@ public static <T> boolean isInLocal(TagKey<T> tagKey, RegistryKey<T> registryKey

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());
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,14 @@
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;

Expand All @@ -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());

Expand All @@ -73,26 +72,37 @@ public static Set<Identifier> loadTag(TagKey<?> tagKey) {
}
}

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;
}

@Nullable
@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) {
}

/**
Expand Down
Loading

0 comments on commit 0eee125

Please sign in to comment.