/** * Code from Xenko GameBase */ using System; using System.Linq; namespace NETCoreTest.Framework { /// /// 固定间隔更新器 /// public class FixedUpdate { private readonly GameTime _updateTime; private readonly TimerTick _playTimer; private readonly TimerTick _updateTimer; private readonly int[] _lastUpdateCount; private readonly float _updateCountAverageSlowLimit; private TimeSpan _singleFrameUpdateTime; private TimeSpan _totalUpdateTime; private readonly TimeSpan _maximumElapsedTime; private TimeSpan _accumulatedElapsedGameTime; private TimeSpan _lastFrameElapsedGameTime; private int _nextLastUpdateCountIndex; private bool _drawRunningSlowly; private bool _forceElapsedTimeToZero; private readonly TimerTick _timer; internal object TickLock = new object(); public FixedUpdate() { // Internals _updateTime = new GameTime(); _playTimer = new TimerTick(); _updateTimer = new TimerTick(); _totalUpdateTime = new TimeSpan(); _timer = new TimerTick(); _maximumElapsedTime = TimeSpan.FromMilliseconds(500.0); TargetElapsedTime = TimeSpan.FromTicks(TimeSpan.TicksPerSecond / 60); // target elapsed time is by default 60Hz _lastUpdateCount = new int[4]; _nextLastUpdateCountIndex = 0; // Calculate the updateCountAverageSlowLimit (assuming moving average is >=3 ) // Example for a moving average of 4: // updateCountAverageSlowLimit = (2 * 2 + (4 - 2)) / 4 = 1.5f const int BadUpdateCountTime = 2; // number of bad frame (a bad frame is a frame that has at least 2 updates) int maxLastCount = 2 * Math.Min(BadUpdateCountTime, _lastUpdateCount.Length); _updateCountAverageSlowLimit = (float) (maxLastCount + (_lastUpdateCount.Length - maxLastCount)) / _lastUpdateCount.Length; } /// /// Gets the current update time from the start of the game. /// /// The current update time. public GameTime UpdateTime => _updateTime; /// /// Gets the play time, can be changed to match to the time of the current rendering scene. /// /// The play time. public TimerTick PlayTime => _playTimer; /// /// 每次更新的时间间隔,默认60Hz
/// Gets or sets the target elapsed time, this is the duration of each tick/update ///
/// The target elapsed time. public TimeSpan TargetElapsedTime { get; set; } /// /// Resets the elapsed time counter. /// public void ResetElapsedTime() { _forceElapsedTimeToZero = true; _drawRunningSlowly = false; Array.Clear(_lastUpdateCount, 0, _lastUpdateCount.Length); _nextLastUpdateCountIndex = 0; } internal void InitializeBeforeRun() { _timer.Reset(); _updateTime.Reset(_totalUpdateTime); // Run the first time an update _updateTimer.Reset(); Update(_updateTime); _updateTimer.Tick(); _singleFrameUpdateTime += _updateTimer.ElapsedTime; // Reset PlayTime _playTimer.Reset(); } /// /// Updates the game's clock and calls Update and Draw. /// public void Tick() { lock (TickLock) { TickInternal(); } } private void TickInternal() { // Update the timer _timer.Tick(); // Update the playTimer timer _playTimer.Tick(); // Measure updateTimer _updateTimer.Reset(); TimeSpan elapsedAdjustedTime = _timer.ElapsedTimeWithPause; if (_forceElapsedTimeToZero) { elapsedAdjustedTime = TimeSpan.Zero; _forceElapsedTimeToZero = false; } if (elapsedAdjustedTime > _maximumElapsedTime) { elapsedAdjustedTime = _maximumElapsedTime; } int updateCount = 1; // If the rounded TargetElapsedTime is equivalent to current ElapsedAdjustedTime // then make ElapsedAdjustedTime = TargetElapsedTime. We take the same internal rules as XNA if (Math.Abs(elapsedAdjustedTime.Ticks - TargetElapsedTime.Ticks) < (TargetElapsedTime.Ticks >> 6)) { elapsedAdjustedTime = TargetElapsedTime; } // Update the accumulated time _accumulatedElapsedGameTime += elapsedAdjustedTime; // Calculate the number of update to issue updateCount = (int) (_accumulatedElapsedGameTime.Ticks / TargetElapsedTime.Ticks); if (updateCount == 0) { // If there is no need for update, then exit return; } // Calculate a moving average on updateCount _lastUpdateCount[_nextLastUpdateCountIndex] = updateCount; float updateCountMean = _lastUpdateCount.Aggregate(0, (current, t) => current + t); updateCountMean /= _lastUpdateCount.Length; _nextLastUpdateCountIndex = (_nextLastUpdateCountIndex + 1) % _lastUpdateCount.Length; // Test when we are running slowly _drawRunningSlowly = updateCountMean > _updateCountAverageSlowLimit; // We are going to call Update updateCount times, so we can substract this from accumulated elapsed game time _accumulatedElapsedGameTime = new TimeSpan(_accumulatedElapsedGameTime.Ticks - (updateCount * TargetElapsedTime.Ticks)); TimeSpan singleFrameElapsedTime = TargetElapsedTime; // Reset the time of the next frame for (_lastFrameElapsedGameTime = TimeSpan.Zero; updateCount > 0; updateCount--) { _updateTime.Update(_totalUpdateTime, singleFrameElapsedTime, _singleFrameUpdateTime, _drawRunningSlowly, true); try { UpdateAndProfile(_updateTime); } finally { _lastFrameElapsedGameTime += singleFrameElapsedTime; _totalUpdateTime += singleFrameElapsedTime; } } // End measuring update time _updateTimer.Tick(); _singleFrameUpdateTime = TimeSpan.Zero; } #region Methods public Action UpdateCallback; /// /// Reference page contains links to related conceptual articles. /// /// /// Time passed since the last call to Update. /// protected virtual void Update(GameTime gameTime) { UpdateCallback?.Invoke(); _lastFrameElapsedGameTime = TimeSpan.Zero; } private void UpdateAndProfile(GameTime gameTime) { _updateTimer.Reset(); Update(gameTime); _updateTimer.Tick(); _singleFrameUpdateTime += _updateTimer.ElapsedTime; _lastFrameElapsedGameTime = TimeSpan.Zero; } #endregion } }