Enchantment shouldn't support direct holders despite the ctor accepting a Holder type. We want to limit the types to ones that are actually used as direct holders in the game
144 lines
8.2 KiB
Java
144 lines
8.2 KiB
Java
package io.papermc.paper.registry;
|
|
|
|
import io.papermc.paper.registry.entry.RegistryEntry;
|
|
import io.papermc.paper.registry.entry.RegistryEntryMeta;
|
|
import io.papermc.paper.registry.legacy.DelayedRegistry;
|
|
import io.papermc.paper.registry.legacy.DelayedRegistryEntry;
|
|
import io.papermc.paper.registry.legacy.LegacyRegistryIdentifiers;
|
|
import java.util.Map;
|
|
import java.util.NoSuchElementException;
|
|
import java.util.Set;
|
|
import java.util.concurrent.ConcurrentHashMap;
|
|
import java.util.stream.Collectors;
|
|
import net.minecraft.resources.ResourceKey;
|
|
import org.bukkit.Keyed;
|
|
import org.bukkit.Registry;
|
|
import org.bukkit.craftbukkit.CraftRegistry;
|
|
import org.jetbrains.annotations.VisibleForTesting;
|
|
import org.jspecify.annotations.Nullable;
|
|
|
|
public class PaperRegistryAccess implements RegistryAccess {
|
|
|
|
// We store the API registries in a memoized supplier, so they can be created on-demand.
|
|
// These suppliers are added to this map right after the instance of nms.Registry is created before it is loaded.
|
|
// We want to do registration there, so we have access to the nms.Registry instance in order to wrap it in a CraftRegistry instance.
|
|
// The memoized Supplier is needed because we *can't* instantiate any CraftRegistry class until **all** the BuiltInRegistries have been added
|
|
// to this map because that would class-load org.bukkit.Registry which would query this map.
|
|
private final Map<RegistryKey<?>, RegistryHolder<?>> registries = new ConcurrentHashMap<>(); // is "identity" because RegistryKey overrides equals and hashCode
|
|
|
|
public static PaperRegistryAccess instance() {
|
|
return (PaperRegistryAccess) RegistryAccessHolder.INSTANCE.orElseThrow(() -> new IllegalStateException("No RegistryAccess implementation found"));
|
|
}
|
|
|
|
@VisibleForTesting
|
|
public Set<RegistryKey<?>> getLoadedServerBackedRegistries() {
|
|
return this.registries.keySet().stream().filter(registryHolder -> {
|
|
final RegistryEntry<?, ?> entry = PaperRegistries.getEntry(registryHolder);
|
|
return entry != null && !(entry.meta() instanceof RegistryEntryMeta.ApiOnly<?,?>);
|
|
}).collect(Collectors.toUnmodifiableSet());
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
@Deprecated(forRemoval = true)
|
|
@Override
|
|
public <T extends Keyed> @Nullable Registry<T> getRegistry(final Class<T> type) {
|
|
final RegistryKey<T> registryKey = byType(type);
|
|
// If our mapping from Class -> RegistryKey did not contain the passed type it was either a completely invalid type or a registry
|
|
// that merely exists as a SimpleRegistry in the org.bukkit.Registry type. We cannot return a registry for these, return null
|
|
// as per method contract in Bukkit#getRegistry.
|
|
if (registryKey == null) return null;
|
|
|
|
final RegistryEntry<?, T> entry = PaperRegistries.getEntry(registryKey);
|
|
final RegistryHolder<T> registry = (RegistryHolder<T>) this.registries.get(registryKey);
|
|
if (registry != null) {
|
|
// if the registry exists, return right away. Since this is the "legacy" method, we return DelayedRegistry
|
|
// for the non-builtin Registry instances stored as fields in Registry.
|
|
return registry.get();
|
|
} else if (entry instanceof DelayedRegistryEntry<?, T>) {
|
|
// if the registry doesn't exist and the entry is marked as "delayed", we create a registry holder that is empty
|
|
// which will later be filled with the actual registry. This is so the fields on org.bukkit.Registry can be populated with
|
|
// registries that don't exist at the time org.bukkit.Registry is statically initialized.
|
|
final RegistryHolder<T> delayedHolder = new RegistryHolder.Delayed<>();
|
|
this.registries.put(registryKey, delayedHolder);
|
|
return delayedHolder.get();
|
|
} else {
|
|
// if the registry doesn't exist yet or doesn't have a delayed entry, just return null
|
|
return null;
|
|
}
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
@Override
|
|
public <T extends Keyed> Registry<T> getRegistry(final RegistryKey<T> key) {
|
|
if (PaperRegistries.getEntry(key) == null) {
|
|
throw new NoSuchElementException(key + " is not a valid registry key");
|
|
}
|
|
final RegistryHolder<T> registryHolder = (RegistryHolder<T>) this.registries.get(key);
|
|
if (registryHolder == null) {
|
|
throw new IllegalArgumentException(key + " points to a registry that is not available yet");
|
|
}
|
|
// since this is the getRegistry method that uses the modern RegistryKey, we unwrap any DelayedRegistry instances
|
|
// that might be returned here. I don't think reference equality is required when doing getRegistry(RegistryKey.WOLF_VARIANT) == Registry.WOLF_VARIANT
|
|
return possiblyUnwrap(registryHolder.get());
|
|
}
|
|
|
|
public <M, T extends Keyed, B extends PaperRegistryBuilder<M, T>> WritableCraftRegistry<M, T, B> getWritableRegistry(final RegistryKey<T> key) {
|
|
final Registry<T> registry = this.getRegistry(key);
|
|
if (registry instanceof WritableCraftRegistry<?, T, ?>) {
|
|
return (WritableCraftRegistry<M, T, B>) registry;
|
|
}
|
|
throw new IllegalArgumentException(key + " does not point to a writable registry");
|
|
}
|
|
|
|
private static <T extends Keyed> Registry<T> possiblyUnwrap(final Registry<T> registry) {
|
|
if (registry instanceof final DelayedRegistry<T, ?> delayedRegistry) { // if not coming from legacy, unwrap the delayed registry
|
|
return delayedRegistry.delegate();
|
|
}
|
|
return registry;
|
|
}
|
|
|
|
public <M> void registerReloadableRegistry(final ResourceKey<? extends net.minecraft.core.Registry<M>> resourceKey, final net.minecraft.core.Registry<M> registry) {
|
|
this.registerRegistry(resourceKey, registry, true);
|
|
}
|
|
|
|
public <M> void registerRegistry(final ResourceKey<? extends net.minecraft.core.Registry<M>> resourceKey, final net.minecraft.core.Registry<M> registry) {
|
|
this.registerRegistry(resourceKey, registry, false);
|
|
}
|
|
|
|
public <M> void lockReferenceHolders(final ResourceKey<? extends net.minecraft.core.Registry<M>> resourceKey) {
|
|
final RegistryEntry<M, Keyed> entry = PaperRegistries.getEntry(resourceKey);
|
|
if (entry == null || !(entry.meta() instanceof final RegistryEntryMeta.ServerSide<M, Keyed> serverSide) || !serverSide.registryTypeMapper().constructorUsesHolder()) {
|
|
return;
|
|
}
|
|
final CraftRegistry<?, M> registry = (CraftRegistry<?, M>) this.getRegistry(entry.apiKey());
|
|
registry.lockReferenceHolders();
|
|
}
|
|
|
|
@SuppressWarnings("unchecked") // this method should be called right after any new MappedRegistry instances are created to later be used by the server.
|
|
private <M, B extends Keyed, R extends Registry<B>> void registerRegistry(final ResourceKey<? extends net.minecraft.core.Registry<M>> resourceKey, final net.minecraft.core.Registry<M> registry, final boolean replace) {
|
|
final RegistryEntry<M, B> entry = PaperRegistries.getEntry(resourceKey);
|
|
if (entry == null) { // skip registries that don't have API entries
|
|
return;
|
|
}
|
|
final RegistryHolder<B> registryHolder = (RegistryHolder<B>) this.registries.get(entry.apiKey());
|
|
if (registryHolder == null || replace) {
|
|
// if the holder doesn't exist yet, or is marked as "replaceable", put it in the map.
|
|
this.registries.put(entry.apiKey(), entry.createRegistryHolder(registry));
|
|
} else {
|
|
if (registryHolder instanceof RegistryHolder.Delayed<?, ?> && entry instanceof final DelayedRegistryEntry<M, B> delayedEntry) {
|
|
// if the registry holder is delayed, and the entry is marked as "delayed", then load the holder with the CraftRegistry instance that wraps the actual nms Registry.
|
|
((RegistryHolder.Delayed<B, R>) registryHolder).loadFrom(delayedEntry, registry);
|
|
} else {
|
|
throw new IllegalArgumentException(resourceKey + " has already been created");
|
|
}
|
|
}
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
@Deprecated
|
|
@VisibleForTesting
|
|
public static <T extends Keyed> @Nullable RegistryKey<T> byType(final Class<T> type) {
|
|
return (RegistryKey<T>) LegacyRegistryIdentifiers.CLASS_TO_KEY_MAP.get(type);
|
|
}
|
|
}
|