Not sure if this was asked before, but here it goes.
Most of the script custom function examples we see have ‘self’ as a parameter.
Lets say we have:
function init(self)
self.state = { score = 0 }
end
function update_score(self, value)
self.state.score = value
end
So to be able to call update_score, we need to do it where we have a ‘self’ reference.
The state lives in ‘self’, so we keep passing it around.
Lua allows the use of local variables, visible to all the code in a file.
This is quite powerful, as it hides data (or functions) that otherwise would be visible outside.
Lets see this alternative to the code above:
local state = {}
function init(self)
state.score = 0
end
function update_score(value)
state.score = value
end
I like this approach, as it has some advantages:
we don’t need to ‘polute’ all the functions with the extra argument self
functions that update the state can be totally decoupled from ‘self’ reference
state is hidden in a local variable, only visible by the script where is declared
Is there any downside relative to the engine (memory management, etc…) of the 2nd approach compared to the 1st?
Is there a very good reason to keep stuffing things in ‘self’ and passing it around in functions?
If you have two instances of the same script with the local state variable both of these scripts will share the same state. The local variable is local to the script, not the script instance.
Clear.
I expected something like that.
Using a local variable is not suitable in situations where you can have 2 instances of the same script affecting data.
It can still be used in a case where only one script instance makes sense, or when “state” does not contain really mutable data, but only references to other local functions (ex: message or input dispatchers)
A way to do this is to use a closure, for example:
local function setupthing()
local a = 0
local function set_a(v) a = a + v end
local function get_a() return a end
return {set_a=set_a,get_a=get_a}
end
function init(self)
self.thing1 = setupthing()
self.thing1.set_a(7)
self.thing2 = setupthing()
self.thing2.set_a(47)
print("things",self.thing1.get_a(),self.thing2.get_a())
end
If this script is attached to two game objects you get: 2 DEBUG:SCRIPT: things 7 47 (2 => separate instance in each GO).
The up value ‘a’ is the required ‘hidden local variable’; whereas a local variable of the script is not hidden (the scope is the whole script).
Indeed that way we can trully hide a variable, only exposing functions which handle it directly.
That truly solves the encapsulation problem.
I think i devised a way to solve the other problem: having to pass ‘self’ with the state in all the functions.
Defold go.get_id() allows the same call to get a different value depending from which game object we are calling from. So at any moment we can get a value wich is unique per game object.
Using this with your closure hack, i just build this:
Stash = {}
Stash.__index = Stash
local function handler()
local stash = {}
local get = function()
local ref = go.get_id() --".", "ref")
local map = stash[ref]
if map == nil then
map = {}
stash[ref] = map
end
return map
end
return get --{ get = get }
end
function Stash.create()
local self = {
map = handler()
}
return setmetatable(self, Stash )
end
function Stash:set(key, value)
local map = self.map()
map[key] = value
end
function Stash:get(key)
local map = self.map()
return map[key]
end
Then, we can have a script such as :
require 'modules.stash'
local stash = Stash.create()
function init(self)
stash:set("id", go.get_id())
print(stash:get("id"))
end
which on a collection with 2 game objects (named go1 and go2) with this script yields the result:
DEBUG:SCRIPT: hash: [/go1]
DEBUG:SCRIPT: hash: [/go2]
The same script keeps distinct states for different GOs, without attaching anything to game object´s self, and then we don’t need to pass it around.
Might be a bit overengineered, but its an interesting solution.