Local node (#734)

This commit is contained in:
TheMode 2022-03-05 17:01:10 +01:00 committed by GitHub
parent cc69fcf05a
commit 1bea4848ac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 207 additions and 67 deletions

View File

@ -15,9 +15,13 @@ import net.minestom.server.coordinate.Pos;
import net.minestom.server.coordinate.Vec;
import net.minestom.server.entity.metadata.EntityMeta;
import net.minestom.server.event.EventDispatcher;
import net.minestom.server.event.EventFilter;
import net.minestom.server.event.EventHandler;
import net.minestom.server.event.EventNode;
import net.minestom.server.event.entity.*;
import net.minestom.server.event.instance.AddEntityToInstanceEvent;
import net.minestom.server.event.instance.RemoveEntityFromInstanceEvent;
import net.minestom.server.event.trait.EntityEvent;
import net.minestom.server.instance.Chunk;
import net.minestom.server.instance.EntityTracker;
import net.minestom.server.instance.Instance;
@ -77,7 +81,7 @@ import java.util.function.UnaryOperator;
* <p>
* To create your own entity you probably want to extends {@link LivingEntity} or {@link EntityCreature} instead.
*/
public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, TagHandler, PermissionHandler, HoverEventSource<ShowEntity>, Sound.Emitter {
public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, EventHandler<EntityEvent>, TagHandler, PermissionHandler, HoverEventSource<ShowEntity>, Sound.Emitter {
private static final Int2ObjectSyncMap<Entity> ENTITY_BY_ID = Int2ObjectSyncMap.hashmap();
private static final Map<UUID, Entity> ENTITY_BY_UUID = new ConcurrentHashMap<>();
@ -143,6 +147,7 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ta
protected final Set<Player> viewers = viewEngine.set;
private final MutableNBTCompound nbtCompound = new MutableNBTCompound();
private final Scheduler scheduler = Scheduler.newScheduler();
private final EventNode<EntityEvent> eventNode = EventNode.lazyMap(this, EventFilter.ENTITY);
private final Set<Permission> permissions = new CopyOnWriteArraySet<>();
protected UUID uuid;
@ -1543,6 +1548,12 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ta
TagReadable.fromCompound(nbtCompound.toCompound()));
}
@Override
@ApiStatus.Experimental
public @NotNull EventNode<EntityEvent> eventNode() {
return eventNode;
}
/**
* Applies knockback to the entity
*

View File

@ -0,0 +1,13 @@
package net.minestom.server.event;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
/**
* Represents an element which can have {@link Event} listeners assigned to it.
*/
@ApiStatus.Experimental
@ApiStatus.NonExtendable
public interface EventHandler<T extends Event> {
@NotNull EventNode<T> eventNode();
}

View File

@ -173,6 +173,13 @@ public sealed interface EventNode<T extends Event> permits EventNodeImpl {
return create(name, filter, (e, h) -> consumer.test(h.getTag(tag)));
}
@ApiStatus.Internal
@ApiStatus.Experimental
static <E extends Event> @NotNull EventNode<E> lazyMap(@NotNull Object owner,
@NotNull EventFilter<E, ?> filter) {
return new EventNodeLazyImpl<>(owner, filter);
}
private static <E extends Event, V> EventNode<E> create(@NotNull String name,
@NotNull EventFilter<E, V> filter,
@Nullable BiPredicate<E, V> predicate) {
@ -190,6 +197,10 @@ public sealed interface EventNode<T extends Event> permits EventNodeImpl {
getHandle((Class<T>) event.getClass()).call(event);
}
default boolean hasListener(@NotNull Class<? extends T> type) {
return getHandle(type).hasListener();
}
/**
* Gets the handle of an event type.
*

View File

@ -20,9 +20,9 @@ import java.util.function.Consumer;
non-sealed class EventNodeImpl<T extends Event> implements EventNode<T> {
private static final Object GLOBAL_CHILD_LOCK = new Object();
private final ClassValue<Handle<T>> handleMap = new ClassValue<>() {
private final ClassValue<ListenerHandle<T>> handleMap = new ClassValue<>() {
@Override
protected Handle<T> computeValue(Class<?> type) {
protected ListenerHandle<T> computeValue(Class<?> type) {
//noinspection unchecked
return new Handle<>((Class<T>) type);
}
@ -450,7 +450,7 @@ non-sealed class EventNodeImpl<T extends Event> implements EventNode<T> {
final var mappedNodeCache = node.mappedNodeCache;
if (mappedNodeCache.isEmpty()) return null;
Set<EventFilter<E, ?>> filters = new HashSet<>(mappedNodeCache.size());
Map<Object, Handle<E>> handlers = new HashMap<>(mappedNodeCache.size());
Map<Object, Handle<E>> handlers = new WeakHashMap<>(mappedNodeCache.size());
// Retrieve all filters used to retrieve potential handlers
for (var mappedEntry : mappedNodeCache.entrySet()) {
final EventNodeImpl<E> mappedNode = mappedEntry.getValue();

View File

@ -0,0 +1,65 @@
package net.minestom.server.event;
import net.minestom.server.MinecraftServer;
import org.jetbrains.annotations.NotNull;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
import java.util.function.Consumer;
final class EventNodeLazyImpl<E extends Event> extends EventNodeImpl<E> {
private static final VarHandle MAPPED;
static {
try {
MAPPED = MethodHandles.lookup().findVarHandle(EventNodeLazyImpl.class, "mapped", boolean.class);
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new IllegalStateException(e);
}
}
private final Object owner;
@SuppressWarnings("unused")
private boolean mapped;
EventNodeLazyImpl(@NotNull Object owner, @NotNull EventFilter<E, ?> filter) {
super(owner.toString(), filter, null);
this.owner = owner;
}
@Override
public @NotNull EventNode<E> addChild(@NotNull EventNode<? extends E> child) {
ensureMap();
return super.addChild(child);
}
@Override
public @NotNull EventNode<E> addListener(@NotNull EventListener<? extends E> listener) {
ensureMap();
return super.addListener(listener);
}
@Override
public @NotNull <E1 extends E> EventNode<E> addListener(@NotNull Class<E1> eventType, @NotNull Consumer<@NotNull E1> listener) {
ensureMap();
return super.addListener(eventType, listener);
}
@Override
public void map(@NotNull EventNode<? extends E> node, @NotNull Object value) {
ensureMap();
super.map(node, value);
}
@Override
public void register(@NotNull EventBinding<? extends E> binding) {
ensureMap();
super.register(binding);
}
private void ensureMap() {
if (MAPPED.compareAndSet(this, false, true)) {
MinecraftServer.getGlobalEventHandler().map(this, owner);
}
}
}

View File

@ -1,9 +1,5 @@
package net.minestom.server.event;
import org.jetbrains.annotations.NotNull;
import java.util.function.Consumer;
/**
* Object containing all the global event listeners.
*/
@ -11,13 +7,4 @@ public final class GlobalEventHandler extends EventNodeImpl<Event> {
public GlobalEventHandler() {
super("global", EventFilter.ALL, null);
}
/**
* @deprecated use {@link #addListener(Class, Consumer)}
*/
@Deprecated
public <V extends Event> boolean addEventCallback(@NotNull Class<V> eventClass, @NotNull EventCallback<V> eventCallback) {
addListener(eventClass, eventCallback::run);
return true;
}
}

View File

@ -1,27 +0,0 @@
package net.minestom.server.event.handler;
import net.minestom.server.event.Event;
import net.minestom.server.event.EventCallback;
import net.minestom.server.event.EventNode;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
/**
* Represents an element which can have {@link Event} listeners assigned to it.
* <p>
* Use {@link EventNode} directly.
*/
@Deprecated
public interface EventHandler<T extends Event> {
@ApiStatus.Internal
@Deprecated(forRemoval = true)
@NotNull EventNode<T> getEventNode();
@Deprecated
default <V extends T> boolean addEventCallback(@NotNull Class<V> eventClass, @NotNull EventCallback<V> eventCallback) {
var node = getEventNode();
node.addListener(eventClass, eventCallback::run);
return true;
}
}

View File

@ -0,0 +1,94 @@
package net.minestom.server.event;
import net.minestom.server.MinecraftServer;
import net.minestom.server.entity.Entity;
import net.minestom.server.entity.EntityType;
import net.minestom.server.item.ItemStack;
import net.minestom.server.item.Material;
import org.junit.jupiter.api.Test;
import java.lang.ref.WeakReference;
import java.util.concurrent.atomic.AtomicBoolean;
import static org.junit.jupiter.api.Assertions.*;
public class EventNodeMapTest {
@Test
public void map() {
var item = ItemStack.of(Material.DIAMOND);
var node = EventNode.all("main");
AtomicBoolean result = new AtomicBoolean(false);
var itemNode = EventNode.type("item_node", EventFilter.ITEM);
assertFalse(node.hasListener(EventNodeTest.ItemTestEvent.class));
itemNode.addListener(EventNodeTest.ItemTestEvent.class, event -> result.set(true));
assertDoesNotThrow(() -> node.map(itemNode, item));
assertTrue(node.hasListener(EventNodeTest.ItemTestEvent.class));
node.call(new EventNodeTest.ItemTestEvent(item));
assertTrue(result.get());
result.set(false);
node.call(new EventNodeTest.ItemTestEvent(ItemStack.of(Material.GOLD_INGOT)));
assertFalse(result.get());
result.set(false);
assertTrue(node.unmap(item));
node.call(new EventNodeTest.ItemTestEvent(item));
assertFalse(result.get());
}
@Test
public void entityLocal() {
var process = MinecraftServer.updateProcess();
var node = process.eventHandler();
var entity = new Entity(EntityType.ZOMBIE);
AtomicBoolean result = new AtomicBoolean(false);
var listener = EventListener.of(EventNodeTest.EntityTestEvent.class, event -> result.set(true));
var handle = node.getHandle(EventNodeTest.EntityTestEvent.class);
assertFalse(handle.hasListener());
entity.eventNode().addListener(listener);
assertTrue(handle.hasListener());
assertFalse(result.get());
handle.call(new EventNodeTest.EntityTestEvent(entity));
assertTrue(result.get());
result.set(false);
entity.eventNode().removeListener(listener);
handle.call(new EventNodeTest.EntityTestEvent(entity));
assertFalse(result.get());
}
@Test
public void ownerGC() {
// Ensure that the mapped object gets GCed
var item = ItemStack.of(Material.DIAMOND);
var node = EventNode.all("main");
var itemNode = EventNode.type("item_node", EventFilter.ITEM);
itemNode.addListener(EventNodeTest.ItemTestEvent.class, event -> {
});
node.map(itemNode, item);
node.call(new EventNodeTest.ItemTestEvent(item));
var ref = new WeakReference<>(item);
//noinspection UnusedAssignment
item = null;
waitUntilCleared(ref);
}
private static void waitUntilCleared(WeakReference<?> ref) {
while (ref.get() != null) {
System.gc();
try {
Thread.sleep(50);
} catch (InterruptedException ignore) {
}
}
}
}

View File

@ -1,6 +1,8 @@
package net.minestom.server.event;
import net.minestom.server.entity.Entity;
import net.minestom.server.event.trait.CancellableEvent;
import net.minestom.server.event.trait.EntityEvent;
import net.minestom.server.event.trait.ItemEvent;
import net.minestom.server.event.trait.RecursiveEvent;
import net.minestom.server.item.ItemStack;
@ -45,6 +47,13 @@ public class EventNodeTest {
}
}
record EntityTestEvent(Entity entity) implements EntityEvent {
@Override
public @NotNull Entity getEntity() {
return entity;
}
}
@Test
public void testCall() {
var node = EventNode.all("main");
@ -211,27 +220,4 @@ public class EventNodeTest {
node.call(new ItemTestEvent(ItemStack.of(Material.DIAMOND)));
assertFalse(result.get());
}
@Test
public void testMap() {
var item = ItemStack.of(Material.DIAMOND);
var node = EventNode.all("main");
AtomicBoolean result = new AtomicBoolean(false);
var itemNode = EventNode.type("item_node", EventFilter.ITEM);
itemNode.addListener(ItemTestEvent.class, event -> result.set(true));
assertDoesNotThrow(() -> node.map(itemNode, item));
node.call(new ItemTestEvent(item));
assertTrue(result.get());
result.set(false);
node.call(new ItemTestEvent(ItemStack.of(Material.GOLD_INGOT)));
assertFalse(result.get());
result.set(false);
assertTrue(node.unmap(item));
node.call(new ItemTestEvent(item));
assertFalse(result.get());
}
}