Removing a previously set background drawing callback results in a crash

The SDK provides a convenience for drawing a background via playdate.graphics.sprite.setBackgroundDrawingCallback(drawCallback). However, there is no documented method for removing it once set. I can achieve the result I'm after by setting it to a no-op function (function() end) but that feels like a hack. Given that it's a callback, I expect to be able to just set it to nil, as most executors of optional callbacks will check for nil before attempting to call them. However, calling setBackgroundDrawingCallback(nil) results in a runtime crash.

I'm honestly not sure if this is a bug, a feature request, or just a misuse of the API on my part, so please let me know if this belongs under another topic. My use case is setting a background for various levels, but sometimes a background isn't needed and I'm looking for the recommended way to clear it.

1 Like

This method is often misunderstood as being a little more magical than it really is; all it does is create and configure a new sprite, and assigns drawCallback to the new sprite's drawing callback. (it's defined in CoreLibs/sprite.lua, and actually the entire source is right in the documentation if you would like to see what it does (See: Inside Playdate).

The newly-created sprite is returned from the function, so to remove it you can call :remove():

local bgSprite = playdate.graphics.sprite.setBackgroundDrawingCallback(drawCallback)
...
bgSprite:remove()

That said, I think you're right that passing nil should remove a previously-set background drawing callback. Looking at this also made me realize that if this method is called more than once, you'll end up with multiple background sprites, which feels like a bug to me. I'll file an issue and get a fix ready for this stuff as soon as possible!

5 Likes

Thanks Dan, that's clarifying. And I apologize, I should have dug a little deeper to check my assumptions. In fact, I did go read the docs, and the main documentation (in bright white text) actually supported my assumption that this would just configure the inner draw loop for the background object. I then glossed over the implementation details section, which would have corrected my seemingly reinforced but ultimately incorrect understanding.

That said, the behavior does go somewhat against my expectations! I think naming plays a part in that, because the side effect of creating a new sprite instance is non-obvious. My mental model was that this would set, overwrite, and hopefully "unset" the background drawing routine for a singular, static background object. The only "magic" I assumed was that the background object would live outside of the standard sprite list, as a singular special-case object which you could update the drawing routine for at any time.

Even if the functionality still manifests as a regular sprite, updating the behavior to avoid creating multiple instances, and to support passing nil (either to remove it, or at least result in a no-op draw handler that appears like a cleared background) sounds like an improvement. Thanks again!

2 Likes

I agree that the naming of the method suggests the usage you were expecting, and that's how it should behave.

I think this will be the new implementation, if you want to replace the implementation in CoreLibs before it makes it into an SDK release:

function spritelib.setBackgroundDrawingCallback(drawCallback)
	
	if drawCallback == nil then
		if bgsprite ~= nil then
			bgsprite:remove()
			bgsprite = nil
		end
		return nil
	end
	
	if bgsprite == nil then
		bgsprite = gfx.sprite.new()
		bgsprite:setSize(playdate.display.getSize())
		bgsprite:setCenter(0, 0)
		bgsprite:moveTo(0, 0)
		bgsprite:setZIndex(-32768)
		bgsprite:setIgnoresDrawOffset(true)
		bgsprite:setUpdatesEnabled(false)
		bgsprite:add()
	end
	
	bgsprite.draw = function(s, x, y, w, h)
		drawCallback(x, y, w, h)
	end
	
	return bgsprite
end
5 Likes

I just ran into the same problem: Calling setBackgroundDrawingCallback with a nil value will lead to problems - #2 by matt

So I would like to know what the status of this issue is.

If the implementation is not changed, then at least the documentation (and maybe examples using this) should reflect that the result of the call can and should be used (to remove it and avoid having multiple backgrounds).
I feel like that is not obvious without looking at the implementation (which few people probably do).