Skip to content

Modder API

DooDesch edited this page Jun 22, 2026 · 2 revisions

Modder API

Snitch lets any mod report its own performance, and it is a zero-overhead no-op when Snitch is not installed - so you can ship the integration unconditionally with no hard dependency. Full working example: ScheduleOne-SnitchExample.

You get the basics for free

When Snitch is installed and sampling, it auto-times every loaded mod's per-frame methods (OnUpdate, OnFixedUpdate, OnLateUpdate, OnGUI) and shows them as <YourMod>.OnUpdate etc. - with zero code on your side. Your mod already appears in the profiler's frame budget without integrating anything at all.

Add the API below only to go further: custom gauges, state distributions, hand-timed sub-sections, or ablation levers.

Add the API

  • Copy-in source (recommended): drop Snitch.cs (from the example repo) into your mod project. It compiles into your DLL - nothing extra to ship.
  • Reference the DLL: reference Snitch.Api.dll.

Both bind to the running Snitch host by reflection, so they share no type with it and work regardless of load order (registrations are queued until the host is up).

Register counters + state with zero wiring

Name a class SnitchProbe with a static Register() anywhere in your mod. Snitch discovers and calls it automatically when sampling starts - you never wire a call into your OnInitializeMelon:

using Snitch.Api;   // Profiler, StateSnapshot

internal static class SnitchProbe
{
    public static void Register()
    {
        // A numeric gauge, polled a few Hz by the host.
        Profiler.RegisterCounter("MyMod.QueueLength", () => MyMod.Queue.Count, "items");

        // An entity/state distribution (a bar panel in the HUD + web dashboard).
        Profiler.RegisterStateProvider("MyMod.Jobs", () =>
            new StateSnapshot { Title = "Jobs" }.Add("running", MyMod.Running).Add("queued", MyMod.Queued));
    }
}

The full API

using Snitch.Api;   // Profiler, StateSnapshot, Scope

// Hand-time a sub-section (finer than the automatic per-mod timing). No heap alloc; no-op when not sampling.
using (Profiler.Sample("MyMod.Pathfinding")) { /* expensive work */ }

// gate hot loops for the absolutely-free path:
if (Profiler.Enabled) using (Profiler.Sample("MyMod.Tick")) { /* ... */ }

// A numeric gauge (polled a few Hz by the host).
Profiler.RegisterCounter("MyMod.QueueLength", () => _queue.Count, "items");

// An entity/state distribution (a bar panel in the HUD + web dashboard).
Profiler.RegisterStateProvider("MyMod.Jobs", () =>
    new StateSnapshot { Title = "Jobs" }.Add("running", _running).Add("queued", _queued));

// An ablation lever so 'snitch ablate mymod.fx' measures your subsystem's causal frame cost.
Profiler.RegisterAblationLever("mymod.fx", apply: () => DisableFx(), restore: () => EnableFx());

// Mark a one-off spike.
Profiler.Mark("MyMod.LevelLoaded");

Rules

  • Call from the Unity main thread. Counter/state delegates are invoked by the host on the main thread, so they may safely touch game objects.
  • Prefix labels with MyMod. so they roll up per mod in the HUD and dashboard.
  • Profiler.Sample returns a readonly struct scope - no heap allocation, and Dispose is a no-op when Snitch isn't sampling.

Your sections, counters and states appear live in the in-game HUD and the Web Dashboard, right alongside the vanilla NPC/trash/quest data.

Clone this wiki locally