Make `EventNode` an interface

This commit is contained in:
TheMode 2021-08-16 23:42:02 +02:00
parent 42a6654993
commit e1c1f1d545
2 changed files with 408 additions and 318 deletions

View File

@ -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 <T> The event type accepted by this node
*/
public class EventNode<T extends Event> {
@ApiStatus.NonExtendable
public interface EventNode<T extends Event> {
/**
* Creates an event node which accepts any event type with no filtering.
@ -39,7 +33,7 @@ public class EventNode<T extends Event> {
* @return An event node with no filtering
*/
@Contract(value = "_ -> new", pure = true)
public static @NotNull EventNode<Event> all(@NotNull String name) {
static @NotNull EventNode<Event> all(@NotNull String name) {
return type(name, EventFilter.ALL);
}
@ -58,8 +52,8 @@ public class EventNode<T extends Event> {
* @return A node with just an event type filter
*/
@Contract(value = "_, _ -> new", pure = true)
public static <E extends Event, V> @NotNull EventNode<E> type(@NotNull String name,
@NotNull EventFilter<E, V> filter) {
static <E extends Event, V> @NotNull EventNode<E> type(@NotNull String name,
@NotNull EventFilter<E, V> filter) {
return create(name, filter, null);
}
@ -83,9 +77,9 @@ public class EventNode<T extends Event> {
* @return A node with an event type filter as well as a condition on the event.
*/
@Contract(value = "_, _, _ -> new", pure = true)
public static <E extends Event, V> @NotNull EventNode<E> event(@NotNull String name,
@NotNull EventFilter<E, V> filter,
@NotNull Predicate<E> predicate) {
static <E extends Event, V> @NotNull EventNode<E> event(@NotNull String name,
@NotNull EventFilter<E, V> filter,
@NotNull Predicate<E> predicate) {
return create(name, filter, (e, h) -> predicate.test(e));
}
@ -111,9 +105,9 @@ public class EventNode<T extends Event> {
* @return A node with an event type filter as well as a condition on the event.
*/
@Contract(value = "_, _, _ -> new", pure = true)
public static <E extends Event, V> @NotNull EventNode<E> type(@NotNull String name,
@NotNull EventFilter<E, V> filter,
@NotNull BiPredicate<E, V> predicate) {
static <E extends Event, V> @NotNull EventNode<E> type(@NotNull String name,
@NotNull EventFilter<E, V> filter,
@NotNull BiPredicate<E, V> predicate) {
return create(name, filter, predicate);
}
@ -136,9 +130,9 @@ public class EventNode<T extends Event> {
* @return A node with an event type filter as well as a condition on the event.
*/
@Contract(value = "_, _, _ -> new", pure = true)
public static <E extends Event, V> @NotNull EventNode<E> value(@NotNull String name,
@NotNull EventFilter<E, V> filter,
@NotNull Predicate<V> predicate) {
static <E extends Event, V> @NotNull EventNode<E> value(@NotNull String name,
@NotNull EventFilter<E, V> filter,
@NotNull Predicate<V> predicate) {
return create(name, filter, (e, h) -> predicate.test(h));
}
@ -155,9 +149,9 @@ public class EventNode<T extends Event> {
* @return A node with an event type filter as well as a handler with the provided tag
*/
@Contract(value = "_, _, _ -> new", pure = true)
public static <E extends Event> @NotNull EventNode<E> tag(@NotNull String name,
@NotNull EventFilter<E, ? extends TagReadable> filter,
@NotNull Tag<?> tag) {
static <E extends Event> @NotNull EventNode<E> tag(@NotNull String name,
@NotNull EventFilter<E, ? extends TagReadable> filter,
@NotNull Tag<?> tag) {
return create(name, filter, (e, h) -> h.hasTag(tag));
}
@ -173,45 +167,18 @@ public class EventNode<T extends Event> {
* @return A node with an event type filter as well as a handler with the provided tag
*/
@Contract(value = "_, _, _, _ -> new", pure = true)
public static <E extends Event, V> @NotNull EventNode<E> tag(@NotNull String name,
@NotNull EventFilter<E, ? extends TagReadable> filter,
@NotNull Tag<V> tag,
@NotNull Predicate<@Nullable V> consumer) {
static <E extends Event, V> @NotNull EventNode<E> tag(@NotNull String name,
@NotNull EventFilter<E, ? extends TagReadable> filter,
@NotNull Tag<V> tag,
@NotNull Predicate<@Nullable V> consumer) {
return create(name, filter, (e, h) -> consumer.test(h.getTag(tag)));
}
private static <E extends Event, V> EventNode<E> create(@NotNull String name,
@NotNull EventFilter<E, V> filter,
@Nullable BiPredicate<E, V> predicate) {
return new EventNode<>(name, filter, predicate != null ? (e, o) -> predicate.test(e, (V) o) : null);
}
private static final Map<Class<? extends Event>, List<EventFilter<?, ?>>> HANDLER_SUPPLIERS = new ConcurrentHashMap<>();
private static final Object GLOBAL_CHILD_LOCK = new Object();
private final Object lock = new Object();
private final Map<Class<? extends T>, ListenerEntry<T>> listenerMap = new ConcurrentHashMap<>();
private final Set<EventNode<T>> children = new CopyOnWriteArraySet<>();
private final Set<EventInterface<T>> interfaces = new CopyOnWriteArraySet<>();
private final Map<Object, EventNode<T>> mappedNode;
protected final String name;
protected final EventFilter<T, ?> filter;
protected final BiPredicate<T, Object> predicate;
protected final Class<T> eventType;
private volatile int priority;
private volatile EventNode<? super T> parent;
protected EventNode(@NotNull String name,
@NotNull EventFilter<T, ?> filter,
@Nullable BiPredicate<T, Object> predicate) {
this.name = name;
this.filter = filter;
this.predicate = predicate;
this.eventType = filter.eventType();
Cache<Object, EventNode<T>> 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<T extends Event> {
*
* @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<T> 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<T extends Event> {
* @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<T extends Event> {
}
@Contract(pure = true)
public @NotNull String getName() {
return name;
}
@NotNull Class<T> getEventType();
@Contract(pure = true)
public int getPriority() {
return priority;
}
@NotNull String getName();
@Contract(pure = true)
int getPriority();
@Contract(value = "_ -> this")
public @NotNull EventNode<T> setPriority(int priority) {
this.priority = priority;
return this;
}
@NotNull EventNode<T> setPriority(int priority);
@Contract(pure = true)
public @Nullable EventNode<? super T> getParent() {
return parent;
}
@Nullable EventNode<? super T> getParent();
/**
* Returns an unmodifiable view of the children in this node.
@ -321,9 +228,7 @@ public class EventNode<T extends Event> {
* @see #removeChild(EventNode)
*/
@Contract(pure = true)
public @NotNull Set<@NotNull EventNode<T>> getChildren() {
return Collections.unmodifiableSet(children);
}
@NotNull Set<@NotNull EventNode<T>> getChildren();
/**
* Locates all child nodes with the given name and event type recursively starting at this node.
@ -333,19 +238,7 @@ public class EventNode<T extends Event> {
* @return All matching event nodes
*/
@Contract(pure = true)
public <E extends T> @NotNull List<EventNode<E>> findChildren(@NotNull String name, Class<E> eventType) {
if (children.isEmpty()) return Collections.emptyList();
synchronized (GLOBAL_CHILD_LOCK) {
List<EventNode<E>> result = new ArrayList<>();
for (EventNode<T> child : children) {
if (EventNode.equals(child, name, eventType)) {
result.add((EventNode<E>) child);
}
result.addAll(child.findChildren(name, eventType));
}
return result;
}
}
<E extends T> @NotNull List<EventNode<E>> findChildren(@NotNull String name, Class<E> eventType);
/**
* Locates all child nodes with the given name and event type recursively starting at this node.
@ -354,8 +247,8 @@ public class EventNode<T extends Event> {
* @return All matching event nodes
*/
@Contract(pure = true)
public @NotNull List<EventNode<T>> findChildren(@NotNull String name) {
return findChildren(name, eventType);
default @NotNull List<EventNode<T>> findChildren(@NotNull String name) {
return findChildren(name, getEventType());
}
/**
@ -367,19 +260,7 @@ public class EventNode<T extends Event> {
* @param eventType The event node type to filter for
* @param eventNode The replacement node
*/
public <E extends T> void replaceChildren(@NotNull String name, @NotNull Class<E> eventType, @NotNull EventNode<E> eventNode) {
if (children.isEmpty()) return;
synchronized (GLOBAL_CHILD_LOCK) {
for (EventNode<T> child : children) {
if (EventNode.equals(child, name, eventType)) {
removeChild(child);
addChild(eventNode);
continue;
}
child.replaceChildren(name, eventType, eventNode);
}
}
}
<E extends T> void replaceChildren(@NotNull String name, @NotNull Class<E> eventType, @NotNull EventNode<E> eventNode);
/**
* Replaces all children matching the given name and type recursively starting from this node.
@ -389,8 +270,8 @@ public class EventNode<T extends Event> {
* @param name The node name to filter for
* @param eventNode The replacement node
*/
public void replaceChildren(@NotNull String name, @NotNull EventNode<T> eventNode) {
replaceChildren(name, eventType, eventNode);
default void replaceChildren(@NotNull String name, @NotNull EventNode<T> eventNode) {
replaceChildren(name, getEventType(), eventNode);
}
/**
@ -399,27 +280,14 @@ public class EventNode<T extends Event> {
* @param name The node name to filter for
* @param eventType The node type to filter for
*/
public void removeChildren(@NotNull String name, @NotNull Class<? extends T> eventType) {
if (children.isEmpty()) return;
synchronized (GLOBAL_CHILD_LOCK) {
for (EventNode<T> child : children) {
if (EventNode.equals(child, name, eventType)) {
removeChild(child);
continue;
}
child.removeChildren(name, eventType);
}
}
}
void removeChildren(@NotNull String name, @NotNull Class<? extends T> 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<T extends Event> {
* @return this, can be used for chaining
*/
@Contract(value = "_ -> this")
public @NotNull EventNode<T> addChild(@NotNull EventNode<? extends T> 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<T>) 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<T> addChild(@NotNull EventNode<? extends T> child);
/**
* Directly removes the given child from this node.
@ -456,135 +305,20 @@ public class EventNode<T extends Event> {
* @return this, can be used for chaining
*/
@Contract(value = "_ -> this")
public @NotNull EventNode<T> removeChild(@NotNull EventNode<? extends T> 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<T> removeChild(@NotNull EventNode<? extends T> child);
@Contract(value = "_ -> this")
public @NotNull EventNode<T> addListener(@NotNull EventListener<? extends T> listener) {
synchronized (GLOBAL_CHILD_LOCK) {
final var eventType = listener.getEventType();
var entry = listenerMap.computeIfAbsent(eventType, aClass -> new ListenerEntry<>());
entry.listeners.add((EventListener<T>) listener);
if (parent != null) {
synchronized (parent.lock) {
parent.increaseChildListenerCount(eventType, 1);
}
}
}
return this;
}
@NotNull EventNode<T> addListener(@NotNull EventListener<? extends T> listener);
@Contract(value = "_, _ -> this")
public <E extends T> @NotNull EventNode<T> addListener(@NotNull Class<E> eventType, @NotNull Consumer<@NotNull E> listener) {
return addListener(EventListener.of(eventType, listener));
}
<E extends T> @NotNull EventNode<T> addListener(@NotNull Class<E> eventType, @NotNull Consumer<@NotNull E> listener);
@Contract(value = "_ -> this")
public @NotNull EventNode<T> removeListener(@NotNull EventListener<? extends T> 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<T> removeListener(@NotNull EventListener<? extends T> listener);
public void map(@NotNull EventNode<? extends T> 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<T>) node);
}
void map(@NotNull EventNode<? extends T> 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<? extends T> eventInterface) {
this.interfaces.add((EventInterface<T>) eventInterface);
}
private void increaseChildListenerCount(Class<? extends T> 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<? extends T> 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<EventFilter<? extends Event, ?>> 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<EventFilter<?, ?>> getEventFilters(Class<? extends Event> eventType) {
return HANDLER_SUPPLIERS.computeIfAbsent(eventType, clazz ->
FILTERS.stream().filter(eventFilter -> eventFilter.eventType().isAssignableFrom(clazz)).collect(Collectors.toList()));
}
private static class ListenerEntry<T extends Event> {
private static final AtomicIntegerFieldUpdater<ListenerEntry> CHILD_UPDATER =
AtomicIntegerFieldUpdater.newUpdater(ListenerEntry.class, "childCount");
List<EventListener<T>> listeners = new CopyOnWriteArrayList<>();
volatile int childCount;
private static int addAndGet(ListenerEntry<?> entry, int add) {
return CHILD_UPDATER.addAndGet(entry, add);
}
}
void registerInterface(@NotNull EventInterface<? extends T> eventInterface);
}

View File

@ -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<T extends Event> implements EventNode<T> {
private static final Object GLOBAL_CHILD_LOCK = new Object();
private final Object lock = new Object();
private final Map<Class<? extends T>, ListenerEntry<T>> listenerMap = new ConcurrentHashMap<>();
private final Set<EventNode<T>> children = new CopyOnWriteArraySet<>();
private final Map<Object, ListenerEntry<T>> mappedNodeCache = new WeakHashMap<>();
private final String name;
private final EventFilter<T, ?> filter;
private final BiPredicate<T, Object> predicate;
private final Class<T> eventType;
private volatile int priority;
private volatile EventNodeImpl<? super T> parent;
protected EventNodeImpl(@NotNull String name,
@NotNull EventFilter<T, ?> filter,
@Nullable BiPredicate<T, Object> 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<T> 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<T> 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 <E extends T> @NotNull List<EventNode<E>> findChildren(@NotNull String name, Class<E> eventType) {
if (children.isEmpty()) return Collections.emptyList();
synchronized (GLOBAL_CHILD_LOCK) {
List<EventNode<E>> result = new ArrayList<>();
for (EventNode<T> child : children) {
if (equals(child, name, eventType)) {
result.add((EventNode<E>) child);
}
result.addAll(child.findChildren(name, eventType));
}
return result;
}
}
@Contract(pure = true)
public @NotNull Set<@NotNull EventNode<T>> getChildren() {
return Collections.unmodifiableSet(children);
}
@Override
public <E extends T> void replaceChildren(@NotNull String name, @NotNull Class<E> eventType, @NotNull EventNode<E> eventNode) {
if (children.isEmpty()) return;
synchronized (GLOBAL_CHILD_LOCK) {
for (EventNode<T> 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<? extends T> eventType) {
if (children.isEmpty()) return;
synchronized (GLOBAL_CHILD_LOCK) {
for (EventNode<T> 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<T> addChild(@NotNull EventNode<? extends T> child) {
synchronized (GLOBAL_CHILD_LOCK) {
final var childImpl = (EventNodeImpl<? extends T>) 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<T>) childImpl);
if (result) {
childImpl.parent = this;
// Increase listener count
propagateNode(childImpl, IntUnaryOperator.identity());
}
}
return this;
}
@Override
public @NotNull EventNode<T> removeChild(@NotNull EventNode<? extends T> child) {
synchronized (GLOBAL_CHILD_LOCK) {
final boolean result = this.children.remove(child);
if (result) {
final var childImpl = (EventNodeImpl<? extends T>) child;
childImpl.parent = null;
// Decrease listener count
propagateNode(childImpl, count -> -count);
}
}
return this;
}
@Override
public @NotNull EventNode<T> addListener(@NotNull EventListener<? extends T> listener) {
synchronized (GLOBAL_CHILD_LOCK) {
final var eventType = listener.getEventType();
var entry = getEntry(eventType);
entry.listeners.add((EventListener<T>) listener);
final var parent = this.parent;
if (parent != null) {
synchronized (parent.lock) {
parent.propagateChildCountChange(eventType, 1);
}
}
}
return this;
}
@Override
public <E extends T> @NotNull EventNode<T> addListener(@NotNull Class<E> eventType, @NotNull Consumer<@NotNull E> listener) {
return addListener(EventListener.of(eventType, listener));
}
@Override
public @NotNull EventNode<T> removeListener(@NotNull EventListener<? extends T> 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<? extends T> node, @NotNull Object value) {
final var nodeImpl = (EventNodeImpl<? extends T>) 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<T>) 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<? extends T> eventInterface) {
synchronized (GLOBAL_CHILD_LOCK) {
for (var eventType : eventInterface.eventTypes()) {
var entry = getEntry((Class<? extends T>) eventType);
entry.interfaces.add((EventInterface<T>) eventInterface);
}
}
}
@Override
public @NotNull Class<T> getEventType() {
return eventType;
}
@Override
public @NotNull String getName() {
return name;
}
@Override
public int getPriority() {
return priority;
}
@Override
public @NotNull EventNode<T> setPriority(int priority) {
this.priority = priority;
return this;
}
@Override
public @Nullable EventNode<? super T> getParent() {
return parent;
}
private void propagateChildCountChange(Class<? extends T> 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<? extends T> 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<T> getEntry(Class<? extends T> type) {
return listenerMap.computeIfAbsent(type, aClass -> new ListenerEntry<>((Class<T>) 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<T extends Event> {
private static final List<EventFilter<? extends Event, ?>> FILTERS = List.of(
EventFilter.ENTITY,
EventFilter.ITEM, EventFilter.INSTANCE,
EventFilter.INVENTORY, EventFilter.BLOCK);
@SuppressWarnings("rawtypes")
private static final AtomicIntegerFieldUpdater<ListenerEntry> CHILD_UPDATER =
AtomicIntegerFieldUpdater.newUpdater(ListenerEntry.class, "childCount");
final List<EventFilter<?, ?>> filters;
final List<EventListener<T>> listeners = new CopyOnWriteArrayList<>();
final Set<EventInterface<T>> interfaces = new CopyOnWriteArraySet<>();
final Map<Object, EventNode<T>> mappedNode = new WeakHashMap<>();
volatile int childCount;
ListenerEntry(Class<T> 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);
}
}
}