Overview

@dirtytalk/spatial tracks damage as rects on top of @dirtytalk/engine, so a 2D scene graph can repaint only the regions that changed.

View .md Edit on GitHub

@dirtytalk/spatial is rect-based damage tracking for 2D scene graphs, built on @dirtytalk/engine. Nodes report what changed and where; a SceneRoot batches the reports into one frame and hands your renderer the list of rects to repaint.

Why a damage list

A dirty bit records only that something changed, so a renderer has to repaint everything. This package records a list of Damage entries instead. Each entry has a rect (the area affected) and a kind ('data', 'layout' or 'paint', which pipeline stages must run). The renderer receives the individual rects, so repaint cost scales with the damaged area rather than the scene size. See Concepts for the underlying model and Dirty channel for the engine primitive.

This package ships the Renderer2D interface, not a renderer. You supply the implementation (Canvas2D, WebGL, WebGPU, …).

Install

pnpm add @dirtytalk/spatial @dirtytalk/engine

Minimal example

import { SceneNode, SceneRoot } from '@dirtytalk/spatial';
import type { Renderer2D, Rect } from '@dirtytalk/spatial';
import { SyncScheduler } from '@dirtytalk/engine';

const renderer: Renderer2D = {
  beginFrame(regions: readonly Rect[]) {
    console.log('repaint', regions);
  },
  endFrame() {},
};

class Button extends SceneNode {
  label = '';

  setLabel(label: string) {
    if (this.label === label) return;
    this.label = label;
    this.markDamaged('paint');
  }

  paint() {}
}

const root = new SceneRoot(renderer, {
  scheduler: new SyncScheduler(),
  bounds: { x: 0, y: 0, w: 800, h: 600 },
});

const button = new Button({ bounds: { x: 10, y: 10, w: 120, h: 40 } });
root.adoptChild(button); // repaint [{ x: 10, y: 10, w: 120, h: 40 }]

button.setLabel('Click me'); // repaint [{ x: 10, y: 10, w: 120, h: 40 }]

SyncScheduler flushes on every mark, which keeps the example linear. In an application, omit scheduler to use the default RAFScheduler, which coalesces all damage raised in a tick into one frame.

Pages

  • Scene graph: SceneNode, SceneRoot, damage kinds, batching, subclassing.
  • Rendering: the Renderer2D contract, frame regions, FrameTiming, rect helpers, a Canvas2D renderer.
  • Pointer events: hit-testing and PointerRouter.
  • API reference: every public export with signatures.

For an interactive walkthrough, see /learn/spatial/.