/Engine/Game/GameTime.cs
C# | 368 lines | 219 code | 37 blank | 112 comment | 15 complexity | d6c5cbf21f36be5d03a7fc7663cf3e8e MD5 | raw file
1using Delta.Engine.Dynamic; 2using Delta.Utilities; 3 4namespace Delta.Engine.Game 5{ 6 /// <summary> 7 /// Game Time is used for having a pausable and resetable time source, 8 /// which is useful for games that can pause.<para /> 9 /// Dependant systems are Effects, Physics. 10 /// </summary> 11 public class GameTime : DynamicModule 12 { 13 #region CheckEvery (Static) 14 /// <summary> 15 /// Check every time (e.g. one second, half a second, every 10 seconds) 16 /// if the time has passed and return true once in that time frame and 17 /// false otherwise. Used for spawning stuff, e.g. every 0.5 seconds and 18 /// is very useful to write framerate independent code. 19 /// Note: If using a small value here like 1.0f/100.0f and we have less 20 /// fps (e.g. 60), then this will return true every time, but obviously 21 /// we cannot return true 100 times if this method is only called 60 times. 22 /// </summary> 23 /// <param name="timeStep">Time step in seconds</param> 24 /// <returns> 25 /// True if the timeStep is reached, false if we have too many frames and 26 /// need to wait until timeStep is reached next. 27 /// </returns> 28 public static bool CheckEvery(float timeStep) 29 { 30 // Make sure the timeStep value is valid! 31 if (timeStep <= 0) 32 { 33 Log.Warning("Invalid time step=" + timeStep); 34 return true; 35 } 36 // Calculate with millisecond values calculated in the timer. 37 return (int)(Milliseconds / (1000 * timeStep)) > 38 (int)(LastMilliseconds / (1000 * timeStep)); 39 } 40 41 /// <summary> 42 /// Check every time (e.g. one second, half a second, every 10 seconds) 43 /// if the time has passed and return true once in that time frame and 44 /// false otherwise. Used for spawning stuff, e.g. every 0.5 seconds and 45 /// is very useful to write framerate independent code. 46 /// Note: If using a small value here like 1.0f/100.0f and we have less 47 /// fps (e.g. 60), then this will return true every time, but obviously 48 /// we cannot return true 100 times if this method is only called 60 times. 49 /// </summary> 50 /// <param name="timeStep">Time step in seconds</param> 51 /// <param name="startTimeMs"> 52 /// Only start checking when this time is reached and then every time step 53 /// after (in milliseconds). 54 /// </param> 55 /// <returns> 56 /// True if the timeStep is reached, false if we have too many frames and 57 /// need to wait until timeStep is reached next. 58 /// </returns> 59 public static bool CheckEvery(float timeStep, long startTimeMs) 60 { 61 // Make sure the timeStep value is valid! 62 if (startTimeMs < 0 || 63 timeStep <= 0) 64 { 65 Log.Warning("Invalid timeStep='" + timeStep + 66 "' or startTimeMs='" + startTimeMs + "'"); 67 return true; 68 } 69 // Calculate with millisecond values calculated in the timer. 70 long relativeStartMs = Milliseconds - startTimeMs; 71 if (relativeStartMs < 0) 72 { 73 // Not started yet 74 return false; 75 } // if 76 77 long relativeLastMs = LastMilliseconds - startTimeMs; 78 return (int)(relativeStartMs / (1000 * timeStep)) > 79 (int)(relativeLastMs / (1000 * timeStep)); 80 } 81 #endregion 82 83 #region GetDeltaToNow (Static) 84 /// <summary> 85 /// Get delta to now in seconds as a floating point value. 86 /// </summary> 87 /// <param name="startTimeInMs">The start time in ms.</param> 88 /// <returns>Float Delta to Now</returns> 89 public static float GetDeltaToNow(long startTimeInMs) 90 { 91 return (Milliseconds - startTimeInMs) / 1000.0f; 92 } 93 #endregion 94 95 #region IsRunning (Static) 96 /// <summary> 97 /// Flag if the game time is updating or not. 98 /// </summary> 99 public static bool IsRunning 100 { 101 get 102 { 103 return Instance.isRunning; 104 } 105 } 106 #endregion 107 108 #region Milliseconds (Static) 109 /// <summary> 110 /// The elapsed game time in milliseconds. 111 /// </summary> 112 public static long Milliseconds 113 { 114 get 115 { 116 return Instance.milliseconds; 117 } 118 } 119 #endregion 120 121 #region LastMilliseconds (Static) 122 /// <summary> 123 /// The elapsed game time in milliseconds from the last frame. This allows 124 /// us to do some checks from frame to frame to see if a timeout or 125 /// cooldown has been reached. See CheckEvery for a use case. 126 /// </summary> 127 public static long LastMilliseconds 128 { 129 get 130 { 131 return Instance.lastMilliseconds; 132 } 133 } 134 #endregion 135 136 #region Delta (Static) 137 /// <summary> 138 /// Returns the current delta time. It represents the difference between 139 /// the last tick and the current tick in seconds (usually small values). 140 /// Used for many calculations and updates to make sure the time goes 141 /// on constantly no matter how many frames we have per second. 142 /// </summary> 143 public static float Delta 144 { 145 get 146 { 147 return Instance.delta; 148 } 149 } 150 #endregion 151 152 #region TotalTime (Static) 153 /// <summary> 154 /// The total time since the game time was started. 155 /// </summary> 156 public static float TotalTime 157 { 158 get 159 { 160 return Instance.totalTime; 161 } 162 } 163 #endregion 164 165 #region Paused (event) 166 /// <summary> 167 /// Event which will occur everytime the game time is paused. 168 /// </summary> 169 public static event RunDelegate Paused; 170 #endregion 171 172 #region Resumed (event) 173 /// <summary> 174 /// Event which will occur everytime the game time is resumed again. 175 /// </summary> 176 public static event RunDelegate Resumed; 177 #endregion 178 179 #region Private 180 181 #region cachedInstance (Private) 182 private static GameTime cachedInstance; 183 #endregion 184 185 #region Instance (Private) 186 private static GameTime Instance 187 { 188 get 189 { 190 if (cachedInstance == null) 191 { 192 cachedInstance = Factory.Create<GameTime>(); 193 } 194 195 return cachedInstance; 196 } 197 } 198 #endregion 199 200 #region timeMsOffset (Private) 201 private long timeMsOffset; 202 #endregion 203 204 #region pauseStartOffset (Private) 205 private long pauseStartOffset; 206 #endregion 207 208 #region isRunning (Private) 209 private bool isRunning; 210 #endregion 211 212 #region totalTime (Private) 213 /// <summary> 214 /// The total time since the game time was started. 215 /// </summary> 216 private float totalTime; 217 #endregion 218 219 #region delta (Private) 220 /// <summary> 221 /// Returns the current delta time. 222 /// </summary> 223 private float delta; 224 #endregion 225 226 #region milliseconds (Private) 227 /// <summary> 228 /// Milliseconds the game time has been running. 229 /// </summary> 230 private long milliseconds; 231 #endregion 232 233 #region lastMilliseconds 234 /// <summary> 235 /// Milliseconds from last frame, allows us to do frame by frame checks. 236 /// </summary> 237 private long lastMilliseconds; 238 #endregion 239 240 #region wasRunningBeforeAppPause 241 /// <summary> 242 /// Flag if the game time was running before it was paused by the 243 /// system information event. If we don't save this state we might 244 /// start the game time again accidentally in OnAppResume. 245 /// </summary> 246 private bool wasRunningBeforeAppPause; 247 #endregion 248 249 #endregion 250 251 #region Constructors 252 /// <summary> 253 /// Create a new game time instance, which is started by default, but can 254 /// be stopped or restarted by the game if a new level starts. 255 /// </summary> 256 internal GameTime() 257 : base("GameTime", typeof(Time)) 258 { 259 wasRunningBeforeAppPause = false; 260 isRunning = true; 261 timeMsOffset += Time.Milliseconds - pauseStartOffset; 262 263 Application.Information.OnAppPause += OnAppPause; 264 Application.Information.OnAppResume += OnAppResume; 265 } 266 267 /// <summary> 268 /// Remove OnAppPause and OnAppResume event handlers added in constructor. 269 /// </summary> 270 ~GameTime() 271 { 272 Application.Information.OnAppPause -= OnAppPause; 273 Application.Information.OnAppResume -= OnAppResume; 274 } 275 #endregion 276 277 #region OnAppPause 278 private void OnAppPause() 279 { 280 wasRunningBeforeAppPause = isRunning; 281 Pause(); 282 } 283 #endregion 284 285 #region OnAppResume 286 private void OnAppResume() 287 { 288 if (wasRunningBeforeAppPause) 289 { 290 Start(); 291 wasRunningBeforeAppPause = false; 292 } 293 } 294 #endregion 295 296 #region Start (Static) 297 /// <summary> 298 /// Start or resume the game time updating. 299 /// </summary> 300 public static void Start() 301 { 302 if (IsRunning == false) 303 { 304 Instance.isRunning = true; 305 Instance.timeMsOffset += Time.Milliseconds - Instance.pauseStartOffset; 306 307 // Still notfiy the listeners (if required) 308 if (Resumed != null && 309 // but only if the time si resumed from a previous pause 310 TotalTime > 0.0f) 311 { 312 Resumed(); 313 } // if 314 } 315 } 316 #endregion 317 318 #region Pause (Static) 319 /// <summary> 320 /// Pause the game time updating. 321 /// </summary> 322 public static void Pause() 323 { 324 if (IsRunning) 325 { 326 Instance.isRunning = false; 327 Instance.pauseStartOffset = Time.Milliseconds; 328 329 // Still notfiy the listeners (if required) 330 if (Paused != null) 331 { 332 Paused(); 333 } // if 334 } 335 } 336 #endregion 337 338 #region Reset (Static) 339 /// <summary> 340 /// Reset the game time. 341 /// </summary> 342 public static void Reset() 343 { 344 Instance.totalTime = 0.0f; 345 } 346 #endregion 347 348 #region Run (Public) 349 /// <summary> 350 /// Update the game time. 351 /// </summary> 352 public override void Run() 353 { 354 if (IsRunning == false) 355 { 356 delta = 0.0f; 357 return; 358 } 359 360 lastMilliseconds = milliseconds; 361 milliseconds = Time.Milliseconds - timeMsOffset; 362 363 delta = Time.Delta; 364 totalTime += Delta; 365 } 366 #endregion 367 } 368}