Object management in Luxinia .97

In the transition from .96 to .97, we chose, to enable the garbage collection for any kind of data, with the result that objects are deleted, once they are no longer refered.

This has some advantatages but also disadvantages. The main advantage is, that reloading code is simple, since the GC (Garbage Collector) takes care of all lost references. However, we need now to keep track of all our references as long as we want them to keep. Our experience was, that we normaly keep a structure in lua anyway in order to interact with everything, so this is the normal case anyway.

If we lose the reference of an object (like an l3dnode or actornode), the visible object will disappear eventually, if the garbage collector calls the destructor of the actornode:

function makeactor ()
  -- actor is the global variable - it will be 
  -- overwritten, so the old actor will be collected
  actor = actornode.new("gc",0,20,0)
  actor.l3d = l3dprimitive.newbox("gc",1,1,1)
  actor.l3d:linkinterface(actor)
end

for i=1,100 do makeactor() end

collectgarbage "collect"
print(actornode.getcount())
-- there's only one active actor

The box will be visible for some time until it is collected. If you want to directly delete it (and not depend on when gc collects stuff), just call :delete as usual.

If we have a file that initializes various global variables, we can easily reload this anytime without too much troubles.

Pitfalls

There are two cases that are problematic:

  • Luxinia crashes
  • Something is not collected though we think it should

Crashes

The first case can happen if a proper cleanup is absolutly necessary. We did not try to make luxinia foolproof and there are things that are simply evil and will make the engine crash, though it is quite unlikely for nearly all simple situations. In most cases, the rendering system is the cause of these troubles, since this is the part which is most hardware specific and should run the fastest. However, this only means that those (few) instructions that cause the crash must make a proper cleanup, i.e. by making sure that the previous setup is deleted properly.

Most of the crashes would be related to resources not being available, like when resourcechunks are used and managed manually. Then you must be sure that the resources in a cleared chunk are not used anymore.

Memory leaks

In some cases, lua variables are not freed by the GC. I've never seen a case where its been the fault of the GC - its always been by my mistake that a variable could not be freed.

Any variable that is reachable from the global context will not be collected - finding the references can be tricky. Examples:

function foo()
  local variable = actornode.new("") -- will be collected
  -- if the function returns
end

function foo2()
  variable = actornode.new("") -- will not be collected
  -- if the function returns - unless we set 
  -- _G.variable = nil
end

function foo3 ()
  local variable = actornode.new("") -- will not be collected
  function bar () 
    print(variable) -- bar is refering variable and 
    -- _G.bar is a global function, so variable CANNOT
    -- be collected, as bar needs it
  end
end

function foo4()
  local variable = actornode.new("") -- will be collected
  local function bar() 
    print(variable)
  end
  bar() -- bar is used only inside foo4 - it'll be also
  -- gced

The foo3 case is a quite tricky example, which can be tough to trace.

Tracking references

It is very complex to trace references where it is kept.

First, we should find out which variables are not gced.

A quite simple way to find out this, is to put the references that should be collected in a weak table and see, if they are collected:

tracker = {}
setmetatable(tracker,{__mode='v'})
function addtrack (key,var)
  tracker[key] = var
end
function printungced ()
  collectgarbage("collect")
  for i,v in pairs(tracker)
    print(i,v)
  end
end

If we call printungced, we will get a list of all variables that have not been collected yet.

If we know, which references are not collected, we can take a closer look in the code:

  • Make sure it is not declared global somewhere
  • Make sure it is not kept in a list which is declared global
  • Make sure that there exists no function that is still refering to the local variable. Functions may refer to global values however, as these are not upvalues of the function.
    • Make sure that the timers / timertasks are no longer refering your functions

The hints above should be able to help to find such a "leak". You could also print out the content of the _G table, including all upvalues of the functions, but as there are quite alot of references this can be a very time consuming task.