PR pixi-reels
Wiki · Architecture
Objects

Classes

The composition tree — what owns what, and what passes through.

Every class in pixi-reels lives on one of three layers: the outer shell the user builds, the scene graph PixiJS renders, or the internals that exist for the duration of a spin. Ownership reads top-to-bottom: the parent always destroys its children on destroy().

What each class does — in plain English

If a name feels opaque, read its one-liner here first. The SVG after this section shows how the names are wired together.

ReelSetBuilder builder

A fluent, chainable configurator. You call .reels(5).visibleSymbols(3)... and end with .build(). It exists so every required piece of setup is forced into one place — forget .ticker(app.ticker) and .build() throws before your game boots.

ReelSet scene graph root

The thing you app.stage.addChild(reelSet). A PixiJS Container that owns every reel, the spin controller, the speed manager, and the win spotlight. All the public methods you'll reach for live here: spin(), setResult(), skip(), destroy().

Reel one column

One vertical column of symbols. A Reel owns the symbols currently on screen, their vertical position, and the "what do I land on" queue. You rarely touch a Reel directly — you drive the whole ReelSet and let it fan out.

ReelViewport clip + layer

The clipped window you see through. Holds the mask so symbols scrolling above or below the visible area are hidden, plus a "promoted" layer where winning symbols get temporarily lifted above the mask so their celebration animation isn't clipped.

ReelMotion vertical scroll

The physics of one reel. Adds a Y delta every tick and wraps symbols that fall off the bottom back to the top. When a symbol wraps, it fires a callback that tells the reel "time to swap identity" — that's how a spinning reel eventually shows the target grid.

StopSequencer landing queue

A tiny queue that holds the symbols a reel must land on, in order. On every wrap, the sequencer hands out the next target. When the queue is empty the reel stops.

SpinController conductor

The conductor of a spin. Hands every reel its next phase (StartSpin → optional AnticipationStop), coordinates with setResult(), fires the spin:* events, and resolves the promise you awaited on.

ReelPhase state

Abstract base for a single stage of a reel's spin. Each stage gets onEnter, update (per-tick), and onSkip. Stock phases: StartPhase (wind-up), SpinPhase (steady-state blur), AnticipationPhase (slow-down for tension), StopPhase (decelerate, snap, bounce).

SpinningMode strategy

The top-level "what does a spin even mean" strategy. StandardMode runs every reel through the phase machine. CascadeMode replaces whole columns atomically for tumble mechanics. ImmediateMode snaps straight to the result — useful in tests.

SpeedManager tempo

Named bundles of timings (spin speed, stop delay, bounce distance, easings). Ship with normal, turbo, superTurbo presets. Switch at runtime with reelSet.setSpeed('turbo'); add your own with addProfile().

ReelSymbol abstract cell

One visible cell on a reel. Abstract base class — subclass it to draw whatever you like. Built-in subclasses: SpriteSymbol (plain texture), AnimatedSpriteSymbol (sprite sheet), SpineSymbol (Spine skeleton, optional peer). Implement onActivate, onDeactivate, playWin, stopAnimation, resize for a new kind.

SymbolRegistry / SymbolFactory pool

SymbolRegistry records "symbol id X is rendered by class Y with options Z". SymbolFactory is the cache on top — it pools instances so scrolling a 5x3 reel for five seconds allocates zero new symbol objects.

FrameBuilder + middleware pipeline

A middleware pipeline that decides which symbol identity fills each new buffer slot when a reel needs another row. Random fill is priority 0, target-frame placement is priority 10. Register your own middleware to implement rules like "no three-in-a-row" or "inject a mystery symbol every 7th spin".

SymbolSpotlight wins

The win-animation primitive. show(positions) dims the losers and runs playWin() on the winners, promoted above the mask. cycle(lines) iterates through multiple lines with a configurable cadence and emits spotlight:start / spotlight:end events.

EventEmitter pub / sub

Typed pub/sub. Event names are colon-namespaced (spin:start, spin:reelLanded, speed:changed). Every exit path from a spin fires an event — wire audio and HUD to events, not to method calls, so skip() and destroy() stay correct without your HUD knowing about them.

TickerRef safe ticker

A thin wrapper around PIXI.Ticker that tracks every callback it adds and removes them all on destroy(). Nobody in this codebase calls ticker.add() directly — that's how we keep teardown correct.

Disposable interface

The cleanup contract. Anything that allocates implements it. reelSet.destroy() cascades through the whole tree — you never have to chase individual pieces yourself.

FakeTicker + HeadlessSymbol tests

The headless harness. A manual ticker you step frame by frame, plus a symbol class that draws nothing. With these you can run an entire spin lifecycle in Node, assert on the final grid, and never load PixiJS's renderer.

ReelSetBuilder .reels() · .symbols() · .build() creates ReelSet extends PIXI.Container · implements Disposable SpinController phase orchestrator SpeedManager named profiles SymbolSpotlight win cycle SymbolFactory pool owner FrameBuilder middleware pipeline REELS[] Reel #0 Reel #1 Reel #2 Reel #3 Reel #4 one reel contains: PER REEL container: PIXI.Container child of ReelViewport.maskedContainer holds the symbol views symbols: ReelSymbol[] buffer + visible + buffer pooled + recycled by SymbolFactory events: EventEmitter<ReelEvents> phase:enter · phase:exit · landed symbol:created · symbol:recycled motion: ReelMotion displace · wrap · snapToGrid fires wrap callback -> factory stopSequencer: StopSequencer target frame queue next() pops each wrap spinningMode: SpinningMode Standard · Cascade · Immediate computeDeltaY() per tick
Public API · Builder — you construct this
Scene graph · ReelSet and its reels
Internals · Subsystems, motion, sequencer
Grouping · conceptual, not a class

Reading the ownership chain

ReelSet.destroy() is the single source of truth for teardown. Internally it:

  1. Destroys the SymbolSpotlight (releases its tween handles).
  2. Destroys the SpinController, which removes the TickerRef callback.
  3. For each Reel: releases every pooled symbol back to the SymbolFactory, removes event listeners, destroys the PixiJS container tree.
  4. Destroys the SymbolFactory, which disposes every class instance in its pool.
  5. Destroys the ReelViewport (masked container).
  6. Emits destroyed, removes every listener, calls super.destroy({ children: true }).

If you extend pixi-reels with a new subsystem that holds resources, it must implement the Disposable interface and be destroyed from this chain — there's no hidden GC to catch you.

Disposable everywhere

ReelSet owns: viewport · reels · factory · subsystems
Reel owns: motion · stopSequencer · events · container
SpinController owns: TickerRef · active phases
SymbolSpotlight owns: gsap timelines · overlay containers
SymbolFactory owns: ObjectPool<ReelSymbol>
TickerRef owns: registered ticker callbacks
ReelSymbol owns: view container · subclass resources

Extension points

The classes on the outer edges are where you plug in custom code:

  • Extend ReelSymbol for a new rendering style (Spine, animated sprite, custom Graphics).
  • Extend ReelPhase<TConfig> for a new spin phase, register it via builder.phases(f => f.register(...)).
  • Implement SpinningMode for a different motion model (cascade, hover, immediate).
  • Implement FrameMiddleware to hook into per-frame symbol generation — random fill and target placement are themselves just middleware.