mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-06-09 10:06:34 +02:00
83c2a1be11
* [Dependency] source generator No more reflection, no more codegen at runtime Also various changes to Roslyn helpers to make this easier to write. Requires all types with dependencies to be partial and not have readonly dependency fields. An analyzer enforces this at warning level, the previous injection strategies have remained in the code *for now* as a fallback. No fallback is available for [field: Dependency] properties, due to a Roslyn bug. Code Fixes exist. We love Roslyn * Apply dependencies generator changes to all code * Release notes * Preprocessor got hands * Handle nullable dependencies These are bad but gotta deal with it. * Apply suggestions from code review Co-authored-by: Moony <moony@hellomouse.net> * Fine, let's not use collection expressions --------- Co-authored-by: Moony <moony@hellomouse.net>
225 lines
8.1 KiB
C#
225 lines
8.1 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Numerics;
|
|
using Robust.Client.Graphics;
|
|
using Robust.Client.ResourceManagement;
|
|
using Robust.Client.Timing;
|
|
using Robust.Shared.Collections;
|
|
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.Network.Messages;
|
|
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>
|
|
sealed partial class NetEntityOverlay : Overlay
|
|
{
|
|
[Dependency] private IClientGameTiming _gameTiming = default!;
|
|
[Dependency] private IClientNetManager _netManager = default!;
|
|
[Dependency] private IClientGameStateManager _gameStateManager = default!;
|
|
[Dependency] private IEntityManager _entityManager = default!;
|
|
|
|
private const uint TrafficHistorySize = 64; // Size of the traffic history bar in game ticks.
|
|
private const int _maxEnts = 128; // maximum number of entities to track.
|
|
|
|
/// <inheritdoc />
|
|
public override OverlaySpace Space => OverlaySpace.ScreenSpace;
|
|
|
|
private readonly Font _font;
|
|
private readonly int _lineHeight;
|
|
private readonly Dictionary<NetEntity, NetEntData> _netEnts = new();
|
|
|
|
public NetEntityOverlay()
|
|
{
|
|
IoCManager.InjectDependencies(this);
|
|
var cache = IoCManager.Resolve<IResourceCache>();
|
|
_font = new VectorFont(cache.GetResource<FontResource>("/EngineFonts/NotoSans/NotoSans-Regular.ttf"), 10);
|
|
_lineHeight = _font.GetLineHeight(1);
|
|
|
|
_gameStateManager.GameStateApplied += HandleGameStateApplied;
|
|
_gameStateManager.PvsLeave += OnPvsLeave;
|
|
}
|
|
|
|
private void OnPvsLeave(MsgStateLeavePvs msg)
|
|
{
|
|
if (msg.Tick.Value + TrafficHistorySize < _gameTiming.LastRealTick.Value)
|
|
return;
|
|
|
|
foreach (var uid in msg.Entities)
|
|
{
|
|
if (!_netEnts.TryGetValue(uid, out var netEnt))
|
|
continue;
|
|
|
|
if (netEnt.LastUpdate < msg.Tick)
|
|
{
|
|
netEnt.InPVS = false;
|
|
netEnt.LastUpdate = msg.Tick;
|
|
}
|
|
|
|
netEnt.Traffic.Add(msg.Tick, NetEntData.EntState.PvsLeave);
|
|
}
|
|
}
|
|
|
|
private void HandleGameStateApplied(GameStateAppliedArgs args)
|
|
{
|
|
var gameState = args.AppliedState;
|
|
|
|
if (!gameState.EntityStates.HasContents)
|
|
return;
|
|
|
|
foreach (var entityState in gameState.EntityStates.Span)
|
|
{
|
|
if (!_netEnts.TryGetValue(entityState.NetEntity, out var netEnt))
|
|
{
|
|
if (_netEnts.Count >= _maxEnts)
|
|
continue;
|
|
|
|
_netEnts[entityState.NetEntity] = netEnt = new();
|
|
}
|
|
|
|
if (!netEnt.InPVS && netEnt.LastUpdate < gameState.ToSequence)
|
|
{
|
|
netEnt.InPVS = true;
|
|
netEnt.Traffic.Add(gameState.ToSequence, NetEntData.EntState.PvsEnter);
|
|
}
|
|
else
|
|
netEnt.Traffic.Add(gameState.ToSequence, NetEntData.EntState.Data);
|
|
|
|
if (netEnt.LastUpdate < gameState.ToSequence)
|
|
netEnt.LastUpdate = gameState.ToSequence;
|
|
|
|
//TODO: calculate size of state and record it here.
|
|
}
|
|
}
|
|
|
|
protected internal override void Draw(in OverlayDrawArgs args)
|
|
{
|
|
if (!_netManager.IsConnected)
|
|
return;
|
|
|
|
switch (args.Space)
|
|
{
|
|
case OverlaySpace.ScreenSpace:
|
|
DrawScreen(args);
|
|
break;
|
|
}
|
|
}
|
|
|
|
private void DrawScreen(in OverlayDrawArgs args)
|
|
{
|
|
// remember, 0,0 is top left of ui with +X right and +Y down
|
|
var screenHandle = args.ScreenHandle;
|
|
|
|
int i = 0;
|
|
foreach (var (nent, netEnt) in _netEnts)
|
|
{
|
|
var uid = _entityManager.GetEntity(nent);
|
|
|
|
if (!_entityManager.EntityExists(uid))
|
|
{
|
|
_netEnts.Remove(nent);
|
|
continue;
|
|
}
|
|
|
|
var xPos = 100;
|
|
var yPos = 10 + _lineHeight * i++;
|
|
var name = $"({uid}) {_entityManager.GetComponent<MetaDataComponent>(uid).EntityPrototype?.ID}";
|
|
var color = netEnt.TextColor(_gameTiming);
|
|
screenHandle.DrawString(_font, new Vector2(xPos + (TrafficHistorySize + 4), yPos), name, color);
|
|
DrawTrafficBox(screenHandle, netEnt, xPos, yPos);
|
|
}
|
|
}
|
|
|
|
private void DrawTrafficBox(DrawingHandleScreen handle, NetEntData 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 (uint i = 1; i <= TrafficHistorySize; i++)
|
|
{
|
|
if (!traffic.TryGetValue(_gameTiming.LastRealTick + (i - TrafficHistorySize), out var tickData))
|
|
continue;
|
|
|
|
var color = tickData switch
|
|
{
|
|
NetEntData.EntState.Data => Color.Green,
|
|
NetEntData.EntState.PvsLeave => Color.Orange,
|
|
NetEntData.EntState.PvsEnter => Color.Cyan,
|
|
_ => throw new Exception("Unexpected value")
|
|
};
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
protected override void DisposeBehavior()
|
|
{
|
|
_gameStateManager.GameStateApplied -= HandleGameStateApplied;
|
|
_gameStateManager.PvsLeave -= OnPvsLeave;
|
|
base.DisposeBehavior();
|
|
}
|
|
|
|
private sealed class NetEntData
|
|
{
|
|
public GameTick LastUpdate = GameTick.Zero;
|
|
public readonly OverflowDictionary<GameTick, EntState> Traffic = new((int)TrafficHistorySize);
|
|
public bool Exists = true;
|
|
public bool InPVS = true;
|
|
|
|
public Color TextColor(IClientGameTiming timing)
|
|
{
|
|
if (!InPVS)
|
|
return Color.Orange; // Entity still exists outside PVS, but not updated anymore.
|
|
|
|
if (timing.LastRealTick < LastUpdate + timing.TickRate)
|
|
return Color.Blue; //Entity in PVS generating ongoing traffic.
|
|
|
|
return Color.Green; // Entity in PVS, but not updated recently.
|
|
}
|
|
|
|
public enum EntState : byte
|
|
{
|
|
Nothing = 0,
|
|
Data = 1,
|
|
PvsLeave = 2,
|
|
PvsEnter = 3
|
|
}
|
|
}
|
|
|
|
private sealed class NetEntityReportCommand : LocalizedCommands
|
|
{
|
|
public override string Command => "net_entityreport";
|
|
|
|
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
|
{
|
|
var overlayMan = IoCManager.Resolve<IOverlayManager>();
|
|
|
|
if (!overlayMan.HasOverlay(typeof(NetEntityOverlay)))
|
|
{
|
|
overlayMan.AddOverlay(new NetEntityOverlay());
|
|
shell.WriteLine("Enabled network entity report overlay.");
|
|
}
|
|
else
|
|
{
|
|
overlayMan.RemoveOverlay(typeof(NetEntityOverlay));
|
|
shell.WriteLine("Disabled network entity report overlay.");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|