moveWithCollisions() causing memory leak?

C-API / SDK 1.12.3 on Linux.

In my sprite update function, if I move my sprite:
playdate->sprite->moveTo( alien_ptr, cx, cy );

I have no issues.

However, if I move my sprite:

int collision_count;
SpriteCollisionInfo *collisions = playdate->sprite->moveWithCollisions( alien_ptr, cx, cy, NULL, NULL, &collision_count );
playdate->system->realloc( collisions, 0 );

I see a ever-increasing memory usage. I'm reasonably sure there are no collisions occurring during either move operation.

Is there some further clean-up that needs to be done after calling this function?

(aside) I rely on the collision handler functions being called, and have no use for the array returned. It would be good if there was a version of this function that did not allocate the list. Or at least one that I could pass a list to, that would save the malloc().

I run the code through valgrind, and I can see the leak being found, but the functions it's reported in seem to be weird - at first I thought the leak was in the simulator memory panel.

==69686== 2,188,032 bytes in 11,396 blocks are definitely lost in loss record 12,266 of 12,267
==69686==    at 0x4849013: operator new(unsigned long) (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==69686==    by 0x3F2680: MallocFrame::AllocationList(double*) (mallocframe.cpp:254)
==69686==    by 0x3F29DF: MallocFrame::Refresh() (mallocframe.cpp:163)
==69686==    by 0x8FAC30: wxEvtHandler::ProcessEventIfMatchesId(wxEventTableEntryBase const&, wxEvtHandler*, wxEvent&) (in /home/sando/Code/Play.Date/PlaydateSDK/PlaydateSDK-1.12.3/bin/PlaydateSimulator)
==69686==    by 0x8FB99C: wxEvtHandler::SearchDynamicEventTable(wxEvent&) (in /home/sando/Code/Play.Date/PlaydateSDK/PlaydateSDK-1.12.3/bin/PlaydateSimulator)
==69686==    by 0x8FBB33: wxEvtHandler::TryHereOnly(wxEvent&) (in /home/sando/Code/Play.Date/PlaydateSDK/PlaydateSDK-1.12.3/bin/PlaydateSimulator)
==69686==    by 0x8FBBDE: wxEvtHandler::ProcessEventLocally(wxEvent&) (in /home/sando/Code/Play.Date/PlaydateSDK/PlaydateSDK-1.12.3/bin/PlaydateSimulator)
==69686==    by 0x8FBCE0: wxEvtHandler::ProcessEvent(wxEvent&) (in /home/sando/Code/Play.Date/PlaydateSDK/PlaydateSDK-1.12.3/bin/PlaydateSimulator)
==69686==    by 0x634DD9: wxWindowBase::UpdateWindowUI(long) (in /home/sando/Code/Play.Date/PlaydateSDK/PlaydateSDK-1.12.3/bin/PlaydateSimulator)
==69686==    by 0x579E22: wxFrameBase::UpdateWindowUI(long) (in /home/sando/Code/Play.Date/PlaydateSDK/PlaydateSDK-1.12.3/bin/PlaydateSimulator)
==69686==    by 0x579FFC: wxFrameBase::OnInternalIdle() (in /home/sando/Code/Play.Date/PlaydateSDK/PlaydateSDK-1.12.3/bin/PlaydateSimulator)
==69686==    by 0x6368BC: wxWindowBase::SendIdleEvents(wxIdleEvent&) (in /home/sando/Code/Play.Date/PlaydateSDK/PlaydateSDK-1.12.3/bin/PlaydateSimulator)
1 Like
if (collision_count) playdate->system->realloc( collisions, 0 );

should fix this, see: Realloc allocates 16 Bytes of memory when pointer is NULL and size is 0

5 Likes

Oh Bother! Thanks for that. I was so stressed for hours trying to chase this one down.

Oh man, this just saved me a lot of time tracking down 1 item of leaking memory.
Got to get more defensive about my frees!

2 Likes

I just caught this same leak when running the "Sprite Game" example in the C_API/Examples (lines 338 and 409).

Any chance we can update this example, and any others that make the same mistake?

Thanks!

1 Like

I've been developing for Playdate using Lua exclusively, and I seem to have run into this issue when I turned off garbage collection. Are you aware of a way to fix or avoid this in Lua?

Hello nimbushed,

If I understand the question, and you are using collisions in Lua, then you will (I expect) be accumulating garbage via the physics API.

So if you have disabled the garbage collector then you will have to occasionally call collectgarbage() manually to avoid a crash.

1 Like

Thanks for the reply, Tim. It sounds like you understood the question. Your response is consistent with my observations.

Did you try the solution he suggested?

No, I don't have C set up. I was looking for a way to prevent garbage accumulation using Lua.

This is the Lua solution @nimbushead

https://www.lua.org/manual/5.4/manual.html#pdf-collectgarbage

Oh, I thought you were referring to his C solution.

I'm aware that I can use Lua's garbage collector; I was looking for a way to avoid the need to collect garbage.

In my game, I'm using sprite:moveWithCollisions, which is generating garbage that needs to be collected. I don't actually need collision-based movement, though, so I tried using just sprite:moveTo for movement and sprite.allOverlappingSprites for handling interaction between sprites. sprite:moveTo does not seem to generate garbage, but sprite.allOverlappingSprites does.

I spent a few minutes playing with rects and polygons, but both of those seem to generate garbage, too.

Exploring garbage collection further, I tried creating a local table in my playdate.update function, which created garbage. Creating local strings and numbers did not seem to create garbage.

Long story short, it seems like any usage of Lua tables will result in the accumulation of garbage. Maybe I should just bite the bullet, dust off my C skills, and get all set up for C development for Playdate.

Thank you, Matt, for replying and giving me a helpful link to Lua's garbage collection documentation. I've enjoyed exploring game development with Playdate, its SDK, and Lua!

Using Lua I think you'll always have some garbage. But I'm surprised if it turned into a problem?

Of course with collection switched off it will build up and eventually crash, as you've seen. But default behaviour will clear it incrementally with little overhead.

Why did you switch it off? Were you seeing garbage collection pauses? If so, did you figure out where in your code was producing too much garbage? @nimbushead

You ask good questions, Matt.

I'm making a shmup, and I've been trying to optimize everywhere I can, and garbage collection was just one place I started looking.

For testing, I made ships fire a bullet every frame, maintaining 60 bullets when only the player ship was present. It ran at about 30 FPS with occasional dips down to around 11-13 FPS. When a gun fires a bullet, there is audio and the creation of a sprite, followed by movement. Once the bullet moves outside the screen, then it gets removed. Bullets inherit from a movable sprite class.

Moving bullets without collisions
35 FPS with no dips. Fortunately, I don't think the bullets need to move with collisions.

Only support bullet movement in movable sprite class
37-38 FPS.

Move bullets in the bullet update method instead of the movable sprite update method
40-41 FPS.

Do not alternate between kDrawModeCopy and kDrawModeInverted
42-44 FPS. The alternating draw mode improved visibility but is unnecessary.

Player fires 60 bullets at once, no rapid-fire

  • Dip to around 43 FPS
  • Maintain 46-47 FPS with 1 enemy present
  • Maintain around 45 FPS with 2 enemies present
  • Maintain around 43 FPS with 4 enemies present

Enemies fire 60 bullets at once, no rapid-fire

  • 1 enemy (60 bullets)
    • Dip to around 40 FPS
    • Maintain just below 50 FPS
  • 2 enemies (120 bullets)
    • Dip to mid-twenties FPS
    • Maintain low thirties FPS
  • 4 enemies (240 bullets)
    • Dip below 10 FPS
    • Maintain mid-teens FPS

Player vs 1 enemy, both fire 60 bullets, no rapid-fire

  • Maintain around 30 FPS

Summary
Moving bullets with collisions was slow, possibly partially due to garbage accumulation and collection. Moving bullets without collisions increased the frame rate by about 5 FPS and prevented large dips. Eliminating calls to the base class resulted in a further increase of around 5-6 FPS. Eliminating sprite:setImageDrawMode increased the frame rate by around 1-4 FPS.

Conclusion
I haven't decided what bullet density I want for the game. A bulletstorm might be infeasible, but a tactical shooter could be interesting.

If you are using the sprite collision system, I would recommend trying out groups if you are not already using it. It filters out collisions and could improve performance. I don't know how it works with sprite.allOverlappingSprites but it's worth a shot.

I think this is probably worth its own thread?

1 Like

haha I was thinking the same thing. Which would be more appropriate between SDK Get Help and SDK Development Discussion?

For anyone interested in some sprite collision exploration, check out this thread.

1 Like