PcoWSkbVqDnWTu_dm2ix
We use cookies on this site to enhance your user experience

Tracking Descendants for Memory Management

Tracking Descendants for Memory Management

Jul 30 2018, 2:04 PM PST 5 min

Introduction

Memory management, is defined as the act of managing computer memory at a system level. The essential requirement of memory management is to provide ways to dynamically allocate portions of memory to programs at their request, and free it for reuse when no longer needed.

Most of the time, you won’t need to worry about memory consumption on Roblox. Lua will (for the most part) manage memory for you, as long as you are doing so correctly.
However, when working on low-end devices like mobile, its always helpful to consider the performance benefits of optimizing your code’s memory usage.

The Basics

By now, you are probably familiar with how Articles/Scope|scopes in Lua work. But did you know that local variables get automatically recycled once there are no more references to it?

This is a fancy feature in Lua, known as garbage collection. When an object in memory is no longer being referenced by anything in Lua, the memory being allocated to maintaining that object’s existence is released, allowing new data to take its place.

In Roblox, objects work in a similar manner to this.
There are 3 primary conditions that prevent objects from being garbage collected:

  • The object is a descendant of the DataModel
  • There is a variable pointing to the object, that is actively being referred to in Lua code
  • Another object in the DataModel is pointing to the object via one of its properties (for example, the ObjectValue/Value|Value property of an ObjectValue)

In most cases if any, the second will be the problem you run into.

When memory is never garbage collected even when its not in use, this is known as a memory leak.

Scenario: Tracking Descendants

Lets say that you want to have an array that gives you access to all of the descendants in the workspace on demand.
You could just write a script like this:

local descendants = {}

local function onDescendantAdded(descendant)
	table.insert(descendants,descendant)
end

workspace.DescendantAdded:connect(onDescendantAdded)

However, there is a problem with this code.
Since we have an active connection in this script, the contents of the descendants array will remain active, until the connection in the script is disconnected. Contrary to popular belief, signals like Instance/DescendantAdded won’t actually disconnect until the object it is associated with is removed, regardless if the script object is still present in the game. Since we are working with the Workspace, this Lua thread will remain active as long as the game is running.

With all of that said, any object that is put into the descendants table will not get garbage collected, and thats a problem.

However, this problem can be solved, with just a little more code.

local descendants = {}

local function onDescendantAdded(descendant)
	descendants[descendant] = true
end

local function onDescendantRemoving(descendant)
	if descendants[descendant] then
		descendants[descendant] = nil
	end
end

workspace.DescendantAdded:connect(onDescendantAdded)
workspace.DescendantRemoving:connect(onDescendantRemoving)

As you can see, we changed our method of tracking objects. Instead of adding them as values to the descendants table, we changed them into a pointer value for a boolean. This allows us to quickly point to the object on the array, and set its value to nil when Instance/DescendantRemoving|DescendantRemoving gets fired. Thus this script won’t cause memory leaks, because its not keeping a pointer reference to the object.