Paper/patches/server/0934-Add-Lifecycle-Event-system.patch

794 lines
40 KiB
Diff
Raw Normal View History

From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Jake Potrebic <jake.m.potrebic@gmail.com>
Date: Tue, 18 Jul 2023 17:49:38 -0700
Subject: [PATCH] Add Lifecycle Event system
This event system is separate from Bukkit's event system and is
meant for managing resources across reloads and from points in the
PluginBootstrap.
diff --git a/src/main/java/io/papermc/paper/plugin/bootstrap/PluginBootstrapContextImpl.java b/src/main/java/io/papermc/paper/plugin/bootstrap/PluginBootstrapContextImpl.java
index 30b50e6294c6eaade5e17cfaf34600d122e6251c..0bb7694188d5fb75bb756ce75d0060ea980027ee 100644
--- a/src/main/java/io/papermc/paper/plugin/bootstrap/PluginBootstrapContextImpl.java
+++ b/src/main/java/io/papermc/paper/plugin/bootstrap/PluginBootstrapContextImpl.java
@@ -1,6 +1,8 @@
package io.papermc.paper.plugin.bootstrap;
import io.papermc.paper.plugin.configuration.PluginMeta;
+import io.papermc.paper.plugin.lifecycle.event.LifecycleEventManager;
+import io.papermc.paper.plugin.lifecycle.event.PaperLifecycleEventManager;
import io.papermc.paper.plugin.provider.PluginProvider;
import java.nio.file.Path;
import net.kyori.adventure.text.logger.slf4j.ComponentLogger;
@@ -12,6 +14,10 @@ public final class PluginBootstrapContextImpl implements BootstrapContext {
private final Path dataFolder;
private final ComponentLogger logger;
private final Path pluginSource;
+ // Paper start - lifecycle events
+ private boolean allowsLifecycleRegistration = true;
+ private final PaperLifecycleEventManager<BootstrapContext> lifecycleEventManager = new PaperLifecycleEventManager<>(this, () -> this.allowsLifecycleRegistration); // Paper - lifecycle events
+ // Paper end - lifecycle events
public PluginBootstrapContextImpl(PluginMeta config, Path dataFolder, ComponentLogger logger, Path pluginSource) {
this.config = config;
@@ -45,4 +51,20 @@ public final class PluginBootstrapContextImpl implements BootstrapContext {
public @NotNull Path getPluginSource() {
return this.pluginSource;
}
+
+ // Paper start - lifecycle event system
+ @Override
+ public @NotNull PluginMeta getPluginMeta() {
+ return this.config;
+ }
+
+ @Override
+ public LifecycleEventManager<BootstrapContext> getLifecycleManager() {
+ return this.lifecycleEventManager;
+ }
+
+ public void lockLifecycleEventRegistration() {
+ this.allowsLifecycleRegistration = false;
+ }
+ // Paper end
}
diff --git a/src/main/java/io/papermc/paper/plugin/lifecycle/event/LifecycleEventRunner.java b/src/main/java/io/papermc/paper/plugin/lifecycle/event/LifecycleEventRunner.java
new file mode 100644
index 0000000000000000000000000000000000000000..d0ef7fa0b3e5935d48f894596be6672b0016948a
--- /dev/null
+++ b/src/main/java/io/papermc/paper/plugin/lifecycle/event/LifecycleEventRunner.java
@@ -0,0 +1,110 @@
+package io.papermc.paper.plugin.lifecycle.event;
+
+import com.google.common.base.Suppliers;
+import com.mojang.logging.LogUtils;
+import io.papermc.paper.plugin.bootstrap.BootstrapContext;
+import io.papermc.paper.plugin.lifecycle.event.registrar.PaperRegistrar;
+import io.papermc.paper.plugin.lifecycle.event.registrar.RegistrarEvent;
+import io.papermc.paper.plugin.lifecycle.event.registrar.RegistrarEventImpl;
+import io.papermc.paper.plugin.lifecycle.event.registrar.ReloadableRegistrarEvent;
+import io.papermc.paper.plugin.lifecycle.event.types.AbstractLifecycleEventType;
+import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEventType;
+import io.papermc.paper.plugin.lifecycle.event.types.OwnerAwareLifecycleEvent;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+import org.bukkit.plugin.Plugin;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.framework.qual.DefaultQualifier;
+import org.slf4j.Logger;
+
+@DefaultQualifier(NonNull.class)
+public class LifecycleEventRunner {
+
+ private static final Logger LOGGER = LogUtils.getClassLogger();
+ private static final Supplier<Set<LifecycleEventType<?, ?, ?>>> BLOCKS_RELOADING = Suppliers.memoize(() -> Set.of( // lazy due to cyclic initialization
+ ));
+ public static final LifecycleEventRunner INSTANCE = new LifecycleEventRunner();
+
+ private final List<LifecycleEventType<?, ?, ?>> lifecycleEventTypes = new ArrayList<>();
+ private boolean blockPluginReloading = false;
+
+ public void checkRegisteredHandler(final LifecycleEventOwner owner, final LifecycleEventType<?, ?, ?> eventType) {
+ /*
+ Lifecycle event handlers for reloadable events that are registered from the BootstrapContext prevent
+ the server from reloading plugins. This is because reloading plugins requires disabling all the plugins,
+ running the reload logic (which would include places where these events should fire) and then re-enabling plugins.
+ */
+ if (owner instanceof BootstrapContext && BLOCKS_RELOADING.get().contains(eventType)) {
+ this.blockPluginReloading = true;
+ }
+ }
+
+ public boolean blocksPluginReloading() {
+ return this.blockPluginReloading;
+ }
+
+ public <O extends LifecycleEventOwner, E extends LifecycleEvent, ET extends LifecycleEventType<O, E, ?>> ET addEventType(final ET eventType) {
+ this.lifecycleEventTypes.add(eventType);
+ return eventType;
+ }
+
+ public <O extends LifecycleEventOwner, E extends PaperLifecycleEvent> void callEvent(final LifecycleEventType<O, ? super E, ?> eventType, final E event) {
+ this.callEvent(eventType, event, $ -> true);
+ }
+
+ public <O extends LifecycleEventOwner, E extends PaperLifecycleEvent> void callEvent(final LifecycleEventType<O, ? super E, ?> eventType, final E event, final Predicate<? super O> ownerPredicate) {
+ final AbstractLifecycleEventType<O, ? super E, ?> lifecycleEventType = (AbstractLifecycleEventType<O, ? super E, ?>) eventType;
+ lifecycleEventType.forEachHandler(event, registeredHandler -> {
+ try {
+ if (event instanceof final OwnerAwareLifecycleEvent<?> ownerAwareEvent) {
+ ownerAwareGenericHelper(ownerAwareEvent, registeredHandler.owner());
+ }
+ registeredHandler.lifecycleEventHandler().run(event);
+ } catch (final Throwable ex) {
+ throw new RuntimeException("Could not run '%s' lifecycle event handler from %s".formatted(lifecycleEventType.name(), registeredHandler.owner().getPluginMeta().getDisplayName()), ex);
+ } finally {
+ if (event instanceof final OwnerAwareLifecycleEvent<?> ownerAwareEvent) {
+ ownerAwareEvent.setOwner(null);
+ }
+ }
+ }, handler -> ownerPredicate.test(handler.owner()));
+ event.invalidate();
+ }
+
+ private static <O extends LifecycleEventOwner> void ownerAwareGenericHelper(final OwnerAwareLifecycleEvent<O> event, final LifecycleEventOwner possibleOwner) {
+ final @Nullable O owner = event.castOwner(possibleOwner);
+ if (owner != null) {
+ event.setOwner(owner);
+ } else {
+ throw new IllegalStateException("Found invalid owner " + possibleOwner + " for event " + event);
+ }
+ }
+
+ public void unregisterAllEventHandlersFor(final Plugin plugin) {
+ for (final LifecycleEventType<?, ?, ?> lifecycleEventType : this.lifecycleEventTypes) {
+ this.removeEventHandlersOwnedBy(lifecycleEventType, plugin);
+ }
+ }
+
+ private <O extends LifecycleEventOwner> void removeEventHandlersOwnedBy(final LifecycleEventType<O, ?, ?> eventType, final Plugin possibleOwner) {
+ final AbstractLifecycleEventType<O, ?, ?> lifecycleEventType = (AbstractLifecycleEventType<O, ?, ?>) eventType;
+ lifecycleEventType.removeMatching(registeredHandler -> registeredHandler.owner().getPluginMeta().getName().equals(possibleOwner.getPluginMeta().getName()));
+ }
+
+ @SuppressWarnings("unchecked")
+ public <O extends LifecycleEventOwner, R extends PaperRegistrar<? super O>> void callStaticRegistrarEvent(final LifecycleEventType<O, ? extends RegistrarEvent<? super R>, ?> lifecycleEventType, final R registrar, final Class<? extends O> ownerClass) {
+ this.callEvent((LifecycleEventType<O, RegistrarEvent<? super R>, ?>) lifecycleEventType, new RegistrarEventImpl<>(registrar, ownerClass), ownerClass::isInstance);
+ }
+
+ @SuppressWarnings("unchecked")
+ public <O extends LifecycleEventOwner, R extends PaperRegistrar<? super O>> void callReloadableRegistrarEvent(final LifecycleEventType<O, ? extends ReloadableRegistrarEvent<? super R>, ?> lifecycleEventType, final R registrar, final Class<? extends O> ownerClass, final ReloadableRegistrarEvent.Cause cause) {
+ this.callEvent((LifecycleEventType<O, ReloadableRegistrarEvent<? super R>, ?>) lifecycleEventType, new RegistrarEventImpl.ReloadableImpl<>(registrar, ownerClass, cause), ownerClass::isInstance);
+ }
+
+ private LifecycleEventRunner() {
+ }
+}
diff --git a/src/main/java/io/papermc/paper/plugin/lifecycle/event/PaperLifecycleEvent.java b/src/main/java/io/papermc/paper/plugin/lifecycle/event/PaperLifecycleEvent.java
new file mode 100644
index 0000000000000000000000000000000000000000..e941405269a773e8a77e26ffd1afd84f53fadff5
--- /dev/null
+++ b/src/main/java/io/papermc/paper/plugin/lifecycle/event/PaperLifecycleEvent.java
@@ -0,0 +1,10 @@
+package io.papermc.paper.plugin.lifecycle.event;
+
+public interface PaperLifecycleEvent extends LifecycleEvent {
+
+ // called after all handlers have been run. Can be
+ // used to invalid various contexts to plugins can't
+ // try to re-use them by storing them from the event
+ default void invalidate() {
+ }
+}
diff --git a/src/main/java/io/papermc/paper/plugin/lifecycle/event/PaperLifecycleEventManager.java b/src/main/java/io/papermc/paper/plugin/lifecycle/event/PaperLifecycleEventManager.java
new file mode 100644
index 0000000000000000000000000000000000000000..d05334016bd01201c755dea04c0cea56b6dfcb50
--- /dev/null
+++ b/src/main/java/io/papermc/paper/plugin/lifecycle/event/PaperLifecycleEventManager.java
@@ -0,0 +1,26 @@
+package io.papermc.paper.plugin.lifecycle.event;
+
+import com.google.common.base.Preconditions;
+import io.papermc.paper.plugin.lifecycle.event.handler.configuration.AbstractLifecycleEventHandlerConfiguration;
+import io.papermc.paper.plugin.lifecycle.event.handler.configuration.LifecycleEventHandlerConfiguration;
+import java.util.function.BooleanSupplier;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.framework.qual.DefaultQualifier;
+
+@DefaultQualifier(NonNull.class)
+public final class PaperLifecycleEventManager<O extends LifecycleEventOwner> implements LifecycleEventManager<O> {
+
+ private final O owner;
+ public final BooleanSupplier registrationCheck;
+
+ public PaperLifecycleEventManager(final O owner, final BooleanSupplier registrationCheck) {
+ this.owner = owner;
+ this.registrationCheck = registrationCheck;
+ }
+
+ @Override
+ public void registerEventHandler(final LifecycleEventHandlerConfiguration<? super O> handlerConfiguration) {
+ Preconditions.checkState(this.registrationCheck.getAsBoolean(), "Cannot register lifecycle event handlers");
+ ((AbstractLifecycleEventHandlerConfiguration<? super O, ?>) handlerConfiguration).registerFrom(this.owner);
+ }
+}
diff --git a/src/main/java/io/papermc/paper/plugin/lifecycle/event/handler/configuration/AbstractLifecycleEventHandlerConfiguration.java b/src/main/java/io/papermc/paper/plugin/lifecycle/event/handler/configuration/AbstractLifecycleEventHandlerConfiguration.java
new file mode 100644
index 0000000000000000000000000000000000000000..fa216e6fd804859293385ed43c53dfca057f317f
--- /dev/null
+++ b/src/main/java/io/papermc/paper/plugin/lifecycle/event/handler/configuration/AbstractLifecycleEventHandlerConfiguration.java
@@ -0,0 +1,28 @@
+package io.papermc.paper.plugin.lifecycle.event.handler.configuration;
+
+import io.papermc.paper.plugin.lifecycle.event.LifecycleEvent;
+import io.papermc.paper.plugin.lifecycle.event.LifecycleEventOwner;
+import io.papermc.paper.plugin.lifecycle.event.handler.LifecycleEventHandler;
+import io.papermc.paper.plugin.lifecycle.event.types.AbstractLifecycleEventType;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.framework.qual.DefaultQualifier;
+
+@DefaultQualifier(NonNull.class)
+public abstract class AbstractLifecycleEventHandlerConfiguration<O extends LifecycleEventOwner, E extends LifecycleEvent> implements LifecycleEventHandlerConfiguration<O> {
+
+ private final LifecycleEventHandler<? super E> handler;
+ private final AbstractLifecycleEventType<O, E, ?> type;
+
+ protected AbstractLifecycleEventHandlerConfiguration(final LifecycleEventHandler<? super E> handler, final AbstractLifecycleEventType<O, E, ?> type) {
+ this.handler = handler;
+ this.type = type;
+ }
+
+ public final void registerFrom(final O owner) {
+ this.type.tryRegister(owner, this);
+ }
+
+ public LifecycleEventHandler<? super E> handler() {
+ return this.handler;
+ }
+}
diff --git a/src/main/java/io/papermc/paper/plugin/lifecycle/event/handler/configuration/MonitorLifecycleEventHandlerConfigurationImpl.java b/src/main/java/io/papermc/paper/plugin/lifecycle/event/handler/configuration/MonitorLifecycleEventHandlerConfigurationImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..ab444d60d72bd692843052df5d7b24fbb5621cf7
--- /dev/null
+++ b/src/main/java/io/papermc/paper/plugin/lifecycle/event/handler/configuration/MonitorLifecycleEventHandlerConfigurationImpl.java
@@ -0,0 +1,28 @@
+package io.papermc.paper.plugin.lifecycle.event.handler.configuration;
+
+import io.papermc.paper.plugin.lifecycle.event.LifecycleEvent;
+import io.papermc.paper.plugin.lifecycle.event.LifecycleEventOwner;
+import io.papermc.paper.plugin.lifecycle.event.handler.LifecycleEventHandler;
+import io.papermc.paper.plugin.lifecycle.event.types.AbstractLifecycleEventType;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.framework.qual.DefaultQualifier;
+
+@DefaultQualifier(NonNull.class)
+public class MonitorLifecycleEventHandlerConfigurationImpl<O extends LifecycleEventOwner, E extends LifecycleEvent> extends AbstractLifecycleEventHandlerConfiguration<O, E> implements MonitorLifecycleEventHandlerConfiguration<O> {
+
+ private boolean monitor = false;
+
+ public MonitorLifecycleEventHandlerConfigurationImpl(final LifecycleEventHandler<? super E> handler, final AbstractLifecycleEventType<O, E, ?> eventType) {
+ super(handler, eventType);
+ }
+
+ public boolean isMonitor() {
+ return this.monitor;
+ }
+
+ @Override
+ public MonitorLifecycleEventHandlerConfiguration<O> monitor() {
+ this.monitor = true;
+ return this;
+ }
+}
diff --git a/src/main/java/io/papermc/paper/plugin/lifecycle/event/handler/configuration/PrioritizedLifecycleEventHandlerConfigurationImpl.java b/src/main/java/io/papermc/paper/plugin/lifecycle/event/handler/configuration/PrioritizedLifecycleEventHandlerConfigurationImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..ccdad31717bf12b844cbeaf11a49247485ec77f1
--- /dev/null
+++ b/src/main/java/io/papermc/paper/plugin/lifecycle/event/handler/configuration/PrioritizedLifecycleEventHandlerConfigurationImpl.java
@@ -0,0 +1,40 @@
+package io.papermc.paper.plugin.lifecycle.event.handler.configuration;
+
+import io.papermc.paper.plugin.lifecycle.event.LifecycleEvent;
+import io.papermc.paper.plugin.lifecycle.event.LifecycleEventOwner;
+import io.papermc.paper.plugin.lifecycle.event.handler.LifecycleEventHandler;
+import io.papermc.paper.plugin.lifecycle.event.types.AbstractLifecycleEventType;
+import java.util.OptionalInt;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.framework.qual.DefaultQualifier;
+
+@DefaultQualifier(NonNull.class)
+public class PrioritizedLifecycleEventHandlerConfigurationImpl<O extends LifecycleEventOwner, E extends LifecycleEvent>
+ extends AbstractLifecycleEventHandlerConfiguration<O, E>
+ implements PrioritizedLifecycleEventHandlerConfiguration<O> {
+
+ private static final OptionalInt DEFAULT_PRIORITY = OptionalInt.of(0);
+ private static final OptionalInt MONITOR_PRIORITY = OptionalInt.empty();
+
+ private OptionalInt priority = DEFAULT_PRIORITY;
+
+ public PrioritizedLifecycleEventHandlerConfigurationImpl(final LifecycleEventHandler<? super E> handler, final AbstractLifecycleEventType<O, E, ?> eventType) {
+ super(handler, eventType);
+ }
+
+ public OptionalInt priority() {
+ return this.priority;
+ }
+
+ @Override
+ public PrioritizedLifecycleEventHandlerConfiguration<O> priority(final int priority) {
+ this.priority = OptionalInt.of(priority);
+ return this;
+ }
+
+ @Override
+ public PrioritizedLifecycleEventHandlerConfiguration<O> monitor() {
+ this.priority = MONITOR_PRIORITY;
+ return this;
+ }
+}
diff --git a/src/main/java/io/papermc/paper/plugin/lifecycle/event/registrar/PaperRegistrar.java b/src/main/java/io/papermc/paper/plugin/lifecycle/event/registrar/PaperRegistrar.java
new file mode 100644
index 0000000000000000000000000000000000000000..b2586c881988fbabe07eef1b43eb1b55f2d3fa52
--- /dev/null
+++ b/src/main/java/io/papermc/paper/plugin/lifecycle/event/registrar/PaperRegistrar.java
@@ -0,0 +1,15 @@
+package io.papermc.paper.plugin.lifecycle.event.registrar;
+
+import io.papermc.paper.plugin.lifecycle.event.LifecycleEventOwner;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.framework.qual.DefaultQualifier;
+
+@DefaultQualifier(NonNull.class)
+public interface PaperRegistrar<O extends LifecycleEventOwner> extends Registrar {
+
+ void setCurrentContext(@Nullable O owner);
+
+ default void invalidate() {
+ }
+}
diff --git a/src/main/java/io/papermc/paper/plugin/lifecycle/event/registrar/RegistrarEventImpl.java b/src/main/java/io/papermc/paper/plugin/lifecycle/event/registrar/RegistrarEventImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..6d530c52aaf0dc2cdfe3bd56af557274a7f44256
--- /dev/null
+++ b/src/main/java/io/papermc/paper/plugin/lifecycle/event/registrar/RegistrarEventImpl.java
@@ -0,0 +1,70 @@
+package io.papermc.paper.plugin.lifecycle.event.registrar;
+
+import io.papermc.paper.plugin.lifecycle.event.LifecycleEventOwner;
+import io.papermc.paper.plugin.lifecycle.event.PaperLifecycleEvent;
+import io.papermc.paper.plugin.lifecycle.event.types.OwnerAwareLifecycleEvent;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.framework.qual.DefaultQualifier;
+
+@DefaultQualifier(NonNull.class)
+public class RegistrarEventImpl<R extends PaperRegistrar<? super O>, O extends LifecycleEventOwner> implements PaperLifecycleEvent, OwnerAwareLifecycleEvent<O>, RegistrarEvent<R> {
+
+ private final R registrar;
+ private final Class<? extends O> ownerClass;
+
+ public RegistrarEventImpl(final R registrar, final Class<? extends O> ownerClass) {
+ this.registrar = registrar;
+ this.ownerClass = ownerClass;
+ }
+
+ @Override
+ public R registrar() {
+ return this.registrar;
+ }
+
+ @Override
+ public final void setOwner(final @Nullable O owner) {
+ this.registrar.setCurrentContext(owner);
+ }
+
+ @Override
+ public final @Nullable O castOwner(final LifecycleEventOwner owner) {
+ return this.ownerClass.isInstance(owner) ? this.ownerClass.cast(owner) : null;
+ }
+
+ @Override
+ public void invalidate() {
+ this.registrar.invalidate();
+ }
+
+ @Override
+ public String toString() {
+ return "RegistrarEventImpl{" +
+ "registrar=" + this.registrar +
+ ", ownerClass=" + this.ownerClass +
+ '}';
+ }
+
+ public static class ReloadableImpl<R extends PaperRegistrar<? super O>, O extends LifecycleEventOwner> extends RegistrarEventImpl<R, O> implements ReloadableRegistrarEvent<R> {
+
+ private final ReloadableRegistrarEvent.Cause cause;
+
+ public ReloadableImpl(final R registrar, final Class<? extends O> ownerClass, final Cause cause) {
+ super(registrar, ownerClass);
+ this.cause = cause;
+ }
+
+ @Override
+ public Cause cause() {
+ return this.cause;
+ }
+
+ @Override
+ public String toString() {
+ return "ReloadableImpl{" +
+ "cause=" + this.cause +
+ "} " + super.toString();
+ }
+ }
+}
diff --git a/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/AbstractLifecycleEventType.java b/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/AbstractLifecycleEventType.java
new file mode 100644
index 0000000000000000000000000000000000000000..9359a36d26970742da3a7abb0050158cd6c64e8e
--- /dev/null
+++ b/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/AbstractLifecycleEventType.java
@@ -0,0 +1,54 @@
+package io.papermc.paper.plugin.lifecycle.event.types;
+
+import io.papermc.paper.plugin.lifecycle.event.LifecycleEvent;
+import io.papermc.paper.plugin.lifecycle.event.LifecycleEventOwner;
+import io.papermc.paper.plugin.lifecycle.event.LifecycleEventRunner;
+import io.papermc.paper.plugin.lifecycle.event.handler.LifecycleEventHandler;
+import io.papermc.paper.plugin.lifecycle.event.handler.configuration.AbstractLifecycleEventHandlerConfiguration;
+import io.papermc.paper.plugin.lifecycle.event.handler.configuration.LifecycleEventHandlerConfiguration;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.framework.qual.DefaultQualifier;
+
+@DefaultQualifier(NonNull.class)
+public abstract class AbstractLifecycleEventType<O extends LifecycleEventOwner, E extends LifecycleEvent, C extends LifecycleEventHandlerConfiguration<O>> implements LifecycleEventType<O, E, C> {
+
+ private final String name;
+ private final Class<? extends O> ownerType;
+
+ protected AbstractLifecycleEventType(final String name, final Class<? extends O> ownerType) {
+ this.name = name;
+ this.ownerType = ownerType;
+ }
+
+ @Override
+ public String name() {
+ return this.name;
+ }
+
+ private void verifyOwner(final O owner) {
+ if (!this.ownerType.isInstance(owner)) {
+ throw new IllegalArgumentException("You cannot register the lifecycle event '" + this.name + "' on " + owner);
+ }
+ }
+
+ public abstract void forEachHandler(E event, Consumer<RegisteredHandler<O, E>> consumer, Predicate<RegisteredHandler<O, E>> predicate);
+
+ public abstract void removeMatching(Predicate<RegisteredHandler<O, E>> predicate);
+
+ protected abstract void register(O owner, AbstractLifecycleEventHandlerConfiguration<O, E> config);
+
+ public final void tryRegister(final O owner, final AbstractLifecycleEventHandlerConfiguration<O, E> config) {
+ this.verifyOwner(owner);
+ LifecycleEventRunner.INSTANCE.checkRegisteredHandler(owner, this);
+ this.register(owner, config);
+ }
+
+ public record RegisteredHandler<O extends LifecycleEventOwner, E extends LifecycleEvent>(O owner, AbstractLifecycleEventHandlerConfiguration<O, E> config) {
+
+ public LifecycleEventHandler<? super E> lifecycleEventHandler() {
+ return this.config().handler();
+ }
+ }
+}
diff --git a/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/LifecycleEventTypeProviderImpl.java b/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/LifecycleEventTypeProviderImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..af0cb3298d9c737417c6e54b360f8dc50a5caf04
--- /dev/null
+++ b/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/LifecycleEventTypeProviderImpl.java
@@ -0,0 +1,25 @@
+package io.papermc.paper.plugin.lifecycle.event.types;
+
+import io.papermc.paper.plugin.lifecycle.event.LifecycleEvent;
+import io.papermc.paper.plugin.lifecycle.event.LifecycleEventOwner;
+import io.papermc.paper.plugin.lifecycle.event.LifecycleEventRunner;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.framework.qual.DefaultQualifier;
+
+@DefaultQualifier(NonNull.class)
+public final class LifecycleEventTypeProviderImpl implements LifecycleEventTypeProvider {
+
+ public static LifecycleEventTypeProviderImpl instance() {
+ return (LifecycleEventTypeProviderImpl) LifecycleEventTypeProvider.PROVIDER;
+ }
+
+ @Override
+ public <O extends LifecycleEventOwner, E extends LifecycleEvent> LifecycleEventType.Monitorable<O, E> monitor(final String name, final Class<? extends O> ownerType) {
+ return LifecycleEventRunner.INSTANCE.addEventType(new MonitorableLifecycleEventType<>(name, ownerType));
+ }
+
+ @Override
+ public <O extends LifecycleEventOwner, E extends LifecycleEvent> LifecycleEventType.Prioritizable<O, E> prioritized(final String name, final Class<? extends O> ownerType) {
+ return LifecycleEventRunner.INSTANCE.addEventType(new PrioritizableLifecycleEventType.Simple<>(name, ownerType));
+ }
+}
diff --git a/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/MonitorableLifecycleEventType.java b/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/MonitorableLifecycleEventType.java
new file mode 100644
index 0000000000000000000000000000000000000000..c71912f0050ce0cc6e416948a354c8a66da606a8
--- /dev/null
+++ b/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/MonitorableLifecycleEventType.java
@@ -0,0 +1,58 @@
+package io.papermc.paper.plugin.lifecycle.event.types;
+
+import io.papermc.paper.plugin.lifecycle.event.LifecycleEvent;
+import io.papermc.paper.plugin.lifecycle.event.LifecycleEventOwner;
+import io.papermc.paper.plugin.lifecycle.event.handler.LifecycleEventHandler;
+import io.papermc.paper.plugin.lifecycle.event.handler.configuration.AbstractLifecycleEventHandlerConfiguration;
+import io.papermc.paper.plugin.lifecycle.event.handler.configuration.MonitorLifecycleEventHandlerConfiguration;
+import io.papermc.paper.plugin.lifecycle.event.handler.configuration.MonitorLifecycleEventHandlerConfigurationImpl;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.framework.qual.DefaultQualifier;
+
+@DefaultQualifier(NonNull.class)
+public class MonitorableLifecycleEventType<O extends LifecycleEventOwner, E extends LifecycleEvent> extends AbstractLifecycleEventType<O, E, MonitorLifecycleEventHandlerConfiguration<O>> implements LifecycleEventType.Monitorable<O, E> {
+
+ final List<RegisteredHandler<O, E>> handlers = new ArrayList<>();
+ int nonMonitorIdx = 0;
+
+ public MonitorableLifecycleEventType(final String name, final Class<? extends O> ownerType) {
+ super(name, ownerType);
+ }
+
+ @Override
+ public MonitorLifecycleEventHandlerConfigurationImpl<O, E> newHandler(final LifecycleEventHandler<? super E> handler) {
+ return new MonitorLifecycleEventHandlerConfigurationImpl<>(handler, this);
+ }
+
+ @Override
+ protected void register(final O owner, final AbstractLifecycleEventHandlerConfiguration<O, E> config) {
+ if (!(config instanceof final MonitorLifecycleEventHandlerConfigurationImpl<?,?> monitor)) {
+ throw new IllegalArgumentException("Configuration must be a MonitorLifecycleEventHandlerConfiguration");
+ }
+ final RegisteredHandler<O, E> registeredHandler = new RegisteredHandler<>(owner, config);
+ if (!monitor.isMonitor()) {
+ this.handlers.add(this.nonMonitorIdx, registeredHandler);
+ this.nonMonitorIdx++;
+ } else {
+ this.handlers.add(registeredHandler);
+ }
+ }
+
+ @Override
+ public void forEachHandler(final E event, final Consumer<RegisteredHandler<O, E>> consumer, final Predicate<RegisteredHandler<O, E>> predicate) {
+ for (final RegisteredHandler<O, E> handler : this.handlers) {
+ if (predicate.test(handler)) {
+ consumer.accept(handler);
+ }
+ }
+ }
+
+ @Override
+ public void removeMatching(final Predicate<RegisteredHandler<O, E>> predicate) {
+ this.handlers.removeIf(predicate);
+ }
+}
diff --git a/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/OwnerAwareLifecycleEvent.java b/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/OwnerAwareLifecycleEvent.java
new file mode 100644
index 0000000000000000000000000000000000000000..3e7e7474f301c0725fa2bcd6e19e476fc35f2d5a
--- /dev/null
+++ b/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/OwnerAwareLifecycleEvent.java
@@ -0,0 +1,15 @@
+package io.papermc.paper.plugin.lifecycle.event.types;
+
+import io.papermc.paper.plugin.lifecycle.event.LifecycleEvent;
+import io.papermc.paper.plugin.lifecycle.event.LifecycleEventOwner;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.framework.qual.DefaultQualifier;
+
+@DefaultQualifier(NonNull.class)
+public interface OwnerAwareLifecycleEvent<O extends LifecycleEventOwner> extends LifecycleEvent {
+
+ void setOwner(@Nullable O owner);
+
+ @Nullable O castOwner(LifecycleEventOwner owner);
+}
diff --git a/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/PrioritizableLifecycleEventType.java b/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/PrioritizableLifecycleEventType.java
new file mode 100644
index 0000000000000000000000000000000000000000..76f92a6fc84c0315f3973dc4e92649b66babc3d5
--- /dev/null
+++ b/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/PrioritizableLifecycleEventType.java
@@ -0,0 +1,74 @@
+package io.papermc.paper.plugin.lifecycle.event.types;
+
+import com.google.common.base.Preconditions;
+import io.papermc.paper.plugin.lifecycle.event.LifecycleEvent;
+import io.papermc.paper.plugin.lifecycle.event.LifecycleEventOwner;
+import io.papermc.paper.plugin.lifecycle.event.handler.LifecycleEventHandler;
+import io.papermc.paper.plugin.lifecycle.event.handler.configuration.AbstractLifecycleEventHandlerConfiguration;
+import io.papermc.paper.plugin.lifecycle.event.handler.configuration.PrioritizedLifecycleEventHandlerConfiguration;
+import io.papermc.paper.plugin.lifecycle.event.handler.configuration.PrioritizedLifecycleEventHandlerConfigurationImpl;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.framework.qual.DefaultQualifier;
+
+@DefaultQualifier(NonNull.class)
+public abstract class PrioritizableLifecycleEventType<
+ O extends LifecycleEventOwner,
+ E extends LifecycleEvent,
+ C extends PrioritizedLifecycleEventHandlerConfiguration<O>
+> extends AbstractLifecycleEventType<O, E, C> {
+
+ private static final Comparator<RegisteredHandler<?, ?>> COMPARATOR = Comparator.comparing(handler -> ((PrioritizedLifecycleEventHandlerConfigurationImpl<?, ?>) handler.config()).priority(), (o1, o2) -> {
+ if (o1.equals(o2)) {
+ return 0;
+ } else if (o1.isEmpty()) {
+ return 1;
+ } else if (o2.isEmpty()) {
+ return -1;
+ } else {
+ return Integer.compare(o1.getAsInt(), o2.getAsInt());
+ }
+ });
+
+ private final List<RegisteredHandler<O, E>> handlers = new ArrayList<>();
+
+ public PrioritizableLifecycleEventType(final String name, final Class<? extends O> ownerType) {
+ super(name, ownerType);
+ }
+
+ @Override
+ protected void register(final O owner, final AbstractLifecycleEventHandlerConfiguration<O, E> config) {
+ Preconditions.checkArgument(config instanceof PrioritizedLifecycleEventHandlerConfigurationImpl<?, ?>, "Configuration must be a PrioritizedLifecycleEventHandlerConfiguration");
+ this.handlers.add(new RegisteredHandler<>(owner, config));
+ this.handlers.sort(COMPARATOR);
+ }
+
+ @Override
+ public void forEachHandler(final E event, final Consumer<RegisteredHandler<O, E>> consumer, final Predicate<RegisteredHandler<O, E>> predicate) {
+ for (final RegisteredHandler<O, E> handler : this.handlers) {
+ if (predicate.test(handler)) {
+ consumer.accept(handler);
+ }
+ }
+ }
+
+ @Override
+ public void removeMatching(final Predicate<RegisteredHandler<O, E>> predicate) {
+ this.handlers.removeIf(predicate);
+ }
+
+ public static class Simple<O extends LifecycleEventOwner, E extends LifecycleEvent> extends PrioritizableLifecycleEventType<O, E, PrioritizedLifecycleEventHandlerConfiguration<O>> implements LifecycleEventType.Prioritizable<O, E> {
+ public Simple(final String name, final Class<? extends O> ownerType) {
+ super(name, ownerType);
+ }
+
+ @Override
+ public PrioritizedLifecycleEventHandlerConfiguration<O> newHandler(final LifecycleEventHandler<? super E> handler) {
+ return new PrioritizedLifecycleEventHandlerConfigurationImpl<>(handler, this);
+ }
+ }
+}
diff --git a/src/main/java/io/papermc/paper/plugin/manager/PaperPluginInstanceManager.java b/src/main/java/io/papermc/paper/plugin/manager/PaperPluginInstanceManager.java
2024-04-26 08:17:15 +02:00
index 834b85f24df023642f8abf7213fe578ac8c17a3e..3e82ea07ca4194844c5528446e2c4a46ff4acee5 100644
--- a/src/main/java/io/papermc/paper/plugin/manager/PaperPluginInstanceManager.java
+++ b/src/main/java/io/papermc/paper/plugin/manager/PaperPluginInstanceManager.java
2024-04-26 08:17:15 +02:00
@@ -293,6 +293,15 @@ class PaperPluginInstanceManager {
+ pluginName + " (Is it up to date?)", ex, plugin); // Paper
}
+ // Paper start - lifecycle event system
+ try {
+ io.papermc.paper.plugin.lifecycle.event.LifecycleEventRunner.INSTANCE.unregisterAllEventHandlersFor(plugin);
+ } catch (Throwable ex) {
+ this.handlePluginException("Error occurred (in the plugin loader) while unregistering lifecycle event handlers for "
+ + pluginName + " (Is it up to date?)", ex, plugin);
+ }
+ // Paper end
+
try {
this.server.getMessenger().unregisterIncomingPluginChannel(plugin);
this.server.getMessenger().unregisterOutgoingPluginChannel(plugin);
diff --git a/src/main/java/io/papermc/paper/plugin/storage/BootstrapProviderStorage.java b/src/main/java/io/papermc/paper/plugin/storage/BootstrapProviderStorage.java
index 2e96308696e131f3f013469a395e5ddda2c5d529..65a66e484c1c39c5f41d97db52f31c67b4479d20 100644
--- a/src/main/java/io/papermc/paper/plugin/storage/BootstrapProviderStorage.java
+++ b/src/main/java/io/papermc/paper/plugin/storage/BootstrapProviderStorage.java
@@ -32,8 +32,9 @@ public class BootstrapProviderStorage extends SimpleProviderStorage<PluginBootst
@Override
public boolean load(PluginProvider<PluginBootstrap> provider, PluginBootstrap provided) {
try {
- BootstrapContext context = PluginBootstrapContextImpl.create(provider, PluginInitializerManager.instance().pluginDirectoryPath());
+ PluginBootstrapContextImpl context = PluginBootstrapContextImpl.create(provider, PluginInitializerManager.instance().pluginDirectoryPath()); // Paper - lifecycle events
provided.bootstrap(context);
+ context.lockLifecycleEventRegistration(); // Paper - lifecycle events
return true;
} catch (Throwable e) {
LOGGER.error("Failed to run bootstrapper for %s. This plugin will not be loaded.".formatted(provider.getSource()), e);
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
2024-06-14 14:11:52 +02:00
index 56f9b69b5a30c4ee5bcbcb8ae33e26f75b477475..1699f091839eb30fffc064a83ad369b8aee3ed4f 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
2024-06-14 10:56:28 +02:00
@@ -1038,6 +1038,11 @@ public final class CraftServer implements Server {
@Override
public void reload() {
+ // Paper start - lifecycle events
+ if (io.papermc.paper.plugin.lifecycle.event.LifecycleEventRunner.INSTANCE.blocksPluginReloading()) {
+ throw new IllegalStateException("A lifecycle event handler has been registered which makes reloading plugins not possible");
+ }
+ // Paper end - lifecycle events
org.spigotmc.WatchdogThread.hasStarted = false; // Paper - Disable watchdog early timeout on reload
this.reloadCount++;
this.configuration = YamlConfiguration.loadConfiguration(this.getConfigFile());
diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/MinecraftInternalPlugin.java b/src/main/java/org/bukkit/craftbukkit/scheduler/MinecraftInternalPlugin.java
index d96399e9bf1a58db5a4a22e58abb99e7660e0694..66bdac50130f523f9dc4379b103b7a469f9ca36b 100644
--- a/src/main/java/org/bukkit/craftbukkit/scheduler/MinecraftInternalPlugin.java
+++ b/src/main/java/org/bukkit/craftbukkit/scheduler/MinecraftInternalPlugin.java
@@ -143,4 +143,11 @@ public class MinecraftInternalPlugin extends PluginBase {
public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
throw new UnsupportedOperationException("Not supported.");
}
+
+ // Paper start - lifecycle events
+ @Override
+ public @NotNull io.papermc.paper.plugin.lifecycle.event.LifecycleEventManager<org.bukkit.plugin.Plugin> getLifecycleManager() {
+ throw new UnsupportedOperationException("Not supported.");
+ }
+ // Paper end - lifecycle events
}
diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
2024-06-14 10:56:28 +02:00
index 3e74e449e5674be3a84168d24f58b108ac334513..dabda356cdd4928c306feaace5bf03924b310613 100644
--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
+++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
Update upstream (Bukkit/CraftBukkit/Spigot) (#10875) Upstream has released updates that appear to apply and compile correctly. This update has not been tested by PaperMC and as with ANY update, please do your own testing Bukkit Changes: 376e37db SPIGOT-7677: Update which entities are marked as spawnable 06c4add3 SPIGOT-7737: Add separate TreeType.MEGA_PINE 19b7caaa SPIGOT-7731: Spawn eggs cannot have damage e585297e PR-1022: Add force option to Player#spawnParticle d26e0094 PR-1018: Add methods to get players seeing specific chunks 8df1ed18 PR-978: Add Material#isCompostable and Material#getCompostChance 4b9b59c7 SPIGOT-7676: Enforce locale parameter in toLowerCase and toUpperCase method calls and always use root locale 8d1e700a PR-1020: Cast instead of using #typed when getting BlockType and ItemType to better work with testing / mocks fa28607a PR-1016: Fix incorrect assumption of Fireball having constant speed 4c6c8586 PR-1015: Add a tool component to ItemMeta 6f6b2123 PR-1014: Add PotionEffectTypeCategory to distinguish between beneficial and harmful effects f511cfe1 PR-1013, SPIGOT-4288, SPIGOT-6202: Add material rerouting in preparation for the switch to ItemType and BlockType def44cbf SPIGOT-7669: Fix typo in ProjectileHitEvent#getHitBlockFace documentation 53fa4f72 PR-1011: Throw an exception if a RecipeChoice is ever supplied air CraftBukkit Changes: ee95e171a SPIGOT-7737: Add separate TreeType.MEGA_PINE 0dae4c62c Fix spawn egg equality check and copy constructor ab59e847c Fix spawn eggs with no entity creating invalid stacks and disconnect creative clients 3b6093b28 SPIGOT-7736: Creative spawn egg use loses components c6b4d5a87 SPIGOT-7731: Spawn eggs cannot have damage 340ccd57f SPIGOT-7735: Fix serialization of player heads with note block sound fd2f41834 SPIGOT-7734: Can't register a custom advancement using unsafe() 02456e2a5 PR-1413: Add force option to Player#spawnParticle 6a61f38b2 SPIGOT-7680: Per-world weather command 58c41cebb PR-1409: Add methods to get players seeing specific chunks 16c976797 PR-1412: Fix shipwreck loot tables not being set for BlockTransformers 7189ba636 PR-1360: Add Material#isCompostable and Material#getCompostChance 900384556 SPIGOT-7676: Enforce locale parameter in toLowerCase and toUpperCase method calls and always use root locale bdb40c5f1 Increase outdated build delay d6607c7dd SPIGOT-7675: Fix FoodComponent config deserialization b148ed332 PR-1406: Fix incorrect assumption of Fireball having constant speed 3ec31ca75 PR-1405: Add a tool component to ItemMeta 5d7d675b9 PR-1404: Add PotionEffectTypeCategory to distinguish between beneficial and harmful effects 960827981 PR-1403, SPIGOT-4288, SPIGOT-6202: Add material rerouting in preparation for the switch to ItemType and BlockType 94e44ec93 PR-1401: Add a config option to accept old keys in registry get calls a43701920 PR-1402: Fix ChunkSnapshot#isSectionEmpty() is always false 87d0a3368 SPIGOT-7668: Move NONE Registry updater to FieldRename to avoid some class loader issues 2ea1e7ac2 PR-1399: Fix regression preventing positive .setDamage value from causing knockback for 0 damage events ba2d49d21 Increase outdated build delay Spigot Changes: fcd94e21 Rebuild patches 342f4939 SPIGOT-7661: Add experimental unload-frozen-chunks option
2024-06-13 16:45:27 +02:00
@@ -663,6 +663,13 @@ public final class CraftMagicNumbers implements UnsafeValues {
}
// Paper end - spawn egg color visibility
+ // Paper start - lifecycle event API
+ @Override
+ public io.papermc.paper.plugin.lifecycle.event.LifecycleEventManager<org.bukkit.plugin.Plugin> createPluginLifecycleEventManager(final org.bukkit.plugin.java.JavaPlugin plugin, final java.util.function.BooleanSupplier registrationCheck) {
+ return new io.papermc.paper.plugin.lifecycle.event.PaperLifecycleEventManager<>(plugin, registrationCheck);
+ }
+ // Paper end - lifecycle event API
+
/**
* This helper class represents the different NBT Tags.
* <p>
diff --git a/src/main/resources/META-INF/services/io.papermc.paper.plugin.lifecycle.event.types.LifecycleEventTypeProvider b/src/main/resources/META-INF/services/io.papermc.paper.plugin.lifecycle.event.types.LifecycleEventTypeProvider
new file mode 100644
index 0000000000000000000000000000000000000000..808b1192b60348ad05f0bfbdeda6f94df4876743
--- /dev/null
+++ b/src/main/resources/META-INF/services/io.papermc.paper.plugin.lifecycle.event.types.LifecycleEventTypeProvider
@@ -0,0 +1 @@
+io.papermc.paper.plugin.lifecycle.event.types.LifecycleEventTypeProviderImpl
diff --git a/src/test/java/io/papermc/paper/plugin/PaperTestPlugin.java b/src/test/java/io/papermc/paper/plugin/PaperTestPlugin.java
index 1d14f530ef888102e47eeeaf0d1a6076e51871c4..90cf0c702ca2ff9de64d9718ecba5f2d128953a6 100644
--- a/src/test/java/io/papermc/paper/plugin/PaperTestPlugin.java
+++ b/src/test/java/io/papermc/paper/plugin/PaperTestPlugin.java
@@ -143,4 +143,11 @@ public class PaperTestPlugin extends PluginBase {
public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
throw new UnsupportedOperationException("Not supported.");
}
+
+ // Paper start - lifecycle events
+ @Override
+ public io.papermc.paper.plugin.lifecycle.event.LifecycleEventManager<org.bukkit.plugin.Plugin> getLifecycleManager() {
+ throw new UnsupportedOperationException("Not supported.");
+ }
+ // Paper end - lifecycle events
}