leef-class-cd2025/proxy_table.lua
2025-01-03 22:02:03 -08:00

88 lines
2.7 KiB
Lua

--- allows the creation of a "proxy" for a a real table which prevents modification while still following any changes to the original table.
--
-- This works with a given tree of tables.
-- be warned that these objects do not prevent the garbage collection of the table they are from. The table of origin (the one they are reproducing) can be accessed with the variable `__LEEF_PROXY_PARENT`.
-- this means you will **NEED TO KEEP A COPY** of whatever table you are protecting.
--
-- @module proxy_table
leef.class.proxy_table = {}
local proxy_table = leef.class.proxy_table
proxy_table.tables_by_proxy = {} --proxies indexed by their tables
local tables_by_proxy = proxy_table.tables_by_proxy
setmetatable(tables_by_proxy, {
__mode = "v" --proxies wont be kept around if their tables dont exist. Since proxy tables themselves have weak keys AND values, this means that proxies will be released if their tables dont exist
})
local proxy_metatable = {
__index = function(t, k)
local real_value = tables_by_proxy[t][k]
local value_type = type(real_value)
if ((value_type == "table") or (value_type == "class")) then
local val = proxy_table.new(real_value)
rawset(t, k, val)
return val
else
return real_value
end
end,
__newindex = function(t,l)
error("attempt to modify proxy table")
end,
__mode = "kv"
}
--- create a new proxy table
-- @tparam table table to create immutable interface for
-- @return Proxy table
-- @function new
function proxy_table.new(table)
assert(table~=proxy_table, "do not call leef.class.proxy_table functions as methods.")
local proxy = {
__LEEF_PROXY_PARENT = table
}
setmetatable(proxy, proxy_metatable)
tables_by_proxy[proxy] = table
return proxy
end
local old_next = next
function next(p, k)
local original = tables_by_proxy[p]
if original then --if the table exists as an index here, it is a proxy.
local next_key, _ = old_next(original, k) --value ignored as might be unsafe to return.
return next_key, original[next_key] --get the value of the proxy that way if it's a table it is protected, and otherwise it will return the same thing.
else
return old_next(p,k)
end
end
--since next is modified we basically just return the normal pairs func.
function pairs(t)
return next, t, nil
end
local function iter(p, i)
i = i + 1
local t = tables_by_proxy[p]
local v = t[i]
if v then
return i, v
end
end
local old_ipairs = ipairs
function ipairs(p)
if tables_by_proxy[p] then
return iter, p, 0
else
return old_ipairs(p)
end
end
--[[local old_ipairs = ipairs
function ipairs(t, ...)
return old_ipairs(proxies[t] or t, ...)
end]]