diff --git a/src/main/java/net/minestom/server/UpdateManager.java b/src/main/java/net/minestom/server/UpdateManager.java index 4a0a0e35c..1de913dcd 100644 --- a/src/main/java/net/minestom/server/UpdateManager.java +++ b/src/main/java/net/minestom/server/UpdateManager.java @@ -113,17 +113,12 @@ public final class UpdateManager { * @param tickStart the time of the tick in milliseconds */ private void serverTick(long tickStart) { - // Tick all instances MinecraftServer.getInstanceManager().getInstances().forEach(instance -> instance.tick(tickStart)); // Server tick (instance/chunk/entity) - // Synchronize with the update manager instance, like the signal for chunk load/unload - final CountDownLatch countDownLatch; - synchronized (this) { - countDownLatch = threadProvider.update(tickStart); - } + final CountDownLatch countDownLatch = threadProvider.update(tickStart); // Wait tick end try { @@ -131,6 +126,9 @@ public final class UpdateManager { } catch (InterruptedException e) { MinecraftServer.getExceptionManager().handleException(e); } + + // Clear removed entities & update threads + this.threadProvider.refreshThreads(); } /** @@ -153,20 +151,10 @@ public final class UpdateManager { * * @return the current thread provider */ - public ThreadProvider getThreadProvider() { + public @NotNull ThreadProvider getThreadProvider() { return threadProvider; } - /** - * Changes the server {@link ThreadProvider}. - * - * @param threadProvider the new thread provider - * @throws NullPointerException if threadProvider is null - */ - public synchronized void setThreadProvider(ThreadProvider threadProvider) { - this.threadProvider = threadProvider; - } - /** * Signals the {@link ThreadProvider} that an instance has been created. *

@@ -174,9 +162,7 @@ public final class UpdateManager { * * @param instance the instance */ - public synchronized void signalInstanceCreate(Instance instance) { - if (this.threadProvider == null) - return; + public void signalInstanceCreate(Instance instance) { this.threadProvider.onInstanceCreate(instance); } @@ -187,9 +173,7 @@ public final class UpdateManager { * * @param instance the instance */ - public synchronized void signalInstanceDelete(Instance instance) { - if (this.threadProvider == null) - return; + public void signalInstanceDelete(Instance instance) { this.threadProvider.onInstanceDelete(instance); } @@ -200,9 +184,7 @@ public final class UpdateManager { * * @param chunk the loaded chunk */ - public synchronized void signalChunkLoad(@NotNull Chunk chunk) { - if (this.threadProvider == null) - return; + public void signalChunkLoad(@NotNull Chunk chunk) { this.threadProvider.onChunkLoad(chunk); } @@ -213,9 +195,7 @@ public final class UpdateManager { * * @param chunk the unloaded chunk */ - public synchronized void signalChunkUnload(@NotNull Chunk chunk) { - if (this.threadProvider == null) - return; + public void signalChunkUnload(@NotNull Chunk chunk) { this.threadProvider.onChunkUnload(chunk); } diff --git a/src/main/java/net/minestom/server/entity/Entity.java b/src/main/java/net/minestom/server/entity/Entity.java index 7a7574525..48a62d0da 100644 --- a/src/main/java/net/minestom/server/entity/Entity.java +++ b/src/main/java/net/minestom/server/entity/Entity.java @@ -22,6 +22,9 @@ import net.minestom.server.instance.Chunk; 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; import net.minestom.server.permission.Permission; @@ -58,7 +61,7 @@ import java.util.function.UnaryOperator; *

* To create your own entity you probably want to extends {@link LivingEntity} or {@link EntityCreature} instead. */ -public class Entity implements Viewable, Tickable, EventHandler, DataContainer, PermissionHandler, HoverEventSource { +public class Entity implements Viewable, Tickable, LockedElement, EventHandler, DataContainer, PermissionHandler, HoverEventSource { private static final Map ENTITY_BY_ID = new ConcurrentHashMap<>(); private static final Map ENTITY_BY_UUID = new ConcurrentHashMap<>(); @@ -123,6 +126,8 @@ public class Entity implements Viewable, Tickable, EventHandler, DataContainer, private long ticks; private final EntityTickEvent tickEvent = new EntityTickEvent(this); + private final Acquirable acquirable = new AcquirableImpl<>(this); + /** * Lock used to support #switchEntityType */ @@ -483,6 +488,9 @@ public class Entity implements Viewable, Tickable, EventHandler, DataContainer, } } + // Acquisition + getAcquiredElement().getHandler().acquisitionTick(); + final boolean isNettyClient = PlayerUtils.isNettyClient(this); // Synchronization with updated fields in #getPosition() @@ -1456,6 +1464,10 @@ public class Entity implements Viewable, Tickable, EventHandler, DataContainer, * WARNING: this does not trigger {@link EntityDeathEvent}. */ public void remove() { + if (isRemoved()) + return; + + MinecraftServer.getUpdateManager().getThreadProvider().removeEntity(this); this.removed = true; this.shouldRemove = true; Entity.ENTITY_BY_ID.remove(id); @@ -1559,6 +1571,11 @@ public class Entity implements Viewable, Tickable, EventHandler, DataContainer, return Objects.requireNonNullElse(this.customSynchronizationCooldown, SYNCHRONIZATION_COOLDOWN); } + @Override + public @NotNull Acquirable getAcquiredElement() { + return (Acquirable) acquirable; + } + public enum Pose { STANDING, FALL_FLYING, diff --git a/src/main/java/net/minestom/server/entity/Player.java b/src/main/java/net/minestom/server/entity/Player.java index bc5d6997e..629a530d4 100644 --- a/src/main/java/net/minestom/server/entity/Player.java +++ b/src/main/java/net/minestom/server/entity/Player.java @@ -561,6 +561,9 @@ public class Player extends LivingEntity implements CommandSender, Localizable, @Override public void remove() { + if(isRemoved()) + return; + callEvent(PlayerDisconnectEvent.class, new PlayerDisconnectEvent(this)); super.remove(); diff --git a/src/main/java/net/minestom/server/lock/Acquirable.java b/src/main/java/net/minestom/server/lock/Acquirable.java index 79adc697e..44621d63a 100644 --- a/src/main/java/net/minestom/server/lock/Acquirable.java +++ b/src/main/java/net/minestom/server/lock/Acquirable.java @@ -1,9 +1,9 @@ package net.minestom.server.lock; +import net.minestom.server.instance.Chunk; import net.minestom.server.thread.BatchThread; -import net.minestom.server.thread.batch.BatchInfo; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import java.util.List; import java.util.concurrent.Phaser; @@ -32,8 +32,7 @@ public interface Acquirable { Acquisition.AcquisitionData data = new Acquisition.AcquisitionData(); final Handler handler = getHandler(); - final BatchInfo batchInfo = handler.getBatchInfo(); - final BatchThread elementThread = batchInfo != null ? batchInfo.getBatchThread() : null; + final BatchThread elementThread = handler.getBatchThread(); final boolean sameThread = Acquisition.acquire(currentThread, elementThread, data); @@ -80,25 +79,29 @@ public interface Acquirable { class Handler { - private volatile BatchInfo batchInfo; + private volatile BatchThread batchThread; + private volatile Chunk batchChunk; - @Nullable - public BatchInfo getBatchInfo() { - return batchInfo; + public BatchThread getBatchThread() { + return batchThread; } - public void refreshBatchInfo(@NotNull BatchInfo batchInfo) { - this.batchInfo = batchInfo; + public Chunk getBatchChunk() { + return batchChunk; + } + + @ApiStatus.Internal + public void refreshBatchInfo(BatchThread batchThread, Chunk batchChunk) { + this.batchThread = batchThread; + this.batchChunk = batchChunk; } /** * Executed during this element tick to empty the current thread acquisition queue. */ public void acquisitionTick() { - final BatchThread batchThread = batchInfo.getBatchThread(); if (batchThread == null) return; - Acquisition.processQueue(batchThread.getQueue()); } } diff --git a/src/main/java/net/minestom/server/lock/Acquisition.java b/src/main/java/net/minestom/server/lock/Acquisition.java index b4269de60..5709137fe 100644 --- a/src/main/java/net/minestom/server/lock/Acquisition.java +++ b/src/main/java/net/minestom/server/lock/Acquisition.java @@ -3,7 +3,6 @@ package net.minestom.server.lock; import net.minestom.server.MinecraftServer; import net.minestom.server.thread.BatchQueue; import net.minestom.server.thread.BatchThread; -import net.minestom.server.thread.batch.BatchInfo; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -250,8 +249,7 @@ public final class Acquisition { for (T element : collection) { final E value = element.unwrap(); - final BatchInfo batchInfo = element.getHandler().getBatchInfo(); - final BatchThread elementThread = batchInfo != null ? batchInfo.getBatchThread() : null; + final BatchThread elementThread = element.getHandler().getBatchThread(); if (currentThread == elementThread) { // The element is managed in the current thread, consumer can be immediately called consumer.accept(value); diff --git a/src/main/java/net/minestom/server/lock/LockedElement.java b/src/main/java/net/minestom/server/lock/LockedElement.java index d7a4d8e33..6d6dac426 100644 --- a/src/main/java/net/minestom/server/lock/LockedElement.java +++ b/src/main/java/net/minestom/server/lock/LockedElement.java @@ -17,7 +17,6 @@ public interface LockedElement { * * @return the acquirable element linked to this object */ - @NotNull - Acquirable getAcquiredElement(); + @NotNull Acquirable getAcquiredElement(); } \ No newline at end of file diff --git a/src/main/java/net/minestom/server/thread/BatchThread.java b/src/main/java/net/minestom/server/thread/BatchThread.java index 8ee70330e..7f11901aa 100644 --- a/src/main/java/net/minestom/server/thread/BatchThread.java +++ b/src/main/java/net/minestom/server/thread/BatchThread.java @@ -87,10 +87,10 @@ public class BatchThread extends Thread { } } - public synchronized void startTick(@NotNull CountDownLatch countDownLatch, Runnable runnable) { + public synchronized void startTick(@NotNull CountDownLatch countDownLatch, @NotNull Runnable runnable) { + this.countDownLatch = countDownLatch; + this.queue.add(runnable); synchronized (tickLock) { - this.queue.add(runnable); - this.countDownLatch = countDownLatch; this.tickLock.notifyAll(); } } diff --git a/src/main/java/net/minestom/server/thread/ThreadProvider.java b/src/main/java/net/minestom/server/thread/ThreadProvider.java index 41d3ab05d..383164260 100644 --- a/src/main/java/net/minestom/server/thread/ThreadProvider.java +++ b/src/main/java/net/minestom/server/thread/ThreadProvider.java @@ -1,28 +1,27 @@ package net.minestom.server.thread; -import net.minestom.server.UpdateManager; +import net.minestom.server.MinecraftServer; import net.minestom.server.entity.Entity; import net.minestom.server.instance.Chunk; import net.minestom.server.instance.Instance; +import net.minestom.server.lock.Acquirable; import org.jetbrains.annotations.NotNull; import java.util.*; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CountDownLatch; /** * Used to link chunks into multiple groups. * Then executed into a thread pool. - *

- * You can change the current thread provider by calling {@link UpdateManager#setThreadProvider(ThreadProvider)}. */ public abstract class ThreadProvider { private final List threads; - private final Map> threadChunkMap = new HashMap<>(); + private final Map> threadChunkMap = new HashMap<>(); private final Map chunkEntryMap = new HashMap<>(); - - private final ArrayDeque batchHandlers = new ArrayDeque<>(); + private final Set removedEntities = ConcurrentHashMap.newKeySet(); public ThreadProvider(int threadCount) { this.threads = new ArrayList<>(threadCount); @@ -36,19 +35,19 @@ public abstract class ThreadProvider { } } - public void onInstanceCreate(@NotNull Instance instance) { + public synchronized void onInstanceCreate(@NotNull Instance instance) { instance.getChunks().forEach(this::addChunk); } - public void onInstanceDelete(@NotNull Instance instance) { + public synchronized void onInstanceDelete(@NotNull Instance instance) { instance.getChunks().forEach(this::removeChunk); } - public void onChunkLoad(Chunk chunk) { + public synchronized void onChunkLoad(Chunk chunk) { addChunk(chunk); } - public void onChunkUnload(Chunk chunk) { + public synchronized void onChunkUnload(Chunk chunk) { removeChunk(chunk); } @@ -60,8 +59,9 @@ public abstract class ThreadProvider { public abstract int findThread(@NotNull Chunk chunk); protected void addChunk(Chunk chunk) { - int thread = findThread(chunk); - var chunks = threadChunkMap.computeIfAbsent(thread, ArrayList::new); + int threadId = findThread(chunk); + BatchThread thread = threads.get(threadId); + var chunks = threadChunkMap.computeIfAbsent(thread, batchThread -> ConcurrentHashMap.newKeySet()); ChunkEntry chunkEntry = new ChunkEntry(thread, chunk); chunks.add(chunkEntry); @@ -72,8 +72,8 @@ public abstract class ThreadProvider { protected void removeChunk(Chunk chunk) { ChunkEntry chunkEntry = chunkEntryMap.get(chunk); if (chunkEntry != null) { - int threadId = chunkEntry.threadId; - var chunks = threadChunkMap.get(threadId); + BatchThread thread = chunkEntry.thread; + var chunks = threadChunkMap.get(thread); if (chunks != null) { chunks.remove(chunkEntry); } @@ -86,34 +86,20 @@ public abstract class ThreadProvider { * * @param time the tick time in milliseconds */ - public @NotNull CountDownLatch update(long time) { + public synchronized @NotNull CountDownLatch update(long time) { CountDownLatch countDownLatch = new CountDownLatch(threads.size()); for (BatchThread thread : threads) { - final int id = threads.indexOf(thread); - if (id == -1) { - countDownLatch.countDown(); - continue; - } - - final var chunkEntries = threadChunkMap.get(id); + final var chunkEntries = threadChunkMap.get(thread); if (chunkEntries == null) { countDownLatch.countDown(); continue; } - // Cache chunk entities - Map> chunkListMap = new HashMap<>(chunkEntries.size()); - for (ChunkEntry chunkEntry : chunkEntries) { - var chunk = chunkEntry.chunk; - var entities = new ArrayList<>(chunk.getInstance().getChunkEntities(chunk)); - chunkListMap.put(chunk, entities); - } - // Execute tick thread.getMainRunnable().startTick(countDownLatch, () -> { - chunkListMap.forEach((chunk, entities) -> { - chunk.tick(time); - entities.forEach(entity -> { + chunkEntries.forEach(chunkEntry -> { + chunkEntry.chunk.tick(time); + chunkEntry.entities.forEach(entity -> { entity.tick(time); }); }); @@ -122,6 +108,56 @@ public abstract class ThreadProvider { return countDownLatch; } + public synchronized void refreshThreads() { + + // Clear removed entities + for (Entity entity : removedEntities) { + Acquirable acquirable = entity.getAcquiredElement(); + Chunk batchChunk = acquirable.getHandler().getBatchChunk(); + + // Remove from list + { + ChunkEntry chunkEntry = chunkEntryMap.get(batchChunk); + if (chunkEntry != null) { + chunkEntry.entities.remove(entity); + } + } + } + this.removedEntities.clear(); + + for (Instance instance : MinecraftServer.getInstanceManager().getInstances()) { + var entities = instance.getEntities(); + for (Entity entity : entities) { + Acquirable acquirable = entity.getAcquiredElement(); + Chunk batchChunk = acquirable.getHandler().getBatchChunk(); + Chunk entityChunk = entity.getChunk(); + if (!Objects.equals(batchChunk, entityChunk)) { + // Entity is possibly not in the correct thread + + // Remove from previous list + { + ChunkEntry chunkEntry = chunkEntryMap.get(batchChunk); + if (chunkEntry != null) { + chunkEntry.entities.remove(entity); + } + } + + // Add to new list + { + ChunkEntry chunkEntry = chunkEntryMap.get(entityChunk); + if (chunkEntry != null) { + chunkEntry.entities.add(entity); + acquirable.getHandler().refreshBatchInfo(chunkEntry.thread, chunkEntry.chunk); + } + } + } + } + } + } + + public void removeEntity(Entity entity) { + this.removedEntities.add(entity); + } public void shutdown() { this.threads.forEach(BatchThread::shutdown); @@ -133,11 +169,12 @@ public abstract class ThreadProvider { } private static class ChunkEntry { - private final int threadId; + private final BatchThread thread; private final Chunk chunk; + private final List entities = new ArrayList<>(); - private ChunkEntry(int threadId, Chunk chunk) { - this.threadId = threadId; + private ChunkEntry(BatchThread thread, Chunk chunk) { + this.thread = thread; this.chunk = chunk; }