Initial ListenerHandle implementation

This commit is contained in:
TheMode 2021-08-19 06:45:23 +02:00
parent 9750e1fe8b
commit b4fbfe572d
3 changed files with 78 additions and 163 deletions

View File

@ -190,7 +190,13 @@ public interface EventNode<T extends Event> {
* *
* @param event the event to execute * @param event the event to execute
*/ */
void call(@NotNull T event); default void call(@NotNull T event) {
call(event, getHandle((Class<T>) event.getClass()));
}
<E extends T> void call(@NotNull E event, ListenerHandle<E> handle);
<E extends T> ListenerHandle<E> getHandle(Class<E> handleType);
/** /**
* Execute a cancellable event with a callback to execute if the event is successful. * Execute a cancellable event with a callback to execute if the event is successful.

View File

@ -1,6 +1,5 @@
package net.minestom.server.event; package net.minestom.server.event;
import net.minestom.server.MinecraftServer;
import net.minestom.server.utils.validate.Check; import net.minestom.server.utils.validate.Check;
import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@ -10,16 +9,14 @@ import java.util.*;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
import java.util.function.BiPredicate; import java.util.function.BiPredicate;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.IntUnaryOperator;
import java.util.stream.Collectors;
class EventNodeImpl<T extends Event> implements EventNode<T> { class EventNodeImpl<T extends Event> implements EventNode<T> {
private static final Object GLOBAL_CHILD_LOCK = new Object(); private static final Object GLOBAL_CHILD_LOCK = new Object();
private final Object lock = new Object(); private final Object lock = new Object();
private final Map<Class<? extends T>, Handle<T>> handleMap = new ConcurrentHashMap<>();
private final Map<Class<? extends T>, ListenerEntry<T>> listenerMap = new ConcurrentHashMap<>(); private final Map<Class<? extends T>, ListenerEntry<T>> listenerMap = new ConcurrentHashMap<>();
private final Set<EventNode<T>> children = new CopyOnWriteArraySet<>(); private final Set<EventNode<T>> children = new CopyOnWriteArraySet<>();
private final Map<Object, ListenerEntry<T>> mappedNodeCache = new WeakHashMap<>(); private final Map<Object, ListenerEntry<T>> mappedNodeCache = new WeakHashMap<>();
@ -41,29 +38,38 @@ class EventNodeImpl<T extends Event> implements EventNode<T> {
} }
@Override @Override
public void call(@NotNull T event) { public <E extends T> void call(@NotNull E event, ListenerHandle<E> handle) {
final var eventClass = event.getClass(); var castedHandle = (Handle<T>) handle;
if (!eventType.isAssignableFrom(eventClass)) return; // Invalid event type Check.stateCondition(castedHandle.node != this, "Invalid handle owner");
// Conditions if (!castedHandle.updated) {
if (predicate != null) { handle(castedHandle);
try { castedHandle.updated = true;
final var value = filter.getHandler(event); }
if (!predicate.test(event, value)) return; List<Consumer<T>> listeners = castedHandle.listeners;
} catch (Exception e) { if (listeners.isEmpty()) return;
MinecraftServer.getExceptionManager().handleException(e); for (Consumer<T> listener : listeners) {
return; listener.accept(event);
}
}
@Override
public <E extends T> ListenerHandle<E> getHandle(Class<E> handleType) {
return (ListenerHandle<E>) handleMap.computeIfAbsent(handleType,
aClass -> new Handle<>(this, (Class<T>) aClass));
}
private void handle(Handle<T> handle) {
ListenerEntry<T> entry = listenerMap.get(handle.eventType);
if (entry != null) {
for (var listener : entry.listeners) {
handle.listeners.add(listener::run);
} }
} }
// Process listeners list // Add children
final var entry = listenerMap.get(eventClass); if (children.isEmpty()) return;
if (entry == null) return; // No listener nor children this.children.stream()
entry.call(event); .sorted(Comparator.comparing(EventNode::getPriority))
// Process children .forEach(child -> ((EventNodeImpl) child).handle(handle));
if (entry.childCount > 0) {
this.children.stream()
.sorted(Comparator.comparing(EventNode::getPriority))
.forEach(child -> child.call(event));
}
} }
@Override @Override
@ -127,11 +133,9 @@ class EventNodeImpl<T extends Event> implements EventNode<T> {
Check.stateCondition(childImpl.parent != null, "Node already has a parent"); Check.stateCondition(childImpl.parent != null, "Node already has a parent");
Check.stateCondition(Objects.equals(parent, child), "Cannot have a child as parent"); Check.stateCondition(Objects.equals(parent, child), "Cannot have a child as parent");
final boolean result = this.children.add((EventNodeImpl<T>) childImpl); final boolean result = this.children.add((EventNodeImpl<T>) childImpl);
if (result) { if (!result) return this;
childImpl.parent = this; childImpl.parent = this;
// Increase listener count childImpl.propagateEvents(); // Propagate after setting the parent
propagateNode(childImpl, IntUnaryOperator.identity());
}
} }
return this; return this;
} }
@ -140,12 +144,10 @@ class EventNodeImpl<T extends Event> implements EventNode<T> {
public @NotNull EventNode<T> removeChild(@NotNull EventNode<? extends T> child) { public @NotNull EventNode<T> removeChild(@NotNull EventNode<? extends T> child) {
synchronized (GLOBAL_CHILD_LOCK) { synchronized (GLOBAL_CHILD_LOCK) {
final boolean result = this.children.remove(child); final boolean result = this.children.remove(child);
if (result) { if (!result) return this;
final var childImpl = (EventNodeImpl<? extends T>) child; final var childImpl = (EventNodeImpl<? extends T>) child;
childImpl.parent = null; childImpl.propagateEvents(); // Propagate before removing the parent
// Decrease listener count childImpl.parent = null;
propagateNode(childImpl, count -> -count);
}
} }
return this; return this;
} }
@ -156,7 +158,7 @@ class EventNodeImpl<T extends Event> implements EventNode<T> {
final var eventType = listener.getEventType(); final var eventType = listener.getEventType();
var entry = getEntry(eventType); var entry = getEntry(eventType);
entry.listeners.add((EventListener<T>) listener); entry.listeners.add((EventListener<T>) listener);
propagateToParent(eventType, 1); propagateEvent(eventType);
} }
return this; return this;
} }
@ -169,69 +171,30 @@ class EventNodeImpl<T extends Event> implements EventNode<T> {
if (entry == null) return this; if (entry == null) return this;
var listeners = entry.listeners; var listeners = entry.listeners;
final boolean removed = listeners.remove(listener); final boolean removed = listeners.remove(listener);
if (removed) propagateToParent(eventType, -1); if (removed) propagateEvent(eventType);
} }
return this; return this;
} }
@Override @Override
public void map(@NotNull EventNode<? extends T> node, @NotNull Object value) { public void map(@NotNull EventNode<? extends T> node, @NotNull Object value) {
final var nodeImpl = (EventNodeImpl<? extends T>) node; // TODO
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}", nodeImpl.eventType, valueType);
synchronized (mappedNodeCache) {
entry.mappedNode.put(value, (EventNode<T>) nodeImpl);
mappedNodeCache.put(value, entry);
// TODO propagate
}
});
}
} }
@Override @Override
public boolean unmap(@NotNull Object value) { public boolean unmap(@NotNull Object value) {
synchronized (GLOBAL_CHILD_LOCK) { // TODO
synchronized (mappedNodeCache) { return false;
var entry = mappedNodeCache.remove(value);
if (entry == null) return false;
final EventNode<T> previousNode = entry.mappedNode.remove(value);
if (previousNode != null) {
// TODO propagate
return true;
}
return false;
}
}
} }
@Override @Override
public void register(@NotNull EventBinding<? extends T> binding) { public void register(@NotNull EventBinding<? extends T> binding) {
synchronized (GLOBAL_CHILD_LOCK) { // TODO
for (var eventType : binding.eventTypes()) {
var entry = getEntry((Class<? extends T>) eventType);
final boolean added = entry.bindingConsumers.add((Consumer<T>) binding.consumer(eventType));
if (added) propagateToParent((Class<? extends T>) eventType, 1);
}
}
} }
@Override @Override
public void unregister(@NotNull EventBinding<? extends T> binding) { public void unregister(@NotNull EventBinding<? extends T> binding) {
synchronized (GLOBAL_CHILD_LOCK) { // TODO
for (var eventType : binding.eventTypes()) {
var entry = listenerMap.get(eventType);
if (entry == null) return;
final boolean removed = entry.bindingConsumers.remove(binding.consumer(eventType));
if (removed) propagateToParent((Class<? extends T>) eventType, -1);
}
}
} }
@Override @Override
@ -260,42 +223,21 @@ class EventNodeImpl<T extends Event> implements EventNode<T> {
return parent; return parent;
} }
private void propagateChildCountChange(Class<? extends T> eventClass, int count) { private void propagateEvents() {
var entry = getEntry(eventClass); this.listenerMap.forEach((eventClass, eventListeners) -> propagateEvent(eventClass));
final int result = ListenerEntry.CHILD_UPDATER.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 propagateToParent(Class<? extends T> eventClass, int count) { private void propagateEvent(Class<? extends T> eventClass) {
final var parent = this.parent; final var parent = this.parent;
if (parent != null) { if (parent == null) return;
synchronized (parent.lock) { var handle = parent.handleMap.get(eventClass);
parent.propagateChildCountChange(eventClass, count); if (handle == null) return;
} handle.updated = false;
} parent.propagateEvent(eventClass);
}
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) { private ListenerEntry<T> getEntry(Class<? extends T> type) {
return listenerMap.computeIfAbsent(type, aClass -> new ListenerEntry<>(this, (Class<T>) aClass)); return listenerMap.computeIfAbsent(type, aClass -> new ListenerEntry<>());
} }
private static boolean equals(EventNode<?> node, String name, Class<?> eventType) { private static boolean equals(EventNode<?> node, String name, Class<?> eventType) {
@ -305,59 +247,19 @@ class EventNodeImpl<T extends Event> implements EventNode<T> {
} }
private static class ListenerEntry<T extends Event> { 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 EventNodeImpl<T> node;
final List<EventFilter<?, ?>> filters;
final List<EventListener<T>> listeners = new CopyOnWriteArrayList<>(); final List<EventListener<T>> listeners = new CopyOnWriteArrayList<>();
final Set<Consumer<T>> bindingConsumers = new CopyOnWriteArraySet<>(); }
final Map<Object, EventNode<T>> mappedNode = new WeakHashMap<>();
volatile int childCount;
ListenerEntry(EventNodeImpl<T> node, Class<T> eventType) { private static final class Handle<E extends Event> implements ListenerHandle<E> {
private final EventNode<E> node;
private final Class<E> eventType;
private final List<Consumer<E>> listeners = new CopyOnWriteArrayList<>();
private volatile boolean updated;
Handle(EventNode<E> node, Class<E> eventType) {
this.node = node; this.node = node;
this.filters = FILTERS.stream().filter(eventFilter -> eventFilter.eventType().isAssignableFrom(eventType)).collect(Collectors.toList()); this.eventType = eventType;
}
void call(T event) {
// Event interfaces
if (!bindingConsumers.isEmpty()) {
for (var consumer : bindingConsumers) {
consumer.accept(event);
}
}
// Mapped listeners
if (!mappedNode.isEmpty()) {
synchronized (node.mappedNodeCache) {
// Check mapped listeners for each individual event handler
for (var filter : filters) {
final var handler = filter.castHandler(event);
final var map = mappedNode.get(handler);
if (map != null) map.call(event);
}
}
}
// Basic 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);
}
}
}
} }
} }
} }

View File

@ -0,0 +1,7 @@
package net.minestom.server.event;
import org.jetbrains.annotations.ApiStatus;
@ApiStatus.NonExtendable
public interface ListenerHandle<E extends Event> {
}