From e1c1f1d5450b6b4f575b922c1dcb03618b051940 Mon Sep 17 00:00:00 2001 From: TheMode Date: Mon, 16 Aug 2021 23:42:02 +0200 Subject: [PATCH] Make `EventNode` an interface --- .../net/minestom/server/event/EventNode.java | 370 +++--------------- .../minestom/server/event/EventNodeImpl.java | 356 +++++++++++++++++ 2 files changed, 408 insertions(+), 318 deletions(-) create mode 100644 src/main/java/net/minestom/server/event/EventNodeImpl.java diff --git a/src/main/java/net/minestom/server/event/EventNode.java b/src/main/java/net/minestom/server/event/EventNode.java index 018f60ec4..f867a6bd9 100644 --- a/src/main/java/net/minestom/server/event/EventNode.java +++ b/src/main/java/net/minestom/server/event/EventNode.java @@ -1,25 +1,18 @@ package net.minestom.server.event; -import com.github.benmanes.caffeine.cache.Cache; -import com.github.benmanes.caffeine.cache.Caffeine; -import net.minestom.server.MinecraftServer; import net.minestom.server.event.trait.CancellableEvent; import net.minestom.server.tag.Tag; import net.minestom.server.tag.TagReadable; -import net.minestom.server.utils.validate.Check; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -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.List; +import java.util.Set; import java.util.function.BiPredicate; import java.util.function.Consumer; import java.util.function.Predicate; -import java.util.stream.Collectors; /** * Represents a single node in an event graph. @@ -30,7 +23,8 @@ import java.util.stream.Collectors; * * @param The event type accepted by this node */ -public class EventNode { +@ApiStatus.NonExtendable +public interface EventNode { /** * Creates an event node which accepts any event type with no filtering. @@ -39,7 +33,7 @@ public class EventNode { * @return An event node with no filtering */ @Contract(value = "_ -> new", pure = true) - public static @NotNull EventNode all(@NotNull String name) { + static @NotNull EventNode all(@NotNull String name) { return type(name, EventFilter.ALL); } @@ -58,8 +52,8 @@ public class EventNode { * @return A node with just an event type filter */ @Contract(value = "_, _ -> new", pure = true) - public static @NotNull EventNode type(@NotNull String name, - @NotNull EventFilter filter) { + static @NotNull EventNode type(@NotNull String name, + @NotNull EventFilter filter) { return create(name, filter, null); } @@ -83,9 +77,9 @@ public class EventNode { * @return A node with an event type filter as well as a condition on the event. */ @Contract(value = "_, _, _ -> new", pure = true) - public static @NotNull EventNode event(@NotNull String name, - @NotNull EventFilter filter, - @NotNull Predicate predicate) { + static @NotNull EventNode event(@NotNull String name, + @NotNull EventFilter filter, + @NotNull Predicate predicate) { return create(name, filter, (e, h) -> predicate.test(e)); } @@ -111,9 +105,9 @@ public class EventNode { * @return A node with an event type filter as well as a condition on the event. */ @Contract(value = "_, _, _ -> new", pure = true) - public static @NotNull EventNode type(@NotNull String name, - @NotNull EventFilter filter, - @NotNull BiPredicate predicate) { + static @NotNull EventNode type(@NotNull String name, + @NotNull EventFilter filter, + @NotNull BiPredicate predicate) { return create(name, filter, predicate); } @@ -136,9 +130,9 @@ public class EventNode { * @return A node with an event type filter as well as a condition on the event. */ @Contract(value = "_, _, _ -> new", pure = true) - public static @NotNull EventNode value(@NotNull String name, - @NotNull EventFilter filter, - @NotNull Predicate predicate) { + static @NotNull EventNode value(@NotNull String name, + @NotNull EventFilter filter, + @NotNull Predicate predicate) { return create(name, filter, (e, h) -> predicate.test(h)); } @@ -155,9 +149,9 @@ public class EventNode { * @return A node with an event type filter as well as a handler with the provided tag */ @Contract(value = "_, _, _ -> new", pure = true) - public static @NotNull EventNode tag(@NotNull String name, - @NotNull EventFilter filter, - @NotNull Tag tag) { + static @NotNull EventNode tag(@NotNull String name, + @NotNull EventFilter filter, + @NotNull Tag tag) { return create(name, filter, (e, h) -> h.hasTag(tag)); } @@ -173,45 +167,18 @@ public class EventNode { * @return A node with an event type filter as well as a handler with the provided tag */ @Contract(value = "_, _, _, _ -> new", pure = true) - public static @NotNull EventNode tag(@NotNull String name, - @NotNull EventFilter filter, - @NotNull Tag tag, - @NotNull Predicate<@Nullable V> consumer) { + static @NotNull EventNode tag(@NotNull String name, + @NotNull EventFilter filter, + @NotNull Tag tag, + @NotNull Predicate<@Nullable V> consumer) { return create(name, filter, (e, h) -> consumer.test(h.getTag(tag))); } private static EventNode create(@NotNull String name, @NotNull EventFilter filter, @Nullable BiPredicate predicate) { - return new EventNode<>(name, filter, predicate != null ? (e, o) -> predicate.test(e, (V) o) : null); - } - - private static final Map, List>> HANDLER_SUPPLIERS = new ConcurrentHashMap<>(); - private static final Object GLOBAL_CHILD_LOCK = new Object(); - private final Object lock = new Object(); - - private final Map, ListenerEntry> listenerMap = new ConcurrentHashMap<>(); - private final Set> children = new CopyOnWriteArraySet<>(); - private final Set> interfaces = new CopyOnWriteArraySet<>(); - private final Map> mappedNode; - - protected final String name; - protected final EventFilter filter; - protected final BiPredicate predicate; - protected final Class eventType; - private volatile int priority; - private volatile EventNode parent; - - protected EventNode(@NotNull String name, - @NotNull EventFilter filter, - @Nullable BiPredicate predicate) { - this.name = name; - this.filter = filter; - this.predicate = predicate; - this.eventType = filter.eventType(); - - Cache> mapCache = Caffeine.newBuilder().weakKeys().build(); - this.mappedNode = mapCache.asMap(); + //noinspection unchecked + return new EventNodeImpl<>(name, filter, predicate != null ? (e, o) -> predicate.test(e, (V) o) : null); } /** @@ -223,61 +190,7 @@ public class EventNode { * * @param event the event to execute */ - 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; - } - } - // Event interfaces - if (!interfaces.isEmpty()) { - this.interfaces.forEach(eventInterface -> { - if (!eventInterface.eventTypes().contains(eventClass)) return; - eventInterface.call(event); - }); - } - // Mapped listeners - if (!mappedNode.isEmpty()) { - // Check mapped listeners for each individual event handler - getEventFilters(eventClass).forEach(filter -> { - final var handler = filter.castHandler(event); - final var map = mappedNode.get(handler); - if (map != null) map.call(event); - }); - } - // Process listener list - final var entry = listenerMap.get(eventClass); - if (entry == null) return; // No listener nor children - - final var listeners = entry.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); - } - } - } - // Process children - if (entry.childCount > 0) { - this.children.stream() - .sorted(Comparator.comparing(EventNode::getPriority)) - .forEach(child -> child.call(event)); - } - } + void call(@NotNull T event); /** * Execute a cancellable event with a callback to execute if the event is successful. @@ -286,7 +199,7 @@ public class EventNode { * @param event The event to execute * @param successCallback A callback if the event is not cancelled */ - public void callCancellable(@NotNull T event, @NotNull Runnable successCallback) { + default void callCancellable(@NotNull T event, @NotNull Runnable successCallback) { call(event); if (!(event instanceof CancellableEvent) || !((CancellableEvent) event).isCancelled()) { successCallback.run(); @@ -294,25 +207,19 @@ public class EventNode { } @Contract(pure = true) - public @NotNull String getName() { - return name; - } + @NotNull Class getEventType(); @Contract(pure = true) - public int getPriority() { - return priority; - } + @NotNull String getName(); + + @Contract(pure = true) + int getPriority(); @Contract(value = "_ -> this") - public @NotNull EventNode setPriority(int priority) { - this.priority = priority; - return this; - } + @NotNull EventNode setPriority(int priority); @Contract(pure = true) - public @Nullable EventNode getParent() { - return parent; - } + @Nullable EventNode getParent(); /** * Returns an unmodifiable view of the children in this node. @@ -321,9 +228,7 @@ public class EventNode { * @see #removeChild(EventNode) */ @Contract(pure = true) - public @NotNull Set<@NotNull EventNode> getChildren() { - return Collections.unmodifiableSet(children); - } + @NotNull Set<@NotNull EventNode> getChildren(); /** * Locates all child nodes with the given name and event type recursively starting at this node. @@ -333,19 +238,7 @@ public class EventNode { * @return All matching event nodes */ @Contract(pure = true) - public @NotNull List> findChildren(@NotNull String name, Class eventType) { - if (children.isEmpty()) return Collections.emptyList(); - synchronized (GLOBAL_CHILD_LOCK) { - List> result = new ArrayList<>(); - for (EventNode child : children) { - if (EventNode.equals(child, name, eventType)) { - result.add((EventNode) child); - } - result.addAll(child.findChildren(name, eventType)); - } - return result; - } - } + @NotNull List> findChildren(@NotNull String name, Class eventType); /** * Locates all child nodes with the given name and event type recursively starting at this node. @@ -354,8 +247,8 @@ public class EventNode { * @return All matching event nodes */ @Contract(pure = true) - public @NotNull List> findChildren(@NotNull String name) { - return findChildren(name, eventType); + default @NotNull List> findChildren(@NotNull String name) { + return findChildren(name, getEventType()); } /** @@ -367,19 +260,7 @@ public class EventNode { * @param eventType The event node type to filter for * @param eventNode The replacement node */ - public void replaceChildren(@NotNull String name, @NotNull Class eventType, @NotNull EventNode eventNode) { - if (children.isEmpty()) return; - synchronized (GLOBAL_CHILD_LOCK) { - for (EventNode child : children) { - if (EventNode.equals(child, name, eventType)) { - removeChild(child); - addChild(eventNode); - continue; - } - child.replaceChildren(name, eventType, eventNode); - } - } - } + void replaceChildren(@NotNull String name, @NotNull Class eventType, @NotNull EventNode eventNode); /** * Replaces all children matching the given name and type recursively starting from this node. @@ -389,8 +270,8 @@ public class EventNode { * @param name The node name to filter for * @param eventNode The replacement node */ - public void replaceChildren(@NotNull String name, @NotNull EventNode eventNode) { - replaceChildren(name, eventType, eventNode); + default void replaceChildren(@NotNull String name, @NotNull EventNode eventNode) { + replaceChildren(name, getEventType(), eventNode); } /** @@ -399,27 +280,14 @@ public class EventNode { * @param name The node name to filter for * @param eventType The node type to filter for */ - public void removeChildren(@NotNull String name, @NotNull Class eventType) { - if (children.isEmpty()) return; - synchronized (GLOBAL_CHILD_LOCK) { - for (EventNode child : children) { - if (EventNode.equals(child, name, eventType)) { - removeChild(child); - continue; - } - child.removeChildren(name, eventType); - } - } - } + void removeChildren(@NotNull String name, @NotNull Class eventType); /** * Recursively removes children with the given name starting at this node. * * @param name The node name to filter for */ - public void removeChildren(@NotNull String name) { - removeChildren(name, eventType); - } + void removeChildren(@NotNull String name); /** * Directly adds a child node to this node. @@ -428,26 +296,7 @@ public class EventNode { * @return this, can be used for chaining */ @Contract(value = "_ -> this") - public @NotNull EventNode addChild(@NotNull EventNode child) { - synchronized (GLOBAL_CHILD_LOCK) { - Check.stateCondition(child.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((EventNode) child); - if (result) { - child.parent = this; - // Increase listener count - synchronized (lock) { - child.listenerMap.forEach((eventClass, eventListeners) -> { - final var entry = child.listenerMap.get(eventClass); - if (entry == null) return; - final int childCount = entry.listeners.size() + entry.childCount; - increaseChildListenerCount(eventClass, childCount); - }); - } - } - } - return this; - } + @NotNull EventNode addChild(@NotNull EventNode child); /** * Directly removes the given child from this node. @@ -456,135 +305,20 @@ public class EventNode { * @return this, can be used for chaining */ @Contract(value = "_ -> this") - public @NotNull EventNode removeChild(@NotNull EventNode child) { - synchronized (GLOBAL_CHILD_LOCK) { - final boolean result = this.children.remove(child); - if (result) { - child.parent = null; - // Decrease listener count - synchronized (lock) { - child.listenerMap.forEach((eventClass, eventListeners) -> { - final var entry = child.listenerMap.get(eventClass); - if (entry == null) - return; - final int childCount = entry.listeners.size() + entry.childCount; - decreaseChildListenerCount(eventClass, childCount); - }); - } - } - } - return this; - } + @NotNull EventNode removeChild(@NotNull EventNode child); @Contract(value = "_ -> this") - public @NotNull EventNode addListener(@NotNull EventListener listener) { - synchronized (GLOBAL_CHILD_LOCK) { - final var eventType = listener.getEventType(); - var entry = listenerMap.computeIfAbsent(eventType, aClass -> new ListenerEntry<>()); - entry.listeners.add((EventListener) listener); - if (parent != null) { - synchronized (parent.lock) { - parent.increaseChildListenerCount(eventType, 1); - } - } - } - return this; - } + @NotNull EventNode addListener(@NotNull EventListener listener); @Contract(value = "_, _ -> this") - public @NotNull EventNode addListener(@NotNull Class eventType, @NotNull Consumer<@NotNull E> listener) { - return addListener(EventListener.of(eventType, listener)); - } + @NotNull EventNode addListener(@NotNull Class eventType, @NotNull Consumer<@NotNull E> listener); @Contract(value = "_ -> this") - public @NotNull EventNode removeListener(@NotNull EventListener listener) { - synchronized (GLOBAL_CHILD_LOCK) { - final var eventType = listener.getEventType(); - var entry = listenerMap.get(eventType); - if (entry == null) return this; - var listeners = entry.listeners; - final boolean removed = listeners.remove(listener); - if (removed && parent != null) { - synchronized (parent.lock) { - parent.decreaseChildListenerCount(eventType, 1); - } - } - } - return this; - } + @NotNull EventNode removeListener(@NotNull EventListener listener); - public void map(@NotNull EventNode node, @NotNull Object value) { - final var nodeType = node.eventType; - final var valueType = value.getClass(); - final boolean correct = getEventFilters(nodeType).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}", nodeType, valueType); - //noinspection unchecked - this.mappedNode.put(value, (EventNode) node); - } + void map(@NotNull EventNode node, @NotNull Object value); - public boolean unmap(@NotNull Object value) { - return mappedNode.remove(value) != null; - } + boolean unmap(@NotNull Object value); - public void registerInterface(@NotNull EventInterface eventInterface) { - this.interfaces.add((EventInterface) eventInterface); - } - - private void increaseChildListenerCount(Class eventClass, int count) { - var entry = listenerMap.computeIfAbsent(eventClass, aClass -> new ListenerEntry<>()); - ListenerEntry.addAndGet(entry, count); - if (parent != null) { - parent.increaseChildListenerCount(eventClass, count); - } - } - - private void decreaseChildListenerCount(Class eventClass, int count) { - var entry = listenerMap.computeIfAbsent(eventClass, aClass -> new ListenerEntry<>()); - final int result = ListenerEntry.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.decreaseChildListenerCount(eventClass, count); - } - } - - private static boolean equals(EventNode node, String name, Class eventType) { - final boolean nameCheck = node.getName().equals(name); - final boolean typeCheck = eventType.isAssignableFrom(node.eventType); - return nameCheck && typeCheck; - } - - private static final List> FILTERS = List.of( - EventFilter.ENTITY, - EventFilter.ITEM, EventFilter.INSTANCE, - EventFilter.INVENTORY, EventFilter.BLOCK); - - /** - * Returns a list of (event->object) functions used to retrieve handler. - * For example `PlayerUseItemEvent` should return a function to retrieve the player, - * and another for the item. - * Event traits are currently hardcoded. - */ - private static List> getEventFilters(Class eventType) { - return HANDLER_SUPPLIERS.computeIfAbsent(eventType, clazz -> - FILTERS.stream().filter(eventFilter -> eventFilter.eventType().isAssignableFrom(clazz)).collect(Collectors.toList())); - } - - private static class ListenerEntry { - private static final AtomicIntegerFieldUpdater CHILD_UPDATER = - AtomicIntegerFieldUpdater.newUpdater(ListenerEntry.class, "childCount"); - - List> listeners = new CopyOnWriteArrayList<>(); - volatile int childCount; - - private static int addAndGet(ListenerEntry entry, int add) { - return CHILD_UPDATER.addAndGet(entry, add); - } - } + void registerInterface(@NotNull EventInterface eventInterface); } diff --git a/src/main/java/net/minestom/server/event/EventNodeImpl.java b/src/main/java/net/minestom/server/event/EventNodeImpl.java new file mode 100644 index 000000000..b4a48d139 --- /dev/null +++ b/src/main/java/net/minestom/server/event/EventNodeImpl.java @@ -0,0 +1,356 @@ +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; +import org.jetbrains.annotations.Nullable; + +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, ListenerEntry> listenerMap = new ConcurrentHashMap<>(); + private final Set> children = new CopyOnWriteArraySet<>(); + private final Map> mappedNodeCache = new WeakHashMap<>(); + + private final String name; + private final EventFilter filter; + private final BiPredicate predicate; + private final Class eventType; + private volatile int priority; + private volatile EventNodeImpl parent; + + protected EventNodeImpl(@NotNull String name, + @NotNull EventFilter filter, + @Nullable BiPredicate predicate) { + this.name = name; + this.filter = filter; + this.predicate = predicate; + this.eventType = filter.eventType(); + } + + @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; + } + } + // Process listeners list + final var entry = listenerMap.get(eventClass); + if (entry == null) return; // No listener nor children + { + // Event interfaces + final var interfaces = entry.interfaces; + if (!interfaces.isEmpty()) { + for (EventInterface inter : interfaces) { + if (!inter.eventTypes().contains(eventClass)) continue; + inter.call(event); + } + } + // Mapped listeners + final var mapped = entry.mappedNode; + if (!mapped.isEmpty()) { + synchronized (mappedNodeCache) { + if (!mapped.isEmpty()) { + // Check mapped listeners for each individual event handler + for (var filter : entry.filters) { + final var handler = filter.castHandler(event); + final var map = mapped.get(handler); + if (map != null) map.call(event); + } + } + } + } + // Basic listeners + final var listeners = entry.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); + } + } + } + } + // Process children + if (entry.childCount > 0) { + this.children.stream() + .sorted(Comparator.comparing(EventNode::getPriority)) + .forEach(child -> child.call(event)); + } + } + + @Override + public @NotNull List> findChildren(@NotNull String name, Class eventType) { + if (children.isEmpty()) return Collections.emptyList(); + synchronized (GLOBAL_CHILD_LOCK) { + List> result = new ArrayList<>(); + for (EventNode child : children) { + if (equals(child, name, eventType)) { + result.add((EventNode) child); + } + result.addAll(child.findChildren(name, eventType)); + } + return result; + } + } + + @Contract(pure = true) + public @NotNull Set<@NotNull EventNode> getChildren() { + return Collections.unmodifiableSet(children); + } + + @Override + public void replaceChildren(@NotNull String name, @NotNull Class eventType, @NotNull EventNode eventNode) { + if (children.isEmpty()) return; + synchronized (GLOBAL_CHILD_LOCK) { + for (EventNode child : children) { + if (equals(child, name, eventType)) { + removeChild(child); + addChild(eventNode); + continue; + } + child.replaceChildren(name, eventType, eventNode); + } + } + } + + @Override + public void removeChildren(@NotNull String name, @NotNull Class eventType) { + if (children.isEmpty()) return; + synchronized (GLOBAL_CHILD_LOCK) { + for (EventNode child : children) { + if (equals(child, name, eventType)) { + removeChild(child); + continue; + } + child.removeChildren(name, eventType); + } + } + } + + @Override + public void removeChildren(@NotNull String name) { + removeChildren(name, eventType); + } + + @Override + public @NotNull EventNode addChild(@NotNull EventNode child) { + synchronized (GLOBAL_CHILD_LOCK) { + 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) { + childImpl.parent = this; + // Increase listener count + propagateNode(childImpl, IntUnaryOperator.identity()); + } + } + return this; + } + + @Override + 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); + } + } + return this; + } + + @Override + public @NotNull EventNode addListener(@NotNull EventListener listener) { + synchronized (GLOBAL_CHILD_LOCK) { + final var eventType = listener.getEventType(); + var entry = getEntry(eventType); + entry.listeners.add((EventListener) listener); + final var parent = this.parent; + if (parent != null) { + synchronized (parent.lock) { + parent.propagateChildCountChange(eventType, 1); + } + } + } + return this; + } + + @Override + public @NotNull EventNode addListener(@NotNull Class eventType, @NotNull Consumer<@NotNull E> listener) { + return addListener(EventListener.of(eventType, listener)); + } + + @Override + public @NotNull EventNode removeListener(@NotNull EventListener listener) { + synchronized (GLOBAL_CHILD_LOCK) { + final var eventType = listener.getEventType(); + var entry = listenerMap.get(eventType); + if (entry == null) return this; + var listeners = entry.listeners; + final boolean removed = listeners.remove(listener); + if (removed && parent != null) { + synchronized (parent.lock) { + parent.propagateChildCountChange(eventType, -1); + } + } + } + return this; + } + + @Override + public void map(@NotNull EventNode node, @NotNull Object value) { + final var nodeImpl = (EventNodeImpl) node; + final var nodeType = nodeImpl.eventType; + 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}", nodeType, valueType); + synchronized (mappedNodeCache) { + entry.mappedNode.put(value, (EventNode) node); + mappedNodeCache.put(value, entry); + } + }); + } + } + + @Override + public boolean unmap(@NotNull Object value) { + synchronized (GLOBAL_CHILD_LOCK) { + synchronized (mappedNodeCache) { + var entry = mappedNodeCache.remove(value); + if (entry == null) return false; + return entry.mappedNode.remove(value) != null; + } + } + } + + @Override + public void registerInterface(@NotNull EventInterface eventInterface) { + synchronized (GLOBAL_CHILD_LOCK) { + for (var eventType : eventInterface.eventTypes()) { + var entry = getEntry((Class) eventType); + entry.interfaces.add((EventInterface) eventInterface); + } + } + } + + @Override + public @NotNull Class getEventType() { + return eventType; + } + + @Override + public @NotNull String getName() { + return name; + } + + @Override + public int getPriority() { + return priority; + } + + @Override + public @NotNull EventNode setPriority(int priority) { + this.priority = priority; + return this; + } + + @Override + public @Nullable EventNode getParent() { + return parent; + } + + private void propagateChildCountChange(Class eventClass, int count) { + var entry = getEntry(eventClass); + final int result = ListenerEntry.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 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)); + }); + } + } + + private ListenerEntry getEntry(Class type) { + return listenerMap.computeIfAbsent(type, aClass -> new ListenerEntry<>((Class) aClass)); + } + + private static boolean equals(EventNode node, String name, Class eventType) { + final boolean nameCheck = node.getName().equals(name); + final boolean typeCheck = eventType.isAssignableFrom(((EventNodeImpl) node).eventType); + return nameCheck && typeCheck; + } + + 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 List> filters; + final List> listeners = new CopyOnWriteArrayList<>(); + final Set> interfaces = new CopyOnWriteArraySet<>(); + final Map> mappedNode = new WeakHashMap<>(); + volatile int childCount; + + ListenerEntry(Class eventType) { + this.filters = FILTERS.stream().filter(eventFilter -> eventFilter.eventType().isAssignableFrom(eventType)).collect(Collectors.toList()); + } + + private static int addAndGet(ListenerEntry entry, int add) { + return CHILD_UPDATER.addAndGet(entry, add); + } + } +}