Skip to content

Pool: retire the legacy name-keyed object pool from engine internals - #1726

Merged
obiot merged 2 commits into
masterfrom
fix/1688-retire-legacy-pool
Oct 9, 2026
Merged

obiot merged 2 commits into
masterfrom
fix/1688-retire-legacy-pool

Conversation

@obiot

@obiot obiot commented Oct 9, 2026

Copy link
Copy Markdown
Member

Description

The engine no longer uses me.pool for anything: zero internal pull and zero internal push. The only two mentions left in src/ are comments, and legacy_pool is imported by index.ts for the public export and by TMXObjectFactory.js for the register bridge that keeps pool.register working.

All four of the issue's items:

  1. push(obj, throwOnError = false). It threw by default for any class never registered, and nothing at the call site made that visible, so the exception aborted whatever was running. Flipped rather than split, because the class is deprecated now and a second method would teach a new spelling on a retiring surface.
  2. atlas.js and TMXTileMap.js construct their classes directly. That needed the module cycle broken first: sprite.js imported loader.js and atlas.js, and class NineSliceSprite extends Sprite reads Sprite at module-evaluation time, so importing either from inside the cycle threw and took 271 of 319 spec files with it. The ten cache read accessors moved to loader/cache.js, and the two instanceof TextureAtlas tests became instanceof Texture2d && isAtlas.
  3. The Tiled name registry is split from the recycling pool. Built-in classes are DEFAULTS: they fill a name nothing else claimed, and a game's own class takes the name over whenever it registers, before or after the first map load.
  4. me.pool is deprecated, with a one-off notice per method naming both replacements (getPool/createPool to pool instances, registerTiledObjectClass to let a Tiled map name a class).

Container now returns a child to the typed pool that built it, which is the question a generic caller has to ask: createPool stamps the owning pool on everything it builds, non-enumerably. Text and ColorLayer gained typed pools.

Found by reviewing this change, and fixed here

  • The built-in Tiled classes had lost their me.-prefixed aliases. Those arrived only as a side effect of pool.register, which registered the factory twice. Dropping the bootstrap registrations silently turned every map object classed me.Trigger into a plain Renderable. The shipped platformer/map1.tmx object id 17 is me.Trigger.
  • registerTiledObjectClass before the first map load was silently discarded, because the built-ins were flushed on the first object built and overwrote it.
  • A recycled Text kept the previous label's state — fillStyle, strokeStyle, floating, and everything a game drives through Renderable (alpha, tint, blend mode, transform, anchor, flip). It also rebuilt its canvas and metrics on every reset, stranding the previous canvas and its GPU texture.
  • RenderTarget#toImageData(), so toBlob/toDataURL/toImageBitmap stop throwing on a WebGPU target. Synchronous readback is not portable.
  • scripts/check-declarations.ts, gated in pnpm types. Nothing compiled against build/*.d.ts, so a whole class of defect was invisible: an @internal parser entry point had become public because two typedefs were inserted between its doc block and the function, and loader.GLTFData had disappeared from the public types. Five of this change's own bugs were only found by looking there.
  • 12 public members reached TypeScript as any, and Body#collisionMask was stripped from the published types while the PhysicsBody interface it implements required it.
  • The trigger_level_change flake. Tween#_onTick takes an absolute performance.now() stamp and ignores a delta outside (0, 1000), so the test's hardcoded 1000 was a valid tick only while the page was under a second old. Verified with 16 consecutive full-suite runs; it used to fail about one run in five.

Breaking

  • pool.pull("Sprite"), pool.pull("Text"), pool.pull("Tween"), pool.pull("Particle") and the other nine built-in names now throw. Nine of the thirteen had recycling off, so the call was a plain construction behind a string key; the four that had it on all have a typed pool.
  • pool.register(name, Class, true) no longer makes a class recyclable on container removal, and pool.register("Sprite", MySprite) no longer substitutes the class used by createSpriteFromName or the Tiled image-layer loader. Registering your own names is unaffected.
  • pool.push(obj) returns false where it used to throw.
  • RenderTarget#getImageData is no longer part of the abstract contract (it stays on the canvas and WebGL targets), and WebGPURenderTarget#readPixels is replaced by toImageData.

The old space-invaders example is removed rather than converted: it was carrying 25 of the gallery's pre-existing type errors on its own.

Type of change

  • Bug fix
  • New feature
  • Documentation update
  • Performance improvement
  • Refactoring (no functional changes)

Checklist

  • I have read the Contributing Guide
  • My code follows the existing code style (pnpm lint passes)
  • I have tested my changes locally (pnpm test passes)
  • I have added tests that cover my changes (if applicable)
  • The build succeeds (pnpm build)

5 new spec files plus additions to 10 existing ones. Every behaviour here is mutation-tested: the fix was reverted and the test confirmed to fail. Beyond the suite, all 53 examples were swept for boot errors, the converted ones had their live scene trees inspected to confirm their Tiled classes really do instantiate (a registration that fails is silent, not a throw), and unmount/remount was exercised on each.

Related issues

Closes #1688

🤖 Generated with Claude Code

https://claude.ai/code/session_01NGvtaUNATVCVxD2qcbiY4t

…1688)

The engine no longer uses `me.pool` for anything. Zero internal `pull` and
zero internal `push`: the only two mentions left in `src/` are comments, and
`legacy_pool` is imported by `index.ts` for the export and by
`TMXObjectFactory.js` for the register bridge.

All four of the issue's items:

1. `push(obj, throwOnError = false)`. It threw by default for any class that
   was never registered, and nothing at the call site made that visible, so
   the exception aborted whatever was running. Flipped rather than split,
   because the class is deprecated now and a second method would teach a new
   spelling on a retiring surface.
2. `atlas.js` and `TMXTileMap.js` construct their classes directly. That
   needed the module cycle broken first: `sprite.js` imported `loader.js` and
   `atlas.js`, and `class NineSliceSprite extends Sprite` reads `Sprite` at
   module-evaluation time, so importing either from inside the cycle threw.
   The ten cache read accessors moved to `loader/cache.js`, and the two
   `instanceof TextureAtlas` tests became `instanceof Texture2d && isAtlas`.
3. The Tiled name registry is split from the recycling pool. Built-in classes
   are DEFAULTS: they fill a name nothing else claimed, and a game's own class
   takes the name over whenever it registers. They keep their `me.`-prefixed
   aliases, which used to arrive only as a side effect of `pool.register`.
4. `me.pool` is deprecated, with a one-off notice per method naming both
   replacements.

`Container` now returns a child to the typed pool that BUILT it, which is the
question a generic caller has to ask: `createPool` stamps the owning pool on
everything it builds. `Text` and `ColorLayer` gained typed pools, and with
them two defects a recycled instance had: the settings-conditional fields
leaked across a reuse, and every reset built a new canvas and metrics while
stranding the previous canvas and its GPU texture.

Also in here because the review found them:

- `RenderTarget#toImageData()`, so `toBlob`/`toDataURL`/`toImageBitmap` stop
  throwing on WebGPU. Synchronous readback is not portable.
- `scripts/check-declarations.ts`, gated in `pnpm types`. Nothing compiled
  against `build/*.d.ts`, so a whole class of defect was invisible: five of
  this change's own bugs were only found by looking there.
- `registerTiledObjectClass` before the first map load was silently discarded.
- 12 public members reached TypeScript as `any`, and `Body#collisionMask` was
  stripped from the published types while `PhysicsBody` required it.
- The `trigger_level_change` flake: `_onTick` takes an absolute
  `performance.now()` stamp and ignores a delta outside `(0, 1000)`, so a
  hardcoded `1000` was a valid tick only while the page was under a second
  old.

Examples move off the legacy pool too, and the old space-invaders example is
removed rather than converted.

Closes #1688

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NGvtaUNATVCVxD2qcbiY4t
Copilot AI balanced review requested due to automatic review settings October 9, 2026 08:38

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

`packages/melonjs`'s own `lint` script is `eslint src tests`, so `scripts/`
is only reached by the ROOT `pnpm lint`, which is what CI runs. The new file
tripped `prefer-template` there and nowhere locally.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NGvtaUNATVCVxD2qcbiY4t
Copilot AI balanced review requested due to automatic review settings October 9, 2026 08:43

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@obiot
obiot merged commit e9d134f into master Oct 9, 2026
6 checks passed
@obiot
obiot deleted the fix/1688-retire-legacy-pool branch October 9, 2026 08:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Pool: retire the legacy name-keyed object pool from engine internals

2 participants