mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 03:30:53 +01:00
* Adds barbones culling. * Visibility culling and recursive parent ent additions. DebugEntityNetView improvements. Visibility moved from session to eyecomponent. * Multiple viewport support. * Perf improvements. * Removed old netbubble system from ServerEntityManager. Supports old NaN system for entities leaving view. Supports old SendFullMap optimization for anchored, non-updating Entities. * Fixes size of netView box. * Remove empty EntityManager.Update method. Switching ViewCulling back to PLINQ.
322 lines
13 KiB
C#
322 lines
13 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Text;
|
|
using Robust.Client.Graphics;
|
|
using Robust.Client.ResourceManagement;
|
|
using Robust.Shared.Enums;
|
|
using Robust.Shared.Console;
|
|
using Robust.Shared.GameObjects;
|
|
using Robust.Shared.IoC;
|
|
using Robust.Shared.Maths;
|
|
using Robust.Shared.Network;
|
|
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 = default!;
|
|
[Dependency] private readonly IClientNetManager _netManager = default!;
|
|
[Dependency] private readonly IClientGameStateManager _gameStateManager = default!;
|
|
|
|
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 readonly Font _font;
|
|
private int _warningPayloadSize;
|
|
private int _midrangePayloadSize;
|
|
|
|
private readonly List<(GameTick Tick, int Payload, int lag, int interp)> _history = new(HistorySize+10);
|
|
|
|
private int _totalHistoryPayload; // sum of all data point sizes in bytes
|
|
|
|
public EntityUid WatchEntId { get; set; }
|
|
|
|
public NetGraphOverlay()
|
|
{
|
|
IoCManager.InjectDependencies(this);
|
|
var cache = IoCManager.Resolve<IResourceCache>();
|
|
_font = new VectorFont(cache.GetResource<FontResource>("/Fonts/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;
|
|
|
|
_totalHistoryPayload += sz;
|
|
_history.Add((toSeq, sz, lag, interpBuff));
|
|
|
|
// not watching an ent
|
|
if(!WatchEntId.IsValid() || WatchEntId.IsClientSide())
|
|
return;
|
|
|
|
string? entStateString = null;
|
|
string? entDelString = null;
|
|
var conShell = IoCManager.Resolve<IConsoleHost>().LocalShell;
|
|
|
|
var entStates = args.AppliedState.EntityStates;
|
|
if (entStates is not null)
|
|
{
|
|
var sb = new StringBuilder();
|
|
foreach (var entState in entStates)
|
|
{
|
|
if (entState.Uid == WatchEntId)
|
|
{
|
|
if(entState.ComponentChanges is not null)
|
|
{
|
|
sb.Append($"\n Changes:");
|
|
foreach (var compChange in entState.ComponentChanges)
|
|
{
|
|
var del = compChange.Deleted ? 'D' : 'C';
|
|
sb.Append($"\n [{del}]{compChange.NetID}:{compChange.ComponentName}");
|
|
}
|
|
}
|
|
|
|
if (entState.ComponentStates is not null)
|
|
{
|
|
sb.Append($"\n States:");
|
|
foreach (var compState in entState.ComponentStates)
|
|
{
|
|
sb.Append($"\n {compState.NetID}:{compState.GetType().Name}");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
entStateString = sb.ToString();
|
|
}
|
|
|
|
var entDeletes = args.AppliedState.EntityDeletions;
|
|
if (entDeletes is not null)
|
|
{
|
|
var sb = new StringBuilder();
|
|
foreach (var entDelete in entDeletes)
|
|
{
|
|
if (entDelete == WatchEntId)
|
|
{
|
|
entDelString = "\n Deleted";
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!string.IsNullOrWhiteSpace(entStateString) || !string.IsNullOrWhiteSpace(entDelString))
|
|
{
|
|
var fullString = $"watchEnt: from={args.AppliedState.FromSequence}, to={args.AppliedState.ToSequence}, eid={WatchEntId}";
|
|
if (!string.IsNullOrWhiteSpace(entStateString))
|
|
fullString += entStateString;
|
|
|
|
if (!string.IsNullOrWhiteSpace(entDelString))
|
|
fullString += entDelString;
|
|
|
|
conShell.WriteLine(fullString + "\n");
|
|
}
|
|
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
protected internal override void FrameUpdate(FrameEventArgs args)
|
|
{
|
|
base.FrameUpdate(args);
|
|
|
|
var over = _history.Count - HistorySize;
|
|
if (over <= 0)
|
|
return;
|
|
|
|
for (int i = 0; i < over; i++)
|
|
{
|
|
var point = _history[i];
|
|
_totalHistoryPayload -= point.Payload;
|
|
}
|
|
|
|
_history.RemoveRange(0, over);
|
|
}
|
|
|
|
protected override void Draw(DrawingHandleBase handle, OverlaySpace currentSpace)
|
|
{
|
|
// remember, 0,0 is top left of ui with +X right and +Y down
|
|
|
|
var leftMargin = 300;
|
|
var width = HistorySize;
|
|
var height = 500;
|
|
var drawSizeThreshold = Math.Min(_totalHistoryPayload / HistorySize, 300);
|
|
|
|
// 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));
|
|
|
|
// Draw size if above average
|
|
if (drawSizeThreshold * 1.5 < state.Payload)
|
|
{
|
|
DrawString((DrawingHandleScreen) handle, _font, new Vector2(xOff, yoff - _font.GetLineHeight(1)), state.Payload.ToString());
|
|
}
|
|
|
|
// 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));
|
|
}
|
|
|
|
// average payload line
|
|
var avgyoff = height - drawSizeThreshold / BytesPerPixel;
|
|
handle.DrawLine(new Vector2(leftMargin, avgyoff), new Vector2(leftMargin + width, avgyoff), Color.DarkGray.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 DisposeBehavior()
|
|
{
|
|
_gameStateManager.GameStateApplied -= HandleGameStateApplied;
|
|
|
|
base.DisposeBehavior();
|
|
}
|
|
|
|
private void DrawString(DrawingHandleScreen handle, Font font, Vector2 pos, string str)
|
|
{
|
|
var baseLine = new Vector2(pos.X, font.GetAscent(1) + pos.Y);
|
|
|
|
foreach (var rune in str.EnumerateRunes())
|
|
{
|
|
var advance = font.DrawChar(handle, rune, baseLine, 1, Color.White);
|
|
baseLine += new Vector2(advance, 0);
|
|
}
|
|
}
|
|
|
|
private class NetShowGraphCommand : IConsoleCommand
|
|
{
|
|
public string Command => "net_graph";
|
|
public string Help => "net_graph <0|1>";
|
|
public string Description => "Toggles the net statistics pannel.";
|
|
|
|
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
|
{
|
|
if (args.Length != 1)
|
|
{
|
|
shell.WriteError("Invalid argument amount. Expected 2 arguments.");
|
|
return;
|
|
}
|
|
|
|
if (!byte.TryParse(args[0], out var iValue))
|
|
{
|
|
shell.WriteLine("Invalid argument: Needs to be 0 or 1.");
|
|
return;
|
|
}
|
|
|
|
var bValue = iValue > 0;
|
|
var overlayMan = IoCManager.Resolve<IOverlayManager>();
|
|
|
|
if(bValue && !overlayMan.HasOverlay(typeof(NetGraphOverlay)))
|
|
{
|
|
overlayMan.AddOverlay(new NetGraphOverlay());
|
|
shell.WriteLine("Enabled network overlay.");
|
|
}
|
|
else if(overlayMan.HasOverlay(typeof(NetGraphOverlay)))
|
|
{
|
|
overlayMan.RemoveOverlay(typeof(NetGraphOverlay));
|
|
shell.WriteLine("Disabled network overlay.");
|
|
}
|
|
}
|
|
}
|
|
|
|
private class NetWatchEntCommand : IConsoleCommand
|
|
{
|
|
public string Command => "net_watchent";
|
|
public string Help => "net_watchent <0|EntityUid>";
|
|
public string Description => "Dumps all network updates for an EntityId to the console.";
|
|
|
|
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
|
{
|
|
if (args.Length != 1)
|
|
{
|
|
shell.WriteError("Invalid argument amount. Expected 1 argument.");
|
|
return;
|
|
}
|
|
|
|
if (!EntityUid.TryParse(args[0], out var eValue))
|
|
{
|
|
shell.WriteError("Invalid argument: Needs to be 0 or an entityId.");
|
|
return;
|
|
}
|
|
|
|
var overlayMan = IoCManager.Resolve<IOverlayManager>();
|
|
|
|
if (overlayMan.HasOverlay(typeof(NetGraphOverlay)))
|
|
{
|
|
var netOverlay = overlayMan.GetOverlay<NetGraphOverlay>();
|
|
|
|
netOverlay.WatchEntId = eValue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|