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
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.
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.
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. 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.
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