Corona SDK Pro Tip of the Day #28
EnterFrame manager

Often you need to use enterFrame listeners in your game. Sometimes you need many of them (for example one for every game object in your game) and keeping track of them becomes really difficult. You have to remove them manually or you will get runtime errors.

The code inside enterFrame is invoked each frame.

To gain more control over these listeners I've created a set of functions which can be called as "enterFrame manager."

local _M = {}

-- Call f next frame
function _M.nextFrame(f)  
    timer.performWithDelay(1, f)
end

-- For internal use
function _M.enterFrame()  
    for i = 1, #_M.enterFrameFunctions do
        _M.enterFrameFunctions[i]()
    end
end

-- Call f each frame
function _M.eachFrame(f)  
    if not _M.enterFrameFunctions then
        _M.enterFrameFunctions = {}
        Runtime:addEventListener('enterFrame', _M.enterFrame)
    end
    table.insert(_M.enterFrameFunctions, f)
    return f
end

-- Stop calling f
function _M.eachFrameRemove(f)  
    if not f or not _M.enterFrameFunctions then return end
    local ind = table.indexOf(_M.enterFrameFunctions, f)
    if ind then
        table.remove(_M.enterFrameFunctions, ind)
        if #_M.enterFrameFunctions == 0 then
            Runtime:removeEventListener('enterFrame', _M.enterFrame)
            _M.enterFrameFunctions = nil
        end
    end
end

-- Stop everything
function _M.eachFrameRemoveAll()  
    Runtime:removeEventListener('enterFrame', _M.enterFrame)
    _M.enterFrameFunctions = nil
end

return _M  

First function app.nextFrame(f) is used to call provided function only on the next frame. I use it for example when I need to remove a physics object currently being collided.

app.nextFrame(function()  
    object:removeSelf()
end)  

It's not related to enterFrame event, but still handy to keep together.

Second function _M.enterFrame() is for internal use only. This is the only function which gets attached to the Runtime. It calls all our managed functions in it's turn. So at any given moment there is maximum one real enterFrame listener.

This functions iterated over internal table _M.enterFrameFunctions and calls each function in it. If you need you can always see how many functions are there, or you can reorganize the code and this table to include tags for each function. That way you will be able to remove many functions by tag.

If you want to remove all functions and stop enterFrame listener, simply call app.eachFrameRemoveAll().

To add functions to be called each frame, you use app.eachFrame(f), it returns reference to the function being added. Save it so you can stop it later with app.eachFrameRemove(f).

Typical usage:

local app = require('lib.app')

local circle = display.newCircle(100, 100, 10)

function circle:startRotate()  
    self.fId = app.eachFrame(function()
        self:rotate(1)
    end)
end

function circle:finalize()  
    app.eachFrameRemove(self.fId)
end  
circle:addEventListener('finalize')

circle:startRotate()  

More on enterFrame listeners here.

Indie Game Developer