# Engine overview

> @dirtytalk/engine is a zero-dependency dirty-tracking core: a Space algebra, pluggable schedulers and a DirtyChannel that coalesces marks.

Source: https://dirtytalk.42.dog/docs/engine/

`@dirtytalk/engine` computes "what changed" once at the source, then tells only the subscribers that care, once per scheduling window. It has zero dependencies and ships no concrete region type: you supply the `Space`.

## When to use it

- Several consumers react to the same mutations, and each cares about a different part of the data (rectangles on a canvas, paths in a state tree, keys in a store).
- You want many mutations in one tick to produce a single notification per subscriber.
- You want to choose when notifications run (immediately, per microtask, per animation frame, or on demand) without changing producer or consumer code.

It is not a reactive graph. There are no computed values, effects, selectors or diffing helpers; producing a region from a mutation is your job. See [Concepts](/docs/concepts/).

## Install

```sh
pnpm add @dirtytalk/engine
```

## Building blocks

| Piece                  | Role                                                                   | Page                                         |
| ---------------------- | ---------------------------------------------------------------------- | -------------------------------------------- |
| `Space<Region>`        | Algebra describing a region: `empty`, `isEmpty`, `union`, `intersects` | [Custom spaces](/docs/engine/custom-spaces/) |
| `Scheduler`            | Decides when a flush runs                                              | [Schedulers](/docs/engine/schedulers/)       |
| `DirtyChannel<Region>` | Accumulates marks, notifies interested subscribers                     | [DirtyChannel](/docs/engine/dirty-channel/)  |

## Minimal example

```ts
import { DirtyChannel, MicrotaskScheduler } from '@dirtytalk/engine';
import type { Space } from '@dirtytalk/engine';

const KeySpace: Space<Set<string>> = {
  empty: () => new Set(),
  isEmpty: (r) => r.size === 0,
  union: (a, b) => new Set([...a, ...b]),
  intersects: (interest, dirty) => {
    for (const key of interest) if (dirty.has(key)) return true;
    return false;
  },
};

const channel = new DirtyChannel(KeySpace, new MicrotaskScheduler());

const unsubscribe = channel.subscribe(
  () => new Set(['users', 'session']),
  (dirty) => console.log('changed:', [...dirty]),
);

channel.mark(new Set(['users']));
channel.mark(new Set(['session', 'theme']));
// After the current microtask queue drains, one call:
// changed: [ 'users', 'session', 'theme' ]

channel.mark(new Set(['theme']));
// No callback: 'theme' is not in the subscriber's interest.

unsubscribe();
channel.dispose();
```

Both `mark` calls land in the same window, so the subscriber is called once with the union of the marks. The callback receives the whole dirty region, not only the part that intersects its interest.

## Where to go next

- [DirtyChannel](/docs/engine/dirty-channel/): marking, subscribing, coalescing, re-entrancy and errors.
- [Schedulers](/docs/engine/schedulers/): the four built-in schedulers and how to write your own.
- [Custom spaces](/docs/engine/custom-spaces/): the `Space` contract with worked examples.
- [API reference](/docs/engine/api/): every export with its signature.
- [Structural](/docs/structural/) and [Spatial](/docs/spatial/): ready-made spaces built on the engine.
- [Interactive chapter](/learn/engine/): see marks, coalescing and flushes run in the browser.
