mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 03:30:53 +01:00
Adds a debug net graph to the game state system.
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
using Robust.Client.Interfaces;
|
||||
using System;
|
||||
using Robust.Client.Interfaces;
|
||||
using Robust.Client.Interfaces.GameObjects;
|
||||
using Robust.Client.Interfaces.GameStates;
|
||||
using Robust.Shared.GameStates;
|
||||
@@ -35,6 +36,12 @@ namespace Robust.Client.GameStates
|
||||
/// <inheritdoc />
|
||||
public int TargetBufferSize => _processor.TargetBufferSize;
|
||||
|
||||
/// <inheritdoc />
|
||||
public int CurrentBufferSize => _processor.CurrentBufferSize;
|
||||
|
||||
/// <inheritdoc />
|
||||
public event Action<GameStateAppliedArgs> GameStateApplied;
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Initialize()
|
||||
{
|
||||
@@ -77,7 +84,7 @@ namespace Robust.Client.GameStates
|
||||
{
|
||||
var state = message.State;
|
||||
|
||||
_processor.AddNewState(state, message.MsgSize);
|
||||
_processor.AddNewState(state);
|
||||
|
||||
// we always ack everything we receive, even if it is late
|
||||
AckGameState(state.ToSequence);
|
||||
@@ -105,6 +112,18 @@ namespace Robust.Client.GameStates
|
||||
_entities.ApplyEntityStates(curState.EntityStates, curState.EntityDeletions, nextState?.EntityStates);
|
||||
_players.ApplyPlayerStates(curState.PlayerStates);
|
||||
_mapManager.ApplyGameStatePost(curState.MapData);
|
||||
|
||||
GameStateApplied?.Invoke(new GameStateAppliedArgs(curState));
|
||||
}
|
||||
}
|
||||
|
||||
public class GameStateAppliedArgs : EventArgs
|
||||
{
|
||||
public GameState AppliedState { get; }
|
||||
|
||||
public GameStateAppliedArgs(GameState appliedState)
|
||||
{
|
||||
AppliedState = appliedState;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ namespace Robust.Client.GameStates
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void AddNewState(GameState state, int stateSize)
|
||||
public void AddNewState(GameState state)
|
||||
{
|
||||
// any state from tick 0 is a full state, and needs to be handled different
|
||||
if (state.FromSequence == GameTick.Zero)
|
||||
@@ -64,7 +64,7 @@ namespace Robust.Client.GameStates
|
||||
_lastFullState = state;
|
||||
|
||||
if (Logging)
|
||||
Logger.InfoS("net", $"Received Full GameState: to={state.ToSequence}, sz={stateSize}");
|
||||
Logger.InfoS("net", $"Received Full GameState: to={state.ToSequence}, sz={state.PayloadSize}");
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -76,7 +76,7 @@ namespace Robust.Client.GameStates
|
||||
if (state.ToSequence <= lastTick && !_waitingForFull) // CurTick isn't set properly when WaitingForFull
|
||||
{
|
||||
if (Logging)
|
||||
Logger.DebugS("net.state", $"Received Old GameState: cTick={_timing.CurTick}, fSeq={state.FromSequence}, tSeq={state.ToSequence}, sz={stateSize}, buf={_stateBuffer.Count}");
|
||||
Logger.DebugS("net.state", $"Received Old GameState: cTick={_timing.CurTick}, fSeq={state.FromSequence}, tSeq={state.ToSequence}, sz={state.PayloadSize}, buf={_stateBuffer.Count}");
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -90,7 +90,7 @@ namespace Robust.Client.GameStates
|
||||
continue;
|
||||
|
||||
if (Logging)
|
||||
Logger.DebugS("net.state", $"Received Dupe GameState: cTick={_timing.CurTick}, fSeq={state.FromSequence}, tSeq={state.ToSequence}, sz={stateSize}, buf={_stateBuffer.Count}");
|
||||
Logger.DebugS("net.state", $"Received Dupe GameState: cTick={_timing.CurTick}, fSeq={state.FromSequence}, tSeq={state.ToSequence}, sz={state.PayloadSize}, buf={_stateBuffer.Count}");
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -99,7 +99,7 @@ namespace Robust.Client.GameStates
|
||||
_stateBuffer.Add(state);
|
||||
|
||||
if (Logging)
|
||||
Logger.DebugS("net.state", $"Received New GameState: cTick={_timing.CurTick}, fSeq={state.FromSequence}, tSeq={state.ToSequence}, sz={stateSize}, buf={_stateBuffer.Count}");
|
||||
Logger.DebugS("net.state", $"Received New GameState: cTick={_timing.CurTick}, fSeq={state.FromSequence}, tSeq={state.ToSequence}, sz={state.PayloadSize}, buf={_stateBuffer.Count}");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -56,8 +56,7 @@ namespace Robust.Client.GameStates
|
||||
/// Adds a new state into the processor. These are usually from networking or replays.
|
||||
/// </summary>
|
||||
/// <param name="state">Newly received state.</param>
|
||||
/// <param name="stateSize">Optionally provide the size in bytes of this new state. This is strictly for debug logging.</param>
|
||||
void AddNewState(GameState state, int stateSize);
|
||||
void AddNewState(GameState state);
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the current and next state to apply for a given game tick.
|
||||
|
||||
@@ -1,33 +1,159 @@
|
||||
using Robust.Client.Graphics;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Graphics.Drawing;
|
||||
using Robust.Client.Graphics.Overlays;
|
||||
using Robust.Client.Interfaces.Console;
|
||||
using Robust.Client.Interfaces.GameStates;
|
||||
using Robust.Client.Interfaces.Graphics.Overlays;
|
||||
using Robust.Client.Interfaces.ResourceManagement;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.Interfaces.Network;
|
||||
using Robust.Shared.Interfaces.Timing;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Robust.Client.GameStates
|
||||
{
|
||||
/// <summary>
|
||||
/// Visual debug overlay for the network diagnostic graph.
|
||||
/// </summary>
|
||||
internal class NetGraphOverlay : Overlay
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _gameTiming;
|
||||
[Dependency] private readonly IClientNetManager _netManager;
|
||||
[Dependency] private readonly IClientGameStateManager _gameStateManager;
|
||||
|
||||
private const int HistorySize = 60 * 3; // number of ticks to keep in history.
|
||||
private const int TargetPayloadBps = 56000 / 8; // Target Payload size in Bytes per second. A mind-numbing fifty-six thousand bits per second, who would ever need more?
|
||||
private const int MidrangePayloadBps = 33600 / 8; // mid-range line
|
||||
private const int BytesPerPixel = 2; // If you are running the game on a DSL connection, you can scale the graph to fit your absurd bandwidth.
|
||||
private const int LowerGraphOffset = 100; // Offset on the Y axis in pixels of the lower lag/interp graph.
|
||||
private const int MsPerPixel = 4; // Latency Milliseconds per pixel, for scaling the graph.
|
||||
|
||||
/// <inheritdoc />
|
||||
public override OverlaySpace Space => OverlaySpace.ScreenSpace;
|
||||
|
||||
private Font _font;
|
||||
private readonly Font _font;
|
||||
private GameTick _lastTick;
|
||||
private int _warningPayloadSize;
|
||||
private int _midrangePayloadSize;
|
||||
|
||||
private List<(GameTick Tick, int Payload, int lag, int interp)> _history = new List<(GameTick Tick, int Payload, int lag, int interp)>(HistorySize+10);
|
||||
|
||||
public NetGraphOverlay() : base(nameof(NetGraphOverlay))
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
var cache = IoCManager.Resolve<IResourceCache>();
|
||||
_font = new VectorFont(cache.GetResource<FontResource>("/Nano/NotoSans/NotoSans-Regular.ttf"), 10);
|
||||
|
||||
_gameStateManager.GameStateApplied += HandleGameStateApplied;
|
||||
}
|
||||
|
||||
private void HandleGameStateApplied(GameStateAppliedArgs args)
|
||||
{
|
||||
var toSeq = args.AppliedState.ToSequence;
|
||||
var sz = args.AppliedState.PayloadSize;
|
||||
|
||||
// calc payload size
|
||||
_warningPayloadSize = TargetPayloadBps / _gameTiming.TickRate;
|
||||
_midrangePayloadSize = MidrangePayloadBps / _gameTiming.TickRate;
|
||||
|
||||
// calc lag
|
||||
var lag = _netManager.ServerChannel.Ping;
|
||||
|
||||
// calc interp info
|
||||
var interpBuff = _gameStateManager.CurrentBufferSize - _gameStateManager.MinBufferSize;
|
||||
|
||||
_history.Add((toSeq, sz, lag, interpBuff));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
internal override void FrameUpdate(RenderFrameEventArgs args)
|
||||
{
|
||||
base.FrameUpdate(args);
|
||||
|
||||
var over = _history.Count - HistorySize;
|
||||
if (over > 0)
|
||||
{
|
||||
_history.RemoveRange(0, over);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Draw(DrawingHandleBase handle)
|
||||
{
|
||||
//TODO: Make me actually work!
|
||||
handle.DrawLine(new Vector2(50,50), new Vector2(100,100), Color.Green);
|
||||
DrawString((DrawingHandleScreen)handle, _font, new Vector2(60, 50), "Hello World!");
|
||||
// remember, 0,0 is top left of ui with +X right and +Y down
|
||||
|
||||
var leftMargin = 300;
|
||||
var width = HistorySize;
|
||||
var height = 500;
|
||||
|
||||
// bottom payload line
|
||||
handle.DrawLine(new Vector2(leftMargin, height), new Vector2(leftMargin + width, height), Color.DarkGray.WithAlpha(0.8f));
|
||||
|
||||
// bottom lag line
|
||||
handle.DrawLine(new Vector2(leftMargin, height + LowerGraphOffset), new Vector2(leftMargin + width, height + LowerGraphOffset), Color.DarkGray.WithAlpha(0.8f));
|
||||
|
||||
int lastLagY = -1;
|
||||
int lastLagMs = -1;
|
||||
// data points
|
||||
for (var i = 0; i < _history.Count; i++)
|
||||
{
|
||||
var state = _history[i];
|
||||
|
||||
// draw the payload size
|
||||
var xOff = leftMargin + i;
|
||||
var yoff = height - state.Payload / BytesPerPixel;
|
||||
handle.DrawLine(new Vector2(xOff, height), new Vector2(xOff, yoff), Color.LightGreen.WithAlpha(0.8f));
|
||||
|
||||
// second tick marks
|
||||
if (state.Tick.Value % _gameTiming.TickRate == 0)
|
||||
{
|
||||
handle.DrawLine(new Vector2(xOff, height), new Vector2(xOff, height+2), Color.LightGray);
|
||||
}
|
||||
|
||||
// lag data
|
||||
var lagYoff = height + LowerGraphOffset - state.lag / MsPerPixel;
|
||||
lastLagY = lagYoff - 1;
|
||||
lastLagMs = state.lag;
|
||||
handle.DrawLine(new Vector2(xOff, lagYoff - 2), new Vector2(xOff, lagYoff - 1), Color.Blue.WithAlpha(0.8f));
|
||||
|
||||
// interp data
|
||||
Color interpColor;
|
||||
if(state.interp < 0)
|
||||
interpColor = Color.Red;
|
||||
else if(state.interp < _gameStateManager.TargetBufferSize - _gameStateManager.MinBufferSize)
|
||||
interpColor = Color.Yellow;
|
||||
else
|
||||
interpColor = Color.Green;
|
||||
|
||||
handle.DrawLine(new Vector2(xOff, height + LowerGraphOffset), new Vector2(xOff, height + LowerGraphOffset + state.interp * 6), interpColor.WithAlpha(0.8f));
|
||||
}
|
||||
|
||||
// top payload warning line
|
||||
var warnYoff = height - _warningPayloadSize / BytesPerPixel;
|
||||
handle.DrawLine(new Vector2(leftMargin, warnYoff), new Vector2(leftMargin + width, warnYoff), Color.DarkGray.WithAlpha(0.8f));
|
||||
|
||||
// mid payload line
|
||||
var midYoff = height - _midrangePayloadSize / BytesPerPixel;
|
||||
handle.DrawLine(new Vector2(leftMargin, midYoff), new Vector2(leftMargin + width, midYoff), Color.DarkGray.WithAlpha(0.8f));
|
||||
|
||||
// payload text
|
||||
DrawString((DrawingHandleScreen)handle, _font, new Vector2(leftMargin + width, warnYoff), "56K");
|
||||
DrawString((DrawingHandleScreen)handle, _font, new Vector2(leftMargin + width, midYoff), "33.6K");
|
||||
|
||||
// interp text info
|
||||
if(lastLagY != -1)
|
||||
DrawString((DrawingHandleScreen)handle, _font, new Vector2(leftMargin + width, lastLagY), $"{lastLagMs.ToString()}ms");
|
||||
|
||||
DrawString((DrawingHandleScreen)handle, _font, new Vector2(leftMargin, height + LowerGraphOffset), $"{_gameStateManager.CurrentBufferSize.ToString()} states");
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
_gameStateManager.GameStateApplied -= HandleGameStateApplied;
|
||||
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
private void DrawString(DrawingHandleScreen handle, Font font, Vector2 pos, string str)
|
||||
|
||||
@@ -75,7 +75,7 @@ namespace Robust.Client.Graphics.Overlays
|
||||
_isDirty = true;
|
||||
}
|
||||
|
||||
internal void FrameUpdate(RenderFrameEventArgs args)
|
||||
internal virtual void FrameUpdate(RenderFrameEventArgs args)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
namespace Robust.Client.Interfaces.GameStates
|
||||
using System;
|
||||
using Robust.Client.GameStates;
|
||||
|
||||
namespace Robust.Client.Interfaces.GameStates
|
||||
{
|
||||
/// <summary>
|
||||
/// Engine service that provides processing and management of game states.
|
||||
@@ -20,6 +23,16 @@
|
||||
/// </summary>
|
||||
int TargetBufferSize { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Number of game states currently in the state buffer.
|
||||
/// </summary>
|
||||
int CurrentBufferSize { get; }
|
||||
|
||||
/// <summary>
|
||||
/// This is called after the game state has been applied for the current tick.
|
||||
/// </summary>
|
||||
event Action<GameStateAppliedArgs> GameStateApplied;
|
||||
|
||||
/// <summary>
|
||||
/// One time initialization of the service.
|
||||
/// </summary>
|
||||
|
||||
@@ -16,6 +16,12 @@ namespace Robust.Shared.GameStates
|
||||
[field:NonSerialized]
|
||||
public bool Extrapolated { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The serialized size in bytes of this game state.
|
||||
/// </summary>
|
||||
[field:NonSerialized]
|
||||
public int PayloadSize { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructor!
|
||||
/// </summary>
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
using Lidgren.Network;
|
||||
using System;
|
||||
using Lidgren.Network;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Interfaces.Network;
|
||||
using Robust.Shared.Interfaces.Serialization;
|
||||
using Robust.Shared.IoC;
|
||||
using System.IO;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.Network.Messages
|
||||
{
|
||||
@@ -20,13 +22,15 @@ namespace Robust.Shared.Network.Messages
|
||||
public override void ReadFromBuffer(NetIncomingMessage buffer)
|
||||
{
|
||||
MsgSize = buffer.LengthBytes;
|
||||
var length = buffer.ReadInt32();
|
||||
var length = buffer.ReadVariableInt32();
|
||||
var stateData = buffer.ReadBytes(length);
|
||||
using (var stateStream = new MemoryStream(stateData))
|
||||
{
|
||||
var serializer = IoCManager.Resolve<IRobustSerializer>();
|
||||
State = serializer.Deserialize<GameState>(stateStream);
|
||||
}
|
||||
|
||||
State.PayloadSize = length;
|
||||
}
|
||||
|
||||
public override void WriteToBuffer(NetOutgoingMessage buffer)
|
||||
@@ -34,8 +38,9 @@ namespace Robust.Shared.Network.Messages
|
||||
var serializer = IoCManager.Resolve<IRobustSerializer>();
|
||||
using (var stateStream = new MemoryStream())
|
||||
{
|
||||
DebugTools.Assert(stateStream.Length <= Int32.MaxValue);
|
||||
serializer.Serialize(stateStream, State);
|
||||
buffer.Write((int)stateStream.Length);
|
||||
buffer.WriteVariableInt32((int)stateStream.Length);
|
||||
buffer.Write(stateStream.ToArray());
|
||||
}
|
||||
MsgSize = buffer.LengthBytes;
|
||||
|
||||
@@ -307,6 +307,9 @@ namespace Robust.Shared.Network
|
||||
{
|
||||
var netConfig = new NetPeerConfiguration("SS14_NetTag");
|
||||
|
||||
// ping the client 4 times every second.
|
||||
netConfig.PingInterval = 0.25f;
|
||||
|
||||
#if DEBUG
|
||||
//Simulate Latency
|
||||
netConfig.SimulatedLoss = _config.GetCVar<float>("net.fakeloss");
|
||||
|
||||
@@ -19,8 +19,8 @@ namespace Robust.UnitTesting.Client.GameStates
|
||||
var timing = timingMock.Object;
|
||||
var processor = new GameStateProcessor(timing);
|
||||
|
||||
processor.AddNewState(GameStateFactory(0, 1), 0);
|
||||
processor.AddNewState(GameStateFactory(1, 2), 0); // buffer is at 2/3, so processing should be blocked
|
||||
processor.AddNewState(GameStateFactory(0, 1));
|
||||
processor.AddNewState(GameStateFactory(1, 2)); // buffer is at 2/3, so processing should be blocked
|
||||
|
||||
// calculate states for first tick
|
||||
timing.CurTick = new GameTick(3);
|
||||
@@ -39,9 +39,9 @@ namespace Robust.UnitTesting.Client.GameStates
|
||||
var timing = timingMock.Object;
|
||||
var processor = new GameStateProcessor(timing);
|
||||
|
||||
processor.AddNewState(GameStateFactory(0, 1), 0);
|
||||
processor.AddNewState(GameStateFactory(1, 2), 0);
|
||||
processor.AddNewState(GameStateFactory(2, 3), 0); // buffer is now full, otherwise cannot calculate states.
|
||||
processor.AddNewState(GameStateFactory(0, 1));
|
||||
processor.AddNewState(GameStateFactory(1, 2));
|
||||
processor.AddNewState(GameStateFactory(2, 3)); // buffer is now full, otherwise cannot calculate states.
|
||||
|
||||
// calculate states for first tick
|
||||
timing.CurTick = new GameTick(1);
|
||||
@@ -67,9 +67,9 @@ namespace Robust.UnitTesting.Client.GameStates
|
||||
var timing = timingMock.Object;
|
||||
var processor = new GameStateProcessor(timing);
|
||||
|
||||
processor.AddNewState(GameStateFactory(0, 1), 0);
|
||||
processor.AddNewState(GameStateFactory(1, 2), 0);
|
||||
processor.AddNewState(GameStateFactory(2, 3), 0); // buffer is now full, otherwise cannot calculate states.
|
||||
processor.AddNewState(GameStateFactory(0, 1));
|
||||
processor.AddNewState(GameStateFactory(1, 2));
|
||||
processor.AddNewState(GameStateFactory(2, 3)); // buffer is now full, otherwise cannot calculate states.
|
||||
|
||||
// calculate states for first tick
|
||||
timing.CurTick = new GameTick(3);
|
||||
@@ -87,7 +87,7 @@ namespace Robust.UnitTesting.Client.GameStates
|
||||
|
||||
// a few moments later...
|
||||
timing.CurTick = new GameTick(5); // current clock is ahead of server
|
||||
processor.AddNewState(GameStateFactory(3, 4), 0); // received a late state
|
||||
processor.AddNewState(GameStateFactory(3, 4)); // received a late state
|
||||
var result = processor.ProcessTickStates(timing.CurTick, out _, out _);
|
||||
|
||||
Assert.That(result, Is.False);
|
||||
@@ -174,9 +174,9 @@ namespace Robust.UnitTesting.Client.GameStates
|
||||
var timing = timingMock.Object;
|
||||
var processor = new GameStateProcessor(timing);
|
||||
|
||||
processor.AddNewState(GameStateFactory(0, 1), 0);
|
||||
processor.AddNewState(GameStateFactory(1, 2), 0);
|
||||
processor.AddNewState(GameStateFactory(2, 3), 0); // buffer is now full, otherwise cannot calculate states.
|
||||
processor.AddNewState(GameStateFactory(0, 1));
|
||||
processor.AddNewState(GameStateFactory(1, 2));
|
||||
processor.AddNewState(GameStateFactory(2, 3)); // buffer is now full, otherwise cannot calculate states.
|
||||
|
||||
// calculate states for first tick
|
||||
timing.CurTick = new GameTick(1);
|
||||
|
||||
Reference in New Issue
Block a user