Day-to-day tools

The things you reach for every session: a performance panel that says why a sketch is slow, a data folder like Processing's, a rewind that really goes back in time, and a history of every Run with a picture of what it drew.

On this page · 6 entries

ShowStats · HideStats · DrawTime · ActualFrameRate

  • ShowStats()
  • HideStats()
  • DrawTime
  • ActualFrameRate

The performance panel, top-left over the sketch: frames per second actually reached, how long Draw() takes (average and slowest), how much memory each frame allocates and how many garbage collections happened since it opened, with a bar graph of the last 120 frames against the frame budget. F3 over the canvas toggles it — in the IDE and in exported apps. DrawTime and ActualFrameRate read the same numbers from code; they're measured every frame whether the panel shows or not.

DataPath · SketchPath · SketchFolder · DataFolder

  • DataPath(name)
  • SketchPath(name)
  • SketchFolder
  • DataFolder

Like Processing, a sketch's files live in a data folder next to its .cs file. Every load with a relative path — LoadImage, LoadStrings, LoadTable, LoadJSONObject, LoadFont, LoadShape, LoadSample, LoadShader2D, CreateReader... — looks in data/ first, then next to the .cs, then where the program runs; saves with a relative path (SaveFrame, SaveStrings, SaveTable...) go into the sketch folder. Exporting the sketch copies data/ next to the app, so the same paths work there. DataPath("x.csv") is the full path inside data/.

Save first. An unsaved sketch has no folder: relative paths are relative to wherever the program runs until you save it. Sketch → Add file to data/... copies files in; Open the sketch folder shows it.

Real rewind ⏸ · ⤺

Pause with ⏸ under the canvas, drag the timeline back, and ⤺ continue from here: the sketch goes back to that exact frame — its own fields (lists, arrays, your objects), its random generator and FrameCount — and runs on from there; the future after it is forgotten. It works after ⚡ Hot Reload too, so you can go back to frame 300 and see what the code you just changed does from there (fields are matched by name).

How far back. About the last 10 seconds. The newest frames are kept exactly as drawn; older ones are compressed in the background, and on a slow machine or a very big sketch the older part keeps fewer frames rather than less time. Rewind shows what the sketch drew: a sketch that only follows the mouse or the microphone looks the same in every frame while you hold still or stay quiet. After Stop the bar greys out until the next Run: a stopped sketch has nothing to rewind or continue from.
What comes back and what doesn't. Snapshots are deep copies taken with the frames — fewer of them when the state is big, none when it's huge (then rewind is visual only). Images, sounds, ports and other outside things are shared, not copied. Millis() keeps running, and the drawing state (Fill, Stroke...) stays as it is now.

Code time machine Ctrl+Shift+H

Every Run keeps a copy of the code and, a moment later, a picture of what it drew. Sketch → Time machine shows them as cards — thumbnail, time, seed, lines added and removed — so you can find "the version that looked like that" by how it looked. Restore puts it back in the editor (one Ctrl+Z undoes it, and its seed is locked so it draws the same), or open it in a new tab to compare. One history per sketch file, identical runs stored once, the newest 200 kept.

Runnable example: the Game of Life, rewound

Pause with ⏸, drag back, press ⤺ and paint cells with the mouse — the story continues differently from that exact frame. F3 shows what each frame costs. Test in Dana opens it in the IDE, or paste it into a new tab and press Run. It is also in Sketch → Examples.

C#
// Real rewind. Under the canvas:
//   ⏸  pause            the slider scrubs through the last frames
//   ⤺  continue from here: the sketch's fields (the grid, the generation,
//      even the random generator) go back to that frame, and it runs on --
//      the future after it is forgotten.
// Paint cells with the mouse (drag) to make the new future different.
public class MySketch : Sketch
{
    const int Cols = 90, Rows = 60, Cell = 8;
    bool[,] _grid = new bool[Cols, Rows];
    bool[,] _next = new bool[Cols, Rows];
    int _generation;

    public override void Setup()
    {
        Size(Cols * Cell, Rows * Cell + 30);
        FrameRate(15);
        for (int x = 0; x < Cols; x++)
for (int y = 0; y < Rows; y++)
    _grid[x, y] = Random(1) < 0.22f;
    }

    public override void Draw()
    {
        Step();
        Background(16, 18, 26);
        NoStroke();
        for (int x = 0; x < Cols; x++)
for (int y = 0; y < Rows; y++)
    if (_grid[x, y])
    {
        FillHSB((x * 2 + _generation) % 360, 55, 95);
        Rect(x * Cell, y * Cell, Cell - 1, Cell - 1);
    }

        Fill(255, 190);
        TextSize(13);
        Text($"generation {_generation}  ·  ⏸ then ⤺ under the canvas to rewind for real  ·  drag to paint", 10, Height - 10);
    }

    void Step()
    {
        for (int x = 0; x < Cols; x++)
for (int y = 0; y < Rows; y++)
{
    int n = 0;
    for (int dx = -1; dx <= 1; dx++)
        for (int dy = -1; dy <= 1; dy++)
            if ((dx != 0 || dy != 0) && _grid[(x + dx + Cols) % Cols, (y + dy + Rows) % Rows])
                n++;
    _next[x, y] = _grid[x, y] ? n is 2 or 3 : n == 3;
}
        (_grid, _next) = (_next, _grid);
        _generation++;
    }

    public override void MouseDragged() => Paint();
    public override void MousePressed() => Paint();

    void Paint()
    {
        int cx = (int)(MouseX / Cell), cy = (int)(MouseY / Cell);
        for (int dx = -1; dx <= 1; dx++)
for (int dy = -1; dy <= 1; dy++)
{
    int x = cx + dx, y = cy + dy;
    if (x >= 0 && x < Cols && y >= 0 && y < Rows)
        _grid[x, y] = true;
}
    }
}

Runnable example: why is it slow?

Raise the particles slider until the F3 graph turns red, and turn on garbage to see what new lists every frame cost. Test in Dana opens it in the IDE, or paste it into a new tab and press Run. It is also in Sketch → Examples.

C#
// The performance panel: press F3 (here it starts open via ShowStats()).
//
//   fps          frames per second actually reached (target: FrameRate())
//   Draw ms      how long your Draw() takes -- the bars; the line is the budget
//   KB/frame     memory Draw() allocates every frame
//   GC 0/1/2     garbage collections since the panel opened
//
// Raise "particles" until the bars go red. Then switch on "garbage": the same
// work, but a new List every frame -- watch KB/frame and the GC counter climb.
public class MySketch : Sketch
{
    [Tweak(100, 20000)] int particles = 3000;
    [Tweak] bool garbage = false;

    private readonly List<PVector> _pos = new();
    private readonly List<PVector> _vel = new();
    private readonly List<PVector> _reused = new();

    public override void Setup()
    {
        Size(720, 480);
        ShowStats();
    }

    public override void Draw()
    {
        while (_pos.Count < particles)
        {
_pos.Add(new PVector(Random(Width), Random(Height)));
_vel.Add(PVector.Random2D().Mult(Random(0.5f, 2)));
        }
        if (_pos.Count > particles)
        {
_pos.RemoveRange(particles, _pos.Count - particles);
_vel.RemoveRange(particles, _vel.Count - particles);
        }

        // The wasteful way allocates a fresh list (and fresh vectors) every frame.
        var visible = garbage ? new List<PVector>() : _reused;
        visible.Clear();

        for (int i = 0; i < _pos.Count; i++)
        {
var p = _pos[i];
p.Add(_vel[i]);
if (p.X < 0) p.X += Width; else if (p.X > Width) p.X -= Width;
if (p.Y < 0) p.Y += Height; else if (p.Y > Height) p.Y -= Height;
visible.Add(garbage ? p.Copy() : p);
        }

        Background(14, 16, 24);
        NoStroke();
        Fill(120, 200, 255, 160);
        foreach (var p in visible)
Circle(p.X, p.Y, 3);

        Fill(255, 180);
        TextSize(13);
        Text($"{particles} particles  ·  garbage {(garbage ? "ON" : "off")}  ·  F3 hides the panel", 14, Height - 14);
    }
}