From 5468bddb3568422c623d2ee08728cc268877bd1c Mon Sep 17 00:00:00 2001 From: kske Date: Fri, 14 Jan 2022 15:44:21 +0100 Subject: [PATCH] Add handler cache The cache has the same structure as the bindings and is updated accordingly. To ensure the correctness and efficiency of the cache, more testing has to be conducted. --- .../java/dev/kske/eventbus/core/EventBus.java | 65 +++++++++++++++---- 1 file changed, 54 insertions(+), 11 deletions(-) diff --git a/core/src/main/java/dev/kske/eventbus/core/EventBus.java b/core/src/main/java/dev/kske/eventbus/core/EventBus.java index 334dd52..9f66c0c 100644 --- a/core/src/main/java/dev/kske/eventbus/core/EventBus.java +++ b/core/src/main/java/dev/kske/eventbus/core/EventBus.java @@ -90,6 +90,16 @@ public final class EventBus { */ private final Map, TreeSet> bindings = new ConcurrentHashMap<>(); + /** + * A cache mapping an event class to all handlers the event should be dispatched to. This + * includes polymorphic handlers that don't reference the event class explicitly. If an event + * class is not contained inside this cache, the {@link #bindings} have to be traversed manually + * in search of applicable handlers. + * + * @since 1.3.0 + */ + private final Map, TreeSet> bindingCache = new ConcurrentHashMap<>(); + /** * Stores all registered event listeners (which declare event handlers) and prevents them from * being garbage collected. @@ -175,24 +185,32 @@ public final class EventBus { * Searches for the event handlers bound to an event class. This includes polymorphic handlers * that are bound to a supertype of the event class. * + * @implNote If the given event type was requested in the past, the handlers are retrieved from + * the {@link #bindingCache}. If not, the entire {@link #bindings} are traversed in + * search of polymorphic handlers compatible with the event type. * @param eventType the event type to use for the search * @return a navigable set containing the applicable handlers in descending order of priority * @since 1.2.0 */ private NavigableSet getHandlersFor(Class eventType) { + if (bindingCache.containsKey(eventType)) { + return bindingCache.get(eventType); + } else { - // Get handlers defined for the event class - TreeSet handlers = - bindings.getOrDefault(eventType, new TreeSet<>(byPriority)); + // Get handlers defined for the event class + TreeSet handlers = + bindings.getOrDefault(eventType, new TreeSet<>(byPriority)); - // Get polymorphic handlers - for (var binding : bindings.entrySet()) - if (binding.getKey().isAssignableFrom(eventType)) - for (var handler : binding.getValue()) - if (handler.isPolymorphic()) - handlers.add(handler); + // Get polymorphic handlers + for (var binding : bindings.entrySet()) + if (binding.getKey().isAssignableFrom(eventType)) + for (var handler : binding.getValue()) + if (handler.isPolymorphic()) + handlers.add(handler); - return handlers; + bindingCache.put(eventType, handlers); + return handlers; + } } /** @@ -369,15 +387,28 @@ public final class EventBus { } /** - * Inserts a new handler into the {@link #bindings} map. + * Inserts a new handler into the {@link #bindings} map. Additionally, the handler is placed + * inside the {@link #bindingCache} where applicable. * * @param handler the handler to bind * @since 1.2.0 */ private void bindHandler(EventHandler handler) { + + // Bind handler bindings.putIfAbsent(handler.getEventType(), new TreeSet<>(byPriority)); logger.log(Level.DEBUG, "Binding event handler {0}", handler); bindings.get(handler.getEventType()).add(handler); + + // Insert handler into cache + bindingCache.putIfAbsent(handler.getEventType(), new TreeSet<>(byPriority)); + bindingCache.get(handler.getEventType()).add(handler); + + // Handler is polymorphic => insert where applicable + if (handler.isPolymorphic()) + for (var binding : bindingCache.entrySet()) + if (binding.getKey().isAssignableFrom(handler.getEventType())) + binding.getValue().add(handler); } /** @@ -402,6 +433,18 @@ public final class EventBus { } } + // Remove bindings from cache + for (var binding : bindingCache.values()) { + var it = binding.iterator(); + while (it.hasNext()) { + var handler = it.next(); + if (handler.getListener() == listener) { + logger.log(Level.TRACE, "Removing event handler {0} from cache", handler); + it.remove(); + } + } + } + // Remove the listener itself registeredListeners.remove(listener); }