mirror of
https://github.com/Minestom/Minestom.git
synced 2025-01-02 22:47:49 +01:00
Fix parent filtering
This commit is contained in:
parent
0a3ad69e58
commit
8b61ead08e
@ -262,8 +262,7 @@ class EventNodeImpl<T extends Event> implements EventNode<T> {
|
|||||||
private static final class Handle<E extends Event> implements ListenerHandle<E> {
|
private static final class Handle<E extends Event> implements ListenerHandle<E> {
|
||||||
private final EventNodeImpl<E> node;
|
private final EventNodeImpl<E> node;
|
||||||
private final Class<E> eventType;
|
private final Class<E> eventType;
|
||||||
private Consumer<E>[] listeners = new Consumer[0];
|
private Consumer<E> listener = null;
|
||||||
private final List<Consumer<E>> listenersCache = new ArrayList<>();
|
|
||||||
private volatile boolean updated;
|
private volatile boolean updated;
|
||||||
|
|
||||||
Handle(EventNodeImpl<E> node, Class<E> eventType) {
|
Handle(EventNodeImpl<E> node, Class<E> eventType) {
|
||||||
@ -273,55 +272,125 @@ class EventNodeImpl<T extends Event> implements EventNode<T> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void call(@NotNull E event) {
|
public void call(@NotNull E event) {
|
||||||
final Consumer<E>[] listeners = updatedListeners();
|
final Consumer<E> listener = updatedListener();
|
||||||
if (listeners.length == 0) return;
|
if (listener != null) {
|
||||||
for (Consumer<E> listener : listeners) {
|
try {
|
||||||
listener.accept(event);
|
listener.accept(event);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
MinecraftServer.getExceptionManager().handleException(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasListener() {
|
public boolean hasListener() {
|
||||||
return updatedListeners().length > 0;
|
return updatedListener() != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
Consumer<E>[] updatedListeners() {
|
@Nullable Consumer<E> updatedListener() {
|
||||||
if (updated) return listeners;
|
if (updated) return listener;
|
||||||
synchronized (GLOBAL_CHILD_LOCK) {
|
synchronized (GLOBAL_CHILD_LOCK) {
|
||||||
if (updated) return listeners;
|
if (updated) return listener;
|
||||||
this.listenersCache.clear();
|
final Consumer<E> listener = createConsumer();
|
||||||
recursiveUpdate(node);
|
this.listener = listener;
|
||||||
final Consumer<E>[] listenersArray = listenersCache.toArray(Consumer[]::new);
|
|
||||||
this.listeners = listenersArray;
|
|
||||||
this.updated = true;
|
this.updated = true;
|
||||||
return listenersArray;
|
return listener;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void recursiveUpdate(EventNodeImpl<E> targetNode) {
|
private @Nullable Consumer<E> createConsumer() {
|
||||||
// Standalone listeners
|
// Standalone listeners
|
||||||
|
List<Consumer<E>> listeners = new ArrayList<>();
|
||||||
forTargetEvents(eventType, type -> {
|
forTargetEvents(eventType, type -> {
|
||||||
final ListenerEntry<E> entry = targetNode.listenerMap.get(type);
|
final ListenerEntry<E> entry = node.listenerMap.get(type);
|
||||||
if (entry != null) appendEntries(entry, targetNode);
|
if (entry != null) {
|
||||||
|
final Consumer<E> result = appendEntries(entry, node);
|
||||||
|
if (result != null) listeners.add(result);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
// Mapped nodes
|
final Consumer<E>[] listenersArray = listeners.toArray(Consumer[]::new);
|
||||||
handleMappedNode(targetNode);
|
// Mapped
|
||||||
// Add children
|
final Consumer<E> mappedListener = handleMappedNode(node);
|
||||||
final Set<EventNodeImpl<E>> children = targetNode.children;
|
// Children
|
||||||
if (children.isEmpty()) return;
|
final Consumer<E>[] childrenListeners = node.children.stream()
|
||||||
children.stream()
|
|
||||||
.filter(child -> child.eventType.isAssignableFrom(eventType)) // Invalid event type
|
.filter(child -> child.eventType.isAssignableFrom(eventType)) // Invalid event type
|
||||||
.sorted(Comparator.comparing(EventNode::getPriority))
|
.sorted(Comparator.comparing(EventNode::getPriority))
|
||||||
.forEach(this::recursiveUpdate);
|
.map(child -> ((Handle<E>) child.getHandle(eventType)).updatedListener())
|
||||||
|
.filter(eConsumer -> eConsumer != null)
|
||||||
|
.toArray(Consumer[]::new);
|
||||||
|
// Empty check
|
||||||
|
final BiPredicate<E, Object> predicate = node.predicate;
|
||||||
|
final EventFilter<E, ?> 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 (listenersArray.length == 0 && mappedListener == null && childrenListeners.length == 0) {
|
||||||
|
// 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<E> listener : listenersArray) {
|
||||||
|
listener.accept(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Mapped nodes
|
||||||
|
if (hasMap) mappedListener.accept(e);
|
||||||
|
// Children
|
||||||
|
if (hasChildren) {
|
||||||
|
for (Consumer<E> childHandle : childrenListeners) {
|
||||||
|
childHandle.accept(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add the node's listeners from {@link EventNode#map(EventNode, Object)}.
|
* Add listeners from {@link EventNode#addListener(EventListener)} and
|
||||||
|
* {@link EventNode#register(EventBinding)} to the handle list.
|
||||||
|
* <p>
|
||||||
|
* Most computation should ideally be done outside the consumers as a one-time cost.
|
||||||
|
*/
|
||||||
|
private @Nullable Consumer<E> appendEntries(ListenerEntry<E> entry, EventNodeImpl<E> targetNode) {
|
||||||
|
final EventListener<E>[] listenersCopy = entry.listeners.toArray(EventListener[]::new);
|
||||||
|
final Consumer<E>[] 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<E> listener = listenersCopy[0];
|
||||||
|
return e -> callListener(targetNode, listener, e);
|
||||||
|
}
|
||||||
|
// Worse case scenario, try to run everything
|
||||||
|
return e -> {
|
||||||
|
if (!listenersEmpty) {
|
||||||
|
for (EventListener<E> listener : listenersCopy) {
|
||||||
|
callListener(targetNode, listener, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!bindingsEmpty) {
|
||||||
|
for (Consumer<E> eConsumer : bindingsCopy) {
|
||||||
|
eConsumer.accept(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a consumer handling {@link EventNode#map(EventNode, Object)}.
|
||||||
* The goal is to limit the amount of map lookup.
|
* The goal is to limit the amount of map lookup.
|
||||||
*/
|
*/
|
||||||
private void handleMappedNode(EventNodeImpl<E> targetNode) {
|
private @Nullable Consumer<E> handleMappedNode(EventNodeImpl<E> targetNode) {
|
||||||
final var mappedNodeCache = targetNode.mappedNodeCache;
|
final var mappedNodeCache = targetNode.mappedNodeCache;
|
||||||
if (mappedNodeCache.isEmpty()) return;
|
if (mappedNodeCache.isEmpty()) return null;
|
||||||
Set<EventFilter<E, ?>> filters = new HashSet<>(mappedNodeCache.size());
|
Set<EventFilter<E, ?>> filters = new HashSet<>(mappedNodeCache.size());
|
||||||
Map<Object, Handle<E>> handlers = new HashMap<>(mappedNodeCache.size());
|
Map<Object, Handle<E>> handlers = new HashMap<>(mappedNodeCache.size());
|
||||||
// Retrieve all filters used to retrieve potential handlers
|
// Retrieve all filters used to retrieve potential handlers
|
||||||
@ -334,94 +403,35 @@ class EventNodeImpl<T extends Event> implements EventNode<T> {
|
|||||||
}
|
}
|
||||||
// If at least one mapped node listen to this handle type,
|
// If at least one mapped node listen to this handle type,
|
||||||
// loop through them and forward to mapped node if there is a match
|
// loop through them and forward to mapped node if there is a match
|
||||||
if (!filters.isEmpty()) {
|
if (filters.isEmpty()) return null;
|
||||||
final EventFilter<E, ?>[] filterList = filters.toArray(EventFilter[]::new);
|
final EventFilter<E, ?>[] filterList = filters.toArray(EventFilter[]::new);
|
||||||
final BiConsumer<EventFilter<E, ?>, E> mapper = (filter, event) -> {
|
final BiConsumer<EventFilter<E, ?>, E> mapper = (filter, event) -> {
|
||||||
final Object handler = filter.castHandler(event);
|
final Object handler = filter.castHandler(event);
|
||||||
final Handle<E> handle = handlers.get(handler);
|
final Handle<E> handle = handlers.get(handler);
|
||||||
if (handle != null) handle.call(event);
|
if (handle != null) handle.call(event);
|
||||||
|
};
|
||||||
|
if (filterList.length == 1) {
|
||||||
|
final var firstFilter = filterList[0];
|
||||||
|
// Common case where there is only one filter
|
||||||
|
return event -> mapper.accept(firstFilter, event);
|
||||||
|
} else if (filterList.length == 2) {
|
||||||
|
final var firstFilter = filterList[0];
|
||||||
|
final var secondFilter = filterList[1];
|
||||||
|
return event -> {
|
||||||
|
mapper.accept(firstFilter, event);
|
||||||
|
mapper.accept(secondFilter, event);
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return event -> {
|
||||||
|
for (var filter : filterList) {
|
||||||
|
mapper.accept(filter, event);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
if (filterList.length == 1) {
|
|
||||||
final var firstFilter = filterList[0];
|
|
||||||
// Common case where there is only one filter
|
|
||||||
this.listenersCache.add(event -> mapper.accept(firstFilter, event));
|
|
||||||
} else if (filterList.length == 2) {
|
|
||||||
final var firstFilter = filterList[0];
|
|
||||||
final var secondFilter = filterList[1];
|
|
||||||
this.listenersCache.add(event -> {
|
|
||||||
mapper.accept(firstFilter, event);
|
|
||||||
mapper.accept(secondFilter, event);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.listenersCache.add(event -> {
|
|
||||||
for (var filter : filterList) {
|
|
||||||
mapper.accept(filter, event);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Add listeners from {@link EventNode#addListener(EventListener)} and
|
|
||||||
* {@link EventNode#register(EventBinding)} to the handle list.
|
|
||||||
* <p>
|
|
||||||
* Most computation should ideally be done outside the consumers as a one-time cost.
|
|
||||||
*/
|
|
||||||
private void appendEntries(ListenerEntry<E> entry, EventNodeImpl<E> targetNode) {
|
|
||||||
final var filter = targetNode.filter;
|
|
||||||
final var predicate = targetNode.predicate;
|
|
||||||
|
|
||||||
final boolean hasPredicate = predicate != null;
|
|
||||||
final EventListener<E>[] listenersCopy = entry.listeners.toArray(EventListener[]::new);
|
|
||||||
final Consumer<E>[] bindingsCopy = entry.bindingConsumers.toArray(Consumer[]::new);
|
|
||||||
final boolean listenersEmpty = listenersCopy.length == 0;
|
|
||||||
final boolean bindingsEmpty = bindingsCopy.length == 0;
|
|
||||||
if (!hasPredicate && listenersEmpty && bindingsEmpty)
|
|
||||||
return; // Nothing to run
|
|
||||||
|
|
||||||
if (!hasPredicate && bindingsEmpty && listenersCopy.length == 1) {
|
|
||||||
// Only one normal listener
|
|
||||||
final EventListener<E> listener = listenersCopy[0];
|
|
||||||
this.listenersCache.add(e -> callListener(targetNode, listener, e));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Worse case scenario, try to run everything
|
|
||||||
this.listenersCache.add(e -> {
|
|
||||||
if (hasPredicate) {
|
|
||||||
final Object value = filter.getHandler(e);
|
|
||||||
try {
|
|
||||||
if (!predicate.test(e, value)) return;
|
|
||||||
} catch (Throwable t) {
|
|
||||||
MinecraftServer.getExceptionManager().handleException(t);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!listenersEmpty) {
|
|
||||||
for (EventListener<E> listener : listenersCopy) {
|
|
||||||
callListener(targetNode, listener, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!bindingsEmpty) {
|
|
||||||
for (Consumer<E> eConsumer : bindingsCopy) {
|
|
||||||
try {
|
|
||||||
eConsumer.accept(e);
|
|
||||||
} catch (Throwable t) {
|
|
||||||
MinecraftServer.getExceptionManager().handleException(t);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
static <E extends Event> void callListener(EventNodeImpl<E> targetNode, EventListener<E> listener, E event) {
|
static <E extends Event> void callListener(EventNodeImpl<E> targetNode, EventListener<E> listener, E event) {
|
||||||
EventListener.Result result;
|
EventListener.Result result = listener.run(event);
|
||||||
try {
|
|
||||||
result = listener.run(event);
|
|
||||||
} catch (Throwable t) {
|
|
||||||
result = EventListener.Result.EXCEPTION;
|
|
||||||
MinecraftServer.getExceptionManager().handleException(t);
|
|
||||||
}
|
|
||||||
if (result == EventListener.Result.EXPIRED) {
|
if (result == EventListener.Result.EXPIRED) {
|
||||||
targetNode.removeListener(listener);
|
targetNode.removeListener(listener);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user