Reputation: 896
I got several objects in several tables. Multiple functions alter and handover the objects to other functions.
Lets say my table is this:
objectTable = {obj1, obj2, obj3}
otherobjTable = {objA, objB, objC, objD}
And let's say these are initialized at the main.lua.
Now, when tracing obj1, it's altered by a function, which alters it and gives an reference to another function, which again, alters it. One step could look like:
function()
if something then func(obj_1)
elseif something else then func(obj_2)
elseif something other then func(obj_3)
//... and so on...
end
function func(received_Object)
if something then
table.insert(received_Object, a value)
end
callAnotherFunction(received_Object)
end
function callAnotherFunction(received_Object)
if input == "Delete it" then
local name = received_Object.name
received_Object = nil
return string.format("%s was deleten", name)
else
return false
end
end
The problem now is, after received_Object = nil, the reference points nil but the object still exists. How can I get it deleted for sure?
Upvotes: 4
Views: 12115
Reputation: 5525
In Lua, certain types (like tables) are always passed by reference, while other types (like numbers) are always passed by value.
Moreover, Lua is a language where memory is managed by garbage collector. Garbage collector deletes an object (a table, for instance), when there are no more references (let's call them anchors) to it.
Now this code:
local t = {}
local t1 = {t}
someFunc(t)
creates three anchors to that table. When someFunc
passes another that table as an argument to another function, a fourth anchor will be created (in form of that function's local variable/argument).
In order for a garbage collector to sweep the first table, all those references must be gone (either by assigning nil
or by going out of scope).
It's important to understand, that when you assign nil
to the local t
, it does not mean that the table will be deleted. Even less so that all references to that table will be invalidated. It means that you're just freeing this one anchor, which is just one of four at that point.
Possible solution
One possible solution would be to pass around the table holding your object along with the index/key, at which the object is stored:
function func(myTable, myKey)
...
end
Now if in this function you do this:
myTable[myKey] = nil
(and no other anchors get created), the object under the key will have no more references pointing to it and will be marked for sweeping by the garbage collector the next time round. Of course, callAnotherFunction
would have to be modified in the same way as well:
callAnotherFunction(myTable, myKey)
...
end
If you perform many operations on that objects within those functions, you may cache it into a local variable to avoid several table lookups. This is ok, as when the function finishes, the anchor will be cleared along with the local variable:
callAnotherFunction(myTable, myKey)
local myObj = myTable[myKey]
...
if myCondition then myTable[myKey] = nil end
end --here myObj is no longer valid, so the anchor is gone.
Another solution
Since you can't afford to change your code as much as suggested above, you could implement the following logic:
Create a metatable for tables that hold your objects:
local mt = {
__newindex = function(tab, key, val)
--if necessary and possible, perform a check, if the value is in fact object of your type
val.storeTableReference(tab, key) --you'll have to implement this in your objects
rawset(tab, key, val);
end
}
local container1 = setmetatable({}, mt)
local container2 = setmetatable({}, mt)
Now when you insert an object into that table:
container1.obj1 = obj1
container2.obj1 = obj1
each time the __newindex metamethod will invoke obj1.storeTableReference
with appropriate references. This function will store those references in (for instance) an internal table.
The only thing that's left to implement is a method for your object that frees those references:
myObj:freeReferences = function()
for k, v in ipairs(self:tableReferences) do --assuming that's where you store the references
k[v] = nil
end
tableReferences = {} --optional, replaces your reference table with an empty one
end
Now this solution is a bit clumsy, as there are a few things you need to be cautious about:
__newindex
only get's fired when the key is first created. So
container1.obj = obj1
and container1.obj = obj2
would only
trigger __newindex
on the first assignment. The solution would be
to first set the obj
key to nil and then to obj2
.obj
to nil in that table manually (or to another
object), you need to make sure that the reference the object stores
gets cleared as well.Upvotes: 8