Files
RobustToolbox/Robust.Client/GameStates/NetEntityOverlay.cs
Acruid e16732eb7b Network View Bubble (#1629)
* 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.
2021-03-29 16:17:34 -07:00

293 lines
11 KiB
C#

using System.Collections.Generic;
using Robust.Client.Graphics;
using Robust.Client.Player;
using Robust.Client.ResourceManagement;
using Robust.Shared.Configuration;
using Robust.Shared.Console;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Network;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
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 | OverlaySpace.WorldSpace;
private readonly Font _font;
private readonly int _lineHeight;
private readonly List<NetEntity> _netEnts = new();
public 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 pvsRange = _configurationManager.GetCVar<float>("net.maxupdaterange");
var pvsCenter = _eyeManager.CurrentEye.Position;
Box2 pvsBox = Box2.CenteredAround(pvsCenter.Position, new Vector2(pvsRange*2, pvsRange*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;
switch (currentSpace)
{
case OverlaySpace.ScreenSpace:
DrawScreen(handle);
break;
case OverlaySpace.WorldSpace:
DrawWorld(handle);
break;
}
}
private void DrawWorld(DrawingHandleBase handle)
{
bool pvsEnabled = _configurationManager.GetCVar<bool>("net.pvs");
if(!pvsEnabled)
return;
float pvsSize = _configurationManager.GetCVar<float>("net.maxupdaterange");
var pvsCenter = _eyeManager.CurrentEye.Position;
Box2 pvsBox = Box2.CenteredAround(pvsCenter.Position, new Vector2(pvsSize, pvsSize));
var worldHandle = (DrawingHandleWorld)handle;
worldHandle.DrawRect(pvsBox, Color.Red, false);
}
private void DrawScreen(DrawingHandleBase handle)
{
// 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];
if (!_entityManager.TryGetEntity(netEnt.Id, out var ent))
{
_netEnts.RemoveSwap(i);
i--;
continue;
}
var xPos = 100;
var yPos = 10 + _lineHeight * i;
var name = $"({netEnt.Id}) {ent.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 DisposeBehavior()
{
_gameStateManager.GameStateApplied -= HandleGameStateApplied;
base.DisposeBehavior();
}
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 rune in str.EnumerateRunes())
{
var advance = font.DrawChar(handle, rune, 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.WriteError("Invalid argument amount. Expected 1 arguments.");
return;
}
if (!byte.TryParse(args[0], out var iValue))
{
shell.WriteError("Invalid argument: Needs to be 0 or 1.");
return;
}
var bValue = iValue > 0;
var overlayMan = IoCManager.Resolve<IOverlayManager>();
if(bValue && !overlayMan.HasOverlay(typeof(NetEntityOverlay)))
{
overlayMan.AddOverlay(new NetEntityOverlay());
shell.WriteLine("Enabled network entity report overlay.");
}
else if(!bValue && overlayMan.HasOverlay(typeof(NetEntityOverlay)))
{
overlayMan.RemoveOverlay(typeof(NetEntityOverlay));
shell.WriteLine("Disabled network entity report overlay.");
}
}
}
}
}