mirror of
https://github.com/Minestom/Minestom.git
synced 2024-09-29 15:07:36 +02:00
Improve the thread provider api
Signed-off-by: TheMode <themode@outlook.fr>
This commit is contained in:
parent
c31aa8a7ec
commit
06d8586f7f
@ -7,8 +7,7 @@ import net.minestom.server.instance.Instance;
|
|||||||
import net.minestom.server.instance.InstanceManager;
|
import net.minestom.server.instance.InstanceManager;
|
||||||
import net.minestom.server.monitoring.TickMonitor;
|
import net.minestom.server.monitoring.TickMonitor;
|
||||||
import net.minestom.server.network.ConnectionManager;
|
import net.minestom.server.network.ConnectionManager;
|
||||||
import net.minestom.server.thread.SingleThreadProvider;
|
import net.minestom.server.thread.ThreadDispatcher;
|
||||||
import net.minestom.server.thread.ThreadProvider;
|
|
||||||
import net.minestom.server.utils.PacketUtils;
|
import net.minestom.server.utils.PacketUtils;
|
||||||
import net.minestom.server.utils.async.AsyncUtils;
|
import net.minestom.server.utils.async.AsyncUtils;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
@ -24,14 +23,14 @@ import java.util.function.LongConsumer;
|
|||||||
/**
|
/**
|
||||||
* Manager responsible for the server ticks.
|
* Manager responsible for the server ticks.
|
||||||
* <p>
|
* <p>
|
||||||
* The {@link ThreadProvider} manages the multi-thread aspect of chunk ticks.
|
* The {@link ThreadDispatcher} manages the multi-thread aspect of chunk ticks.
|
||||||
*/
|
*/
|
||||||
public final class UpdateManager {
|
public final class UpdateManager {
|
||||||
|
|
||||||
private volatile boolean stopRequested;
|
private volatile boolean stopRequested;
|
||||||
|
|
||||||
// TODO make configurable
|
// TODO make configurable
|
||||||
private ThreadProvider threadProvider = new SingleThreadProvider();
|
private ThreadDispatcher threadDispatcher = ThreadDispatcher.singleThread();
|
||||||
|
|
||||||
private final Queue<LongConsumer> tickStartCallbacks = new ConcurrentLinkedQueue<>();
|
private final Queue<LongConsumer> tickStartCallbacks = new ConcurrentLinkedQueue<>();
|
||||||
private final Queue<LongConsumer> tickEndCallbacks = new ConcurrentLinkedQueue<>();
|
private final Queue<LongConsumer> tickEndCallbacks = new ConcurrentLinkedQueue<>();
|
||||||
@ -96,7 +95,7 @@ public final class UpdateManager {
|
|||||||
MinecraftServer.getExceptionManager().handleException(e);
|
MinecraftServer.getExceptionManager().handleException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.threadProvider.shutdown();
|
this.threadDispatcher.shutdown();
|
||||||
}, MinecraftServer.THREAD_NAME_TICK_SCHEDULER).start();
|
}, MinecraftServer.THREAD_NAME_TICK_SCHEDULER).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -115,11 +114,11 @@ public final class UpdateManager {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
// Tick all chunks (and entities inside)
|
// Tick all chunks (and entities inside)
|
||||||
this.threadProvider.updateAndAwait(tickStart);
|
this.threadDispatcher.updateAndAwait(tickStart);
|
||||||
|
|
||||||
// Clear removed entities & update threads
|
// Clear removed entities & update threads
|
||||||
long tickTime = System.currentTimeMillis() - tickStart;
|
final long tickTime = System.currentTimeMillis() - tickStart;
|
||||||
this.threadProvider.refreshThreads(tickTime);
|
this.threadDispatcher.refreshThreads(tickTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -138,56 +137,56 @@ public final class UpdateManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the current {@link ThreadProvider}.
|
* Gets the current {@link ThreadDispatcher}.
|
||||||
*
|
*
|
||||||
* @return the current thread provider
|
* @return the current thread provider
|
||||||
*/
|
*/
|
||||||
public @NotNull ThreadProvider getThreadProvider() {
|
public @NotNull ThreadDispatcher getThreadProvider() {
|
||||||
return threadProvider;
|
return threadDispatcher;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Signals the {@link ThreadProvider} that an instance has been created.
|
* Signals the {@link ThreadDispatcher} that an instance has been created.
|
||||||
* <p>
|
* <p>
|
||||||
* WARNING: should be automatically done by the {@link InstanceManager}.
|
* WARNING: should be automatically done by the {@link InstanceManager}.
|
||||||
*
|
*
|
||||||
* @param instance the instance
|
* @param instance the instance
|
||||||
*/
|
*/
|
||||||
public void signalInstanceCreate(Instance instance) {
|
public void signalInstanceCreate(Instance instance) {
|
||||||
this.threadProvider.onInstanceCreate(instance);
|
this.threadDispatcher.onInstanceCreate(instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Signals the {@link ThreadProvider} that an instance has been deleted.
|
* Signals the {@link ThreadDispatcher} that an instance has been deleted.
|
||||||
* <p>
|
* <p>
|
||||||
* WARNING: should be automatically done by the {@link InstanceManager}.
|
* WARNING: should be automatically done by the {@link InstanceManager}.
|
||||||
*
|
*
|
||||||
* @param instance the instance
|
* @param instance the instance
|
||||||
*/
|
*/
|
||||||
public void signalInstanceDelete(Instance instance) {
|
public void signalInstanceDelete(Instance instance) {
|
||||||
this.threadProvider.onInstanceDelete(instance);
|
this.threadDispatcher.onInstanceDelete(instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Signals the {@link ThreadProvider} that a chunk has been loaded.
|
* Signals the {@link ThreadDispatcher} that a chunk has been loaded.
|
||||||
* <p>
|
* <p>
|
||||||
* WARNING: should be automatically done by the {@link Instance} implementation.
|
* WARNING: should be automatically done by the {@link Instance} implementation.
|
||||||
*
|
*
|
||||||
* @param chunk the loaded chunk
|
* @param chunk the loaded chunk
|
||||||
*/
|
*/
|
||||||
public void signalChunkLoad(@NotNull Chunk chunk) {
|
public void signalChunkLoad(@NotNull Chunk chunk) {
|
||||||
this.threadProvider.onChunkLoad(chunk);
|
this.threadDispatcher.onChunkLoad(chunk);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Signals the {@link ThreadProvider} that a chunk has been unloaded.
|
* Signals the {@link ThreadDispatcher} that a chunk has been unloaded.
|
||||||
* <p>
|
* <p>
|
||||||
* WARNING: should be automatically done by the {@link Instance} implementation.
|
* WARNING: should be automatically done by the {@link Instance} implementation.
|
||||||
*
|
*
|
||||||
* @param chunk the unloaded chunk
|
* @param chunk the unloaded chunk
|
||||||
*/
|
*/
|
||||||
public void signalChunkUnload(@NotNull Chunk chunk) {
|
public void signalChunkUnload(@NotNull Chunk chunk) {
|
||||||
this.threadProvider.onChunkUnload(chunk);
|
this.threadDispatcher.onChunkUnload(chunk);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package net.minestom.server.acquirable;
|
package net.minestom.server.acquirable;
|
||||||
|
|
||||||
import net.minestom.server.entity.Entity;
|
import net.minestom.server.entity.Entity;
|
||||||
import net.minestom.server.thread.ThreadProvider;
|
import net.minestom.server.thread.ThreadDispatcher;
|
||||||
import net.minestom.server.thread.TickThread;
|
import net.minestom.server.thread.TickThread;
|
||||||
import net.minestom.server.utils.async.AsyncUtils;
|
import net.minestom.server.utils.async.AsyncUtils;
|
||||||
import org.jetbrains.annotations.ApiStatus;
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
@ -36,7 +36,7 @@ public interface Acquirable<T> {
|
|||||||
* @param entries the new chunk entries
|
* @param entries the new chunk entries
|
||||||
*/
|
*/
|
||||||
@ApiStatus.Internal
|
@ApiStatus.Internal
|
||||||
static void refreshEntries(@NotNull Collection<ThreadProvider.ChunkEntry> entries) {
|
static void refreshEntries(@NotNull Collection<ThreadDispatcher.ChunkEntry> entries) {
|
||||||
AcquirableImpl.ENTRIES.set(entries);
|
AcquirableImpl.ENTRIES.set(entries);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -157,19 +157,19 @@ public interface Acquirable<T> {
|
|||||||
@NotNull Handler getHandler();
|
@NotNull Handler getHandler();
|
||||||
|
|
||||||
final class Handler {
|
final class Handler {
|
||||||
private volatile ThreadProvider.ChunkEntry chunkEntry;
|
private volatile ThreadDispatcher.ChunkEntry chunkEntry;
|
||||||
|
|
||||||
public ThreadProvider.ChunkEntry getChunkEntry() {
|
public ThreadDispatcher.ChunkEntry getChunkEntry() {
|
||||||
return chunkEntry;
|
return chunkEntry;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ApiStatus.Internal
|
@ApiStatus.Internal
|
||||||
public void refreshChunkEntry(@NotNull ThreadProvider.ChunkEntry chunkEntry) {
|
public void refreshChunkEntry(@NotNull ThreadDispatcher.ChunkEntry chunkEntry) {
|
||||||
this.chunkEntry = chunkEntry;
|
this.chunkEntry = chunkEntry;
|
||||||
}
|
}
|
||||||
|
|
||||||
public TickThread getTickThread() {
|
public TickThread getTickThread() {
|
||||||
final ThreadProvider.ChunkEntry entry = this.chunkEntry;
|
final ThreadDispatcher.ChunkEntry entry = this.chunkEntry;
|
||||||
return entry != null ? entry.getThread() : null;
|
return entry != null ? entry.getThread() : null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package net.minestom.server.acquirable;
|
package net.minestom.server.acquirable;
|
||||||
|
|
||||||
import net.minestom.server.thread.ThreadProvider;
|
import net.minestom.server.thread.ThreadDispatcher;
|
||||||
import net.minestom.server.thread.TickThread;
|
import net.minestom.server.thread.TickThread;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
@ -11,7 +11,7 @@ import java.util.concurrent.atomic.AtomicLong;
|
|||||||
import java.util.concurrent.locks.ReentrantLock;
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
|
||||||
final class AcquirableImpl<T> implements Acquirable<T> {
|
final class AcquirableImpl<T> implements Acquirable<T> {
|
||||||
static final ThreadLocal<Collection<ThreadProvider.ChunkEntry>> ENTRIES = ThreadLocal.withInitial(Collections::emptySet);
|
static final ThreadLocal<Collection<ThreadDispatcher.ChunkEntry>> ENTRIES = ThreadLocal.withInitial(Collections::emptySet);
|
||||||
static final AtomicLong WAIT_COUNTER_NANO = new AtomicLong();
|
static final AtomicLong WAIT_COUNTER_NANO = new AtomicLong();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -33,7 +33,6 @@ import net.minestom.server.potion.PotionEffect;
|
|||||||
import net.minestom.server.potion.TimedPotion;
|
import net.minestom.server.potion.TimedPotion;
|
||||||
import net.minestom.server.tag.Tag;
|
import net.minestom.server.tag.Tag;
|
||||||
import net.minestom.server.tag.TagHandler;
|
import net.minestom.server.tag.TagHandler;
|
||||||
import net.minestom.server.thread.ThreadProvider;
|
|
||||||
import net.minestom.server.utils.PacketUtils;
|
import net.minestom.server.utils.PacketUtils;
|
||||||
import net.minestom.server.utils.async.AsyncUtils;
|
import net.minestom.server.utils.async.AsyncUtils;
|
||||||
import net.minestom.server.utils.block.BlockIterator;
|
import net.minestom.server.utils.block.BlockIterator;
|
||||||
@ -164,7 +163,6 @@ public class Entity implements Viewable, Tickable, TagHandler, PermissionHandler
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Schedules a task to be run during the next entity tick.
|
* Schedules a task to be run during the next entity tick.
|
||||||
* It ensures that the task will be executed in the same thread as the entity (depending of the {@link ThreadProvider}).
|
|
||||||
*
|
*
|
||||||
* @param callback the task to execute during the next entity tick
|
* @param callback the task to execute during the next entity tick
|
||||||
*/
|
*/
|
||||||
|
@ -26,7 +26,6 @@ import net.minestom.server.network.packet.server.play.BlockActionPacket;
|
|||||||
import net.minestom.server.network.packet.server.play.TimeUpdatePacket;
|
import net.minestom.server.network.packet.server.play.TimeUpdatePacket;
|
||||||
import net.minestom.server.tag.Tag;
|
import net.minestom.server.tag.Tag;
|
||||||
import net.minestom.server.tag.TagHandler;
|
import net.minestom.server.tag.TagHandler;
|
||||||
import net.minestom.server.thread.ThreadProvider;
|
|
||||||
import net.minestom.server.utils.PacketUtils;
|
import net.minestom.server.utils.PacketUtils;
|
||||||
import net.minestom.server.utils.chunk.ChunkUtils;
|
import net.minestom.server.utils.chunk.ChunkUtils;
|
||||||
import net.minestom.server.utils.entity.EntityUtils;
|
import net.minestom.server.utils.entity.EntityUtils;
|
||||||
@ -129,8 +128,6 @@ public abstract class Instance implements BlockGetter, BlockSetter, Tickable, Ta
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Schedules a task to be run during the next instance tick.
|
* Schedules a task to be run during the next instance tick.
|
||||||
* It ensures that the task will be executed in the same thread as the instance
|
|
||||||
* and its chunks/entities (depending of the {@link ThreadProvider}).
|
|
||||||
*
|
*
|
||||||
* @param callback the task to execute during the next instance tick
|
* @param callback the task to execute during the next instance tick
|
||||||
*/
|
*/
|
||||||
|
@ -1,28 +0,0 @@
|
|||||||
package net.minestom.server.thread;
|
|
||||||
|
|
||||||
import net.minestom.server.instance.Chunk;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Each {@link Chunk} gets assigned to a random thread.
|
|
||||||
*/
|
|
||||||
public class PerChunkThreadProvider extends ThreadProvider {
|
|
||||||
|
|
||||||
public PerChunkThreadProvider(int threadCount) {
|
|
||||||
super(threadCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
public PerChunkThreadProvider() {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int findThread(@NotNull Chunk chunk) {
|
|
||||||
return chunk.hashCode();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull RefreshType getChunkRefreshType() {
|
|
||||||
return RefreshType.NEVER;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,29 +0,0 @@
|
|||||||
package net.minestom.server.thread;
|
|
||||||
|
|
||||||
import net.minestom.server.instance.Chunk;
|
|
||||||
import net.minestom.server.instance.Instance;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Each {@link Instance} gets assigned to a random thread.
|
|
||||||
*/
|
|
||||||
public class PerInstanceThreadProvider extends ThreadProvider {
|
|
||||||
|
|
||||||
public PerInstanceThreadProvider(int threadCount) {
|
|
||||||
super(threadCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
public PerInstanceThreadProvider() {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int findThread(@NotNull Chunk chunk) {
|
|
||||||
return chunk.getInstance().hashCode();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull RefreshType getChunkRefreshType() {
|
|
||||||
return RefreshType.NEVER;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,24 +0,0 @@
|
|||||||
package net.minestom.server.thread;
|
|
||||||
|
|
||||||
import net.minestom.server.instance.Chunk;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Uses a single thread for all chunks.
|
|
||||||
*/
|
|
||||||
public class SingleThreadProvider extends ThreadProvider {
|
|
||||||
|
|
||||||
public SingleThreadProvider() {
|
|
||||||
super(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int findThread(@NotNull Chunk chunk) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull RefreshType getChunkRefreshType() {
|
|
||||||
return RefreshType.NEVER;
|
|
||||||
}
|
|
||||||
}
|
|
325
src/main/java/net/minestom/server/thread/ThreadDispatcher.java
Normal file
325
src/main/java/net/minestom/server/thread/ThreadDispatcher.java
Normal file
@ -0,0 +1,325 @@
|
|||||||
|
package net.minestom.server.thread;
|
||||||
|
|
||||||
|
import net.minestom.server.MinecraftServer;
|
||||||
|
import net.minestom.server.acquirable.Acquirable;
|
||||||
|
import net.minestom.server.entity.Entity;
|
||||||
|
import net.minestom.server.instance.Chunk;
|
||||||
|
import net.minestom.server.instance.Instance;
|
||||||
|
import net.minestom.server.utils.MathUtils;
|
||||||
|
import net.minestom.server.utils.chunk.ChunkUtils;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||||
|
import java.util.concurrent.Phaser;
|
||||||
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to link chunks into multiple groups.
|
||||||
|
* Then executed into a thread pool.
|
||||||
|
*/
|
||||||
|
public final class ThreadDispatcher {
|
||||||
|
private final ThreadProvider provider;
|
||||||
|
private final List<TickThread> threads;
|
||||||
|
|
||||||
|
private final Map<TickThread, Set<ChunkEntry>> threadChunkMap = new HashMap<>();
|
||||||
|
private final Map<Chunk, ChunkEntry> chunkEntryMap = new HashMap<>();
|
||||||
|
private final ArrayDeque<Chunk> chunkUpdateQueue = new ArrayDeque<>();
|
||||||
|
|
||||||
|
private final Queue<Chunk> chunkLoadRequests = new ConcurrentLinkedQueue<>();
|
||||||
|
private final Queue<Chunk> chunkUnloadRequests = new ConcurrentLinkedQueue<>();
|
||||||
|
private final Queue<Entity> entityUpdateRequests = new ConcurrentLinkedQueue<>();
|
||||||
|
private final Queue<Entity> entityRemovalRequests = new ConcurrentLinkedQueue<>();
|
||||||
|
|
||||||
|
private final Phaser phaser = new Phaser(1);
|
||||||
|
|
||||||
|
private ThreadDispatcher(ThreadProvider provider, int threadCount) {
|
||||||
|
this.provider = provider;
|
||||||
|
this.threads = new ArrayList<>(threadCount);
|
||||||
|
|
||||||
|
for (int i = 0; i < threadCount; i++) {
|
||||||
|
final TickThread.BatchRunnable batchRunnable = new TickThread.BatchRunnable();
|
||||||
|
final TickThread tickThread = new TickThread(batchRunnable, i);
|
||||||
|
this.threads.add(tickThread);
|
||||||
|
tickThread.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static @NotNull ThreadDispatcher of(@NotNull ThreadProvider provider, int threadCount) {
|
||||||
|
return new ThreadDispatcher(provider, threadCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static @NotNull ThreadDispatcher singleThread() {
|
||||||
|
return of(ThreadProvider.SINGLE, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the maximum percentage of tick time that can be spent refreshing chunks thread.
|
||||||
|
* <p>
|
||||||
|
* Percentage based on {@link MinecraftServer#TICK_MS}.
|
||||||
|
*
|
||||||
|
* @return the refresh percentage
|
||||||
|
*/
|
||||||
|
public float getRefreshPercentage() {
|
||||||
|
return 0.3f;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Minimum time used to refresh chunks and entities thread.
|
||||||
|
*
|
||||||
|
* @return the minimum refresh time in milliseconds
|
||||||
|
*/
|
||||||
|
public int getMinimumRefreshTime() {
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maximum time used to refresh chunks and entities thread.
|
||||||
|
*
|
||||||
|
* @return the maximum refresh time in milliseconds
|
||||||
|
*/
|
||||||
|
public int getMaximumRefreshTime() {
|
||||||
|
return (int) (MinecraftServer.TICK_MS * 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepares the update by creating the {@link TickThread} tasks.
|
||||||
|
*
|
||||||
|
* @param time the tick time in milliseconds
|
||||||
|
*/
|
||||||
|
public void updateAndAwait(long time) {
|
||||||
|
for (var entry : threadChunkMap.entrySet()) {
|
||||||
|
final TickThread thread = entry.getKey();
|
||||||
|
final Set<ChunkEntry> chunkEntries = entry.getValue();
|
||||||
|
if (chunkEntries == null || chunkEntries.isEmpty()) {
|
||||||
|
// Nothing to tick
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Execute tick
|
||||||
|
this.phaser.register();
|
||||||
|
thread.runnable.startTick(phaser, () -> {
|
||||||
|
Acquirable.refreshEntries(chunkEntries);
|
||||||
|
|
||||||
|
final ReentrantLock lock = thread.getLock();
|
||||||
|
lock.lock();
|
||||||
|
for (ChunkEntry chunkEntry : chunkEntries) {
|
||||||
|
final Chunk chunk = chunkEntry.chunk;
|
||||||
|
if (!ChunkUtils.isLoaded(chunk)) return;
|
||||||
|
try {
|
||||||
|
chunk.tick(time);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
MinecraftServer.getExceptionManager().handleException(e);
|
||||||
|
}
|
||||||
|
final List<Entity> entities = chunkEntry.entities;
|
||||||
|
if (!entities.isEmpty()) {
|
||||||
|
for (Entity entity : entities) {
|
||||||
|
if (lock.hasQueuedThreads()) {
|
||||||
|
lock.unlock();
|
||||||
|
// #acquire() callbacks should be called here
|
||||||
|
lock.lock();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
entity.tick(time);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
MinecraftServer.getExceptionManager().handleException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lock.unlock();
|
||||||
|
// #acquire() callbacks
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.phaser.arriveAndAwaitAdvance();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called at the end of each tick to clear removed entities,
|
||||||
|
* refresh the chunk linked to an entity, and chunk threads based on {@link ThreadProvider#findThread(Chunk)}.
|
||||||
|
*
|
||||||
|
* @param tickTime the duration of the tick in ms,
|
||||||
|
* used to ensure that the refresh does not take more time than the tick itself
|
||||||
|
*/
|
||||||
|
public void refreshThreads(long tickTime) {
|
||||||
|
processLoadedChunks();
|
||||||
|
processUnloadedChunks();
|
||||||
|
processRemovedEntities();
|
||||||
|
processUpdatedEntities();
|
||||||
|
if (provider.getChunkRefreshType() == ThreadProvider.RefreshType.NEVER)
|
||||||
|
return;
|
||||||
|
|
||||||
|
final int timeOffset = MathUtils.clamp((int) ((double) tickTime * getRefreshPercentage()),
|
||||||
|
getMinimumRefreshTime(), getMaximumRefreshTime());
|
||||||
|
final long endTime = System.currentTimeMillis() + timeOffset;
|
||||||
|
final int size = chunkUpdateQueue.size();
|
||||||
|
int counter = 0;
|
||||||
|
while (true) {
|
||||||
|
final Chunk chunk = chunkUpdateQueue.pollFirst();
|
||||||
|
if (!ChunkUtils.isLoaded(chunk)) {
|
||||||
|
removeChunk(chunk);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Update chunk threads
|
||||||
|
switchChunk(chunk);
|
||||||
|
// Add back to the deque
|
||||||
|
chunkUpdateQueue.addLast(chunk);
|
||||||
|
|
||||||
|
if (++counter > size || System.currentTimeMillis() >= endTime)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shutdowns all the {@link TickThread tick threads}.
|
||||||
|
* <p>
|
||||||
|
* Action is irreversible.
|
||||||
|
*/
|
||||||
|
public void shutdown() {
|
||||||
|
this.threads.forEach(TickThread::shutdown);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onInstanceCreate(@NotNull Instance instance) {
|
||||||
|
instance.getChunks().forEach(this::onChunkLoad);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onInstanceDelete(@NotNull Instance instance) {
|
||||||
|
instance.getChunks().forEach(this::onChunkUnload);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onChunkLoad(Chunk chunk) {
|
||||||
|
this.chunkLoadRequests.add(chunk);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onChunkUnload(Chunk chunk) {
|
||||||
|
this.chunkUnloadRequests.add(chunk);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateEntity(@NotNull Entity entity) {
|
||||||
|
this.entityUpdateRequests.add(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeEntity(@NotNull Entity entity) {
|
||||||
|
this.entityRemovalRequests.add(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void switchChunk(@NotNull Chunk chunk) {
|
||||||
|
ChunkEntry chunkEntry = chunkEntryMap.get(chunk);
|
||||||
|
if (chunkEntry == null) return;
|
||||||
|
Set<ChunkEntry> chunks = threadChunkMap.get(chunkEntry.thread);
|
||||||
|
if (chunks == null || chunks.isEmpty()) return;
|
||||||
|
chunks.remove(chunkEntry);
|
||||||
|
setChunkThread(chunk, tickThread -> {
|
||||||
|
chunkEntry.thread = tickThread;
|
||||||
|
return chunkEntry;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private @NotNull ChunkEntry setChunkThread(@NotNull Chunk chunk,
|
||||||
|
@NotNull Function<TickThread, ChunkEntry> chunkEntrySupplier) {
|
||||||
|
final int threadId = Math.abs(provider.findThread(chunk)) % threads.size();
|
||||||
|
TickThread thread = threads.get(threadId);
|
||||||
|
Set<ChunkEntry> chunks = threadChunkMap.computeIfAbsent(thread, tickThread -> new HashSet<>());
|
||||||
|
|
||||||
|
ChunkEntry chunkEntry = chunkEntrySupplier.apply(thread);
|
||||||
|
chunks.add(chunkEntry);
|
||||||
|
return chunkEntry;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeChunk(Chunk chunk) {
|
||||||
|
ChunkEntry chunkEntry = chunkEntryMap.get(chunk);
|
||||||
|
if (chunkEntry != null) {
|
||||||
|
TickThread thread = chunkEntry.thread;
|
||||||
|
Set<ChunkEntry> chunks = threadChunkMap.get(thread);
|
||||||
|
if (chunks != null) {
|
||||||
|
chunks.remove(chunkEntry);
|
||||||
|
}
|
||||||
|
chunkEntryMap.remove(chunk);
|
||||||
|
}
|
||||||
|
this.chunkUpdateQueue.remove(chunk);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processLoadedChunks() {
|
||||||
|
Chunk chunk;
|
||||||
|
while ((chunk = chunkLoadRequests.poll()) != null) {
|
||||||
|
Chunk finalChunk = chunk;
|
||||||
|
ChunkEntry chunkEntry = setChunkThread(chunk, (thread) -> new ChunkEntry(thread, finalChunk));
|
||||||
|
this.chunkEntryMap.put(chunk, chunkEntry);
|
||||||
|
this.chunkUpdateQueue.add(chunk);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processUnloadedChunks() {
|
||||||
|
Chunk chunk;
|
||||||
|
while ((chunk = chunkUnloadRequests.poll()) != null) {
|
||||||
|
removeChunk(chunk);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processRemovedEntities() {
|
||||||
|
Entity entity;
|
||||||
|
while ((entity = entityRemovalRequests.poll()) != null) {
|
||||||
|
var acquirableEntity = entity.getAcquirable();
|
||||||
|
ChunkEntry chunkEntry = acquirableEntity.getHandler().getChunkEntry();
|
||||||
|
if (chunkEntry != null) {
|
||||||
|
chunkEntry.entities.remove(entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processUpdatedEntities() {
|
||||||
|
Entity entity;
|
||||||
|
while ((entity = entityUpdateRequests.poll()) != null) {
|
||||||
|
ChunkEntry chunkEntry;
|
||||||
|
|
||||||
|
var acquirableEntity = entity.getAcquirable();
|
||||||
|
chunkEntry = acquirableEntity.getHandler().getChunkEntry();
|
||||||
|
// Remove from previous list
|
||||||
|
if (chunkEntry != null) {
|
||||||
|
chunkEntry.entities.remove(entity);
|
||||||
|
}
|
||||||
|
// Add to new list
|
||||||
|
chunkEntry = chunkEntryMap.get(entity.getChunk());
|
||||||
|
if (chunkEntry != null) {
|
||||||
|
chunkEntry.entities.add(entity);
|
||||||
|
acquirableEntity.getHandler().refreshChunkEntry(chunkEntry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class ChunkEntry {
|
||||||
|
private volatile TickThread thread;
|
||||||
|
private final Chunk chunk;
|
||||||
|
private final List<Entity> entities = new ArrayList<>();
|
||||||
|
|
||||||
|
private ChunkEntry(TickThread thread, Chunk chunk) {
|
||||||
|
this.thread = thread;
|
||||||
|
this.chunk = chunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NotNull TickThread getThread() {
|
||||||
|
return thread;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NotNull Chunk getChunk() {
|
||||||
|
return chunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NotNull List<Entity> getEntities() {
|
||||||
|
return entities;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
ChunkEntry that = (ChunkEntry) o;
|
||||||
|
return chunk.equals(that.chunk);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(chunk);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,317 +1,36 @@
|
|||||||
package net.minestom.server.thread;
|
package net.minestom.server.thread;
|
||||||
|
|
||||||
import net.minestom.server.MinecraftServer;
|
|
||||||
import net.minestom.server.acquirable.Acquirable;
|
|
||||||
import net.minestom.server.entity.Entity;
|
|
||||||
import net.minestom.server.instance.Chunk;
|
import net.minestom.server.instance.Chunk;
|
||||||
import net.minestom.server.instance.Instance;
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
import net.minestom.server.utils.MathUtils;
|
|
||||||
import net.minestom.server.utils.chunk.ChunkUtils;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.*;
|
@FunctionalInterface
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
@ApiStatus.Experimental
|
||||||
import java.util.concurrent.Phaser;
|
public interface ThreadProvider {
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
ThreadProvider PER_CHUNk = Object::hashCode;
|
||||||
import java.util.function.Function;
|
ThreadProvider PER_INSTANCE = chunk -> chunk.getInstance().hashCode();
|
||||||
|
ThreadProvider SINGLE = chunk -> 0;
|
||||||
/**
|
|
||||||
* Used to link chunks into multiple groups.
|
|
||||||
* Then executed into a thread pool.
|
|
||||||
*/
|
|
||||||
public abstract class ThreadProvider {
|
|
||||||
|
|
||||||
private final List<TickThread> threads;
|
|
||||||
|
|
||||||
private final Map<TickThread, Set<ChunkEntry>> threadChunkMap = new HashMap<>();
|
|
||||||
private final Map<Chunk, ChunkEntry> chunkEntryMap = new HashMap<>();
|
|
||||||
// Iterated over to refresh the thread used to update entities & chunks
|
|
||||||
private final ArrayDeque<Chunk> chunks = new ArrayDeque<>();
|
|
||||||
private final Set<Entity> updatableEntities = ConcurrentHashMap.newKeySet();
|
|
||||||
private final Set<Entity> removedEntities = ConcurrentHashMap.newKeySet();
|
|
||||||
|
|
||||||
private final Phaser phaser = new Phaser(1);
|
|
||||||
|
|
||||||
public ThreadProvider(int threadCount) {
|
|
||||||
this.threads = new ArrayList<>(threadCount);
|
|
||||||
|
|
||||||
for (int i = 0; i < threadCount; i++) {
|
|
||||||
final TickThread.BatchRunnable batchRunnable = new TickThread.BatchRunnable();
|
|
||||||
final TickThread tickThread = new TickThread(batchRunnable, i);
|
|
||||||
this.threads.add(tickThread);
|
|
||||||
|
|
||||||
tickThread.start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public ThreadProvider() {
|
|
||||||
this(Runtime.getRuntime().availableProcessors());
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized void onInstanceCreate(@NotNull Instance instance) {
|
|
||||||
instance.getChunks().forEach(this::addChunk);
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized void onInstanceDelete(@NotNull Instance instance) {
|
|
||||||
instance.getChunks().forEach(this::removeChunk);
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized void onChunkLoad(Chunk chunk) {
|
|
||||||
addChunk(chunk);
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized void onChunkUnload(Chunk chunk) {
|
|
||||||
removeChunk(chunk);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Performs a server tick for all chunks based on their linked thread.
|
* Performs a server tick for all chunks based on their linked thread.
|
||||||
*
|
*
|
||||||
* @param chunk the chunk
|
* @param chunk the chunk
|
||||||
*/
|
*/
|
||||||
public abstract int findThread(@NotNull Chunk chunk);
|
int findThread(@NotNull Chunk chunk);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defines how often chunks thread should be updated.
|
* Defines how often chunks thread should be updated.
|
||||||
*
|
*
|
||||||
* @return the refresh type
|
* @return the refresh type
|
||||||
*/
|
*/
|
||||||
public @NotNull RefreshType getChunkRefreshType() {
|
default @NotNull RefreshType getChunkRefreshType() {
|
||||||
return RefreshType.CONSTANT;
|
return RefreshType.NEVER;
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents the maximum percentage of tick time that can be spent refreshing chunks thread.
|
|
||||||
* <p>
|
|
||||||
* Percentage based on {@link MinecraftServer#TICK_MS}.
|
|
||||||
*
|
|
||||||
* @return the refresh percentage
|
|
||||||
*/
|
|
||||||
public float getRefreshPercentage() {
|
|
||||||
return 0.3f;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Minimum time used to refresh chunks and entities thread.
|
|
||||||
*
|
|
||||||
* @return the minimum refresh time in milliseconds
|
|
||||||
*/
|
|
||||||
public int getMinimumRefreshTime() {
|
|
||||||
return 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Maximum time used to refresh chunks and entities thread.
|
|
||||||
*
|
|
||||||
* @return the maximum refresh time in milliseconds
|
|
||||||
*/
|
|
||||||
public int getMaximumRefreshTime() {
|
|
||||||
return (int) (MinecraftServer.TICK_MS * 0.3);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Prepares the update by creating the {@link TickThread} tasks.
|
|
||||||
*
|
|
||||||
* @param time the tick time in milliseconds
|
|
||||||
*/
|
|
||||||
public void updateAndAwait(long time) {
|
|
||||||
for (var entry : threadChunkMap.entrySet()) {
|
|
||||||
final TickThread thread = entry.getKey();
|
|
||||||
final var chunkEntries = entry.getValue();
|
|
||||||
if (chunkEntries == null || chunkEntries.isEmpty()) {
|
|
||||||
// Nothing to tick
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// Execute tick
|
|
||||||
this.phaser.register();
|
|
||||||
thread.runnable.startTick(phaser, () -> {
|
|
||||||
Acquirable.refreshEntries(chunkEntries);
|
|
||||||
|
|
||||||
final ReentrantLock lock = thread.getLock();
|
|
||||||
lock.lock();
|
|
||||||
for (ChunkEntry chunkEntry : chunkEntries) {
|
|
||||||
final Chunk chunk = chunkEntry.chunk;
|
|
||||||
if (!ChunkUtils.isLoaded(chunk)) return;
|
|
||||||
try {
|
|
||||||
chunk.tick(time);
|
|
||||||
} catch (Throwable e) {
|
|
||||||
MinecraftServer.getExceptionManager().handleException(e);
|
|
||||||
}
|
|
||||||
final List<Entity> entities = chunkEntry.entities;
|
|
||||||
if (!entities.isEmpty()) {
|
|
||||||
for (Entity entity : entities) {
|
|
||||||
if (lock.hasQueuedThreads()) {
|
|
||||||
lock.unlock();
|
|
||||||
// #acquire() callbacks should be called here
|
|
||||||
lock.lock();
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
entity.tick(time);
|
|
||||||
} catch (Throwable e) {
|
|
||||||
MinecraftServer.getExceptionManager().handleException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
lock.unlock();
|
|
||||||
// #acquire() callbacks
|
|
||||||
});
|
|
||||||
}
|
|
||||||
this.phaser.arriveAndAwaitAdvance();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called at the end of each tick to clear removed entities,
|
|
||||||
* refresh the chunk linked to an entity, and chunk threads based on {@link #findThread(Chunk)}.
|
|
||||||
*
|
|
||||||
* @param tickTime the duration of the tick in ms,
|
|
||||||
* used to ensure that the refresh does not take more time than the tick itself
|
|
||||||
*/
|
|
||||||
public synchronized void refreshThreads(long tickTime) {
|
|
||||||
// Clear removed entities
|
|
||||||
processRemovedEntities();
|
|
||||||
// Update entities chunks
|
|
||||||
processUpdatedEntities();
|
|
||||||
if (getChunkRefreshType() == RefreshType.NEVER)
|
|
||||||
return;
|
|
||||||
|
|
||||||
final int timeOffset = MathUtils.clamp((int) ((double) tickTime * getRefreshPercentage()),
|
|
||||||
getMinimumRefreshTime(), getMaximumRefreshTime());
|
|
||||||
final long endTime = System.currentTimeMillis() + timeOffset;
|
|
||||||
final int size = chunks.size();
|
|
||||||
int counter = 0;
|
|
||||||
while (true) {
|
|
||||||
final Chunk chunk = chunks.pollFirst();
|
|
||||||
if (!ChunkUtils.isLoaded(chunk)) {
|
|
||||||
removeChunk(chunk);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// Update chunk threads
|
|
||||||
switchChunk(chunk);
|
|
||||||
// Add back to the deque
|
|
||||||
chunks.addLast(chunk);
|
|
||||||
|
|
||||||
if (++counter > size)
|
|
||||||
break;
|
|
||||||
if (System.currentTimeMillis() >= endTime)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add an entity into the waiting list to get assigned in a thread.
|
|
||||||
* <p>
|
|
||||||
* Called when entering a new chunk.
|
|
||||||
*
|
|
||||||
* @param entity the entity to add
|
|
||||||
*/
|
|
||||||
public void updateEntity(@NotNull Entity entity) {
|
|
||||||
this.updatableEntities.add(entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add an entity into the waiting list to get removed from its thread.
|
|
||||||
* <p>
|
|
||||||
* Called in {@link Entity#remove()}.
|
|
||||||
*
|
|
||||||
* @param entity the entity to remove
|
|
||||||
*/
|
|
||||||
public void removeEntity(@NotNull Entity entity) {
|
|
||||||
this.removedEntities.add(entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shutdowns all the {@link TickThread tick threads}.
|
|
||||||
* <p>
|
|
||||||
* Action is irreversible.
|
|
||||||
*/
|
|
||||||
public void shutdown() {
|
|
||||||
this.threads.forEach(TickThread::shutdown);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addChunk(@NotNull Chunk chunk) {
|
|
||||||
ChunkEntry chunkEntry = setChunkThread(chunk, (thread) -> new ChunkEntry(thread, chunk));
|
|
||||||
this.chunkEntryMap.put(chunk, chunkEntry);
|
|
||||||
this.chunks.add(chunk);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void switchChunk(@NotNull Chunk chunk) {
|
|
||||||
ChunkEntry chunkEntry = chunkEntryMap.get(chunk);
|
|
||||||
if (chunkEntry == null)
|
|
||||||
return;
|
|
||||||
var chunks = threadChunkMap.get(chunkEntry.thread);
|
|
||||||
if (chunks == null || chunks.isEmpty())
|
|
||||||
return;
|
|
||||||
chunks.remove(chunkEntry);
|
|
||||||
|
|
||||||
setChunkThread(chunk, tickThread -> {
|
|
||||||
chunkEntry.thread = tickThread;
|
|
||||||
return chunkEntry;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private @NotNull ChunkEntry setChunkThread(@NotNull Chunk chunk,
|
|
||||||
@NotNull Function<TickThread, ChunkEntry> chunkEntrySupplier) {
|
|
||||||
final int threadId = Math.abs(findThread(chunk)) % threads.size();
|
|
||||||
TickThread thread = threads.get(threadId);
|
|
||||||
var chunks = threadChunkMap.computeIfAbsent(thread, tickThread -> ConcurrentHashMap.newKeySet());
|
|
||||||
|
|
||||||
ChunkEntry chunkEntry = chunkEntrySupplier.apply(thread);
|
|
||||||
chunks.add(chunkEntry);
|
|
||||||
return chunkEntry;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void removeChunk(Chunk chunk) {
|
|
||||||
ChunkEntry chunkEntry = chunkEntryMap.get(chunk);
|
|
||||||
if (chunkEntry != null) {
|
|
||||||
TickThread thread = chunkEntry.thread;
|
|
||||||
var chunks = threadChunkMap.get(thread);
|
|
||||||
if (chunks != null) {
|
|
||||||
chunks.remove(chunkEntry);
|
|
||||||
}
|
|
||||||
chunkEntryMap.remove(chunk);
|
|
||||||
}
|
|
||||||
this.chunks.remove(chunk);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void processRemovedEntities() {
|
|
||||||
if (removedEntities.isEmpty())
|
|
||||||
return;
|
|
||||||
for (Entity entity : removedEntities) {
|
|
||||||
var acquirableEntity = entity.getAcquirable();
|
|
||||||
ChunkEntry chunkEntry = acquirableEntity.getHandler().getChunkEntry();
|
|
||||||
// Remove from list
|
|
||||||
if (chunkEntry != null) {
|
|
||||||
chunkEntry.entities.remove(entity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.removedEntities.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void processUpdatedEntities() {
|
|
||||||
if (updatableEntities.isEmpty())
|
|
||||||
return;
|
|
||||||
for (Entity entity : updatableEntities) {
|
|
||||||
var acquirableEntity = entity.getAcquirable();
|
|
||||||
ChunkEntry handlerChunkEntry = acquirableEntity.getHandler().getChunkEntry();
|
|
||||||
// Remove from previous list
|
|
||||||
if (handlerChunkEntry != null) {
|
|
||||||
handlerChunkEntry.entities.remove(entity);
|
|
||||||
}
|
|
||||||
// Add to new list
|
|
||||||
ChunkEntry chunkEntry = chunkEntryMap.get(entity.getChunk());
|
|
||||||
if (chunkEntry != null) {
|
|
||||||
chunkEntry.entities.add(entity);
|
|
||||||
acquirableEntity.getHandler().refreshChunkEntry(chunkEntry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.updatableEntities.clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defines how often chunks thread should be refreshed.
|
* Defines how often chunks thread should be refreshed.
|
||||||
*/
|
*/
|
||||||
public enum RefreshType {
|
enum RefreshType {
|
||||||
/**
|
/**
|
||||||
* Chunk thread is constant after being defined.
|
* Chunk thread is constant after being defined.
|
||||||
*/
|
*/
|
||||||
@ -325,40 +44,4 @@ public abstract class ThreadProvider {
|
|||||||
*/
|
*/
|
||||||
RARELY
|
RARELY
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class ChunkEntry {
|
|
||||||
private volatile TickThread thread;
|
|
||||||
private final Chunk chunk;
|
|
||||||
private final List<Entity> entities = new ArrayList<>();
|
|
||||||
|
|
||||||
private ChunkEntry(TickThread thread, Chunk chunk) {
|
|
||||||
this.thread = thread;
|
|
||||||
this.chunk = chunk;
|
|
||||||
}
|
|
||||||
|
|
||||||
public @NotNull TickThread getThread() {
|
|
||||||
return thread;
|
|
||||||
}
|
|
||||||
|
|
||||||
public @NotNull Chunk getChunk() {
|
|
||||||
return chunk;
|
|
||||||
}
|
|
||||||
|
|
||||||
public @NotNull List<Entity> getEntities() {
|
|
||||||
return entities;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) return true;
|
|
||||||
if (o == null || getClass() != o.getClass()) return false;
|
|
||||||
ChunkEntry that = (ChunkEntry) o;
|
|
||||||
return chunk.equals(that.chunk);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return Objects.hash(chunk);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ import java.util.concurrent.locks.ReentrantLock;
|
|||||||
/**
|
/**
|
||||||
* Thread responsible for ticking {@link net.minestom.server.instance.Chunk chunks} and {@link net.minestom.server.entity.Entity entities}.
|
* Thread responsible for ticking {@link net.minestom.server.instance.Chunk chunks} and {@link net.minestom.server.entity.Entity entities}.
|
||||||
* <p>
|
* <p>
|
||||||
* Created in {@link ThreadProvider}, and awaken every tick with a task to execute.
|
* Created in {@link ThreadDispatcher}, and awaken every tick with a task to execute.
|
||||||
*/
|
*/
|
||||||
public class TickThread extends Thread {
|
public class TickThread extends Thread {
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user