feat: improve server ticking accuracy, transition to Thread#sleep and… (#2054)

* feat: improve server ticking accuracy, transition to Thread#sleep and compensate for ticks that take too long

* feat: use nano time instead of milliseconds and add compensation for the rare case of the server oversleeping
This commit is contained in:
DeidaraMC 2024-03-27 03:14:40 -04:00 committed by GitHub
parent ceeab08a2a
commit d04e9e3e71
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 28 additions and 19 deletions

View File

@ -1,17 +1,18 @@
package net.minestom.server.thread;
import net.minestom.server.MinecraftServer;
import net.minestom.server.ServerFlag;
import net.minestom.server.ServerProcess;
import org.jetbrains.annotations.ApiStatus;
import java.util.concurrent.locks.LockSupport;
@ApiStatus.Internal
public final class TickSchedulerThread extends MinestomThread {
private final ServerProcess serverProcess;
private static final long TICK_TIME_NANOS = 1_000_000_000L / ServerFlag.SERVER_TICKS_PER_SECOND;
// Windows has an issue with periodically being unable to sleep for < ~16ms at a time
private static final long SLEEP_THRESHOLD = System.getProperty("os.name", "")
.toLowerCase().startsWith("windows") ? 17 : 2;
private final long startTickNs = System.nanoTime();
private long tick = 1;
private final ServerProcess serverProcess;
public TickSchedulerThread(ServerProcess serverProcess) {
super(MinecraftServer.THREAD_NAME_TICK_SCHEDULER);
@ -20,7 +21,7 @@ public final class TickSchedulerThread extends MinestomThread {
@Override
public void run() {
final long tickNs = (long) (MinecraftServer.TICK_MS * 1e6);
long timeOverslept = 0;
while (serverProcess.isAlive()) {
final long tickStart = System.nanoTime();
try {
@ -28,23 +29,31 @@ public final class TickSchedulerThread extends MinestomThread {
} catch (Exception e) {
serverProcess.exception().handleException(e);
}
fixTickRate(tickNs);
long tickEnd = System.nanoTime();
long nextTickTime = tickEnd + TICK_TIME_NANOS - (tickEnd - tickStart) - timeOverslept;
waitUntilNextTick(nextTickTime);
timeOverslept = System.nanoTime() - nextTickTime;
}
}
private void fixTickRate(long tickNs) {
long nextTickNs = startTickNs + (tickNs * tick);
if (System.nanoTime() < nextTickNs) {
while (true) {
// Checks in every 1/10 ms to see if the current time has reached the next scheduled time.
Thread.yield();
LockSupport.parkNanos(100000);
long currentNs = System.nanoTime();
if (currentNs >= nextTickNs) {
break;
}
private void waitUntilNextTick(long nextTickTimeNanos) {
long currentTime;
while ((currentTime = System.nanoTime()) < nextTickTimeNanos) {
long remainingTime = nextTickTimeNanos - currentTime;
// Sleep less the closer we are to the next tick
long remainingMilliseconds = remainingTime / 1_000_000L;
if (remainingMilliseconds >= SLEEP_THRESHOLD) {
sleepThread(remainingMilliseconds / 2);
}
}
tick++;
}
private void sleepThread(long time) {
try {
Thread.sleep(time);
} catch (InterruptedException e) {
serverProcess.exception().handleException(e);
}
}
}