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; 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.function.BiConsumer; import java.util.function.BiPredicate; import java.util.function.Consumer; class EventNodeImpl implements EventNode { private static final Object GLOBAL_CHILD_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<>(); private final String name; private final EventFilter filter; private final BiPredicate predicate; private final Class eventType; private volatile int priority; private volatile EventNodeImpl parent; 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 E event, @NotNull ListenerHandle handle) { final Handle castedHandle = (Handle) handle; Check.argCondition(castedHandle.node != this, "Invalid handle owner"); if (!castedHandle.updated) castedHandle.update(); final List> listeners = castedHandle.listeners; if (listeners.isEmpty()) return; for (Consumer listener : listeners) { listener.accept(event); } } @Override public @NotNull ListenerHandle getHandle(@NotNull Class handleType) { //noinspection unchecked return (ListenerHandle) handleMap.computeIfAbsent(handleType, aClass -> new Handle<>(this, (Class) aClass)); } @Override public boolean hasListener(@NotNull ListenerHandle handle) { final Handle castedHandle = (Handle) handle; if (!castedHandle.updated) castedHandle.update(); return !castedHandle.listeners.isEmpty(); } @Override public @NotNull List> findChildren(@NotNull String name, Class eventType) { synchronized (GLOBAL_CHILD_LOCK) { if (children.isEmpty()) return Collections.emptyList(); 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) { synchronized (GLOBAL_CHILD_LOCK) { if (children.isEmpty()) return; 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) { synchronized (GLOBAL_CHILD_LOCK) { if (children.isEmpty()) return; 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"); if (!children.add((EventNodeImpl) childImpl)) return this; // Couldn't add the child (already present?) childImpl.parent = this; childImpl.propagateEvents(this); // Propagate after setting the parent } return this; } @Override public @NotNull EventNode removeChild(@NotNull EventNode child) { synchronized (GLOBAL_CHILD_LOCK) { final var childImpl = (EventNodeImpl) child; 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; } @Override public @NotNull EventNode addListener(@NotNull EventListener listener) { synchronized (GLOBAL_CHILD_LOCK) { final var eventType = listener.eventType(); ListenerEntry entry = getEntry(eventType); entry.listeners.add((EventListener) listener); propagateEvent(parent, eventType); } return this; } @Override public @NotNull EventNode removeListener(@NotNull EventListener listener) { synchronized (GLOBAL_CHILD_LOCK) { final var eventType = listener.eventType(); ListenerEntry entry = listenerMap.get(eventType); if (entry == null) return this; // There is no listener with such type var listeners = entry.listeners; if (listeners.remove(listener)) propagateEvent(parent, eventType); } return this; } @Override public void map(@NotNull EventNode node, @NotNull Object value) { 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.parent = null; nodeImpl.parent = this; nodeImpl.propagateEvents(this); // Propagate after setting the parent } } @Override public boolean unmap(@NotNull Object value) { synchronized (GLOBAL_CHILD_LOCK) { final var mappedNode = this.mappedNodeCache.remove(value); if (mappedNode == null) return false; // Mapped node not found final var childImpl = (EventNodeImpl) mappedNode; childImpl.propagateEvents(parent); // Propagate before removing the parent childImpl.parent = null; return true; } } @Override public void register(@NotNull EventBinding binding) { 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(parent, (Class) eventType); } } } @Override public void unregister(@NotNull EventBinding binding) { 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(parent, (Class) eventType); } } } @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 propagateEvents(EventNodeImpl parent) { this.listenerMap.keySet().forEach(aClass -> propagateEvent(parent, aClass)); } private void propagateEvent(EventNodeImpl parent, Class eventClass) { if (parent == null) return; forTargetEvents(eventClass, type -> { Handle parentHandle = (Handle) parent.handleMap.get(type); if (parentHandle == null) return; parentHandle.updated = false; parent.propagateEvent(parent.parent, type); }); } private ListenerEntry getEntry(Class type) { return listenerMap.computeIfAbsent(type, aClass -> new ListenerEntry<>()); } 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 void forTargetEvents(Class type, Consumer> consumer) { consumer.accept(type); // Recursion if (RecursiveEvent.class.isAssignableFrom(type)) { final Class superclass = type.getSuperclass(); if (superclass != null && RecursiveEvent.class.isAssignableFrom(superclass)) { forTargetEvents(superclass, consumer); } } } private static class ListenerEntry { final List> listeners = new CopyOnWriteArrayList<>(); final Set> bindingConsumers = new CopyOnWriteArraySet<>(); } private static final class Handle implements ListenerHandle { private final EventNodeImpl node; private final Class eventType; private final List> listeners = new CopyOnWriteArrayList<>(); private volatile boolean updated; Handle(EventNodeImpl node, Class eventType) { this.node = node; this.eventType = eventType; } void update() { synchronized (GLOBAL_CHILD_LOCK) { this.listeners.clear(); recursiveUpdate(node); this.updated = true; } } private void recursiveUpdate(EventNodeImpl targetNode) { // Standalone listeners forTargetEvents(eventType, type -> { final ListenerEntry entry = targetNode.listenerMap.get(type); if (entry != null) appendEntries(entry, targetNode); }); // Mapped nodes handleMappedNode(targetNode); // Add children final var children = targetNode.children; if (children.isEmpty()) return; children.stream() .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()); 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 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))); } // 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); // 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 -> { mapper.accept(firstFilter, event); mapper.accept(secondFilter, event); }); } else { this.listeners.add(event -> { for (var filter : filterList) { mapper.accept(filter, event); } }); } } } 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); 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; } 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) { 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); } } } }