diff options
Diffstat (limited to 'patches/server/1001-Registry-Modification-API.patch')
-rw-r--r-- | patches/server/1001-Registry-Modification-API.patch | 852 |
1 files changed, 852 insertions, 0 deletions
diff --git a/patches/server/1001-Registry-Modification-API.patch b/patches/server/1001-Registry-Modification-API.patch new file mode 100644 index 0000000000..e139aba408 --- /dev/null +++ b/patches/server/1001-Registry-Modification-API.patch @@ -0,0 +1,852 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jake Potrebic <[email protected]> +Date: Mon, 27 Feb 2023 18:28:39 -0800 +Subject: [PATCH] Registry Modification API + +== AT == +public net.minecraft.server.RegistryLayer STATIC_ACCESS +public net.minecraft.core.MappedRegistry validateWrite(Lnet/minecraft/resources/ResourceKey;)V + +diff --git a/src/main/java/io/papermc/paper/registry/PaperRegistries.java b/src/main/java/io/papermc/paper/registry/PaperRegistries.java +index 40ac461d2f1906059377c77229612f540e827d75..7a2fb53ab280893b1c9c886ccb3480b695343cbf 100644 +--- a/src/main/java/io/papermc/paper/registry/PaperRegistries.java ++++ b/src/main/java/io/papermc/paper/registry/PaperRegistries.java +@@ -44,6 +44,7 @@ import org.checkerframework.framework.qual.DefaultQualifier; + + import static io.papermc.paper.registry.entry.RegistryEntry.apiOnly; + import static io.papermc.paper.registry.entry.RegistryEntry.entry; ++import static io.papermc.paper.registry.entry.RegistryEntry.writable; + + @DefaultQualifier(NonNull.class) + public final class PaperRegistries { +diff --git a/src/main/java/io/papermc/paper/registry/PaperRegistryAccess.java b/src/main/java/io/papermc/paper/registry/PaperRegistryAccess.java +index d591e3a2e19d5358a0d25a5a681368943622d231..f05ebf453406a924da3de6fb250f4793a1b3c612 100644 +--- a/src/main/java/io/papermc/paper/registry/PaperRegistryAccess.java ++++ b/src/main/java/io/papermc/paper/registry/PaperRegistryAccess.java +@@ -80,6 +80,14 @@ public class PaperRegistryAccess implements RegistryAccess { + 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(); +diff --git a/src/main/java/io/papermc/paper/registry/PaperRegistryBuilder.java b/src/main/java/io/papermc/paper/registry/PaperRegistryBuilder.java +new file mode 100644 +index 0000000000000000000000000000000000000000..4cf32102a134ebef67d3893cfd24bf0add321eb9 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/registry/PaperRegistryBuilder.java +@@ -0,0 +1,24 @@ ++package io.papermc.paper.registry; ++ ++import org.checkerframework.checker.nullness.qual.Nullable; ++ ++public interface PaperRegistryBuilder<M, T> extends RegistryBuilder<T> { ++ ++ M build(); ++ ++ @FunctionalInterface ++ interface Filler<M, T, B extends PaperRegistryBuilder<M, T>> { ++ ++ B fill(final TypedKey<T> key, final @Nullable M nms); ++ ++ default Factory<M, T, B> asFactory() { ++ return key -> this.fill(key, null); ++ } ++ } ++ ++ @FunctionalInterface ++ interface Factory<M, T, B extends PaperRegistryBuilder<M, T>> { ++ ++ B create(final TypedKey<T> key); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/registry/PaperRegistryListenerManager.java b/src/main/java/io/papermc/paper/registry/PaperRegistryListenerManager.java +new file mode 100644 +index 0000000000000000000000000000000000000000..6b3ba059b4f530b9e3d1c3d6bbc033e96c6a9bc2 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/registry/PaperRegistryListenerManager.java +@@ -0,0 +1,167 @@ ++package io.papermc.paper.registry; ++ ++import com.google.common.base.Preconditions; ++import io.papermc.paper.plugin.bootstrap.BootstrapContext; ++import io.papermc.paper.plugin.entrypoint.Entrypoint; ++import io.papermc.paper.plugin.entrypoint.LaunchEntryPointHandler; ++import io.papermc.paper.plugin.lifecycle.event.LifecycleEventRunner; ++import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEventType; ++import io.papermc.paper.registry.entry.RegistryEntry; ++import io.papermc.paper.registry.entry.RegistryEntryInfo; ++import io.papermc.paper.registry.event.RegistryEntryAddEventImpl; ++import io.papermc.paper.registry.event.RegistryEventMap; ++import io.papermc.paper.registry.event.RegistryEventProvider; ++import io.papermc.paper.registry.event.RegistryFreezeEvent; ++import io.papermc.paper.registry.event.RegistryFreezeEventImpl; ++import io.papermc.paper.registry.event.type.RegistryEntryAddEventType; ++import io.papermc.paper.registry.event.type.RegistryEntryAddEventTypeImpl; ++import io.papermc.paper.registry.event.type.RegistryLifecycleEventType; ++import net.kyori.adventure.key.Key; ++import net.minecraft.core.Holder; ++import net.minecraft.core.MappedRegistry; ++import net.minecraft.core.RegistrationInfo; ++import net.minecraft.core.Registry; ++import net.minecraft.core.WritableRegistry; ++import net.minecraft.resources.ResourceKey; ++import net.minecraft.resources.ResourceLocation; ++import org.checkerframework.checker.nullness.qual.Nullable; ++import org.intellij.lang.annotations.Subst; ++ ++public final class PaperRegistryListenerManager { ++ ++ public static final PaperRegistryListenerManager INSTANCE = new PaperRegistryListenerManager(); ++ ++ public final RegistryEventMap valueAddHooks = new RegistryEventMap("value add"); ++ public final RegistryEventMap freezeHooks = new RegistryEventMap("freeze"); ++ ++ private PaperRegistryListenerManager() { ++ } ++ ++ /** ++ * For {@link Registry#register(Registry, String, Object)} ++ */ ++ public <M> M registerWithListeners(final Registry<M> registry, final String id, final M nms) { ++ return this.registerWithListeners(registry, new ResourceLocation(id), nms); ++ } ++ ++ /** ++ * For {@link Registry#register(Registry, ResourceLocation, Object)} ++ */ ++ public <M> M registerWithListeners(final Registry<M> registry, final ResourceLocation loc, final M nms) { ++ return this.registerWithListeners(registry, ResourceKey.create(registry.key(), loc), nms); ++ } ++ ++ /** ++ * For {@link Registry#register(Registry, ResourceKey, Object)} ++ */ ++ public <M> M registerWithListeners(final Registry<M> registry, final ResourceKey<M> key, final M nms) { ++ return this.registerWithListeners(registry, key, nms, RegistrationInfo.BUILT_IN, PaperRegistryListenerManager::registerWithInstance); ++ } ++ ++ /** ++ * For {@link Registry#registerForHolder(Registry, ResourceLocation, Object)} ++ */ ++ public <M> Holder.Reference<M> registerForHolderWithListeners(final Registry<M> registry, final ResourceLocation loc, final M nms) { ++ return this.registerForHolderWithListeners(registry, ResourceKey.create(registry.key(), loc), nms); ++ } ++ ++ /** ++ * For {@link Registry#registerForHolder(Registry, ResourceKey, Object)} ++ */ ++ public <M> Holder.Reference<M> registerForHolderWithListeners(final Registry<M> registry, final ResourceKey<M> key, final M nms) { ++ return this.registerWithListeners(registry, key, nms, RegistrationInfo.BUILT_IN, WritableRegistry::register); ++ } ++ ++ public <M, T extends org.bukkit.Keyed, B extends PaperRegistryBuilder<M, T>> void registerWithListeners( ++ final Registry<M> registry, ++ final ResourceKey<M> key, ++ final M nms, ++ final RegistrationInfo registrationInfo ++ ) { ++ this.registerWithListeners(registry, key, nms, registrationInfo, WritableRegistry::register); ++ } ++ ++ public <M, T extends org.bukkit.Keyed, B extends PaperRegistryBuilder<M, T>, R> R registerWithListeners( ++ final Registry<M> registry, ++ final ResourceKey<M> key, ++ final M nms, ++ final RegistrationInfo registrationInfo, ++ final RegisterMethod<M, R> registerMethod ++ ) { ++ Preconditions.checkState(LaunchEntryPointHandler.INSTANCE.hasEntered(Entrypoint.BOOTSTRAPPER), registry.key() + " tried to run modification listeners before bootstrappers have been called"); // verify that bootstrappers have been called ++ final @Nullable RegistryEntryInfo<M, T> entry = PaperRegistries.getEntry(registry.key()); ++ if (!(entry instanceof RegistryEntry.Modifiable<?, ?, ?>) || !this.valueAddHooks.hasHooks(entry.apiKey())) { ++ return registerMethod.register((WritableRegistry<M>) registry, key, nms, registrationInfo); ++ } ++ final RegistryEntry.Modifiable<M, T, B> modifiableEntry = (RegistryEntry.Modifiable<M, T, B>) entry; ++ @SuppressWarnings("PatternValidation") final TypedKey<T> typedKey = TypedKey.create(entry.apiKey(), Key.key(key.location().getNamespace(), key.location().getPath())); ++ final B builder = modifiableEntry.fillBuilder(typedKey, nms); ++ return this.registerWithListeners(registry, modifiableEntry, key, nms, builder, registrationInfo, registerMethod); ++ } ++ ++ public <M, T extends org.bukkit.Keyed, B extends PaperRegistryBuilder<M, T>> void registerWithListeners( ++ final Registry<M> registry, ++ final RegistryEntry.Modifiable<M, T, B> entry, ++ final ResourceKey<M> key, ++ final @Nullable M oldNms, ++ final B builder, ++ final RegistrationInfo registrationInfo ++ ) { ++ this.registerWithListeners(registry, entry, key, oldNms, builder, registrationInfo, WritableRegistry::register); ++ } ++ ++ public <M, T extends org.bukkit.Keyed, B extends PaperRegistryBuilder<M, T>, R> R registerWithListeners( ++ final Registry<M> registry, ++ final RegistryEntry.Modifiable<M, T, B> entry, ++ final ResourceKey<M> key, ++ final @Nullable M oldNms, ++ final B builder, ++ final RegistrationInfo registrationInfo, ++ final RegisterMethod<M, R> registerMethod ++ ) { ++ @Subst("namespace:key") final ResourceLocation beingAdded = key.location(); ++ @SuppressWarnings("PatternValidation") final TypedKey<T> typedKey = TypedKey.create(entry.apiKey(), Key.key(beingAdded.getNamespace(), beingAdded.getPath())); ++ final RegistryEntryAddEventImpl<T, B> event = entry.createEntryAddEvent(typedKey, builder); ++ LifecycleEventRunner.INSTANCE.callEvent(this.valueAddHooks.getHook(entry.apiKey()), event); ++ if (oldNms != null) { ++ ((MappedRegistry<M>) registry).clearIntrusiveHolder(oldNms); ++ } ++ final M newNms = event.builder().build(); ++ return registerMethod.register((WritableRegistry<M>) registry, key, newNms, registrationInfo); ++ } ++ ++ private static <M> M registerWithInstance(final WritableRegistry<M> writableRegistry, final ResourceKey<M> key, final M value, final RegistrationInfo registrationInfo) { ++ writableRegistry.register(key, value, registrationInfo); ++ return value; ++ } ++ ++ @FunctionalInterface ++ public interface RegisterMethod<M, R> { ++ ++ R register(WritableRegistry<M> writableRegistry, ResourceKey<M> key, M value, RegistrationInfo registrationInfo); ++ } ++ ++ public <M, T extends org.bukkit.Keyed, B extends PaperRegistryBuilder<M, T>> void runFreezeListeners(final ResourceKey<? extends Registry<M>> resourceKey) { ++ final @Nullable RegistryEntryInfo<M, T> entry = PaperRegistries.getEntry(resourceKey); ++ if (!(entry instanceof RegistryEntry.Writable<?, ?, ?>) || !this.freezeHooks.hasHooks(entry.apiKey())) { ++ return; ++ } ++ final RegistryEntry.Writable<M, T, B> writableEntry = (RegistryEntry.Writable<M, T, B>) entry; ++ final RegistryFreezeEventImpl<T, B> event = writableEntry.createFreezeEvent(PaperRegistryAccess.instance().getWritableRegistry(entry.apiKey())); ++ LifecycleEventRunner.INSTANCE.callEvent(this.freezeHooks.getHook(entry.apiKey()), event); ++ } ++ ++ public <T, B extends RegistryBuilder<T>> RegistryEntryAddEventType<T, B> getRegistryValueAddEventType(final RegistryEventProvider<T, B> type) { ++ if (!(PaperRegistries.getEntry(type.registryKey()) instanceof RegistryEntry.Modifiable)) { ++ throw new IllegalArgumentException(type.registryKey() + " does not support RegistryAdditionEvent"); ++ } ++ return this.valueAddHooks.getOrCreate(type, RegistryEntryAddEventTypeImpl::new); ++ } ++ ++ public <T, B extends RegistryBuilder<T>> LifecycleEventType.Prioritizable<BootstrapContext, RegistryFreezeEvent<T, B>> getRegistryFreezeEventType(final RegistryEventProvider<T, B> type) { ++ if (!(PaperRegistries.getEntry(type.registryKey()) instanceof RegistryEntry.Writable)) { ++ throw new IllegalArgumentException(type.registryKey() + " does not support RegistryPreFreezeEvent"); ++ } ++ return this.freezeHooks.getOrCreate(type, RegistryLifecycleEventType::new); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/registry/WritableCraftRegistry.java b/src/main/java/io/papermc/paper/registry/WritableCraftRegistry.java +new file mode 100644 +index 0000000000000000000000000000000000000000..aff4895d5add72e22ae9f723b77843435ff044aa +--- /dev/null ++++ b/src/main/java/io/papermc/paper/registry/WritableCraftRegistry.java +@@ -0,0 +1,85 @@ ++package io.papermc.paper.registry; ++ ++import com.mojang.serialization.Lifecycle; ++import io.papermc.paper.adventure.PaperAdventure; ++import io.papermc.paper.registry.entry.RegistryEntry; ++import io.papermc.paper.registry.event.WritableRegistry; ++import java.util.Optional; ++import java.util.function.BiFunction; ++import java.util.function.Consumer; ++import net.minecraft.core.MappedRegistry; ++import net.minecraft.core.RegistrationInfo; ++import net.minecraft.resources.ResourceKey; ++import org.bukkit.Keyed; ++import org.bukkit.NamespacedKey; ++import org.bukkit.craftbukkit.CraftRegistry; ++import org.bukkit.craftbukkit.util.ApiVersion; ++import org.checkerframework.checker.nullness.qual.Nullable; ++ ++public class WritableCraftRegistry<M, T extends Keyed, B extends PaperRegistryBuilder<M, T>> extends CraftRegistry<T, M> { ++ ++ private static final RegistrationInfo FROM_PLUGIN = new RegistrationInfo(Optional.empty(), Lifecycle.experimental()); ++ ++ private final RegistryEntry.BuilderHolder<M, T, B> entry; ++ private final MappedRegistry<M> registry; ++ public final WritableRegistry<T, B> apiWritableRegistry = new ApiWritableRegistry(); ++ private final PaperRegistryBuilder.Factory<M, T, ? extends B> builderFactory; ++ private final BiFunction<? super NamespacedKey, M, T> minecraftToBukkit; ++ ++ public WritableCraftRegistry( ++ final RegistryEntry.BuilderHolder<M, T, B> entry, ++ final Class<?> classToPreload, ++ final MappedRegistry<M> registry, ++ final BiFunction<NamespacedKey, ApiVersion, NamespacedKey> serializationUpdater, ++ final PaperRegistryBuilder.Factory<M, T, ? extends B> builderFactory, ++ final BiFunction<? super NamespacedKey, M, T> minecraftToBukkit ++ ) { ++ super(classToPreload, registry, null, serializationUpdater); ++ this.entry = entry; ++ this.registry = registry; ++ this.builderFactory = builderFactory; ++ this.minecraftToBukkit = minecraftToBukkit; ++ } ++ ++ public void register(final TypedKey<T> key, final Consumer<? super B> value) { ++ final ResourceKey<M> resourceKey = ResourceKey.create(this.registry.key(), PaperAdventure.asVanilla(key.key())); ++ this.registry.validateWrite(resourceKey); ++ final B builder = this.newBuilder(key); ++ value.accept(builder); ++ if (this.entry instanceof final RegistryEntry.Modifiable<M, T, B> modifiable && PaperRegistryListenerManager.INSTANCE.valueAddHooks.hasHooks(this.entry.apiKey())) { ++ PaperRegistryListenerManager.INSTANCE.registerWithListeners( ++ this.registry, ++ modifiable, ++ resourceKey, ++ null, ++ builder, ++ FROM_PLUGIN ++ ); ++ } else { ++ this.registry.register(resourceKey, builder.build(), FROM_PLUGIN); ++ } ++ } ++ ++ @Override ++ public final @Nullable T createBukkit(final NamespacedKey namespacedKey, final @Nullable M minecraft) { ++ if (minecraft == null) { ++ return null; ++ } ++ return this.minecraftToBukkit(namespacedKey, minecraft); ++ } ++ ++ public T minecraftToBukkit(final NamespacedKey namespacedKey, final M minecraft) { ++ return this.minecraftToBukkit.apply(namespacedKey, minecraft); ++ } ++ ++ protected B newBuilder(final TypedKey<T> key) { ++ return this.builderFactory.create(key); ++ } ++ ++ public class ApiWritableRegistry implements WritableRegistry<T, B> { ++ @Override ++ public void register(final TypedKey<T> key, final Consumer<? super B> value) { ++ WritableCraftRegistry.this.register(key, value); ++ } ++ } ++} +diff --git a/src/main/java/io/papermc/paper/registry/entry/AddableRegistryEntry.java b/src/main/java/io/papermc/paper/registry/entry/AddableRegistryEntry.java +new file mode 100644 +index 0000000000000000000000000000000000000000..a0f24d396e3a258be1978b2baa3f8e07ff54538f +--- /dev/null ++++ b/src/main/java/io/papermc/paper/registry/entry/AddableRegistryEntry.java +@@ -0,0 +1,43 @@ ++package io.papermc.paper.registry.entry; ++ ++import io.papermc.paper.registry.PaperRegistryBuilder; ++import io.papermc.paper.registry.RegistryHolder; ++import io.papermc.paper.registry.RegistryKey; ++import io.papermc.paper.registry.TypedKey; ++import io.papermc.paper.registry.WritableCraftRegistry; ++import java.util.function.BiFunction; ++import net.minecraft.core.MappedRegistry; ++import net.minecraft.core.Registry; ++import net.minecraft.resources.ResourceKey; ++import org.bukkit.Keyed; ++import org.bukkit.NamespacedKey; ++ ++public class AddableRegistryEntry<M, T extends Keyed, B extends PaperRegistryBuilder<M, T>> extends CraftRegistryEntry<M, T> implements RegistryEntry.Addable<M, T, B> { ++ ++ private final PaperRegistryBuilder.Filler<M, T, B> builderFiller; ++ ++ protected AddableRegistryEntry( ++ final ResourceKey<? extends Registry<M>> mcKey, ++ final RegistryKey<T> apiKey, ++ final Class<?> classToPreload, ++ final BiFunction<NamespacedKey, M, T> minecraftToBukkit, ++ final PaperRegistryBuilder.Filler<M, T, B> builderFiller ++ ) { ++ super(mcKey, apiKey, classToPreload, minecraftToBukkit); ++ this.builderFiller = builderFiller; ++ } ++ ++ private WritableCraftRegistry<M, T, B> createRegistry(final Registry<M> registry) { ++ return new WritableCraftRegistry<>(this, this.classToPreload, (MappedRegistry<M>) registry, this.updater, this.builderFiller.asFactory(), this.minecraftToBukkit); ++ } ++ ++ @Override ++ public RegistryHolder<T> createRegistryHolder(final Registry<M> nmsRegistry) { ++ return new RegistryHolder.Memoized<>(() -> this.createRegistry(nmsRegistry)); ++ } ++ ++ @Override ++ public B fillBuilder(final TypedKey<T> key, final M nms) { ++ return this.builderFiller.fill(key, nms); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/registry/entry/ModifiableRegistryEntry.java b/src/main/java/io/papermc/paper/registry/entry/ModifiableRegistryEntry.java +new file mode 100644 +index 0000000000000000000000000000000000000000..df10008f6bde1047c8a13523c0a8fc01222a4e62 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/registry/entry/ModifiableRegistryEntry.java +@@ -0,0 +1,31 @@ ++package io.papermc.paper.registry.entry; ++ ++import io.papermc.paper.registry.PaperRegistryBuilder; ++import io.papermc.paper.registry.RegistryKey; ++import io.papermc.paper.registry.TypedKey; ++import java.util.function.BiFunction; ++import net.minecraft.core.Registry; ++import net.minecraft.resources.ResourceKey; ++import org.bukkit.Keyed; ++import org.bukkit.NamespacedKey; ++ ++public class ModifiableRegistryEntry<M, T extends Keyed, B extends PaperRegistryBuilder<M, T>> extends CraftRegistryEntry<M, T> implements RegistryEntry.Modifiable<M, T, B> { ++ ++ protected final PaperRegistryBuilder.Filler<M, T, B> builderFiller; ++ ++ protected ModifiableRegistryEntry( ++ final ResourceKey<? extends Registry<M>> mcKey, ++ final RegistryKey<T> apiKey, ++ final Class<?> toPreload, ++ final BiFunction<NamespacedKey, M, T> minecraftToBukkit, ++ final PaperRegistryBuilder.Filler<M, T, B> builderFiller ++ ) { ++ super(mcKey, apiKey, toPreload, minecraftToBukkit); ++ this.builderFiller = builderFiller; ++ } ++ ++ @Override ++ public B fillBuilder(final TypedKey<T> key, final M nms) { ++ return this.builderFiller.fill(key, nms); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/registry/entry/RegistryEntry.java b/src/main/java/io/papermc/paper/registry/entry/RegistryEntry.java +index 15991bf13894d850f360a520d1815711d25973ec..c49170d469dca809dc42421168d14e57e24d734b 100644 +--- a/src/main/java/io/papermc/paper/registry/entry/RegistryEntry.java ++++ b/src/main/java/io/papermc/paper/registry/entry/RegistryEntry.java +@@ -1,7 +1,12 @@ + package io.papermc.paper.registry.entry; + ++import io.papermc.paper.registry.PaperRegistryBuilder; + import io.papermc.paper.registry.RegistryHolder; + import io.papermc.paper.registry.RegistryKey; ++import io.papermc.paper.registry.TypedKey; ++import io.papermc.paper.registry.WritableCraftRegistry; ++import io.papermc.paper.registry.event.RegistryEntryAddEventImpl; ++import io.papermc.paper.registry.event.RegistryFreezeEventImpl; + import io.papermc.paper.registry.legacy.DelayedRegistryEntry; + import java.util.function.BiFunction; + import java.util.function.Supplier; +@@ -32,6 +37,37 @@ public interface RegistryEntry<M, B extends Keyed> extends RegistryEntryInfo<M, + return new DelayedRegistryEntry<>(this); + } + ++ interface BuilderHolder<M, T, B extends PaperRegistryBuilder<M, T>> extends RegistryEntryInfo<M, T> { ++ ++ B fillBuilder(TypedKey<T> key, M nms); ++ } ++ ++ /** ++ * Can mutate values being added to the registry ++ */ ++ interface Modifiable<M, T, B extends PaperRegistryBuilder<M, T>> extends BuilderHolder<M, T, B> { ++ ++ default RegistryEntryAddEventImpl<T, B> createEntryAddEvent(final TypedKey<T> key, final B initialBuilder) { ++ return new RegistryEntryAddEventImpl<>(key, initialBuilder, this.apiKey()); ++ } ++ } ++ ++ /** ++ * Can only add new values to the registry, not modify any values. ++ */ ++ interface Addable<M, T extends Keyed, B extends PaperRegistryBuilder<M, T>> extends BuilderHolder<M, T, B> { // TODO remove Keyed ++ ++ default RegistryFreezeEventImpl<T, B> createFreezeEvent(final WritableCraftRegistry<M, T, B> writableRegistry) { ++ return new RegistryFreezeEventImpl<>(this.apiKey(), writableRegistry.apiWritableRegistry); ++ } ++ } ++ ++ /** ++ * Can mutate values and add new values. ++ */ ++ interface Writable<M, T extends Keyed, B extends PaperRegistryBuilder<M, T>> extends Modifiable<M, T, B>, Addable<M, T, B> { // TODO remove Keyed ++ } ++ + static <M, B extends Keyed> RegistryEntry<M, B> entry( + final ResourceKey<? extends Registry<M>> mcKey, + final RegistryKey<B> apiKey, +@@ -48,4 +84,24 @@ public interface RegistryEntry<M, B extends Keyed> extends RegistryEntryInfo<M, + ) { + return new ApiRegistryEntry<>(mcKey, apiKey, apiRegistrySupplier); + } ++ ++ static <M, T extends Keyed, B extends PaperRegistryBuilder<M, T>> RegistryEntry<M, T> modifiable( ++ final ResourceKey<? extends Registry<M>> mcKey, ++ final RegistryKey<T> apiKey, ++ final Class<?> toPreload, ++ final BiFunction<NamespacedKey, M, T> minecraftToBukkit, ++ final PaperRegistryBuilder.Filler<M, T, B> filler ++ ) { ++ return new ModifiableRegistryEntry<>(mcKey, apiKey, toPreload, minecraftToBukkit, filler); ++ } ++ ++ static <M, T extends Keyed, B extends PaperRegistryBuilder<M, T>> RegistryEntry<M, T> writable( ++ final ResourceKey<? extends Registry<M>> mcKey, ++ final RegistryKey<T> apiKey, ++ final Class<?> toPreload, ++ final BiFunction<NamespacedKey, M, T> minecraftToBukkit, ++ final PaperRegistryBuilder.Filler<M, T, B> filler ++ ) { ++ return new WritableRegistryEntry<>(mcKey, apiKey, toPreload, minecraftToBukkit, filler); ++ } + } +diff --git a/src/main/java/io/papermc/paper/registry/entry/WritableRegistryEntry.java b/src/main/java/io/papermc/paper/registry/entry/WritableRegistryEntry.java +new file mode 100644 +index 0000000000000000000000000000000000000000..562accce731630327d116afd1c9d559df7e386bd +--- /dev/null ++++ b/src/main/java/io/papermc/paper/registry/entry/WritableRegistryEntry.java +@@ -0,0 +1,22 @@ ++package io.papermc.paper.registry.entry; ++ ++import io.papermc.paper.registry.PaperRegistryBuilder; ++import io.papermc.paper.registry.RegistryKey; ++import java.util.function.BiFunction; ++import net.minecraft.core.Registry; ++import net.minecraft.resources.ResourceKey; ++import org.bukkit.Keyed; ++import org.bukkit.NamespacedKey; ++ ++public class WritableRegistryEntry<M, T extends Keyed, B extends PaperRegistryBuilder<M, T>> extends AddableRegistryEntry<M, T, B> implements RegistryEntry.Writable<M, T, B> { // TODO remove Keyed ++ ++ protected WritableRegistryEntry( ++ final ResourceKey<? extends Registry<M>> mcKey, ++ final RegistryKey<T> apiKey, ++ final Class<?> classToPreload, ++ final BiFunction<NamespacedKey, M, T> minecraftToBukkit, ++ final PaperRegistryBuilder.Filler<M, T, B> builderFiller ++ ) { ++ super(mcKey, apiKey, classToPreload, minecraftToBukkit, builderFiller); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/registry/event/RegistryEntryAddEventImpl.java b/src/main/java/io/papermc/paper/registry/event/RegistryEntryAddEventImpl.java +new file mode 100644 +index 0000000000000000000000000000000000000000..a1d747c1a46d3aa8bcdef82bebb48f1fd760e61f +--- /dev/null ++++ b/src/main/java/io/papermc/paper/registry/event/RegistryEntryAddEventImpl.java +@@ -0,0 +1,13 @@ ++package io.papermc.paper.registry.event; ++ ++import io.papermc.paper.plugin.lifecycle.event.PaperLifecycleEvent; ++import io.papermc.paper.registry.RegistryBuilder; ++import io.papermc.paper.registry.RegistryKey; ++import io.papermc.paper.registry.TypedKey; ++ ++public record RegistryEntryAddEventImpl<T, B extends RegistryBuilder<T>>( ++ TypedKey<T> key, ++ B builder, ++ RegistryKey<T> registryKey ++) implements RegistryEntryAddEvent<T, B>, PaperLifecycleEvent { ++} +diff --git a/src/main/java/io/papermc/paper/registry/event/RegistryEventMap.java b/src/main/java/io/papermc/paper/registry/event/RegistryEventMap.java +new file mode 100644 +index 0000000000000000000000000000000000000000..f5ea23173dcbe491742c3dd051c147ef397307a0 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/registry/event/RegistryEventMap.java +@@ -0,0 +1,44 @@ ++package io.papermc.paper.registry.event; ++ ++import io.papermc.paper.plugin.bootstrap.BootstrapContext; ++import io.papermc.paper.plugin.lifecycle.event.LifecycleEventRunner; ++import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEventType; ++import io.papermc.paper.registry.RegistryBuilder; ++import io.papermc.paper.registry.RegistryKey; ++import java.util.HashMap; ++import java.util.Map; ++import java.util.Objects; ++import java.util.function.BiFunction; ++ ++public final class RegistryEventMap { ++ ++ private final Map<RegistryKey<?>, LifecycleEventType<BootstrapContext, ? extends RegistryEvent<?>, ?>> hooks = new HashMap<>(); ++ private final String name; ++ ++ public RegistryEventMap(final String name) { ++ this.name = name; ++ } ++ ++ @SuppressWarnings("unchecked") ++ public <T, B extends RegistryBuilder<T>, E extends RegistryEvent<T>, ET extends LifecycleEventType<BootstrapContext, E, ?>> ET getOrCreate(final RegistryEventProvider<T, B> type, final BiFunction<? super RegistryEventProvider<T, B>, ? super String, ET> eventTypeCreator) { ++ final ET registerHook; ++ if (this.hooks.containsKey(type.registryKey())) { ++ registerHook = (ET) this.hooks.get(type.registryKey()); ++ } else { ++ registerHook = eventTypeCreator.apply(type, this.name); ++ LifecycleEventRunner.INSTANCE.addEventType(registerHook); ++ this.hooks.put(type.registryKey(), registerHook); ++ } ++ return registerHook; ++ } ++ ++ @SuppressWarnings("unchecked") ++ public <T, E extends RegistryEvent<T>> LifecycleEventType<BootstrapContext, E, ?> getHook(final RegistryKey<T> registryKey) { ++ return (LifecycleEventType<BootstrapContext, E, ?>) Objects.requireNonNull(this.hooks.get(registryKey), "No hook for " + registryKey); ++ } ++ ++ public boolean hasHooks(final RegistryKey<?> registryKey) { ++ return this.hooks.containsKey(registryKey); ++ } ++ ++} +diff --git a/src/main/java/io/papermc/paper/registry/event/RegistryEventTypeProviderImpl.java b/src/main/java/io/papermc/paper/registry/event/RegistryEventTypeProviderImpl.java +new file mode 100644 +index 0000000000000000000000000000000000000000..34c842ffa355e3c8001dd7b8551bcb49229a6391 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/registry/event/RegistryEventTypeProviderImpl.java +@@ -0,0 +1,24 @@ ++package io.papermc.paper.registry.event; ++ ++import io.papermc.paper.plugin.bootstrap.BootstrapContext; ++import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEventType; ++import io.papermc.paper.registry.PaperRegistryListenerManager; ++import io.papermc.paper.registry.RegistryBuilder; ++import io.papermc.paper.registry.event.type.RegistryEntryAddEventType; ++ ++public class RegistryEventTypeProviderImpl implements RegistryEventTypeProvider { ++ ++ public static RegistryEventTypeProviderImpl instance() { ++ return (RegistryEventTypeProviderImpl) RegistryEventTypeProvider.provider(); ++ } ++ ++ @Override ++ public <T, B extends RegistryBuilder<T>> RegistryEntryAddEventType<T, B> registryEntryAdd(final RegistryEventProvider<T, B> type) { ++ return PaperRegistryListenerManager.INSTANCE.getRegistryValueAddEventType(type); ++ } ++ ++ @Override ++ public <T, B extends RegistryBuilder<T>> LifecycleEventType.Prioritizable<BootstrapContext, RegistryFreezeEvent<T, B>> registryFreeze(final RegistryEventProvider<T, B> type) { ++ return PaperRegistryListenerManager.INSTANCE.getRegistryFreezeEventType(type); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/registry/event/RegistryFreezeEventImpl.java b/src/main/java/io/papermc/paper/registry/event/RegistryFreezeEventImpl.java +new file mode 100644 +index 0000000000000000000000000000000000000000..2db5d33d0b72ec3c9ff1c3042d9246dfa4344346 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/registry/event/RegistryFreezeEventImpl.java +@@ -0,0 +1,11 @@ ++package io.papermc.paper.registry.event; ++ ++import io.papermc.paper.plugin.lifecycle.event.PaperLifecycleEvent; ++import io.papermc.paper.registry.RegistryBuilder; ++import io.papermc.paper.registry.RegistryKey; ++ ++public record RegistryFreezeEventImpl<T, B extends RegistryBuilder<T>>( ++ RegistryKey<T> registryKey, ++ WritableRegistry<T, B> registry ++) implements RegistryFreezeEvent<T, B>, PaperLifecycleEvent { ++} +diff --git a/src/main/java/io/papermc/paper/registry/event/package-info.java b/src/main/java/io/papermc/paper/registry/event/package-info.java +new file mode 100644 +index 0000000000000000000000000000000000000000..14d2d9766b8dee763f220c397aba3ad432d02aaa +--- /dev/null ++++ b/src/main/java/io/papermc/paper/registry/event/package-info.java +@@ -0,0 +1,5 @@ ++@DefaultQualifier(NonNull.class) ++package io.papermc.paper.registry.event; ++ ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.framework.qual.DefaultQualifier; +diff --git a/src/main/java/io/papermc/paper/registry/event/type/RegistryEntryAddEventTypeImpl.java b/src/main/java/io/papermc/paper/registry/event/type/RegistryEntryAddEventTypeImpl.java +new file mode 100644 +index 0000000000000000000000000000000000000000..32303ea9b3da736cbe26d06e57f5dcc3aa32a99b +--- /dev/null ++++ b/src/main/java/io/papermc/paper/registry/event/type/RegistryEntryAddEventTypeImpl.java +@@ -0,0 +1,32 @@ ++package io.papermc.paper.registry.event.type; ++ ++import io.papermc.paper.plugin.bootstrap.BootstrapContext; ++import io.papermc.paper.plugin.lifecycle.event.handler.LifecycleEventHandler; ++import io.papermc.paper.plugin.lifecycle.event.types.PrioritizableLifecycleEventType; ++import io.papermc.paper.registry.RegistryBuilder; ++import io.papermc.paper.registry.event.RegistryEntryAddEvent; ++import io.papermc.paper.registry.event.RegistryEventProvider; ++import java.util.function.Consumer; ++import java.util.function.Predicate; ++ ++public class RegistryEntryAddEventTypeImpl<T, B extends RegistryBuilder<T>> extends PrioritizableLifecycleEventType<BootstrapContext, RegistryEntryAddEvent<T, B>, RegistryEntryAddConfiguration<T>> implements RegistryEntryAddEventType<T, B> { ++ ++ public RegistryEntryAddEventTypeImpl(final RegistryEventProvider<T, B> type, final String eventName) { ++ super(type.registryKey() + " / " + eventName, BootstrapContext.class); ++ } ++ ++ @Override ++ public RegistryEntryAddConfiguration<T> newHandler(final LifecycleEventHandler<? super RegistryEntryAddEvent<T, B>> handler) { ++ return new RegistryEntryAddHandlerConfiguration<>(handler, this); ++ } ++ ++ @Override ++ public void forEachHandler(final RegistryEntryAddEvent<T, B> event, final Consumer<RegisteredHandler<BootstrapContext, RegistryEntryAddEvent<T, B>>> consumer, final Predicate<RegisteredHandler<BootstrapContext, RegistryEntryAddEvent<T, B>>> predicate) { ++ super.forEachHandler(event, consumer, predicate.and(handler -> this.matchesTarget(event, handler))); ++ } ++ ++ private boolean matchesTarget(final RegistryEntryAddEvent<T, B> event, final RegisteredHandler<BootstrapContext, RegistryEntryAddEvent<T, B>> handler) { ++ final RegistryEntryAddHandlerConfiguration<T, B> config = (RegistryEntryAddHandlerConfiguration<T, B>) handler.config(); ++ return config.target() == null || event.key().equals(config.target()); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/registry/event/type/RegistryEntryAddHandlerConfiguration.java b/src/main/java/io/papermc/paper/registry/event/type/RegistryEntryAddHandlerConfiguration.java +new file mode 100644 +index 0000000000000000000000000000000000000000..53df2dd1a9e1cef90bd8504c717b1cc6374b6f4e +--- /dev/null ++++ b/src/main/java/io/papermc/paper/registry/event/type/RegistryEntryAddHandlerConfiguration.java +@@ -0,0 +1,39 @@ ++package io.papermc.paper.registry.event.type; ++ ++import io.papermc.paper.plugin.bootstrap.BootstrapContext; ++import io.papermc.paper.plugin.lifecycle.event.handler.LifecycleEventHandler; ++import io.papermc.paper.plugin.lifecycle.event.handler.configuration.PrioritizedLifecycleEventHandlerConfigurationImpl; ++import io.papermc.paper.plugin.lifecycle.event.types.AbstractLifecycleEventType; ++import io.papermc.paper.registry.RegistryBuilder; ++import io.papermc.paper.registry.TypedKey; ++import io.papermc.paper.registry.event.RegistryEntryAddEvent; ++import org.checkerframework.checker.nullness.qual.Nullable; ++ ++public class RegistryEntryAddHandlerConfiguration<T, B extends RegistryBuilder<T>> extends PrioritizedLifecycleEventHandlerConfigurationImpl<BootstrapContext, RegistryEntryAddEvent<T, B>> implements RegistryEntryAddConfiguration<T> { ++ ++ private @Nullable TypedKey<T> target; ++ ++ public RegistryEntryAddHandlerConfiguration(final LifecycleEventHandler<? super RegistryEntryAddEvent<T, B>> handler, final AbstractLifecycleEventType<BootstrapContext, RegistryEntryAddEvent<T, B>, ?> eventType) { ++ super(handler, eventType); ++ } ++ ++ public @Nullable TypedKey<T> target() { ++ return this.target; ++ } ++ ++ @Override ++ public RegistryEntryAddConfiguration<T> onlyFor(final TypedKey<T> key) { ++ this.target = key; ++ return this; ++ } ++ ++ @Override ++ public RegistryEntryAddConfiguration<T> priority(final int priority) { ++ return (RegistryEntryAddConfiguration<T>) super.priority(priority); ++ } ++ ++ @Override ++ public RegistryEntryAddConfiguration<T> monitor() { ++ return (RegistryEntryAddConfiguration<T>) super.monitor(); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/registry/event/type/RegistryLifecycleEventType.java b/src/main/java/io/papermc/paper/registry/event/type/RegistryLifecycleEventType.java +new file mode 100644 +index 0000000000000000000000000000000000000000..dcc0f6b337840a78d38abdf2eb3f4bbd1676f58f +--- /dev/null ++++ b/src/main/java/io/papermc/paper/registry/event/type/RegistryLifecycleEventType.java +@@ -0,0 +1,14 @@ ++package io.papermc.paper.registry.event.type; ++ ++import io.papermc.paper.plugin.bootstrap.BootstrapContext; ++import io.papermc.paper.plugin.lifecycle.event.types.PrioritizableLifecycleEventType; ++import io.papermc.paper.registry.RegistryBuilder; ++import io.papermc.paper.registry.event.RegistryEvent; ++import io.papermc.paper.registry.event.RegistryEventProvider; ++ ++public final class RegistryLifecycleEventType<T, B extends RegistryBuilder<T>, E extends RegistryEvent<T>> extends PrioritizableLifecycleEventType.Simple<BootstrapContext, E> { ++ ++ public RegistryLifecycleEventType(final RegistryEventProvider<T, B> type, final String eventName) { ++ super(type.registryKey() + " / " + eventName, BootstrapContext.class); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/registry/package-info.java b/src/main/java/io/papermc/paper/registry/package-info.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0b80179ff90e085568d7ceafd9b17511789dc99b +--- /dev/null ++++ b/src/main/java/io/papermc/paper/registry/package-info.java +@@ -0,0 +1,5 @@ ++@DefaultQualifier(NonNull.class) ++package io.papermc.paper.registry; ++ ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.framework.qual.DefaultQualifier; +diff --git a/src/main/java/net/minecraft/core/MappedRegistry.java b/src/main/java/net/minecraft/core/MappedRegistry.java +index edbbafd1705345282e5e6251eb71bfde5793b7d4..f22d22ebcedcc9c20225677844c86a1ad27c4211 100644 +--- a/src/main/java/net/minecraft/core/MappedRegistry.java ++++ b/src/main/java/net/minecraft/core/MappedRegistry.java +@@ -441,4 +441,12 @@ public class MappedRegistry<T> implements WritableRegistry<T> { + public HolderLookup.RegistryLookup<T> asLookup() { + return this.lookup; + } ++ // Paper start ++ // used to clear intrusive holders from GameEvent, Item, Block, EntityType, and Fluid from unused instances of those types ++ public void clearIntrusiveHolder(final T instance) { ++ if (this.unregisteredIntrusiveHolders != null) { ++ this.unregisteredIntrusiveHolders.remove(instance); ++ } ++ } ++ // Paper end + } +diff --git a/src/main/java/net/minecraft/core/registries/BuiltInRegistries.java b/src/main/java/net/minecraft/core/registries/BuiltInRegistries.java +index 44b7927081b476813505cab6b3a2da2ec2942c54..c96bcd815ec9bf76625b5bedef461da49d7240ce 100644 +--- a/src/main/java/net/minecraft/core/registries/BuiltInRegistries.java ++++ b/src/main/java/net/minecraft/core/registries/BuiltInRegistries.java +@@ -328,6 +328,7 @@ public class BuiltInRegistries { + } + public static void bootStrap(Runnable runnable) { + // Paper end ++ REGISTRY.freeze(); // Paper - freeze main registry early + createContents(); + runnable.run(); // Paper + freeze(); +@@ -346,6 +347,7 @@ public class BuiltInRegistries { + REGISTRY.freeze(); + + for (Registry<?> registry : REGISTRY) { ++ io.papermc.paper.registry.PaperRegistryListenerManager.INSTANCE.runFreezeListeners(registry.key()); // Paper + registry.freeze(); + } + } +diff --git a/src/main/java/net/minecraft/resources/RegistryDataLoader.java b/src/main/java/net/minecraft/resources/RegistryDataLoader.java +index abadf4abe08dc3bb6612b42cbb3f7df3ffa28ce9..ce65f2ea462b920e5e14b8cb687ef371c311d242 100644 +--- a/src/main/java/net/minecraft/resources/RegistryDataLoader.java ++++ b/src/main/java/net/minecraft/resources/RegistryDataLoader.java +@@ -136,6 +136,7 @@ public class RegistryDataLoader { + list.forEach(loader -> loadable.apply((RegistryDataLoader.Loader<?>)loader, registryInfoLookup)); + list.forEach(loader -> { + Registry<?> registry = loader.registry(); ++ io.papermc.paper.registry.PaperRegistryListenerManager.INSTANCE.runFreezeListeners(loader.registry.key()); // Paper - run pre-freeze listeners + + try { + registry.freeze(); +@@ -199,7 +200,7 @@ public class RegistryDataLoader { + JsonElement jsonElement = JsonParser.parseReader(reader); + DataResult<E> dataResult = decoder.parse(ops, jsonElement); + E object = dataResult.getOrThrow(); +- registry.register(key, object, entryInfo); ++ io.papermc.paper.registry.PaperRegistryListenerManager.INSTANCE.registerWithListeners(registry, key, object, entryInfo); // Paper + } + } + +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java b/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java +index d21b7e39d71c785f47f790e1ad4be33a8e8e6e51..43d686a9958cff96f5b15d93e920c8f2313aa65b 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java +@@ -156,11 +156,11 @@ public class CraftRegistry<B extends Keyed, M> implements Registry<B> { + private final Map<NamespacedKey, B> cache = new HashMap<>(); + private final Map<B, NamespacedKey> byValue = new java.util.IdentityHashMap<>(); // Paper - improve Registry + private final net.minecraft.core.Registry<M> minecraftRegistry; +- private final BiFunction<NamespacedKey, M, B> minecraftToBukkit; ++ private final BiFunction<? super NamespacedKey, M, B> minecraftToBukkit; // Paper + private final BiFunction<NamespacedKey, ApiVersion, NamespacedKey> serializationUpdater; // Paper - rename to make it *clear* what it is *only* for + private boolean init; + +- public CraftRegistry(Class<?> bukkitClass, net.minecraft.core.Registry<M> minecraftRegistry, BiFunction<NamespacedKey, M, B> minecraftToBukkit, BiFunction<NamespacedKey, ApiVersion, NamespacedKey> serializationUpdater) { // Paper - relax preload class ++ public CraftRegistry(Class<?> bukkitClass, net.minecraft.core.Registry<M> minecraftRegistry, BiFunction<? super NamespacedKey, M, B> minecraftToBukkit, BiFunction<NamespacedKey, ApiVersion, NamespacedKey> serializationUpdater) { // Paper - relax preload class + this.bukkitClass = bukkitClass; + this.minecraftRegistry = minecraftRegistry; + this.minecraftToBukkit = minecraftToBukkit; +diff --git a/src/main/resources/META-INF/services/io.papermc.paper.registry.event.RegistryEventTypeProvider b/src/main/resources/META-INF/services/io.papermc.paper.registry.event.RegistryEventTypeProvider +new file mode 100644 +index 0000000000000000000000000000000000000000..8bee1a5ed877a04e4d027593df1f42cefdd824e7 +--- /dev/null ++++ b/src/main/resources/META-INF/services/io.papermc.paper.registry.event.RegistryEventTypeProvider +@@ -0,0 +1 @@ ++io.papermc.paper.registry.event.RegistryEventTypeProviderImpl |