Update entities thread

This commit is contained in:
TheMode 2021-04-15 01:44:08 +02:00
parent fec36d4706
commit 11b1bbea2e
8 changed files with 123 additions and 86 deletions

View File

@ -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 <code>threadProvider</code> is null
*/
public synchronized void setThreadProvider(ThreadProvider threadProvider) {
this.threadProvider = threadProvider;
}
/**
* Signals the {@link ThreadProvider} that an instance has been created.
* <p>
@ -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);
}

View File

@ -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;
* <p>
* 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<ShowEntity> {
public class Entity implements Viewable, Tickable, LockedElement, EventHandler, DataContainer, PermissionHandler, HoverEventSource<ShowEntity> {
private static final Map<Integer, Entity> ENTITY_BY_ID = new ConcurrentHashMap<>();
private static final Map<UUID, Entity> 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<Entity> 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 <T> Acquirable<T> getAcquiredElement() {
return (Acquirable<T>) acquirable;
}
public enum Pose {
STANDING,
FALL_FLYING,

View File

@ -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();

View File

@ -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<T> {
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<T> {
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());
}
}

View File

@ -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);

View File

@ -17,7 +17,6 @@ public interface LockedElement {
*
* @return the acquirable element linked to this object
*/
@NotNull
<T> Acquirable<T> getAcquiredElement();
<T> @NotNull Acquirable<T> getAcquiredElement();
}

View File

@ -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();
}
}

View File

@ -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.
* <p>
* You can change the current thread provider by calling {@link UpdateManager#setThreadProvider(ThreadProvider)}.
*/
public abstract class ThreadProvider {
private final List<BatchThread> threads;
private final Map<Integer, List<ChunkEntry>> threadChunkMap = new HashMap<>();
private final Map<BatchThread, Set<ChunkEntry>> threadChunkMap = new HashMap<>();
private final Map<Chunk, ChunkEntry> chunkEntryMap = new HashMap<>();
private final ArrayDeque<Chunk> batchHandlers = new ArrayDeque<>();
private final Set<Entity> 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<Chunk, List<Entity>> 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<Entity> 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<Entity> 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<Entity> entities = new ArrayList<>();
private ChunkEntry(int threadId, Chunk chunk) {
this.threadId = threadId;
private ChunkEntry(BatchThread thread, Chunk chunk) {
this.thread = thread;
this.chunk = chunk;
}