diff --git a/src/main/java/net/minestom/server/entity/Entity.java b/src/main/java/net/minestom/server/entity/Entity.java index 8d663401f..585a925e1 100644 --- a/src/main/java/net/minestom/server/entity/Entity.java +++ b/src/main/java/net/minestom/server/entity/Entity.java @@ -23,7 +23,6 @@ import net.minestom.server.instance.Instance; import net.minestom.server.instance.InstanceManager; import net.minestom.server.instance.block.CustomBlock; import net.minestom.server.lock.Acquirable; -import net.minestom.server.lock.AcquirableImpl; import net.minestom.server.lock.LockedElement; import net.minestom.server.network.packet.server.play.*; import net.minestom.server.network.player.PlayerConnection; @@ -126,7 +125,7 @@ public class Entity implements Viewable, Tickable, LockedElement, EventHandler, private long ticks; private final EntityTickEvent tickEvent = new EntityTickEvent(this); - private final Acquirable acquirable = new AcquirableImpl<>(this); + private final Acquirable acquirable = new Acquirable<>(this); /** * Lock used to support #switchEntityType diff --git a/src/main/java/net/minestom/server/lock/Acquirable.java b/src/main/java/net/minestom/server/lock/Acquirable.java index 9be5fc502..56d9486b5 100644 --- a/src/main/java/net/minestom/server/lock/Acquirable.java +++ b/src/main/java/net/minestom/server/lock/Acquirable.java @@ -13,32 +13,37 @@ import java.util.function.Consumer; /** * Represents an element which can be acquired. * Used for synchronization purpose. - *

- * Implementations of this class are recommended to be immutable (or at least thread-safe). - * The default one is {@link AcquirableImpl}. * * @param the acquirable object type */ -public interface Acquirable { +public class Acquirable { - ThreadLocal> CURRENT_ENTITIES = ThreadLocal.withInitial(Collections::emptyList); + public static final ThreadLocal> CURRENT_ENTITIES = ThreadLocal.withInitial(Collections::emptyList); - static @NotNull Collection<@NotNull Entity> currentEntities() { + public static @NotNull Collection<@NotNull Entity> currentEntities() { return CURRENT_ENTITIES.get(); } @ApiStatus.Internal - static void refreshEntities(@NotNull Collection<@NotNull Entity> entities) { + public static void refreshEntities(@NotNull Collection<@NotNull Entity> entities) { CURRENT_ENTITIES.set(entities); } + private final T value; + private final Handler handler; + + public Acquirable(@NotNull T value) { + this.value = value; + this.handler = new Handler(); + } + /** * Blocks the current thread until 'this' can be acquired, * and execute {@code consumer} as a callback with the acquired object. * * @param consumer the acquisition consumer */ - default void acquire(@NotNull Consumer<@NotNull T> consumer) { + public void acquire(@NotNull Consumer<@NotNull T> consumer) { final Thread currentThread = Thread.currentThread(); final BatchThread elementThread = getHandler().getBatchThread(); Acquisition.acquire(currentThread, elementThread, () -> consumer.accept(unwrap())); @@ -52,7 +57,7 @@ public interface Acquirable { * @return true if the acquisition happened without synchronization, * false otherwise */ - default boolean tryAcquire(@NotNull Consumer<@NotNull T> consumer) { + public boolean tryAcquire(@NotNull Consumer<@NotNull T> consumer) { final Thread currentThread = Thread.currentThread(); final BatchThread elementThread = getHandler().getBatchThread(); if (elementThread == null || elementThread == currentThread) { @@ -69,15 +74,32 @@ public interface Acquirable { * * @param consumer the consumer of the acquired object */ - default void scheduledAcquire(@NotNull Consumer consumer) { + public void scheduledAcquire(@NotNull Consumer consumer) { Acquisition.scheduledAcquireRequest(this, consumer); } - @NotNull T unwrap(); + /** + * Unwrap the contained object unsafely. + *

+ * Should only be considered when thread-safety is not necessary (e.g. comparing positions) + * + * @return the unwraped value + */ + public @NotNull T unwrap() { + return value; + } - @NotNull Handler getHandler(); + /** + * Gets the {@link Handler} of this acquirable element, + * containing the currently linked thread. + * + * @return this element handler + */ + public @NotNull Handler getHandler() { + return handler; + } - class Handler { + public static class Handler { private volatile ThreadProvider.ChunkEntry chunkEntry; diff --git a/src/main/java/net/minestom/server/lock/AcquirableImpl.java b/src/main/java/net/minestom/server/lock/AcquirableImpl.java deleted file mode 100644 index 58944cd35..000000000 --- a/src/main/java/net/minestom/server/lock/AcquirableImpl.java +++ /dev/null @@ -1,33 +0,0 @@ -package net.minestom.server.lock; - -import org.jetbrains.annotations.NotNull; - -/** - * Basic implementation of {@link Acquirable}. - *

- * Class is immutable. - * - * @param the object type which can be acquired - */ -public class AcquirableImpl implements Acquirable { - - private final T value; - private final Handler handler; - - public AcquirableImpl(@NotNull T value) { - this.value = value; - this.handler = new Handler(); - } - - @NotNull - @Override - public T unwrap() { - return value; - } - - @NotNull - @Override - public Handler getHandler() { - return handler; - } -} \ No newline at end of file diff --git a/src/main/java/net/minestom/server/lock/Acquisition.java b/src/main/java/net/minestom/server/lock/Acquisition.java index d851bfdd9..ed3787a9e 100644 --- a/src/main/java/net/minestom/server/lock/Acquisition.java +++ b/src/main/java/net/minestom/server/lock/Acquisition.java @@ -14,10 +14,23 @@ public final class Acquisition { private static final ThreadLocal SCHEDULED_ACQUISITION = ThreadLocal.withInitial(ScheduledAcquisition::new); + /** + * Global lock used for synchronization. + */ private static final Monitor GLOBAL_MONITOR = new Monitor(); private static final AtomicLong WAIT_COUNTER_NANO = new AtomicLong(); + /** + * Acquires a {@link Collection}. + *

+ * Order is not guaranteed. + * + * @param collection the collection to acquire + * @param consumer the consumer called for each of the collection element + * @param the object type + * @param the acquirable object + */ public static > void acquireForEach(@NotNull Collection collection, @NotNull Consumer consumer) { final Thread currentThread = Thread.currentThread(); @@ -65,7 +78,8 @@ public final class Acquisition { /** * Ensures that {@code callback} is safely executed inside the batch thread. */ - protected static void acquire(@NotNull Thread currentThread, @Nullable BatchThread elementThread, Runnable callback) { + protected static void acquire(@NotNull Thread currentThread, @Nullable BatchThread elementThread, + @NotNull Runnable callback) { if (elementThread == null || elementThread == currentThread) { callback.run(); } else { @@ -119,6 +133,9 @@ public final class Acquisition { private static > Map> retrieveThreadMap(@NotNull Collection collection, @NotNull Thread currentThread, @NotNull Consumer consumer) { + // Separate a collection of acquirable elements into a map of thread->elements + // Useful to reduce the number of acquisition + Map> threadCacheMap = new HashMap<>(); for (Object obj : collection) { T element = (T) obj;