app: fix potential bug on exit due to already dropped anim when draw() is called after window quit request. see below for more detailed info.

= Crash =

1. User quits -> god.stop_app() -> runtime stops
2. Arc<ManagedSeqAnim> drops -> delete_unmanaged_anim() -> animation removed from self.anims
3. miniquad event loop calls draw() one more time
4. draw_call() hits GfxDrawInstruction::Animation(anim_id) -> self.anims.get_mut(&anim_id).unwrap()
   PANIC! Animation was already deleted in step 2

= Cause =

- DrawCall holds Animation(AnimId) - just an ID, not a reference
- self.anims: HashMap<AnimId, GfxSeqAnim> stores actual animation data
- ManagedSeqAnim::drop() removes animation from self.anims
- DrawCalls may still reference deleted animations
- Race during shutdown: animations deleted before final draw()

= Fix =

Change Animation(AnimId) to Animation(ManagedSeqAnimPtr):
- Arc keeps ManagedSeqAnim alive as long as DrawCall exists
- Drop only fires when all DrawCalls are dropped
- Animation stays in self.anims until safe to delete
- Uses Rust ownership to prevent bug at compile time
This commit is contained in:
jkds
2026-01-04 07:14:18 +01:00
parent 8028ea9b10
commit 6b9ef3aff2
3 changed files with 10 additions and 10 deletions

View File

@@ -472,14 +472,14 @@ impl AsyncEncodable for DrawMesh {
}
}
#[derive(Debug, Clone, SerialEncodable)]
#[derive(Debug, Clone)]
pub enum DrawInstruction {
SetScale(f32),
Move(Point),
SetPos(Point),
ApplyView(Rectangle),
Draw(DrawMesh),
Animation(AnimId),
Animation(ManagedSeqAnimPtr),
EnableDebug,
SetPipeline(GraphicPipeline),
}
@@ -506,7 +506,7 @@ impl DrawInstruction {
}
}
#[derive(Clone, Debug, Default, SerialEncodable)]
#[derive(Clone, Debug, Default)]
pub struct DrawCall {
pub instrs: Vec<DrawInstruction>,
pub dcs: Vec<DcId>,
@@ -560,7 +560,7 @@ enum GfxDrawInstruction {
SetPos(Point),
ApplyView(Rectangle),
Draw(GfxDrawMesh),
Animation(AnimId),
Animation(ManagedSeqAnimPtr),
EnableDebug,
SetPipeline(GraphicPipeline),
}
@@ -721,10 +721,10 @@ impl<'a> RenderContext<'a> {
self.ctx.apply_bindings(&bindings);
self.ctx.draw(0, mesh.num_elements, 1);
}
GfxDrawInstruction::Animation(anim_id) => {
let anim = self.anims.get_mut(&anim_id).unwrap();
anim.is_visible = true;
if let Some(dc) = anim.tick() {
GfxDrawInstruction::Animation(anim) => {
let gfx_anim = self.anims.get_mut(&anim.id).unwrap();
gfx_anim.is_visible = true;
if let Some(dc) = gfx_anim.tick() {
self.draw_call(&dc, indent + 1, is_debug);
}
}

View File

@@ -57,7 +57,7 @@ impl Trax {
0u8.encode(&mut self.buf).unwrap();
epoch.encode(&mut self.buf).unwrap();
batch_id.encode(&mut self.buf).unwrap();
dcs.encode(&mut self.buf).unwrap();
//dcs.encode(&mut self.buf).unwrap();
}
pub fn put_start_batch(

View File

@@ -250,7 +250,7 @@ impl Video {
vec![
DrawInstruction::SetPipeline(GraphicPipeline::YUV),
DrawInstruction::Move(rect.pos()),
DrawInstruction::Animation(vid_data.anim.id),
DrawInstruction::Animation(vid_data.anim.clone()),
],
vec![],
self.z_index.get(),