From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Jake Potrebic <jake.m.potrebic@gmail.com> Date: Wed, 2 Mar 2022 13:33:08 -0800 Subject: [PATCH] Add PaperRegistry PaperRegistry is a server-backed impl of bukkit's Registry interface diff --git a/src/main/java/io/papermc/paper/registry/PaperRegistry.java b/src/main/java/io/papermc/paper/registry/PaperRegistry.java new file mode 100644 index 0000000000000000000000000000000000000000..8d1f3c4891870b4239df678dd1e52e9f4ef74b2c --- /dev/null +++ b/src/main/java/io/papermc/paper/registry/PaperRegistry.java @@ -0,0 +1,147 @@ +package io.papermc.paper.registry; + +import com.google.common.base.Preconditions; +import com.google.common.base.Suppliers; +import net.minecraft.core.Holder; +import net.minecraft.core.Registry; +import net.minecraft.core.RegistryAccess; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.MinecraftServer; +import org.bukkit.Keyed; +import org.bukkit.NamespacedKey; +import org.bukkit.craftbukkit.util.CraftNamespacedKey; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.framework.qual.DefaultQualifier; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Supplier; + +@DefaultQualifier(NonNull.class) +public abstract class PaperRegistry<API extends Keyed, MINECRAFT> implements org.bukkit.Registry<API> { + + @SuppressWarnings("FieldMayBeFinal") // non-final for testing + private static Supplier<RegistryAccess> REGISTRY_ACCESS = Suppliers.memoize(() -> MinecraftServer.getServer().registryAccess()); + private static final Map<RegistryKey<?, ?>, PaperRegistry<?, ?>> INTERNAL_REGISTRIES = new HashMap<>(); + public static final Map<RegistryKey<?, ?>, PaperRegistry<?, ?>> REGISTRIES = Collections.unmodifiableMap(INTERNAL_REGISTRIES); + private static final Map<Class<?>, PaperRegistry<?, ?>> REGISTRY_BY_API_CLASS = new HashMap<>(); + private static final Map<ResourceKey<? extends Registry<?>>, PaperRegistry<?, ?>> REGISTRY_BY_RES_KEY = new HashMap<>(); + + private boolean registered; + private final RegistryKey<API, MINECRAFT> registryKey; + private final Supplier<Registry<MINECRAFT>> registry; + private final Map<NamespacedKey, API> cache = new ConcurrentHashMap<>(); + private final Map<NamespacedKey, ResourceKey<MINECRAFT>> resourceKeyCache = new ConcurrentHashMap<>(); + + public PaperRegistry(RegistryKey<API, MINECRAFT> registryKey) { + this.registryKey = registryKey; + this.registry = Suppliers.memoize(() -> REGISTRY_ACCESS.get().registryOrThrow(this.registryKey.resourceKey())); + } + + @Override + public @Nullable API get(NamespacedKey key) { + return this.cache.computeIfAbsent(key, k -> { + final @Nullable MINECRAFT nms = this.registry.get().get(CraftNamespacedKey.toMinecraft(k)); + if (nms != null) { + return this.convertToApi(k, nms); + } + return null; + }); + } + + public abstract API convertToApi(NamespacedKey key, MINECRAFT nms); + + public API convertToApi(ResourceLocation resourceLocation, MINECRAFT nms) { + return this.convertToApi(CraftNamespacedKey.fromMinecraft(resourceLocation), nms); + } + + public API convertToApi(Holder<MINECRAFT> nmsHolder) { + final Optional<ResourceKey<MINECRAFT>> key = nmsHolder.unwrapKey(); + if (nmsHolder.isBound() && key.isPresent()) { + return this.convertToApi(key.get().location(), nmsHolder.value()); + } else if (!nmsHolder.isBound() && key.isPresent()) { + return this.convertToApi(key.get().location(), this.registry.get().getOrThrow(key.get())); + } else if (nmsHolder.isBound() && key.isEmpty()) { + final @Nullable ResourceLocation loc = this.registry.get().getKey(nmsHolder.value()); + if (loc != null) { + return this.convertToApi(loc, nmsHolder.value()); + } + } + throw new IllegalStateException("Cannot convert " + nmsHolder + " to an API type in: " + this.registryKey); + } + + public MINECRAFT getMinecraftValue(API apiValue) { + return this.registry.get().getOptional(CraftNamespacedKey.toMinecraft(apiValue.getKey())).orElseThrow(); + } + + public Holder<MINECRAFT> getMinecraftHolder(API apiValue) { + return this.registry.get().getHolderOrThrow(this.resourceKeyCache.computeIfAbsent(apiValue.getKey(), key -> ResourceKey.create(this.registryKey.resourceKey(), CraftNamespacedKey.toMinecraft(key)))); + } + + @Override + public Iterator<API> iterator() { + return this.registry.get().keySet().stream().map(key -> this.get(CraftNamespacedKey.fromMinecraft(key))).iterator(); + } + + public void clearCache() { + this.cache.clear(); + } + + public void register() { + if (this.registered) { + throw new IllegalStateException("Already registered: " + this.registryKey.apiClass()); + } + INTERNAL_REGISTRIES.put(this.registryKey, this); + REGISTRY_BY_API_CLASS.put(this.registryKey.apiClass(), this); + REGISTRY_BY_RES_KEY.put(this.registryKey.resourceKey(), this); + this.registered = true; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) return true; + if (o == null || !PaperRegistry.class.isAssignableFrom(o.getClass())) return false; + PaperRegistry<?, ?> that = (PaperRegistry<?, ?>) o; + return this.registryKey.equals(that.registryKey); + } + + @Override + public int hashCode() { + return Objects.hash(this.registryKey); + } + + protected static <T> Supplier<Registry<T>> registryFor(ResourceKey<? extends Registry<T>> registryKey) { + return Suppliers.memoize(() -> REGISTRY_ACCESS.get().registryOrThrow(registryKey)); + } + + public static void clearCaches() { + for (PaperRegistry<?, ?> registry : INTERNAL_REGISTRIES.values()) { + registry.clearCache(); + } + } + + @SuppressWarnings("unchecked") + public static <T extends Keyed> PaperRegistry<T, ?> getRegistry(Class<T> classOfT) { + Preconditions.checkArgument(REGISTRY_BY_API_CLASS.containsKey(classOfT), "No registry for that type"); + return (PaperRegistry<T, ?>) REGISTRY_BY_API_CLASS.get(classOfT); + } + + @SuppressWarnings("unchecked") + public static <T> PaperRegistry<?, T> getRegistry(ResourceKey<? extends Registry<T>> resourceKey) { + Preconditions.checkArgument(REGISTRY_BY_RES_KEY.containsKey(resourceKey)); + return (PaperRegistry<?, T>) REGISTRY_BY_RES_KEY.get(resourceKey); + } + + @SuppressWarnings("unchecked") + public static <A extends Keyed, M> PaperRegistry<A, M> getRegistry(RegistryKey<A, M> registryKey) { + Preconditions.checkArgument(INTERNAL_REGISTRIES.containsKey(registryKey)); + return (PaperRegistry<A, M>) INTERNAL_REGISTRIES.get(registryKey); + } +} diff --git a/src/main/java/io/papermc/paper/registry/RegistryKey.java b/src/main/java/io/papermc/paper/registry/RegistryKey.java new file mode 100644 index 0000000000000000000000000000000000000000..6f39e343147803e15e7681c993b8797a629702e7 --- /dev/null +++ b/src/main/java/io/papermc/paper/registry/RegistryKey.java @@ -0,0 +1,8 @@ +package io.papermc.paper.registry; + +import net.minecraft.core.Registry; +import net.minecraft.resources.ResourceKey; +import org.bukkit.Keyed; + +public record RegistryKey<API extends Keyed, MINECRAFT>(Class<API> apiClass, ResourceKey<? extends Registry<MINECRAFT>> resourceKey) { +} diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java index 3eda7f5469803af5fae138b81976ecae2b7c6c2e..9941456a019a34384f892c44df3c73f9416bd560 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java @@ -2032,6 +2032,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa this.packRepository.setSelected(dataPacks); this.worldData.setDataPackConfig(MinecraftServer.getSelectedPacks(this.packRepository)); this.resources.managers.updateRegistryTags(this.registryAccess()); + io.papermc.paper.PaperRegistry.clearCaches(); // Paper new io.papermc.paper.event.server.ServerResourcesReloadedEvent(cause).callEvent(); // Paper if (Thread.currentThread() != this.serverThread) return; // Paper //this.getPlayerList().saveAll(); // Paper - we don't need to do this diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java index 5540c5a881a9d34a8c50d6b6f0694303c76aa610..9af0bd83c03a7e9fba04f7b9f0c66029a7f4b65a 100644 --- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java @@ -512,6 +512,11 @@ public final class CraftMagicNumbers implements UnsafeValues { public int nextEntityId() { return net.minecraft.world.entity.Entity.nextEntityId(); } + + @Override + public <T extends org.bukkit.Keyed> Registry<T> registryFor(Class<T> classOfT) { + return io.papermc.paper.registry.PaperRegistry.getRegistry(classOfT); + } // Paper end /** diff --git a/src/test/java/org/bukkit/support/AbstractTestingBase.java b/src/test/java/org/bukkit/support/AbstractTestingBase.java index d6b6b7a3d2949126520e8256563afeb2b43bf12c..37934f0da922d696373e7a3a8cf976fcb9015271 100644 --- a/src/test/java/org/bukkit/support/AbstractTestingBase.java +++ b/src/test/java/org/bukkit/support/AbstractTestingBase.java @@ -38,6 +38,15 @@ public abstract class AbstractTestingBase { MultiPackResourceManager resourceManager = new MultiPackResourceManager(PackType.SERVER_DATA, Collections.singletonList(new VanillaPackResources(ServerPacksSource.BUILT_IN_METADATA, "minecraft"))); // add tags and loot tables for unit tests RegistryAccess.Frozen registry = RegistryAccess.builtinCopy().freeze(); + // Paper start + try { + java.lang.reflect.Field field = io.papermc.paper.registry.PaperRegistry.class.getDeclaredField("REGISTRY_ACCESS"); + field.trySetAccessible(); + field.set(null, com.google.common.base.Suppliers.ofInstance(registry)); + } catch (ReflectiveOperationException ex) { + throw new IllegalStateException("Could not reflectively set RegistryAccess in PaperRegistry", ex); + } + // Paper end // Register vanilla pack DATA_PACK = ReloadableServerResources.loadResources(resourceManager, registry, Commands.CommandSelection.DEDICATED, 0, MoreExecutors.directExecutor(), MoreExecutors.directExecutor()).join(); // Bind tags