diff --git a/paper-server/src/main/java/io/papermc/paper/plugin/bootstrap/PluginBootstrapContextImpl.java b/paper-server/src/main/java/io/papermc/paper/plugin/bootstrap/PluginBootstrapContextImpl.java index 30b50e6294..0bb7694188 100644 --- a/paper-server/src/main/java/io/papermc/paper/plugin/bootstrap/PluginBootstrapContextImpl.java +++ b/paper-server/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 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 getLifecycleManager() { + return this.lifecycleEventManager; + } + + public void lockLifecycleEventRegistration() { + this.allowsLifecycleRegistration = false; + } + // Paper end } diff --git a/paper-server/src/main/java/io/papermc/paper/plugin/lifecycle/event/LifecycleEventRunner.java b/paper-server/src/main/java/io/papermc/paper/plugin/lifecycle/event/LifecycleEventRunner.java new file mode 100644 index 0000000000..ce808520d6 --- /dev/null +++ b/paper-server/src/main/java/io/papermc/paper/plugin/lifecycle/event/LifecycleEventRunner.java @@ -0,0 +1,100 @@ +package io.papermc.paper.plugin.lifecycle.event; + +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.function.Predicate; +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; + +@DefaultQualifier(NonNull.class) +public class LifecycleEventRunner { + + public static final LifecycleEventRunner INSTANCE = new LifecycleEventRunner(); + + private final List> lifecycleEventTypes = new ArrayList<>(); + private boolean blockPluginReloading = false; + + public void checkRegisteredHandler(final O owner, final AbstractLifecycleEventType 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 (eventType.blocksReloading(owner)) { + this.blockPluginReloading = true; + } + } + + public boolean blocksPluginReloading() { + return this.blockPluginReloading; + } + + public > void addEventType(final ET eventType) { + this.lifecycleEventTypes.add(eventType); + } + + public void callEvent(final LifecycleEventType eventType, final E event) { + this.callEvent(eventType, event, $ -> true); + } + + public void callEvent(final LifecycleEventType eventType, final E event, final Predicate ownerPredicate) { + final AbstractLifecycleEventType lifecycleEventType = (AbstractLifecycleEventType) 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 void ownerAwareGenericHelper(final OwnerAwareLifecycleEvent 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 void removeEventHandlersOwnedBy(final LifecycleEventType eventType, final Plugin possibleOwner) { + final AbstractLifecycleEventType lifecycleEventType = (AbstractLifecycleEventType) eventType; + lifecycleEventType.removeMatching(registeredHandler -> registeredHandler.owner().getPluginMeta().getName().equals(possibleOwner.getPluginMeta().getName())); + } + + @SuppressWarnings("unchecked") + public > void callStaticRegistrarEvent(final LifecycleEventType, ?> lifecycleEventType, final R registrar, final Class ownerClass) { + this.callEvent((LifecycleEventType, ?>) lifecycleEventType, new RegistrarEventImpl<>(registrar, ownerClass), ownerClass::isInstance); + } + + @SuppressWarnings("unchecked") + public > void callReloadableRegistrarEvent(final LifecycleEventType, ?> lifecycleEventType, final R registrar, final Class ownerClass, final ReloadableRegistrarEvent.Cause cause) { + this.callEvent((LifecycleEventType, ?>) lifecycleEventType, new RegistrarEventImpl.ReloadableImpl<>(registrar, ownerClass, cause), ownerClass::isInstance); + } + + private LifecycleEventRunner() { + } +} diff --git a/paper-server/src/main/java/io/papermc/paper/plugin/lifecycle/event/PaperLifecycleEvent.java b/paper-server/src/main/java/io/papermc/paper/plugin/lifecycle/event/PaperLifecycleEvent.java new file mode 100644 index 0000000000..e941405269 --- /dev/null +++ b/paper-server/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/paper-server/src/main/java/io/papermc/paper/plugin/lifecycle/event/PaperLifecycleEventManager.java b/paper-server/src/main/java/io/papermc/paper/plugin/lifecycle/event/PaperLifecycleEventManager.java new file mode 100644 index 0000000000..d05334016b --- /dev/null +++ b/paper-server/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 implements LifecycleEventManager { + + 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 handlerConfiguration) { + Preconditions.checkState(this.registrationCheck.getAsBoolean(), "Cannot register lifecycle event handlers"); + ((AbstractLifecycleEventHandlerConfiguration) handlerConfiguration).registerFrom(this.owner); + } +} diff --git a/paper-server/src/main/java/io/papermc/paper/plugin/lifecycle/event/handler/configuration/AbstractLifecycleEventHandlerConfiguration.java b/paper-server/src/main/java/io/papermc/paper/plugin/lifecycle/event/handler/configuration/AbstractLifecycleEventHandlerConfiguration.java new file mode 100644 index 0000000000..fa216e6fd8 --- /dev/null +++ b/paper-server/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 implements LifecycleEventHandlerConfiguration { + + private final LifecycleEventHandler handler; + private final AbstractLifecycleEventType type; + + protected AbstractLifecycleEventHandlerConfiguration(final LifecycleEventHandler handler, final AbstractLifecycleEventType type) { + this.handler = handler; + this.type = type; + } + + public final void registerFrom(final O owner) { + this.type.tryRegister(owner, this); + } + + public LifecycleEventHandler handler() { + return this.handler; + } +} diff --git a/paper-server/src/main/java/io/papermc/paper/plugin/lifecycle/event/handler/configuration/MonitorLifecycleEventHandlerConfigurationImpl.java b/paper-server/src/main/java/io/papermc/paper/plugin/lifecycle/event/handler/configuration/MonitorLifecycleEventHandlerConfigurationImpl.java new file mode 100644 index 0000000000..ab444d60d7 --- /dev/null +++ b/paper-server/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 extends AbstractLifecycleEventHandlerConfiguration implements MonitorLifecycleEventHandlerConfiguration { + + private boolean monitor = false; + + public MonitorLifecycleEventHandlerConfigurationImpl(final LifecycleEventHandler handler, final AbstractLifecycleEventType eventType) { + super(handler, eventType); + } + + public boolean isMonitor() { + return this.monitor; + } + + @Override + public MonitorLifecycleEventHandlerConfiguration monitor() { + this.monitor = true; + return this; + } +} diff --git a/paper-server/src/main/java/io/papermc/paper/plugin/lifecycle/event/handler/configuration/PrioritizedLifecycleEventHandlerConfigurationImpl.java b/paper-server/src/main/java/io/papermc/paper/plugin/lifecycle/event/handler/configuration/PrioritizedLifecycleEventHandlerConfigurationImpl.java new file mode 100644 index 0000000000..ccdad31717 --- /dev/null +++ b/paper-server/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 + extends AbstractLifecycleEventHandlerConfiguration + implements PrioritizedLifecycleEventHandlerConfiguration { + + 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 handler, final AbstractLifecycleEventType eventType) { + super(handler, eventType); + } + + public OptionalInt priority() { + return this.priority; + } + + @Override + public PrioritizedLifecycleEventHandlerConfiguration priority(final int priority) { + this.priority = OptionalInt.of(priority); + return this; + } + + @Override + public PrioritizedLifecycleEventHandlerConfiguration monitor() { + this.priority = MONITOR_PRIORITY; + return this; + } +} diff --git a/paper-server/src/main/java/io/papermc/paper/plugin/lifecycle/event/registrar/PaperRegistrar.java b/paper-server/src/main/java/io/papermc/paper/plugin/lifecycle/event/registrar/PaperRegistrar.java new file mode 100644 index 0000000000..b2586c8819 --- /dev/null +++ b/paper-server/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 extends Registrar { + + void setCurrentContext(@Nullable O owner); + + default void invalidate() { + } +} diff --git a/paper-server/src/main/java/io/papermc/paper/plugin/lifecycle/event/registrar/RegistrarEventImpl.java b/paper-server/src/main/java/io/papermc/paper/plugin/lifecycle/event/registrar/RegistrarEventImpl.java new file mode 100644 index 0000000000..6d530c52aa --- /dev/null +++ b/paper-server/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, O extends LifecycleEventOwner> implements PaperLifecycleEvent, OwnerAwareLifecycleEvent, RegistrarEvent { + + private final R registrar; + private final Class ownerClass; + + public RegistrarEventImpl(final R registrar, final Class 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, O extends LifecycleEventOwner> extends RegistrarEventImpl implements ReloadableRegistrarEvent { + + private final ReloadableRegistrarEvent.Cause cause; + + public ReloadableImpl(final R registrar, final Class 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/paper-server/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/AbstractLifecycleEventType.java b/paper-server/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/AbstractLifecycleEventType.java new file mode 100644 index 0000000000..01a4e9a36a --- /dev/null +++ b/paper-server/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/AbstractLifecycleEventType.java @@ -0,0 +1,62 @@ +package io.papermc.paper.plugin.lifecycle.event.types; + +import io.papermc.paper.plugin.bootstrap.BootstrapContext; +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> implements LifecycleEventType { + + private final String name; + private final Class ownerType; + + protected AbstractLifecycleEventType(final String name, final Class ownerType) { + this.name = name; + this.ownerType = ownerType; + LifecycleEventRunner.INSTANCE.addEventType(this); + } + + @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 boolean blocksReloading(final O eventOwner) { + return eventOwner instanceof BootstrapContext; + } + + public abstract boolean hasHandlers(); + + public abstract void forEachHandler(E event, Consumer> consumer, Predicate> predicate); + + public abstract void removeMatching(Predicate> predicate); + + protected abstract void register(O owner, AbstractLifecycleEventHandlerConfiguration config); + + public final void tryRegister(final O owner, final AbstractLifecycleEventHandlerConfiguration config) { + this.verifyOwner(owner); + LifecycleEventRunner.INSTANCE.checkRegisteredHandler(owner, this); + this.register(owner, config); + } + + public record RegisteredHandler(O owner, AbstractLifecycleEventHandlerConfiguration config) { + + public LifecycleEventHandler lifecycleEventHandler() { + return this.config().handler(); + } + } +} diff --git a/paper-server/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/LifecycleEventTypeProviderImpl.java b/paper-server/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/LifecycleEventTypeProviderImpl.java new file mode 100644 index 0000000000..b11346e04b --- /dev/null +++ b/paper-server/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 LifecycleEventType.Monitorable monitor(final String name, final Class ownerType) { + return new MonitorableLifecycleEventType<>(name, ownerType); + } + + @Override + public LifecycleEventType.Prioritizable prioritized(final String name, final Class ownerType) { + return new PrioritizableLifecycleEventType.Simple<>(name, ownerType); + } +} diff --git a/paper-server/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/MonitorableLifecycleEventType.java b/paper-server/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/MonitorableLifecycleEventType.java new file mode 100644 index 0000000000..abb969cf6e --- /dev/null +++ b/paper-server/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/MonitorableLifecycleEventType.java @@ -0,0 +1,63 @@ +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 extends AbstractLifecycleEventType> implements LifecycleEventType.Monitorable { + + final List> handlers = new ArrayList<>(); + int nonMonitorIdx = 0; + + public MonitorableLifecycleEventType(final String name, final Class ownerType) { + super(name, ownerType); + } + + @Override + public boolean hasHandlers() { + return !this.handlers.isEmpty(); + } + + @Override + public MonitorLifecycleEventHandlerConfigurationImpl newHandler(final LifecycleEventHandler handler) { + return new MonitorLifecycleEventHandlerConfigurationImpl<>(handler, this); + } + + @Override + protected void register(final O owner, final AbstractLifecycleEventHandlerConfiguration config) { + if (!(config instanceof final MonitorLifecycleEventHandlerConfigurationImpl monitor)) { + throw new IllegalArgumentException("Configuration must be a MonitorLifecycleEventHandlerConfiguration"); + } + final RegisteredHandler 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> consumer, final Predicate> predicate) { + for (final RegisteredHandler handler : this.handlers) { + if (predicate.test(handler)) { + consumer.accept(handler); + } + } + } + + @Override + public void removeMatching(final Predicate> predicate) { + this.handlers.removeIf(predicate); + } +} diff --git a/paper-server/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/OwnerAwareLifecycleEvent.java b/paper-server/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/OwnerAwareLifecycleEvent.java new file mode 100644 index 0000000000..3e7e7474f3 --- /dev/null +++ b/paper-server/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 extends LifecycleEvent { + + void setOwner(@Nullable O owner); + + @Nullable O castOwner(LifecycleEventOwner owner); +} diff --git a/paper-server/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/PrioritizableLifecycleEventType.java b/paper-server/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/PrioritizableLifecycleEventType.java new file mode 100644 index 0000000000..2ed622a61d --- /dev/null +++ b/paper-server/src/main/java/io/papermc/paper/plugin/lifecycle/event/types/PrioritizableLifecycleEventType.java @@ -0,0 +1,79 @@ +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 +> extends AbstractLifecycleEventType { + + private static final Comparator> 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> handlers = new ArrayList<>(); + + public PrioritizableLifecycleEventType(final String name, final Class ownerType) { + super(name, ownerType); + } + + @Override + public boolean hasHandlers() { + return !this.handlers.isEmpty(); + } + + @Override + protected void register(final O owner, final AbstractLifecycleEventHandlerConfiguration 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> consumer, final Predicate> predicate) { + for (final RegisteredHandler handler : this.handlers) { + if (predicate.test(handler)) { + consumer.accept(handler); + } + } + } + + @Override + public void removeMatching(final Predicate> predicate) { + this.handlers.removeIf(predicate); + } + + public static class Simple extends PrioritizableLifecycleEventType> implements LifecycleEventType.Prioritizable { + public Simple(final String name, final Class ownerType) { + super(name, ownerType); + } + + @Override + public PrioritizedLifecycleEventHandlerConfiguration newHandler(final LifecycleEventHandler handler) { + return new PrioritizedLifecycleEventHandlerConfigurationImpl<>(handler, this); + } + } +} diff --git a/paper-server/src/main/java/io/papermc/paper/plugin/manager/PaperPluginInstanceManager.java b/paper-server/src/main/java/io/papermc/paper/plugin/manager/PaperPluginInstanceManager.java index 834b85f24d..3e82ea07ca 100644 --- a/paper-server/src/main/java/io/papermc/paper/plugin/manager/PaperPluginInstanceManager.java +++ b/paper-server/src/main/java/io/papermc/paper/plugin/manager/PaperPluginInstanceManager.java @@ -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/paper-server/src/main/java/io/papermc/paper/plugin/storage/BootstrapProviderStorage.java b/paper-server/src/main/java/io/papermc/paper/plugin/storage/BootstrapProviderStorage.java index 2e96308696..65a66e484c 100644 --- a/paper-server/src/main/java/io/papermc/paper/plugin/storage/BootstrapProviderStorage.java +++ b/paper-server/src/main/java/io/papermc/paper/plugin/storage/BootstrapProviderStorage.java @@ -32,8 +32,9 @@ public class BootstrapProviderStorage extends SimpleProviderStorage 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/paper-server/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftServer.java index eaea08530c..b4a823a62d 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftServer.java @@ -1062,6 +1062,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/paper-server/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/paper-server/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java index 44abd5742f..0debfd0f95 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java @@ -649,6 +649,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 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. *

diff --git a/paper-server/src/main/resources/META-INF/services/io.papermc.paper.plugin.lifecycle.event.types.LifecycleEventTypeProvider b/paper-server/src/main/resources/META-INF/services/io.papermc.paper.plugin.lifecycle.event.types.LifecycleEventTypeProvider new file mode 100644 index 0000000000..808b1192b6 --- /dev/null +++ b/paper-server/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/paper-server/src/test/java/io/papermc/paper/plugin/PaperTestPlugin.java b/paper-server/src/test/java/io/papermc/paper/plugin/PaperTestPlugin.java index 1d14f530ef..90cf0c702c 100644 --- a/paper-server/src/test/java/io/papermc/paper/plugin/PaperTestPlugin.java +++ b/paper-server/src/test/java/io/papermc/paper/plugin/PaperTestPlugin.java @@ -143,4 +143,11 @@ public class PaperTestPlugin extends PluginBase { public List 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 getLifecycleManager() { + throw new UnsupportedOperationException("Not supported."); + } + // Paper end - lifecycle events }