diff --git a/Projects/Server/Main.cs b/Projects/Server/Main.cs
index cdd1174a7..1eabf25fc 100644
--- a/Projects/Server/Main.cs
+++ b/Projects/Server/Main.cs
@@ -110,12 +110,13 @@ public static class Core
public static long Uptime => TickCount - _firstTick;
- private static long _cycleIndex;
- private static readonly double[] _cyclesPerSecond = new double[128];
+ private static double _currentCPS;
+ private static double _averageCPS;
+ private static bool _cpsInitialized;
- public static double CyclesPerSecond => _cyclesPerSecond[_cycleIndex];
+ public static double CyclesPerSecond => _currentCPS;
- public static double AverageCPS => _cyclesPerSecond.Average();
+ public static double AverageCPS => _averageCPS;
public static string BaseDirectory
{
@@ -454,20 +455,12 @@ public static class Core
public static void RunEventLoop()
{
-#if DEBUG
- const bool isDebugMode = true;
-#else
- const bool isDebugMode = false;
-#endif
-
- var idleCPU = ServerConfiguration.GetSetting("core.enableIdleCPU", isDebugMode);
-
try
{
- var cycleCount = _cyclesPerSecond.Length;
- var last = _tickCount;
+ var lastRaw = Stopwatch.GetTimestamp();
const int interval = 100;
double frequency = Stopwatch.Frequency * interval;
+ const double alpha = 2.0 / 129; // EMA smoothing (≈128-sample window)
var sample = 0;
@@ -504,20 +497,26 @@ public static class Core
if (sample++ == interval)
{
sample = 0;
- var now = GetTimestamp();
+ var nowRaw = Stopwatch.GetTimestamp();
- var cyclesPerSecond = frequency / (now - last);
- _cyclesPerSecond[_cycleIndex++] = cyclesPerSecond;
- if (_cycleIndex == cycleCount)
+ _currentCPS = frequency / (nowRaw - lastRaw);
+
+ if (!_cpsInitialized)
{
- _cycleIndex = 0;
+ _averageCPS = _currentCPS;
+ _cpsInitialized = true;
+ }
+ else
+ {
+ _averageCPS += alpha * (_currentCPS - _averageCPS);
}
- last = now;
+ lastRaw = nowRaw;
- if (idleCPU && cyclesPerSecond > 125)
+ var sleepMs = (int)Timer.MillisecondsUntilNextTick(_tickCount);
+ if (sleepMs >= 2)
{
- Thread.Sleep(2);
+ NetState.WaitForCompletion(sleepMs - 1);
}
}
}
diff --git a/Projects/Server/Network/NetState/NetState.Network.cs b/Projects/Server/Network/NetState/NetState.Network.cs
index 8065cc997..9bfd39673 100644
--- a/Projects/Server/Network/NetState/NetState.Network.cs
+++ b/Projects/Server/Network/NetState/NetState.Network.cs
@@ -57,6 +57,16 @@ public partial class NetState
///
public static IIORingGroup Ring => _socketManager?.Ring;
+ ///
+ /// Waits for network I/O completions or until the specified timeout expires.
+ /// Used by the game loop to sleep efficiently while remaining responsive to network events.
+ ///
+ /// Maximum time to wait in milliseconds.
+ public static void WaitForCompletion(int timeoutMs)
+ {
+ _socketManager?.WaitForCompletion(timeoutMs);
+ }
+
///
/// Gets the listening addresses that the server is bound to.
///
diff --git a/Projects/Server/Server.csproj b/Projects/Server/Server.csproj
index c61980df3..02f6974eb 100644
--- a/Projects/Server/Server.csproj
+++ b/Projects/Server/Server.csproj
@@ -34,7 +34,7 @@
-
+
diff --git a/Projects/Server/Timer/Timer.TimerWheel.cs b/Projects/Server/Timer/Timer.TimerWheel.cs
index ca8dd3862..8c9dc47fd 100644
--- a/Projects/Server/Timer/Timer.TimerWheel.cs
+++ b/Projects/Server/Timer/Timer.TimerWheel.cs
@@ -62,6 +62,9 @@ public partial class Timer
}
}
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static long MillisecondsUntilNextTick(long tickCount) => Math.Max(0, _tickRate - (tickCount - _lastTickTurned));
+
private static void Turn()
{
var turnNextWheel = false;