#Lifecycle Event System

1 messages · Page 1 of 1 (latest)

fossil temple
#

PR: https://github.com/PaperMC/Paper/pull/9629
Full doc: https://gist.github.com/Machine-Maker/8e3fc6063c98e81cae7cee1ac230936f

This is a new event system required for events during early initialization. The 2 currently planned uses are for the Brigadier Commands API ( #1121227200277004398 ) and the Registry Modification API ( #1179518554702348319 ). The gist linked above has the full rundown on what this is, why its needed, and how its used. The TL;DR version, is that the existing event system is not designed for operation before the server has been initialized which happens too late during server startup for key events that plugin bootstraps might want to listen to.

The registration of event handlers can happen in 2 places, in PluginBootstrap#bootstrap or in JavaPlugin#onEnable. Event types will support registration of handlers in one or both of those places.

Here is an example of the event system being used in JavaPlugin#onEnable to handle command registration.

@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)
}

The complete write-up for this system is available here

proud rock
#

Not sure if there's any internal reasons this isn't possible/a good idea, but would a util on the events themselves to register a handler make sense?
E.g. something like

LifecycleEvents.COMMANDS.registerHandler(this, event -> {

Taking in the plugin instance would probably cover most use cases and feels less boilerplate-y if you have multiple calls; could even have an overload to take in the LifecycleEventManager directly.

fossil temple
#

yeah, that was discussed but we don't really have that pattern anywhere else, registering something on a field rather than from a method. I had it setup like that initially, but changed it so you are registering on the manager. I don't really think its less boilerplate-y. You still need the same information, you are just switching the first 2 "things". Going from manager.registerEventHandler(LifecycleEvents.COMMANDS... to LifecycleEvents.COMMANDS.registerEventHandler(manger/this...

#

Plus, having the regsitration on the manager just makes it more clear that you can only register when you have the manager which is only available in 2 spots (that limit is enforced too, its not just based on where you can access the manager)

rough patio
#

was something like manager.registerEventHandler(LifecyleEvents.COMMANDS, event -> {}) considered?

fossil temple
#

yes, and we can add overloads that do that absolutely. But we wanted sortof a builder for the handler so you can set priority/monitor state.

#

but we can for sure add an overload that just does that and doesn't set a priority or monitor. The thought was, that if we added more configurable stuff to the handler, we didn't want to end up with 10 different register methods each with 1 more param

rough patio
#

because creating a handler without a manager doesn't seem very useful

fossil temple
#

yeah, it isn't useful really at all. unless you pass it to the register method later. We thought about this
register(COMMANDS, handlerBuilder -> handlerBuilder.handler(event -> {}).priority(1)); but that is a nested lambda which is ugly to work with

#

this aspect of the design is really just for preventing an ever expanding list of register methods for any other configurable things. It also lets us define what can be configured based on the event type itself

#

like some events we might not want plugins to be able to set a priority, it'd just be based on the plugin's load order

rough patio
#

maybe it could be split into register(COMMANDS, event -> {}, handlerBuilder -> handlerBuilder.priority(1)) or something like that, with an overload that drops the last arg?

fossil temple
#

is the double lambda parameters worth it? I've never been a fan of that in a method

pallid dock
#

kotlin fanboys will be ENRAGED if they won't be able to have a single lambda as the last parameter to make their code less readable peperla

rough patio
shrewd stream
#

:nogroovy:

rough patio
shrewd stream
rough patio
#

nested lambdas also have the problem that you need to have separate types so you can makes sure the handler method is actually called once

mighty plank
#

To add my two cents as these early events arnt as important to myself persronally....

But imo, that way of registering events is pretty clean. I'd be more than happy to double down on this concept and have this be a way to register normal events as an alternative to the totally good and not annotation heavy at all listener solution we currently have

#

So I guese that, at the same time, gives blessing to the general concept itself?

eager agate
#

the traditional event system does make it very convient to bulk register event listeners. is there similar convenience here, or do you have to register them all explicitly one by one?

fossil temple
#

well I'm not expecting there to be nearly as many events in this system as there are in the bukkit system.

#

We could maybe add a similar "scan the class for methods annotated with whatever" system on top of this if there's enough need for it

#

it gets a little tricky tho, since the actual event objects themselves have more complex generic parameters whereas bukkits events never have generic parameters

sinful plume
#

or changing build() with register() or something similar

#

of course it should be annotated with @CheckReturnValue so the ide will help devs to not forget to register the builder

fossil temple
#

I don't like the final method call being the thing that registers something. even with that annotation, its ripe for people forgetting and wondering why its not working

sinful plume
#

then maybe

manager.registerEventHandler(LifecyleEvents.COMMANDS, event -> {})

to delegate to

manager.registerEventHandler(LifecycleEvents.COMMANDS.newHandler(passedConsumer).priority(100));

as I'm 100% sure that most plugins won't need to specify priority or other builder parameters, and if they do need, may use the second code directly

fossil temple
#

Ya, as discussed above, that is something we can add.

ember lynx
#

How is the priotiry sorted ?
Stupid like bukkit currently or useful

fossil temple
#

its just a number, an int

ember lynx
#

But how is sorted ?

#

The bukkit way is currently confusing

#

And whats happend if code inside of the lambda throws a exception ?
If it possible to catch it via other Method or just throw away ?
for example:

manager.registerEventHandler(LifecycleEvents.COMMANDS.newHandler(passedConsumer).priority(100).onFailure(passedFailreConsumer));
opaque spear
#

i think the higher int -> higher priority

#

so the handler will do its stuff later than lower priority handlers'

ripe vector
#

Mhh, but for inter plugin compatibility how can we minimise any conflict.

uneven wigeon
#

There is no magical way to avoid conflicts, this is generally something in which devs are going to need to figure out in a sense

#

I do kinda want named listeners for a reason, and the priorties should probably have some form of like, "suggested levels" as such

#

normal = 500, etc

ripe vector
#

Mhh, in that scenario maybe also put a recommendation to document the priorities each public plugin uses for their specific events for other devs to quickly check in case of compatibility issues

#

Or use a priority based system using an Priority Queue

#

Where the priority is based on other plugin contexts using the Comparable interface

#

With bottom up heap construction

ripe vector
#

You can even combine both, like you can use the normal builder with simple Integer priorities, but plugins can also opt-in with providing their own comparable implementation if they wanna ensure compatibility with other plugins

fossil temple
#

for example, my instinct is with the registry modification stuff, that any exception should really just hard crash the server

#

expecially for the built-ins. For the data drivens from regsitries, can maybe just ignore

#

but that could be part of the handler builder, a consumer for exceptions

#

as for the whole priority convo, I'm not sure what events will even have that. It's probably that for the registry modification stuff it should just be based on the load order which plugins already can configure

#

for the brig commands api, a number is probably fine?

ember lynx
#

Yeah is fine, thanks for that feedback

blissful night
#

So, theoretically speaking, would the new events API and its priority system help resolve plugin command conflicts by defining the behavior of replacing an existing command in the LifecycleEvents.COMMANDS event?

#

That is, I am hoping that by having command registration be movable to this lifecycle event, and by defining whether event.registrar().register overrides existing commands, it will become easier to automatically deconflict commands from other plugins, without instructing users to configure the commands.yml.

fossil temple
fringe geyser
#

Can this Event System be an alternative to the bukkit one? Like, remade for paper servers?

proud rock
uneven wigeon
#

No, having two event systems side by side is generally dumb

#

the only reason we have a seperate event system for this stuff is because bukkit is not available at the point in which you'd want a lot of this stuff to be registerable, hence, new event system without the bukkit dependency

proud rock
#

Yeah that's what I meant by for specific cases that require it, stuff the normal bukkit event system can't be used for

fringe geyser
#

(Oh, I forgot to respond...)

Ok, I understand. I had just a slim hope of getting a reworked event system...

timber horizon
#

is this PR blocked by something or it's ready to merge? can't wait for brigadier api 🥹

shrewd dune
mighty plank
#

Closest to can think to it analogy wise is showing someone a fidget spinner, describing it in meticulous details and all the ways the fidget spinner can be used.

Vs just showing the funni spin triangle going brt

#

Bonus points if said example shows the ball ache workaround thstd be needed if the system didn't exist

cold ore
#

The PR exposes Brigadier, how'd you use it directly without it

shrewd dune
cold ore
#

What's that?

#

Perhaps off-topic for this thread

shrewd dune
cold ore
#

Interesting

shrewd dune
#

Essentially provides access to internals, but as you've said, it's probably off topic already :)

fringe geyser
#

When is the deadline?

shrewd stream
#

You'll have a few days for feedback at least, we will see for how long anything comes in

untold wasp
#

I don't see PluginBootsrap#bootstrap in any examples, what does it do

fossil temple
#

that is already in the API, its part of the paper plugin system

untold wasp
#

ah ok didn't know that

fossil temple
#

just another class you can specify in your paper-plugin.yml that is created very early, before anything happens

untold wasp
#

neat

#

i'm a bit confusled if I understand correctly, #onEnable is called way after datapacks and stuff, so isn't it too late to register an event to register a command, which needs to be done before datapacks?

stuck glen
#

I do like the Briadier Commands 😍

sullen sigil
#

you can still register stuff for a /minecraft:reload

round panther
#

the get prefix looks kinda meh

fossil temple
fossil temple
#

oh wait, you said that, why did I read "before"

untold wasp
#

yes

#

idk

fossil temple
#

yes, that's correct, it is too late to register a command that datapacks can see

#

however, you can register commands that datapacks can't see just fine, that's how commands are registered now

#

but the lifecycle event system allows us to fire the event twice, depending on where you registered a handler from

stuck glen
#

but where should we register commands then? onload?

fossil temple
#

fire it once for the bootstrap, which is before datapacks, and fire it again for javaplugin

#

you can continue to register them in onEnable just fine

untold wasp
fossil temple
flat turret
#

What's the point of Lifecycle Event System ?

untold wasp
#

kinda funny how your gist is linked twice in the pinned message

velvet totem
#

This is a paper plugin exclusive feature right?

untold wasp
#

well i got nothing more to say, good job Machine Maker 👍 thanks for all your work

untold wasp
round panther
#

yes

fringe geyser
fossil temple
untold wasp
#

yesn't then

velvet totem
#

Ahh I see

velvet totem
shrewd dune
#

But, of course, it's true what you said :)

buoyant patrol
#

Is there any plan to add “post” events? For plugins that don’t care about modifying or adding anything to registries for example, but do want to listen for every time the registry is frozen

fossil temple
#

Yeah, you can register handlers as a “monitor” which will always run last

#

atm, there’s no runtime check that you don’t change anything in the event handler if you do register as a monitor. It could be added tho

buoyant patrol
#

Cool, thanks. Would that event run for even currently built in registries that cannot be modified? (Eg blocks)

fossil temple
#

No, it doesn’t at the moment. (But that stuff isn’t being added with the event system, that’s for #1179518554702348319 which uses the system)

buoyant patrol
#

Ahh sorry thought it was the same channel for both

quartz jasper
#

Is it explained somewhere why these usecases aren't handled by the existing event system?

uneven wigeon
#

Because the existing event system relies on Bukkit

#

and Bukkit is only available stupidly late

fossil temple
quartz jasper
#

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.
Why is this true? Can we make it not have to be true?

uneven wigeon
#

It is true because datapack stuff loads much earlier

#

command functions will resolve the entire tree to their target commands, etc, and so the only way to make that "not true", would be to start delying random things from occuring in datapacks

#

which is just a nightmare to handle with a fuckton of engineering effort that would be better invested on futureproofing, i.e. this PR

quartz jasper
#

Oh, so commands in datapacks resolve their command strings using the command tree on instantiation which happens before Bukkit is ready?

uneven wigeon
#

yes

#

a lot of technical stuff is going to rely on plugins being able to do stuff earlier, and it's much easier to just accept that and adopt tooling for it rather than spending years fighting against it until we have to pull the "oh shit" level of changes one day, see, Biome, Material, etc

quartz jasper
#

I guess, it just seems weird to have two different event systems at the same time

uneven wigeon
#

well, yes; but the alternative is that we go back in time 15 or so years...

pallid dock
#

have you tried that tho?

uneven wigeon
#

||I tried, but we then argued and said that we should of gone back like even further, so, we did, but, I just couldn't kill a baby, I just couldn't||

steel nebula
#

Does the lifecycle event system have an "event(s)" for when resources(like datapacks) are reloaded and/or loaded?

fossil temple
#

Well right now, it has no events, this is just the system.

#

The two currently planned event types are for commands and registry modification

#

What are you wanting to do related to resources/datapacks exactly?

steel nebula
#

I was hoping to hook into the process of "datapack loading/reloading" so I can read the datapacks at the time of them being loaded instead of way after that. It's not NEEDED but I wanted to do it then because the plugin depends on datapacks to gather information for what to do, so it made sense to move the parsing time to then

fossil temple
#

Yeah, we can probably add an event for when datapacks are being detected before they are read.

steel nebula
#

Ok, ty!

#

Super excited for this, it's super super cool

high raven
#

Question:
I got an open PR at https://github.com/PaperMC/Waterfall/pull/836

Since Waterfall seems to be dead at development, I wanted to ask if I could potentially run commands and libraries with this so I can register Bungee/Paper on startup at a custom HA-Proxy manager and defer the shutdown until all players disconnected?

Otherwise I'm going to open a new pr sometime with custom fixes

prime shore
high raven
#

Oh lord

#

I really just messed up - I thought paper was another proxy lmao

#

so nevermind

sinful plume
#

it's merged 🎉🎉🎉

cold ore
#

Woooo

#

🎊

steel nebula
#

YAHOO!

steel nebula
fossil temple
#

there aren't any events yet. this was just the framework

steel nebula
#

arlighty, ty

untold wasp
#

oh nice

#

is it brigadier time now?

late coral
#

Now? No.
Next? Maybe. In my words, I would say that Brigadier is more feasible to evaluate now that the lifecycle event system is "locked in".

steel nebula
#

Are there any examples for this? I want to try and see how it works and the example in the paper repo(test dir) doesn't seem to work(missing classes, so probably out of date?)

sinful plume
steel nebula
#

Ahh ok

fossil temple
#

Merged a new lifecycle event, one for datapack discovery. You can point to a file system path, or a path inside your jar to "discover" a datapack when the game goes searching for them. Super useful for seamlessly providing a datapack along side your plugin

fringe geyser
#

Can you create a jar-internal datapack or creating one using nms?

#

Instead of "deposing" the datapack somewhere to be picked up again

sinful plume
fringe geyser
#

Oh, I skipped words 💀

fossil temple
#

yeah, the example in the javadoc on DatapackRegistrar shows that as an example

fringe geyser
#

But you can't make one using nms magic? So technically, if you make a datapack builder or something which is using a library. It would still need to deposit it?

fossil temple
#

idk what you mean "make one using nms magic"

velvet totem
#

Would be nice to have something similar for resource packs although I know that is kinda limiting at the moment

fossil temple
#

run the datapack builder when you compile your plugin jar and include that created thing

#

or run it in the boostrapper to output to some file in your plugin folder, and point the event at that

fringe geyser
fossil temple
#

all this is is giving the server a path for a datapack. It doesn't have any control over what's in the directory at that path, that's the same as vanilla

#

you aren't specifying what's in the datapack with the event, just where the datapack is located

fringe geyser
#

Can't it take in a reader of some sorts? Or is it too cumbersome?

fossil temple
#

so whatever method you use to create the datapack will work

#

No, it takes a path. Because you need a directory structure

#

datapacks consist of multiple files in a directory hierarchy, not one file

fringe geyser
#

I mean, you could make a hierarchical reader but ig it is really too much for an API

fossil temple
#

what is the benefit? just write out the datapack to some directory and then use that.

fringe geyser
#

Benefit would be no interaction with disk/memory. But that's, tbh, not really that much of a problem

sinful plume
fossil temple
#

yeah, in-memory datapacks aren't a thing in vanilla, would require more substantial changes to support that, for I think not a lot of gain

#

even the vanilla datapack isn't "in memory", its just inside the jar as 4000 json files

fringe geyser
#

That was more or less to what I wanted to ask. But if in-memory aren't a thing... it's sad

sinful plume
#

That's just too far from how nms reads the datapacks and how it expects things

fringe geyser
#

Hmm, understandable thonkeyes