Search code examples
memory-leaksluacrashcoronasdk2d-games

Corona SDK / Lua / memory leak


So I have this rain module for a game that I am developing, which is causing a massive system memory leak, which leads to lag and ultimately crash of the application. The function "t.start" is called with a timer every 50 ms.

Though I I've tried I can't really find the cause for this! Maybe I am overlooking something but I can't help it. As you see I niled out the graphics related locals...Does anyone notice something?

As a secondary issue : Does anyone have tips on preloading next scene for a smooth scene change? Because the loading itself is causing a short freeze when I put it in "scene:show()"...

Thanks for your help! Greetings, Nils

local t = {}
    local composer = require("composer")
    t.drops = {}

    function t.fall(drops, group)
        for i = 1, #drops, 1 do
            local thisDrop = drops[i]
            function thisDrop:enterFrame()
                if aboutToBeDestroyed == true then
                    Runtime:removeEventListener("enterFrame", self)
                    return true
                end
                local randomY = math.random(32, 64)
                if self.x ~= nil then
                    self:translate(0, randomY)
                    if self.y > 2000 then
                        self:removeSelf()
                        Runtime:removeEventListener("enterFrame", self)
                        self = nil
                    end
                end
            end
            Runtime:addEventListener("enterFrame", drops[i])
            thisDrop = nil
        end
    end

    t.clean = function()
        for i = 1, #t.drops, 1 do
            if t.drops[i] ~= nil then
                table.remove(t.drops, i)
                t.drops[i] = nil
            end
        end
    end

    function t.start(group)
        local drops = {}
        local theGroup = group
        for i = 1, 20, 1 do
            local randomWidth = math.random(5, 30)
            local dropV = display.newRect(group, 1, 1, randomWidth, 30)
            local drop1 = display.newSnapshot(dropV.contentWidth , dropV.contentHeight * 3)
            drop1.canvas:insert(dropV)
            drop1.fill.effect = "filter.blurVertical"
            drop1.fill.effect.blurSize = 30
            drop1.fill.effect.sigma = 140
            drop1:invalidate("canvas")
            drop1:scale(0.75, 90)
            drop1:invalidate("canvas")
            drop1:scale(1, 1 / 60)
            drop1:invalidate("canvas")
            local drop = display.newSnapshot(drop1.contentWidth * 1.5, drop1.contentHeight)
            drop.canvas:insert(drop1)
            drop.fill.effect = "filter.blurHorizontal"
            drop.fill.effect.blurSize = 10
            drop:invalidate("canvas")
            drop.alpha = 0.375
            local randomY = math.random(-500, 500)
            drop.y = randomY
            drop.anchorY = 0
            drop.x = (i - 1) * 54
            drops[i] = drop
            table.insert(t.drops, drop)
            local dropV, drop1, drop = nil
        end
        composer.setVariable("drops", t.drops)
        t.fall(drops, group)
        drops = nil
        t.clean()
    end
return t

EDIT : I found out that it definitely has something to do with the nested snapshots, which are created for the purpose of applying filter effects. I removed one snapshot, so that I only have a vector object inside a snapshot and voila : memory increases way slower. The question is : why?


Solution

  • Generally, you don't need enterFrame event at all - you can simply do transition from start point (math.random(-500, 500)) to end point (2000 in your code). Just randomise speed and use onComplete handler to remove object

    local targetY = 2000
    local speedPerMs = math.random(32, 64) * 60 / 1000
    local timeToTravel = (targetY - randomY) / speedPerMs
    transition.to( drop, {
    time = timeToTravel,
        x = xx,
        y = targetY,
        onComplete = function()
            drop:removeSelf()
        end
    } )
    

    Edit 1: I found that with your code removing drop is not enough. This works for me:

    drop:removeSelf()
    dropV:removeSelf()
    drop1:removeSelf()
    

    Some ideas about memory consumption:

    1) Probably you can use 1 enterFrame handler for array of drops - this will reduce memory consumption. Also don't add methods to local objects like 'function thisDrop:enterFrame()' - this is not optimal here, because you creating 20 new functions every 50 ms

    2) Your code creates 400 'drop' objects every second and they usually live no more than ~78 frames (means 1.3 sec in 60fps environment). Better to use pool of objects and reuse existing objects

    3) enterFrame function depends on current fps of device, so your rain will be slower with low fps. Low fps -> objects falls slower -> more objects on scene -> fps go down. I suggest you to calculate deltaTime between 2 enterFrame calls and ajust falling speed according to deltaTime

    Edit 2 Seems like :removeSelf() for snapshot didn't remove child object. I modified your code and memory consumption drops a lot

    if self.y > 2000 then
        local drop1 = self.group[1]
        local dropV = drop1.group[1]
        dropV:removeSelf()
        drop1:removeSelf()
        self:removeSelf()
        Runtime:removeEventListener("enterFrame", self)
        self = nil
    end