Skip to content

Instantly share code, notes, and snippets.

@Machine-Maker
Last active January 9, 2024 21:42
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Machine-Maker/8e3fc6063c98e81cae7cee1ac230936f to your computer and use it in GitHub Desktop.
Save Machine-Maker/8e3fc6063c98e81cae7cee1ac230936f to your computer and use it in GitHub Desktop.
Lifecycle Event System

Lifecycle Event System

Pull Request

What is this?

A new event system for plugins to register handlers for specific events that relate to the startup or reload of servers.

Why is this needed?

We already have an event system, why do we need another one? Fair question. The answer is in the lifecycle part of the name. Some of these events fire well before JavaPlugin instances are created, before the MinecraftServer instance is created, right at the very start of server startup, even before all the registries have been initialized which is like the 2nd thing to happen on a vanilla server. The existing Bukkit event system is not designed to exist at this time, and modifying it to support this environment is more trouble than just having a separate system for specific events that can fire during this early initialization.

Here is an ever-expanding list of specific reasons why we can't just modify the existing event system to support this new need for events:

  1. You cannot have generics on Bukkit events because there is 0 compile time checking since they are registered reflectively. This is a problem because the events are mostly going to follow a very similar pattern, specifically the registry modification events. If we can’t use generics, there’s gonna be a ton of useless event classes

  2. Another reason is, that the existing system has priorities, but always has them. I feel that with lifecycle stuff, there may be some events for which we do not want to support priorities (it would based purely on plugin load order then).

  3. Exists too late. HandlerList and event registration all deal in Plugin type which does not exist, cannot exist, during the bootstrapper. Changing this would require a substantial rewrite of the existing system and probably confuse api consumers who expect all RegisteredListeners to have a Plugin attached.

  4. A new system lets us use interfaces and server implementations for events which dramatically simplifies them. With the Bukkit system you could kind of do this with a server impl event extending the api event, but interfaces are more flexible.

  5. A new system lets us enforce, at compile time, which events you can register where based on the context of the registration. So you can’t even register a handler for an event in the wrong spot, that will be a compiler error thanks to generics.

What happens during this initialization that plugins can use?

Well, lots of things. There are 2 things currently on the plan that would make use of this system, but there is plenty of room for expansion later.

Briadier Command API

The first is the upcoming Brigadier Command API. If a plugin wants their commands to be usable in Command Functions, these commands have to exist before datapacks are loaded, which is well before traditional plugins are loaded. Our relatively new Paper Plugin system does, however, provide a bootstrapper that is executed before. A plugin listening to a command registration event in that bootstrapper could register commands that a datapack would be aware of. Of course, command registration during the normal JavaPlugin#onEnable would still be supported, those commands just wouldn't exist for datapacks.

Registry Modification API

The other use is the Registry Modification API. This powerful API provides events plugin bootstrappers can listen to in order to modify previously immutable characteristics in data types like biomes, block types, item types, and, well everything that has a Registry which is a lot. These things happen either at the very beginning of the server initialization (right after a plugin's bootstrapper runs) or during datapack load, well before JavaPlugin exists. So these events would make use of the Lifecycle Event System.

How is it used?

Here is an example of it being used to register events inside JavaPlugin#onEnable

@Override
public void onEnable() {
    final LifecycleEventManager<Plugin> manager = this.getLifecycleManager();
    
    // register a handler for the command event which provides the "Commands" interface for
    // registering new commands
    manager.registerEventHandler(LifecycleEvents.COMMANDS.newHandler(event -> {
        final Commands commands = event.registrar();
        commands.register(this.getPluginMeta(), "some-command", (commandSourceStack, args) -> 1);
    }).priority(100)); // sets a priority of 100. Some events have priorities, some don't

    manager.registerEventHandler(LifecycleEvents.COMMANDS.newHandler(event -> {
        final Commands commands = event.registrar();
    }).monitor()); // sets this handler as a monitor so it runs last (should only be used for reading information)
}

Anywhere you can register handlers for any event (right now, just JavaPlugin#onEnable and with a BootstrapContext, you get the manager for lifecycle events (like you get the plugin manager for regular events) and register a handler on it. You create a handler with the static field provided for each event type. There, you can set the priority or if you want the event to be registered as a monitor which will run last, but shouldn't be used for modifying anything within the event. The handler can be a lambda method, or a class which implements the functional interface to handle the event object. The static fields representing each type of event have generic parameters designed in such a way that you cannot register a handler for an event designed only for use in the bootstrapper or JavaPlugin#onEnable. The command event however will be usable in both.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment