singularity-forge/docs-internal/pi-ui-tui/16-performance-caching-and-invalidation.md
Lex Christopherson d20d5e8fb5 docs: add Mintlify documentation site and move internal docs
Add a proper public-facing documentation site using Mintlify with 19 MDX
pages covering getting started, auto mode, commands, configuration, and
all user-facing features. Move internal/SDK documentation (Pi SDK, TUI,
context & hooks, research notes, ADRs) to docs-internal/ since they
should not be part of the public documentation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 09:54:41 -06:00

1.8 KiB

Performance — Caching and Invalidation

The Caching Pattern

Always cache render() output and recompute only when state changes:

class CachedComponent {
  private cachedWidth?: number;
  private cachedLines?: string[];

  render(width: number): string[] {
    if (this.cachedLines && this.cachedWidth === width) {
      return this.cachedLines;
    }

    // Expensive computation here
    const lines = this.computeLines(width);

    this.cachedWidth = width;
    this.cachedLines = lines;
    return lines;
  }

  invalidate(): void {
    this.cachedWidth = undefined;
    this.cachedLines = undefined;
  }
}

The Update Cycle

State changes → invalidate() → tui.requestRender() → render(width) called
  1. Something changes your component's state (user input, timer, async result)
  2. Call this.invalidate() to clear caches
  3. Call tui.requestRender() to schedule a re-render
  4. The TUI calls render(width) on the next frame
  5. Your component recomputes its output (since cache was cleared)

Game Loop Pattern (Real-Time Updates)

class GameComponent {
  private interval: ReturnType<typeof setInterval> | null = null;
  private version = 0;
  private cachedVersion = -1;

  constructor(private tui: { requestRender: () => void }) {
    this.interval = setInterval(() => {
      this.tick();
      this.version++;
      this.tui.requestRender();
    }, 100);  // 10 FPS
  }

  render(width: number): string[] {
    if (this.cachedVersion === this.version && /* width unchanged */) {
      return this.cachedLines;
    }
    // ... render ...
    this.cachedVersion = this.version;
    return lines;
  }

  dispose(): void {
    if (this.interval) {
      clearInterval(this.interval);
      this.interval = null;
    }
  }
}