Skip to content

Fix memory growth when a model is replaced or loses a load race - #520

Open
openmubr wants to merge 1 commit into
Ignies:mainfrom
openmubr:pr/model-swap-leak
Open

openmubr wants to merge 1 commit into
Ignies:mainfrom
openmubr:pr/model-swap-leak

Conversation

@openmubr

@openmubr openmubr commented Nov 8, 2026

Copy link
Copy Markdown
Contributor

Long sessions grew by hundreds of MB per hour. Orphaned skeletons (with their bone textures) and animation groups piled up in the scene, for three reasons:

  • ModelObject.load only disposed the old clone's mesh, so the cloned skeleton and its clips stayed in the scene. It now uses disposeGltf.
  • A load overtaken by a newer one (Pet, GM mark, equipment parts, band instruments), or whose entity was gone by the time it landed, returned without freeing the freshly cloned model. It now calls disposeLoadedModel.
  • Hero.syncPlayerAppearance set charAppearance.changed on every inventory change (a potion drunk, loot picked up, an item moved in the vault), so each one re-dressed about 10 parts. It now only does so when what is worn (model, level, excellent/ancient, sockets) actually changes.

Reproduced offline: 120 quick swaps left 128 orphaned skeletons and animation groups went 114 -> 1054; with the fix there are 0 orphans.

Tests: src/controllers/render/modelSwapLeak.test.ts (3 cases, all fail without the fix). vitest on src/controllers/render, src/ecs/systems and src/modules/hero passes (32 files, 216 tests); tsc shows only the existing install/cli errors.

🤖 Generated with Claude Code

@openmubr
openmubr requested a review from Ignies as a code owner November 8, 2026 12:38
Long sessions grew by hundreds of MB per hour. Orphaned skeletons (with
their bone textures) and animation groups piled up in the scene because:

- ModelObject.load only disposed the old clone's mesh; the cloned
  skeleton and its clips stayed in the scene. It now uses disposeGltf.
- A load overtaken by a newer one (Pet, GM mark, equipment parts, band
  instruments) or whose entity had already gone returned without
  freeing the freshly cloned model. It now calls disposeLoadedModel.
- Hero.syncPlayerAppearance set charAppearance.changed on every
  inventory change (potion drunk, loot picked up, item moved), so each
  one re-dressed ~10 parts. It now only does so when what is worn
  (model, level, excellent/ancient, sockets) actually changes.

Reproduced offline: 120 quick swaps left 128 orphaned skeletons and
animation groups went 114 -> 1054; with the fix, 0 orphans.
Tests: src/controllers/render/modelSwapLeak.test.ts (fail without the fix).
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.

1 participant