Files
RobustToolbox/Robust.Client/Profiling/ProfViewManager.cs
T
Pieter-Jan Briers 83c2a1be11 [Dependency] source generator part 2 (#6550)
* [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>
2026-05-08 12:38:33 +02:00

136 lines
3.9 KiB
C#

using System;
using Robust.Shared.Collections;
using Robust.Shared.Console;
using Robust.Shared.IoC;
using Robust.Shared.Profiling;
using Robust.Shared.Timing;
namespace Robust.Client.Profiling;
/// <summary>
/// Manager for managing recording and snapshots of profiles, consuming shared <see cref="ProfManager"/>
/// </summary>
public sealed partial class ProfViewManager
{
[Dependency]
private ProfManager _profManager = default!;
public ValueList<Snapshot> Snapshots;
public event Action? SnapshotsUpdated;
private int _nextId = 1;
public void Snap()
{
var buffer = _profManager.Buffer.Snapshot();
var lowestIndex = CalcLowestIndex(buffer);
var highestIndex = buffer.IndexWriteOffset - 1;
long start;
long end;
if (lowestIndex == buffer.IndexWriteOffset)
{
// Literally not a single valid index. Time to give up.
start = 0;
end = 0;
}
else
{
(start, end) = CalcBufferRange(lowestIndex, highestIndex, buffer);
}
Snapshots.Add(new Snapshot
{
StartFrame = start,
EndFrame = end,
LowestValidIndex = lowestIndex,
Buffer = buffer,
Identifier = _nextId++
});
SnapshotsUpdated?.Invoke();
}
public void DeleteSnapshot(Snapshot snapshot)
{
if (Snapshots.Remove(snapshot))
SnapshotsUpdated?.Invoke();
}
private static long CalcLowestIndex(in ProfBuffer buffer)
{
// Any commands further back than this index are invalid.
// Indices in their range are thus also invalid and to be ignored.
var cmdBufferStartValid = buffer.LogWriteOffset - buffer.LogBuffer.LongLength;
// Just scan over the index ring buffer in reverse to find the earliest index that's still valid.
var min = buffer.IndexWriteOffset - buffer.IndexBuffer.LongLength;
for (var i = buffer.IndexWriteOffset - 1; i >= min; i--)
{
ref var index = ref buffer.Index(i);
if (index.StartPos < cmdBufferStartValid)
return i + 1;
}
return min;
}
private (long start, long end) CalcBufferRange(long lowestIndex, long highestIndex, in ProfBuffer buffer)
{
return (GetFrameOfIndex(lowestIndex, buffer), GetFrameOfIndex(highestIndex, buffer));
}
private long GetFrameOfIndex(long index, in ProfBuffer buffer)
{
ref var indexRef = ref buffer.Index(index);
ref var firstLog = ref buffer.Log(indexRef.StartPos);
if (firstLog.Type != ProfLogType.Value ||
_profManager.GetString(firstLog.Value.StringId) != GameLoop.ProfTextStartFrame)
throw new InvalidOperationException("Unable to find start frame");
if (firstLog.Value.Value.Type != ProfValueType.Int64)
throw new InvalidOperationException("Start frame has incorrect value type");
return firstLog.Value.Value.Int64;
}
public long GetIndexOfFrame(long frame, Snapshot snapshot)
{
for (var i = snapshot.Buffer.IndexWriteOffset - 1; i >= snapshot.LowestValidIndex; i--)
{
if (GetFrameOfIndex(i, snapshot.Buffer) == frame)
return i;
}
return 0;
}
public sealed class Snapshot
{
public int Identifier;
public long LowestValidIndex;
public long StartFrame;
public long EndFrame;
public ProfBuffer Buffer;
public long FrameCount => EndFrame - StartFrame + 1;
}
}
public sealed partial class ProfSnapshotCommand : LocalizedCommands
{
[Dependency] private ProfViewManager _profView = default!;
// ReSharper disable once StringLiteralTypo
public override string Command => "profsnap";
public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
_profView.Snap();
}
}