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.
This commit is contained in:
Jake Potrebic 2023-07-18 14:47:02 -07:00
parent cf77104dbc
commit eb68d0e1b5
18 changed files with 484 additions and 2 deletions

View File

@ -1,5 +1,7 @@
package io.papermc.paper.plugin.bootstrap; package io.papermc.paper.plugin.bootstrap;
import io.papermc.paper.plugin.lifecycle.event.LifecycleEventManager;
import io.papermc.paper.plugin.lifecycle.event.LifecycleEventOwner;
import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.NullMarked;
@ -12,5 +14,13 @@ import org.jspecify.annotations.NullMarked;
@ApiStatus.Experimental @ApiStatus.Experimental
@NullMarked @NullMarked
@ApiStatus.NonExtendable @ApiStatus.NonExtendable
public interface BootstrapContext extends PluginProviderContext { public interface BootstrapContext extends PluginProviderContext, LifecycleEventOwner {
/**
* Get the lifecycle event manager for registering handlers
* for lifecycle events allowed on the {@link BootstrapContext}.
*
* @return the lifecycle event manager
*/
LifecycleEventManager<BootstrapContext> getLifecycleManager();
} }

View File

@ -0,0 +1,17 @@
package io.papermc.paper.plugin.lifecycle.event;
import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents;
import org.jetbrains.annotations.ApiStatus;
/**
* Base type for all Lifecycle Events.
* <p>
* Lifecycle events are generally fired when the older
* event system is not available, like during early
* server initialization.
* @see LifecycleEvents
*/
@ApiStatus.Experimental
@ApiStatus.NonExtendable
public interface LifecycleEvent {
}

View File

@ -0,0 +1,53 @@
package io.papermc.paper.plugin.lifecycle.event;
import io.papermc.paper.plugin.lifecycle.event.handler.LifecycleEventHandler;
import io.papermc.paper.plugin.lifecycle.event.handler.configuration.LifecycleEventHandlerConfiguration;
import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEventType;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.NullMarked;
/**
* Manages a plugin's lifecycle events. Can be obtained
* from {@link org.bukkit.plugin.Plugin} or {@link io.papermc.paper.plugin.bootstrap.BootstrapContext}.
*
* @param <O> the owning type, {@link org.bukkit.plugin.Plugin} or {@link io.papermc.paper.plugin.bootstrap.BootstrapContext}
*/
@ApiStatus.Experimental
@NullMarked
@ApiStatus.NonExtendable
public interface LifecycleEventManager<O extends LifecycleEventOwner> {
/**
* Registers an event handler for a specific event type.
* <p>
* This is shorthand for creating a new {@link LifecycleEventHandlerConfiguration} and
* just passing in the {@link LifecycleEventHandler}.
* <pre>{@code
* LifecycleEventHandler<RegistrarEvent<Commands>> handler = new Handler();
* manager.registerEventHandler(LifecycleEvents.COMMANDS, handler);
* }</pre>
* is equivalent to
* <pre>{@code
* LifecycleEventHandler<RegistrarEvent<Commands>> handler = new Handler();
* manager.registerEventHandler(LifecycleEvents.COMMANDS.newHandler(handler));
* }</pre>
*
* @param eventType the event type to listen to
* @param eventHandler the handler for that event
* @param <E> the type of the event object
*/
default <E extends LifecycleEvent> void registerEventHandler(final LifecycleEventType<? super O, ? extends E, ?> eventType, final LifecycleEventHandler<? super E> eventHandler) {
this.registerEventHandler(eventType.newHandler(eventHandler));
}
/**
* Registers an event handler configuration.
* <p>
* Configurations are created via {@link LifecycleEventType#newHandler(LifecycleEventHandler)}.
* Event types may have different configurations options available on the builder-like object
* returned by {@link LifecycleEventType#newHandler(LifecycleEventHandler)}.
*
* @param handlerConfiguration the handler configuration to register
*/
void registerEventHandler(LifecycleEventHandlerConfiguration<? super O> handlerConfiguration);
}

View File

@ -0,0 +1,25 @@
package io.papermc.paper.plugin.lifecycle.event;
import io.papermc.paper.plugin.configuration.PluginMeta;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.NullMarked;
/**
* Implemented by types that are considered owners
* of registered handlers for lifecycle events. Generally
* the types that implement this interface also provide
* a {@link LifecycleEventManager} where you can register
* event handlers.
*/
@ApiStatus.Experimental
@NullMarked
@ApiStatus.NonExtendable
public interface LifecycleEventOwner {
/**
* Get the plugin meta for this plugin.
*
* @return the plugin meta
*/
PluginMeta getPluginMeta();
}

View File

@ -0,0 +1,19 @@
package io.papermc.paper.plugin.lifecycle.event.handler;
import io.papermc.paper.plugin.lifecycle.event.LifecycleEvent;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.NullMarked;
/**
* A handler for a specific event. Can be implemented
* in a concrete class or as a lambda.
*
* @param <E> the event
*/
@ApiStatus.Experimental
@NullMarked
@FunctionalInterface
public interface LifecycleEventHandler<E extends LifecycleEvent> {
void run(E event);
}

View File

@ -0,0 +1,20 @@
package io.papermc.paper.plugin.lifecycle.event.handler.configuration;
import io.papermc.paper.plugin.lifecycle.event.LifecycleEventOwner;
import io.papermc.paper.plugin.lifecycle.event.handler.LifecycleEventHandler;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.NullMarked;
/**
* Base type for constructing configured event handlers for
* lifecycle events. Usually created via {@link io.papermc.paper.plugin.lifecycle.event.types.LifecycleEventType#newHandler(LifecycleEventHandler)}
* from event types in {@link io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents}
*
* @param <O>
*/
@SuppressWarnings("unused")
@ApiStatus.Experimental
@NullMarked
@ApiStatus.NonExtendable
public interface LifecycleEventHandlerConfiguration<O extends LifecycleEventOwner> {
}

View File

@ -0,0 +1,27 @@
package io.papermc.paper.plugin.lifecycle.event.handler.configuration;
import io.papermc.paper.plugin.lifecycle.event.LifecycleEventOwner;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Contract;
import org.jspecify.annotations.NullMarked;
/**
* Handler configuration for event types that allow "monitor" handlers.
*
* @param <O> the required owner type
*/
@ApiStatus.Experimental
@NullMarked
@ApiStatus.NonExtendable
public interface MonitorLifecycleEventHandlerConfiguration<O extends LifecycleEventOwner> extends LifecycleEventHandlerConfiguration<O> {
/**
* Sets this handler configuration to be considered a "monitor".
* These handlers will run last and should only be used by plugins
* to observe changes from previously run handlers.
*
* @return this configuration for chaining
*/
@Contract("-> this")
MonitorLifecycleEventHandlerConfiguration<O> monitor();
}

View File

@ -0,0 +1,41 @@
package io.papermc.paper.plugin.lifecycle.event.handler.configuration;
import io.papermc.paper.plugin.lifecycle.event.LifecycleEventOwner;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Contract;
import org.jspecify.annotations.NullMarked;
/**
* Handler configuration that allows both "monitor" and prioritized handlers.
* The default priority is 0.
*
* @param <O> the required owner type
*/
@ApiStatus.Experimental
@NullMarked
@ApiStatus.NonExtendable
public interface PrioritizedLifecycleEventHandlerConfiguration<O extends LifecycleEventOwner> extends LifecycleEventHandlerConfiguration<O> {
/**
* Sets the priority for this handler. Resets
* all previous calls to {@link #monitor()}. A
* lower numeric value correlates to the handler
* being run earlier.
*
* @param priority the numerical priority
* @return this configuration for chaining
*/
@Contract("_ -> this")
PrioritizedLifecycleEventHandlerConfiguration<O> priority(int priority);
/**
* Sets this handler configuration to be considered a "monitor".
* These handlers will run last and should only be used by plugins
* to observe any changes from previously ran handlers.
*
* @return this configuration for chaining
*/
@Contract("-> this")
PrioritizedLifecycleEventHandlerConfiguration<O> monitor();
}

View File

@ -0,0 +1,12 @@
package io.papermc.paper.plugin.lifecycle.event.registrar;
import org.jetbrains.annotations.ApiStatus;
/**
* To be implemented by types that provide ways to register types
* either on server start or during a reload
*/
@ApiStatus.Experimental
@ApiStatus.NonExtendable
public interface Registrar {
}

View File

@ -0,0 +1,28 @@
package io.papermc.paper.plugin.lifecycle.event.registrar;
import io.papermc.paper.plugin.lifecycle.event.LifecycleEvent;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Contract;
import org.jspecify.annotations.NullMarked;
/**
* A lifecycle event that exposes a {@link Registrar} of some kind
* to allow management of various things. Look at implementations of
* {@link Registrar} for an idea of what uses this event.
*
* @param <R> registrar type
* @see ReloadableRegistrarEvent
*/
@ApiStatus.Experimental
@NullMarked
@ApiStatus.NonExtendable
public interface RegistrarEvent<R extends Registrar> extends LifecycleEvent {
/**
* Get the registrar related to this event.
*
* @return the registrar
*/
@Contract(pure = true)
R registrar();
}

View File

@ -0,0 +1,39 @@
package io.papermc.paper.plugin.lifecycle.event.registrar;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Contract;
import org.jspecify.annotations.NullMarked;
/**
* A lifecycle event that exposes a {@link Registrar} that is
* reloadable.
*
* @param <R> the registrar type
* @see RegistrarEvent
*/
@ApiStatus.Experimental
@NullMarked
@ApiStatus.NonExtendable
public interface ReloadableRegistrarEvent<R extends Registrar> extends RegistrarEvent<R> {
/**
* Get the cause of this reload.
*
* @return the cause
*/
@Contract(pure = true)
Cause cause();
@ApiStatus.Experimental
enum Cause {
/**
* The initial load of the server.
*/
INITIAL,
/**
* A reload, triggered via one of the various mechanisms like
* the bukkit or minecraft reload commands.
*/
RELOAD
}
}

View File

@ -0,0 +1,74 @@
package io.papermc.paper.plugin.lifecycle.event.types;
import io.papermc.paper.plugin.lifecycle.event.LifecycleEvent;
import io.papermc.paper.plugin.lifecycle.event.LifecycleEventManager;
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.LifecycleEventHandlerConfiguration;
import io.papermc.paper.plugin.lifecycle.event.handler.configuration.MonitorLifecycleEventHandlerConfiguration;
import io.papermc.paper.plugin.lifecycle.event.handler.configuration.PrioritizedLifecycleEventHandlerConfiguration;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Contract;
import org.jspecify.annotations.NullMarked;
/**
* Base type for all types of lifecycle events. Differs from
* {@link LifecycleEvent} which is the actual event object, whereas
* this is an object representing the type of the event. Used
* to construct subtypes of {@link LifecycleEventHandlerConfiguration} for
* use in {@link LifecycleEventManager}
*
* @param <O> the required owner type
* @param <E> the event object type
* @param <C> the configuration type
*/
@ApiStatus.Experimental
@NullMarked
@ApiStatus.NonExtendable
public interface LifecycleEventType<O extends LifecycleEventOwner, E extends LifecycleEvent, C extends LifecycleEventHandlerConfiguration<O>> {
/**
* Gets the name of the lifecycle event.
*
* @return the name
*/
@Contract(pure = true)
String name();
/**
* Create a configuration for this event with the specified
* handler.
*
* @param handler the event handler
* @return a new configuration
* @see LifecycleEventManager#registerEventHandler(LifecycleEventHandlerConfiguration)
*/
@Contract("_ -> new")
C newHandler(LifecycleEventHandler<? super E> handler);
/**
* Lifecycle event type that supports separate registration
* of handlers as "monitors" that are run last. Useful
* if a plugin wants to only observe the changes other handlers
* made.
*
* @param <O> the required owner type
* @param <E> the event object type
*/
@ApiStatus.Experimental
@ApiStatus.NonExtendable
interface Monitorable<O extends LifecycleEventOwner, E extends LifecycleEvent> extends LifecycleEventType<O, E, MonitorLifecycleEventHandlerConfiguration<O>> {
}
/**
* Lifecycle event type that supports both {@link Monitorable "monitors"} and
* specific numeric-based priorities.
*
* @param <O> the required owner type
* @param <E> the event object type
*/
@ApiStatus.Experimental
@ApiStatus.NonExtendable
interface Prioritizable<O extends LifecycleEventOwner, E extends LifecycleEvent> extends LifecycleEventType<O, E, PrioritizedLifecycleEventHandlerConfiguration<O>> {
}
}

View File

@ -0,0 +1,24 @@
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 java.util.Optional;
import java.util.ServiceLoader;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.NullMarked;
@ApiStatus.Internal
@NullMarked
interface LifecycleEventTypeProvider {
Optional<LifecycleEventTypeProvider> INSTANCE = ServiceLoader.load(LifecycleEventTypeProvider.class)
.findFirst();
static LifecycleEventTypeProvider provider() {
return INSTANCE.orElseThrow();
}
<O extends LifecycleEventOwner, E extends LifecycleEvent> LifecycleEventType.Monitorable<O, E> monitor(String name, Class<? extends O> ownerType);
<O extends LifecycleEventOwner, E extends LifecycleEvent> LifecycleEventType.Prioritizable<O, E> prioritized(String name, Class<? extends O> ownerType);
}

View File

@ -0,0 +1,54 @@
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.LifecycleEventManager;
import io.papermc.paper.plugin.lifecycle.event.LifecycleEventOwner;
import org.bukkit.plugin.Plugin;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.NullMarked;
/**
* Holds various types of lifecycle events for
* use when creating event handler configurations
* in {@link LifecycleEventManager}.
*/
@ApiStatus.Experimental
@NullMarked
public final class LifecycleEvents {
//<editor-fold desc="helper methods" defaultstate="collapsed">
@ApiStatus.Internal
static <E extends LifecycleEvent> LifecycleEventType.Monitorable<Plugin, E> plugin(final String name) {
return monitor(name, Plugin.class);
}
@ApiStatus.Internal
static <E extends LifecycleEvent> LifecycleEventType.Prioritizable<Plugin, E> pluginPrioritized(final String name) {
return prioritized(name, Plugin.class);
}
@ApiStatus.Internal
static <E extends LifecycleEvent> LifecycleEventType.Monitorable<BootstrapContext, E> bootstrap(final String name) {
return monitor(name, BootstrapContext.class);
}
@ApiStatus.Internal
static <E extends LifecycleEvent> LifecycleEventType.Prioritizable<BootstrapContext, E> bootstrapPrioritized(final String name) {
return prioritized(name, BootstrapContext.class);
}
@ApiStatus.Internal
static <O extends LifecycleEventOwner, E extends LifecycleEvent, O2 extends O> LifecycleEventType.Monitorable<O, E> monitor(final String name, final Class<O2> ownerType) {
return LifecycleEventTypeProvider.provider().monitor(name, ownerType);
}
@ApiStatus.Internal
static <O extends LifecycleEventOwner, E extends LifecycleEvent> LifecycleEventType.Prioritizable<O, E> prioritized(final String name, final Class<? extends O> ownerType) {
return LifecycleEventTypeProvider.provider().prioritized(name, ownerType);
}
//</editor-fold>
private LifecycleEvents() {
}
}

View File

@ -271,4 +271,12 @@ public interface UnsafeValues {
*/ */
@Nullable org.bukkit.Color getSpawnEggLayerColor(org.bukkit.entity.EntityType entityType, int layer); @Nullable org.bukkit.Color getSpawnEggLayerColor(org.bukkit.entity.EntityType entityType, int layer);
// Paper end - spawn egg color visibility // Paper end - spawn egg color visibility
// Paper start - lifecycle event API
/**
* @hidden
*/
@org.jetbrains.annotations.ApiStatus.Internal
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);
// Paper end - lifecycle event API
} }

View File

@ -16,7 +16,7 @@ import org.jetbrains.annotations.Nullable;
* <p> * <p>
* The use of {@link PluginBase} is recommended for actual Implementation * The use of {@link PluginBase} is recommended for actual Implementation
*/ */
public interface Plugin extends TabExecutor { public interface Plugin extends TabExecutor, io.papermc.paper.plugin.lifecycle.event.LifecycleEventOwner { // Paper
/** /**
* Returns the folder that the plugin data files are located in. The * Returns the folder that the plugin data files are located in. The
* folder may not yet exist. * folder may not yet exist.
@ -224,4 +224,14 @@ public interface Plugin extends TabExecutor {
*/ */
@NotNull @NotNull
public String getName(); public String getName();
// Paper start - lifecycle events
/**
* Get the lifecycle event manager for registering handlers
* for lifecycle events allowed on the {@link Plugin}.
*
* @return the lifecycle event manager
*/
io.papermc.paper.plugin.lifecycle.event.@NotNull LifecycleEventManager<Plugin> getLifecycleManager();
// Paper end - lifecycle events
} }

View File

@ -48,6 +48,11 @@ public abstract class JavaPlugin extends PluginBase {
private FileConfiguration newConfig = null; private FileConfiguration newConfig = null;
private File configFile = null; private File configFile = null;
private Logger logger = null; // Paper - PluginLogger -> Logger private Logger logger = null; // Paper - PluginLogger -> Logger
// Paper start - lifecycle events
@SuppressWarnings("deprecation")
private final io.papermc.paper.plugin.lifecycle.event.LifecycleEventManager<org.bukkit.plugin.Plugin> lifecycleEventManager = org.bukkit.Bukkit.getUnsafe().createPluginLifecycleEventManager(this, () -> this.allowsLifecycleRegistration);
private boolean allowsLifecycleRegistration = true;
// Paper end
public JavaPlugin() { public JavaPlugin() {
// Paper start // Paper start
@ -279,7 +284,9 @@ public abstract class JavaPlugin extends PluginBase {
isEnabled = enabled; isEnabled = enabled;
if (isEnabled) { if (isEnabled) {
try { // Paper - lifecycle events
onEnable(); onEnable();
} finally { this.allowsLifecycleRegistration = false; } // Paper - lifecycle events
} else { } else {
onDisable(); onDisable();
} }
@ -457,4 +464,11 @@ public abstract class JavaPlugin extends PluginBase {
} }
return plugin; return plugin;
} }
// Paper start - lifecycle events
@Override
public final io.papermc.paper.plugin.lifecycle.event.@NotNull LifecycleEventManager<org.bukkit.plugin.Plugin> getLifecycleManager() {
return this.lifecycleEventManager;
}
// Paper end - lifecycle events
} }

View File

@ -133,4 +133,11 @@ public class TestPlugin extends PluginBase {
public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) { public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
throw new UnsupportedOperationException("Not supported."); throw new UnsupportedOperationException("Not supported.");
} }
// Paper start - lifecycle events
@Override
public io.papermc.paper.plugin.lifecycle.event.LifecycleEventManager<Plugin> getLifecycleManager() {
throw new UnsupportedOperationException("Not supported.");
}
// Paper end - lifecycle events
} }