Proxy

Proxy is a function (as used in Princess by vrld, and a part of love-boilerplate by Headchant) which can simplify using resources in LÖVE.

For example, instead of writing:

background = love.graphics.newImage('img/background.png')
love.graphics.draw(background, 0, 0)

you can simply write:

love.graphics.draw(Image.background, 0, 0)

Using Proxy

Proxies are set up like this:

Image = Proxy(function(k) return love.graphics.newImage('img/' .. k .. '.png') end)
Sfx   = Proxy(function(k) return love.audio.newSource('sfx/' .. k .. '.ogg', 'static') end)
Music = Proxy(function(k) return love.audio.newSource('music/' .. k .. '.ogg', 'stream') end)

Resources can now be used like this:

-- Creates an new image from 'img/rainbow.png' and draws it.
love.graphics.draw(Image.rainbow, 123, 123)

-- Creates a new audio source from 'music/smoothjazz.ogg' and sets it to loop.
Music.smoothjazz:setLooping(true)

When resources are used for the first time, they're stored. When they're used afterwards, the stored versions are used.

-- Creates a new audio source from 'sfx/sparkle.ogg' and plays it.
Sfx.sparkle:play()

-- Plays 'sfx/sparkle.ogg' again, and doesn't need to create it.
Sfx.sparkle:play()

The Proxy function

function Proxy(f)
    return setmetatable({}, {__index = function(self, k)
        local v = f(k)
        self[k] = v
        return v
    end})
end

Haha okay what's going on

To understand all this, let's

Lua things

setmetatable takes two arguments: The table to set the metatable of, and the metatable to set to it. It returns the table which it set the metatable of.

The __index metamethod is called when a table with this metamethod is accessed at an index at which it doesn't have anything at. It can be set to a function which takes two arguments, which will be: The table this metamethod was called from, and the value it attemped to index.

local variables are only accessible within the function they are defined in, so as not to disrupt the values of any global variables with the same name.

What Proxy does

A run-through

function Proxy(f)
    return setmetatable({}, {__index = function(self, k)
        local v = f(k)
        self[k] = v
        return v
    end})
end

Image = Proxy(function(k) return love.graphics.newImage('img/' .. k .. '.png') end)

love.graphics.draw(Image.rainbow, 123, 123)

So, what's going on?

Image is being indexed with the string 'rainbow'.

Is there an image at Image.rainbow? If so, it's drawn. If not, what happens?

Image is whatever is returned by Proxy(function(k) return love.graphics.newImage('img/' .. k .. '.png') end).

What does it return?

function Proxy(f)
    return setmetatable({}, {__index = function(self, k) ---[[etc.]] end)
end

It returns what setmetable returns, which is its first argument, an empty table. This table has a metatable, with the metamethod __index.

What does this __index metamethod do?

Let's see what happens when we call Proxy with the argument it is being called with.

function Proxy(function(k) return love.graphics.newImage('img/' .. k .. '.png') end)
    return setmetatable({}, {__index = function(self, k)
        local v = f(k)
        self[k] = v
        return v
    end})
end

f is the argument given to Proxy, so local v = f(k) can be worked out too.

The argument returns love.graphics.newImage('img/' .. k .. '.png')

function Proxy(function(k) return love.graphics.newImage('img/' .. k .. '.png') end)
    return setmetatable({}, {__index = function(self, k) 
        local v = love.graphics.newImage('img/' .. k .. '.png')
        self[k] = v
        return v
    end})
end

The __index metamethod, as shown above, ends up as this:

function(self, k) 
    local v = love.graphics.newImage('img/' .. k .. '.png')
    self[k] = v
    return v
end

So, If Image.rainbow dosen't exist, this __index metamethod is run, with the argument self being the table Image and the argument k being the string 'rainbow'.

function(Image, 'rainbow')
    local v = love.graphics.newImage('img/' .. 'rainbow' .. '.png')
    Image['rainbow'] = v
    return v
end

v is the new image resouce, from the file at 'img/rainbow.png', and it's returned at the end of the function.

So when Image.rainbow is used, this __index metamethod is called, and the new image is returned!

Image['rainbow'] = v sets the entry of the table Image at the index rainbow to the value of v, the image.

Image didn't have any value at the index 'rainbow' before now, so why isn't the __index metamethod called again here? It's because the __index metamethod is only run when the value at the index of the table has attempted to be accessed, not when it has attempted to be set.

So now the actual image is stored in Image.rainbow!

After the first time Image.rainbow is used, the rainbow entry of Image is the image, and not nil, and therefore the __index metamethod isn't called and it uses the stored image.



Copyright is waived on all original parts of this code walkthrough.