Skip to content

fix(player): re-borrow the SSAB buffer after an in-place reload - #253

Merged
Naruto merged 1 commit into
developfrom
fix/reimport-dangling-ssab-borrow
Aug 3, 2026
Merged

fix(player): re-borrow the SSAB buffer after an in-place reload#253
Naruto merged 1 commit into
developfrom
fix/reimport-dangling-ssab-borrow

Conversation

@MasamiYitsuse

Copy link
Copy Markdown
Collaborator

Description

Re-importing a project while a SpriteStudioPlayer2D is in the scene could crash the editor. Instance parts were required to reproduce it.

The runtime borrows a .ssab buffer rather than copying it, and that borrow was outliving the buffer.

Root cause

SSImporter::_refresh_cached_output (ss_importer.cpp:846) refreshes every generated .ssab by calling load_from_file on the resource already in the cache, then emitting changed. SSABResource::load_from_file reassigns binary, so the object survives but its buffer is freed and replaced.

_fetchAnimation hands that buffer to ss_resource_create_borrow and keeps _currentAnimationData pointing into the same FlatBuffer, so both dangle the moment it is replaced.

The player's own resource is covered: the Node2D connects changed and onSSABReloaded re-borrows. The gap is the external .ssab files — sibling packs loaded by _load_external_ssabs so Instance children can resolve ref_anime_pack — which nothing connects changed on.

Whether it crashed came down to refresh order. _record_ssabs_in_dir (:820) pushes the generated files in DirAccess enumeration order, so when the parent pack sorts before the external one (Instance before Instance_SourceAnime, for example) the children are rebuilt while the external still holds its old buffer, and are left pointing at freed memory when it is refreshed a moment later. Freed-but-not-yet-reused memory usually still reads, which is why it only crashed sometimes.

This is not module-only. copy_from is (#ifndef SPRITESTUDIO_GODOT_EXTENSION), but the importer path above compiles in both builds.

Fix

SSABResource now carries a generation, bumped wherever binary is replaced — load_from_file and copy_from. In load_from_file it is bumped up-front so the failure paths, which leave binary reassigned or cleared, invalidate outstanding borrows as well.

SsInternalPlayer records the generation it borrowed and checks it — for itself and, recursively, for its Instance children — before it steps or draws, rebuilding through onSSABReloaded on a mismatch. Two call sites: update, ahead of the is_playing early-return so a stopped player still notices, and _seek_and_redraw, which draws without going through update. The recursion walks children that already exist, not the reference graph they were built from, so it terminates regardless of how the data references itself.

Comparing get_data_ptr() instead would not be enough: freeing and re-allocating a same-sized buffer frequently lands on the same address.

Alternative considered

Connecting changed on the external resources as well. Rejected on two counts: copy_from emits no changed at all, and the set of resources to watch changes with every animation switch and every level of Instance nesting, so it re-creates the same "one missed spot equals a dangling borrow" failure this bug is an instance of. Checking at the point of use keeps the invariant local to the borrow.

Verification

  • Custom module and GDExtension builds both clean
  • Manually confirmed: the iteration loop and pace that used to crash no longer does

No SDK change; the submodule pin is untouched.

A re-import reloads a .ssab into the same Resource object, freeing the
buffer the runtime borrowed. Only the player's own resource was watched
for "changed"; the external .ssab files Instance children borrow had no
watcher, so those children kept reading freed memory.

Track a generation on SSABResource and verify it, for the player and its
Instance children, before each step or draw.
@Naruto
Naruto merged commit 1f03f33 into develop Aug 3, 2026
0 of 2 checks passed
@Naruto
Naruto deleted the fix/reimport-dangling-ssab-borrow branch August 3, 2026 01:10
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.

2 participants