Skip to content

Commit 58f6a9a

Browse files
committed
Overhaul game fixed time update timing
1 parent 5747188 commit 58f6a9a

File tree

1 file changed

+80
-48
lines changed

1 file changed

+80
-48
lines changed

src/MHServerEmu.Games/Game.cs

+80-48
Original file line numberDiff line numberDiff line change
@@ -25,22 +25,26 @@ namespace MHServerEmu.Games
2525
{
2626
public partial class Game
2727
{
28+
public const string Version = "1.52.0.1700";
29+
2830
[ThreadStatic]
2931
internal static Game Current;
3032

31-
public const string Version = "1.52.0.1700";
32-
33+
private const int TargetFrameRate = 20;
34+
public readonly TimeSpan FixedTimeBetweenUpdates = TimeSpan.FromMilliseconds(1000f / TargetFrameRate);
35+
3336
private static readonly Logger Logger = LogManager.CreateLogger();
3437

35-
public const int TickRate = 20; // Ticks per second based on client behavior
36-
public const long TickTime = 1000 / TickRate; // ms per tick
37-
3838
private readonly NetStructGameOptions _gameOptions;
3939
private readonly object _gameLock = new();
4040
private readonly CoreNetworkMailbox<FrontendClient> _mailbox = new();
41-
private readonly Stopwatch _tickWatch = new();
4241

43-
private int _tickCount;
42+
private readonly Stopwatch _gameTimer = new();
43+
private TimeSpan _accumulatedFixedTimeUpdateTime; // How much has passed since the last fixed time update
44+
private TimeSpan _lastFixedTimeUpdateStartTime; // When was the last time we tried to do a fixed time update
45+
private TimeSpan _lastFixedTimeUpdateProcessTime; // How long the last fixed time took
46+
private int _frameCount;
47+
4448
private ulong _currentRepId;
4549

4650
public static TimeSpan StartTime { get; } = TimeSpan.FromMilliseconds(1);
@@ -56,7 +60,6 @@ public partial class Game
5660
public ulong CurrentRepId { get => ++_currentRepId; }
5761
// We use a dictionary property instead of AccessMessageHandlerHash(), which is essentially just a getter
5862
public Dictionary<ulong, IArchiveMessageHandler> MessageHandlerDict { get; } = new();
59-
public TimeSpan FixedTimeBetweenUpdates { get; private set; }
6063

6164
public override string ToString() => $"serverGameId=0x{Id:X}";
6265

@@ -88,46 +91,6 @@ public Game(ulong id)
8891
Logger.Info($"Game 0x{Id:X} created, initial replication id: {_currentRepId}");
8992
}
9093

91-
public void Update()
92-
{
93-
Current = this;
94-
95-
while (true)
96-
{
97-
_tickWatch.Restart();
98-
Interlocked.Increment(ref _tickCount);
99-
100-
lock (_gameLock) // lock to prevent state from being modified mid-update
101-
{
102-
// Handle all queued messages
103-
while (_mailbox.HasMessages)
104-
{
105-
var message = _mailbox.PopNextMessage();
106-
PlayerConnection connection = NetworkManager.GetPlayerConnection(message.Item1);
107-
connection.ReceiveMessage(message.Item2);
108-
}
109-
110-
FixedTimeBetweenUpdates = TimeSpan.FromMilliseconds(TickTime); // TODO UpdateFixedTime();
111-
// Update event manager
112-
EventManager.Update();
113-
// Update locomote
114-
EntityManager.LocomoteEntities();
115-
// Update physics manager
116-
EntityManager.PhysicsResolveEntities();
117-
118-
// Send responses to all clients
119-
NetworkManager.SendAllPendingMessages();
120-
}
121-
122-
_tickWatch.Stop();
123-
124-
if (_tickWatch.ElapsedMilliseconds > TickTime)
125-
Logger.Warn($"Game update took longer ({_tickWatch.ElapsedMilliseconds} ms) than target tick time ({TickTime} ms)");
126-
else
127-
Thread.Sleep((int)(TickTime - _tickWatch.ElapsedMilliseconds));
128-
}
129-
}
130-
13194
public void Handle(FrontendClient client, MessagePackage message)
13295
{
13396
lock (_gameLock)
@@ -222,6 +185,75 @@ public void BroadcastMessage(IMessage message)
222185
NetworkManager.BroadcastMessage(message);
223186
}
224187

188+
private void Update()
189+
{
190+
Current = this;
191+
_gameTimer.Start();
192+
193+
while (true)
194+
{
195+
UpdateFixedTime();
196+
}
197+
}
198+
199+
private void UpdateFixedTime()
200+
{
201+
// First we make sure enough time has passed to do another fixed time update
202+
TimeSpan currentTime = _gameTimer.Elapsed;
203+
_accumulatedFixedTimeUpdateTime += currentTime - _lastFixedTimeUpdateStartTime;
204+
_lastFixedTimeUpdateStartTime = currentTime;
205+
206+
if (_accumulatedFixedTimeUpdateTime < FixedTimeBetweenUpdates)
207+
{
208+
// Thread.Sleep() can sleep for longer than specified, so rather than sleeping
209+
// for the entire time window between fixed updates, we do it in 1 ms intervals.
210+
// For reference see MonoGame implementation here:
211+
// https://github.com/MonoGame/MonoGame/blob/develop/MonoGame.Framework/Game.cs#L518
212+
if ((FixedTimeBetweenUpdates - _accumulatedFixedTimeUpdateTime).TotalMilliseconds >= 2.0)
213+
Thread.Sleep(1);
214+
return;
215+
}
216+
217+
lock (_gameLock) // Lock to prevent state from being modified mid-update
218+
{
219+
while (_accumulatedFixedTimeUpdateTime >= FixedTimeBetweenUpdates)
220+
{
221+
_accumulatedFixedTimeUpdateTime -= FixedTimeBetweenUpdates;
222+
223+
TimeSpan fixedUpdateStartTime = _gameTimer.Elapsed;
224+
225+
DoFixedTimeUpdate();
226+
_frameCount++;
227+
228+
_lastFixedTimeUpdateProcessTime = _gameTimer.Elapsed - fixedUpdateStartTime;
229+
230+
if (_lastFixedTimeUpdateProcessTime > FixedTimeBetweenUpdates)
231+
Logger.Warn($"UpdateFixedTime(): Took longer ({_lastFixedTimeUpdateProcessTime.TotalMilliseconds:0.00} ms) than FixedTimeBetweenUpdates ({FixedTimeBetweenUpdates.TotalMilliseconds:0.00} ms)");
232+
}
233+
}
234+
}
235+
236+
private void DoFixedTimeUpdate()
237+
{
238+
// Handle all queued messages
239+
while (_mailbox.HasMessages)
240+
{
241+
var message = _mailbox.PopNextMessage();
242+
PlayerConnection connection = NetworkManager.GetPlayerConnection(message.Item1);
243+
connection.ReceiveMessage(message.Item2);
244+
}
245+
246+
// Update event manager
247+
EventManager.Update();
248+
// Update locomote
249+
EntityManager.LocomoteEntities();
250+
// Update physics manager
251+
EntityManager.PhysicsResolveEntities();
252+
253+
// Send responses to all clients
254+
NetworkManager.SendAllPendingMessages();
255+
}
256+
225257
private List<IMessage> GetBeginLoadingMessages(PlayerConnection playerConnection)
226258
{
227259
List<IMessage> messageList = new();

0 commit comments

Comments
 (0)