Custom monitors: values, units and cleanup
A C# monitor with cached values: seconds, quantity, unique IDs and removal on exit.
Create two counters
Add JobTelemetry.cs to a 1.0.0 C# project and attach it to an Entity. On tree entry it registers time and quantity monitors under a unique docs_perf_<instance ID> category. Both start at 0. Call Publish(0.125, 9592) on the main thread: the API returns 0.125 seconds and a count of 9592.
JobTelemetry.cs
using System;
using Reckfell;
public partial class JobTelemetry : Entity
{
public string SecondsId { get; private set; } = "";
public string CountId { get; private set; } = "";
public double Seconds { get; private set; }
public int Count { get; private set; }
private bool _registered;
public override void _EnterTree()
{
string category = "docs_perf_" + GetInstanceId();
SecondsId = category + "/prime_seconds";
CountId = category + "/prime_count";
Performance.AddCustomMonitor(SecondsId, Callable.From(() => Seconds),
type: Performance.MonitorType.Time);
Performance.AddCustomMonitor(CountId, Callable.From(() => Count),
type: Performance.MonitorType.Quantity);
_registered = true;
}
public void Publish(double seconds, int count)
{
if (!ReckfellThread.IsMainThread())
throw new InvalidOperationException("Publish must run on the main thread");
if (!double.IsFinite(seconds) || seconds < 0 || count < 0)
throw new ArgumentOutOfRangeException(nameof(seconds));
Seconds = seconds;
Count = count;
}
public override void _ExitTree()
{
if (!_registered) return;
Performance.RemoveCustomMonitor(SecondsId);
Performance.RemoveCustomMonitor(CountId);
_registered = false;
}
}When running with the editor debugger connected, open the bottom debugger panel and its monitors tab, find docs_perf_<instance ID>, and enable the required graphs. A value of 0.125 seconds formats as 125 ms. API registration, types, values and cleanup are runtime-checked; native editor mouse actions and display of these custom graphs were not verified in this fixture.
For nonzero values without additional gameplay code, run PrimeJobsDemo and its files. It publishes the final count and elapsed time from scheduling the .NET job to consuming its result. That includes waits and is not pure CPU time.
The callback is not an accumulator
GetCustomMonitor synchronously invokes the registered Callable. The engine neither automatically times your function nor stores its event history for you. This callback only returns cached Seconds/Count; do not perform expensive computation, loading, file writes or Entity creation inside it.
The current remote debugger sends polls roughly once per second. A short spike between polls may be missed. For events, accumulate a count or maximum over an explicitly defined interval; for long frames use the interval sampler. A one-second polling graph is not a complete per-frame profile.
Units and names
| MonitorType | Input | Editor formatting |
|---|---|---|
| Quantity | Count | Integer |
| Time | Seconds | Value × 1000, ms |
| Memory | Bytes | Memory size |
| Percentage | Fraction from 0 to 1 | Value × 100, % |
The type defines meaning and formatting; it does not calculate the metric. Pass 0.25 for 25%, or 0.125 for 125 ms. Do not confuse allocated bytes, managed-heap size and total process memory.
Names are global to the current process. Duplicate AddCustomMonitor IDs produce an error; this example includes an instance ID. Exactly one “/” separates category and metric in the checked editor source. Two JobTelemetry instances do not overwrite each other.
Lifetime
The Callable captures its component. _ExitTree removes both owned monitors before the receiver disappears; _EnterTree registers them on reentry. Do not remove someone else's monitor because a short name matches. Check HasCustomMonitor before reading a potentially removed ID.
Call Publish on the main thread. Transfer worker results there before updating the cache. No connected editor is required for HasCustomMonitor/GetCustomMonitor, but those APIs do not create a graph capture. File output, history export and an in-game overlay require separate code.