mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 03:30:53 +01:00
Adds the net_entityreport console command, that displays a network history of entity updates.
This commit is contained in:
262
Robust.Client/GameStates/NetEntityOverlay.cs
Normal file
262
Robust.Client/GameStates/NetEntityOverlay.cs
Normal file
@@ -0,0 +1,262 @@
|
||||
using System.Collections.Generic;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Graphics.Drawing;
|
||||
using Robust.Client.Graphics.Overlays;
|
||||
using Robust.Client.Interfaces.GameStates;
|
||||
using Robust.Client.Interfaces.Graphics.ClientEye;
|
||||
using Robust.Client.Interfaces.Graphics.Overlays;
|
||||
using Robust.Client.Interfaces.ResourceManagement;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Interfaces.Configuration;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
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>
|
||||
/// A network entity report that lists all entities as they are updated through game states.
|
||||
/// https://developer.valvesoftware.com/wiki/Networking_Entities#cl_entityreport
|
||||
/// </summary>
|
||||
class NetEntityOverlay : Overlay
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly IClientNetManager _netManager = default!;
|
||||
[Dependency] private readonly IClientGameStateManager _gameStateManager = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
|
||||
private const int TrafficHistorySize = 64; // Size of the traffic history bar in game ticks.
|
||||
|
||||
/// <inheritdoc />
|
||||
public override OverlaySpace Space => OverlaySpace.ScreenSpace;
|
||||
|
||||
private readonly Font _font;
|
||||
private readonly int _lineHeight;
|
||||
private readonly List<NetEntity> _netEnts = new();
|
||||
|
||||
public NetEntityOverlay() : base(nameof(NetEntityOverlay))
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
var cache = IoCManager.Resolve<IResourceCache>();
|
||||
_font = new VectorFont(cache.GetResource<FontResource>("/Fonts/NotoSans/NotoSans-Regular.ttf"), 10);
|
||||
_lineHeight = _font.GetLineHeight(1);
|
||||
|
||||
_gameStateManager.GameStateApplied += HandleGameStateApplied;
|
||||
}
|
||||
|
||||
private void HandleGameStateApplied(GameStateAppliedArgs args)
|
||||
{
|
||||
if(_gameTiming.InPrediction) // we only care about real server states.
|
||||
return;
|
||||
|
||||
// Shift traffic history down one
|
||||
for (var i = 0; i < _netEnts.Count; i++)
|
||||
{
|
||||
var traffic = _netEnts[i].Traffic;
|
||||
for (int j = 1; j < TrafficHistorySize; j++)
|
||||
{
|
||||
traffic[j - 1] = traffic[j];
|
||||
}
|
||||
|
||||
traffic[^1] = 0;
|
||||
}
|
||||
|
||||
var gameState = args.AppliedState;
|
||||
|
||||
if(gameState.EntityStates is not null)
|
||||
{
|
||||
// Loop over every entity that gets updated this state and record the traffic
|
||||
foreach (var entityState in gameState.EntityStates)
|
||||
{
|
||||
var newEnt = true;
|
||||
for(var i=0;i<_netEnts.Count;i++)
|
||||
{
|
||||
var netEnt = _netEnts[i];
|
||||
|
||||
if (netEnt.Id != entityState.Uid)
|
||||
continue;
|
||||
|
||||
//TODO: calculate size of state and record it here.
|
||||
netEnt.Traffic[^1] = 1;
|
||||
netEnt.LastUpdate = gameState.ToSequence;
|
||||
newEnt = false;
|
||||
_netEnts[i] = netEnt; // copy struct back
|
||||
break;
|
||||
}
|
||||
|
||||
if (!newEnt)
|
||||
continue;
|
||||
|
||||
var newNetEnt = new NetEntity(entityState.Uid);
|
||||
newNetEnt.Traffic[^1] = 1;
|
||||
newNetEnt.LastUpdate = gameState.ToSequence;
|
||||
_netEnts.Add(newNetEnt);
|
||||
}
|
||||
}
|
||||
|
||||
bool pvsEnabled = _configurationManager.GetCVar<bool>("net.pvs");
|
||||
float pvsSize = _configurationManager.GetCVar<float>("net.maxupdaterange");
|
||||
var pvsCenter = _eyeManager.CurrentEye.Position;
|
||||
Box2 pvsBox = Box2.CenteredAround(pvsCenter.Position, new Vector2(pvsSize*2, pvsSize*2));
|
||||
|
||||
int timeout = _gameTiming.TickRate * 3;
|
||||
for (int i = 0; i < _netEnts.Count; i++)
|
||||
{
|
||||
var netEnt = _netEnts[i];
|
||||
|
||||
if(_entityManager.EntityExists(netEnt.Id))
|
||||
{
|
||||
//TODO: Whoever is working on PVS remake, change the InPVS detection.
|
||||
var position = _entityManager.GetEntity(netEnt.Id).Transform.MapPosition;
|
||||
netEnt.InPVS = !pvsEnabled || (pvsBox.Contains(position.Position) && position.MapId == pvsCenter.MapId);
|
||||
_netEnts[i] = netEnt; // copy struct back
|
||||
continue;
|
||||
}
|
||||
|
||||
netEnt.Exists = false;
|
||||
if (netEnt.LastUpdate.Value + timeout < _gameTiming.LastRealTick.Value)
|
||||
{
|
||||
_netEnts.RemoveAt(i);
|
||||
i--;
|
||||
continue;
|
||||
}
|
||||
|
||||
_netEnts[i] = netEnt; // copy struct back
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Draw(DrawingHandleBase handle, OverlaySpace currentSpace)
|
||||
{
|
||||
if (!_netManager.IsConnected)
|
||||
return;
|
||||
|
||||
// remember, 0,0 is top left of ui with +X right and +Y down
|
||||
var screenHandle = (DrawingHandleScreen)handle;
|
||||
|
||||
for (int i = 0; i < _netEnts.Count; i++)
|
||||
{
|
||||
var netEnt = _netEnts[i];
|
||||
|
||||
var xPos = 100;
|
||||
var yPos = 10 + _lineHeight * i;
|
||||
var name = $"({netEnt.Id}) {_entityManager.GetEntity(netEnt.Id).Prototype?.ID}";
|
||||
var color = CalcTextColor(ref netEnt);
|
||||
DrawString(screenHandle, _font, new Vector2(xPos + (TrafficHistorySize + 4), yPos), name, color);
|
||||
DrawTrafficBox(screenHandle, ref netEnt, xPos, yPos);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawTrafficBox(DrawingHandleScreen handle, ref NetEntity netEntity, int x, int y)
|
||||
{
|
||||
handle.DrawRect(UIBox2.FromDimensions(x+1, y, TrafficHistorySize + 1, _lineHeight), new Color(32, 32, 32, 128));
|
||||
handle.DrawRect(UIBox2.FromDimensions(x, y, TrafficHistorySize + 2, _lineHeight), Color.Gray.WithAlpha(0.15f), false);
|
||||
|
||||
var traffic = netEntity.Traffic;
|
||||
|
||||
//TODO: Local peak size, actually scale the peaks
|
||||
for (int i = 0; i < TrafficHistorySize; i++)
|
||||
{
|
||||
if(traffic[i] == 0)
|
||||
continue;
|
||||
|
||||
var xPos = x + 1 + i;
|
||||
var yPosA = y + 1;
|
||||
var yPosB = yPosA + _lineHeight - 1;
|
||||
handle.DrawLine(new Vector2(xPos, yPosA), new Vector2(xPos, yPosB), Color.Green);
|
||||
}
|
||||
}
|
||||
|
||||
private Color CalcTextColor(ref NetEntity ent)
|
||||
{
|
||||
if(!ent.Exists)
|
||||
return Color.Gray; // Entity is deleted, will be removed from list soon.
|
||||
|
||||
if(!ent.InPVS)
|
||||
return Color.Red; // Entity still exists outside PVS, but not updated anymore.
|
||||
|
||||
if(_gameTiming.LastRealTick < ent.LastUpdate + _gameTiming.TickRate)
|
||||
return Color.Blue; //Entity in PVS generating ongoing traffic.
|
||||
|
||||
return Color.Green; // Entity in PVS, but not updated recently.
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
_gameStateManager.GameStateApplied -= HandleGameStateApplied;
|
||||
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
private static void DrawString(DrawingHandleScreen handle, Font font, Vector2 pos, string str, Color textColor)
|
||||
{
|
||||
var baseLine = new Vector2(pos.X, font.GetAscent(1) + pos.Y);
|
||||
|
||||
foreach (var chr in str)
|
||||
{
|
||||
var advance = font.DrawChar(handle, chr, baseLine, 1, textColor);
|
||||
baseLine += new Vector2(advance, 0);
|
||||
}
|
||||
}
|
||||
|
||||
private struct NetEntity
|
||||
{
|
||||
public GameTick LastUpdate;
|
||||
public readonly EntityUid Id;
|
||||
public readonly int[] Traffic;
|
||||
public bool Exists;
|
||||
public bool InPVS;
|
||||
|
||||
public NetEntity(EntityUid id)
|
||||
{
|
||||
LastUpdate = GameTick.Zero;
|
||||
Id = id;
|
||||
Traffic = new int[TrafficHistorySize];
|
||||
Exists = true;
|
||||
InPVS = true;
|
||||
}
|
||||
}
|
||||
|
||||
private class NetEntityReportCommand : IConsoleCommand
|
||||
{
|
||||
public string Command => "net_entityreport";
|
||||
public string Help => "net_entityreport <0|1>";
|
||||
public string Description => "Toggles the net entity report panel.";
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length != 1)
|
||||
{
|
||||
shell.WriteLine("Invalid argument amount. Expected 2 arguments.", Color.Red);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!byte.TryParse(args[0], out var iValue))
|
||||
{
|
||||
shell.WriteLine("Invalid argument: Needs to be 0 or 1.", Color.Red);
|
||||
return;
|
||||
}
|
||||
|
||||
var bValue = iValue > 0;
|
||||
var overlayMan = IoCManager.Resolve<IOverlayManager>();
|
||||
|
||||
if(bValue && !overlayMan.HasOverlay(nameof(NetEntityOverlay)))
|
||||
{
|
||||
overlayMan.AddOverlay(new NetEntityOverlay());
|
||||
shell.WriteLine("Enabled network entity report overlay.");
|
||||
}
|
||||
else if(!bValue && overlayMan.HasOverlay(nameof(NetEntityOverlay)))
|
||||
{
|
||||
overlayMan.RemoveOverlay(nameof(NetEntityOverlay));
|
||||
shell.WriteLine("Disabled network entity report overlay.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Log;
|
||||
@@ -78,10 +78,10 @@ namespace Robust.Shared
|
||||
CVarDef.Create("net.state_buf_merge_threshold", 5, CVar.ARCHIVE);
|
||||
|
||||
public static readonly CVarDef<bool> NetPVS =
|
||||
CVarDef.Create("net.pvs", true, CVar.ARCHIVE);
|
||||
CVarDef.Create("net.pvs", true, CVar.ARCHIVE | CVar.REPLICATED | CVar.SERVER);
|
||||
|
||||
public static readonly CVarDef<float> NetMaxUpdateRange =
|
||||
CVarDef.Create("net.maxupdaterange", 12.5f, CVar.ARCHIVE);
|
||||
CVarDef.Create("net.maxupdaterange", 12.5f, CVar.ARCHIVE | CVar.REPLICATED | CVar.SERVER);
|
||||
|
||||
public static readonly CVarDef<bool> NetLogLateMsg =
|
||||
CVarDef.Create("net.log_late_msg", true);
|
||||
|
||||
Reference in New Issue
Block a user