fishbait/FishBait-UnityProject/Assets/FishNet/Runtime/Managing/Timing/TimeManager.cs
2022-08-14 14:51:58 +02:00

1051 lines
No EOL
38 KiB
C#

using FishNet.Connection;
using FishNet.Documenting;
using FishNet.Managing.Logging;
using FishNet.Object;
using FishNet.Serializing;
using FishNet.Serializing.Helping;
using FishNet.Transporting;
using FishNet.Utility;
using FishNet.Utility.Extension;
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using UnityEngine;
using SystemStopwatch = System.Diagnostics.Stopwatch;
using UnityScene = UnityEngine.SceneManagement.Scene;
namespace FishNet.Managing.Timing
{
/// <summary>
/// Provides data and actions for network time and tick based systems.
/// </summary>
[DisallowMultipleComponent]
[AddComponentMenu("FishNet/Manager/TimeManager")]
public sealed partial class TimeManager : MonoBehaviour
{
//#region Types.
//public enum BufferPurgeType
//{
// /// <summary>
// /// Run an additional input per tick when buffered inputs are higher than normal.
// /// This prevents clients from sending excessive inputs but may briefly disrupt clients synchronization if their timing is drastically off.
// /// Use this option for more secure prediction.
// /// </summary>
// Discard = 0,
// /// <summary>
// /// Run an additional input per tick when buffered inputs are higher than normal.
// /// This is useful for keeping the client synchronized with the server by processing inputs that would normally be discarded.
// /// However, by running extra buffered inputs the client has a better opportunity to cheat.
// /// </summary>
// Run = 1
//}
//#endregion
#region Public.
/// <summary>
/// Called before performing a reconcile on NetworkBehaviour.
/// </summary>
public event Action<NetworkBehaviour> OnPreReconcile;
/// <summary>
/// Called after performing a reconcile on a NetworkBehaviour.
/// </summary>
public event Action<NetworkBehaviour> OnPostReconcile;
/// <summary>
/// Called before physics is simulated when replaying a replicate method.
/// Contains the PhysicsScene and PhysicsScene2D which was simulated.
/// </summary>
public event Action<PhysicsScene, PhysicsScene2D> OnPreReplicateReplay;
/// <summary>
/// Called after physics is simulated when replaying a replicate method.
/// Contains the PhysicsScene and PhysicsScene2D which was simulated.
/// </summary>
public event Action<PhysicsScene, PhysicsScene2D> OnPostReplicateReplay;
/// <summary>
/// Called right before a tick occurs, as well before data is read.
/// </summary>
public event Action OnPreTick;
/// <summary>
/// Called when a tick occurs.
/// </summary>
public event Action OnTick;
/// <summary>
/// When using TimeManager for physics timing, this is called immediately before physics simulation will occur for the tick.
/// While using Unity for physics timing, this is called during FixedUpdate.
/// This may be useful if you wish to run physics differently for stacked scenes.
/// </summary>
public event Action<float> OnPhysicsSimulation;
/// <summary>
/// Called after a tick occurs; physics would have simulated if using PhysicsMode.TimeManager.
/// </summary>
public event Action OnPostTick;
/// <summary>
/// Called when MonoBehaviours call Update.
/// </summary>
public event Action OnUpdate;
/// <summary>
/// Called when MonoBehaviours call LateUpdate.
/// </summary>
public event Action OnLateUpdate;
/// <summary>
/// Called when MonoBehaviours call FixedUpdate.
/// </summary>
public event Action OnFixedUpdate;
/// <summary>
/// RoundTripTime in milliseconds.
/// </summary>
public long RoundTripTime { get; private set; }
/// <summary>
/// True if the number of frames per second are less than the number of expected ticks per second.
/// </summary>
internal bool LowFrameRate => ((Time.unscaledTime - _lastMultipleTicks) < 1f);
/// <summary>
/// Tick on the last received packet, be it from server or client.
/// </summary>
public uint LastPacketTick { get; internal set; }
/// <summary>
/// Current approximate network tick as it is on server.
/// When running as client only this is an approximation to what the server tick is.
/// The value of this field may increase and decrease as timing adjusts.
/// This value is reset upon disconnecting.
/// Tick can be used to get the server time by using TicksToTime().
/// Use LocalTick for values that only increase.
/// </summary>
public uint Tick { get; internal set; }
/// <summary>
/// Percentage as 0-100 of how much into next tick the time is.
/// </summary>
[Obsolete("Use GetPreciseTick or GetTickPercent instead.")] //Remove on 2023/01/01
public byte TickPercent
{
get
{
if (_networkManager == null)
return 0;
double delta = (_networkManager.IsServer) ? TickDelta : _adjustedTickDelta;
double percent = (_elapsedTickTime / delta) * 100;
return (byte)Mathf.Clamp((float)percent, 0, 100);
}
}
/// <summary>
/// DeltaTime for TickRate.
/// </summary>
[HideInInspector]
public double TickDelta { get; private set; }
/// <summary>
/// True if the TimeManager will or has ticked this frame.
/// </summary>
public bool FrameTicked { get; private set; }
/// <summary>
/// How long the local server has been connected.
/// </summary>
public float ServerUptime { get; private set; }
/// <summary>
/// How long the local client has been connected.
/// </summary>
public float ClientUptime { get; private set; }
/// <summary>
///
/// </summary>
private bool _isReplaying;
/// <summary>
/// Returns if any prediction is replaying.
/// </summary>
/// <returns></returns>
public bool IsReplaying() => _isReplaying;
/// <summary>
/// Returns if scene is replaying.
/// </summary>
/// <param name="scene"></param>
/// <returns></returns>
public bool IsReplaying(UnityScene scene) => _replayingScenes.Contains(scene);
/// <summary>
/// True if any predictions are replaying.
/// </summary>
#endregion
#region Serialized.
/// <summary>
///
/// </summary>
[Tooltip("How many times per second the server will simulate. This does not limit server frame rate.")]
[Range(1, 240)]
[SerializeField]
private ushort _tickRate = 30;
/// <summary>
/// How many times per second the server will simulate. This does not limit server frame rate.
/// </summary>
public ushort TickRate { get => _tickRate; private set => _tickRate = value; }
/// <summary>
///
/// </summary>
[Tooltip("How often in seconds to a connections ping. This is also responsible for approximating server tick. This value does not affect prediction.")]
[Range(1, 15)]
[SerializeField]
private byte _pingInterval = 1;
/// <summary>
/// How often in seconds to a connections ping. This is also responsible for approximating server tick. This value does not affect prediction.
/// </summary>
internal byte PingInterval => _pingInterval;
/// <summary>
/// How often in seconds to update prediction timing. Lower values will result in marginally more accurate timings at the cost of bandwidth.
/// </summary>
[Tooltip("How often in seconds to update prediction timing. Lower values will result in marginally more accurate timings at the cost of bandwidth.")]
[Range(1, 15)]
[SerializeField]
private byte _timingInterval = 2;
/// <summary>
///
/// </summary>
[Tooltip("How to perform physics.")]
[SerializeField]
private PhysicsMode _physicsMode = PhysicsMode.Unity;
/// <summary>
/// How to perform physics.
/// </summary>
public PhysicsMode PhysicsMode => _physicsMode;
/// <summary>
///
/// </summary>
[Tooltip("Maximum number of buffered inputs which will be accepted from client before old inputs are discarded.")]
[Range(1, 100)]
[SerializeField]
private byte _maximumBufferedInputs = 15;
/// <summary>
/// Maximum number of buffered inputs which will be accepted from client before old inputs are discarded.
/// </summary>
public byte MaximumBufferedInputs => _maximumBufferedInputs;
#endregion
#region Private.
/// <summary>
/// Scenes which are currently replaying prediction.
/// </summary>
private HashSet<UnityScene> _replayingScenes = new HashSet<UnityScene>(new SceneHandleEqualityComparer());
/// <summary>
/// Ticks that have passed on client since the last time server sent an UpdateTicksBroadcast.
/// </summary>
private uint _clientTicks = 0;
/// <summary>
/// Last Tick the server sent out UpdateTicksBroadcast.
/// </summary>
private uint _lastUpdateTicks = 0;
/// <summary>
///
/// </summary>
private uint _localTick;
/// <summary>
/// A tick that is not synchronized. This value will only increment. May be used for indexing or Ids with custom logic.
/// When called on the server Tick is returned, otherwise LocalTick is returned.
/// This value resets upon disconnecting.
/// </summary>
public uint LocalTick
{
get => (_networkManager.IsServer) ? Tick : _localTick;
private set => _localTick = value;
}
/// <summary>
/// Stopwatch used for pings.
/// </summary>
SystemStopwatch _pingStopwatch = new SystemStopwatch();
/// <summary>
/// Ticks passed since last ping.
/// </summary>
private uint _pingTicks;
/// <summary>
/// MovingAverage instance used to calculate mean ping.
/// </summary>
private MovingAverage _pingAverage = new MovingAverage(5);
/// <summary>
/// Time elapsed after ticks. This is extra time beyond the simulation rate.
/// </summary>
private double _elapsedTickTime;
/// <summary>
/// NetworkManager used with this.
/// </summary>
private NetworkManager _networkManager;
/// <summary>
/// Internal deltaTime for clients. Controlled by the server.
/// </summary>
private double _adjustedTickDelta;
/// <summary>
/// Range which client timing may reside within.
/// </summary>
private double[] _clientTimingRange;
/// <summary>
/// Last frame an iteration occurred for incoming.
/// </summary>
private int _lastIncomingIterationFrame = -1;
/// <summary>
/// True if client received Pong since last ping.
/// </summary>
private bool _receivedPong = true;
/// <summary>
/// Last unscaledTime multiple ticks occurred in a single frame.
/// </summary>
private float _lastMultipleTicks;
/// <summary>
/// Number of TimeManagers open which are using manual physics.
/// </summary>
private static uint _manualPhysics;
#endregion
#region Const.
/// <summary>
/// Maximum percentage timing may vary from SimulationInterval for clients.
/// </summary>
private const float CLIENT_TIMING_PERCENT_RANGE = 0.5f;
/// <summary>
/// Percentage of TickDelta client will adjust when needing to speed up.
/// </summary>
private const double CLIENT_SPEEDUP_PERCENT = 0.003d;
/// <summary>
/// Percentage of TickDelta client will adjust when needing to slow down.
/// </summary>
private const double CLIENT_SLOWDOWN_PERCENT = 0.005d;
/// <summary>
/// When steps to be sent to clients are equal to or higher than this value in either direction a reset steps will be sent.
/// </summary>
private const byte RESET_STEPS_THRESHOLD = 5;
/// <summary>
/// Playerprefs string to load and save user fixed time.
/// </summary>
private const string SAVED_FIXED_TIME_TEXT = "SavedFixedTimeFN";
#endregion
private void OnEnable()
{
UnityEngine.SceneManagement.SceneManager.sceneUnloaded += SceneManager_sceneUnloaded;
}
#if UNITY_EDITOR
private void OnDisable()
{
UnityEngine.SceneManagement.SceneManager.sceneUnloaded -= SceneManager_sceneUnloaded;
//If closing/stopping.
if (ApplicationState.IsQuitting())
{
_manualPhysics = 0;
UnsetSimulationSettings();
}
else if (PhysicsMode == PhysicsMode.TimeManager)
{
_manualPhysics = Math.Max(0, _manualPhysics - 1);
}
}
#endif
/// <summary>
/// Called when FixedUpdate ticks. This is called before any other script.
/// </summary>
internal void TickFixedUpdate()
{
OnFixedUpdate?.Invoke();
/* Invoke onsimulation if using Unity time.
* Otherwise let the tick cycling part invoke. */
if (PhysicsMode == PhysicsMode.Unity)
OnPhysicsSimulation?.Invoke(Time.fixedDeltaTime);
}
/// <summary>
/// Called when Update ticks. This is called before any other script.
/// </summary>
internal void TickUpdate()
{
if (_networkManager.IsServer)
ServerUptime += Time.deltaTime;
if (_networkManager.IsClient)
ClientUptime += Time.deltaTime;
IncreaseTick();
OnUpdate?.Invoke();
}
/// <summary>
/// Called when LateUpdate ticks. This is called after all other scripts.
/// </summary>
internal void TickLateUpdate()
{
OnLateUpdate?.Invoke();
}
/// <summary>
/// Initializes this script for use.
/// </summary>
internal void InitializeOnceInternal(NetworkManager networkManager)
{
_networkManager = networkManager;
SetInitialValues();
_networkManager.ServerManager.OnServerConnectionState += ServerManager_OnServerConnectionState;
_networkManager.ClientManager.OnClientConnectionState += ClientManager_OnClientConnectionState;
AddNetworkLoops();
}
/// <summary>
/// Adds network loops to gameObject.
/// </summary>
private void AddNetworkLoops()
{
//Writer.
if (!gameObject.TryGetComponent<NetworkWriterLoop>(out _))
gameObject.AddComponent<NetworkWriterLoop>();
//Reader.
if (!gameObject.TryGetComponent<NetworkReaderLoop>(out _))
gameObject.AddComponent<NetworkReaderLoop>();
}
/// <summary>
/// Called when a scene unloads.
/// </summary>
/// <param name="arg0"></param>
private void SceneManager_sceneUnloaded(UnityScene s)
{
_replayingScenes.Remove(s);
}
/// <summary>
/// Called after the local client connection state changes.
/// </summary>
private void ClientManager_OnClientConnectionState(ClientConnectionStateArgs obj)
{
if (obj.ConnectionState != LocalConnectionState.Started)
{
_replayingScenes.Clear();
_pingStopwatch.Stop();
ClientUptime = 0f;
LocalTick = 0;
//Also reset Tick if not running as host.
if (!_networkManager.IsServer)
Tick = 0;
}
//Started.
else
{
_pingStopwatch.Restart();
}
}
/// <summary>
/// Called after the local server connection state changes.
/// </summary>
private void ServerManager_OnServerConnectionState(ServerConnectionStateArgs obj)
{
//If no servers are running.
if (!_networkManager.ServerManager.AnyServerStarted())
{
ServerUptime = 0f;
Tick = 0;
}
}
/// <summary>
/// Invokes OnPre/PostReconcile events.
/// Internal use.
/// </summary>
[APIExclude]
[CodegenMakePublic] //To internal.
public void InvokeOnReconcile(NetworkBehaviour nb, bool before)
{
nb.IsReconciling = before;
if (before)
OnPreReconcile?.Invoke(nb);
else
OnPostReconcile?.Invoke(nb);
}
/// <summary>
/// Invokes OnReplicateReplay.
/// Internal use.
/// </summary>
[APIExclude]
[CodegenMakePublic] //To internal.
public void InvokeOnReplicateReplay(UnityScene scene, PhysicsScene ps, PhysicsScene2D ps2d, bool before)
{
_isReplaying = before;
if (before)
{
_replayingScenes.Add(scene);
OnPreReplicateReplay?.Invoke(ps, ps2d);
}
else
{
_replayingScenes.Remove(scene);
OnPostReplicateReplay?.Invoke(ps, ps2d);
}
}
/// <summary>
/// Sets values to use based on settings.
/// </summary>
private void SetInitialValues()
{
SetTickRate(TickRate);
InitializePhysicsMode(PhysicsMode);
}
/// <summary>
/// Sets simulation settings to Unity defaults.
/// </summary>
private void UnsetSimulationSettings()
{
Physics.autoSimulation = true;
#if !UNITY_2020_2_OR_NEWER
Physics2D.autoSimulation = true;
#else
Physics2D.simulationMode = SimulationMode2D.FixedUpdate;
#endif
float simulationTime = PlayerPrefs.GetFloat(SAVED_FIXED_TIME_TEXT, float.MinValue);
if (simulationTime != float.MinValue)
Time.fixedDeltaTime = simulationTime;
}
/// <summary>
/// Initializes physics mode when starting.
/// </summary>
/// <param name="automatic"></param>
private void InitializePhysicsMode(PhysicsMode mode)
{
//Disable.
if (mode == PhysicsMode.Disabled)
{
SetPhysicsMode(mode);
}
//Do not automatically simulate.
else if (mode == PhysicsMode.TimeManager)
{
#if UNITY_EDITOR
//Preserve user tick rate.
PlayerPrefs.SetFloat(SAVED_FIXED_TIME_TEXT, Time.fixedDeltaTime);
//Let the player know.
if (Time.fixedDeltaTime != (float)TickDelta)
Debug.LogWarning("Time.fixedDeltaTime is being overriden with TimeManager.TickDelta");
#endif
Time.fixedDeltaTime = (float)TickDelta;
/* Only check this if network manager
* is not null. It would be null via
* OnValidate. */
if (_networkManager != null)
{
//If at least one time manager is already running manual physics.
if (_manualPhysics > 0)
{
if (_networkManager.CanLog(LoggingType.Error))
Debug.LogError($"There are multiple TimeManagers instantiated which are using manual physics. Manual physics with multiple TimeManagers is not supported.");
}
_manualPhysics++;
}
SetPhysicsMode(mode);
}
//Automatically simulate.
else
{
#if UNITY_EDITOR
float savedTime = PlayerPrefs.GetFloat(SAVED_FIXED_TIME_TEXT, float.MinValue);
if (savedTime != float.MinValue && Time.fixedDeltaTime != savedTime)
{
Debug.LogWarning("Time.fixedDeltaTime has been set back to user values.");
Time.fixedDeltaTime = savedTime;
}
PlayerPrefs.DeleteKey(SAVED_FIXED_TIME_TEXT);
#endif
SetPhysicsMode(mode);
}
}
/// <summary>
/// Updates physics based on which physics mode to use.
/// </summary>
/// <param name="enabled"></param>
public void SetPhysicsMode(PhysicsMode mode)
{
//Disable.
if (mode == PhysicsMode.Disabled || mode == PhysicsMode.TimeManager)
{
Physics.autoSimulation = false;
#if !UNITY_2020_2_OR_NEWER
Physics2D.autoSimulation = false;
#else
Physics2D.simulationMode = SimulationMode2D.Script;
#endif
}
//Automatically simulate.
else
{
Physics.autoSimulation = true;
#if !UNITY_2020_2_OR_NEWER
Physics2D.autoSimulation = true;
#else
Physics2D.simulationMode = SimulationMode2D.FixedUpdate;
#endif
}
}
#region PingPong.
/// <summary>
/// Modifies client ping based on LocalTick and clientTIck.
/// </summary>
/// <param name="clientTick"></param>
internal void ModifyPing(uint clientTick)
{
uint tickDifference = (LocalTick - clientTick);
_pingAverage.ComputeAverage(tickDifference);
double averageInTime = (_pingAverage.Average * TickDelta * 1000);
RoundTripTime = (long)Math.Round(averageInTime);
_receivedPong = true;
}
/// <summary>
/// Sends a ping to the server.
/// </summary>
private void TrySendPing(uint? tickOverride = null)
{
byte pingInterval = PingInterval;
/* How often client may send ping is based on if
* the server responded to the last ping.
* A response may not be received if the server
* believes the client is pinging too fast, or if the
* client is having difficulties reaching the server. */
long requiredTime = (pingInterval * 1000);
float multiplier = (_receivedPong) ? 1f : 1.5f;
requiredTime = (long)(requiredTime * multiplier);
uint requiredTicks = TimeToTicks(pingInterval * multiplier);
_pingTicks++;
/* We cannot just consider time because ticks might run slower
* from adjustments. We also cannot only consider ticks because
* they might run faster from adjustments. Therefor require both
* to have pass checks. */
if (_pingTicks < requiredTicks || _pingStopwatch.ElapsedMilliseconds < requiredTime)
return;
_pingTicks = 0;
_pingStopwatch.Restart();
//Unset receivedPong, wait for new response.
_receivedPong = false;
uint tick = (tickOverride == null) ? LocalTick : tickOverride.Value;
using (PooledWriter writer = WriterPool.GetWriter())
{
writer.WriteUInt16((ushort)PacketId.PingPong);
writer.WriteUInt32(tick, AutoPackType.Unpacked);
_networkManager.TransportManager.SendToServer((byte)Channel.Unreliable, writer.GetArraySegment());
}
}
/// <summary>
/// Sends a pong to a client.
/// </summary>
internal void SendPong(NetworkConnection conn, uint clientTick)
{
if (!conn.IsActive || !conn.Authenticated)
return;
using (PooledWriter writer = WriterPool.GetWriter())
{
writer.WriteUInt16((ushort)PacketId.PingPong);
writer.WriteUInt32(clientTick, AutoPackType.Unpacked);
conn.SendToClient((byte)Channel.Unreliable, writer.GetArraySegment());
}
}
#endregion
/// <summary>
/// Increases the tick based on simulation rate.
/// </summary>
private void IncreaseTick()
{
bool isClient = _networkManager.IsClient;
double timePerSimulation = (_networkManager.IsServer) ? TickDelta : _adjustedTickDelta;
double time = Time.unscaledDeltaTime;
_elapsedTickTime += time;
FrameTicked = (_elapsedTickTime >= timePerSimulation);
//Multiple ticks will occur this frame.
if (_elapsedTickTime > (timePerSimulation * 2d))
_lastMultipleTicks = Time.unscaledTime;
while (_elapsedTickTime >= timePerSimulation)
{
_elapsedTickTime -= timePerSimulation;
OnPreTick?.Invoke();
/* This has to be called inside the loop because
* OnPreTick promises data hasn't been read yet.
* Therefor iterate must occur after OnPreTick.
* Iteration will only run once per frame. */
TryIterateData(true);
OnTick?.Invoke();
if (PhysicsMode == PhysicsMode.TimeManager)
{
float tick = (float)TickDelta;
OnPhysicsSimulation?.Invoke(tick);
Physics.Simulate(tick);
Physics2D.Simulate(tick);
}
OnPostTick?.Invoke();
/* If isClient this is the
* last tick during this loop. */
if (isClient && (_elapsedTickTime < timePerSimulation))
TrySendPing(LocalTick + 1);
if (_networkManager.IsServer)
SendTimingAdjustment();
//Send out data.
TryIterateData(false);
if (_networkManager.IsClient)
_clientTicks++;
Tick++;
LocalTick++;
}
}
#region TicksToTime.
/// <summary>
/// Returns the percentage of how far the TimeManager is into the next tick.
/// </summary>
/// <returns></returns>
public double GetTickPercent()
{
if (_networkManager == null)
return default;
double delta = (_networkManager.IsServer) ? TickDelta : _adjustedTickDelta;
double percent = (_elapsedTickTime / delta) * 100d;
return percent;
}
/// <summary>
/// Returns a PreciseTick.
/// </summary>
/// <param name="tick">Tick to set within the returned PreciseTick.</param>
/// <returns></returns>
public PreciseTick GetPreciseTick(uint tick)
{
if (_networkManager == null)
return default;
double delta = (_networkManager.IsServer) ? TickDelta : _adjustedTickDelta;
double percent = (_elapsedTickTime / delta) * 100;
return new PreciseTick(tick, percent);
}
/// <summary>
/// Returns a PreciseTick.
/// </summary>
/// <param name="tickType">Tick to use within PreciseTick.</param>
/// <returns></returns>
public PreciseTick GetPreciseTick(TickType tickType)
{
if (_networkManager == null)
return default;
if (tickType == TickType.Tick)
{
return GetPreciseTick(Tick);
}
else if (tickType == TickType.LocalTick)
{
return GetPreciseTick(LocalTick);
}
else if (tickType == TickType.LastPacketTick)
{
return GetPreciseTick(LastPacketTick);
}
else
{
if (_networkManager.CanLog(LoggingType.Error))
Debug.LogError($"TickType {tickType.ToString()} is unhandled.");
return default;
}
}
/// <summary>
/// Converts current ticks to time.
/// </summary>
/// <param name="tickType">TickType to compare against.</param>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public double TicksToTime(TickType tickType = TickType.LocalTick)
{
if (tickType == TickType.LocalTick)
{
return TicksToTime(LocalTick);
}
else if (tickType == TickType.Tick)
{
return TicksToTime(Tick);
}
else if (tickType == TickType.LastPacketTick)
{
return TicksToTime(LastPacketTick);
}
else
{
if (_networkManager != null && _networkManager.CanLog(LoggingType.Error))
Debug.LogError($"TickType {tickType} is unhandled.");
return 0d;
}
}
/// <summary>
/// Converts current ticks to time.
/// </summary>
/// <param name="useLocalTick">True to use the LocalTick, false to use Tick.</param>
/// <returns></returns>
[Obsolete("Use TicksToTime(TickType) instead.")]
[MethodImpl(MethodImplOptions.AggressiveInlining)] //Remove on 2023/01/01
public double TicksToTime(bool useLocalTick = true)
{
if (useLocalTick)
return TicksToTime(LocalTick);
else
return TicksToTime(Tick);
}
/// <summary>
/// Converts a number ticks to time.
/// </summary>
/// <param name="ticks">Ticks to convert.</param>
/// <returns></returns>
public double TicksToTime(uint ticks)
{
return (TickDelta * (double)ticks);
}
/// <summary>
/// Converts time passed from currentTick to previous. Value will be negative if previousTick is larger than currentTick.
/// </summary>
/// <param name="currentTick">The current tick.</param>
/// <param name="previousTick">The previous tick.</param>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public double TicksToTime(uint currentTick, uint previousTick)
{
double multiplier;
double result;
if (currentTick >= previousTick)
{
multiplier = 1f;
result = TicksToTime(currentTick - previousTick);
}
else
{
multiplier = -1f;
result = TicksToTime(previousTick - currentTick);
}
return (result * multiplier);
}
/// <summary>
/// Gets time passed from Tick to preciseTick.
/// </summary>
/// <param name="preciseTick">PreciseTick value to compare against.</param>
/// <param name="allowNegative">True to allow negative values. When false and value would be negative 0 is returned.</param>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public double TimePassed(PreciseTick preciseTick, bool allowNegative = false)
{
PreciseTick currentPt = GetPreciseTick(TickType.Tick);
long tickDifference = (currentPt.Tick - preciseTick.Tick);
double percentDifference = (currentPt.Percent - preciseTick.Percent);
/* If tickDifference is less than 0 or tickDifference and percentDifference are 0 or less
* then the result would be negative. */
bool negativeValue = (tickDifference < 0 || (tickDifference <= 0 && percentDifference <= 0));
if (!allowNegative && negativeValue)
return 0d;
double tickTime = TimePassed(preciseTick.Tick, true);
double percent = (percentDifference / 100);
double percentTime = (percent * TickDelta);
return (tickTime + percentTime);
}
/// <summary>
/// Gets time passed from Tick to previousTick.
/// </summary>
/// <param name="previousTick">The previous tick.</param>
/// <param name="allowNegative">True to allow negative values. When false and value would be negative 0 is returned.</param>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public double TimePassed(uint previousTick, bool allowNegative = false)
{
uint currentTick = Tick;
//Difference will be positive.
if (currentTick >= previousTick)
{
return TicksToTime(currentTick - previousTick);
}
//Difference would be negative.
else
{
if (!allowNegative)
{
return 0d;
}
else
{
double difference = TicksToTime(previousTick - currentTick);
return (difference * -1d);
}
}
}
#endregion
[MethodImpl(MethodImplOptions.AggressiveInlining)]
/// <summary>
/// Converts time to ticks.
/// </summary>
/// <param name="time">Time to convert.</param>
/// <returns></returns>
public uint TimeToTicks(double time, TickRounding rounding = TickRounding.RoundNearest)
{
double result = (time / TickDelta);
if (rounding == TickRounding.RoundNearest)
return (uint)Math.Round(result);
else if (rounding == TickRounding.RoundDown)
return (uint)Math.Floor(result);
else
return (uint)Math.Ceiling(result);
}
/// <summary>
/// Tries to iterate incoming or outgoing data.
/// </summary>
/// <param name="incoming">True to iterate incoming.</param>
private void TryIterateData(bool incoming)
{
if (incoming)
{
/* It's not possible for data to come in
* more than once per frame but there could
* be new data going out each tick, since
* movement is often based off the tick system.
* Because of this don't iterate incoming if
* it's the same frame but the outgoing
* may iterate multiple times per frame. */
int frameCount = Time.frameCount;
if (frameCount == _lastIncomingIterationFrame)
return;
_lastIncomingIterationFrame = frameCount;
_networkManager.TransportManager.IterateIncoming(true);
_networkManager.TransportManager.IterateIncoming(false);
}
else
{
_networkManager.TransportManager.IterateOutgoing(true);
_networkManager.TransportManager.IterateOutgoing(false);
}
}
#region Timing adjusting.
/// <summary>
/// Sends a TimingUpdate packet to clients.
/// </summary>
private void SendTimingAdjustment()
{
uint requiredTicks = TimeToTicks(_timingInterval);
uint tick = Tick;
if (tick - _lastUpdateTicks >= requiredTicks)
{
//Now send using a packetId.
PooledWriter writer = WriterPool.GetWriter();
writer.WritePacketId(PacketId.TimingUpdate);
_networkManager.TransportManager.SendToClients((byte)Channel.Unreliable, writer.GetArraySegment());
writer.Dispose();
_lastUpdateTicks = tick;
}
}
/// <summary>
/// Called on client when server sends a timing update.
/// </summary>
/// <param name="ta"></param>
internal void ParseTimingUpdate()
{
//Don't adjust timing on server.
if (_networkManager.IsServer)
return;
//Add half of rtt onto tick.
uint rttTicks = TimeToTicks((RoundTripTime / 2) / 1000f);
Tick = LastPacketTick + rttTicks;
uint expected = (uint)(TickRate * _timingInterval);
long difference;
//If ticking too fast.
if (_clientTicks > expected)
difference = (long)(_clientTicks - expected);
//Not ticking fast enough.
else
difference = (long)((expected - _clientTicks) * -1);
//If difference is unusually off then reset timings.
if (Mathf.Abs(difference) >= RESET_STEPS_THRESHOLD)
{
_adjustedTickDelta = TickDelta;
}
else
{
sbyte steps = (sbyte)Mathf.Clamp(difference, sbyte.MinValue, sbyte.MaxValue);
double percent = (steps < 0) ? CLIENT_SPEEDUP_PERCENT : CLIENT_SLOWDOWN_PERCENT;
double change = (steps * (percent * TickDelta));
_adjustedTickDelta = MathFN.ClampDouble(_adjustedTickDelta + change, _clientTimingRange[0], _clientTimingRange[1]);
}
_clientTicks = 0;
}
#endregion
/// <summary>
/// Sets the TickRate to use. This value is not synchronized, it must be set on client and server independently.
/// </summary>
/// <param name="value">New TickRate to use.</param>
public void SetTickRate(ushort value)
{
TickRate = value;
TickDelta = (1d / TickRate);
_adjustedTickDelta = TickDelta;
_clientTimingRange = new double[]
{
TickDelta * (1f - CLIENT_TIMING_PERCENT_RANGE),
TickDelta * (1f + CLIENT_TIMING_PERCENT_RANGE)
};
}
#region UNITY_EDITOR
private void OnValidate()
{
SetInitialValues();
}
#endregion
}
}