API reference
Reference for every public export of @dirtytalk/engine with exact TypeScript signatures, parameters, return values, behaviour and examples.
All exports of @dirtytalk/engine, with signatures taken from the source. For explanations see DirtyChannel, Schedulers and Custom spaces.
import {
DirtyChannel,
SyncScheduler,
ManualScheduler,
MicrotaskScheduler,
RAFScheduler,
} from '@dirtytalk/engine';
import type { Space, Scheduler } from '@dirtytalk/engine';
Types
Space
interface Space<Region> {
empty(): Region;
isEmpty(r: Region): boolean;
union(a: Region, b: Region): Region;
intersects(interest: Region, dirty: Region): boolean;
unionInto?(acc: Region, b: Region): Region;
}
The algebra of “what changed” and “what I care about”. Contracts: union(empty(), r) equals r; intersects(empty(), _) is false; all operations are pure, except that unionInto may mutate and return acc.
| Method | Returns |
|---|---|
empty() | A region meaning “nothing”. |
isEmpty(r) | true if r is empty. |
union(a, b) | A region covering both. May return a or b by reference. |
intersects(interest, dirty) | true if a subscriber with interest should be notified of dirty. |
unionInto(acc, b) | Optional. union that may mutate acc; must not mutate b. |
import type { Space } from '@dirtytalk/engine';
const BitSpace: Space<number> = {
empty: () => 0,
isEmpty: (r) => r === 0,
union: (a, b) => a | b,
intersects: (i, d) => (i & d) !== 0,
};
See Custom spaces.
Scheduler
interface Scheduler {
request(flush: () => void): void;
cancel?(flush: () => void): void;
}
Decides when a flush runs. request asks for flush to be called later; cancel, if present, drops a pending request.
import type { Scheduler } from '@dirtytalk/engine';
const immediate: Scheduler = {
request: (flush) => flush(),
};
See Schedulers.
Classes
DirtyChannel
class DirtyChannel<Region> {
constructor(
space: Space<Region>,
scheduler: Scheduler,
options?: { onError?: (err: unknown) => void },
);
mark(r: Region): void;
subscribe(interest: () => Region, cb: (dirty: Region) => void): () => void;
dispose(): void;
}
Accumulates marked regions and delivers them to interested subscribers in one flush per scheduler window.
import { DirtyChannel, ManualScheduler } from '@dirtytalk/engine';
import type { Space } from '@dirtytalk/engine';
const BitSpace: Space<number> = {
empty: () => 0,
isEmpty: (r) => r === 0,
union: (a, b) => a | b,
intersects: (i, d) => (i & d) !== 0,
};
const scheduler = new ManualScheduler();
const channel = new DirtyChannel(BitSpace, scheduler, {
onError: (err) => console.error(err),
});
const unsubscribe = channel.subscribe(
() => 0b01,
(dirty) => console.log('dirty', dirty),
);
channel.mark(0b01);
scheduler.pump(); // logs: dirty 1
unsubscribe();
channel.dispose();
constructor
| Parameter | Description |
|---|---|
space | The Space used to combine and compare regions. |
scheduler | The Scheduler that triggers flushes. Not called during construction. |
options.onError | Optional handler for subscriber errors. Without it, errors are rethrown from the flush. |
mark
mark(r: Region): void. Merges r into the pending dirty region and requests a flush if none is pending. Returns nothing. Does nothing after dispose(). Throws only if space.union/unionInto throws, or if the scheduler flushes synchronously and the flush rethrows subscriber errors.
subscribe
subscribe(interest, cb): () => void. Registers a subscriber. interest is evaluated once per flush; cb is called with the full dirty region when space.intersects(interest(), dirty) is true. Returns an idempotent unsubscribe function. After dispose(), returns a no-op function. Does not throw.
dispose
dispose(): void. Cancels a pending flush, drops subscribers and pending marks, and makes the channel inert. Idempotent.
Flush errors
During a flush, errors from interest, space.intersects and cb are isolated per subscriber. With onError, each is passed to it. Without, after all subscribers have run, a single error is rethrown as-is and several are rethrown as AggregateError with message DirtyChannel: subscriber errors during flush.
SyncScheduler
class SyncScheduler implements Scheduler {
request(flush: () => void): void;
}
Calls flush immediately inside request. No deduplication, no cancel. Throws whatever flush throws.
import { DirtyChannel, SyncScheduler } from '@dirtytalk/engine';
import type { Space } from '@dirtytalk/engine';
declare const space: Space<number>;
const channel = new DirtyChannel(space, new SyncScheduler());
ManualScheduler
class ManualScheduler implements Scheduler {
request(flush: () => void): void;
cancel(flush: () => void): void;
pump(): void;
}
Holds requests until pump(). Requests of the same function are deduplicated.
| Method | Description |
|---|---|
request(flush) | Adds flush to the pending set. |
cancel(flush) | Removes flush from the pending set. |
pump() | Runs the flushes pending now; requests made during the pump wait for the next one. No-op if none pending. Runs every flush even if some throw, then rethrows a single error as-is or an AggregateError (ManualScheduler: flush errors during drain). |
import { ManualScheduler } from '@dirtytalk/engine';
const scheduler = new ManualScheduler();
scheduler.request(() => console.log('flushed'));
scheduler.pump(); // logs: flushed
MicrotaskScheduler
class MicrotaskScheduler implements Scheduler {
request(flush: () => void): void;
cancel(flush: () => void): void;
}
Runs all pending flushes in one queueMicrotask callback. Duplicates are deduplicated. cancel removes a flush from the pending set. Errors from flushes are rethrown inside the microtask after all have run (MicrotaskScheduler: flush errors during drain for several).
import { MicrotaskScheduler } from '@dirtytalk/engine';
const scheduler = new MicrotaskScheduler();
scheduler.request(() => console.log('flushed'));
await Promise.resolve(); // logs: flushed
RAFScheduler
class RAFScheduler implements Scheduler {
request(flush: () => void): void;
cancel(flush: () => void): void;
}
Runs all pending flushes on the next animation frame. If globalThis.requestAnimationFrame is not a function when the scheduler is constructed, it uses setTimeout(..., 16) instead. cancel removes a flush and, if nothing else is pending, cancels the frame or timer. Errors are rethrown after all flushes have run (RAFScheduler: flush errors during drain for several).
import { RAFScheduler } from '@dirtytalk/engine';
const scheduler = new RAFScheduler();
scheduler.request(() => console.log('next frame'));