From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Jake Potrebic 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 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/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>> BLOCKS_RELOADING = Suppliers.memoize(() -> Set.of( // lazy due to cyclic initialization + )); + public static final LifecycleEventRunner INSTANCE = new LifecycleEventRunner(); + + private final List> 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 > ET addEventType(final ET eventType) { + this.lifecycleEventTypes.add(eventType); + return 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/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 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/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 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/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 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/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 + 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/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 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, 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/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> implements LifecycleEventType { + + private final String name; + private final Class ownerType; + + protected AbstractLifecycleEventType(final String name, final Class 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> 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/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 LifecycleEventType.Monitorable monitor(final String name, final Class ownerType) { + return LifecycleEventRunner.INSTANCE.addEventType(new MonitorableLifecycleEventType<>(name, ownerType)); + } + + @Override + public LifecycleEventType.Prioritizable prioritized(final String name, final Class 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 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 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/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 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 +> 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 + 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/src/main/java/io/papermc/paper/plugin/manager/PaperPluginInstanceManager.java b/src/main/java/io/papermc/paper/plugin/manager/PaperPluginInstanceManager.java 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 @@ -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 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 index 3ca0d01d4d55d0919624356751144985587ddc25..4bb633d202eebd679a07ce45f486b301717dbafd 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java @@ -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 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 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 index be009fe105a4fff86d592ebc8df75650aff74a29..d329da33f9bfea5e30e42cd30974b7d3b775f446 100644 --- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java @@ -657,6 +657,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/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 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 }