Resource Making a own EventAPI

Discussion in 'Spigot Plugin Development' started by Mxrlin1, Jul 10, 2021.

  1. Before starting: This resource is just for learning so don't comment something like "why would u use it, bukkit made already that"...

    Greetings,
    I often asked myself, how is it possible to do an own EventAPI, like the one in bukkit.
    And if you just look a bit in the code, its quite simple.

    So I want to present it to you, how to it. When doing this you actually can learn some things about Reflection:

    1 First, you want to create an 'Annotation' (@Interface), that will come everytime in front of the method in the listener classes. (Like @EventHandler)
    Code (Text):
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface EventHandler {
    }
    Set the target to methods, so this annotation can only be used for methods. Thats not a must.

    2 Create now the listener interface, so we can later save all classes that are implementing that in a list.
    Code (Text):
    public interface Listener {
    }

    3 Create now the Event class. You can do it an abstract class, but I if you want to do it like me, just use an interface.
    Code (Text):
    public interface Event {

        boolean isCanceled();

        void setCanceled(boolean canceled);

    }

    4 Now create something like a EventManager, where you can register the Events, Listener, and call Events.
    Code (Text):
    public class EventManager {
    }

    5 If you want to, that events need to be registered create an list for that, and create a list for all registered listeners.
    Code (Text):
    private static List<Event> registeredEvents = new ArrayList<>();

        private static List<Listener> registeredListener = new ArrayList<>();

    6 Now we need to be able to register events/listeners. Just do simple methods for that.
    Code (Text):
    public static boolean registerEvent(Event event){
            if(registeredEvents.contains(event)) return false;
            registeredEvents.add(event);
            return true;
        }

        public static boolean registerListener(Listener listener){
            if(registeredListener.contains(listener)) return false;
            registeredListener.add(listener);
            return true;
        }

    7 Now the a bit more complicated part: the callEvent Method
    Code (Text):
    public static Event callEvent(Event event){
            if(!registeredEvents.contains(event)) return null;
            for(Listener listener : registeredListener){ // Looping through all registeredListener
                Class<? extends Listener> c = listener.getClass(); // Getting the class of the Listener
                Method[] methods = c.getMethods(); // Getting all Methods from the class
                for(Method method : methods){ // Looping through all Methods
                    Object annotation = method.getAnnotation(EventHandler.class); // Save the annotation as Object
                    if(annotation == null){ // If annotation is null = there is not that annotation
                        continue; // Skipping to next method
                    }
                    try { // Trying to do something
                        method.invoke(event, event); // Invoking the method, might changing the event (setCanceled)
                    } catch (IllegalAccessException | InvocationTargetException ignored) { // if it can't do it, nothing will happen (skipping to next method)
                    }
                }
            }
            return event; // Return the event, where something might be changed
        }

    Thats it! You just created your (simple) event api. How to use it? Yeah, im gonna show you that now:

    Extra 1 Create a own event by creating a class implementing/extending the Event class/interface you created. In my example it's a block break event:
    Code (Text):
    public class BreakBlockEvent implements Event {

        private Player player;
        private Block block;

        public BreakBlockEvent(Player player, Block block) {
            this.player = player;
            this.block = block;
        }

        public Player getPlayer() {
            return player;
        }

        public Block getBlock() {
            return block;
        }

        private boolean canceled;

        @Override
        public boolean isCanceled() {
            return canceled;
        }

        @Override
        public void setCanceled(boolean canceled) {
            this.canceled = canceled;
        }
    }

    Extra 2 Now create a class, that is implementing the Listener class, there create a method with the Annotation you created.
    Code (Text):
    public class BreakBlockListener implements Listener {

        @EventHandler
        public void onBlockBreak(BreakBlockEvent event) {
            // do something
        }

    }

    Extra 2.1 Now we have to register that Listener:
    Code (Text):
    public static void register(){
            EventManager.registerListener(new BreakBlockListener());
        }

    Extra 2.2 If you want to call that created event, that also very simple:
    Code (Text):
    public static boolean callBlockBreak(Player player, Block block){
            Event event = EventManager.callEvent(new BreakBlockEvent(player, block));
            return event.isCanceled();
        }
    Calling the event will call the method you created in Step E2.

    This is a very simple Event API, that I didn't tested. This resource is pretty much just for learning and not really for using, except you're writing your own Bukkit.
    And with that, thats it! I hope you learned something while reading, and if so please leave a positive rating.
     
  2. This is nice tutorial/ressource. Thanks to sharing.
     
  3. SteelPhoenix

    Moderator

    Note that this implementation does not take concurrency into account.
    Also, you'd be better off sorting handlers by event when they get registered, so calling an event is not as costly.