Proper Object Deletion / memory leaks

I’m a relative newbie to Corona but have worked with Codea before (and so am reasonably familiar with lua).  I’m trying to get the hang of object deletion in Corona, since it works very differently to Codea.  I have some “bullet” code that creates bullet objects (instances of a class) but which of course I want to be completely deleted when they leave the screen, to free up the memory.

I’m monitoring the memory, but I’m clearly not destroying each bullet entirely because in the following scene file the memory just trickles up indefinitely.  I’m doing my best to destroy the display object, the physics object, and all transitions associated with the object, in the checkEverything function but the memory still trickles up.  Not very fast, but I can’t tell if this is to be expected or not?

If you’re wondering about where my class code comes from I’m shamelessly nicking it from Codea since that’s what I’m used to!

Is there anything else I should be doing?  Or is there a more “Corona”-y way to achieve the same result that I’m missing?

https://bitbucket.org/TwoLivesLeft/core/wiki/CodeaClasses

[lua]


– level1.lua


local composer = require( “composer” )

local scene = composer.newScene()

local physics = require “physics”

physics.start(); physics.pause()


– forward declarations and other locals

local screenW, screenH, halfW = display.contentWidth, display.contentHeight, display.contentWidth*0.5

local fireState = false

local bullets = {}

local Bullet

local class = function(base)

  local c = {}    – a new class instance

  if type(base) == ‘table’ then

    – our new class is a shallow copy of the base class!

    for i,v in pairs(base) do

      c[i] = v

    end

    c._base = base

  end

  – the class will be the metatable for all its objects,

  – and they will look up their methods in it.

  c.__index = c

  – expose a constructor which can be called by <classname>(<args>)

  local mt = {}

  mt.__call = function(class_tbl, …)

    local obj = {}

    setmetatable(obj,c)

    if class_tbl.init then

      class_tbl.init(obj,…)

    else 

      – make sure that any stuff from the base class is initialized!

      if base and base.init then

        base.init(obj, …)

      end

    end

    return obj

  end

  c.is_a = function(self, klass)

    local m = getmetatable(self)

    while m do 

      if m == klass then return true end

      m = m._base

    end

    return false

  end

  setmetatable(c, mt)

  return c

end

local function fireCannon()

  table.insert(bullets, Bullet())

end

local function checkEverything()

  for i = #bullets, 1, -1 do

    if bullets[i]:checkPos() then

      physics.removeBody(bullets[i].ellipse)

      bullets[i].ellipse:removeSelf()

      transition.cancel(bullets[i].ellipse)

      bullets[i].ellipse = nil

      table.remove(bullets, i)

     end

   end

   collectgarbage()

end

function scene:create( event )

  – Called when the scene’s view does not exist.

  – 

  – INSERT code here to initialize the scene

  – e.g. add display objects to ‘sceneGroup’, add touch listeners, etc.

  local sceneGroup = self.view

  – create a grey rectangle as the backdrop

  local background = display.newRect( 0, 0, screenW, screenH )

  background.anchorX = 0

  background.anchorY = 0

  background:setFillColor( 0 )

  sceneGroup:insert( background )

  timer.performWithDelay( 100, fireCannon , -1 )

  timer.performWithDelay(1000, checkEverything, -1)

end

function scene:show( event )

  local sceneGroup = self.view

  local phase = event.phase

  if phase == “will” then

    – Called when the scene is still off screen and is about to move on screen

  elseif phase == “did” then

    physics.start()

  end

end

function scene:hide( event )

  local sceneGroup = self.view

  local phase = event.phase

  if event.phase == “will” then

    – Called when the scene is on screen and is about to move off screen

    –

    – INSERT code here to pause the scene

    – e.g. stop timers, stop animation, unload sounds, etc.)

    physics.stop()

  elseif phase == “did” then

    – Called when the scene is now off screen

  end

end

function scene:destroy( event )

  – Called prior to the removal of scene’s “view” (sceneGroup)

  – 

  – INSERT code here to cleanup the scene

  – e.g. remove display objects, remove touch listeners, save state, etc.

  local sceneGroup = self.view

  package.loaded[physics] = nil

  physics = nil

end

Bullet = class()

function Bullet:init()

  self.ellipse = display.newCircle( display.contentWidth/2, display.contentHeight - 250, 10 )

  self.ellipse.stroke = {1,1,1}

  self.ellipse.strokeWidth = 1

  self.ellipse.fill = {1,0,0}

  transition.to(self.ellipse, {time = 1000, y = -100})

  physics.addBody( self.ellipse, “static”, { radius=10, density = 0} )

end

function Bullet:checkPos()

  if self.ellipse.x < -100 or (self.ellipse.x > (display.contentWidth + 100)) or

  self.ellipse.y < -100 or (self.ellipse.y > (display.contentWidth + 100)) then

    return true

  end

  return false

end

local monitorMem = function()

  collectgarbage()

  print( "MemUsage: " … collectgarbage(“count”) )

  local textMem = system.getInfo( “textureMemoryUsed” ) / 1000000

  print( "TexMem: " … textMem )

end

Runtime:addEventListener( “enterFrame”, monitorMem )

– Listener setup

scene:addEventListener( “create”, scene )

scene:addEventListener( “show”, scene )

scene:addEventListener( “hide”, scene )

scene:addEventListener( “destroy”, scene )

return scene

[/lua]

When you initialise your bullets you can keep track of the transition by putting

self.trans = transition.to...

then when you want to stop the transition in your checkEverything function, you can put

transition.cancel(bullets[i].trans) bullets[i].trans = nil

Currently, you are calling transition.cancel on a nil value. i.e. the display object you just removed.

Also, you are transitioning your ellipses to point y = - 100, and your checkPos function checks whether y is less than -100, which it won’t ever be because that is where the transition ends.

Thanks, @hasty - it’s really obvious when you put it that way!  Memory leaks eliminated.

When you initialise your bullets you can keep track of the transition by putting

self.trans = transition.to...

then when you want to stop the transition in your checkEverything function, you can put

transition.cancel(bullets[i].trans) bullets[i].trans = nil

Currently, you are calling transition.cancel on a nil value. i.e. the display object you just removed.

Also, you are transitioning your ellipses to point y = - 100, and your checkPos function checks whether y is less than -100, which it won’t ever be because that is where the transition ends.

Thanks, @hasty - it’s really obvious when you put it that way!  Memory leaks eliminated.