From a075231770a69a40175ce74c275bbd02605dcbdb Mon Sep 17 00:00:00 2001 From: TheMode Date: Sat, 14 Aug 2021 01:39:45 +0200 Subject: [PATCH 01/19] Add EventInterface prototype --- .../minestom/server/event/EventInterface.java | 35 +++++++++++++++++++ .../net/minestom/server/event/EventNode.java | 7 ++++ 2 files changed, 42 insertions(+) create mode 100644 src/main/java/net/minestom/server/event/EventInterface.java diff --git a/src/main/java/net/minestom/server/event/EventInterface.java b/src/main/java/net/minestom/server/event/EventInterface.java new file mode 100644 index 000000000..e9c9fde39 --- /dev/null +++ b/src/main/java/net/minestom/server/event/EventInterface.java @@ -0,0 +1,35 @@ +package net.minestom.server.event; + +import org.jetbrains.annotations.NotNull; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.BiConsumer; + +public final class EventInterface { + + public static @NotNull Builder builder(@NotNull Class type) { + return new Builder<>(); + } + + final Map, BiConsumer> mapped; + + EventInterface(Map, BiConsumer> map) { + this.mapped = map; + } + + public static class Builder { + private final Map, BiConsumer> mapped = new HashMap<>(); + + @SuppressWarnings("unchecked") + public Builder map(@NotNull Class eventType, + @NotNull BiConsumer<@NotNull T, @NotNull E> consumer) { + this.mapped.put(eventType, (BiConsumer) consumer); + return this; + } + + public @NotNull EventInterface build() { + return new EventInterface<>(Map.copyOf(mapped)); + } + } +} diff --git a/src/main/java/net/minestom/server/event/EventNode.java b/src/main/java/net/minestom/server/event/EventNode.java index c6b359564..ed96aac9e 100644 --- a/src/main/java/net/minestom/server/event/EventNode.java +++ b/src/main/java/net/minestom/server/event/EventNode.java @@ -514,6 +514,13 @@ public class EventNode { return this; } + public void addInter(@NotNull EventInterface inter, @NotNull I value) { + inter.mapped.forEach((eventType, consumer) -> { + // TODO cache so listeners can be removed from the EventInterface + addListener((EventListener) EventListener.builder(eventType).handler(event -> consumer.accept(value, event)).build()); + }); + } + private void increaseChildListenerCount(Class eventClass, int count) { var entry = listenerMap.computeIfAbsent(eventClass, aClass -> new ListenerEntry<>()); ListenerEntry.addAndGet(entry, count); From 5f51448da64f06013155db20c6dc2ff904cad8d2 Mon Sep 17 00:00:00 2001 From: TheMode Date: Sat, 14 Aug 2021 02:11:22 +0200 Subject: [PATCH 02/19] Add WIP `EventNode.Mapped` --- .../net/minestom/server/event/EventNode.java | 35 ++++++++++++++++--- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/src/main/java/net/minestom/server/event/EventNode.java b/src/main/java/net/minestom/server/event/EventNode.java index ed96aac9e..7e5399f22 100644 --- a/src/main/java/net/minestom/server/event/EventNode.java +++ b/src/main/java/net/minestom/server/event/EventNode.java @@ -177,6 +177,12 @@ public class EventNode { return create(name, filter, (e, h) -> consumer.test(h.getTag(tag))); } + public static @NotNull Mapped mapped(@NotNull String name, + @NotNull EventFilter filter, + @NotNull V value) { + return new Mapped<>(name, filter, value); + } + private static EventNode create(@NotNull String name, @NotNull EventFilter filter, @Nullable BiPredicate predicate) { @@ -188,6 +194,7 @@ public class EventNode { private final Map, ListenerEntry> listenerMap = new ConcurrentHashMap<>(); private final Set> children = new CopyOnWriteArraySet<>(); + private final Map> mappedNode = new ConcurrentHashMap<>(); protected final String name; protected final EventFilter filter; @@ -211,12 +218,11 @@ public class EventNode { * @param event the called event * @return true to enter the node, false otherwise */ - protected boolean condition(@NotNull T event) { + protected boolean condition(@NotNull T event, @Nullable Object handler) { if (predicate == null) return true; - final var value = filter.getHandler(event); try { - return predicate.test(event, value); + return predicate.test(event, handler); } catch (Exception e) { MinecraftServer.getExceptionManager().handleException(e); return false; @@ -238,10 +244,18 @@ public class EventNode { // Invalid event type return; } - if (!condition(event)) { + final var value = filter.getHandler(event); + if (!condition(event, value)) { // Cancelled by superclass return; } + // Mapped listeners + if (value != null && !mappedNode.isEmpty()) { + // FIXME: `value` is always null when `EventFilter#all` is used + // we might need a way to retrieve all the possible handlers from an event class + final var map = mappedNode.get(value); + if (map != null) map.call(event); + } // Process listener list final var entry = listenerMap.get(eventClass); if (entry == null) { @@ -514,6 +528,10 @@ public class EventNode { return this; } + public void map(@NotNull Mapped map) { + this.mappedNode.put(map.value, (EventNode) map); + } + public void addInter(@NotNull EventInterface inter, @NotNull I value) { inter.mapped.forEach((eventType, consumer) -> { // TODO cache so listeners can be removed from the EventInterface @@ -559,4 +577,13 @@ public class EventNode { return CHILD_UPDATER.addAndGet(entry, add); } } + + public static final class Mapped extends EventNode { + private final V value; + + Mapped(@NotNull String name, @NotNull EventFilter filter, @NotNull V value) { + super(name, filter, null); + this.value = value; + } + } } From 174cc2ea8f9fb7404c6b399cb03e8429f257b2fb Mon Sep 17 00:00:00 2001 From: TheMode Date: Sat, 14 Aug 2021 02:48:26 +0200 Subject: [PATCH 03/19] Make mapped nodes work --- .../net/minestom/server/event/EventNode.java | 48 +++++++++++++++---- 1 file changed, 38 insertions(+), 10 deletions(-) diff --git a/src/main/java/net/minestom/server/event/EventNode.java b/src/main/java/net/minestom/server/event/EventNode.java index 7e5399f22..e805e0818 100644 --- a/src/main/java/net/minestom/server/event/EventNode.java +++ b/src/main/java/net/minestom/server/event/EventNode.java @@ -1,7 +1,7 @@ package net.minestom.server.event; import net.minestom.server.MinecraftServer; -import net.minestom.server.event.trait.CancellableEvent; +import net.minestom.server.event.trait.*; import net.minestom.server.tag.Tag; import net.minestom.server.tag.TagReadable; import net.minestom.server.utils.validate.Check; @@ -16,6 +16,7 @@ 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.Function; import java.util.function.Predicate; /** @@ -189,6 +190,7 @@ public class EventNode { return new EventNode<>(name, filter, predicate != null ? (e, o) -> predicate.test(e, (V) o) : null); } + private static final Map, List>> HANDLER_SUPPLIERS = new ConcurrentHashMap<>(); private static final Object GLOBAL_CHILD_LOCK = new Object(); private final Object lock = new Object(); @@ -218,11 +220,12 @@ public class EventNode { * @param event the called event * @return true to enter the node, false otherwise */ - protected boolean condition(@NotNull T event, @Nullable Object handler) { + protected boolean condition(@NotNull T event) { if (predicate == null) return true; + final var value = filter.getHandler(event); try { - return predicate.test(event, handler); + return predicate.test(event, value); } catch (Exception e) { MinecraftServer.getExceptionManager().handleException(e); return false; @@ -244,17 +247,18 @@ public class EventNode { // Invalid event type return; } - final var value = filter.getHandler(event); - if (!condition(event, value)) { + if (!condition(event)) { // Cancelled by superclass return; } // Mapped listeners - if (value != null && !mappedNode.isEmpty()) { - // FIXME: `value` is always null when `EventFilter#all` is used - // we might need a way to retrieve all the possible handlers from an event class - final var map = mappedNode.get(value); - if (map != null) map.call(event); + if (!mappedNode.isEmpty()) { + // Check mapped listeners for each individual event handler + getEventMapping(eventClass).forEach(function -> { + final var handler = function.apply(event); + final var map = mappedNode.get(handler); + if (map != null) map.call(event); + }); } // Process listener list final var entry = listenerMap.get(eventClass); @@ -566,6 +570,30 @@ public class EventNode { return nameCheck && typeCheck; } + // 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. + // All event trait are currently hardcoded. + private static List> getEventMapping(Class eventClass) { + return HANDLER_SUPPLIERS.computeIfAbsent(eventClass, clazz -> { + List> result = new ArrayList<>(); + if (EntityEvent.class.isAssignableFrom(clazz)) { + result.add(e -> ((EntityEvent) e).getEntity()); + } else if (PlayerEvent.class.isAssignableFrom(clazz)) { + result.add(e -> ((PlayerEvent) e).getPlayer()); + } else if (ItemEvent.class.isAssignableFrom(clazz)) { + result.add(e -> ((ItemEvent) e).getItemStack()); + } else if (InstanceEvent.class.isAssignableFrom(clazz)) { + result.add(e -> ((InstanceEvent) e).getInstance()); + } else if (InventoryEvent.class.isAssignableFrom(clazz)) { + result.add(e -> ((InventoryEvent) e).getInventory()); + } else if (BlockEvent.class.isAssignableFrom(clazz)) { + result.add(e -> ((BlockEvent) e).getBlock()); + } + return result; + }); + } + private static class ListenerEntry { private static final AtomicIntegerFieldUpdater CHILD_UPDATER = AtomicIntegerFieldUpdater.newUpdater(ListenerEntry.class, "childCount"); From ebb8d03985046f04021445ac7e8ec82be3ab81e5 Mon Sep 17 00:00:00 2001 From: TheMode Date: Sat, 14 Aug 2021 02:56:34 +0200 Subject: [PATCH 04/19] Fix `getEventMapping` --- .../net/minestom/server/event/EventNode.java | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/main/java/net/minestom/server/event/EventNode.java b/src/main/java/net/minestom/server/event/EventNode.java index e805e0818..c9c8e828e 100644 --- a/src/main/java/net/minestom/server/event/EventNode.java +++ b/src/main/java/net/minestom/server/event/EventNode.java @@ -577,17 +577,21 @@ public class EventNode { private static List> getEventMapping(Class eventClass) { return HANDLER_SUPPLIERS.computeIfAbsent(eventClass, clazz -> { List> result = new ArrayList<>(); - if (EntityEvent.class.isAssignableFrom(clazz)) { - result.add(e -> ((EntityEvent) e).getEntity()); - } else if (PlayerEvent.class.isAssignableFrom(clazz)) { + if (PlayerEvent.class.isAssignableFrom(clazz)) { result.add(e -> ((PlayerEvent) e).getPlayer()); - } else if (ItemEvent.class.isAssignableFrom(clazz)) { + } else if (EntityEvent.class.isAssignableFrom(clazz)) { + result.add(e -> ((EntityEvent) e).getEntity()); + } + if (ItemEvent.class.isAssignableFrom(clazz)) { result.add(e -> ((ItemEvent) e).getItemStack()); - } else if (InstanceEvent.class.isAssignableFrom(clazz)) { + } + if (InstanceEvent.class.isAssignableFrom(clazz)) { result.add(e -> ((InstanceEvent) e).getInstance()); - } else if (InventoryEvent.class.isAssignableFrom(clazz)) { + } + if (InventoryEvent.class.isAssignableFrom(clazz)) { result.add(e -> ((InventoryEvent) e).getInventory()); - } else if (BlockEvent.class.isAssignableFrom(clazz)) { + } + if (BlockEvent.class.isAssignableFrom(clazz)) { result.add(e -> ((BlockEvent) e).getBlock()); } return result; From e5f0dc80615bc9fb9e1194d4cbe0e9ada0a1f8b2 Mon Sep 17 00:00:00 2001 From: TheMode Date: Sat, 14 Aug 2021 21:20:51 +0200 Subject: [PATCH 05/19] Improve mapped node implementation. Add handler type to EventFilter --- .../minestom/server/event/EventFilter.java | 38 +++++++-- .../net/minestom/server/event/EventNode.java | 82 ++++++++----------- 2 files changed, 63 insertions(+), 57 deletions(-) diff --git a/src/main/java/net/minestom/server/event/EventFilter.java b/src/main/java/net/minestom/server/event/EventFilter.java index 4a7740266..439ae3fd1 100644 --- a/src/main/java/net/minestom/server/event/EventFilter.java +++ b/src/main/java/net/minestom/server/event/EventFilter.java @@ -4,8 +4,10 @@ import net.minestom.server.entity.Entity; import net.minestom.server.entity.Player; import net.minestom.server.event.trait.*; import net.minestom.server.instance.Instance; +import net.minestom.server.instance.block.Block; import net.minestom.server.inventory.Inventory; import net.minestom.server.item.ItemStack; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -27,14 +29,16 @@ import java.util.function.Function; */ public interface EventFilter { - EventFilter ALL = from(Event.class, null); - EventFilter ENTITY = from(EntityEvent.class, EntityEvent::getEntity); - EventFilter PLAYER = from(PlayerEvent.class, PlayerEvent::getPlayer); - EventFilter ITEM = from(ItemEvent.class, ItemEvent::getItemStack); - EventFilter INSTANCE = from(InstanceEvent.class, InstanceEvent::getInstance); - EventFilter INVENTORY = from(InventoryEvent.class, InventoryEvent::getInventory); + EventFilter ALL = from(Event.class, null, null); + EventFilter ENTITY = from(EntityEvent.class, Entity.class, EntityEvent::getEntity); + EventFilter PLAYER = from(PlayerEvent.class, Player.class, PlayerEvent::getPlayer); + EventFilter ITEM = from(ItemEvent.class, ItemStack.class, ItemEvent::getItemStack); + EventFilter INSTANCE = from(InstanceEvent.class, Instance.class, InstanceEvent::getInstance); + EventFilter INVENTORY = from(InventoryEvent.class, Inventory.class, InventoryEvent::getInventory); + EventFilter BLOCK = from(BlockEvent.class, Block.class, BlockEvent::getBlock); static EventFilter from(@NotNull Class eventType, + @Nullable Class handlerType, @Nullable Function handlerGetter) { return new EventFilter<>() { @Override @@ -43,9 +47,14 @@ public interface EventFilter { } @Override - public @NotNull Class getEventType() { + public @NotNull Class eventType() { return eventType; } + + @Override + public @Nullable Class handlerType() { + return handlerType; + } }; } @@ -58,10 +67,23 @@ public interface EventFilter { */ @Nullable H getHandler(@NotNull E event); + @ApiStatus.Internal + default @Nullable H castHandler(@NotNull Object event) { + //noinspection unchecked + return getHandler((E) event); + } + /** * The event type to filter on. * * @return The event type. */ - @NotNull Class getEventType(); + @NotNull Class eventType(); + + /** + * The type returned by {@link #getHandler(Event)}. + * + * @return the handler type, null if not any + */ + @Nullable Class handlerType(); } diff --git a/src/main/java/net/minestom/server/event/EventNode.java b/src/main/java/net/minestom/server/event/EventNode.java index c9c8e828e..dd57afdcb 100644 --- a/src/main/java/net/minestom/server/event/EventNode.java +++ b/src/main/java/net/minestom/server/event/EventNode.java @@ -1,7 +1,7 @@ package net.minestom.server.event; import net.minestom.server.MinecraftServer; -import net.minestom.server.event.trait.*; +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; @@ -16,8 +16,8 @@ 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.Function; import java.util.function.Predicate; +import java.util.stream.Collectors; /** * Represents a single node in an event graph. @@ -178,19 +178,13 @@ public class EventNode { return create(name, filter, (e, h) -> consumer.test(h.getTag(tag))); } - public static @NotNull Mapped mapped(@NotNull String name, - @NotNull EventFilter filter, - @NotNull V value) { - return new Mapped<>(name, filter, value); - } - private static EventNode create(@NotNull String name, @NotNull EventFilter filter, @Nullable BiPredicate predicate) { return new EventNode<>(name, filter, predicate != null ? (e, o) -> predicate.test(e, (V) o) : null); } - private static final Map, List>> HANDLER_SUPPLIERS = new ConcurrentHashMap<>(); + private static final Map, List>> HANDLER_SUPPLIERS = new ConcurrentHashMap<>(); private static final Object GLOBAL_CHILD_LOCK = new Object(); private final Object lock = new Object(); @@ -211,7 +205,7 @@ public class EventNode { this.name = name; this.filter = filter; this.predicate = predicate; - this.eventType = filter.getEventType(); + this.eventType = filter.eventType(); } /** @@ -254,8 +248,8 @@ public class EventNode { // Mapped listeners if (!mappedNode.isEmpty()) { // Check mapped listeners for each individual event handler - getEventMapping(eventClass).forEach(function -> { - final var handler = function.apply(event); + getEventFilters(eventClass).forEach(filter -> { + final var handler = filter.castHandler(event); final var map = mappedNode.get(handler); if (map != null) map.call(event); }); @@ -532,8 +526,19 @@ public class EventNode { return this; } - public void map(@NotNull Mapped map) { - this.mappedNode.put(map.value, (EventNode) map); + public void map(@NotNull EventNode node, @NotNull Object value) { + final var nodeType = node.eventType; + final boolean correct = getEventFilters(nodeType).stream().anyMatch(eventFilter -> { + final var handlerType = eventFilter.handlerType(); + return handlerType != null && handlerType.isAssignableFrom(value.getClass()); + }); + Check.stateCondition(!correct, "The node {0} is not compatible with objects of type {1}", nodeType, value.getClass()); + //noinspection unchecked + this.mappedNode.put(value, (EventNode) node); + } + + public boolean unmap(@NotNull Object value) { + return mappedNode.remove(value) != null; } public void addInter(@NotNull EventInterface inter, @NotNull I value) { @@ -570,32 +575,20 @@ public class EventNode { return nameCheck && typeCheck; } - // 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. - // All event trait are currently hardcoded. - private static List> getEventMapping(Class eventClass) { - return HANDLER_SUPPLIERS.computeIfAbsent(eventClass, clazz -> { - List> result = new ArrayList<>(); - if (PlayerEvent.class.isAssignableFrom(clazz)) { - result.add(e -> ((PlayerEvent) e).getPlayer()); - } else if (EntityEvent.class.isAssignableFrom(clazz)) { - result.add(e -> ((EntityEvent) e).getEntity()); - } - if (ItemEvent.class.isAssignableFrom(clazz)) { - result.add(e -> ((ItemEvent) e).getItemStack()); - } - if (InstanceEvent.class.isAssignableFrom(clazz)) { - result.add(e -> ((InstanceEvent) e).getInstance()); - } - if (InventoryEvent.class.isAssignableFrom(clazz)) { - result.add(e -> ((InventoryEvent) e).getInventory()); - } - if (BlockEvent.class.isAssignableFrom(clazz)) { - result.add(e -> ((BlockEvent) e).getBlock()); - } - return result; - }); + private static final List> 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> getEventFilters(Class eventType) { + return HANDLER_SUPPLIERS.computeIfAbsent(eventType, clazz -> + FILTERS.stream().filter(eventFilter -> eventFilter.eventType().isAssignableFrom(clazz)).collect(Collectors.toList())); } private static class ListenerEntry { @@ -609,13 +602,4 @@ public class EventNode { return CHILD_UPDATER.addAndGet(entry, add); } } - - public static final class Mapped extends EventNode { - private final V value; - - Mapped(@NotNull String name, @NotNull EventFilter filter, @NotNull V value) { - super(name, filter, null); - this.value = value; - } - } } From 93405cd180df6e38f7c5d172a441f014eebea490 Mon Sep 17 00:00:00 2001 From: TheMode Date: Sat, 14 Aug 2021 21:28:51 +0200 Subject: [PATCH 06/19] Style --- src/main/java/net/minestom/server/event/EventNode.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/net/minestom/server/event/EventNode.java b/src/main/java/net/minestom/server/event/EventNode.java index dd57afdcb..990483a89 100644 --- a/src/main/java/net/minestom/server/event/EventNode.java +++ b/src/main/java/net/minestom/server/event/EventNode.java @@ -528,11 +528,12 @@ public class EventNode { public void map(@NotNull EventNode 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(value.getClass()); + return handlerType != null && handlerType.isAssignableFrom(valueType); }); - Check.stateCondition(!correct, "The node {0} is not compatible with objects of type {1}", nodeType, value.getClass()); + Check.stateCondition(!correct, "The node filter {0} is not compatible with type {1}", nodeType, valueType); //noinspection unchecked this.mappedNode.put(value, (EventNode) node); } From 02e8d530796979e8d1a54dd9f05e8c5c36c8aa29 Mon Sep 17 00:00:00 2001 From: TheMode Date: Mon, 16 Aug 2021 05:53:11 +0200 Subject: [PATCH 07/19] Use weak references to store mapped nodes --- src/main/java/net/minestom/server/event/EventNode.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/java/net/minestom/server/event/EventNode.java b/src/main/java/net/minestom/server/event/EventNode.java index 990483a89..4440f5e98 100644 --- a/src/main/java/net/minestom/server/event/EventNode.java +++ b/src/main/java/net/minestom/server/event/EventNode.java @@ -1,5 +1,7 @@ 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; @@ -190,7 +192,7 @@ public class EventNode { private final Map, ListenerEntry> listenerMap = new ConcurrentHashMap<>(); private final Set> children = new CopyOnWriteArraySet<>(); - private final Map> mappedNode = new ConcurrentHashMap<>(); + private final Map> mappedNode; protected final String name; protected final EventFilter filter; @@ -206,6 +208,9 @@ public class EventNode { this.filter = filter; this.predicate = predicate; this.eventType = filter.eventType(); + + Cache> mapCache = Caffeine.newBuilder().weakKeys().build(); + this.mappedNode = mapCache.asMap(); } /** From 8617d98c95f78459361c744c03b87f73e5cea4a0 Mon Sep 17 00:00:00 2001 From: TheMode Date: Mon, 16 Aug 2021 07:58:58 +0200 Subject: [PATCH 08/19] Rework EventInterface --- .../minestom/server/event/EventInterface.java | 52 +++++++++++++------ .../net/minestom/server/event/EventNode.java | 15 ++++-- 2 files changed, 47 insertions(+), 20 deletions(-) diff --git a/src/main/java/net/minestom/server/event/EventInterface.java b/src/main/java/net/minestom/server/event/EventInterface.java index e9c9fde39..6c3f1854d 100644 --- a/src/main/java/net/minestom/server/event/EventInterface.java +++ b/src/main/java/net/minestom/server/event/EventInterface.java @@ -2,34 +2,56 @@ package net.minestom.server.event; import org.jetbrains.annotations.NotNull; +import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.function.BiConsumer; +import java.util.function.Predicate; -public final class EventInterface { +public interface EventInterface { - public static @NotNull Builder builder(@NotNull Class type) { - return new Builder<>(); + static @NotNull FilteredBuilder filtered(@NotNull EventFilter filter, @NotNull Predicate predicate) { + return new FilteredBuilder<>(filter, predicate); } - final Map, BiConsumer> mapped; + @NotNull Collection> eventTypes(); - EventInterface(Map, BiConsumer> map) { - this.mapped = map; - } + void call(@NotNull E event); - public static class Builder { - private final Map, BiConsumer> mapped = new HashMap<>(); + class FilteredBuilder { + private final EventFilter filter; + private final Predicate predicate; + private final Map, BiConsumer> mapped = new HashMap<>(); - @SuppressWarnings("unchecked") - public Builder map(@NotNull Class eventType, - @NotNull BiConsumer<@NotNull T, @NotNull E> consumer) { - this.mapped.put(eventType, (BiConsumer) consumer); + FilteredBuilder(EventFilter filter, Predicate predicate) { + this.filter = filter; + this.predicate = predicate; + } + + public FilteredBuilder map(@NotNull Class eventType, + @NotNull BiConsumer<@NotNull T, @NotNull M> consumer) { + this.mapped.put(eventType, (BiConsumer) consumer); return this; } - public @NotNull EventInterface build() { - return new EventInterface<>(Map.copyOf(mapped)); + public @NotNull EventInterface build() { + final var copy = Map.copyOf(mapped); + final var eventTypes = copy.keySet(); + return new EventInterface<>() { + @Override + public @NotNull Collection> eventTypes() { + return eventTypes; + } + + @Override + public void call(@NotNull E event) { + final T handler = filter.getHandler(event); + if (!predicate.test(handler)) return; + final var consumer = copy.get(event.getClass()); + if (consumer == null) return; + consumer.accept(handler, event); + } + }; } } } diff --git a/src/main/java/net/minestom/server/event/EventNode.java b/src/main/java/net/minestom/server/event/EventNode.java index 4440f5e98..029571d0c 100644 --- a/src/main/java/net/minestom/server/event/EventNode.java +++ b/src/main/java/net/minestom/server/event/EventNode.java @@ -192,6 +192,7 @@ public class EventNode { private final Map, ListenerEntry> listenerMap = new ConcurrentHashMap<>(); private final Set> children = new CopyOnWriteArraySet<>(); + private final Set> interfaces = new CopyOnWriteArraySet<>(); private final Map> mappedNode; protected final String name; @@ -250,6 +251,13 @@ public class EventNode { // Cancelled by superclass 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 @@ -547,11 +555,8 @@ public class EventNode { return mappedNode.remove(value) != null; } - public void addInter(@NotNull EventInterface inter, @NotNull I value) { - inter.mapped.forEach((eventType, consumer) -> { - // TODO cache so listeners can be removed from the EventInterface - addListener((EventListener) EventListener.builder(eventType).handler(event -> consumer.accept(value, event)).build()); - }); + public void registerInterface(@NotNull EventInterface eventInterface) { + this.interfaces.add((EventInterface) eventInterface); } private void increaseChildListenerCount(Class eventClass, int count) { From bfdc5a53cad3b562a07cc777d93a56842ebe3bda Mon Sep 17 00:00:00 2001 From: TheMode Date: Mon, 16 Aug 2021 19:04:42 +0200 Subject: [PATCH 09/19] Style cleanup --- .../net/minestom/server/event/EventNode.java | 58 +++++-------------- 1 file changed, 16 insertions(+), 42 deletions(-) diff --git a/src/main/java/net/minestom/server/event/EventNode.java b/src/main/java/net/minestom/server/event/EventNode.java index 029571d0c..018f60ec4 100644 --- a/src/main/java/net/minestom/server/event/EventNode.java +++ b/src/main/java/net/minestom/server/event/EventNode.java @@ -214,24 +214,6 @@ public class EventNode { this.mappedNode = mapCache.asMap(); } - /** - * Condition to enter the node. - * - * @param event the called event - * @return true to enter the node, false otherwise - */ - protected boolean condition(@NotNull T event) { - if (predicate == null) - return true; - final var value = filter.getHandler(event); - try { - return predicate.test(event, value); - } catch (Exception e) { - MinecraftServer.getExceptionManager().handleException(e); - return false; - } - } - /** * Executes the given event on this node. The event must pass all conditions before * it will be forwarded to the listeners. @@ -243,13 +225,16 @@ public class EventNode { */ public void call(@NotNull T event) { final var eventClass = event.getClass(); - if (!eventType.isAssignableFrom(eventClass)) { - // Invalid event type - return; - } - if (!condition(event)) { - // Cancelled by superclass - return; + 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()) { @@ -269,10 +254,7 @@ public class EventNode { } // Process listener list final var entry = listenerMap.get(eventClass); - if (entry == null) { - // No listener nor children - return; - } + if (entry == null) return; // No listener nor children final var listeners = entry.listeners; if (!listeners.isEmpty()) { @@ -352,9 +334,7 @@ public class EventNode { */ @Contract(pure = true) public @NotNull List> findChildren(@NotNull String name, Class eventType) { - if (children.isEmpty()) { - return Collections.emptyList(); - } + if (children.isEmpty()) return Collections.emptyList(); synchronized (GLOBAL_CHILD_LOCK) { List> result = new ArrayList<>(); for (EventNode child : children) { @@ -388,9 +368,7 @@ public class EventNode { * @param eventNode The replacement node */ public void replaceChildren(@NotNull String name, @NotNull Class eventType, @NotNull EventNode eventNode) { - if (children.isEmpty()) { - return; - } + if (children.isEmpty()) return; synchronized (GLOBAL_CHILD_LOCK) { for (EventNode child : children) { if (EventNode.equals(child, name, eventType)) { @@ -422,9 +400,7 @@ public class EventNode { * @param eventType The node type to filter for */ public void removeChildren(@NotNull String name, @NotNull Class eventType) { - if (children.isEmpty()) { - return; - } + if (children.isEmpty()) return; synchronized (GLOBAL_CHILD_LOCK) { for (EventNode child : children) { if (EventNode.equals(child, name, eventType)) { @@ -463,8 +439,7 @@ public class EventNode { synchronized (lock) { child.listenerMap.forEach((eventClass, eventListeners) -> { final var entry = child.listenerMap.get(eventClass); - if (entry == null) - return; + if (entry == null) return; final int childCount = entry.listeners.size() + entry.childCount; increaseChildListenerCount(eventClass, childCount); }); @@ -526,8 +501,7 @@ public class EventNode { synchronized (GLOBAL_CHILD_LOCK) { final var eventType = listener.getEventType(); var entry = listenerMap.get(eventType); - if (entry == null) - return this; + if (entry == null) return this; var listeners = entry.listeners; final boolean removed = listeners.remove(listener); if (removed && parent != null) { From 2339fe1c0749db4ad199ae8275fb2d41488e9a2f Mon Sep 17 00:00:00 2001 From: TheMode Date: Mon, 16 Aug 2021 20:07:57 +0200 Subject: [PATCH 10/19] Cache BlockImpl hashcode --- .../net/minestom/server/instance/block/BlockImpl.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/main/java/net/minestom/server/instance/block/BlockImpl.java b/src/main/java/net/minestom/server/instance/block/BlockImpl.java index d9be4d089..df311d2f4 100644 --- a/src/main/java/net/minestom/server/instance/block/BlockImpl.java +++ b/src/main/java/net/minestom/server/instance/block/BlockImpl.java @@ -69,6 +69,8 @@ final class BlockImpl implements Block { private final NBTCompound nbt; private final BlockHandler handler; + private int hashCode; // Cache + BlockImpl(@NotNull Registry.BlockEntry registry, @NotNull Map, Block> propertyEntry, @NotNull Map properties, @@ -151,7 +153,12 @@ final class BlockImpl implements Block { @Override public int hashCode() { - return Objects.hash(stateId(), nbt, handler); + int result = hashCode; + if (result == 0) { + result = Objects.hash(stateId(), nbt, handler); + this.hashCode = result; + } + return result; } @Override From 42a6654993eec1c2be8f5c27609bc78b0aee9b30 Mon Sep 17 00:00:00 2001 From: TheMode Date: Mon, 16 Aug 2021 20:08:48 +0200 Subject: [PATCH 11/19] Improve tickable block loop --- .../server/instance/DynamicChunk.java | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/main/java/net/minestom/server/instance/DynamicChunk.java b/src/main/java/net/minestom/server/instance/DynamicChunk.java index 38060b5b7..0a5595af5 100644 --- a/src/main/java/net/minestom/server/instance/DynamicChunk.java +++ b/src/main/java/net/minestom/server/instance/DynamicChunk.java @@ -2,6 +2,7 @@ package net.minestom.server.instance; import com.extollit.gaming.ai.path.model.ColumnarOcclusionFieldList; import it.unimi.dsi.fastutil.ints.Int2ObjectAVLTreeMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectMaps; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import net.minestom.server.coordinate.Vec; import net.minestom.server.entity.Player; @@ -10,8 +11,8 @@ import net.minestom.server.instance.block.Block; import net.minestom.server.instance.block.BlockHandler; import net.minestom.server.network.packet.server.play.ChunkDataPacket; import net.minestom.server.network.packet.server.play.UpdateLightPacket; -import net.minestom.server.network.player.PlayerSocketConnection; import net.minestom.server.network.player.PlayerConnection; +import net.minestom.server.network.player.PlayerSocketConnection; import net.minestom.server.utils.ArrayUtils; import net.minestom.server.utils.PacketUtils; import net.minestom.server.utils.chunk.ChunkUtils; @@ -88,20 +89,18 @@ public class DynamicChunk extends Chunk { @Override public void tick(long time) { - if (tickableMap.isEmpty()) - return; - for (var entry : tickableMap.int2ObjectEntrySet()) { + if (tickableMap.isEmpty()) return; + Int2ObjectMaps.fastForEach(tickableMap, entry -> { final int index = entry.getIntKey(); final Block block = entry.getValue(); - final var handler = block.handler(); - if (handler != null) { - final int x = ChunkUtils.blockIndexToChunkPositionX(index); - final int y = ChunkUtils.blockIndexToChunkPositionY(index); - final int z = ChunkUtils.blockIndexToChunkPositionZ(index); - final Vec blockPosition = new Vec(x, y, z); - handler.tick(new BlockHandler.Tick(block, instance, blockPosition)); - } - } + final BlockHandler handler = block.handler(); + if (handler == null) return; + final int x = ChunkUtils.blockIndexToChunkPositionX(index); + final int y = ChunkUtils.blockIndexToChunkPositionY(index); + final int z = ChunkUtils.blockIndexToChunkPositionZ(index); + final Vec blockPosition = new Vec(x, y, z); + handler.tick(new BlockHandler.Tick(block, instance, blockPosition)); + }); } @Override From e1c1f1d5450b6b4f575b922c1dcb03618b051940 Mon Sep 17 00:00:00 2001 From: TheMode Date: Mon, 16 Aug 2021 23:42:02 +0200 Subject: [PATCH 12/19] Make `EventNode` an interface --- .../net/minestom/server/event/EventNode.java | 370 +++--------------- .../minestom/server/event/EventNodeImpl.java | 356 +++++++++++++++++ 2 files changed, 408 insertions(+), 318 deletions(-) create mode 100644 src/main/java/net/minestom/server/event/EventNodeImpl.java diff --git a/src/main/java/net/minestom/server/event/EventNode.java b/src/main/java/net/minestom/server/event/EventNode.java index 018f60ec4..f867a6bd9 100644 --- a/src/main/java/net/minestom/server/event/EventNode.java +++ b/src/main/java/net/minestom/server/event/EventNode.java @@ -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 The event type accepted by this node */ -public class EventNode { +@ApiStatus.NonExtendable +public interface EventNode { /** * Creates an event node which accepts any event type with no filtering. @@ -39,7 +33,7 @@ public class EventNode { * @return An event node with no filtering */ @Contract(value = "_ -> new", pure = true) - public static @NotNull EventNode all(@NotNull String name) { + static @NotNull EventNode all(@NotNull String name) { return type(name, EventFilter.ALL); } @@ -58,8 +52,8 @@ public class EventNode { * @return A node with just an event type filter */ @Contract(value = "_, _ -> new", pure = true) - public static @NotNull EventNode type(@NotNull String name, - @NotNull EventFilter filter) { + static @NotNull EventNode type(@NotNull String name, + @NotNull EventFilter filter) { return create(name, filter, null); } @@ -83,9 +77,9 @@ public class EventNode { * @return A node with an event type filter as well as a condition on the event. */ @Contract(value = "_, _, _ -> new", pure = true) - public static @NotNull EventNode event(@NotNull String name, - @NotNull EventFilter filter, - @NotNull Predicate predicate) { + static @NotNull EventNode event(@NotNull String name, + @NotNull EventFilter filter, + @NotNull Predicate predicate) { return create(name, filter, (e, h) -> predicate.test(e)); } @@ -111,9 +105,9 @@ public class EventNode { * @return A node with an event type filter as well as a condition on the event. */ @Contract(value = "_, _, _ -> new", pure = true) - public static @NotNull EventNode type(@NotNull String name, - @NotNull EventFilter filter, - @NotNull BiPredicate predicate) { + static @NotNull EventNode type(@NotNull String name, + @NotNull EventFilter filter, + @NotNull BiPredicate predicate) { return create(name, filter, predicate); } @@ -136,9 +130,9 @@ public class EventNode { * @return A node with an event type filter as well as a condition on the event. */ @Contract(value = "_, _, _ -> new", pure = true) - public static @NotNull EventNode value(@NotNull String name, - @NotNull EventFilter filter, - @NotNull Predicate predicate) { + static @NotNull EventNode value(@NotNull String name, + @NotNull EventFilter filter, + @NotNull Predicate predicate) { return create(name, filter, (e, h) -> predicate.test(h)); } @@ -155,9 +149,9 @@ public class EventNode { * @return A node with an event type filter as well as a handler with the provided tag */ @Contract(value = "_, _, _ -> new", pure = true) - public static @NotNull EventNode tag(@NotNull String name, - @NotNull EventFilter filter, - @NotNull Tag tag) { + static @NotNull EventNode tag(@NotNull String name, + @NotNull EventFilter filter, + @NotNull Tag tag) { return create(name, filter, (e, h) -> h.hasTag(tag)); } @@ -173,45 +167,18 @@ public class EventNode { * @return A node with an event type filter as well as a handler with the provided tag */ @Contract(value = "_, _, _, _ -> new", pure = true) - public static @NotNull EventNode tag(@NotNull String name, - @NotNull EventFilter filter, - @NotNull Tag tag, - @NotNull Predicate<@Nullable V> consumer) { + static @NotNull EventNode tag(@NotNull String name, + @NotNull EventFilter filter, + @NotNull Tag tag, + @NotNull Predicate<@Nullable V> consumer) { return create(name, filter, (e, h) -> consumer.test(h.getTag(tag))); } private static EventNode create(@NotNull String name, @NotNull EventFilter filter, @Nullable BiPredicate predicate) { - return new EventNode<>(name, filter, predicate != null ? (e, o) -> predicate.test(e, (V) o) : null); - } - - private static final Map, List>> HANDLER_SUPPLIERS = new ConcurrentHashMap<>(); - private static final Object GLOBAL_CHILD_LOCK = new Object(); - private final Object lock = new Object(); - - private final Map, ListenerEntry> listenerMap = new ConcurrentHashMap<>(); - private final Set> children = new CopyOnWriteArraySet<>(); - private final Set> interfaces = new CopyOnWriteArraySet<>(); - private final Map> mappedNode; - - protected final String name; - protected final EventFilter filter; - protected final BiPredicate predicate; - protected final Class eventType; - private volatile int priority; - private volatile EventNode parent; - - protected EventNode(@NotNull String name, - @NotNull EventFilter filter, - @Nullable BiPredicate predicate) { - this.name = name; - this.filter = filter; - this.predicate = predicate; - this.eventType = filter.eventType(); - - Cache> 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 { * * @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 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 { * @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 { } @Contract(pure = true) - public @NotNull String getName() { - return name; - } + @NotNull Class getEventType(); @Contract(pure = true) - public int getPriority() { - return priority; - } + @NotNull String getName(); + + @Contract(pure = true) + int getPriority(); @Contract(value = "_ -> this") - public @NotNull EventNode setPriority(int priority) { - this.priority = priority; - return this; - } + @NotNull EventNode setPriority(int priority); @Contract(pure = true) - public @Nullable EventNode getParent() { - return parent; - } + @Nullable EventNode getParent(); /** * Returns an unmodifiable view of the children in this node. @@ -321,9 +228,7 @@ public class EventNode { * @see #removeChild(EventNode) */ @Contract(pure = true) - public @NotNull Set<@NotNull EventNode> getChildren() { - return Collections.unmodifiableSet(children); - } + @NotNull Set<@NotNull EventNode> getChildren(); /** * Locates all child nodes with the given name and event type recursively starting at this node. @@ -333,19 +238,7 @@ public class EventNode { * @return All matching event nodes */ @Contract(pure = true) - public @NotNull List> findChildren(@NotNull String name, Class eventType) { - if (children.isEmpty()) return Collections.emptyList(); - synchronized (GLOBAL_CHILD_LOCK) { - List> result = new ArrayList<>(); - for (EventNode child : children) { - if (EventNode.equals(child, name, eventType)) { - result.add((EventNode) child); - } - result.addAll(child.findChildren(name, eventType)); - } - return result; - } - } + @NotNull List> findChildren(@NotNull String name, Class eventType); /** * Locates all child nodes with the given name and event type recursively starting at this node. @@ -354,8 +247,8 @@ public class EventNode { * @return All matching event nodes */ @Contract(pure = true) - public @NotNull List> findChildren(@NotNull String name) { - return findChildren(name, eventType); + default @NotNull List> findChildren(@NotNull String name) { + return findChildren(name, getEventType()); } /** @@ -367,19 +260,7 @@ public class EventNode { * @param eventType The event node type to filter for * @param eventNode The replacement node */ - public void replaceChildren(@NotNull String name, @NotNull Class eventType, @NotNull EventNode eventNode) { - if (children.isEmpty()) return; - synchronized (GLOBAL_CHILD_LOCK) { - for (EventNode child : children) { - if (EventNode.equals(child, name, eventType)) { - removeChild(child); - addChild(eventNode); - continue; - } - child.replaceChildren(name, eventType, eventNode); - } - } - } + void replaceChildren(@NotNull String name, @NotNull Class eventType, @NotNull EventNode eventNode); /** * Replaces all children matching the given name and type recursively starting from this node. @@ -389,8 +270,8 @@ public class EventNode { * @param name The node name to filter for * @param eventNode The replacement node */ - public void replaceChildren(@NotNull String name, @NotNull EventNode eventNode) { - replaceChildren(name, eventType, eventNode); + default void replaceChildren(@NotNull String name, @NotNull EventNode eventNode) { + replaceChildren(name, getEventType(), eventNode); } /** @@ -399,27 +280,14 @@ public class EventNode { * @param name The node name to filter for * @param eventType The node type to filter for */ - public void removeChildren(@NotNull String name, @NotNull Class eventType) { - if (children.isEmpty()) return; - synchronized (GLOBAL_CHILD_LOCK) { - for (EventNode child : children) { - if (EventNode.equals(child, name, eventType)) { - removeChild(child); - continue; - } - child.removeChildren(name, eventType); - } - } - } + void removeChildren(@NotNull String name, @NotNull Class 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 { * @return this, can be used for chaining */ @Contract(value = "_ -> this") - public @NotNull EventNode addChild(@NotNull EventNode 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) 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 addChild(@NotNull EventNode child); /** * Directly removes the given child from this node. @@ -456,135 +305,20 @@ public class EventNode { * @return this, can be used for chaining */ @Contract(value = "_ -> this") - public @NotNull EventNode removeChild(@NotNull EventNode 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 removeChild(@NotNull EventNode child); @Contract(value = "_ -> this") - public @NotNull EventNode addListener(@NotNull EventListener listener) { - synchronized (GLOBAL_CHILD_LOCK) { - final var eventType = listener.getEventType(); - var entry = listenerMap.computeIfAbsent(eventType, aClass -> new ListenerEntry<>()); - entry.listeners.add((EventListener) listener); - if (parent != null) { - synchronized (parent.lock) { - parent.increaseChildListenerCount(eventType, 1); - } - } - } - return this; - } + @NotNull EventNode addListener(@NotNull EventListener listener); @Contract(value = "_, _ -> this") - public @NotNull EventNode addListener(@NotNull Class eventType, @NotNull Consumer<@NotNull E> listener) { - return addListener(EventListener.of(eventType, listener)); - } + @NotNull EventNode addListener(@NotNull Class eventType, @NotNull Consumer<@NotNull E> listener); @Contract(value = "_ -> this") - public @NotNull EventNode removeListener(@NotNull EventListener 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 removeListener(@NotNull EventListener listener); - public void map(@NotNull EventNode 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) node); - } + void map(@NotNull EventNode 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 eventInterface) { - this.interfaces.add((EventInterface) eventInterface); - } - - private void increaseChildListenerCount(Class 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 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> 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> getEventFilters(Class eventType) { - return HANDLER_SUPPLIERS.computeIfAbsent(eventType, clazz -> - FILTERS.stream().filter(eventFilter -> eventFilter.eventType().isAssignableFrom(clazz)).collect(Collectors.toList())); - } - - private static class ListenerEntry { - private static final AtomicIntegerFieldUpdater CHILD_UPDATER = - AtomicIntegerFieldUpdater.newUpdater(ListenerEntry.class, "childCount"); - - List> listeners = new CopyOnWriteArrayList<>(); - volatile int childCount; - - private static int addAndGet(ListenerEntry entry, int add) { - return CHILD_UPDATER.addAndGet(entry, add); - } - } + void registerInterface(@NotNull EventInterface eventInterface); } diff --git a/src/main/java/net/minestom/server/event/EventNodeImpl.java b/src/main/java/net/minestom/server/event/EventNodeImpl.java new file mode 100644 index 000000000..b4a48d139 --- /dev/null +++ b/src/main/java/net/minestom/server/event/EventNodeImpl.java @@ -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 implements EventNode { + private static final Object GLOBAL_CHILD_LOCK = new Object(); + private final Object lock = new Object(); + + private final Map, ListenerEntry> listenerMap = new ConcurrentHashMap<>(); + private final Set> children = new CopyOnWriteArraySet<>(); + private final Map> mappedNodeCache = new WeakHashMap<>(); + + private final String name; + private final EventFilter filter; + private final BiPredicate predicate; + private final Class eventType; + private volatile int priority; + private volatile EventNodeImpl parent; + + protected EventNodeImpl(@NotNull String name, + @NotNull EventFilter filter, + @Nullable BiPredicate 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 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 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 @NotNull List> findChildren(@NotNull String name, Class eventType) { + if (children.isEmpty()) return Collections.emptyList(); + synchronized (GLOBAL_CHILD_LOCK) { + 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) { + if (children.isEmpty()) return; + synchronized (GLOBAL_CHILD_LOCK) { + for (EventNode 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 eventType) { + if (children.isEmpty()) return; + synchronized (GLOBAL_CHILD_LOCK) { + for (EventNode 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 addChild(@NotNull EventNode child) { + synchronized (GLOBAL_CHILD_LOCK) { + final var childImpl = (EventNodeImpl) 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) childImpl); + if (result) { + childImpl.parent = this; + // Increase listener count + propagateNode(childImpl, IntUnaryOperator.identity()); + } + } + return this; + } + + @Override + public @NotNull EventNode removeChild(@NotNull EventNode child) { + synchronized (GLOBAL_CHILD_LOCK) { + final boolean result = this.children.remove(child); + if (result) { + final var childImpl = (EventNodeImpl) child; + childImpl.parent = null; + // Decrease listener count + propagateNode(childImpl, count -> -count); + } + } + return this; + } + + @Override + public @NotNull EventNode addListener(@NotNull EventListener listener) { + synchronized (GLOBAL_CHILD_LOCK) { + final var eventType = listener.getEventType(); + var entry = getEntry(eventType); + entry.listeners.add((EventListener) listener); + final var parent = this.parent; + if (parent != null) { + synchronized (parent.lock) { + parent.propagateChildCountChange(eventType, 1); + } + } + } + return this; + } + + @Override + public @NotNull EventNode addListener(@NotNull Class eventType, @NotNull Consumer<@NotNull E> listener) { + return addListener(EventListener.of(eventType, listener)); + } + + @Override + public @NotNull EventNode removeListener(@NotNull EventListener 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 node, @NotNull Object value) { + final var nodeImpl = (EventNodeImpl) 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) 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 eventInterface) { + synchronized (GLOBAL_CHILD_LOCK) { + for (var eventType : eventInterface.eventTypes()) { + var entry = getEntry((Class) eventType); + entry.interfaces.add((EventInterface) eventInterface); + } + } + } + + @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() { + return parent; + } + + private void propagateChildCountChange(Class 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 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 getEntry(Class type) { + return listenerMap.computeIfAbsent(type, aClass -> new ListenerEntry<>((Class) 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 { + private static final List> FILTERS = List.of( + EventFilter.ENTITY, + EventFilter.ITEM, EventFilter.INSTANCE, + EventFilter.INVENTORY, EventFilter.BLOCK); + @SuppressWarnings("rawtypes") + private static final AtomicIntegerFieldUpdater CHILD_UPDATER = + AtomicIntegerFieldUpdater.newUpdater(ListenerEntry.class, "childCount"); + + final List> filters; + final List> listeners = new CopyOnWriteArrayList<>(); + final Set> interfaces = new CopyOnWriteArraySet<>(); + final Map> mappedNode = new WeakHashMap<>(); + volatile int childCount; + + ListenerEntry(Class 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); + } + } +} From d5cdb36e477152ee5c6974d7a82d6ba219eccc2d Mon Sep 17 00:00:00 2001 From: TheMode Date: Tue, 17 Aug 2021 01:35:16 +0200 Subject: [PATCH 13/19] Remove unnecessary list check --- .../minestom/server/event/EventInterface.java | 1 - .../minestom/server/event/EventNodeImpl.java | 79 ++++++++----------- 2 files changed, 35 insertions(+), 45 deletions(-) diff --git a/src/main/java/net/minestom/server/event/EventInterface.java b/src/main/java/net/minestom/server/event/EventInterface.java index 6c3f1854d..529537157 100644 --- a/src/main/java/net/minestom/server/event/EventInterface.java +++ b/src/main/java/net/minestom/server/event/EventInterface.java @@ -48,7 +48,6 @@ public interface EventInterface { final T handler = filter.getHandler(event); if (!predicate.test(handler)) return; final var consumer = copy.get(event.getClass()); - if (consumer == null) return; consumer.accept(handler, event); } }; diff --git a/src/main/java/net/minestom/server/event/EventNodeImpl.java b/src/main/java/net/minestom/server/event/EventNodeImpl.java index b4a48d139..e45a50cc3 100644 --- a/src/main/java/net/minestom/server/event/EventNodeImpl.java +++ b/src/main/java/net/minestom/server/event/EventNodeImpl.java @@ -57,46 +57,7 @@ class EventNodeImpl implements EventNode { // 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 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 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); - } - } - } - } + entry.call(event); // Process children if (entry.childCount > 0) { this.children.stream() @@ -241,7 +202,8 @@ class EventNodeImpl implements EventNode { }); Check.stateCondition(!correct, "The node filter {0} is not compatible with type {1}", nodeType, valueType); synchronized (mappedNodeCache) { - entry.mappedNode.put(value, (EventNode) node); + System.out.println("add " + value + " " + nodeImpl + " " + type); + entry.mappedNode.put(value, (EventNode) nodeImpl); mappedNodeCache.put(value, entry); } }); @@ -297,7 +259,7 @@ class EventNodeImpl implements EventNode { private void propagateChildCountChange(Class eventClass, int count) { var entry = getEntry(eventClass); - final int result = ListenerEntry.addAndGet(entry, count); + final int result = ListenerEntry.CHILD_UPDATER.addAndGet(entry, count); if (result == 0 && entry.listeners.isEmpty()) { this.listenerMap.remove(eventClass); } else if (result < 0) { @@ -349,8 +311,37 @@ class EventNodeImpl implements EventNode { 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); + void call(T event) { + // Event interfaces + if (!interfaces.isEmpty()) { + for (EventInterface inter : interfaces) { + inter.call(event); + } + } + // Mapped listeners + if (!mappedNode.isEmpty()) { + // 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 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); + } + } + } } } } From a5a3b4f31e9959b0b8389b00cba64bc03f1d1e76 Mon Sep 17 00:00:00 2001 From: TheMode Date: Tue, 17 Aug 2021 01:55:22 +0200 Subject: [PATCH 14/19] Remove unnecessary map lookup for event interfaces --- .../minestom/server/event/EventInterface.java | 17 +++++++++++------ .../minestom/server/event/EventNodeImpl.java | 10 ++++++---- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/src/main/java/net/minestom/server/event/EventInterface.java b/src/main/java/net/minestom/server/event/EventInterface.java index 529537157..17fadc53d 100644 --- a/src/main/java/net/minestom/server/event/EventInterface.java +++ b/src/main/java/net/minestom/server/event/EventInterface.java @@ -6,6 +6,7 @@ import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.function.BiConsumer; +import java.util.function.Consumer; import java.util.function.Predicate; public interface EventInterface { @@ -16,7 +17,8 @@ public interface EventInterface { @NotNull Collection> eventTypes(); - void call(@NotNull E event); + Consumer consumer(Class eventType); + class FilteredBuilder { private final EventFilter filter; @@ -30,6 +32,7 @@ public interface EventInterface { public FilteredBuilder map(@NotNull Class eventType, @NotNull BiConsumer<@NotNull T, @NotNull M> consumer) { + //noinspection unchecked this.mapped.put(eventType, (BiConsumer) consumer); return this; } @@ -44,11 +47,13 @@ public interface EventInterface { } @Override - public void call(@NotNull E event) { - final T handler = filter.getHandler(event); - if (!predicate.test(handler)) return; - final var consumer = copy.get(event.getClass()); - consumer.accept(handler, event); + public Consumer consumer(Class eventType) { + final var consumer = copy.get(eventType); + return event -> { + final T handler = filter.getHandler(event); + if (!predicate.test(handler)) return; + consumer.accept(handler, event); + }; } }; } diff --git a/src/main/java/net/minestom/server/event/EventNodeImpl.java b/src/main/java/net/minestom/server/event/EventNodeImpl.java index e45a50cc3..020d469ba 100644 --- a/src/main/java/net/minestom/server/event/EventNodeImpl.java +++ b/src/main/java/net/minestom/server/event/EventNodeImpl.java @@ -202,7 +202,6 @@ class EventNodeImpl implements EventNode { }); Check.stateCondition(!correct, "The node filter {0} is not compatible with type {1}", nodeType, valueType); synchronized (mappedNodeCache) { - System.out.println("add " + value + " " + nodeImpl + " " + type); entry.mappedNode.put(value, (EventNode) nodeImpl); mappedNodeCache.put(value, entry); } @@ -226,7 +225,9 @@ class EventNodeImpl implements EventNode { synchronized (GLOBAL_CHILD_LOCK) { for (var eventType : eventInterface.eventTypes()) { var entry = getEntry((Class) eventType); + final var consumer = eventInterface.consumer(eventType); entry.interfaces.add((EventInterface) eventInterface); + entry.interfaceConsumers.add((Consumer) consumer); } } } @@ -304,6 +305,7 @@ class EventNodeImpl implements EventNode { final List> filters; final List> listeners = new CopyOnWriteArrayList<>(); final Set> interfaces = new CopyOnWriteArraySet<>(); + final Set> interfaceConsumers = new CopyOnWriteArraySet<>(); final Map> mappedNode = new WeakHashMap<>(); volatile int childCount; @@ -313,9 +315,9 @@ class EventNodeImpl implements EventNode { void call(T event) { // Event interfaces - if (!interfaces.isEmpty()) { - for (EventInterface inter : interfaces) { - inter.call(event); + if (!interfaceConsumers.isEmpty()) { + for (var consumer : interfaceConsumers) { + consumer.accept(event); } } // Mapped listeners From 390c383e82e16b18ac312cc55d8f546d3cea2c3f Mon Sep 17 00:00:00 2001 From: TheMode Date: Tue, 17 Aug 2021 02:24:21 +0200 Subject: [PATCH 15/19] Rename EventInterface, add `unregister` --- ...{EventInterface.java => EventBinding.java} | 28 +++++++++++-------- .../net/minestom/server/event/EventNode.java | 4 ++- .../minestom/server/event/EventNodeImpl.java | 27 ++++++++++++------ 3 files changed, 37 insertions(+), 22 deletions(-) rename src/main/java/net/minestom/server/event/{EventInterface.java => EventBinding.java} (65%) diff --git a/src/main/java/net/minestom/server/event/EventInterface.java b/src/main/java/net/minestom/server/event/EventBinding.java similarity index 65% rename from src/main/java/net/minestom/server/event/EventInterface.java rename to src/main/java/net/minestom/server/event/EventBinding.java index 17fadc53d..543ced590 100644 --- a/src/main/java/net/minestom/server/event/EventInterface.java +++ b/src/main/java/net/minestom/server/event/EventBinding.java @@ -9,7 +9,7 @@ import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.function.Predicate; -public interface EventInterface { +public interface EventBinding { static @NotNull FilteredBuilder filtered(@NotNull EventFilter filter, @NotNull Predicate predicate) { return new FilteredBuilder<>(filter, predicate); @@ -17,8 +17,7 @@ public interface EventInterface { @NotNull Collection> eventTypes(); - Consumer consumer(Class eventType); - + @NotNull Consumer<@NotNull E> consumer(@NotNull Class eventType); class FilteredBuilder { private final EventFilter filter; @@ -37,23 +36,28 @@ public interface EventInterface { return this; } - public @NotNull EventInterface build() { + public @NotNull EventBinding build() { final var copy = Map.copyOf(mapped); final var eventTypes = copy.keySet(); - return new EventInterface<>() { + + Map, Consumer> consumers = new HashMap<>(eventTypes.size()); + for (var eventType : eventTypes) { + final var consumer = copy.get(eventType); + consumers.put(eventType, event -> { + final T handler = filter.getHandler(event); + if (!predicate.test(handler)) return; + consumer.accept(handler, event); + }); + } + return new EventBinding<>() { @Override public @NotNull Collection> eventTypes() { return eventTypes; } @Override - public Consumer consumer(Class eventType) { - final var consumer = copy.get(eventType); - return event -> { - final T handler = filter.getHandler(event); - if (!predicate.test(handler)) return; - consumer.accept(handler, event); - }; + public @NotNull Consumer consumer(@NotNull Class eventType) { + return consumers.get(eventType); } }; } diff --git a/src/main/java/net/minestom/server/event/EventNode.java b/src/main/java/net/minestom/server/event/EventNode.java index f867a6bd9..f39e65cc0 100644 --- a/src/main/java/net/minestom/server/event/EventNode.java +++ b/src/main/java/net/minestom/server/event/EventNode.java @@ -320,5 +320,7 @@ public interface EventNode { boolean unmap(@NotNull Object value); - void registerInterface(@NotNull EventInterface eventInterface); + void register(@NotNull EventBinding binding); + + void unregister(@NotNull EventBinding binding); } diff --git a/src/main/java/net/minestom/server/event/EventNodeImpl.java b/src/main/java/net/minestom/server/event/EventNodeImpl.java index 020d469ba..efb01f53a 100644 --- a/src/main/java/net/minestom/server/event/EventNodeImpl.java +++ b/src/main/java/net/minestom/server/event/EventNodeImpl.java @@ -221,13 +221,23 @@ class EventNodeImpl implements EventNode { } @Override - public void registerInterface(@NotNull EventInterface eventInterface) { + public void register(@NotNull EventBinding binding) { synchronized (GLOBAL_CHILD_LOCK) { - for (var eventType : eventInterface.eventTypes()) { + for (var eventType : binding.eventTypes()) { var entry = getEntry((Class) eventType); - final var consumer = eventInterface.consumer(eventType); - entry.interfaces.add((EventInterface) eventInterface); - entry.interfaceConsumers.add((Consumer) consumer); + final var consumer = binding.consumer(eventType); + entry.bindingConsumers.add((Consumer) consumer); + } + } + } + + @Override + public void unregister(@NotNull EventBinding binding) { + synchronized (GLOBAL_CHILD_LOCK) { + for (var eventType : binding.eventTypes()) { + var entry = listenerMap.get(eventType); + if (entry == null) return; + entry.bindingConsumers.remove(binding.consumer(eventType)); } } } @@ -304,8 +314,7 @@ class EventNodeImpl implements EventNode { final List> filters; final List> listeners = new CopyOnWriteArrayList<>(); - final Set> interfaces = new CopyOnWriteArraySet<>(); - final Set> interfaceConsumers = new CopyOnWriteArraySet<>(); + final Set> bindingConsumers = new CopyOnWriteArraySet<>(); final Map> mappedNode = new WeakHashMap<>(); volatile int childCount; @@ -315,8 +324,8 @@ class EventNodeImpl implements EventNode { void call(T event) { // Event interfaces - if (!interfaceConsumers.isEmpty()) { - for (var consumer : interfaceConsumers) { + if (!bindingConsumers.isEmpty()) { + for (var consumer : bindingConsumers) { consumer.accept(event); } } From 523c9b512a1b2123bd658543fd73956877427625 Mon Sep 17 00:00:00 2001 From: TheMode Date: Tue, 17 Aug 2021 02:33:48 +0200 Subject: [PATCH 16/19] More cleanup --- .../net/minestom/server/event/EventNode.java | 4 ++- .../minestom/server/event/EventNodeImpl.java | 35 +++++++++---------- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/src/main/java/net/minestom/server/event/EventNode.java b/src/main/java/net/minestom/server/event/EventNode.java index f39e65cc0..6eb4187bb 100644 --- a/src/main/java/net/minestom/server/event/EventNode.java +++ b/src/main/java/net/minestom/server/event/EventNode.java @@ -311,7 +311,9 @@ public interface EventNode { @NotNull EventNode addListener(@NotNull EventListener listener); @Contract(value = "_, _ -> this") - @NotNull EventNode addListener(@NotNull Class eventType, @NotNull Consumer<@NotNull E> listener); + default @NotNull EventNode addListener(@NotNull Class eventType, @NotNull Consumer<@NotNull E> listener) { + return addListener(EventListener.of(eventType, listener)); + } @Contract(value = "_ -> this") @NotNull EventNode removeListener(@NotNull EventListener listener); diff --git a/src/main/java/net/minestom/server/event/EventNodeImpl.java b/src/main/java/net/minestom/server/event/EventNodeImpl.java index efb01f53a..fc82a4a18 100644 --- a/src/main/java/net/minestom/server/event/EventNodeImpl.java +++ b/src/main/java/net/minestom/server/event/EventNodeImpl.java @@ -88,8 +88,8 @@ class EventNodeImpl implements EventNode { @Override public void replaceChildren(@NotNull String name, @NotNull Class eventType, @NotNull EventNode eventNode) { - if (children.isEmpty()) return; synchronized (GLOBAL_CHILD_LOCK) { + if (children.isEmpty()) return; for (EventNode child : children) { if (equals(child, name, eventType)) { removeChild(child); @@ -103,8 +103,8 @@ class EventNodeImpl implements EventNode { @Override public void removeChildren(@NotNull String name, @NotNull Class eventType) { - if (children.isEmpty()) return; synchronized (GLOBAL_CHILD_LOCK) { + if (children.isEmpty()) return; for (EventNode child : children) { if (equals(child, name, eventType)) { removeChild(child); @@ -166,11 +166,6 @@ class EventNodeImpl implements EventNode { return this; } - @Override - public @NotNull EventNode addListener(@NotNull Class eventType, @NotNull Consumer<@NotNull E> listener) { - return addListener(EventListener.of(eventType, listener)); - } - @Override public @NotNull EventNode removeListener(@NotNull EventListener listener) { synchronized (GLOBAL_CHILD_LOCK) { @@ -191,7 +186,6 @@ class EventNodeImpl implements EventNode { @Override public void map(@NotNull EventNode node, @NotNull Object value) { final var nodeImpl = (EventNodeImpl) node; - final var nodeType = nodeImpl.eventType; final var valueType = value.getClass(); synchronized (GLOBAL_CHILD_LOCK) { nodeImpl.listenerMap.forEach((type, listenerEntry) -> { @@ -200,7 +194,7 @@ class EventNodeImpl implements EventNode { 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); + Check.stateCondition(!correct, "The node filter {0} is not compatible with type {1}", nodeImpl.eventType, valueType); synchronized (mappedNodeCache) { entry.mappedNode.put(value, (EventNode) nodeImpl); mappedNodeCache.put(value, entry); @@ -225,8 +219,7 @@ class EventNodeImpl implements EventNode { synchronized (GLOBAL_CHILD_LOCK) { for (var eventType : binding.eventTypes()) { var entry = getEntry((Class) eventType); - final var consumer = binding.consumer(eventType); - entry.bindingConsumers.add((Consumer) consumer); + entry.bindingConsumers.add((Consumer) binding.consumer(eventType)); } } } @@ -294,7 +287,7 @@ class EventNodeImpl implements EventNode { } private ListenerEntry getEntry(Class type) { - return listenerMap.computeIfAbsent(type, aClass -> new ListenerEntry<>((Class) aClass)); + return listenerMap.computeIfAbsent(type, aClass -> new ListenerEntry<>(this, (Class) aClass)); } private static boolean equals(EventNode node, String name, Class eventType) { @@ -312,13 +305,15 @@ class EventNodeImpl implements EventNode { private static final AtomicIntegerFieldUpdater CHILD_UPDATER = AtomicIntegerFieldUpdater.newUpdater(ListenerEntry.class, "childCount"); + final EventNodeImpl node; final List> filters; final List> listeners = new CopyOnWriteArrayList<>(); final Set> bindingConsumers = new CopyOnWriteArraySet<>(); final Map> mappedNode = new WeakHashMap<>(); volatile int childCount; - ListenerEntry(Class eventType) { + ListenerEntry(EventNodeImpl node, Class eventType) { + this.node = node; this.filters = FILTERS.stream().filter(eventFilter -> eventFilter.eventType().isAssignableFrom(eventType)).collect(Collectors.toList()); } @@ -331,11 +326,15 @@ class EventNodeImpl implements EventNode { } // Mapped listeners if (!mappedNode.isEmpty()) { - // 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); + synchronized (node.mappedNodeCache) { + if (!mappedNode.isEmpty()) { + // 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 From c5cf7e4ab22bbd7f56ec6133893aa946d3711d71 Mon Sep 17 00:00:00 2001 From: TheMode Date: Tue, 17 Aug 2021 02:35:43 +0200 Subject: [PATCH 17/19] Fix GlobalEventHandler --- src/main/java/net/minestom/server/event/GlobalEventHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/net/minestom/server/event/GlobalEventHandler.java b/src/main/java/net/minestom/server/event/GlobalEventHandler.java index 0e1a8c94f..b97f42cc3 100644 --- a/src/main/java/net/minestom/server/event/GlobalEventHandler.java +++ b/src/main/java/net/minestom/server/event/GlobalEventHandler.java @@ -7,7 +7,7 @@ import java.util.function.Consumer; /** * Object containing all the global event listeners. */ -public final class GlobalEventHandler extends EventNode { +public final class GlobalEventHandler extends EventNodeImpl { public GlobalEventHandler() { super("global", EventFilter.ALL, null); } From 488fe06f01a3d3d62c46532d16394d37b05c0de6 Mon Sep 17 00:00:00 2001 From: TheMode Date: Tue, 17 Aug 2021 02:58:12 +0200 Subject: [PATCH 18/19] Make new methods experimental --- src/main/java/net/minestom/server/event/EventBinding.java | 2 ++ src/main/java/net/minestom/server/event/EventNode.java | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/src/main/java/net/minestom/server/event/EventBinding.java b/src/main/java/net/minestom/server/event/EventBinding.java index 543ced590..8089671dd 100644 --- a/src/main/java/net/minestom/server/event/EventBinding.java +++ b/src/main/java/net/minestom/server/event/EventBinding.java @@ -1,5 +1,6 @@ package net.minestom.server.event; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import java.util.Collection; @@ -9,6 +10,7 @@ import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.function.Predicate; +@ApiStatus.Experimental public interface EventBinding { static @NotNull FilteredBuilder filtered(@NotNull EventFilter filter, @NotNull Predicate predicate) { diff --git a/src/main/java/net/minestom/server/event/EventNode.java b/src/main/java/net/minestom/server/event/EventNode.java index 6eb4187bb..83bf2440c 100644 --- a/src/main/java/net/minestom/server/event/EventNode.java +++ b/src/main/java/net/minestom/server/event/EventNode.java @@ -318,11 +318,15 @@ public interface EventNode { @Contract(value = "_ -> this") @NotNull EventNode removeListener(@NotNull EventListener listener); + @ApiStatus.Experimental void map(@NotNull EventNode node, @NotNull Object value); + @ApiStatus.Experimental boolean unmap(@NotNull Object value); + @ApiStatus.Experimental void register(@NotNull EventBinding binding); + @ApiStatus.Experimental void unregister(@NotNull EventBinding binding); } From d0a5c781cabdcde5d3dc4a7798ef0f7f0e1a11cb Mon Sep 17 00:00:00 2001 From: TheMode Date: Tue, 17 Aug 2021 03:10:16 +0200 Subject: [PATCH 19/19] Propagate binding listeners --- .../minestom/server/event/EventNodeImpl.java | 36 +++++++++++-------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/src/main/java/net/minestom/server/event/EventNodeImpl.java b/src/main/java/net/minestom/server/event/EventNodeImpl.java index fc82a4a18..8ae2507ec 100644 --- a/src/main/java/net/minestom/server/event/EventNodeImpl.java +++ b/src/main/java/net/minestom/server/event/EventNodeImpl.java @@ -156,12 +156,7 @@ class EventNodeImpl implements EventNode { final var eventType = listener.getEventType(); var entry = getEntry(eventType); entry.listeners.add((EventListener) listener); - final var parent = this.parent; - if (parent != null) { - synchronized (parent.lock) { - parent.propagateChildCountChange(eventType, 1); - } - } + propagateToParent(eventType, 1); } return this; } @@ -174,11 +169,7 @@ class EventNodeImpl implements EventNode { 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); - } - } + if (removed) propagateToParent(eventType, -1); } return this; } @@ -198,6 +189,7 @@ class EventNodeImpl implements EventNode { synchronized (mappedNodeCache) { entry.mappedNode.put(value, (EventNode) nodeImpl); mappedNodeCache.put(value, entry); + // TODO propagate } }); } @@ -209,7 +201,12 @@ class EventNodeImpl implements EventNode { synchronized (mappedNodeCache) { var entry = mappedNodeCache.remove(value); if (entry == null) return false; - return entry.mappedNode.remove(value) != null; + final EventNode previousNode = entry.mappedNode.remove(value); + if (previousNode != null) { + // TODO propagate + return true; + } + return false; } } } @@ -219,7 +216,8 @@ class EventNodeImpl implements EventNode { synchronized (GLOBAL_CHILD_LOCK) { for (var eventType : binding.eventTypes()) { var entry = getEntry((Class) eventType); - entry.bindingConsumers.add((Consumer) binding.consumer(eventType)); + final boolean added = entry.bindingConsumers.add((Consumer) binding.consumer(eventType)); + if (added) propagateToParent((Class) eventType, 1); } } } @@ -230,7 +228,8 @@ class EventNodeImpl implements EventNode { for (var eventType : binding.eventTypes()) { var entry = listenerMap.get(eventType); if (entry == null) return; - entry.bindingConsumers.remove(binding.consumer(eventType)); + final boolean removed = entry.bindingConsumers.remove(binding.consumer(eventType)); + if (removed) propagateToParent((Class) eventType, -1); } } } @@ -274,6 +273,15 @@ class EventNodeImpl implements EventNode { } } + private void propagateToParent(Class eventClass, int count) { + final var parent = this.parent; + if (parent != null) { + synchronized (parent.lock) { + parent.propagateChildCountChange(eventClass, count); + } + } + } + private void propagateNode(EventNodeImpl child, IntUnaryOperator operator) { synchronized (lock) { final var listeners = child.listenerMap;