lucko / helper

A collection of utilities and extended APIs to support the rapid and easy development of Bukkit plugins.
MIT License
456 stars 87 forks source link

(Enhancement) Better event priority system #131

Open fan87 opened 2 years ago

fan87 commented 2 years ago

Here's an example code:


Events.subscribe(PlayerJoinEvent.class)
    .name("listenerA") // Optional, and by adding it you can reference it
    .handler(e -> e.setJoinMessage("A"));

Events.subscribe(PlayerJoinEvent.class)
    .name("listenerB")
    .mustRunAfter("listenerA") // Still: Optional. This will ensure that the event will be handled after listener A
    .mustRunBefore("listenerC") // You get the idea: it will ensure that the event will be handled before listenerB
    .handler(e -> e.setJoinMessage("B"));

Events.enableUnexistingListenerWarning(); // Check the code below

Events.subscribe(PlayerJoinEvent.class)
    .name("listenerC")
    .mustRunBefore("listenerB") // B must run before C, and C also must run before B: conflict. 
    // Throwing an exception with stack to let the developer know what is causing it 
    // (Sometimes it can get really complicated, for example, A must run after B must run after C... and must run after A)
    .mustRunAfter("listenerThatDoesntExist") // If listener doesn't exist and listener has been registered for over
    // <a configurable value> seconds, it will send a warning message to the console to let the developer know they probably
    // mistyped the listener name
    // Well, if it's in production state, developers probably don't want there to be a warning message in user's terminal,
    // that's why there's a function: `enableUnexistingListenerWarning()`, so developer can check if it's development build,
    // and enable it if needed
    .disableListenerNotExistingWarning() // With this, the listener can be not existing, and it won't print a warning message
    // So if listener is going to be registered later, it won't print the message
    .handler(e -> e.setJoinMessage("B"));

You can do it with a topological sorting algorithm, it would help a lot of developers.

(In my previous plugins, event priority gets really complicated and gets more than 5 layers, so in the end, I decided to code my own Event system instead of using an existing one, and having to figure out which priority should it be is very annoying and very hard to refactor. For example: If you want to insert an listener between A and B, you'll have to change everything before A or everything after B, having mustRunAfter and mustRunBefoer makes it way easier, so if plugin developers want to do something like damage processing in order, they can do it without painfully figuring out what priority should it be.)

I know it sounds impossible because Bukkit's event system is really not that great.

The only way I can think of to implement it is to have a custom event system, and have the main listener that listens to every single event, and call listeners. But I don't really know how you actually listen to every event since there can be custom events, a way would be attaching to the server JVM instance, modifying the bytecode of "callEvent" and adding instructions that call an inner function of this library.

Another way to listen to all events is by using reflection, going through every plugin class loader, and finding every class that extends Event class. It sounds more possible but there can be some limitations like

  1. takes around 1~2 seconds to start the server
  2. Hacks like plugman won't work But it's less stupid than attaching into JVM and adding bytecodes, but it will be slower. Probably make 2 of them and make it configurable?

It may sound really stupid, but it will actually help a lot of people trying to develop complicated plugins.