@wonderlandlabs-pixi-ux/ticker-forest
Repository: https://github.com/wonderlandlabs-pixi-ux/wonderlandlabs-pixi-ux/tree/main/packages/ticker-forest
Abstract base class for Forestry state management that synchronizes state changes with PixiJS rendering via the ticker pattern.
Overview
This class solves the problem of PixiJS artifacts that occur when PixiJS operations are performed outside of ticker handlers. While Forestry state changes are synchronous, PixiJS-centric effects must be encapsulated inside ticker handlers to work correctly.
Installation
yarn add @wonderlandlabs-pixi-ux/ticker-forest
Pattern
- Subclass manages its own state
- Subclass calls
this.dirty()when PixiJS updates are needed - Base class schedules a ticker callback for the next frame with
ticker.addOnce(...) - Ticker calls
resolve()and clears the internal dirty flag
Usage
Subclasses must implement resolve() and call dirty() when state changes require Pixi updates.
Example
import { TickerForest } from '@wonderlandlabs-pixi-ux/ticker-forest';
import { Application } from 'pixi.js';
interface MyState {
position: { x: number; y: number };
}
class MyStore extends TickerForest<MyState> {
constructor(app: Application) {
super(
{ value: { position: { x: 0, y: 0 } } },
{ app }
);
}
updatePosition(x: number, y: number) {
this.mutate(draft => {
draft.position.x = x;
draft.position.y = y;
});
this.dirty();
}
protected resolve(): void {
// Perform PixiJS operations here
const { position } = this.value;
this.sprite.position.set(position.x, position.y);
}
}
API
Constructor
constructor(
args: StoreParams<T>,
config?: {
app?: Application;
ticker?: Ticker;
container?: Container;
dirtyOnScale?: boolean | DirtyOnScaleOptions | DirtyOnScale;
} | Application
)
args- The Forestry configuration object (includes{value: ..., res: ...}and other Forest options)config.app- Optional PixiJS Application instanceconfig.ticker- Optional explicit ticker overrideconfig.container- Optional container reference for consumersconfig.dirtyOnScale- Optional automatic dirty tracking for scale changes
Ticker resolution precedence:
- Explicit
config.ticker(orstore.ticker = ...) config.app?.tickerstore.$parent?.ticker
dirtyOnScale options:
trueenables scale tracking with defaults:watchX: true,watchY: truenew DirtyOnScale(...)allows sharing one configured comparator/reader instancewatchX/watchYselect which axis contributes to dirty checks- Set both
watchX: falseandwatchY: falseto disable tracking via object config.
Core Methods
dirty(): void
Marks the store dirty and schedules a single resolve on the next ticker frame. If the store is already dirty, no duplicate ticker callback is queued.
kickoff(): void
Trigger an initial resolve on the next ticker frame. Subclasses should call this in their constructor after initialization to ensure initial PixiJS operations are performed.
getScale(): { x: number; y: number }
Returns the current container scale as {x, y}. By default this is measured relative to the root parent, so nested transform chains are reflected in the result.
getInverseScale(): { x: number; y: number }
Returns the inverse scale of getScale() as {x, y}. This is the counter-scale value commonly used to keep UI affordances (titlebars, handles, labels) visually constant under zoom.
Abstract Method (Must Implement)
resolve(): void
Perform PixiJS operations. This method is called inside a ticker handler, ensuring that PixiJS operations are synchronized with the rendering loop.
ticker: Ticker | undefined (getter/setter)
Direct ticker access for animation sync. This can be explicitly set or inherited from app/parent.
cleanup(): void
Cleanup method to remove ticker listeners. Subclasses should call super.cleanup() in their cleanup/destroy methods.
Dependencies
@wonderlandlabs/forestry4- State managementpixi.js- PixiJS rendering engine
License
MIT
APPENDIX: Beyond PIXI
This library was originally wrote to solve a technical problem in PIXI. however it is also useful in any high performance animation to establish a common pattern:
- Data changes flag parts of a tree as "dirty" / need re-rendering
- Inside an animation loop the visuals are updated immediately before showing a frame
This is a useful economy; for instance say the positin of a sprite is changed three or four times. There is no reason to recompute the sprite four times, -- only once, before the next frame is rendered. That is, instead of data changes immediately triggering a regeneration of the visuals it just notes that visual regeneration is needed and is performed each frame, unless the visuals have not changed.
Box, one of the immediate use cases of ticker-forest, was designed to create a "flex-like" api for Pixi, for convenience in design; however, once the pixi elements were stripped (for ease of rapid development) it became clear that the math and utility of Box was not purely confined to Pixi but could be used in any system with a render tree.
To this end, we are redesigning the ticker-forest class to be pixi-friendly, but to mainly depend on the concept fo a ticker to which render events can be added (or removed).
So, you can add render events to any system (svg, dom) and use the Box system to contain generic descriptors for styling, size, and position of a rectangular container in a 2d space. However while it is useable in Pixi, your render engine can be any 2d render system you wish, and the TickerForest class while it does depend on the Ticker interface can be used in any visualization system you may want to.