mirror of
https://github.com/Minestom/Minestom.git
synced 2024-11-13 22:25:53 +01:00
Local node (#734)
This commit is contained in:
parent
cc69fcf05a
commit
1bea4848ac
@ -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
|
||||
*
|
||||
|
13
src/main/java/net/minestom/server/event/EventHandler.java
Normal file
13
src/main/java/net/minestom/server/event/EventHandler.java
Normal 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();
|
||||
}
|
@ -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.
|
||||
*
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user