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;
}