Corona SDK Pro Tip of the Day #20
Automatic local variables

Accessing global variables is more expensive than accessing their local copies. You probably familiar with this example:

-- Slow
local value = 0  
for i = 1, 10000 do  
    value = value + math.sqrt(i)
end

-- Fast
local mSqrt = math.sqrt  
local value = 0  
for i = 1, 10000 do  
    value = value + mSqrt(i)
end  

Having math.sqrt localized as mSqrt improves performance. However I keep telling that most of the time it doesn't make a difference.

BUT, my today's Pro Tip is more about usability rather than performance. You know that making global variables is a bad practice, so you have to put them into some module and access them with it's help, like:

local myGlobals = require('my_globals')  
-- Read
local someVariabe = myGlobals.someVariable  
print(someVariabe)

-- Set
local anotherVariabe = 42  
myGlobals.anotherVariabe = anotherVariabe  

Or if you are concerned with performance, you'd write something like

local mSqrt, mFloor, mCeil = math.sqrt, math.floor, math.ceil  

And so on. It gets tedious. It always troubled me. So several months ago I tried to automate this process, I came up with a solution, but it only worked in the Simulator and not on a device. So I abandoned this for a while.

Until recently Ed Maurina emailed me about this problem and if I know a solution for it. I gave him my code, but to my surprise it worked on devices! Either Corona has changed something or my code just had to wait a little like a good wine.

Ed has written a blog post on his website about it. You can go on and read it.

Thanks Ed for reminding me about this!

So I returned to my code and finished it to the state I always wanted.

local _M = {}

local locals = {  
    _W = display.contentWidth,
    _H = display.contentHeight,
    _T = display.screenOriginY,
    _B = display.viewableContentHeight - display.screenOriginY,
    _L = display.screenOriginX,
    _R = display.viewableContentWidth - display.screenOriginX,
    _CX = math.floor(display.contentWidth * 0.5),
    _CY = math.floor(display.contentHeight * 0.5),
    mFloor = math.floor,
    tInsert = table.insert,
    mCeil = math.ceil,
    mFloor = math.floor,
    mAbs = math.abs,
    mAtan2 = math.atan2,
    mSin = math.sin,
    mCos = math.cos,
    mPi = math.pi,
    mSqrt = math.sqrt,
    mRandom = math.random,
    tInsert = table.insert,
    tRemove = table.remove,
    tForeach = table.foreach,
    tShuffle = table.shuffle,
    sSub = string.sub,
    sLower = string.lower}
locals._SW = locals._R - locals._L  
locals._SH = locals._B - locals._T

function _M.setLocals()  
    local i = 1
    repeat
        local k, v = debug.getlocal(2, i)
        if k and v == nil then
            if locals[k] ~= nil then
                debug.setlocal(2, i, locals[k])
            else
                error('No value for a local variable: ' .. k, 2)
            end
        end
        i = i + 1
    until not k
end

return _M  

And I use it like this in my applications. I put it first in any Lua files that use it:

local _T, _B, _L, _R, _CX, _CY, _SW, _SH  
local app = require('lib.app')  
app.setLocals()  

setLocals() function looks what local variables are defined above it and assigns values to them according to the locals table in my app.lua. If it doesn't find a value for a variable, it raises an error.

This is possible because of two functions debug.setgetlocal() and debug.setlocal().

Also it's important to know that to achieve best performance you have to set local variables in functions as well.

local mSqrt  
local app = require('lib.app')  
app.setLocals()

local function doStuff()  
    -- Localize again
    local mSqrt = mSqrt
    local value = 0
    for i = 1, 10000 do
        value = value + mSqrt(i)
    end
end  

Localizing variabels helps in heavy computation modules and functions. Common examples are particle engines and tile engines. These things must be as fast as possible.

Indie Game Developer