From 86c5823b0bfdfc21e38850b46bb486c4e10e940c Mon Sep 17 00:00:00 2001 From: Techcable Date: Sun, 28 Feb 2016 22:46:22 -0700 Subject: [PATCH] Use ASM for events, via the Event4J library According to benchmarks, this is about 7 nanoseconds faster than reflection (34 ns -> 27 ns). Event4J falls back to Java 8's new MethodHandle if the event method isn't public. diff --git a/event/pom.xml b/event/pom.xml index b6ef990..ff72600 100644 --- a/event/pom.xml +++ b/event/pom.xml @@ -17,4 +17,24 @@ Waterfall-Event Generic java event dispatching API intended for use with Waterfall. + + + + net.techcable + event4j + 1.1.0 + + + org.ow2.asm + asm-all + 5.0.4 + + + + + + techcable-repo + https://repo.techcable.net/content/groups/public/ + + diff --git a/event/src/main/java/net/md_5/bungee/event/EventBus.java b/event/src/main/java/net/md_5/bungee/event/EventBus.java index 5b5d420..c93aa16 100644 --- a/event/src/main/java/net/md_5/bungee/event/EventBus.java +++ b/event/src/main/java/net/md_5/bungee/event/EventBus.java @@ -1,26 +1,13 @@ package net.md_5.bungee.event; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.text.MessageFormat; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; -import java.util.logging.Level; import java.util.logging.Logger; public class EventBus { - private final Map, Map>> byListenerAndPriority = new HashMap<>(); - private final Map, EventHandlerMethod[]> byEventBaked = new ConcurrentHashMap<>(); - private final Lock lock = new ReentrantLock(); + private final net.techcable.event4j.EventBus event4JBus = net.techcable.event4j.EventBus.builder() + .eventMarker((m) -> m.isAnnotationPresent(EventHandler.class) ? m.getAnnotation(EventHandler.class)::priority : null) + .build(); private final Logger logger; public EventBus() @@ -35,167 +22,16 @@ public class EventBus public void post(Object event) { - EventHandlerMethod[] handlers = byEventBaked.get( event.getClass() ); - - if ( handlers != null ) - { - for ( EventHandlerMethod method : handlers ) - { - try - { - method.invoke( event ); - } catch ( IllegalAccessException ex ) - { - throw new Error( "Method became inaccessible: " + event, ex ); - } catch ( IllegalArgumentException ex ) - { - throw new Error( "Method rejected target/argument: " + event, ex ); - } catch ( InvocationTargetException ex ) - { - logger.log( Level.WARNING, MessageFormat.format( "Error dispatching event {0} to listener {1}", event, method.getListener() ), ex.getCause() ); - } - } - } - } - - private Map, Map>> findHandlers(Object listener) - { - Map, Map>> handler = new HashMap<>(); - for ( Method m : listener.getClass().getDeclaredMethods() ) - { - EventHandler annotation = m.getAnnotation( EventHandler.class ); - if ( annotation != null ) - { - Class[] params = m.getParameterTypes(); - if ( params.length != 1 ) - { - logger.log( Level.INFO, "Method {0} in class {1} annotated with {2} does not have single argument", new Object[] - { - m, listener.getClass(), annotation - } ); - continue; - } - Map> prioritiesMap = handler.get( params[0] ); - if ( prioritiesMap == null ) - { - prioritiesMap = new HashMap<>(); - handler.put( params[0], prioritiesMap ); - } - Set priority = prioritiesMap.get( annotation.priority() ); - if ( priority == null ) - { - priority = new HashSet<>(); - prioritiesMap.put( annotation.priority(), priority ); - } - priority.add( m ); - } - } - return handler; + event4JBus.fire(event); } public void register(Object listener) { - Map, Map>> handler = findHandlers( listener ); - lock.lock(); - try - { - for ( Map.Entry, Map>> e : handler.entrySet() ) - { - Map> prioritiesMap = byListenerAndPriority.get( e.getKey() ); - if ( prioritiesMap == null ) - { - prioritiesMap = new HashMap<>(); - byListenerAndPriority.put( e.getKey(), prioritiesMap ); - } - for ( Map.Entry> entry : e.getValue().entrySet() ) - { - Map currentPriorityMap = prioritiesMap.get( entry.getKey() ); - if ( currentPriorityMap == null ) - { - currentPriorityMap = new HashMap<>(); - prioritiesMap.put( entry.getKey(), currentPriorityMap ); - } - Method[] baked = new Method[ entry.getValue().size() ]; - currentPriorityMap.put( listener, entry.getValue().toArray( baked ) ); - } - bakeHandlers( e.getKey() ); - } - } finally - { - lock.unlock(); - } + event4JBus.register(listener); } public void unregister(Object listener) { - Map, Map>> handler = findHandlers( listener ); - lock.lock(); - try - { - for ( Map.Entry, Map>> e : handler.entrySet() ) - { - Map> prioritiesMap = byListenerAndPriority.get( e.getKey() ); - if ( prioritiesMap != null ) - { - for ( Byte priority : e.getValue().keySet() ) - { - Map currentPriority = prioritiesMap.get( priority ); - if ( currentPriority != null ) - { - currentPriority.remove( listener ); - if ( currentPriority.isEmpty() ) - { - prioritiesMap.remove( priority ); - } - } - } - if ( prioritiesMap.isEmpty() ) - { - byListenerAndPriority.remove( e.getKey() ); - } - } - bakeHandlers( e.getKey() ); - } - } finally - { - lock.unlock(); - } - } - - /** - * Shouldn't be called without first locking the writeLock; intended for use - * only inside {@link #register(java.lang.Object) register(Object)} or - * {@link #unregister(java.lang.Object) unregister(Object)}. - */ - private void bakeHandlers(Class eventClass) - { - Map> handlersByPriority = byListenerAndPriority.get( eventClass ); - if ( handlersByPriority != null ) - { - List handlersList = new ArrayList<>( handlersByPriority.size() * 2 ); - - // Either I'm really tired, or the only way we can iterate between Byte.MIN_VALUE and Byte.MAX_VALUE inclusively, - // with only a byte on the stack is by using a do {} while() format loop. - byte value = Byte.MIN_VALUE; - do - { - Map handlersByListener = handlersByPriority.get( value ); - if ( handlersByListener != null ) - { - for ( Map.Entry listenerHandlers : handlersByListener.entrySet() ) - { - for ( Method method : listenerHandlers.getValue() ) - { - EventHandlerMethod ehm = new EventHandlerMethod( listenerHandlers.getKey(), method ); - handlersList.add( ehm ); - } - } - } - } while ( value++ < Byte.MAX_VALUE ); - byEventBaked.put( eventClass, handlersList.toArray( new EventHandlerMethod[ handlersList.size() ] ) ); - } else - { - byEventBaked.remove( eventClass ); - } + event4JBus.unregister(listener); } } diff --git a/event/src/main/java/net/md_5/bungee/event/EventHandlerMethod.java b/event/src/main/java/net/md_5/bungee/event/EventHandlerMethod.java deleted file mode 100644 index ad19c02..0000000 --- a/event/src/main/java/net/md_5/bungee/event/EventHandlerMethod.java +++ /dev/null @@ -1,21 +0,0 @@ -package net.md_5.bungee.event; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import lombok.AllArgsConstructor; -import lombok.Getter; - -@AllArgsConstructor -public class EventHandlerMethod -{ - - @Getter - private final Object listener; - @Getter - private final Method method; - - public void invoke(Object event) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException - { - method.invoke( listener, event ); - } -} -- 2.8.3