mirror of
https://github.com/Minestom/Minestom.git
synced 2025-01-26 01:51:26 +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.coordinate.Vec;
|
||||||
import net.minestom.server.entity.metadata.EntityMeta;
|
import net.minestom.server.entity.metadata.EntityMeta;
|
||||||
import net.minestom.server.event.EventDispatcher;
|
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.entity.*;
|
||||||
import net.minestom.server.event.instance.AddEntityToInstanceEvent;
|
import net.minestom.server.event.instance.AddEntityToInstanceEvent;
|
||||||
import net.minestom.server.event.instance.RemoveEntityFromInstanceEvent;
|
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.Chunk;
|
||||||
import net.minestom.server.instance.EntityTracker;
|
import net.minestom.server.instance.EntityTracker;
|
||||||
import net.minestom.server.instance.Instance;
|
import net.minestom.server.instance.Instance;
|
||||||
@ -77,7 +81,7 @@ import java.util.function.UnaryOperator;
|
|||||||
* <p>
|
* <p>
|
||||||
* To create your own entity you probably want to extends {@link LivingEntity} or {@link EntityCreature} instead.
|
* 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 Int2ObjectSyncMap<Entity> ENTITY_BY_ID = Int2ObjectSyncMap.hashmap();
|
||||||
private static final Map<UUID, Entity> ENTITY_BY_UUID = new ConcurrentHashMap<>();
|
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;
|
protected final Set<Player> viewers = viewEngine.set;
|
||||||
private final MutableNBTCompound nbtCompound = new MutableNBTCompound();
|
private final MutableNBTCompound nbtCompound = new MutableNBTCompound();
|
||||||
private final Scheduler scheduler = Scheduler.newScheduler();
|
private final Scheduler scheduler = Scheduler.newScheduler();
|
||||||
|
private final EventNode<EntityEvent> eventNode = EventNode.lazyMap(this, EventFilter.ENTITY);
|
||||||
private final Set<Permission> permissions = new CopyOnWriteArraySet<>();
|
private final Set<Permission> permissions = new CopyOnWriteArraySet<>();
|
||||||
|
|
||||||
protected UUID uuid;
|
protected UUID uuid;
|
||||||
@ -1543,6 +1548,12 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ta
|
|||||||
TagReadable.fromCompound(nbtCompound.toCompound()));
|
TagReadable.fromCompound(nbtCompound.toCompound()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@ApiStatus.Experimental
|
||||||
|
public @NotNull EventNode<EntityEvent> eventNode() {
|
||||||
|
return eventNode;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Applies knockback to the entity
|
* 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)));
|
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,
|
private static <E extends Event, V> EventNode<E> create(@NotNull String name,
|
||||||
@NotNull EventFilter<E, V> filter,
|
@NotNull EventFilter<E, V> filter,
|
||||||
@Nullable BiPredicate<E, V> predicate) {
|
@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);
|
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.
|
* 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> {
|
non-sealed class EventNodeImpl<T extends Event> implements EventNode<T> {
|
||||||
private static final Object GLOBAL_CHILD_LOCK = new Object();
|
private static final Object GLOBAL_CHILD_LOCK = new Object();
|
||||||
|
|
||||||
private final ClassValue<Handle<T>> handleMap = new ClassValue<>() {
|
private final ClassValue<ListenerHandle<T>> handleMap = new ClassValue<>() {
|
||||||
@Override
|
@Override
|
||||||
protected Handle<T> computeValue(Class<?> type) {
|
protected ListenerHandle<T> computeValue(Class<?> type) {
|
||||||
//noinspection unchecked
|
//noinspection unchecked
|
||||||
return new Handle<>((Class<T>) type);
|
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;
|
final var mappedNodeCache = node.mappedNodeCache;
|
||||||
if (mappedNodeCache.isEmpty()) return null;
|
if (mappedNodeCache.isEmpty()) return null;
|
||||||
Set<EventFilter<E, ?>> filters = new HashSet<>(mappedNodeCache.size());
|
Set<EventFilter<E, ?>> filters = new HashSet<>(mappedNodeCache.size());
|
||||||
Map<Object, Handle<E>> handlers = new HashMap<>(mappedNodeCache.size());
|
Map<Object, Handle<E>> handlers = new WeakHashMap<>(mappedNodeCache.size());
|
||||||
// Retrieve all filters used to retrieve potential handlers
|
// Retrieve all filters used to retrieve potential handlers
|
||||||
for (var mappedEntry : mappedNodeCache.entrySet()) {
|
for (var mappedEntry : mappedNodeCache.entrySet()) {
|
||||||
final EventNodeImpl<E> mappedNode = mappedEntry.getValue();
|
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;
|
package net.minestom.server.event;
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import java.util.function.Consumer;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Object containing all the global event listeners.
|
* Object containing all the global event listeners.
|
||||||
*/
|
*/
|
||||||
@ -11,13 +7,4 @@ public final class GlobalEventHandler extends EventNodeImpl<Event> {
|
|||||||
public GlobalEventHandler() {
|
public GlobalEventHandler() {
|
||||||
super("global", EventFilter.ALL, null);
|
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;
|
package net.minestom.server.event;
|
||||||
|
|
||||||
|
import net.minestom.server.entity.Entity;
|
||||||
import net.minestom.server.event.trait.CancellableEvent;
|
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.ItemEvent;
|
||||||
import net.minestom.server.event.trait.RecursiveEvent;
|
import net.minestom.server.event.trait.RecursiveEvent;
|
||||||
import net.minestom.server.item.ItemStack;
|
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
|
@Test
|
||||||
public void testCall() {
|
public void testCall() {
|
||||||
var node = EventNode.all("main");
|
var node = EventNode.all("main");
|
||||||
@ -211,27 +220,4 @@ public class EventNodeTest {
|
|||||||
node.call(new ItemTestEvent(ItemStack.of(Material.DIAMOND)));
|
node.call(new ItemTestEvent(ItemStack.of(Material.DIAMOND)));
|
||||||
assertFalse(result.get());
|
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