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; import org.jetbrains.annotations.Nullable; import java.lang.invoke.MethodHandles; import java.lang.invoke.VarHandle; 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; non-sealed 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 @NotNull ListenerHandle getHandle(@NotNull Class handleType) { //noinspection unchecked return (ListenerHandle) handleMap.computeIfAbsent(handleType, aClass -> new Handle<>(this, (Class) aClass)); } @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 @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.invalidateEventsFor(this); } 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.parent = null; childImpl.invalidateEventsFor(this); } 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); invalidateEvent(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 if (entry.listeners.remove(listener)) invalidateEvent(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"); EventNodeImpl previous = this.mappedNodeCache.put(value, (EventNodeImpl) nodeImpl); if (previous != null) previous.parent = null; nodeImpl.parent = this; nodeImpl.invalidateEventsFor(this); } } @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.parent = null; childImpl.invalidateEventsFor(this); 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) invalidateEvent((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) invalidateEvent((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 invalidateEventsFor(EventNodeImpl node) { for (Class eventType : listenerMap.keySet()) { node.invalidateEvent(eventType); } // TODO bindings? for (EventNodeImpl child : children) { child.invalidateEventsFor(node); } } private void invalidateEvent(Class eventClass) { forTargetEvents(eventClass, type -> { Handle handle = handleMap.get(type); if (handle != null) Handle.UPDATED.setRelease(handle, false); }); final EventNodeImpl parent = this.parent; if (parent != null) parent.invalidateEvent(eventClass); } private ListenerEntry getEntry(Class type) { return listenerMap.computeIfAbsent(type, aClass -> new ListenerEntry<>()); } private static boolean equals(EventNode node, String name, Class eventType) { return node.getName().equals(name) && eventType.isAssignableFrom((node.getEventType())); } 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<>(); } static final class Handle implements ListenerHandle { private static final VarHandle UPDATED; static { try { UPDATED = MethodHandles.lookup().findVarHandle(Handle.class, "updated", boolean.class); } catch (NoSuchFieldException | IllegalAccessException e) { throw new IllegalStateException(e); } } private final EventNodeImpl node; private final Class eventType; private Consumer listener = null; @SuppressWarnings("unused") private boolean updated; // Use the UPDATED var handle Handle(EventNodeImpl node, Class eventType) { this.node = node; this.eventType = eventType; } @Override public void call(@NotNull E event) { final Consumer listener = updatedListener(); if (listener == null) return; try { listener.accept(event); } catch (Throwable e) { MinecraftServer.getExceptionManager().handleException(e); } } @Override public boolean hasListener() { return updatedListener() != null; } @Nullable Consumer updatedListener() { if ((boolean) UPDATED.getAcquire(this)) return listener; synchronized (GLOBAL_CHILD_LOCK) { if ((boolean) UPDATED.getAcquire(this)) return listener; final Consumer listener = createConsumer(); this.listener = listener; UPDATED.setRelease(this, true); return listener; } } private @Nullable Consumer createConsumer() { // Standalone listeners List> listeners = new ArrayList<>(); forTargetEvents(eventType, type -> { final ListenerEntry entry = node.listenerMap.get(type); if (entry != null) { final Consumer result = listenersConsumer(entry); if (result != null) listeners.add(result); } }); final Consumer[] listenersArray = listeners.toArray(Consumer[]::new); // Mapped final Consumer mappedListener = mappedConsumer(); // Children final Consumer[] childrenListeners = node.children.stream() .filter(child -> child.eventType.isAssignableFrom(eventType)) // Invalid event type .sorted(Comparator.comparing(EventNode::getPriority)) .map(child -> ((Handle) child.getHandle(eventType)).updatedListener()) .filter(Objects::nonNull) .toArray(Consumer[]::new); // Empty check final BiPredicate predicate = node.predicate; final EventFilter filter = node.filter; final boolean hasPredicate = predicate != null; final boolean hasListeners = listenersArray.length > 0; final boolean hasMap = mappedListener != null; final boolean hasChildren = childrenListeners.length > 0; if (!hasListeners && !hasMap && !hasChildren) { // No listener return null; } return e -> { // Filtering if (hasPredicate) { final Object value = filter.getHandler(e); if (!predicate.test(e, value)) return; } // Normal listeners if (hasListeners) { for (Consumer listener : listenersArray) { listener.accept(e); } } // Mapped nodes if (hasMap) mappedListener.accept(e); // Children if (hasChildren) { for (Consumer childHandle : childrenListeners) { childHandle.accept(e); } } }; } /** * Create a consumer calling all listeners from {@link EventNode#addListener(EventListener)} and * {@link EventNode#register(EventBinding)}. *

* Most computation should ideally be done outside the consumers as a one-time cost. */ private @Nullable Consumer listenersConsumer(@NotNull ListenerEntry entry) { final EventListener[] listenersCopy = entry.listeners.toArray(EventListener[]::new); final Consumer[] bindingsCopy = entry.bindingConsumers.toArray(Consumer[]::new); final boolean listenersEmpty = listenersCopy.length == 0; final boolean bindingsEmpty = bindingsCopy.length == 0; if (listenersEmpty && bindingsEmpty) return null; if (bindingsEmpty && listenersCopy.length == 1) { // Only one normal listener final EventListener listener = listenersCopy[0]; return e -> callListener(listener, e); } // Worse case scenario, try to run everything return e -> { if (!listenersEmpty) { for (EventListener listener : listenersCopy) { callListener(listener, e); } } if (!bindingsEmpty) { for (Consumer eConsumer : bindingsCopy) { eConsumer.accept(e); } } }; } /** * Create a consumer handling {@link EventNode#map(EventNode, Object)}. * The goal is to limit the amount of map lookup. */ private @Nullable Consumer mappedConsumer() { final var mappedNodeCache = node.mappedNodeCache; if (mappedNodeCache.isEmpty()) return null; Set> filters = new HashSet<>(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(); final Handle handle = (Handle) mappedNode.getHandle(eventType); if (!handle.hasListener()) continue; // Implicit update filters.add(mappedNode.filter); 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 if (filters.isEmpty()) return null; final EventFilter[] filterList = filters.toArray(EventFilter[]::new); final BiConsumer, E> mapper = (filter, event) -> { final Object handler = filter.castHandler(event); final Handle handle = handlers.get(handler); if (handle != null) handle.call(event); }; // Specialize the consumer depending on the number of filters to avoid looping return switch (filterList.length) { case 1 -> event -> mapper.accept(filterList[0], event); case 2 -> event -> { mapper.accept(filterList[0], event); mapper.accept(filterList[1], event); }; case 3 -> event -> { mapper.accept(filterList[0], event); mapper.accept(filterList[1], event); mapper.accept(filterList[2], event); }; default -> event -> { for (var filter : filterList) { mapper.accept(filter, event); } }; }; } void callListener(@NotNull EventListener listener, E event) { EventListener.Result result = listener.run(event); if (result == EventListener.Result.EXPIRED) { node.removeListener(listener); UPDATED.setRelease(this, false); } } } }