CTT/Server/Model/Module/UpdateModule/FixedUpdate.cs

228 lines
7.5 KiB
C#
Raw Normal View History

2021-04-08 20:09:59 +08:00
/**
* Code from Xenko GameBase
*/
using System;
using System.Linq;
namespace NETCoreTest.Framework
{
/// <summary>
/// 固定间隔更新器
/// </summary>
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)
2021-04-11 19:50:39 +08:00
int maxLastCount = 2 * Math.Min(BadUpdateCountTime, _lastUpdateCount.Length);
2021-04-08 20:09:59 +08:00
_updateCountAverageSlowLimit = (float) (maxLastCount + (_lastUpdateCount.Length - maxLastCount)) / _lastUpdateCount.Length;
}
/// <summary>
/// Gets the current update time from the start of the game.
/// </summary>
/// <value>The current update time.</value>
public GameTime UpdateTime => _updateTime;
/// <summary>
/// Gets the play time, can be changed to match to the time of the current rendering scene.
/// </summary>
/// <value>The play time.</value>
public TimerTick PlayTime => _playTimer;
/// <summary>
/// 每次更新的时间间隔,默认60Hz<br/>
/// Gets or sets the target elapsed time, this is the duration of each tick/update
/// </summary>
/// <value>The target elapsed time.</value>
public TimeSpan TargetElapsedTime { get; set; }
/// <summary>
/// Resets the elapsed time counter.
/// </summary>
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();
}
/// <summary>
/// Updates the game's clock and calls Update and Draw.
/// </summary>
public void Tick()
{
lock (TickLock)
{
TickInternal();
}
}
private void TickInternal()
{
// Update the timer
_timer.Tick();
// Update the playTimer timer
_playTimer.Tick();
// Measure updateTimer
_updateTimer.Reset();
2021-04-11 19:50:39 +08:00
TimeSpan elapsedAdjustedTime = _timer.ElapsedTimeWithPause;
2021-04-08 20:09:59 +08:00
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;
2021-04-11 19:50:39 +08:00
float updateCountMean = _lastUpdateCount.Aggregate<int, float>(0, (current, t) => current + t);
2021-04-08 20:09:59 +08:00
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));
2021-04-11 19:50:39 +08:00
TimeSpan singleFrameElapsedTime = TargetElapsedTime;
2021-04-08 20:09:59 +08:00
// 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;
/// <summary>
/// Reference page contains links to related conceptual articles.
/// </summary>
/// <param name="gameTime">
/// Time passed since the last call to Update.
/// </param>
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
}
}