From b4fbfe572d7b815a79c9795bba54f1d446b61f01 Mon Sep 17 00:00:00 2001 From: TheMode Date: Thu, 19 Aug 2021 06:45:23 +0200 Subject: [PATCH 01/24] Initial ListenerHandle implementation --- .../net/minestom/server/event/EventNode.java | 8 +- .../minestom/server/event/EventNodeImpl.java | 226 +++++------------- .../minestom/server/event/ListenerHandle.java | 7 + 3 files changed, 78 insertions(+), 163 deletions(-) create mode 100644 src/main/java/net/minestom/server/event/ListenerHandle.java diff --git a/src/main/java/net/minestom/server/event/EventNode.java b/src/main/java/net/minestom/server/event/EventNode.java index 83bf2440c..9cd4c8ae2 100644 --- a/src/main/java/net/minestom/server/event/EventNode.java +++ b/src/main/java/net/minestom/server/event/EventNode.java @@ -190,7 +190,13 @@ public interface EventNode { * * @param event the event to execute */ - void call(@NotNull T event); + default void call(@NotNull T event) { + call(event, getHandle((Class) event.getClass())); + } + + void call(@NotNull E event, ListenerHandle handle); + + ListenerHandle getHandle(Class handleType); /** * Execute a cancellable event with a callback to execute if the event is successful. diff --git a/src/main/java/net/minestom/server/event/EventNodeImpl.java b/src/main/java/net/minestom/server/event/EventNodeImpl.java index ad659117e..0cc36ba86 100644 --- a/src/main/java/net/minestom/server/event/EventNodeImpl.java +++ b/src/main/java/net/minestom/server/event/EventNodeImpl.java @@ -1,6 +1,5 @@ package net.minestom.server.event; -import net.minestom.server.MinecraftServer; import net.minestom.server.utils.validate.Check; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; @@ -10,16 +9,14 @@ import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArraySet; -import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; import java.util.function.BiPredicate; import java.util.function.Consumer; -import java.util.function.IntUnaryOperator; -import java.util.stream.Collectors; class EventNodeImpl implements EventNode { private static final Object GLOBAL_CHILD_LOCK = new Object(); private final Object lock = new Object(); + private final Map, Handle> handleMap = new ConcurrentHashMap<>(); private final Map, ListenerEntry> listenerMap = new ConcurrentHashMap<>(); private final Set> children = new CopyOnWriteArraySet<>(); private final Map> mappedNodeCache = new WeakHashMap<>(); @@ -41,29 +38,38 @@ class EventNodeImpl implements EventNode { } @Override - public void call(@NotNull T event) { - final var eventClass = event.getClass(); - if (!eventType.isAssignableFrom(eventClass)) return; // Invalid event type - // Conditions - if (predicate != null) { - try { - final var value = filter.getHandler(event); - if (!predicate.test(event, value)) return; - } catch (Exception e) { - MinecraftServer.getExceptionManager().handleException(e); - return; + public void call(@NotNull E event, ListenerHandle handle) { + var castedHandle = (Handle) handle; + Check.stateCondition(castedHandle.node != this, "Invalid handle owner"); + if (!castedHandle.updated) { + handle(castedHandle); + castedHandle.updated = true; + } + List> listeners = castedHandle.listeners; + if (listeners.isEmpty()) return; + for (Consumer listener : listeners) { + listener.accept(event); + } + } + + @Override + public ListenerHandle getHandle(Class handleType) { + return (ListenerHandle) handleMap.computeIfAbsent(handleType, + aClass -> new Handle<>(this, (Class) aClass)); + } + + private void handle(Handle handle) { + ListenerEntry entry = listenerMap.get(handle.eventType); + if (entry != null) { + for (var listener : entry.listeners) { + handle.listeners.add(listener::run); } } - // Process listeners list - final var entry = listenerMap.get(eventClass); - if (entry == null) return; // No listener nor children - entry.call(event); - // Process children - if (entry.childCount > 0) { - this.children.stream() - .sorted(Comparator.comparing(EventNode::getPriority)) - .forEach(child -> child.call(event)); - } + // Add children + if (children.isEmpty()) return; + this.children.stream() + .sorted(Comparator.comparing(EventNode::getPriority)) + .forEach(child -> ((EventNodeImpl) child).handle(handle)); } @Override @@ -127,11 +133,9 @@ class EventNodeImpl implements EventNode { Check.stateCondition(childImpl.parent != null, "Node already has a parent"); Check.stateCondition(Objects.equals(parent, child), "Cannot have a child as parent"); final boolean result = this.children.add((EventNodeImpl) childImpl); - if (result) { - childImpl.parent = this; - // Increase listener count - propagateNode(childImpl, IntUnaryOperator.identity()); - } + if (!result) return this; + childImpl.parent = this; + childImpl.propagateEvents(); // Propagate after setting the parent } return this; } @@ -140,12 +144,10 @@ class EventNodeImpl implements EventNode { public @NotNull EventNode removeChild(@NotNull EventNode child) { synchronized (GLOBAL_CHILD_LOCK) { final boolean result = this.children.remove(child); - if (result) { - final var childImpl = (EventNodeImpl) child; - childImpl.parent = null; - // Decrease listener count - propagateNode(childImpl, count -> -count); - } + if (!result) return this; + final var childImpl = (EventNodeImpl) child; + childImpl.propagateEvents(); // Propagate before removing the parent + childImpl.parent = null; } return this; } @@ -156,7 +158,7 @@ class EventNodeImpl implements EventNode { final var eventType = listener.getEventType(); var entry = getEntry(eventType); entry.listeners.add((EventListener) listener); - propagateToParent(eventType, 1); + propagateEvent(eventType); } return this; } @@ -169,69 +171,30 @@ class EventNodeImpl implements EventNode { if (entry == null) return this; var listeners = entry.listeners; final boolean removed = listeners.remove(listener); - if (removed) propagateToParent(eventType, -1); + if (removed) propagateEvent(eventType); } return this; } @Override public void map(@NotNull EventNode node, @NotNull Object value) { - final var nodeImpl = (EventNodeImpl) node; - final var valueType = value.getClass(); - synchronized (GLOBAL_CHILD_LOCK) { - nodeImpl.listenerMap.forEach((type, listenerEntry) -> { - final var entry = getEntry(type); - final boolean correct = entry.filters.stream().anyMatch(eventFilter -> { - final var handlerType = eventFilter.handlerType(); - return handlerType != null && handlerType.isAssignableFrom(valueType); - }); - Check.stateCondition(!correct, "The node filter {0} is not compatible with type {1}", nodeImpl.eventType, valueType); - synchronized (mappedNodeCache) { - entry.mappedNode.put(value, (EventNode) nodeImpl); - mappedNodeCache.put(value, entry); - // TODO propagate - } - }); - } + // TODO } @Override public boolean unmap(@NotNull Object value) { - synchronized (GLOBAL_CHILD_LOCK) { - synchronized (mappedNodeCache) { - var entry = mappedNodeCache.remove(value); - if (entry == null) return false; - final EventNode previousNode = entry.mappedNode.remove(value); - if (previousNode != null) { - // TODO propagate - return true; - } - return false; - } - } + // TODO + return false; } @Override public void register(@NotNull EventBinding binding) { - synchronized (GLOBAL_CHILD_LOCK) { - for (var eventType : binding.eventTypes()) { - var entry = getEntry((Class) eventType); - final boolean added = entry.bindingConsumers.add((Consumer) binding.consumer(eventType)); - if (added) propagateToParent((Class) eventType, 1); - } - } + // TODO } @Override public void unregister(@NotNull EventBinding binding) { - synchronized (GLOBAL_CHILD_LOCK) { - for (var eventType : binding.eventTypes()) { - var entry = listenerMap.get(eventType); - if (entry == null) return; - final boolean removed = entry.bindingConsumers.remove(binding.consumer(eventType)); - if (removed) propagateToParent((Class) eventType, -1); - } - } + // TODO } @Override @@ -260,42 +223,21 @@ class EventNodeImpl implements EventNode { return parent; } - private void propagateChildCountChange(Class eventClass, int count) { - var entry = getEntry(eventClass); - final int result = ListenerEntry.CHILD_UPDATER.addAndGet(entry, count); - if (result == 0 && entry.listeners.isEmpty()) { - this.listenerMap.remove(eventClass); - } else if (result < 0) { - throw new IllegalStateException("Something wrong happened, listener count: " + result); - } - if (parent != null) { - parent.propagateChildCountChange(eventClass, count); - } + private void propagateEvents() { + this.listenerMap.forEach((eventClass, eventListeners) -> propagateEvent(eventClass)); } - private void propagateToParent(Class eventClass, int count) { + private void propagateEvent(Class eventClass) { final var parent = this.parent; - if (parent != null) { - synchronized (parent.lock) { - parent.propagateChildCountChange(eventClass, count); - } - } - } - - private void propagateNode(EventNodeImpl child, IntUnaryOperator operator) { - synchronized (lock) { - final var listeners = child.listenerMap; - listeners.forEach((eventClass, eventListeners) -> { - final var entry = listeners.get(eventClass); - if (entry == null) return; - final int childCount = entry.listeners.size() + entry.childCount; - propagateChildCountChange(eventClass, operator.applyAsInt(childCount)); - }); - } + if (parent == null) return; + var handle = parent.handleMap.get(eventClass); + if (handle == null) return; + handle.updated = false; + parent.propagateEvent(eventClass); } private ListenerEntry getEntry(Class type) { - return listenerMap.computeIfAbsent(type, aClass -> new ListenerEntry<>(this, (Class) aClass)); + return listenerMap.computeIfAbsent(type, aClass -> new ListenerEntry<>()); } private static boolean equals(EventNode node, String name, Class eventType) { @@ -305,59 +247,19 @@ class EventNodeImpl implements EventNode { } private static class ListenerEntry { - private static final List> FILTERS = List.of( - EventFilter.ENTITY, - EventFilter.ITEM, EventFilter.INSTANCE, - EventFilter.INVENTORY, EventFilter.BLOCK); - @SuppressWarnings("rawtypes") - private static final AtomicIntegerFieldUpdater CHILD_UPDATER = - AtomicIntegerFieldUpdater.newUpdater(ListenerEntry.class, "childCount"); - - final EventNodeImpl node; - final List> filters; final List> listeners = new CopyOnWriteArrayList<>(); - final Set> bindingConsumers = new CopyOnWriteArraySet<>(); - final Map> mappedNode = new WeakHashMap<>(); - volatile int childCount; + } - ListenerEntry(EventNodeImpl node, Class eventType) { + private static final class Handle implements ListenerHandle { + private final EventNode node; + private final Class eventType; + private final List> listeners = new CopyOnWriteArrayList<>(); + + private volatile boolean updated; + + Handle(EventNode node, Class eventType) { this.node = node; - this.filters = FILTERS.stream().filter(eventFilter -> eventFilter.eventType().isAssignableFrom(eventType)).collect(Collectors.toList()); - } - - void call(T event) { - // Event interfaces - if (!bindingConsumers.isEmpty()) { - for (var consumer : bindingConsumers) { - consumer.accept(event); - } - } - // Mapped listeners - if (!mappedNode.isEmpty()) { - synchronized (node.mappedNodeCache) { - // Check mapped listeners for each individual event handler - for (var filter : filters) { - final var handler = filter.castHandler(event); - final var map = mappedNode.get(handler); - if (map != null) map.call(event); - } - } - } - // Basic listeners - if (!listeners.isEmpty()) { - for (EventListener listener : listeners) { - EventListener.Result result; - try { - result = listener.run(event); - } catch (Exception e) { - result = EventListener.Result.EXCEPTION; - MinecraftServer.getExceptionManager().handleException(e); - } - if (result == EventListener.Result.EXPIRED) { - listeners.remove(listener); - } - } - } + this.eventType = eventType; } } } diff --git a/src/main/java/net/minestom/server/event/ListenerHandle.java b/src/main/java/net/minestom/server/event/ListenerHandle.java new file mode 100644 index 000000000..84afc58f0 --- /dev/null +++ b/src/main/java/net/minestom/server/event/ListenerHandle.java @@ -0,0 +1,7 @@ +package net.minestom.server.event; + +import org.jetbrains.annotations.ApiStatus; + +@ApiStatus.NonExtendable +public interface ListenerHandle { +} From 43fc7ad6247400a352f5d0e35daab21c0095096e Mon Sep 17 00:00:00 2001 From: TheMode Date: Thu, 19 Aug 2021 07:12:53 +0200 Subject: [PATCH 02/24] Add support for EventBinding --- .../minestom/server/event/EventNodeImpl.java | 38 ++++++++++++++----- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/src/main/java/net/minestom/server/event/EventNodeImpl.java b/src/main/java/net/minestom/server/event/EventNodeImpl.java index 0cc36ba86..502b587d1 100644 --- a/src/main/java/net/minestom/server/event/EventNodeImpl.java +++ b/src/main/java/net/minestom/server/event/EventNodeImpl.java @@ -41,11 +41,14 @@ class EventNodeImpl implements EventNode { public void call(@NotNull E event, ListenerHandle handle) { var castedHandle = (Handle) handle; Check.stateCondition(castedHandle.node != this, "Invalid handle owner"); + List> listeners = castedHandle.listeners; if (!castedHandle.updated) { - handle(castedHandle); + listeners.clear(); + synchronized (GLOBAL_CHILD_LOCK) { + handle(castedHandle); + } castedHandle.updated = true; } - List> listeners = castedHandle.listeners; if (listeners.isEmpty()) return; for (Consumer listener : listeners) { listener.accept(event); @@ -61,9 +64,13 @@ class EventNodeImpl implements EventNode { private void handle(Handle handle) { ListenerEntry entry = listenerMap.get(handle.eventType); if (entry != null) { + // Add normal listeners for (var listener : entry.listeners) { handle.listeners.add(listener::run); } + // Add bindings + handle.listeners.addAll(entry.bindingConsumers); + // TODO mapped node } // Add children if (children.isEmpty()) return; @@ -189,12 +196,25 @@ class EventNodeImpl implements EventNode { @Override public void register(@NotNull EventBinding binding) { - // TODO + synchronized (GLOBAL_CHILD_LOCK) { + for (var eventType : binding.eventTypes()) { + ListenerEntry entry = getEntry((Class) eventType); + final boolean added = entry.bindingConsumers.add((Consumer) binding.consumer(eventType)); + if (added) propagateEvent((Class) eventType); + } + } } @Override public void unregister(@NotNull EventBinding binding) { - // TODO + synchronized (GLOBAL_CHILD_LOCK) { + for (var eventType : binding.eventTypes()) { + ListenerEntry entry = listenerMap.get(eventType); + if (entry == null) return; + final boolean removed = entry.bindingConsumers.remove(binding.consumer(eventType)); + if (removed) propagateEvent((Class) eventType); + } + } } @Override @@ -224,15 +244,15 @@ class EventNodeImpl implements EventNode { } private void propagateEvents() { - this.listenerMap.forEach((eventClass, eventListeners) -> propagateEvent(eventClass)); + this.listenerMap.keySet().forEach(this::propagateEvent); } private void propagateEvent(Class eventClass) { final var parent = this.parent; if (parent == null) return; - var handle = parent.handleMap.get(eventClass); - if (handle == null) return; - handle.updated = false; + Handle parentHandle = parent.handleMap.get(eventClass); + if (parentHandle == null) return; + parentHandle.updated = false; parent.propagateEvent(eventClass); } @@ -248,13 +268,13 @@ class EventNodeImpl implements EventNode { private static class ListenerEntry { final List> listeners = new CopyOnWriteArrayList<>(); + final Set> bindingConsumers = new CopyOnWriteArraySet<>(); } private static final class Handle implements ListenerHandle { private final EventNode node; private final Class eventType; private final List> listeners = new CopyOnWriteArrayList<>(); - private volatile boolean updated; Handle(EventNode node, Class eventType) { From 044849b5ac8ea93a6d7c15e23d7a114db44591b7 Mon Sep 17 00:00:00 2001 From: TheMode Date: Thu, 19 Aug 2021 22:21:51 +0200 Subject: [PATCH 03/24] Add support for node predicate, fast exit when the node type is incompatible --- .../minestom/server/event/EventNodeImpl.java | 26 +++++++++++++------ 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/src/main/java/net/minestom/server/event/EventNodeImpl.java b/src/main/java/net/minestom/server/event/EventNodeImpl.java index 502b587d1..0805d605b 100644 --- a/src/main/java/net/minestom/server/event/EventNodeImpl.java +++ b/src/main/java/net/minestom/server/event/EventNodeImpl.java @@ -14,11 +14,10 @@ import java.util.function.Consumer; class EventNodeImpl implements EventNode { private static final Object GLOBAL_CHILD_LOCK = new Object(); - private final Object lock = new Object(); private final Map, Handle> handleMap = new ConcurrentHashMap<>(); private final Map, ListenerEntry> listenerMap = new ConcurrentHashMap<>(); - private final Set> children = new CopyOnWriteArraySet<>(); + private final Set> children = new CopyOnWriteArraySet<>(); private final Map> mappedNodeCache = new WeakHashMap<>(); private final String name; @@ -45,7 +44,7 @@ class EventNodeImpl implements EventNode { if (!castedHandle.updated) { listeners.clear(); synchronized (GLOBAL_CHILD_LOCK) { - handle(castedHandle); + handle(castedHandle.eventType, castedHandle.listeners); } castedHandle.updated = true; } @@ -61,22 +60,33 @@ class EventNodeImpl implements EventNode { aClass -> new Handle<>(this, (Class) aClass)); } - private void handle(Handle handle) { - ListenerEntry entry = listenerMap.get(handle.eventType); + private void handle(Class handleType, List> listeners) { + ListenerEntry entry = listenerMap.get(handleType); if (entry != null) { // Add normal listeners for (var listener : entry.listeners) { - handle.listeners.add(listener::run); + if (predicate != null) { + // Ensure that the event is valid before running + listeners.add(event -> { + final var value = filter.getHandler(event); + if (!predicate.test(event, value)) return; + listener.run(event); + }); + } else { + // No predicate, run directly + listeners.add(listener::run); + } } // Add bindings - handle.listeners.addAll(entry.bindingConsumers); + listeners.addAll(entry.bindingConsumers); // TODO mapped node } // Add children if (children.isEmpty()) return; this.children.stream() .sorted(Comparator.comparing(EventNode::getPriority)) - .forEach(child -> ((EventNodeImpl) child).handle(handle)); + .filter(child -> child.eventType.isAssignableFrom(handleType)) // Invalid event type + .forEach(child -> child.handle(handleType, listeners)); } @Override From 46d2542d59bb89a25deaabd5bbc4ef55883a80ef Mon Sep 17 00:00:00 2001 From: TheMode Date: Thu, 19 Aug 2021 22:53:20 +0200 Subject: [PATCH 04/24] Rename EventListener#eventType --- src/main/java/net/minestom/server/event/EventListener.java | 4 ++-- src/main/java/net/minestom/server/event/EventNodeImpl.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/net/minestom/server/event/EventListener.java b/src/main/java/net/minestom/server/event/EventListener.java index 83271d2ff..7dc6a0bd5 100644 --- a/src/main/java/net/minestom/server/event/EventListener.java +++ b/src/main/java/net/minestom/server/event/EventListener.java @@ -19,7 +19,7 @@ import java.util.function.Predicate; */ public interface EventListener { - @NotNull Class getEventType(); + @NotNull Class eventType(); @NotNull Result run(@NotNull T event); @@ -122,7 +122,7 @@ public interface EventListener { final var handler = this.handler; return new EventListener<>() { @Override - public @NotNull Class getEventType() { + public @NotNull Class eventType() { return eventType; } diff --git a/src/main/java/net/minestom/server/event/EventNodeImpl.java b/src/main/java/net/minestom/server/event/EventNodeImpl.java index 0805d605b..f4c2dd926 100644 --- a/src/main/java/net/minestom/server/event/EventNodeImpl.java +++ b/src/main/java/net/minestom/server/event/EventNodeImpl.java @@ -172,7 +172,7 @@ class EventNodeImpl implements EventNode { @Override public @NotNull EventNode addListener(@NotNull EventListener listener) { synchronized (GLOBAL_CHILD_LOCK) { - final var eventType = listener.getEventType(); + final var eventType = listener.eventType(); var entry = getEntry(eventType); entry.listeners.add((EventListener) listener); propagateEvent(eventType); @@ -183,7 +183,7 @@ class EventNodeImpl implements EventNode { @Override public @NotNull EventNode removeListener(@NotNull EventListener listener) { synchronized (GLOBAL_CHILD_LOCK) { - final var eventType = listener.getEventType(); + final var eventType = listener.eventType(); var entry = listenerMap.get(eventType); if (entry == null) return this; var listeners = entry.listeners; From 04be72c42979b6da909b51108cc539f58ab5860c Mon Sep 17 00:00:00 2001 From: TheMode Date: Fri, 20 Aug 2021 03:00:25 +0200 Subject: [PATCH 05/24] Support listener expiration --- .../minestom/server/event/EventNodeImpl.java | 84 ++++++++++++------- 1 file changed, 52 insertions(+), 32 deletions(-) diff --git a/src/main/java/net/minestom/server/event/EventNodeImpl.java b/src/main/java/net/minestom/server/event/EventNodeImpl.java index f4c2dd926..e5fe1d960 100644 --- a/src/main/java/net/minestom/server/event/EventNodeImpl.java +++ b/src/main/java/net/minestom/server/event/EventNodeImpl.java @@ -1,5 +1,6 @@ package net.minestom.server.event; +import net.minestom.server.MinecraftServer; import net.minestom.server.utils.validate.Check; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; @@ -42,11 +43,11 @@ class EventNodeImpl implements EventNode { Check.stateCondition(castedHandle.node != this, "Invalid handle owner"); List> listeners = castedHandle.listeners; if (!castedHandle.updated) { - listeners.clear(); synchronized (GLOBAL_CHILD_LOCK) { - handle(castedHandle.eventType, castedHandle.listeners); + listeners.clear(); + castedHandle.update(this); + castedHandle.updated = true; } - castedHandle.updated = true; } if (listeners.isEmpty()) return; for (Consumer listener : listeners) { @@ -60,35 +61,6 @@ class EventNodeImpl implements EventNode { aClass -> new Handle<>(this, (Class) aClass)); } - private void handle(Class handleType, List> listeners) { - ListenerEntry entry = listenerMap.get(handleType); - if (entry != null) { - // Add normal listeners - for (var listener : entry.listeners) { - if (predicate != null) { - // Ensure that the event is valid before running - listeners.add(event -> { - final var value = filter.getHandler(event); - if (!predicate.test(event, value)) return; - listener.run(event); - }); - } else { - // No predicate, run directly - listeners.add(listener::run); - } - } - // Add bindings - listeners.addAll(entry.bindingConsumers); - // TODO mapped node - } - // Add children - if (children.isEmpty()) return; - this.children.stream() - .sorted(Comparator.comparing(EventNode::getPriority)) - .filter(child -> child.eventType.isAssignableFrom(handleType)) // Invalid event type - .forEach(child -> child.handle(handleType, listeners)); - } - @Override public @NotNull List> findChildren(@NotNull String name, Class eventType) { synchronized (GLOBAL_CHILD_LOCK) { @@ -291,5 +263,53 @@ class EventNodeImpl implements EventNode { this.node = node; this.eventType = eventType; } + + void update(EventNodeImpl targetNode) { + final var handleType = eventType; + ListenerEntry entry = targetNode.listenerMap.get(handleType); + if (entry != null) appendEntry(listeners, entry, targetNode); + // Add children + final var children = targetNode.children; + if (children.isEmpty()) return; + children.stream() + .filter(child -> child.eventType.isAssignableFrom(handleType)) // Invalid event type + .sorted(Comparator.comparing(EventNode::getPriority)) + .forEach(this::update); + } + + static void appendEntry(List> handleListeners, ListenerEntry entry, EventNodeImpl targetNode) { + final var filter = targetNode.filter; + final var predicate = targetNode.predicate; + // Add normal listeners + for (var listener : entry.listeners) { + if (predicate != null) { + // Ensure that the event is valid before running + handleListeners.add(e -> { + final var value = filter.getHandler(e); + if (!predicate.test(e, value)) return; + callListener(targetNode, listener, e); + }); + } else { + // No predicate, run directly + handleListeners.add(e -> callListener(targetNode, listener, e)); + } + } + // Add bindings + handleListeners.addAll(entry.bindingConsumers); + // TODO mapped node + } + + static void callListener(EventNodeImpl targetNode, EventListener listener, E event) { + EventListener.Result result; + try { + result = listener.run(event); + } catch (Exception e) { + result = EventListener.Result.EXCEPTION; + MinecraftServer.getExceptionManager().handleException(e); + } + if (result == EventListener.Result.EXPIRED) { + targetNode.removeListener(listener); + } + } } } From 88a3a7d37c5456ec190719c7f2b73c417e64158f Mon Sep 17 00:00:00 2001 From: TheMode Date: Fri, 20 Aug 2021 05:21:53 +0200 Subject: [PATCH 06/24] Update doc --- .../net/minestom/server/event/EventNode.java | 31 ++++++++++++++----- .../minestom/server/event/EventNodeImpl.java | 7 +++-- 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/src/main/java/net/minestom/server/event/EventNode.java b/src/main/java/net/minestom/server/event/EventNode.java index 9cd4c8ae2..26a00a011 100644 --- a/src/main/java/net/minestom/server/event/EventNode.java +++ b/src/main/java/net/minestom/server/event/EventNode.java @@ -182,21 +182,36 @@ public interface EventNode { } /** - * Executes the given event on this node. The event must pass all conditions before - * it will be forwarded to the listeners. - *

- * Calling an event on a node will execute all child nodes, however, an event may be - * called anywhere on the event graph and it will propagate down from there only. + * Calls an event starting from this node. * - * @param event the event to execute + * @param event the event to call */ default void call(@NotNull T event) { + //noinspection unchecked call(event, getHandle((Class) event.getClass())); } - void call(@NotNull E event, ListenerHandle handle); + /** + * Calls an event starting from this node. + *

+ * The event handle can be retrieved using {@link #getHandle(Class)} + * and is useful to avoid map lookups for high-frequency events. + * + * @param event the event to call + * @param handle the event handle linked to this node + * @param the event type + * @throws IllegalArgumentException if {@param handle}'s owner is not {@code this} + */ + void call(@NotNull E event, @NotNull ListenerHandle handle); - ListenerHandle getHandle(Class handleType); + /** + * Gets the handle of an event type. + * + * @param handleType the handle type + * @param the event type + * @return the handle linked to {@param handleType} + */ + @NotNull ListenerHandle getHandle(@NotNull Class handleType); /** * Execute a cancellable event with a callback to execute if the event is successful. diff --git a/src/main/java/net/minestom/server/event/EventNodeImpl.java b/src/main/java/net/minestom/server/event/EventNodeImpl.java index e5fe1d960..09edc9f94 100644 --- a/src/main/java/net/minestom/server/event/EventNodeImpl.java +++ b/src/main/java/net/minestom/server/event/EventNodeImpl.java @@ -38,9 +38,9 @@ class EventNodeImpl implements EventNode { } @Override - public void call(@NotNull E event, ListenerHandle handle) { + public void call(@NotNull E event, @NotNull ListenerHandle handle) { var castedHandle = (Handle) handle; - Check.stateCondition(castedHandle.node != this, "Invalid handle owner"); + Check.argCondition(castedHandle.node != this, "Invalid handle owner"); List> listeners = castedHandle.listeners; if (!castedHandle.updated) { synchronized (GLOBAL_CHILD_LOCK) { @@ -56,7 +56,8 @@ class EventNodeImpl implements EventNode { } @Override - public ListenerHandle getHandle(Class handleType) { + public @NotNull ListenerHandle getHandle(@NotNull Class handleType) { + //noinspection unchecked return (ListenerHandle) handleMap.computeIfAbsent(handleType, aClass -> new Handle<>(this, (Class) aClass)); } From e16a6629089feeb5c0cc83ab76a168ee3b53aa7e Mon Sep 17 00:00:00 2001 From: TheMode Date: Fri, 20 Aug 2021 05:57:29 +0200 Subject: [PATCH 07/24] Add EventNode#hasListener --- .../net/minestom/server/event/EventNode.java | 14 ++++++++ .../minestom/server/event/EventNodeImpl.java | 32 +++++++++++++------ 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/src/main/java/net/minestom/server/event/EventNode.java b/src/main/java/net/minestom/server/event/EventNode.java index 26a00a011..5d166b00e 100644 --- a/src/main/java/net/minestom/server/event/EventNode.java +++ b/src/main/java/net/minestom/server/event/EventNode.java @@ -213,6 +213,20 @@ public interface EventNode { */ @NotNull ListenerHandle getHandle(@NotNull Class handleType); + /** + * Gets if any listener has been registered for the given handle. + * May trigger an update if the cached data is not correct. + *

+ * Useful if you are able to avoid expensive computation in the case where + * the event is unused. Be aware that {@link #call(Event, ListenerHandle)} + * has similar optimization built-in. + * + * @param handle the listener handle + * @param the event type + * @return true if the event has 1 or more listeners + */ + boolean hasListener(@NotNull ListenerHandle handle); + /** * Execute a cancellable event with a callback to execute if the event is successful. * Event conditions and propagation is the same as {@link #call(Event)}. diff --git a/src/main/java/net/minestom/server/event/EventNodeImpl.java b/src/main/java/net/minestom/server/event/EventNodeImpl.java index 09edc9f94..15fb2b3ba 100644 --- a/src/main/java/net/minestom/server/event/EventNodeImpl.java +++ b/src/main/java/net/minestom/server/event/EventNodeImpl.java @@ -43,11 +43,7 @@ class EventNodeImpl implements EventNode { Check.argCondition(castedHandle.node != this, "Invalid handle owner"); List> listeners = castedHandle.listeners; if (!castedHandle.updated) { - synchronized (GLOBAL_CHILD_LOCK) { - listeners.clear(); - castedHandle.update(this); - castedHandle.updated = true; - } + castedHandle.update(); } if (listeners.isEmpty()) return; for (Consumer listener : listeners) { @@ -62,6 +58,16 @@ class EventNodeImpl implements EventNode { aClass -> new Handle<>(this, (Class) aClass)); } + @Override + public boolean hasListener(@NotNull ListenerHandle handle) { + var castedHandle = (Handle) handle; + List> listeners = castedHandle.listeners; + if (!castedHandle.updated) { + castedHandle.update(); + } + return !listeners.isEmpty(); + } + @Override public @NotNull List> findChildren(@NotNull String name, Class eventType) { synchronized (GLOBAL_CHILD_LOCK) { @@ -255,17 +261,25 @@ class EventNodeImpl implements EventNode { } private static final class Handle implements ListenerHandle { - private final EventNode node; + private final EventNodeImpl node; private final Class eventType; private final List> listeners = new CopyOnWriteArrayList<>(); private volatile boolean updated; - Handle(EventNode node, Class eventType) { + Handle(EventNodeImpl node, Class eventType) { this.node = node; this.eventType = eventType; } - void update(EventNodeImpl targetNode) { + void update() { + synchronized (GLOBAL_CHILD_LOCK) { + listeners.clear(); + recursiveUpdate(node); + updated = true; + } + } + + private void recursiveUpdate(EventNodeImpl targetNode) { final var handleType = eventType; ListenerEntry entry = targetNode.listenerMap.get(handleType); if (entry != null) appendEntry(listeners, entry, targetNode); @@ -275,7 +289,7 @@ class EventNodeImpl implements EventNode { children.stream() .filter(child -> child.eventType.isAssignableFrom(handleType)) // Invalid event type .sorted(Comparator.comparing(EventNode::getPriority)) - .forEach(this::update); + .forEach(this::recursiveUpdate); } static void appendEntry(List> handleListeners, ListenerEntry entry, EventNodeImpl targetNode) { From a3656768482bef8c95257c43baa05c6402878931 Mon Sep 17 00:00:00 2001 From: TheMode Date: Fri, 20 Aug 2021 10:05:44 +0200 Subject: [PATCH 08/24] Add RecursiveEvent --- .../minestom/server/event/EventNodeImpl.java | 24 +++++++++++++++++++ .../server/event/trait/RecursiveEvent.java | 6 +++++ 2 files changed, 30 insertions(+) create mode 100644 src/main/java/net/minestom/server/event/trait/RecursiveEvent.java diff --git a/src/main/java/net/minestom/server/event/EventNodeImpl.java b/src/main/java/net/minestom/server/event/EventNodeImpl.java index 15fb2b3ba..79395c0d7 100644 --- a/src/main/java/net/minestom/server/event/EventNodeImpl.java +++ b/src/main/java/net/minestom/server/event/EventNodeImpl.java @@ -1,6 +1,7 @@ package net.minestom.server.event; import net.minestom.server.MinecraftServer; +import net.minestom.server.event.trait.RecursiveEvent; import net.minestom.server.utils.validate.Check; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; @@ -237,6 +238,9 @@ class EventNodeImpl implements EventNode { } private void propagateEvent(Class eventClass) { + // Recursive event + forEachRecursive(eventType, recursiveClass -> propagateEvent((Class) recursiveClass)); + // Propagate final var parent = this.parent; if (parent == null) return; Handle parentHandle = parent.handleMap.get(eventClass); @@ -255,6 +259,21 @@ class EventNodeImpl implements EventNode { return nameCheck && typeCheck; } + private static boolean isRecursive(Class type) { + if (type == null) return false; + return RecursiveEvent.class.isAssignableFrom(type); + } + + private static void forEachRecursive(Class type, Consumer> consumer) { + if (isRecursive(type)) { + final var superclass = type.getSuperclass(); + if (isRecursive(superclass)) consumer.accept((Class) superclass); + for (var inter : type.getInterfaces()) { + if (isRecursive(inter)) consumer.accept((Class) inter); + } + } + } + private static class ListenerEntry { final List> listeners = new CopyOnWriteArrayList<>(); final Set> bindingConsumers = new CopyOnWriteArraySet<>(); @@ -283,6 +302,11 @@ class EventNodeImpl implements EventNode { final var handleType = eventType; ListenerEntry entry = targetNode.listenerMap.get(handleType); if (entry != null) appendEntry(listeners, entry, targetNode); + // Recursive event + forEachRecursive(eventType, recursiveClass -> { + ListenerEntry recursiveEntry = targetNode.listenerMap.get(recursiveClass); + if (recursiveEntry != null) appendEntry(listeners, recursiveEntry, targetNode); + }); // Add children final var children = targetNode.children; if (children.isEmpty()) return; diff --git a/src/main/java/net/minestom/server/event/trait/RecursiveEvent.java b/src/main/java/net/minestom/server/event/trait/RecursiveEvent.java new file mode 100644 index 000000000..c22320c0d --- /dev/null +++ b/src/main/java/net/minestom/server/event/trait/RecursiveEvent.java @@ -0,0 +1,6 @@ +package net.minestom.server.event.trait; + +import net.minestom.server.event.Event; + +public interface RecursiveEvent extends Event { +} From 9a4ced9a6ef663e7ee4b87b741c2761c0de14fa7 Mon Sep 17 00:00:00 2001 From: TheMode Date: Fri, 20 Aug 2021 10:13:08 +0200 Subject: [PATCH 09/24] Cleanup --- .../minestom/server/event/EventNodeImpl.java | 41 ++++++++----------- 1 file changed, 17 insertions(+), 24 deletions(-) diff --git a/src/main/java/net/minestom/server/event/EventNodeImpl.java b/src/main/java/net/minestom/server/event/EventNodeImpl.java index 79395c0d7..6aec17cd9 100644 --- a/src/main/java/net/minestom/server/event/EventNodeImpl.java +++ b/src/main/java/net/minestom/server/event/EventNodeImpl.java @@ -238,15 +238,14 @@ class EventNodeImpl implements EventNode { } private void propagateEvent(Class eventClass) { - // Recursive event - forEachRecursive(eventType, recursiveClass -> propagateEvent((Class) recursiveClass)); - // Propagate - final var parent = this.parent; - if (parent == null) return; - Handle parentHandle = parent.handleMap.get(eventClass); - if (parentHandle == null) return; - parentHandle.updated = false; - parent.propagateEvent(eventClass); + forTargetEvents(eventClass, type -> { + final var parent = this.parent; + if (parent == null) return; + Handle parentHandle = parent.handleMap.get(type); + if (parentHandle == null) return; + parentHandle.updated = false; + parent.propagateEvent((Class) type); + }); } private ListenerEntry getEntry(Class type) { @@ -259,17 +258,14 @@ class EventNodeImpl implements EventNode { return nameCheck && typeCheck; } - private static boolean isRecursive(Class type) { - if (type == null) return false; - return RecursiveEvent.class.isAssignableFrom(type); - } - - private static void forEachRecursive(Class type, Consumer> consumer) { - if (isRecursive(type)) { + private static void forTargetEvents(Class type, Consumer> consumer) { + consumer.accept(type); + // Recursion + if (RecursiveEvent.class.isAssignableFrom(type)) { final var superclass = type.getSuperclass(); - if (isRecursive(superclass)) consumer.accept((Class) superclass); + if (superclass != null && RecursiveEvent.class.isAssignableFrom(superclass)) consumer.accept(superclass); for (var inter : type.getInterfaces()) { - if (isRecursive(inter)) consumer.accept((Class) inter); + if (RecursiveEvent.class.isAssignableFrom(inter)) consumer.accept(inter); } } } @@ -300,12 +296,9 @@ class EventNodeImpl implements EventNode { private void recursiveUpdate(EventNodeImpl targetNode) { final var handleType = eventType; - ListenerEntry entry = targetNode.listenerMap.get(handleType); - if (entry != null) appendEntry(listeners, entry, targetNode); - // Recursive event - forEachRecursive(eventType, recursiveClass -> { - ListenerEntry recursiveEntry = targetNode.listenerMap.get(recursiveClass); - if (recursiveEntry != null) appendEntry(listeners, recursiveEntry, targetNode); + forTargetEvents(handleType, type -> { + ListenerEntry entry = targetNode.listenerMap.get(type); + if (entry != null) appendEntry(listeners, entry, targetNode); }); // Add children final var children = targetNode.children; From 8297089ca87f95dde2c807fcba20c77628d2ddf9 Mon Sep 17 00:00:00 2001 From: TheMode Date: Fri, 20 Aug 2021 10:27:22 +0200 Subject: [PATCH 10/24] Fix recursive class selection --- src/main/java/net/minestom/server/event/EventNodeImpl.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/main/java/net/minestom/server/event/EventNodeImpl.java b/src/main/java/net/minestom/server/event/EventNodeImpl.java index 6aec17cd9..d36de21df 100644 --- a/src/main/java/net/minestom/server/event/EventNodeImpl.java +++ b/src/main/java/net/minestom/server/event/EventNodeImpl.java @@ -262,10 +262,9 @@ class EventNodeImpl implements EventNode { consumer.accept(type); // Recursion if (RecursiveEvent.class.isAssignableFrom(type)) { - final var superclass = type.getSuperclass(); - if (superclass != null && RecursiveEvent.class.isAssignableFrom(superclass)) consumer.accept(superclass); - for (var inter : type.getInterfaces()) { - if (RecursiveEvent.class.isAssignableFrom(inter)) consumer.accept(inter); + final Class superclass = type.getSuperclass(); + if (superclass != null && RecursiveEvent.class.isAssignableFrom(superclass)) { + forTargetEvents(superclass, consumer); } } } From 67e12d07eb6a86a4400a803a3947ccfd51b45512 Mon Sep 17 00:00:00 2001 From: TheMode Date: Fri, 20 Aug 2021 20:50:11 +0200 Subject: [PATCH 11/24] Add LEATHER_HORSE_ARMOR meta --- src/main/java/net/minestom/server/item/ItemStackBuilder.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/net/minestom/server/item/ItemStackBuilder.java b/src/main/java/net/minestom/server/item/ItemStackBuilder.java index 168f8d43a..7cb46d4bb 100644 --- a/src/main/java/net/minestom/server/item/ItemStackBuilder.java +++ b/src/main/java/net/minestom/server/item/ItemStackBuilder.java @@ -51,6 +51,7 @@ public class ItemStackBuilder { MATERIAL_SUPPLIER_MAP.put(Material.LEATHER_CHESTPLATE, LeatherArmorMeta.Builder::new); MATERIAL_SUPPLIER_MAP.put(Material.LEATHER_LEGGINGS, LeatherArmorMeta.Builder::new); MATERIAL_SUPPLIER_MAP.put(Material.LEATHER_BOOTS, LeatherArmorMeta.Builder::new); + MATERIAL_SUPPLIER_MAP.put(Material.LEATHER_HORSE_ARMOR, LeatherArmorMeta.Builder::new); } protected ItemStackBuilder(@NotNull Material material) { From 7dfa477500281ddd60772249c723d7f49284b7f2 Mon Sep 17 00:00:00 2001 From: TheMode Date: Sat, 21 Aug 2021 01:24:30 +0200 Subject: [PATCH 12/24] Initial mapped node support --- .../minestom/server/event/EventNodeImpl.java | 61 +++++++++++++++---- 1 file changed, 50 insertions(+), 11 deletions(-) diff --git a/src/main/java/net/minestom/server/event/EventNodeImpl.java b/src/main/java/net/minestom/server/event/EventNodeImpl.java index d36de21df..a2c858bc5 100644 --- a/src/main/java/net/minestom/server/event/EventNodeImpl.java +++ b/src/main/java/net/minestom/server/event/EventNodeImpl.java @@ -20,7 +20,7 @@ class EventNodeImpl implements EventNode { private final Map, Handle> handleMap = new ConcurrentHashMap<>(); private final Map, ListenerEntry> listenerMap = new ConcurrentHashMap<>(); private final Set> children = new CopyOnWriteArraySet<>(); - private final Map> mappedNodeCache = new WeakHashMap<>(); + private final Map> mappedNodeCache = new WeakHashMap<>(); private final String name; private final EventFilter filter; @@ -175,13 +175,30 @@ class EventNodeImpl implements EventNode { @Override public void map(@NotNull EventNode node, @NotNull Object value) { - // TODO + synchronized (GLOBAL_CHILD_LOCK) { + final var nodeImpl = (EventNodeImpl) node; + Check.stateCondition(nodeImpl.parent != null, "Node already has a parent"); + Check.stateCondition(Objects.equals(parent, nodeImpl), "Cannot map to self"); + var previous = this.mappedNodeCache.put(value, (EventNodeImpl) nodeImpl); + if (previous != null) { + previous.propagateEvents(); // Propagate before removing the parent + previous.parent = null; + } + nodeImpl.parent = this; + nodeImpl.propagateEvents(); // Propagate after setting the parent + } } @Override public boolean unmap(@NotNull Object value) { - // TODO - return false; + synchronized (GLOBAL_CHILD_LOCK) { + final var mappedNode = this.mappedNodeCache.remove(value); + if (mappedNode == null) return false; + final var childImpl = (EventNodeImpl) mappedNode; + childImpl.propagateEvents(); // Propagate before removing the parent + childImpl.parent = null; + return true; + } } @Override @@ -287,18 +304,41 @@ class EventNodeImpl implements EventNode { void update() { synchronized (GLOBAL_CHILD_LOCK) { - listeners.clear(); + this.listeners.clear(); recursiveUpdate(node); - updated = true; + this.updated = true; } } private void recursiveUpdate(EventNodeImpl targetNode) { final var handleType = eventType; + // Standalone listeners forTargetEvents(handleType, type -> { ListenerEntry entry = targetNode.listenerMap.get(type); - if (entry != null) appendEntry(listeners, entry, targetNode); + if (entry != null) appendEntries(listeners, entry, targetNode); }); + // Mapped nodes + { + Set> filters = new HashSet<>(); + for (var mappedEntry : targetNode.mappedNodeCache.entrySet()) { + var mappedNode = mappedEntry.getValue(); + if (!mappedNode.eventType.isAssignableFrom(handleType)) continue; + forTargetEvents(handleType, type -> { + final boolean hasEvent = mappedNode.listenerMap.containsKey(type); + if (!hasEvent) return; + filters.add(mappedNode.filter); + }); + } + if (!filters.isEmpty()) { + this.listeners.add(event -> { + for (var filter : filters) { + final var handler = filter.castHandler(event); + final var map = targetNode.mappedNodeCache.get(handler); + if (map != null) map.call(event); + } + }); + } + } // Add children final var children = targetNode.children; if (children.isEmpty()) return; @@ -308,10 +348,10 @@ class EventNodeImpl implements EventNode { .forEach(this::recursiveUpdate); } - static void appendEntry(List> handleListeners, ListenerEntry entry, EventNodeImpl targetNode) { + static void appendEntries(List> handleListeners, ListenerEntry entry, EventNodeImpl targetNode) { final var filter = targetNode.filter; final var predicate = targetNode.predicate; - // Add normal listeners + // Normal listeners for (var listener : entry.listeners) { if (predicate != null) { // Ensure that the event is valid before running @@ -325,9 +365,8 @@ class EventNodeImpl implements EventNode { handleListeners.add(e -> callListener(targetNode, listener, e)); } } - // Add bindings + // Bindings handleListeners.addAll(entry.bindingConsumers); - // TODO mapped node } static void callListener(EventNodeImpl targetNode, EventListener listener, E event) { From 429d12c7e3a9ac0e0970dabff67becc094ba500f Mon Sep 17 00:00:00 2001 From: TheMode Date: Sat, 21 Aug 2021 01:35:47 +0200 Subject: [PATCH 13/24] Add some comments --- .../minestom/server/event/EventNodeImpl.java | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/main/java/net/minestom/server/event/EventNodeImpl.java b/src/main/java/net/minestom/server/event/EventNodeImpl.java index a2c858bc5..1ab16bd06 100644 --- a/src/main/java/net/minestom/server/event/EventNodeImpl.java +++ b/src/main/java/net/minestom/server/event/EventNodeImpl.java @@ -319,22 +319,25 @@ class EventNodeImpl implements EventNode { }); // Mapped nodes { - Set> filters = new HashSet<>(); - for (var mappedEntry : targetNode.mappedNodeCache.entrySet()) { + final var mappedNodeCache = targetNode.mappedNodeCache; + Set> filters = new HashSet<>(mappedNodeCache.size()); + // Retrieve all filters used to retrieve potential handlers + for (var mappedEntry : mappedNodeCache.entrySet()) { var mappedNode = mappedEntry.getValue(); if (!mappedNode.eventType.isAssignableFrom(handleType)) continue; forTargetEvents(handleType, type -> { - final boolean hasEvent = mappedNode.listenerMap.containsKey(type); - if (!hasEvent) return; + if (!mappedNode.listenerMap.containsKey(type)) return; // No normal listener to this handle type filters.add(mappedNode.filter); }); } + // If at least one mapped node listen to this handle type, + // loop through them and forward to mapped node if there is a match if (!filters.isEmpty()) { this.listeners.add(event -> { for (var filter : filters) { - final var handler = filter.castHandler(event); - final var map = targetNode.mappedNodeCache.get(handler); - if (map != null) map.call(event); + final Object handler = filter.castHandler(event); + final EventNode mappedNode = mappedNodeCache.get(handler); + if (mappedNode != null) mappedNode.call(event); } }); } From 0ad763a813be6b3ebfa7e1252912256a2a96a9ea Mon Sep 17 00:00:00 2001 From: TheMode Date: Sat, 21 Aug 2021 02:00:30 +0200 Subject: [PATCH 14/24] Performance improvement --- .../minestom/server/event/EventNodeImpl.java | 64 +++++++++++-------- 1 file changed, 36 insertions(+), 28 deletions(-) diff --git a/src/main/java/net/minestom/server/event/EventNodeImpl.java b/src/main/java/net/minestom/server/event/EventNodeImpl.java index 1ab16bd06..61f57364b 100644 --- a/src/main/java/net/minestom/server/event/EventNodeImpl.java +++ b/src/main/java/net/minestom/server/event/EventNodeImpl.java @@ -311,46 +311,51 @@ class EventNodeImpl implements EventNode { } private void recursiveUpdate(EventNodeImpl targetNode) { - final var handleType = eventType; // Standalone listeners - forTargetEvents(handleType, type -> { + forTargetEvents(eventType, type -> { ListenerEntry entry = targetNode.listenerMap.get(type); if (entry != null) appendEntries(listeners, entry, targetNode); }); // Mapped nodes - { - final var mappedNodeCache = targetNode.mappedNodeCache; - Set> filters = new HashSet<>(mappedNodeCache.size()); - // Retrieve all filters used to retrieve potential handlers - for (var mappedEntry : mappedNodeCache.entrySet()) { - var mappedNode = mappedEntry.getValue(); - if (!mappedNode.eventType.isAssignableFrom(handleType)) continue; - forTargetEvents(handleType, type -> { - if (!mappedNode.listenerMap.containsKey(type)) return; // No normal listener to this handle type - filters.add(mappedNode.filter); - }); - } - // If at least one mapped node listen to this handle type, - // loop through them and forward to mapped node if there is a match - if (!filters.isEmpty()) { - this.listeners.add(event -> { - for (var filter : filters) { - final Object handler = filter.castHandler(event); - final EventNode mappedNode = mappedNodeCache.get(handler); - if (mappedNode != null) mappedNode.call(event); - } - }); - } - } + handleMappedNode(targetNode); // Add children final var children = targetNode.children; if (children.isEmpty()) return; children.stream() - .filter(child -> child.eventType.isAssignableFrom(handleType)) // Invalid event type + .filter(child -> child.eventType.isAssignableFrom(eventType)) // Invalid event type .sorted(Comparator.comparing(EventNode::getPriority)) .forEach(this::recursiveUpdate); } + private void handleMappedNode(EventNodeImpl targetNode) { + final var mappedNodeCache = targetNode.mappedNodeCache; + if (mappedNodeCache.isEmpty()) return; + Set> filters = new HashSet<>(mappedNodeCache.size()); + // Retrieve all filters used to retrieve potential handlers + for (var mappedEntry : mappedNodeCache.entrySet()) { + final EventNodeImpl mappedNode = mappedEntry.getValue(); + if (!mappedNode.eventType.isAssignableFrom(eventType)) continue; + final var mappedListeners = mappedNode.listenerMap; + if (mappedListeners.isEmpty()) + continue; // The mapped node does not have any listener (perhaps throw a warning?) + forTargetEvents(eventType, type -> { + if (!mappedListeners.containsKey(type)) return; // No normal listener to this handle type + filters.add(mappedNode.filter); + }); + } + // If at least one mapped node listen to this handle type, + // loop through them and forward to mapped node if there is a match + if (!filters.isEmpty()) { + this.listeners.add(event -> { + for (var filter : filters) { + final Object handler = filter.castHandler(event); + final EventNode mappedNode = mappedNodeCache.get(handler); + if (mappedNode != null) mappedNode.call(event); + } + }); + } + } + static void appendEntries(List> handleListeners, ListenerEntry entry, EventNodeImpl targetNode) { final var filter = targetNode.filter; final var predicate = targetNode.predicate; @@ -369,7 +374,10 @@ class EventNodeImpl implements EventNode { } } // Bindings - handleListeners.addAll(entry.bindingConsumers); + final var bindingConsumers = entry.bindingConsumers; + if (!bindingConsumers.isEmpty()) { // Ensure no array clone + handleListeners.addAll(bindingConsumers); + } } static void callListener(EventNodeImpl targetNode, EventListener listener, E event) { From cfbd655027e9a1f4d6d2d475e95ba80894a79ab1 Mon Sep 17 00:00:00 2001 From: TheMode Date: Sat, 21 Aug 2021 02:08:25 +0200 Subject: [PATCH 15/24] Cleanup --- .../minestom/server/event/EventNodeImpl.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/main/java/net/minestom/server/event/EventNodeImpl.java b/src/main/java/net/minestom/server/event/EventNodeImpl.java index 61f57364b..56ed122f0 100644 --- a/src/main/java/net/minestom/server/event/EventNodeImpl.java +++ b/src/main/java/net/minestom/server/event/EventNodeImpl.java @@ -29,9 +29,9 @@ class EventNodeImpl implements EventNode { private volatile int priority; private volatile EventNodeImpl parent; - protected EventNodeImpl(@NotNull String name, - @NotNull EventFilter filter, - @Nullable BiPredicate predicate) { + EventNodeImpl(@NotNull String name, + @NotNull EventFilter filter, + @Nullable BiPredicate predicate) { this.name = name; this.filter = filter; this.predicate = predicate; @@ -313,8 +313,8 @@ class EventNodeImpl implements EventNode { private void recursiveUpdate(EventNodeImpl targetNode) { // Standalone listeners forTargetEvents(eventType, type -> { - ListenerEntry entry = targetNode.listenerMap.get(type); - if (entry != null) appendEntries(listeners, entry, targetNode); + final ListenerEntry entry = targetNode.listenerMap.get(type); + if (entry != null) appendEntries(entry, targetNode); }); // Mapped nodes handleMappedNode(targetNode); @@ -356,27 +356,27 @@ class EventNodeImpl implements EventNode { } } - static void appendEntries(List> handleListeners, ListenerEntry entry, EventNodeImpl targetNode) { + private void appendEntries(ListenerEntry entry, EventNodeImpl targetNode) { final var filter = targetNode.filter; final var predicate = targetNode.predicate; // Normal listeners for (var listener : entry.listeners) { if (predicate != null) { // Ensure that the event is valid before running - handleListeners.add(e -> { + this.listeners.add(e -> { final var value = filter.getHandler(e); if (!predicate.test(e, value)) return; callListener(targetNode, listener, e); }); } else { // No predicate, run directly - handleListeners.add(e -> callListener(targetNode, listener, e)); + this.listeners.add(e -> callListener(targetNode, listener, e)); } } // Bindings final var bindingConsumers = entry.bindingConsumers; if (!bindingConsumers.isEmpty()) { // Ensure no array clone - handleListeners.addAll(bindingConsumers); + this.listeners.addAll(bindingConsumers); } } From c6cc96a5f957ace5526a236a1365d85bcaeb62dd Mon Sep 17 00:00:00 2001 From: TheMode Date: Sat, 21 Aug 2021 04:15:47 +0200 Subject: [PATCH 16/24] Less change propagation/volatile read --- .../minestom/server/event/EventNodeImpl.java | 67 ++++++++----------- 1 file changed, 28 insertions(+), 39 deletions(-) diff --git a/src/main/java/net/minestom/server/event/EventNodeImpl.java b/src/main/java/net/minestom/server/event/EventNodeImpl.java index 56ed122f0..a4f77d76d 100644 --- a/src/main/java/net/minestom/server/event/EventNodeImpl.java +++ b/src/main/java/net/minestom/server/event/EventNodeImpl.java @@ -40,12 +40,10 @@ class EventNodeImpl implements EventNode { @Override public void call(@NotNull E event, @NotNull ListenerHandle handle) { - var castedHandle = (Handle) handle; + final Handle castedHandle = (Handle) handle; Check.argCondition(castedHandle.node != this, "Invalid handle owner"); - List> listeners = castedHandle.listeners; - if (!castedHandle.updated) { - castedHandle.update(); - } + if (!castedHandle.updated) castedHandle.update(); + final List> listeners = castedHandle.listeners; if (listeners.isEmpty()) return; for (Consumer listener : listeners) { listener.accept(event); @@ -61,12 +59,9 @@ class EventNodeImpl implements EventNode { @Override public boolean hasListener(@NotNull ListenerHandle handle) { - var castedHandle = (Handle) handle; - List> listeners = castedHandle.listeners; - if (!castedHandle.updated) { - castedHandle.update(); - } - return !listeners.isEmpty(); + final Handle castedHandle = (Handle) handle; + if (!castedHandle.updated) castedHandle.update(); + return !castedHandle.listeners.isEmpty(); } @Override @@ -129,10 +124,9 @@ class EventNodeImpl implements EventNode { final var childImpl = (EventNodeImpl) child; Check.stateCondition(childImpl.parent != null, "Node already has a parent"); Check.stateCondition(Objects.equals(parent, child), "Cannot have a child as parent"); - final boolean result = this.children.add((EventNodeImpl) childImpl); - if (!result) return this; + if (!children.add((EventNodeImpl) childImpl)) return this; // Couldn't add the child (already present?) childImpl.parent = this; - childImpl.propagateEvents(); // Propagate after setting the parent + childImpl.propagateEvents(this); // Propagate after setting the parent } return this; } @@ -140,10 +134,10 @@ class EventNodeImpl implements EventNode { @Override public @NotNull EventNode removeChild(@NotNull EventNode child) { synchronized (GLOBAL_CHILD_LOCK) { - final boolean result = this.children.remove(child); - if (!result) return this; final var childImpl = (EventNodeImpl) child; - childImpl.propagateEvents(); // Propagate before removing the parent + final boolean result = this.children.remove(childImpl); + if (!result) return this; // Child not found + childImpl.propagateEvents(parent); // Propagate before removing the parent childImpl.parent = null; } return this; @@ -153,9 +147,9 @@ class EventNodeImpl implements EventNode { public @NotNull EventNode addListener(@NotNull EventListener listener) { synchronized (GLOBAL_CHILD_LOCK) { final var eventType = listener.eventType(); - var entry = getEntry(eventType); + ListenerEntry entry = getEntry(eventType); entry.listeners.add((EventListener) listener); - propagateEvent(eventType); + propagateEvent(parent, eventType); } return this; } @@ -164,11 +158,10 @@ class EventNodeImpl implements EventNode { public @NotNull EventNode removeListener(@NotNull EventListener listener) { synchronized (GLOBAL_CHILD_LOCK) { final var eventType = listener.eventType(); - var entry = listenerMap.get(eventType); - if (entry == null) return this; + ListenerEntry entry = listenerMap.get(eventType); + if (entry == null) return this; // There is no listener with such type var listeners = entry.listeners; - final boolean removed = listeners.remove(listener); - if (removed) propagateEvent(eventType); + if (listeners.remove(listener)) propagateEvent(parent, eventType); } return this; } @@ -180,12 +173,9 @@ class EventNodeImpl implements EventNode { Check.stateCondition(nodeImpl.parent != null, "Node already has a parent"); Check.stateCondition(Objects.equals(parent, nodeImpl), "Cannot map to self"); var previous = this.mappedNodeCache.put(value, (EventNodeImpl) nodeImpl); - if (previous != null) { - previous.propagateEvents(); // Propagate before removing the parent - previous.parent = null; - } + if (previous != null) previous.parent = null; nodeImpl.parent = this; - nodeImpl.propagateEvents(); // Propagate after setting the parent + nodeImpl.propagateEvents(this); // Propagate after setting the parent } } @@ -193,9 +183,9 @@ class EventNodeImpl implements EventNode { public boolean unmap(@NotNull Object value) { synchronized (GLOBAL_CHILD_LOCK) { final var mappedNode = this.mappedNodeCache.remove(value); - if (mappedNode == null) return false; + if (mappedNode == null) return false; // Mapped node not found final var childImpl = (EventNodeImpl) mappedNode; - childImpl.propagateEvents(); // Propagate before removing the parent + childImpl.propagateEvents(parent); // Propagate before removing the parent childImpl.parent = null; return true; } @@ -207,7 +197,7 @@ class EventNodeImpl implements EventNode { for (var eventType : binding.eventTypes()) { ListenerEntry entry = getEntry((Class) eventType); final boolean added = entry.bindingConsumers.add((Consumer) binding.consumer(eventType)); - if (added) propagateEvent((Class) eventType); + if (added) propagateEvent(parent, (Class) eventType); } } } @@ -219,7 +209,7 @@ class EventNodeImpl implements EventNode { ListenerEntry entry = listenerMap.get(eventType); if (entry == null) return; final boolean removed = entry.bindingConsumers.remove(binding.consumer(eventType)); - if (removed) propagateEvent((Class) eventType); + if (removed) propagateEvent(parent, (Class) eventType); } } } @@ -250,18 +240,17 @@ class EventNodeImpl implements EventNode { return parent; } - private void propagateEvents() { - this.listenerMap.keySet().forEach(this::propagateEvent); + private void propagateEvents(EventNodeImpl parent) { + this.listenerMap.keySet().forEach(aClass -> propagateEvent(parent, aClass)); } - private void propagateEvent(Class eventClass) { + private void propagateEvent(EventNodeImpl parent, Class eventClass) { + if (parent == null) return; forTargetEvents(eventClass, type -> { - final var parent = this.parent; - if (parent == null) return; - Handle parentHandle = parent.handleMap.get(type); + Handle parentHandle = (Handle) parent.handleMap.get(type); if (parentHandle == null) return; parentHandle.updated = false; - parent.propagateEvent((Class) type); + parent.propagateEvent(parent.parent, type); }); } From a58e149918fe9195c359e2c14899908ed100eee4 Mon Sep 17 00:00:00 2001 From: TheMode Date: Sat, 21 Aug 2021 04:21:03 +0200 Subject: [PATCH 17/24] Remove unnecessary generic declaration --- src/main/java/net/minestom/server/event/EventNode.java | 3 +-- src/main/java/net/minestom/server/event/EventNodeImpl.java | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/net/minestom/server/event/EventNode.java b/src/main/java/net/minestom/server/event/EventNode.java index 5d166b00e..ab91ab7cd 100644 --- a/src/main/java/net/minestom/server/event/EventNode.java +++ b/src/main/java/net/minestom/server/event/EventNode.java @@ -222,10 +222,9 @@ public interface EventNode { * has similar optimization built-in. * * @param handle the listener handle - * @param the event type * @return true if the event has 1 or more listeners */ - boolean hasListener(@NotNull ListenerHandle handle); + boolean hasListener(@NotNull ListenerHandle handle); /** * Execute a cancellable event with a callback to execute if the event is successful. diff --git a/src/main/java/net/minestom/server/event/EventNodeImpl.java b/src/main/java/net/minestom/server/event/EventNodeImpl.java index a4f77d76d..8a58b5b78 100644 --- a/src/main/java/net/minestom/server/event/EventNodeImpl.java +++ b/src/main/java/net/minestom/server/event/EventNodeImpl.java @@ -58,7 +58,7 @@ class EventNodeImpl implements EventNode { } @Override - public boolean hasListener(@NotNull ListenerHandle handle) { + public boolean hasListener(@NotNull ListenerHandle handle) { final Handle castedHandle = (Handle) handle; if (!castedHandle.updated) castedHandle.update(); return !castedHandle.listeners.isEmpty(); From f9c67d9b2b8ba6a6d1675945ad4be1afa01f73ee Mon Sep 17 00:00:00 2001 From: TheMode Date: Sat, 21 Aug 2021 07:01:48 +0200 Subject: [PATCH 18/24] Improve mapped node listening --- .../minestom/server/event/EventNodeImpl.java | 37 ++++++++++++++++--- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/src/main/java/net/minestom/server/event/EventNodeImpl.java b/src/main/java/net/minestom/server/event/EventNodeImpl.java index 8a58b5b78..8fad5a7af 100644 --- a/src/main/java/net/minestom/server/event/EventNodeImpl.java +++ b/src/main/java/net/minestom/server/event/EventNodeImpl.java @@ -335,13 +335,40 @@ class EventNodeImpl implements EventNode { // If at least one mapped node listen to this handle type, // loop through them and forward to mapped node if there is a match if (!filters.isEmpty()) { - this.listeners.add(event -> { - for (var filter : filters) { - final Object handler = filter.castHandler(event); + final var filterList = List.copyOf(filters); + final int size = filterList.size(); + if (size == 1) { + final var firstFilter = filterList.get(0); + this.listeners.add(event -> { + // Common case where there is only one filter + final Object handler = firstFilter.castHandler(event); final EventNode mappedNode = mappedNodeCache.get(handler); if (mappedNode != null) mappedNode.call(event); - } - }); + }); + } else if (size == 2) { + final var firstFilter = filterList.get(0); + final var secondFilter = filterList.get(1); + this.listeners.add(event -> { + // First check + final Object handler1 = firstFilter.castHandler(event); + final EventNode mappedNode1 = mappedNodeCache.get(handler1); + if (mappedNode1 != null) mappedNode1.call(event); + + // Second check + final Object handler2 = secondFilter.castHandler(event); + final EventNode mappedNode2 = mappedNodeCache.get(handler2); + if (mappedNode2 != null) mappedNode2.call(event); + }); + } else { + this.listeners.add(event -> { + for (var filter : filterList) { + final Object handler = filter.castHandler(event); + final EventNode mappedNode = mappedNodeCache.get(handler); + if (mappedNode != null) mappedNode.call(event); + } + + }); + } } } From d9c000f80ae7b4f696ef6c77375a6a919cd6d4e0 Mon Sep 17 00:00:00 2001 From: TheMode Date: Sat, 21 Aug 2021 10:02:57 +0200 Subject: [PATCH 19/24] Cache listener handles for mapped nodes Signed-off-by: TheMode --- .../minestom/server/event/EventNodeImpl.java | 44 ++++++++----------- 1 file changed, 18 insertions(+), 26 deletions(-) diff --git a/src/main/java/net/minestom/server/event/EventNodeImpl.java b/src/main/java/net/minestom/server/event/EventNodeImpl.java index 8fad5a7af..2d4f5bfc7 100644 --- a/src/main/java/net/minestom/server/event/EventNodeImpl.java +++ b/src/main/java/net/minestom/server/event/EventNodeImpl.java @@ -1,5 +1,6 @@ package net.minestom.server.event; +import it.unimi.dsi.fastutil.Pair; import net.minestom.server.MinecraftServer; import net.minestom.server.event.trait.RecursiveEvent; import net.minestom.server.utils.validate.Check; @@ -11,6 +12,7 @@ import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArraySet; +import java.util.function.BiConsumer; import java.util.function.BiPredicate; import java.util.function.Consumer; @@ -320,53 +322,43 @@ class EventNodeImpl implements EventNode { final var mappedNodeCache = targetNode.mappedNodeCache; if (mappedNodeCache.isEmpty()) return; Set> filters = new HashSet<>(mappedNodeCache.size()); + Map, ListenerHandle>> handlers = new HashMap<>(mappedNodeCache.size()); // Retrieve all filters used to retrieve potential handlers for (var mappedEntry : mappedNodeCache.entrySet()) { final EventNodeImpl mappedNode = mappedEntry.getValue(); - if (!mappedNode.eventType.isAssignableFrom(eventType)) continue; - final var mappedListeners = mappedNode.listenerMap; - if (mappedListeners.isEmpty()) + final Class mappedNodeType = mappedNode.eventType; + if (!mappedNodeType.isAssignableFrom(eventType)) continue; + if (mappedNode.listenerMap.isEmpty()) continue; // The mapped node does not have any listener (perhaps throw a warning?) - forTargetEvents(eventType, type -> { - if (!mappedListeners.containsKey(type)) return; // No normal listener to this handle type - filters.add(mappedNode.filter); - }); + filters.add(mappedNode.filter); + handlers.put(mappedEntry.getKey(), Pair.of(mappedNode, mappedNode.getHandle(eventType))); } // If at least one mapped node listen to this handle type, // loop through them and forward to mapped node if there is a match if (!filters.isEmpty()) { final var filterList = List.copyOf(filters); final int size = filterList.size(); + final BiConsumer, E> mapper = (filter, event) -> { + final Object handler = filter.castHandler(event); + final var mapResult = handlers.get(handler); + if (mapResult != null) mapResult.first().call(event, mapResult.second()); + }; if (size == 1) { final var firstFilter = filterList.get(0); - this.listeners.add(event -> { - // Common case where there is only one filter - final Object handler = firstFilter.castHandler(event); - final EventNode mappedNode = mappedNodeCache.get(handler); - if (mappedNode != null) mappedNode.call(event); - }); + // Common case where there is only one filter + this.listeners.add(event -> mapper.accept(firstFilter, event)); } else if (size == 2) { final var firstFilter = filterList.get(0); final var secondFilter = filterList.get(1); this.listeners.add(event -> { - // First check - final Object handler1 = firstFilter.castHandler(event); - final EventNode mappedNode1 = mappedNodeCache.get(handler1); - if (mappedNode1 != null) mappedNode1.call(event); - - // Second check - final Object handler2 = secondFilter.castHandler(event); - final EventNode mappedNode2 = mappedNodeCache.get(handler2); - if (mappedNode2 != null) mappedNode2.call(event); + mapper.accept(firstFilter, event); + mapper.accept(secondFilter, event); }); } else { this.listeners.add(event -> { for (var filter : filterList) { - final Object handler = filter.castHandler(event); - final EventNode mappedNode = mappedNodeCache.get(handler); - if (mappedNode != null) mappedNode.call(event); + mapper.accept(filter, event); } - }); } } From 6f88abf987f479f8a560c1a4e81bcd77ffca0ce4 Mon Sep 17 00:00:00 2001 From: TheMode Date: Sun, 22 Aug 2021 00:41:30 +0200 Subject: [PATCH 20/24] Warn about EventNode#map performance --- .../java/net/minestom/server/event/EventNode.java | 15 +++++++++++++++ .../net/minestom/server/event/EventNodeImpl.java | 3 +-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/main/java/net/minestom/server/event/EventNode.java b/src/main/java/net/minestom/server/event/EventNode.java index ab91ab7cd..a4b5e85e9 100644 --- a/src/main/java/net/minestom/server/event/EventNode.java +++ b/src/main/java/net/minestom/server/event/EventNode.java @@ -352,9 +352,24 @@ public interface EventNode { @Contract(value = "_ -> this") @NotNull EventNode removeListener(@NotNull EventListener listener); + /** + * Maps a specific object to a node. + *

+ * Be aware that such structure have huge performance penalty as they will + * always require a map lookup. Use only at last resort. + * + * @param node the node to map + * @param value the mapped value + */ @ApiStatus.Experimental void map(@NotNull EventNode node, @NotNull Object value); + /** + * Undo {@link #map(EventNode, Object)} + * + * @param value the value to unmap + * @return true if the value has been unmapped, false if nothing happened + */ @ApiStatus.Experimental boolean unmap(@NotNull Object value); diff --git a/src/main/java/net/minestom/server/event/EventNodeImpl.java b/src/main/java/net/minestom/server/event/EventNodeImpl.java index 2d4f5bfc7..ac6b5b97b 100644 --- a/src/main/java/net/minestom/server/event/EventNodeImpl.java +++ b/src/main/java/net/minestom/server/event/EventNodeImpl.java @@ -326,8 +326,7 @@ class EventNodeImpl implements EventNode { // Retrieve all filters used to retrieve potential handlers for (var mappedEntry : mappedNodeCache.entrySet()) { final EventNodeImpl mappedNode = mappedEntry.getValue(); - final Class mappedNodeType = mappedNode.eventType; - if (!mappedNodeType.isAssignableFrom(eventType)) continue; + if (!mappedNode.eventType.isAssignableFrom(eventType)) continue; if (mappedNode.listenerMap.isEmpty()) continue; // The mapped node does not have any listener (perhaps throw a warning?) filters.add(mappedNode.filter); From 9c7c409fae314ab3f6aa2a5de9267577add17b07 Mon Sep 17 00:00:00 2001 From: TheMode Date: Sun, 22 Aug 2021 03:08:37 +0200 Subject: [PATCH 21/24] Reduce listener count --- .../minestom/server/event/EventNodeImpl.java | 52 ++++++++++++------- 1 file changed, 33 insertions(+), 19 deletions(-) diff --git a/src/main/java/net/minestom/server/event/EventNodeImpl.java b/src/main/java/net/minestom/server/event/EventNodeImpl.java index ac6b5b97b..c0b50cdcf 100644 --- a/src/main/java/net/minestom/server/event/EventNodeImpl.java +++ b/src/main/java/net/minestom/server/event/EventNodeImpl.java @@ -327,8 +327,10 @@ class EventNodeImpl implements EventNode { for (var mappedEntry : mappedNodeCache.entrySet()) { final EventNodeImpl mappedNode = mappedEntry.getValue(); if (!mappedNode.eventType.isAssignableFrom(eventType)) continue; - if (mappedNode.listenerMap.isEmpty()) + final var listenerMap = mappedNode.listenerMap; + if (listenerMap.isEmpty()) continue; // The mapped node does not have any listener (perhaps throw a warning?) + if (!listenerMap.containsKey(eventType)) continue; filters.add(mappedNode.filter); handlers.put(mappedEntry.getKey(), Pair.of(mappedNode, mappedNode.getHandle(eventType))); } @@ -366,25 +368,37 @@ class EventNodeImpl implements EventNode { private void appendEntries(ListenerEntry entry, EventNodeImpl targetNode) { final var filter = targetNode.filter; final var predicate = targetNode.predicate; - // Normal listeners - for (var listener : entry.listeners) { - if (predicate != null) { - // Ensure that the event is valid before running - this.listeners.add(e -> { - final var value = filter.getHandler(e); - if (!predicate.test(e, value)) return; - callListener(targetNode, listener, e); - }); - } else { - // No predicate, run directly - this.listeners.add(e -> callListener(targetNode, listener, e)); + + final boolean hasPredicate = predicate != null; + final var listenersCopy = List.copyOf(entry.listeners); + final var bindingsCopy = List.copyOf(entry.bindingConsumers); + if (!hasPredicate && listenersCopy.isEmpty() && bindingsCopy.isEmpty()) + return; // Nothing to run + + if (!hasPredicate && bindingsCopy.isEmpty() && listenersCopy.size() == 1) { + // Only one normal listener + final EventListener listener = listenersCopy.get(0); + this.listeners.add(e -> callListener(targetNode, listener, e)); + return; + } + + // Worth case scenario, try to run everything + this.listeners.add(e -> { + if (hasPredicate) { + final var value = filter.getHandler(e); + if (!predicate.test(e, value)) return; } - } - // Bindings - final var bindingConsumers = entry.bindingConsumers; - if (!bindingConsumers.isEmpty()) { // Ensure no array clone - this.listeners.addAll(bindingConsumers); - } + if (!listenersCopy.isEmpty()) { + for (EventListener listener : listenersCopy) { + callListener(targetNode, listener, e); + } + } + if (!bindingsCopy.isEmpty()) { + for (Consumer eConsumer : bindingsCopy) { + eConsumer.accept(e); + } + } + }); } static void callListener(EventNodeImpl targetNode, EventListener listener, E event) { From d4f8aa6fcb409910f951ef023123a0130cabdcd3 Mon Sep 17 00:00:00 2001 From: TheMode Date: Sun, 22 Aug 2021 04:17:42 +0200 Subject: [PATCH 22/24] Simplify & inline mapped listeners Signed-off-by: TheMode --- .../minestom/server/event/EventNodeImpl.java | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/main/java/net/minestom/server/event/EventNodeImpl.java b/src/main/java/net/minestom/server/event/EventNodeImpl.java index c0b50cdcf..e40dac7b7 100644 --- a/src/main/java/net/minestom/server/event/EventNodeImpl.java +++ b/src/main/java/net/minestom/server/event/EventNodeImpl.java @@ -1,6 +1,5 @@ package net.minestom.server.event; -import it.unimi.dsi.fastutil.Pair; import net.minestom.server.MinecraftServer; import net.minestom.server.event.trait.RecursiveEvent; import net.minestom.server.utils.validate.Check; @@ -322,17 +321,14 @@ class EventNodeImpl implements EventNode { final var mappedNodeCache = targetNode.mappedNodeCache; if (mappedNodeCache.isEmpty()) return; Set> filters = new HashSet<>(mappedNodeCache.size()); - Map, ListenerHandle>> handlers = new HashMap<>(mappedNodeCache.size()); + Map> handlers = new HashMap<>(mappedNodeCache.size()); // Retrieve all filters used to retrieve potential handlers for (var mappedEntry : mappedNodeCache.entrySet()) { final EventNodeImpl mappedNode = mappedEntry.getValue(); - if (!mappedNode.eventType.isAssignableFrom(eventType)) continue; - final var listenerMap = mappedNode.listenerMap; - if (listenerMap.isEmpty()) - continue; // The mapped node does not have any listener (perhaps throw a warning?) - if (!listenerMap.containsKey(eventType)) continue; + final var handle = (Handle) mappedNode.getHandle(eventType); + if (!mappedNode.hasListener(handle)) continue; // Implicit update filters.add(mappedNode.filter); - handlers.put(mappedEntry.getKey(), Pair.of(mappedNode, mappedNode.getHandle(eventType))); + handlers.put(mappedEntry.getKey(), handle); } // If at least one mapped node listen to this handle type, // loop through them and forward to mapped node if there is a match @@ -341,8 +337,13 @@ class EventNodeImpl implements EventNode { final int size = filterList.size(); final BiConsumer, E> mapper = (filter, event) -> { final Object handler = filter.castHandler(event); - final var mapResult = handlers.get(handler); - if (mapResult != null) mapResult.first().call(event, mapResult.second()); + final var handle = handlers.get(handler); + if (handle != null) { + if (!handle.updated) handle.update(); + for (Consumer listener : handle.listeners) { + listener.accept(event); + } + } }; if (size == 1) { final var firstFilter = filterList.get(0); From ab91c08a6695fe3fd12ea8f5f936b946f01de792 Mon Sep 17 00:00:00 2001 From: TheMode Date: Sun, 22 Aug 2021 04:39:53 +0200 Subject: [PATCH 23/24] Typo --- src/main/java/net/minestom/server/event/EventNodeImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/net/minestom/server/event/EventNodeImpl.java b/src/main/java/net/minestom/server/event/EventNodeImpl.java index e40dac7b7..5f92276f4 100644 --- a/src/main/java/net/minestom/server/event/EventNodeImpl.java +++ b/src/main/java/net/minestom/server/event/EventNodeImpl.java @@ -383,7 +383,7 @@ class EventNodeImpl implements EventNode { return; } - // Worth case scenario, try to run everything + // Worse case scenario, try to run everything this.listeners.add(e -> { if (hasPredicate) { final var value = filter.getHandler(e); From 234d26dce9268222b81fecedf277588c3fa26809 Mon Sep 17 00:00:00 2001 From: TheMode Date: Sun, 22 Aug 2021 05:02:44 +0200 Subject: [PATCH 24/24] Add some comments --- .../minestom/server/event/EventNodeImpl.java | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/src/main/java/net/minestom/server/event/EventNodeImpl.java b/src/main/java/net/minestom/server/event/EventNodeImpl.java index 5f92276f4..6eccb4234 100644 --- a/src/main/java/net/minestom/server/event/EventNodeImpl.java +++ b/src/main/java/net/minestom/server/event/EventNodeImpl.java @@ -309,7 +309,7 @@ class EventNodeImpl implements EventNode { // Mapped nodes handleMappedNode(targetNode); // Add children - final var children = targetNode.children; + final Set> children = targetNode.children; if (children.isEmpty()) return; children.stream() .filter(child -> child.eventType.isAssignableFrom(eventType)) // Invalid event type @@ -317,6 +317,10 @@ class EventNodeImpl implements EventNode { .forEach(this::recursiveUpdate); } + /** + * Add the node's listeners from {@link EventNode#map(EventNode, Object)}. + * The goal is to limit the amount of map lookup. + */ private void handleMappedNode(EventNodeImpl targetNode) { final var mappedNodeCache = targetNode.mappedNodeCache; if (mappedNodeCache.isEmpty()) return; @@ -325,7 +329,7 @@ class EventNodeImpl implements EventNode { // Retrieve all filters used to retrieve potential handlers for (var mappedEntry : mappedNodeCache.entrySet()) { final EventNodeImpl mappedNode = mappedEntry.getValue(); - final var handle = (Handle) mappedNode.getHandle(eventType); + final Handle handle = (Handle) mappedNode.getHandle(eventType); if (!mappedNode.hasListener(handle)) continue; // Implicit update filters.add(mappedNode.filter); handlers.put(mappedEntry.getKey(), handle); @@ -337,8 +341,8 @@ class EventNodeImpl implements EventNode { final int size = filterList.size(); final BiConsumer, E> mapper = (filter, event) -> { final Object handler = filter.castHandler(event); - final var handle = handlers.get(handler); - if (handle != null) { + final Handle handle = handlers.get(handler); + if (handle != null) { // Run the listeners of the mapped node if (!handle.updated) handle.update(); for (Consumer listener : handle.listeners) { listener.accept(event); @@ -366,13 +370,19 @@ class EventNodeImpl implements EventNode { } } + /** + * Add listeners from {@link EventNode#addListener(EventListener)} and + * {@link EventNode#register(EventBinding)} to the handle list. + *

+ * Most computation should ideally be done outside the consumers as a one-time cost. + */ private void appendEntries(ListenerEntry entry, EventNodeImpl targetNode) { final var filter = targetNode.filter; final var predicate = targetNode.predicate; final boolean hasPredicate = predicate != null; - final var listenersCopy = List.copyOf(entry.listeners); - final var bindingsCopy = List.copyOf(entry.bindingConsumers); + final List> listenersCopy = List.copyOf(entry.listeners); + final List> bindingsCopy = List.copyOf(entry.bindingConsumers); if (!hasPredicate && listenersCopy.isEmpty() && bindingsCopy.isEmpty()) return; // Nothing to run @@ -386,7 +396,7 @@ class EventNodeImpl implements EventNode { // Worse case scenario, try to run everything this.listeners.add(e -> { if (hasPredicate) { - final var value = filter.getHandler(e); + final Object value = filter.getHandler(e); if (!predicate.test(e, value)) return; } if (!listenersCopy.isEmpty()) {