Compare commits

...

6 Commits

Author SHA1 Message Date
Kai S. K. Engelbart cc5c07079a
Merge pull request 'Handler Caching' (#37) from f/handler-caching into develop
zdm/event-bus/pipeline/head This commit looks good Details
Reviewed-on: https://git.kske.dev/zdm/event-bus/pulls/37
Reviewed-by: delvh <leon@kske.dev>
2022-01-18 17:11:38 +01:00
Kai S. K. Engelbart 8fae4f6d76
Remove print statements from test
zdm/event-bus/pipeline/head This commit looks good Details
2022-01-18 17:09:21 +01:00
Kai S. K. Engelbart 2d276a1d74
Compare listener using equals() during removal 2022-01-18 17:09:05 +01:00
Kai S. K. Engelbart 8609c6a90c
Simplify binding cache usage
zdm/event-bus/pipeline/head This commit looks good Details
2022-01-18 15:00:18 +01:00
Kai S. K. Engelbart ee9d08b2b8
Test binding cache
zdm/event-bus/pipeline/head This commit looks good Details
2022-01-18 13:44:33 +01:00
Kai S. K. Engelbart 5468bddb35
Add handler cache
zdm/event-bus/pipeline/head This commit looks good Details
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.
2022-01-14 15:44:21 +01:00
3 changed files with 82 additions and 32 deletions

View File

@ -90,6 +90,16 @@ public final class EventBus {
*/
private final Map<Class<?>, TreeSet<EventHandler>> 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<Class<?>, TreeSet<EventHandler>> bindingCache = new ConcurrentHashMap<>();
/**
* Stores all registered event listeners (which declare event handlers) and prevents them from
* being garbage collected.
@ -175,24 +185,29 @@ 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<EventHandler> getHandlersFor(Class<?> eventType) {
return bindingCache.computeIfAbsent(eventType, k -> {
// Get handlers defined for the event class
TreeSet<EventHandler> handlers =
bindings.getOrDefault(eventType, new TreeSet<>(byPriority));
// Get handlers defined for the event class
TreeSet<EventHandler> 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;
return handlers;
});
}
/**
@ -369,15 +384,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) {
bindings.putIfAbsent(handler.getEventType(), new TreeSet<>(byPriority));
// Bind handler
logger.log(Level.DEBUG, "Binding event handler {0}", handler);
bindings.get(handler.getEventType()).add(handler);
bindings.computeIfAbsent(handler.getEventType(), k -> new TreeSet<>(byPriority))
.add(handler);
// Insert handler into cache
bindingCache.computeIfAbsent(handler.getEventType(), k -> new TreeSet<>(byPriority))
.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);
}
/**
@ -395,13 +423,25 @@ public final class EventBus {
var it = binding.iterator();
while (it.hasNext()) {
var handler = it.next();
if (handler.getListener() == listener) {
if (handler.getListener().equals(listener)) {
logger.log(Level.DEBUG, "Unbinding event handler {0}", handler);
it.remove();
}
}
}
// Remove bindings from cache
for (var binding : bindingCache.values()) {
var it = binding.iterator();
while (it.hasNext()) {
var handler = it.next();
if (handler.getListener().equals(listener)) {
logger.log(Level.TRACE, "Removing event handler {0} from cache", handler);
it.remove();
}
}
}
// Remove the listener itself
registeredListeners.remove(listener);
}
@ -414,6 +454,7 @@ public final class EventBus {
public void clearListeners() {
logger.log(Level.INFO, "Clearing event listeners");
bindings.clear();
bindingCache.clear();
registeredListeners.clear();
}

View File

@ -14,8 +14,7 @@ import org.junit.jupiter.api.*;
@Priority(150)
class DispatchTest {
EventBus bus;
static int hits;
EventBus bus;
/**
* Constructs an event bus and registers this test instance as an event listener.
@ -27,8 +26,8 @@ class DispatchTest {
bus = new EventBus();
bus.registerListener(this);
bus.registerListener(SimpleEvent.class, e -> {
++hits;
assertEquals(4, hits);
e.increment();
assertEquals(3, e.getCounter());
});
}
@ -51,29 +50,39 @@ class DispatchTest {
*/
@Test
void testDebugExecutionOrder() {
String executionOrder = bus.debugExecutionOrder(SimpleEvent.class);
System.out.println(executionOrder);
assertEquals(
"Event handler execution order for class dev.kske.eventbus.core.SimpleEvent (3 handler(s)):\n"
+ "==========================================================================================\n"
+ "ReflectiveEventHandler[eventType=class dev.kske.eventbus.core.SimpleEvent, polymorphic=true, priority=200, method=void dev.kske.eventbus.core.DispatchTest.onSimpleEventFirst(), useParameter=false]\n"
+ "ReflectiveEventHandler[eventType=class dev.kske.eventbus.core.SimpleEvent, polymorphic=false, priority=150, method=static void dev.kske.eventbus.core.DispatchTest.onSimpleEventSecond(), useParameter=false]\n"
+ "ReflectiveEventHandler[eventType=class dev.kske.eventbus.core.SimpleEvent, polymorphic=true, priority=200, method=void dev.kske.eventbus.core.DispatchTest.onSimpleEventFirst(dev.kske.eventbus.core.SimpleEvent), useParameter=true]\n"
+ "ReflectiveEventHandler[eventType=class dev.kske.eventbus.core.SimpleEvent, polymorphic=false, priority=150, method=static void dev.kske.eventbus.core.DispatchTest.onSimpleEventSecond(dev.kske.eventbus.core.SimpleEvent), useParameter=true]\n"
+ "CallbackEventHandler[eventType=class dev.kske.eventbus.core.SimpleEvent, polymorphic=false, priority=100]\n"
+ "==========================================================================================",
executionOrder);
bus.debugExecutionOrder(SimpleEvent.class));
}
@Event(SimpleEvent.class)
/**
* Tests whether the handlers bound to an event type are correct when retrieved from the binding
* cache. On the second call of {@link EventBus#debugExecutionOrder(Class)} the cache is used.
*
* @since 1.3.0
*/
@Test
void testBindingCache() {
assertEquals(bus.debugExecutionOrder(SimpleEventSub.class),
bus.debugExecutionOrder(SimpleEventSub.class));
}
@Event
@Priority(200)
void onSimpleEventFirst() {
++hits;
assertTrue(hits == 1 || hits == 2);
void onSimpleEventFirst(SimpleEvent event) {
event.increment();
assertTrue(event.getCounter() == 1 || event.getCounter() == 2);
}
@Event(SimpleEvent.class)
@Event
@Polymorphic(false)
static void onSimpleEventSecond() {
++hits;
assertEquals(3, hits);
static void onSimpleEventSecond(SimpleEvent event) {
event.increment();
assertEquals(2, event.getCounter());
}
}

View File

@ -14,7 +14,7 @@ class SimpleEvent {
@Override
public String toString() {
return String.format("SimpleEvent[%d]", counter);
return String.format("%s[%d]", getClass().getSimpleName(), counter);
}
void increment() {