mirror of
https://github.com/Minestom/Minestom.git
synced 2025-01-03 23:17:48 +01:00
WIP per-chunk thread
This commit is contained in:
parent
356150847e
commit
a55ea6d0c2
@ -41,7 +41,7 @@ public final class UpdateManager {
|
||||
{
|
||||
// DEFAULT THREAD PROVIDER
|
||||
//threadProvider = new PerGroupChunkProvider();
|
||||
threadProvider = new PerInstanceThreadProvider(2);
|
||||
threadProvider = new PerInstanceThreadProvider(4);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -132,9 +132,6 @@ public final class UpdateManager {
|
||||
} catch (InterruptedException e) {
|
||||
MinecraftServer.getExceptionManager().handleException(e);
|
||||
}
|
||||
|
||||
// Reset thread cost count
|
||||
this.threadProvider.cleanup();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -202,13 +199,12 @@ public final class UpdateManager {
|
||||
* <p>
|
||||
* WARNING: should be automatically done by the {@link Instance} implementation.
|
||||
*
|
||||
* @param instance the instance of the chunk
|
||||
* @param chunk the loaded chunk
|
||||
*/
|
||||
public synchronized void signalChunkLoad(Instance instance, @NotNull Chunk chunk) {
|
||||
public synchronized void signalChunkLoad(@NotNull Chunk chunk) {
|
||||
if (this.threadProvider == null)
|
||||
return;
|
||||
this.threadProvider.onChunkLoad(instance, chunk);
|
||||
this.threadProvider.onChunkLoad(chunk);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -216,13 +212,12 @@ public final class UpdateManager {
|
||||
* <p>
|
||||
* WARNING: should be automatically done by the {@link Instance} implementation.
|
||||
*
|
||||
* @param instance the instance of the chunk
|
||||
* @param chunk the unloaded chunk
|
||||
*/
|
||||
public synchronized void signalChunkUnload(Instance instance, @NotNull Chunk chunk) {
|
||||
public synchronized void signalChunkUnload(@NotNull Chunk chunk) {
|
||||
if (this.threadProvider == null)
|
||||
return;
|
||||
this.threadProvider.onChunkUnload(instance, chunk);
|
||||
this.threadProvider.onChunkUnload(chunk);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -506,7 +506,7 @@ public class InstanceContainer extends Instance {
|
||||
protected void retrieveChunk(int chunkX, int chunkZ, @Nullable ChunkCallback callback) {
|
||||
final boolean loaded = chunkLoader.loadChunk(this, chunkX, chunkZ, chunk -> {
|
||||
cacheChunk(chunk);
|
||||
UPDATE_MANAGER.signalChunkLoad(this, chunk);
|
||||
UPDATE_MANAGER.signalChunkLoad(chunk);
|
||||
// Execute callback and event in the instance thread
|
||||
scheduleNextTick(instance -> {
|
||||
callChunkLoadEvent(chunkX, chunkZ);
|
||||
@ -544,7 +544,7 @@ public class InstanceContainer extends Instance {
|
||||
OptionalCallback.execute(callback, chunk);
|
||||
}
|
||||
|
||||
UPDATE_MANAGER.signalChunkLoad(this, chunk);
|
||||
UPDATE_MANAGER.signalChunkLoad(chunk);
|
||||
callChunkLoadEvent(chunkX, chunkZ);
|
||||
}
|
||||
|
||||
@ -641,7 +641,7 @@ public class InstanceContainer extends Instance {
|
||||
final Chunk copiedChunk = chunk.copy(copiedInstance, chunkX, chunkZ);
|
||||
|
||||
copiedInstance.cacheChunk(copiedChunk);
|
||||
UPDATE_MANAGER.signalChunkLoad(copiedInstance, copiedChunk);
|
||||
UPDATE_MANAGER.signalChunkLoad(copiedChunk);
|
||||
}
|
||||
|
||||
return copiedInstance;
|
||||
@ -682,7 +682,7 @@ public class InstanceContainer extends Instance {
|
||||
* Adds a {@link Chunk} to the internal instance map.
|
||||
* <p>
|
||||
* WARNING: the chunk will not automatically be sent to players and
|
||||
* {@link net.minestom.server.UpdateManager#signalChunkLoad(Instance, Chunk)} must be called manually.
|
||||
* {@link net.minestom.server.UpdateManager#signalChunkLoad(Chunk)} must be called manually.
|
||||
*
|
||||
* @param chunk the chunk to cache
|
||||
*/
|
||||
@ -823,7 +823,7 @@ public class InstanceContainer extends Instance {
|
||||
|
||||
chunk.unload();
|
||||
|
||||
UPDATE_MANAGER.signalChunkUnload(this, chunk);
|
||||
UPDATE_MANAGER.signalChunkUnload(chunk);
|
||||
}
|
||||
this.scheduledChunksToRemove.clear();
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ public final class Acquisition {
|
||||
// The goal of the contention service it is manage the situation where two threads are waiting for each other
|
||||
ACQUISITION_CONTENTION_SERVICE.scheduleAtFixedRate(() -> {
|
||||
|
||||
final Set<BatchThread> threads = MinecraftServer.getUpdateManager().getThreadProvider().getThreads();
|
||||
final List<BatchThread> threads = MinecraftServer.getUpdateManager().getThreadProvider().getThreads();
|
||||
|
||||
for (BatchThread batchThread : threads) {
|
||||
final BatchThread waitingThread = (BatchThread) batchThread.getQueue().getWaitingThread();
|
||||
|
@ -15,8 +15,6 @@ public class BatchThread extends Thread {
|
||||
|
||||
private final BatchQueue queue;
|
||||
|
||||
private int cost;
|
||||
|
||||
public BatchThread(@NotNull BatchRunnable runnable, int number) {
|
||||
super(runnable, MinecraftServer.THREAD_NAME_TICK + "-" + number);
|
||||
this.runnable = runnable;
|
||||
@ -25,14 +23,6 @@ public class BatchThread extends Thread {
|
||||
this.runnable.setLinkedThread(this);
|
||||
}
|
||||
|
||||
public int getCost() {
|
||||
return cost;
|
||||
}
|
||||
|
||||
public void setCost(int cost) {
|
||||
this.cost = cost;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public BatchRunnable getMainRunnable() {
|
||||
return runnable;
|
||||
@ -43,9 +33,8 @@ public class BatchThread extends Thread {
|
||||
return queue;
|
||||
}
|
||||
|
||||
public void addRunnable(@NotNull Runnable runnable, int cost) {
|
||||
public void addRunnable(@NotNull Runnable runnable) {
|
||||
this.runnable.queue.add(runnable);
|
||||
this.cost += cost;
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
|
@ -1,50 +1,18 @@
|
||||
package net.minestom.server.thread;
|
||||
|
||||
import net.minestom.server.MinecraftServer;
|
||||
import net.minestom.server.instance.Chunk;
|
||||
import net.minestom.server.instance.Instance;
|
||||
import net.minestom.server.instance.InstanceManager;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class PerInstanceThreadProvider extends ThreadProvider {
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
private static final InstanceManager INSTANCE_MANAGER = MinecraftServer.getInstanceManager();
|
||||
public class PerInstanceThreadProvider extends ThreadProvider {
|
||||
|
||||
public PerInstanceThreadProvider(int threadCount) {
|
||||
super(threadCount);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInstanceCreate(@NotNull Instance instance) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInstanceDelete(@NotNull Instance instance) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChunkLoad(@NotNull Instance instance, Chunk chunk) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChunkUnload(@NotNull Instance instance, Chunk chunk) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(long time) {
|
||||
for (Instance instance : INSTANCE_MANAGER.getInstances()) {
|
||||
createBatch(batchHandler -> {
|
||||
|
||||
for (Chunk chunk : instance.getChunks()) {
|
||||
// Tick chunks & entities
|
||||
batchHandler.updateChunk(chunk, time);
|
||||
}
|
||||
|
||||
}, time);
|
||||
}
|
||||
public int findThread(@NotNull Chunk chunk) {
|
||||
return ThreadLocalRandom.current().nextInt(getThreads().size());
|
||||
}
|
||||
}
|
||||
|
@ -3,19 +3,10 @@ package net.minestom.server.thread;
|
||||
import net.minestom.server.UpdateManager;
|
||||
import net.minestom.server.instance.Chunk;
|
||||
import net.minestom.server.instance.Instance;
|
||||
import net.minestom.server.thread.batch.BatchHandler;
|
||||
import net.minestom.server.utils.time.Cooldown;
|
||||
import net.minestom.server.utils.time.TimeUnit;
|
||||
import net.minestom.server.utils.time.UpdateOption;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Used to link chunks into multiple groups.
|
||||
@ -25,16 +16,13 @@ import java.util.function.Consumer;
|
||||
*/
|
||||
public abstract class ThreadProvider {
|
||||
|
||||
private final Set<BatchThread> threads;
|
||||
private final List<BatchThread> threads;
|
||||
|
||||
private final List<BatchHandler> batchHandlers = new ArrayList<>();
|
||||
|
||||
private UpdateOption batchesRefreshCooldown;
|
||||
private long lastBatchRefreshTime;
|
||||
private final Map<Integer, List<Chunk>> threadChunkMap = new HashMap<>();
|
||||
private final ArrayDeque<Chunk> batchHandlers = new ArrayDeque<>();
|
||||
|
||||
public ThreadProvider(int threadCount) {
|
||||
this.threads = new HashSet<>(threadCount);
|
||||
this.batchesRefreshCooldown = new UpdateOption(500, TimeUnit.MILLISECOND);
|
||||
this.threads = new ArrayList<>(threadCount);
|
||||
|
||||
for (int i = 0; i < threadCount; i++) {
|
||||
final BatchThread.BatchRunnable batchRunnable = new BatchThread.BatchRunnable();
|
||||
@ -45,76 +33,61 @@ public abstract class ThreadProvider {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when an {@link Instance} is registered.
|
||||
*
|
||||
* @param instance the newly create {@link Instance}
|
||||
*/
|
||||
public abstract void onInstanceCreate(@NotNull Instance instance);
|
||||
public void onInstanceCreate(@NotNull Instance instance) {
|
||||
instance.getChunks().forEach(this::addChunk);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when an {@link Instance} is unregistered.
|
||||
*
|
||||
* @param instance the deleted {@link Instance}
|
||||
*/
|
||||
public abstract void onInstanceDelete(@NotNull Instance instance);
|
||||
public void onInstanceDelete(@NotNull Instance instance) {
|
||||
instance.getChunks().forEach(this::removeChunk);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a chunk is loaded.
|
||||
* <p>
|
||||
* Be aware that this is possible for an instance to load chunks before being registered.
|
||||
*
|
||||
* @param instance the instance of the chunk
|
||||
* @param chunk the chunk
|
||||
*/
|
||||
public abstract void onChunkLoad(@NotNull Instance instance, Chunk chunk);
|
||||
public void onChunkLoad(Chunk chunk) {
|
||||
addChunk(chunk);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a chunk is unloaded.
|
||||
*
|
||||
* @param instance the instance of the chunk
|
||||
* @param chunk the chunk
|
||||
*/
|
||||
public abstract void onChunkUnload(@NotNull Instance instance, Chunk chunk);
|
||||
public void onChunkUnload(Chunk chunk) {
|
||||
removeChunk(chunk);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a server tick for all chunks based on their linked thread.
|
||||
*
|
||||
* @param time the update time in milliseconds
|
||||
* @param chunk the chunk
|
||||
*/
|
||||
public abstract void update(long time);
|
||||
public abstract int findThread(@NotNull Chunk chunk);
|
||||
|
||||
public void createBatch(@NotNull Consumer<BatchHandler> consumer, long time) {
|
||||
BatchHandler batchHandler = new BatchHandler();
|
||||
protected void addChunk(Chunk chunk) {
|
||||
int thread = findThread(chunk);
|
||||
var chunks = threadChunkMap.computeIfAbsent(thread, ArrayList::new);
|
||||
chunks.add(chunk);
|
||||
}
|
||||
|
||||
consumer.accept(batchHandler);
|
||||
|
||||
this.batchHandlers.add(batchHandler);
|
||||
|
||||
batchHandler.pushTask(threads, time);
|
||||
protected void removeChunk(Chunk chunk) {
|
||||
int thread = findThread(chunk);
|
||||
var chunks = threadChunkMap.get(thread);
|
||||
if (chunks != null) {
|
||||
chunks.remove(chunk);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the update.
|
||||
* <p>
|
||||
* {@link #update(long)} is called based on its cooldown to limit the overhead.
|
||||
* The cooldown can be modified using {@link #setBatchesRefreshCooldown(UpdateOption)}.
|
||||
*
|
||||
* @param time the tick time in milliseconds
|
||||
*/
|
||||
public void prepareUpdate(long time) {
|
||||
// Verify if the batches should be updated
|
||||
if (batchesRefreshCooldown == null ||
|
||||
!Cooldown.hasCooldown(time, lastBatchRefreshTime, batchesRefreshCooldown)) {
|
||||
this.lastBatchRefreshTime = time;
|
||||
this.batchHandlers.clear();
|
||||
update(time);
|
||||
} else {
|
||||
// Push the tasks
|
||||
for (BatchHandler batchHandler : batchHandlers) {
|
||||
batchHandler.pushTask(threads, time);
|
||||
}
|
||||
}
|
||||
this.threadChunkMap.forEach((threadId, chunks) -> {
|
||||
BatchThread thread = threads.get(threadId);
|
||||
var chunksCopy = new ArrayList<>(chunks);
|
||||
thread.addRunnable(() -> {
|
||||
for (Chunk chunk : chunksCopy) {
|
||||
chunk.tick(time);
|
||||
chunk.getInstance().getChunkEntities(chunk).forEach(entity -> {
|
||||
entity.tick(time);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@ -127,27 +100,12 @@ public abstract class ThreadProvider {
|
||||
return countDownLatch;
|
||||
}
|
||||
|
||||
public void cleanup() {
|
||||
for (BatchThread thread : threads) {
|
||||
thread.setCost(0);
|
||||
}
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
this.threads.forEach(BatchThread::shutdown);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Set<BatchThread> getThreads() {
|
||||
public List<BatchThread> getThreads() {
|
||||
return threads;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public UpdateOption getBatchesRefreshCooldown() {
|
||||
return batchesRefreshCooldown;
|
||||
}
|
||||
|
||||
public void setBatchesRefreshCooldown(@Nullable UpdateOption batchesRefreshCooldown) {
|
||||
this.batchesRefreshCooldown = batchesRefreshCooldown;
|
||||
}
|
||||
}
|
@ -1,63 +0,0 @@
|
||||
package net.minestom.server.thread.batch;
|
||||
|
||||
import net.minestom.server.instance.Chunk;
|
||||
import net.minestom.server.lock.Acquirable;
|
||||
import net.minestom.server.thread.BatchThread;
|
||||
import net.minestom.server.utils.validate.Check;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Set;
|
||||
|
||||
public class BatchHandler {
|
||||
|
||||
private final BatchInfo batchInfo = new BatchInfo();
|
||||
|
||||
private final ArrayList<Chunk> chunks = new ArrayList<>();
|
||||
private int estimatedCost;
|
||||
|
||||
public void updateChunk(@NotNull Chunk chunk, long time) {
|
||||
// Set the BatchInfo field
|
||||
//Acquirable.Handler handler = acquirable.getHandler();
|
||||
//handler.refreshBatchInfo(batchInfo);
|
||||
|
||||
this.chunks.add(chunk);
|
||||
this.estimatedCost++;
|
||||
}
|
||||
|
||||
public void pushTask(@NotNull Set<BatchThread> threads, long time) {
|
||||
BatchThread fitThread = null;
|
||||
int minCost = Integer.MAX_VALUE;
|
||||
|
||||
// Find the thread with the lowest number of tasks
|
||||
for (BatchThread thread : threads) {
|
||||
final boolean switchThread = fitThread == null || thread.getCost() < minCost;
|
||||
if (switchThread) {
|
||||
fitThread = thread;
|
||||
minCost = thread.getCost();
|
||||
}
|
||||
}
|
||||
|
||||
Check.notNull(fitThread, "The task thread returned null, something went terribly wrong.");
|
||||
|
||||
// The thread has been decided
|
||||
this.batchInfo.refreshThread(fitThread);
|
||||
|
||||
// Create the runnable and send it to the thread for execution in the next tick
|
||||
final Runnable runnable = createRunnable(time);
|
||||
fitThread.addRunnable(runnable, estimatedCost);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private Runnable createRunnable(long time) {
|
||||
return () -> {
|
||||
for (Chunk chunk : chunks) {
|
||||
chunk.tick(time);
|
||||
chunk.getInstance().getEntities().forEach(entity -> {
|
||||
entity.tick(time);
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user