Improve tick scheduling

This commit is contained in:
TheMode 2021-07-01 00:34:58 +02:00
parent 997e35459e
commit 2e8b3477bf
4 changed files with 28 additions and 33 deletions

View File

@ -108,16 +108,8 @@ public final class UpdateManager {
// Tick all instances // Tick all instances
MinecraftServer.getInstanceManager().getInstances().forEach(instance -> MinecraftServer.getInstanceManager().getInstances().forEach(instance ->
instance.tick(tickStart)); instance.tick(tickStart));
// Tick all chunks (and entities inside) // Tick all chunks (and entities inside)
final CountDownLatch countDownLatch = threadProvider.update(tickStart); this.threadProvider.updateAndAwait(tickStart);
// Wait tick end
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
// Clear removed entities & update threads // Clear removed entities & update threads
long tickTime = System.currentTimeMillis() - tickStart; long tickTime = System.currentTimeMillis() - tickStart;

View File

@ -20,6 +20,8 @@ public interface Acquirable<T> {
* Gets all the {@link Entity entities} being ticked in the current thread. * Gets all the {@link Entity entities} being ticked in the current thread.
* <p> * <p>
* Useful when you want to ensure that no acquisition is ever done. * Useful when you want to ensure that no acquisition is ever done.
* <p>
* Be aware that the entity stream is only updated at the beginning of the thread tick.
* *
* @return the entities ticked in the current thread * @return the entities ticked in the current thread
*/ */

View File

@ -11,7 +11,7 @@ import org.jetbrains.annotations.NotNull;
import java.util.*; import java.util.*;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.Phaser;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Function; import java.util.function.Function;
@ -30,6 +30,8 @@ public abstract class ThreadProvider {
private final Set<Entity> updatableEntities = ConcurrentHashMap.newKeySet(); private final Set<Entity> updatableEntities = ConcurrentHashMap.newKeySet();
private final Set<Entity> removedEntities = ConcurrentHashMap.newKeySet(); private final Set<Entity> removedEntities = ConcurrentHashMap.newKeySet();
private final Phaser phaser = new Phaser(1);
public ThreadProvider(int threadCount) { public ThreadProvider(int threadCount) {
this.threads = new ArrayList<>(threadCount); this.threads = new ArrayList<>(threadCount);
@ -112,24 +114,23 @@ public abstract class ThreadProvider {
* *
* @param time the tick time in milliseconds * @param time the tick time in milliseconds
*/ */
public synchronized @NotNull CountDownLatch update(long time) { public void updateAndAwait(long time) {
CountDownLatch countDownLatch = new CountDownLatch(threads.size()); for (var entry : threadChunkMap.entrySet()) {
for (TickThread thread : threads) { final TickThread thread = entry.getKey();
final var chunkEntries = entry.getValue();
if (chunkEntries == null || chunkEntries.isEmpty()) {
// Nothing to tick
continue;
}
// Execute tick // Execute tick
thread.runnable.startTick(countDownLatch, () -> { this.phaser.register();
final var chunkEntries = threadChunkMap.get(thread); thread.runnable.startTick(phaser, () -> {
if (chunkEntries == null || chunkEntries.isEmpty()) {
// Nothing to tick
Acquirable.refreshEntries(Collections.emptySet());
return;
}
Acquirable.refreshEntries(chunkEntries); Acquirable.refreshEntries(chunkEntries);
final ReentrantLock lock = thread.getLock(); final ReentrantLock lock = thread.getLock();
lock.lock(); lock.lock();
chunkEntries.forEach(chunkEntry -> { chunkEntries.forEach(chunkEntry -> {
Chunk chunk = chunkEntry.chunk; final Chunk chunk = chunkEntry.chunk;
if (!ChunkUtils.isLoaded(chunk)) if (!ChunkUtils.isLoaded(chunk))
return; return;
chunk.tick(time); chunk.tick(time);
@ -138,18 +139,18 @@ public abstract class ThreadProvider {
for (Entity entity : entities) { for (Entity entity : entities) {
if (lock.hasQueuedThreads()) { if (lock.hasQueuedThreads()) {
lock.unlock(); lock.unlock();
// #acquire callbacks should be called here // #acquire() callbacks should be called here
lock.lock(); lock.lock();
} }
entity.tick(time); entity.tick(time);
} }
} }
}); });
Acquirable.refreshEntries(Collections.emptySet());
lock.unlock(); lock.unlock();
// #acquire() callbacks
}); });
} }
return countDownLatch; this.phaser.arriveAndAwaitAdvance();
} }
/** /**

View File

@ -4,7 +4,7 @@ import net.minestom.server.MinecraftServer;
import net.minestom.server.utils.validate.Check; import net.minestom.server.utils.validate.Check;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.Phaser;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import java.util.concurrent.locks.LockSupport; import java.util.concurrent.locks.LockSupport;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
@ -60,16 +60,16 @@ public class TickThread extends Thread {
// The context is necessary to control the tick rates // The context is necessary to control the tick rates
if (localContext != null) { if (localContext != null) {
// Execute tick // Execute tick
localContext.runnable.run();
localContext.countDownLatch.countDown();
CONTEXT_UPDATER.compareAndSet(this, localContext, null); CONTEXT_UPDATER.compareAndSet(this, localContext, null);
localContext.runnable.run();
localContext.phaser.arriveAndDeregister();
} }
LockSupport.park(this); LockSupport.park(this);
} }
} }
protected void startTick(@NotNull CountDownLatch countDownLatch, @NotNull Runnable runnable) { protected void startTick(@NotNull Phaser phaser, @NotNull Runnable runnable) {
this.tickContext = new TickContext(countDownLatch, runnable); this.tickContext = new TickContext(phaser, runnable);
LockSupport.unpark(tickThread); LockSupport.unpark(tickThread);
} }
@ -79,11 +79,11 @@ public class TickThread extends Thread {
} }
private static class TickContext { private static class TickContext {
private final CountDownLatch countDownLatch; private final Phaser phaser;
private final Runnable runnable; private final Runnable runnable;
private TickContext(@NotNull CountDownLatch countDownLatch, @NotNull Runnable runnable) { private TickContext(@NotNull Phaser phaser, @NotNull Runnable runnable) {
this.countDownLatch = countDownLatch; this.phaser = phaser;
this.runnable = runnable; this.runnable = runnable;
} }
} }