Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
net.minestom.server.event.EventNodeImpl Maven / Gradle / Ivy
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 super T> 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 extends T> 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 extends T> child) {
synchronized (GLOBAL_CHILD_LOCK) {
final var childImpl = (EventNodeImpl extends T>) 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 extends T> child) {
synchronized (GLOBAL_CHILD_LOCK) {
final var childImpl = (EventNodeImpl extends T>) 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 extends T> 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 extends T> 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 extends T> binding) {
synchronized (GLOBAL_CHILD_LOCK) {
for (var eventType : binding.eventTypes()) {
ListenerEntry entry = getEntry((Class extends T>) eventType);
final boolean added = entry.bindingConsumers.add((Consumer) binding.consumer(eventType));
if (added) invalidateEvent((Class extends T>) eventType);
}
}
}
@Override
public void unregister(@NotNull EventBinding extends T> 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 extends T>) 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 super T> 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 extends @NotNull Graph> 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 super T> node) {
assert Thread.holdsLock(GLOBAL_CHILD_LOCK);
for (Class extends T> eventType : listenerMap.keySet()) {
node.invalidateEvent(eventType);
}
// TODO bindings?
for (EventNodeImpl child : children) {
child.invalidateEventsFor(node);
}
}
private void invalidateEvent(Class extends T> eventClass) {
forTargetEvents(eventClass, type -> {
Handle handle = handleMap.computeIfAbsent(type,
aClass -> new Handle<>((Class) aClass));
handle.invalidate();
});
final EventNodeImpl super T> parent = this.parent;
if (parent != null) parent.invalidateEvent(eventClass);
}
private ListenerEntry getEntry(Class extends T> 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();
}
}
}
}