All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.minestom.server.event.EventNodeImpl Maven / Gradle / Ivy

There is a newer version: 7320437640
Show newest version
package net.minestom.server.event;

import net.minestom.server.MinecraftServer;
import net.minestom.server.ServerFlag;
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.ref.WeakReference;
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 {

    static final Object GLOBAL_CHILD_LOCK = new Object();

    private final Map> handleMap = new ConcurrentHashMap<>();
    final Map, ListenerEntry> listenerMap = new ConcurrentHashMap<>();
    final Set> children = new CopyOnWriteArraySet<>();

    // Used to store mapped nodes before any listener is added
    // Necessary to avoid creating multiple nodes for the same object
    // Always accessed through the global lock.
    final Map>> mappedNodeCache = new WeakHashMap<>();
    // Store mapped nodes with at least one listener
    // Map is copied and mutated for each new active mapped node
    // Can be considered immutable.
    volatile Map>> registeredMappedNode = new WeakHashMap<>();

    final String name;
    final EventFilter filter;
    final BiPredicate predicate;
    final Class eventType;
    volatile int priority;
    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
    @SuppressWarnings("unchecked")
    public  @NotNull ListenerHandle getHandle(@NotNull Class handleType) {
        return (ListenerHandle) handleMap.computeIfAbsent(handleType,
                aClass -> new Handle<>((Class) aClass));
    }

    @Override
    public  @NotNull List> findChildren(@NotNull String name, Class eventType) {
        synchronized (GLOBAL_CHILD_LOCK) {
            final Set> children = getChildren();
            if (children.isEmpty()) return List.of();
            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) {
            final Set> children = getChildren();
            if (children.isEmpty()) return;
            for (EventNode child : children) {
                if (equals(child, name, eventType)) {
                    removeChild(child);
                    addChild(eventNode);
                    break;
                }
                child.replaceChildren(name, eventType, eventNode);
            }
        }
    }

    @Override
    public void removeChildren(@NotNull String name, @NotNull Class eventType) {
        synchronized (GLOBAL_CHILD_LOCK) {
            final Set> children = getChildren();
            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(!ServerFlag.EVENT_NODE_ALLOW_MULTIPLE_PARENTS && 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 @NotNull  EventNode map(@NotNull H value, @NotNull EventFilter filter) {
        EventNodeImpl node;
        synchronized (GLOBAL_CHILD_LOCK) {
            node = new EventNodeLazyImpl<>(this, value, filter);
            Check.stateCondition(node.parent != null, "Node already has a parent");
            Check.stateCondition(Objects.equals(parent, node), "Cannot map to self");
            WeakReference> previousRef = this.mappedNodeCache.putIfAbsent(value,
                    new WeakReference<>((EventNodeLazyImpl) node));
            EventNodeImpl previous;
            if (previousRef != null && (previous = previousRef.get()) != null) return (EventNode) previous;
            node.parent = this;
        }
        return node;
    }

    @Override
    public void unmap(@NotNull Object value) {
        synchronized (GLOBAL_CHILD_LOCK) {
            Map>> registered = new WeakHashMap<>(registeredMappedNode);
            final WeakReference> mappedNodeRef = registered.remove(value);
            this.registeredMappedNode = registered;
            EventNodeLazyImpl mappedNode;
            if (mappedNodeRef != null && (mappedNode = mappedNodeRef.get()) != null) {
                mappedNode.invalidateEventsFor(this);
            }
        }
    }

    @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() {
        Check.stateCondition(ServerFlag.EVENT_NODE_ALLOW_MULTIPLE_PARENTS, "Cannot use getParent when multiple parents are allowed");
        return parent;
    }

    @Override
    public String toString() {
        return createStringGraph(createGraph());
    }

    Graph createGraph() {
        synchronized (GLOBAL_CHILD_LOCK) {
            List children = this.children.stream().map(EventNodeImpl::createGraph).toList();
            return new Graph(getName(), getEventType().getSimpleName(), getPriority(), children);
        }
    }

    static String createStringGraph(Graph graph) {
        StringBuilder buffer = new StringBuilder();
        genToStringTree(buffer, "", "", graph);
        return buffer.toString();
    }

    private static void genToStringTree(StringBuilder buffer, String prefix, String childrenPrefix, Graph graph) {
        buffer.append(prefix);
        buffer.append(String.format("%s - EventType: %s - Priority: %d", graph.name(), graph.eventType(), graph.priority()));
        buffer.append('\n');
        var nextNodes = graph.children();
        for (Iterator iterator = nextNodes.iterator(); iterator.hasNext(); ) {
            Graph next = iterator.next();
            if (iterator.hasNext()) {
                genToStringTree(buffer, childrenPrefix + '├' + '─' + " ", childrenPrefix + '│' + "   ", next);
            } else {
                genToStringTree(buffer, childrenPrefix + '└' + '─' + " ", childrenPrefix + "    ", next);
            }
        }
    }

    record Graph(String name, String eventType, int priority,
                 List children) {
        public Graph {
            children = children.stream().sorted(Comparator.comparingInt(Graph::priority)).toList();
        }
    }

    void invalidateEventsFor(EventNodeImpl node) {
        assert Thread.holdsLock(GLOBAL_CHILD_LOCK);
        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.computeIfAbsent(type,
                    aClass -> new Handle<>((Class) aClass));
            handle.invalidate();
        });
        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<>();
    }

    @SuppressWarnings("unchecked")
    final class Handle implements ListenerHandle {
        private final Class eventType;
        private Consumer listener = null;
        private volatile boolean updated;

        Handle(Class eventType) {
            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;
        }

        void invalidate() {
            this.updated = false;
            this.listener = null;
        }

        @Nullable Consumer updatedListener() {
            if (updated) return listener;
            synchronized (GLOBAL_CHILD_LOCK) {
                if (updated) return listener;
                final Consumer listener = createConsumer();
                this.listener = listener;
                this.updated = true;
                return listener;
            }
        }

        private @Nullable Consumer createConsumer() {
            var node = (EventNodeImpl) EventNodeImpl.this;
            // 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(Object, EventFilter)}. * The goal is to limit the amount of map lookup. */ private @Nullable Consumer mappedConsumer() { var node = (EventNodeImpl) EventNodeImpl.this; final var mappedNodeCache = node.registeredMappedNode; if (mappedNodeCache.isEmpty()) return null; Set> filters = new HashSet<>(mappedNodeCache.size()); Map>> handlers = new WeakHashMap<>(mappedNodeCache.size()); // Retrieve all filters used to retrieve potential handlers for (var mappedEntry : mappedNodeCache.entrySet()) { final WeakReference> mappedNodeRef = mappedEntry.getValue(); final EventNodeLazyImpl mappedNode = mappedNodeRef.get(); if (mappedNode == null) continue; // Weak reference collected final Handle handle = (Handle) mappedNode.getHandle(eventType); if (!handle.hasListener()) continue; // Implicit update filters.add(mappedNode.filter); handlers.put(mappedEntry.getKey(), new WeakReference<>(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 WeakReference> handleRef = handlers.get(handler); final Handle handle = handleRef != null ? handleRef.get() : null; 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) { var node = (EventNodeImpl) EventNodeImpl.this; EventListener.Result result = listener.run(event); if (result == EventListener.Result.EXPIRED) { node.removeListener(listener); invalidate(); } } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy