From 6973d4300632a892762547f6dd7117b560d21ada Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Tue, 9 Feb 2021 14:28:04 +0100 Subject: [PATCH] Network timing synchronization. Lidgren NetTime.Now is now synchronized to IGameTiming.RealTime. NetChannel is now a nested class of NetManager. Add IGameTiming.ServerTime, INetChannel.RemoteTimeOffset, INetChannel.RemoteTime --- Lidgren.Network/Lidgren.Network | 2 +- .../CustomControls/DebugTimePanel.cs | 2 +- .../Interfaces/Network/INetChannel.cs | 13 ++- .../Interfaces/Timing/IGameTiming.cs | 9 ++ Robust.Shared/Network/NetChannel.cs | 84 ------------------ .../Network/NetManager.NetChannel.cs | 87 +++++++++++++++++++ Robust.Shared/Network/NetManager.cs | 22 +++++ Robust.Shared/Timing/GameTiming.cs | 25 +++++- .../RobustIntegrationTest.NetManager.cs | 5 ++ 9 files changed, 160 insertions(+), 89 deletions(-) delete mode 100644 Robust.Shared/Network/NetChannel.cs create mode 100644 Robust.Shared/Network/NetManager.NetChannel.cs diff --git a/Lidgren.Network/Lidgren.Network b/Lidgren.Network/Lidgren.Network index 4a5cedacb2f..73554e60616 160000 --- a/Lidgren.Network/Lidgren.Network +++ b/Lidgren.Network/Lidgren.Network @@ -1 +1 @@ -Subproject commit 4a5cedacb2f58e17e062ea758cb284bbc13e1358 +Subproject commit 73554e60616667c1e6f402237e4172756a1085d4 diff --git a/Robust.Client/UserInterface/CustomControls/DebugTimePanel.cs b/Robust.Client/UserInterface/CustomControls/DebugTimePanel.cs index e6a72996801..c61561d53a6 100644 --- a/Robust.Client/UserInterface/CustomControls/DebugTimePanel.cs +++ b/Robust.Client/UserInterface/CustomControls/DebugTimePanel.cs @@ -52,7 +52,7 @@ protected override void Update(FrameEventArgs args) _contents.Text = $@"Paused: {_gameTiming.Paused}, CurTick: {_gameTiming.CurTick}/{_gameTiming.CurTick-1}, CurServerTick: {_gameState.CurServerTick}, Pred: {_gameTiming.CurTick.Value - _gameState.CurServerTick.Value-1} CurTime: {_gameTiming.CurTime:hh\:mm\:ss\.ff}, RealTime: {_gameTiming.RealTime:hh\:mm\:ss\.ff}, CurFrame: {_gameTiming.CurFrame} -TickTimingAdjustment: {_gameTiming.TickTimingAdjustment}"; +ServerTime: {_gameTiming.ServerTime}, TickTimingAdjustment: {_gameTiming.TickTimingAdjustment}"; MinimumSizeChanged(); } diff --git a/Robust.Shared/Interfaces/Network/INetChannel.cs b/Robust.Shared/Interfaces/Network/INetChannel.cs index 317ac179244..72c3c5e8a8c 100644 --- a/Robust.Shared/Interfaces/Network/INetChannel.cs +++ b/Robust.Shared/Interfaces/Network/INetChannel.cs @@ -1,4 +1,5 @@ -using System.Net; +using System; +using System.Net; using Robust.Shared.Network; namespace Robust.Shared.Interfaces.Network @@ -34,6 +35,16 @@ public interface INetChannel LoginType AuthType { get; } + /// + /// Offset between local RealTime and remote RealTime. + /// + TimeSpan RemoteTimeOffset { get; } + + /// + /// Remote RealTime. + /// + TimeSpan RemoteTime { get; } + /// /// Average round trip time in milliseconds between the remote peer and us. /// diff --git a/Robust.Shared/Interfaces/Timing/IGameTiming.cs b/Robust.Shared/Interfaces/Timing/IGameTiming.cs index e31503efea5..813af2219fd 100644 --- a/Robust.Shared/Interfaces/Timing/IGameTiming.cs +++ b/Robust.Shared/Interfaces/Timing/IGameTiming.cs @@ -31,6 +31,15 @@ public interface IGameTiming /// TimeSpan RealTime { get; } + /// + /// The of the server. + /// + /// + /// 0 if we are the client and we are not connected to a server. + /// if we are the server. + /// + TimeSpan ServerTime { get; } + /// /// The simulated time it took to render the last frame. /// diff --git a/Robust.Shared/Network/NetChannel.cs b/Robust.Shared/Network/NetChannel.cs deleted file mode 100644 index bd0718d6298..00000000000 --- a/Robust.Shared/Network/NetChannel.cs +++ /dev/null @@ -1,84 +0,0 @@ -using System; -using System.Net; -using Lidgren.Network; -using Robust.Shared.Interfaces.Network; - -namespace Robust.Shared.Network -{ - /// - /// A network connection from this local peer to a remote peer. - /// - internal class NetChannel : INetChannel - { - private readonly NetManager _manager; - private readonly NetConnection _connection; - - /// - public long ConnectionId => _connection.RemoteUniqueIdentifier; - - /// - public INetManager NetPeer => _manager; - - public string UserName { get; } - public LoginType AuthType { get; } - - /// - public short Ping => (short) Math.Round(_connection.AverageRoundtripTime * 1000); - - /// - public bool IsConnected => _connection.Status == NetConnectionStatus.Connected; - - /// - public IPEndPoint RemoteEndPoint => _connection.RemoteEndPoint; - - /// - /// Exposes the lidgren connection. - /// - public NetConnection Connection => _connection; - - public NetUserId UserId { get; } - - // Only used on server, contains the encryption to use for this channel. - public NetEncryption? Encryption { get; set; } - - /// - /// Creates a new instance of a NetChannel. - /// - /// The server this channel belongs to. - /// The raw NetConnection to the remote peer. - internal NetChannel(NetManager manager, NetConnection connection, NetUserId userId, string userName, LoginType loginType) - { - _manager = manager; - _connection = connection; - UserId = userId; - UserName = userName; - AuthType = loginType; - } - - /// - public T CreateNetMessage() - where T : NetMessage - { - return _manager.CreateNetMessage(); - } - - /// - public void SendMessage(NetMessage message) - { - if (_manager.IsClient) - { - _manager.ClientSendMessage(message); - return; - } - - _manager.ServerSendMessage(message, this); - } - - /// - public void Disconnect(string reason) - { - if (_connection.Status == NetConnectionStatus.Connected) - _connection.Disconnect(reason); - } - } -} diff --git a/Robust.Shared/Network/NetManager.NetChannel.cs b/Robust.Shared/Network/NetManager.NetChannel.cs new file mode 100644 index 00000000000..dc31f0fa61c --- /dev/null +++ b/Robust.Shared/Network/NetManager.NetChannel.cs @@ -0,0 +1,87 @@ +using System; +using System.Net; +using Lidgren.Network; +using Robust.Shared.Interfaces.Network; + +namespace Robust.Shared.Network +{ + public partial class NetManager + { + private class NetChannel : INetChannel + { + private readonly NetManager _manager; + private readonly NetConnection _connection; + + /// + public long ConnectionId => _connection.RemoteUniqueIdentifier; + + /// + public INetManager NetPeer => _manager; + + public string UserName { get; } + public LoginType AuthType { get; } + public TimeSpan RemoteTimeOffset => TimeSpan.FromSeconds(_connection.RemoteTimeOffset); + public TimeSpan RemoteTime => _manager._timing.RealTime + RemoteTimeOffset; + + /// + public short Ping => (short) Math.Round(_connection.AverageRoundtripTime * 1000); + + /// + public bool IsConnected => _connection.Status == NetConnectionStatus.Connected; + + /// + public IPEndPoint RemoteEndPoint => _connection.RemoteEndPoint; + + /// + /// Exposes the lidgren connection. + /// + public NetConnection Connection => _connection; + + public NetUserId UserId { get; } + + // Only used on server, contains the encryption to use for this channel. + public NetEncryption? Encryption { get; set; } + + /// + /// Creates a new instance of a NetChannel. + /// + /// The server this channel belongs to. + /// The raw NetConnection to the remote peer. + internal NetChannel(NetManager manager, NetConnection connection, NetUserId userId, string userName, + LoginType loginType) + { + _manager = manager; + _connection = connection; + UserId = userId; + UserName = userName; + AuthType = loginType; + } + + /// + public T CreateNetMessage() + where T : NetMessage + { + return _manager.CreateNetMessage(); + } + + /// + public void SendMessage(NetMessage message) + { + if (_manager.IsClient) + { + _manager.ClientSendMessage(message); + return; + } + + _manager.ServerSendMessage(message, this); + } + + /// + public void Disconnect(string reason) + { + if (_connection.Status == NetConnectionStatus.Connected) + _connection.Disconnect(reason); + } + } + } +} diff --git a/Robust.Shared/Network/NetManager.cs b/Robust.Shared/Network/NetManager.cs index 32298e1f4e4..34bbb769a6a 100644 --- a/Robust.Shared/Network/NetManager.cs +++ b/Robust.Shared/Network/NetManager.cs @@ -14,6 +14,7 @@ using Robust.Shared.Interfaces.Configuration; using Robust.Shared.Interfaces.Network; using Robust.Shared.Interfaces.Serialization; +using Robust.Shared.Interfaces.Timing; using Robust.Shared.IoC; using Robust.Shared.Log; using Robust.Shared.Utility; @@ -112,6 +113,7 @@ public partial class NetManager : IClientNetManager, IServerNetManager, IDisposa [Dependency] private readonly IConfigurationManagerInternal _config = default!; [Dependency] private readonly IAuthManager _authManager = default!; + [Dependency] private readonly IGameTiming _timing = default!; /// /// Holds lookup table for NetMessage.Id -> NetMessage.Type @@ -235,6 +237,8 @@ public void Initialize(bool isServer) throw new InvalidOperationException("NetManager has already been initialized."); } + SynchronizeNetTime(); + IsServer = isServer; _config.OnValueChanged(CVars.NetVerbose, NetVerboseChanged); @@ -265,6 +269,24 @@ public void Initialize(bool isServer) } } + private void SynchronizeNetTime() + { + // Synchronize Lidgren NetTime with our RealTime. + + for (var i = 0; i < 10; i++) + { + // Try and set this in a loop to avoid any JIT hang fuckery or similar. + // Loop until the time is within acceptable margin. + // Fixing this "properly" would basically require re-architecturing Lidgren to do DI stuff + // so we can more sanely wire these together. + NetTime.SetNow(_timing.RealTime.TotalSeconds); + var dev = TimeSpan.FromSeconds(NetTime.Now) - _timing.RealTime; + + if (Math.Abs(dev.TotalMilliseconds) < 0.05) + break; + } + } + private void UpdateNetMessageFunctions(MsgStringTableEntries.Entry[] entries) { foreach (var entry in entries) diff --git a/Robust.Shared/Timing/GameTiming.cs b/Robust.Shared/Timing/GameTiming.cs index 4bb2a2dd4e5..dcbe38a83e0 100644 --- a/Robust.Shared/Timing/GameTiming.cs +++ b/Robust.Shared/Timing/GameTiming.cs @@ -1,10 +1,10 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; +using Robust.Shared.Interfaces.Network; using Robust.Shared.Interfaces.Timing; +using Robust.Shared.IoC; using Robust.Shared.Utility; -using Robust.Shared.Log; namespace Robust.Shared.Timing { @@ -20,6 +20,8 @@ public class GameTiming : IGameTiming private readonly List _realFrameTimes = new(NumFrames); private TimeSpan _lastRealTime; + [Dependency] private readonly INetManager _netManager = default!; + /// /// Default constructor. /// @@ -87,6 +89,25 @@ public TimeSpan CurTime /// public TimeSpan RealTime => _realTimer.Elapsed; + public TimeSpan ServerTime + { + get + { + if (_netManager.IsServer) + { + return RealTime; + } + + var clientNetManager = (IClientNetManager) _netManager; + if (clientNetManager.ServerChannel == null) + { + return TimeSpan.Zero; + } + + return clientNetManager.ServerChannel.RemoteTime; + } + } + /// /// The simulated time it took to render the last frame. /// diff --git a/Robust.UnitTesting/RobustIntegrationTest.NetManager.cs b/Robust.UnitTesting/RobustIntegrationTest.NetManager.cs index 4beb90b2f3c..e0df545eef0 100644 --- a/Robust.UnitTesting/RobustIntegrationTest.NetManager.cs +++ b/Robust.UnitTesting/RobustIntegrationTest.NetManager.cs @@ -5,6 +5,8 @@ using System.Threading.Channels; using System.Threading.Tasks; using Robust.Shared.Interfaces.Network; +using Robust.Shared.Interfaces.Timing; +using Robust.Shared.IoC; using Robust.Shared.Network; using Robust.Shared.Utility; @@ -14,6 +16,7 @@ public partial class RobustIntegrationTest { internal sealed class IntegrationNetManager : IClientNetManager, IServerNetManager { + [Dependency] private readonly IGameTiming _gameTiming = default!; public bool IsServer { get; private set; } public bool IsClient => !IsServer; public bool IsRunning { get; private set; } @@ -371,6 +374,8 @@ private sealed class IntegrationNetChannel : INetChannel public NetUserId UserId { get; } public string UserName { get; } public LoginType AuthType => LoginType.GuestAssigned; + public TimeSpan RemoteTimeOffset => TimeSpan.Zero; // TODO: Fix this + public TimeSpan RemoteTime => _owner._gameTiming.RealTime + RemoteTimeOffset; public short Ping => default; public IntegrationNetChannel(IntegrationNetManager owner, ChannelWriter otherChannel, int uid,