@@ -25,22 +25,26 @@ namespace MHServerEmu.Games
25
25
{
26
26
public partial class Game
27
27
{
28
+ public const string Version = "1.52.0.1700" ;
29
+
28
30
[ ThreadStatic ]
29
31
internal static Game Current ;
30
32
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
+
33
36
private static readonly Logger Logger = LogManager . CreateLogger ( ) ;
34
37
35
- public const int TickRate = 20 ; // Ticks per second based on client behavior
36
- public const long TickTime = 1000 / TickRate ; // ms per tick
37
-
38
38
private readonly NetStructGameOptions _gameOptions ;
39
39
private readonly object _gameLock = new ( ) ;
40
40
private readonly CoreNetworkMailbox < FrontendClient > _mailbox = new ( ) ;
41
- private readonly Stopwatch _tickWatch = new ( ) ;
42
41
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
+
44
48
private ulong _currentRepId ;
45
49
46
50
public static TimeSpan StartTime { get ; } = TimeSpan . FromMilliseconds ( 1 ) ;
@@ -56,7 +60,6 @@ public partial class Game
56
60
public ulong CurrentRepId { get => ++ _currentRepId ; }
57
61
// We use a dictionary property instead of AccessMessageHandlerHash(), which is essentially just a getter
58
62
public Dictionary < ulong , IArchiveMessageHandler > MessageHandlerDict { get ; } = new ( ) ;
59
- public TimeSpan FixedTimeBetweenUpdates { get ; private set ; }
60
63
61
64
public override string ToString ( ) => $ "serverGameId=0x{ Id : X} ";
62
65
@@ -88,46 +91,6 @@ public Game(ulong id)
88
91
Logger . Info ( $ "Game 0x{ Id : X} created, initial replication id: { _currentRepId } ") ;
89
92
}
90
93
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
-
131
94
public void Handle ( FrontendClient client , MessagePackage message )
132
95
{
133
96
lock ( _gameLock )
@@ -222,6 +185,75 @@ public void BroadcastMessage(IMessage message)
222
185
NetworkManager . BroadcastMessage ( message ) ;
223
186
}
224
187
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
+
225
257
private List < IMessage > GetBeginLoadingMessages ( PlayerConnection playerConnection )
226
258
{
227
259
List < IMessage > messageList = new ( ) ;
0 commit comments