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 { +}