@wonderlandlabs-pixi-ux/ticker-forest
Repository: https://github.com/wonderlandlabs-pixi-ux/wonderlandlabs-pixi-ux/tree/main/packages/ticker-forest
ticker-forest is the shared render scheduler for the monorepo.
It bridges state changes and frame rendering so Pixi updates always happen inside ticker callbacks.
Overview
TickerForest<T> solves a common issue in Pixi UIs:
state updates can happen at any time, but Pixi display operations are safest when applied in a frame/ticker cycle.
Core flow:
- Subclasses mark themselves dirty (
makeDirty(...)). - Subclasses call
queueResolve()once. TickerForestrunsresolve()on the next tick.clearDirty()resets dirty flags after render work is done.
Installation
yarn add @wonderlandlabs-pixi-ux/ticker-forest
API
Constructor
constructor(
args: StoreParams<T>,
config?: {
app?: Application;
ticker?: Ticker;
container?: Container;
dirtyOnScale?: boolean | {
enabled?: boolean;
watchX?: boolean;
watchY?: boolean;
epsilon?: number;
relativeToRootParent?: boolean;
};
} | Application
)
args: Forestry config (value,res, and other Forest options)config.app: optionalApplicationconfig.ticker: explicit ticker overrideconfig.container: container used for scale observation/helpersconfig.dirtyOnScale: auto-mark dirty when container scale changes
Ticker resolution precedence:
- Explicit
config.ticker(orstore.ticker = ...) config.app?.tickerstore.$parent?.ticker
dirtyOnScale options
dirtyOnScale: true uses defaults:
watchX: truewatchY: trueepsilon: 0.0001relativeToRootParent: true
You can also pass an object:
enabled: turn observer on/offwatchX/watchY: choose tracked axesepsilon: tolerance fordistinctUntilChangedrelativeToRootParent: iftrue, compare scale relative to top-most parent; iffalse, use localcontainer.scale
When scale changes and isDirty() is currently false, the store performs:
makeDirty({ reason: 'scale' })queueResolve()
Scale helpers
getScale(): { x, y }getInverseScale(): { x, y }
getInverseScale() is computed directly from getScale(), so components can use consistent counter-scaling.
Typical use case:
- keep titlebar content, labels, and handles at stable visual size while zoom changes container scale.
Required subclass contract
isDirty(): booleanmakeDirty(data?: unknown): voidclearDirty(): voidresolve(): void
Example: counter-scaled titlebar content
protected resolve(): void {
const inverse = this.getInverseScale();
const titlebarHeight = this.value.style?.titlebarHeight ?? 30;
// Keep width aligned to the parent window, but counter-scale Y content.
this.titlebarBackground.width = this.value.rect.width;
this.titlebarBackground.height = titlebarHeight * inverse.y;
this.titlebarContent.scale.set(1, inverse.y);
// Clip expanded children to the visible titlebar.
this.titlebarMask.width = this.value.rect.width;
this.titlebarMask.height = titlebarHeight * inverse.y;
}
Public Methods
queueResolve()- schedule resolve on next ticker framekickoff()- schedule initial resolvetickergetter/setter - explicit ticker control/inheritancecleanup()- remove ticker observers/listeners
Minimal subclass example
import { TickerForest } from '@wonderlandlabs-pixi-ux/ticker-forest';
import { Application } from 'pixi.js';
interface MyState {
dirty: boolean;
position: { x: number; y: number };
}
class MyStore extends TickerForest<MyState> {
constructor(app: Application) {
super(
{ value: { dirty: true, position: { x: 0, y: 0 } } },
{ app, dirtyOnScale: true }
);
this.kickoff();
}
setPosition(x: number, y: number): void {
this.mutate(draft => {
draft.position.x = x;
draft.position.y = y;
});
this.makeDirty({ reason: 'position' });
this.queueResolve();
}
protected isDirty(): boolean {
return this.value.dirty;
}
protected makeDirty(): void {
this.mutate(draft => {
draft.dirty = true;
});
}
protected clearDirty(): void {
this.mutate(draft => {
draft.dirty = false;
});
}
protected resolve(): void {
// Pixi mutations here
}
}
Dependencies
@wonderlandlabs/forestry4- State managementpixi.js- PixiJS rendering engine
License
MIT