ButtonWidget causes texture memory leak

Hi guys,

I’ve found that using widget.newButton with image sheet causes texture memory to never be released later on… In attached sample project, if you uncomment creation of ButtonWidget in scene1 it will cause texture memory to never be released in scene2.

ButtonWidget is destroyed using following code:

function scene:destroyScene( event ) print("SCENE \_ 1 : Destroying") if testButton then testButton:removeSelf() testButton = nil end end

Any thoughts? I’ve also created a bug report for this, case 25855, Corona Version 2013.1193 on Mac

@snikitin, how do you create buttons?  Do you use image sheet?  If you use image sheet for your buttons, you need to nil it too.

Naomi

Edit:  BTW, I’m sorry but I didn’t download your zip file.

I create button using image sheet:

widget.newButton{ sheet = imageSheet, defaultFrame = sheetInfo:getFrameIndex("button\_1"), overFrame = sheetInfo:getFrameIndex("button\_2"), onEvent = onTestButtonEvent } 

Nilling image sheet has no effect, you can check it yourself using the example I’ve attached to original post.

Urmm, I’m not sure how you measure the texture memory, but garbage collection may not happen the moment you nil the image sheet.  From what I understand, you kind’a need to let lua do its thing and clear the texture memory when it does.  So long as loading and removing the scene repeatedly wouldn’t constantly add to the overall texture memory, i.e., so long as it doesn’t increase the total texture memory every time you enter the same scene, then I think it should be okay. 

Naomi

Well, if you don’t want to look up the code I’ve attached, here are the sources:

main.lua:

local storyboard = require "storyboard" storyboard.gotoScene( "scene1" ) 

scene1.lua:

local storyboard = require "storyboard" local scene = storyboard.newScene() local widget = require "widget" local sheetInfo local imageSheet local testButton local unimportantImage local function onTestButtonEvent(event) end function scene:createScene( event ) local group = self.view sheetInfo = require "sheets.test" imageSheet = graphics.newImageSheet("sheets/test.png", sheetInfo:getSheet()) --- Uncomment this section to see how ButtonWidget causes Texture Memory not to be released in the next scene testButton = widget.newButton{ sheet = imageSheet, defaultFrame = sheetInfo:getFrameIndex("button\_1"), overFrame = sheetInfo:getFrameIndex("button\_2"), onEvent = onTestButtonEvent } testButton:setReferencePoint(display.CenterReferencePoint) testButton.x, testButton.y = display.contentWidth \* 0.5, display.contentHeight \* 0.5 group:insert(testButton) unimportantImage = display.newImageRect(imageSheet, sheetInfo:getFrameIndex("image\_1"), 10, 10) unimportantImage:setReferencePoint(display.TopCenterReferencePoint) unimportantImage.x, unimportantImage.y = display.contentWidth \* 0.2, display.contentHeight \* 0.2 group:insert(unimportantImage) end function scene:enterScene( event ) print("SCENE \_ 1 : Texture Memory used = ", system.getInfo("textureMemoryUsed") / 1024 .. "Kb") timer.performWithDelay(2000, function() storyboard.gotoScene("scene2") end) end function scene:exitScene( event ) end function scene:destroyScene( event ) print("SCENE \_ 1 : Destroying") if testButton then testButton:removeSelf() testButton = nil end sheetInfo = nil imageSheet = nil end scene:addEventListener( "createScene", scene ) scene:addEventListener( "enterScene", scene ) scene:addEventListener( "exitScene", scene ) scene:addEventListener( "destroyScene", scene ) return scene

scene2:lua:

local storyboard = require "storyboard" local scene = storyboard.newScene() local widget = require "widget" function scene:createScene( event ) local group = self.view end function scene:enterScene( event ) storyboard.removeAll() collectgarbage() timer.performWithDelay(2000, function() collectgarbage() print("SCENE \_ 2 : Texture Memory used = ", system.getInfo("textureMemoryUsed") / 1024 .. "Kb") end, 0) end function scene:exitScene( event ) end function scene:destroyScene( event ) end scene:addEventListener( "createScene", scene ) scene:addEventListener( "enterScene", scene ) scene:addEventListener( "exitScene", scene ) scene:addEventListener( "destroyScene", scene ) return scene

You are loading the imagesheet in your createScene but you are not removing it in the destroyScene. Making it nil does not remove it from memory. The imagesheet is separate from your widgets, therefore the widget wouldnt remove the entire sheet from memory, only it’s own references to it.

imagesheet:removeSelf() imagesheet = nil

Ah, good point, @Anderoth.  I always thought it odd that nil’ing could clear out the loaded image sheet.  In an old tutorial that was posted way back when graphics.newImageSheet() was first added to Corona SDK, I believe I read that all I needed to do was nil’ing the image sheet – but it looks like this old tutorial is now obsolete and removed.  And truly, it makes good sense to remove it first before nilling it.  Thanks.

Naomi

Hi Anderoth, Naomi,

Just for clarification, an image sheet is not “remove-able” via the typical methods ( removeSelf(), display.remove() ). It’s a userdata object, so does not apply to the display and removal from it. Proper removal should just involved nil’ing it out, as you recall from the old blog post Naomi.

Brent

Ah, okay, thank you, Brent.  So, just nil’ing would do the job then (and I was about to go back and change all of my code relating to image sheet disposal.)  I’m so glad you let us know just in time.  Now I don’t need to change my code.  * sigh of relief *

Naomi

You’re welcome Naomi! I have tested and confirmed that there is no memory leak if you properly nil out an image sheet.

Brent

Hi Brent,

What do you mean - no memory leak? So for you, texture memory usage that is printed in scene2 is zero? For me it’s not, event when image sheet is NILed in destroyScene in scene1. Maybe you haven’t uncommented creation of widget in scene1?

Also, guys, if you don’t know exactly what are you talking about, please defer from posting comments - it only pollutes the thread…

Hi @snikitin17,

I’ll probably need to test your actual code tomorrow. The issue is likely a scoping/cleanup error in your Storyboard setup, as the others in this post have suggested. My statement is based off core testing that I did internally, and recently, which shows that if an image sheet is properly nil’led out, there is no memory leak. That’s why I responded to Naomi and Anderoth to clarify that very specific point.

Best regards,

Brent

Thanks a lot Brent!

I am looking forward to your answer  :slight_smile:

Hi all,

I think I’ve figured out what is going on. Basically, you cannot rely on the memory being freed even after explicit collectgarbage call. Texture memory is redeemed later on correctly, there is no problem with button widgets.

@snikitin, how do you create buttons?  Do you use image sheet?  If you use image sheet for your buttons, you need to nil it too.

Naomi

Edit:  BTW, I’m sorry but I didn’t download your zip file.

I create button using image sheet:

widget.newButton{ sheet = imageSheet, defaultFrame = sheetInfo:getFrameIndex("button\_1"), overFrame = sheetInfo:getFrameIndex("button\_2"), onEvent = onTestButtonEvent } 

Nilling image sheet has no effect, you can check it yourself using the example I’ve attached to original post.

Urmm, I’m not sure how you measure the texture memory, but garbage collection may not happen the moment you nil the image sheet.  From what I understand, you kind’a need to let lua do its thing and clear the texture memory when it does.  So long as loading and removing the scene repeatedly wouldn’t constantly add to the overall texture memory, i.e., so long as it doesn’t increase the total texture memory every time you enter the same scene, then I think it should be okay. 

Naomi

Well, if you don’t want to look up the code I’ve attached, here are the sources:

main.lua:

local storyboard = require "storyboard" storyboard.gotoScene( "scene1" ) 

scene1.lua:

local storyboard = require "storyboard" local scene = storyboard.newScene() local widget = require "widget" local sheetInfo local imageSheet local testButton local unimportantImage local function onTestButtonEvent(event) end function scene:createScene( event ) local group = self.view sheetInfo = require "sheets.test" imageSheet = graphics.newImageSheet("sheets/test.png", sheetInfo:getSheet()) --- Uncomment this section to see how ButtonWidget causes Texture Memory not to be released in the next scene testButton = widget.newButton{ sheet = imageSheet, defaultFrame = sheetInfo:getFrameIndex("button\_1"), overFrame = sheetInfo:getFrameIndex("button\_2"), onEvent = onTestButtonEvent } testButton:setReferencePoint(display.CenterReferencePoint) testButton.x, testButton.y = display.contentWidth \* 0.5, display.contentHeight \* 0.5 group:insert(testButton) unimportantImage = display.newImageRect(imageSheet, sheetInfo:getFrameIndex("image\_1"), 10, 10) unimportantImage:setReferencePoint(display.TopCenterReferencePoint) unimportantImage.x, unimportantImage.y = display.contentWidth \* 0.2, display.contentHeight \* 0.2 group:insert(unimportantImage) end function scene:enterScene( event ) print("SCENE \_ 1 : Texture Memory used = ", system.getInfo("textureMemoryUsed") / 1024 .. "Kb") timer.performWithDelay(2000, function() storyboard.gotoScene("scene2") end) end function scene:exitScene( event ) end function scene:destroyScene( event ) print("SCENE \_ 1 : Destroying") if testButton then testButton:removeSelf() testButton = nil end sheetInfo = nil imageSheet = nil end scene:addEventListener( "createScene", scene ) scene:addEventListener( "enterScene", scene ) scene:addEventListener( "exitScene", scene ) scene:addEventListener( "destroyScene", scene ) return scene

scene2:lua:

local storyboard = require "storyboard" local scene = storyboard.newScene() local widget = require "widget" function scene:createScene( event ) local group = self.view end function scene:enterScene( event ) storyboard.removeAll() collectgarbage() timer.performWithDelay(2000, function() collectgarbage() print("SCENE \_ 2 : Texture Memory used = ", system.getInfo("textureMemoryUsed") / 1024 .. "Kb") end, 0) end function scene:exitScene( event ) end function scene:destroyScene( event ) end scene:addEventListener( "createScene", scene ) scene:addEventListener( "enterScene", scene ) scene:addEventListener( "exitScene", scene ) scene:addEventListener( "destroyScene", scene ) return scene

You are loading the imagesheet in your createScene but you are not removing it in the destroyScene. Making it nil does not remove it from memory. The imagesheet is separate from your widgets, therefore the widget wouldnt remove the entire sheet from memory, only it’s own references to it.

imagesheet:removeSelf() imagesheet = nil

Ah, good point, @Anderoth.  I always thought it odd that nil’ing could clear out the loaded image sheet.  In an old tutorial that was posted way back when graphics.newImageSheet() was first added to Corona SDK, I believe I read that all I needed to do was nil’ing the image sheet – but it looks like this old tutorial is now obsolete and removed.  And truly, it makes good sense to remove it first before nilling it.  Thanks.

Naomi