Memory Leak in SDK

I've stumbled across a rather bizarre memory leak. I don't fully understand what's going on, but I think it must be a bug under the hood. Here's a simple repro:

import "CoreLibs/graphics"
-- commenting out sprites import resolves leak, regardless of below
import "CoreLibs/sprites"

function playdate.update()
    -- must have at least 3 calls to drawTextAligned()
    playdate.graphics.drawTextAligned("Some", 42, 198, kTextAlignment.center )
    playdate.graphics.drawTextAligned("Stuff", 42, 210, kTextAlignment.center )
    playdate.graphics.drawTextAligned("Here", 42, 222, kTextAlignment.center )
end

My device after a few minutes of running the above snippet:

I modified the code in the following way and it appears to no longer eat memory, although if you comment out the last drawTextAligned it will start eating again.

import "CoreLibs/sprites"
import "CoreLibs/graphics"

local GFX   <const> = playdate.graphics;

local line1 <const> = "Some"
local line2 <const> = "Stuff"
local line3 <const> = "Here"
local line4 <const> = "Extra!"

function playdate.update()
    GFX.drawTextAligned(line1, 200, 10, kTextAlignment.center )
    GFX.drawTextAligned(line2, 200, 30, kTextAlignment.center )
    GFX.drawTextAligned(line3, 200, 50, kTextAlignment.center )
    GFX.drawTextAligned(line4, 200, 70, kTextAlignment.center )
end

This actually just poses more questions I think.

I spent a while trying to narrow down the problem, but it seems a little inconsistent. From what I have seen, it looks like playdate.graphics.drawTextAligned makes use of str:gmatch, which generates a lot of garbage every frame. Normally the garbage collector will clean it up, but sometimes it seems to get out of control and will continue generating garbage very quickly.

If it's garbage collection, can the effects be managed by using Lua command collectgarbage()?

https://www.tutorialspoint.com/lua/lua_garbage_collection.htm

I tried to change the align to left, guessing that that would be the simplest to place and it was still slowly eating memory. I removed all rendered text and even it still was ever so slowly eating memory, I removed the FPS counter, which is also text, memory leak stopped. It's gotta be the text.

If I put collectgarbage() in the update loop it does prevent it from eating ram, and craters the FPS to 26.

Ahh I hadn't realized the Lua source was in the SDK folder!

You seem to be correct, gmatch is creating garbage every frame as compiling with this modifications to graphics.lua fixes the leak:

function playdate.graphics.drawTextAligned(str, x, y, textAlignment, lineHeightAdjustment)

	local font = g.getFont()
	if font == nil then print('error: no font set!') return 0, 0 end
	local lineHeight = font:getHeight() + font:getLeading() + math.floor(lineHeightAdjustment or 0)
	local ox = x
	str = ""..str -- if a number was passed in, convert it to a string
	local styleCharacterForNewline = ""

	-- gmatch create garbage!
	-- for line in str:gmatch("[^\r\n]*") do		-- split into hard-coded lines
		local line = str
		line = _addStyleToLine(styleCharacterForNewline, line)
		
		local width = g.getTextSize(line)
		
		if textAlignment == kTextAlignment.right then
			x = ox - width
		elseif textAlignment == kTextAlignment.center then
			x = ox - (width / 2)
		end
		
		g.drawText(line, x, y)
		
		y += lineHeight
		styleCharacterForNewline = _styleCharacterForNewline(line)
	-- end
	
end

It's not garbage collection as the collection is never occurring. Here's a screen shot of a longer session. Garbage hasn't been collected yet and the RAM is 50% used.

Manually triggering garbage collection via collectgarbage() does free up memory, but this doesn't fix the underlying leak.

1 Like

This is good info. I'll mark this one for Dave.

2 Likes

I stupidly forgot about this issue then encountered it myself.

More thoughts in the thread below, such as it:

  • only happening with custom fonts (not a copy of the system font)
  • only at high frame rates (46 and higher)
  • only happening on my game over screen and not in my main game state (which is busier so I guess GC is being triggered by other things)

https://devforum.play.date/t/memory-build-up-using-font-drawtextaligned-at-high-setrefreshrate/6700

1 Like

This is still happening in 1.12.2

okay, I think I see what's happening. We're asking dlmalloc for the # of bytes allocated to decide how hard we want to push the allocator, and it turns out that can take a bit of time when there are a lot of small allocations. I didn't realize it was walking a list, had assumed it kept a tally. :confused: So by the time it's figured out how long it needs to run the collector it's already used up all the time available. I'll add a memory tally to dlmalloc and I'll also look at why drawTextAligned is generating so much garbage.

2 Likes

I've been experiencing a similar issue using drawLocalizedTextInRect, though it was a lot more extreme; if i called it in update without a conditional, memory would run out extremely quickly. i've modified it to only call when absolutely needed, but i still lose about .5% of memory for each text box generated. Not much, but could be an issue in the future, especially on repeat playthroughs