C# debugging: choose the right tool
A runnable example and the boundaries of Output, Remote Inspector, Profiler and managed debugging.
Start with the symptom
| Symptom | Tool and result |
|---|---|
| Project does not build | dotnet build output; fix the first error |
| Wrong command result | Output, operation inputs and its result |
| Wrong object or property value | Remote tree and Inspector of the running application |
| Slow frame | Frame metrics, then measurement of a specific workload |
| Growing memory | A repeatable scenario and separate native/C# accounting |
| C# locals or stepping required | A separately configured managed debugger |
Reckfell 1.0.0 can transmit C# errors and object properties through remote diagnostics. This is not full managed breakpoint, locals and watches support in the built-in panel. Built-in C# method profiling is not provided here either.
Run the check level
Use a C# project with Reckfell.NET.Sdk 1.0.0 and net10.0.
- Save all four files below with their exact names.
- Run dotnet build.
- Create a level with an Entity root, attach DiagnosticsExample.cs and save as diagnostics.rscene.
- Run it. Output should contain DIAGNOSTICS checksum=8386560; lifetime=True; ok=True.
MEASURE and MEMORY values vary with the machine and run. They are not performance targets. In the running tree find DiagnosticsExample/InspectionTarget: DiagnosticToken equals 424242. A standalone level root may have another name; the child is named InspectionTarget.
DebugInspectionTarget.cs
using Reckfell;
public partial class DebugInspectionTarget : Entity
{
[Export] public int DiagnosticToken { get; set; } = 424242;
}WorkMeasurement.cs
using System;
using System.Diagnostics;
public readonly record struct WorkMeasurement(double Milliseconds, long AllocatedBytes, long Checksum)
{
public static WorkMeasurement Run(int iterations)
{
if (iterations < 0 || iterations > 10_000_000)
throw new ArgumentOutOfRangeException(nameof(iterations));
long before = GC.GetAllocatedBytesForCurrentThread();
long start = Stopwatch.GetTimestamp();
long checksum = 0;
for (int i = 0; i < iterations; i++) checksum += i;
double elapsed = Stopwatch.GetElapsedTime(start).TotalMilliseconds;
long allocated = GC.GetAllocatedBytesForCurrentThread() - before;
return new WorkMeasurement(elapsed, allocated, checksum);
}
}ObjectLifetimeProbe.cs
using System;
using System.Collections.Generic;
using Reckfell;
public static class ObjectLifetimeProbe
{
public static bool Run()
{
var retained = new List<Entity>();
var parent = new Entity();
var child = new Entity();
retained.Add(child);
parent.AddChild(child);
parent.RemoveChild(child);
parent.Free();
bool detachedSurvived = ReckfellObject.IsInstanceValid(child);
child.Free();
bool wrapperOutlivedNative = retained[0] is not null &&
!ReckfellObject.IsInstanceValid(retained[0]);
retained.Clear();
return detachedSurvived && wrapperOutlivedNative;
}
public static void PrintSnapshot()
{
double objects = Performance.GetMonitor(Performance.Monitor.ObjectCount);
double entities = Performance.GetMonitor(Performance.Monitor.ObjectNodeCount);
double orphans = Performance.GetMonitor(Performance.Monitor.ObjectOrphanNodeCount);
long managed = GC.GetTotalMemory(false);
ReckfellApi.Print($"MEMORY objects={objects}; entitiesInTree={entities}; outsideTree={orphans}; managedApproxBytes={managed}");
}
}DiagnosticsExample.cs
using System.Globalization;
using Reckfell;
public partial class DiagnosticsExample : Entity
{
public bool Succeeded { get; private set; }
public override void _Ready()
{
var target = new DebugInspectionTarget { Name = "InspectionTarget" };
AddChild(target);
_ = WorkMeasurement.Run(64);
WorkMeasurement measured = WorkMeasurement.Run(4096);
bool lifetime = ObjectLifetimeProbe.Run();
Succeeded = measured.Checksum == 8386560 && lifetime;
ReckfellApi.Print($"DIAGNOSTICS checksum={measured.Checksum}; lifetime={lifetime}; ok={Succeeded}");
ReckfellApi.Print("MEASURE ms=" + measured.Milliseconds.ToString("F4", CultureInfo.InvariantCulture) +
"; currentThreadAllocatedBytes=" + measured.AllocatedBytes);
ObjectLifetimeProbe.PrintSnapshot();
}
}Preserve reproducibility
Record engine version, Debug/ExportRelease configuration, platform, reproduction steps and the first error. Change one cause at a time. Use logging settings rather than capturing only the final line without context.