I think it’s worth noting that, in cases where all of a table’s descendants are known not to be frozen prior to the call; table.isfrozen
could be used in place of caching.
table.isfrozen Implementation
local DeepFreeze = (function()
local FreezeNode = function(NodeArray, Node, IsHostNode)
for Key, Value in pairs(Node) do
if type(Value) == "table" then
table.insert(NodeArray, Value)
end
end
if IsHostNode and table.isfrozen(Node) then -- No redundancy; isfrozen is only called when IsHostNode is true.
return nil
end
table.freeze(Node)
end
local StepNextNodes = function(CurrentNodes)
local NextNodes = {}
for Index, Node in pairs(CurrentNodes) do
if not table.isfrozen(Node) then
FreezeNode(NextNodes, Node, false)
end
end
return NextNodes
end
return function(HostNode)
local CurrentNodes = {}
FreezeNode(CurrentNodes, HostNode, true)
while #CurrentNodes > 0 do
CurrentNodes = StepNextNodes(CurrentNodes)
end
end
end)()
But of course, that wouldn’t be the ideal functionality if some subtables are already frozen prior to the function call. In that case; you’d either have to precompute the tree, or use a HashTable in order to keep track of cyclic references.
My HashTable Implementation
local DeepFreeze = (function()
local FreezeNode = function(NodeArray, FrozenNodes, Node)
for Key, Value in pairs(Node) do
if type(Value) == "table" then
table.insert(NodeArray, Value)
end
end
if not table.isfrozen(Node) then
table.freeze(Node)
end
FrozenNodes[Node] = true
end
local StepNextNodes = function(CurrentNodes, FrozenNodes)
local NextNodes = {}
for Index, Node in pairs(CurrentNodes) do
if FrozenNodes[Node] == nil then
FreezeNode(NextNodes, FrozenNodes, Node)
end
end
return NextNodes
end
return function(HostNode)
local FrozenNodes = {}
local CurrentNodes = {}
FreezeNode(CurrentNodes, FrozenNodes, HostNode)
while #CurrentNodes > 0 do
CurrentNodes = StepNextNodes(CurrentNodes, FrozenNodes)
end
end
end)()
I used an inline function in order to define both examples, but it should be preferred to have a ModuleScript make the return instead. I’m also operating on the assumption that table.freeze
won’t throw an error when called on tables that have potentially already been frozen.
Edit: So we’ve gotten a bit more information, and it unfortunately seems that table.freeze
will indeed error when called on a frozen table. Both implementations have been edited accordingly.