Scripts: passing 'self' around vs local state variable

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?

Thank you,
Carlos Silva

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.

4 Likes

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)

Thank you

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).

1 Like

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.

Cheers,
Carlos Silva