From d04e9e3e715cdd4d606ba8a4f58358b216d7c296 Mon Sep 17 00:00:00 2001 From: DeidaraMC <117625071+DeidaraMC@users.noreply.github.com> Date: Wed, 27 Mar 2024 03:14:40 -0400 Subject: [PATCH] =?UTF-8?q?feat:=20improve=20server=20ticking=20accuracy,?= =?UTF-8?q?=20transition=20to=20Thread#sleep=20and=E2=80=A6=20(#2054)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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 --- .../server/thread/TickSchedulerThread.java | 47 +++++++++++-------- 1 file changed, 28 insertions(+), 19 deletions(-) diff --git a/src/main/java/net/minestom/server/thread/TickSchedulerThread.java b/src/main/java/net/minestom/server/thread/TickSchedulerThread.java index 2319605aa..7536f4af2 100644 --- a/src/main/java/net/minestom/server/thread/TickSchedulerThread.java +++ b/src/main/java/net/minestom/server/thread/TickSchedulerThread.java @@ -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); + } } }