- Joined
- May 9, 2014
- Messages
- 1,820
So, here's a snippet which allows one to easily reuse and request tables, instead of creating and destroying them regularly.
EDIT: Updated with a redesign in the system. Now, creating NTables will instead generate an allocator table. (Best explained with example code)
Example Code: (Pseudo-code)
The idea behind this is that when NTables are out of scope, they will be cleared and recycled, instead of being removed entirely. However, to combat crashes when leaving the game, a cache_clear event watcher will flag the NTables for removal, ensuring that no NTables are ever destroyed in-game for all players.
EDIT: Updated with a redesign in the system. Now, creating NTables will instead generate an allocator table. (Best explained with example code)
Lua:
--[[
--------------
NTable
--------------
NTable:create(size, constructor, destructor)
NTable:new(size, constructor, destructor)
NTable(size, constructor, destructor)
- Returns a table with a preloaded list of tables defined by size.
- constructor is a function with variable arguments.
- destructor is a function with one argument, the destroyed table instance.
NTable:flush(table)
- Explicitly recycles the table back into the list of tables, if and only if
the table belongs to the NTable object.
NTable:destroy()
- Flushes all associated tables to the NTable object, then nullifies
the NTable object.
]]
NTable = setmetatable({PRELOAD_SIZE=16}, {})
do
local tab = NTable
local mtab = getmetatable(tab)
local weak_tab = {clear=false, __mode = 'k'}
local weak_val = {__mode = 'v'}
local very_weak_tab = {__mode = 'kv'}
local func_tables = {construct=setmetatable({}, weak_tab), destroy=setmetatable({}, weak_tab),
shadow=setmetatable({}, weak_tab)}
local meta_set, meta_get, m_table = setmetatable, getmetatable, setmetatable({}, weak_tab)
mtab.__metatable = tab
mtab.__index = mtab
mtab.__newindex = function(t, k, v) if mtab[k] then return; end; rawset(t, k, v) end
local function recycle(self, t)
if self.list.index[t] then return end
if func_tables.destroy[self] then func_tables.destroy[self](t) end
self.list[#self.list + 1] = t
self.list.index[t] = #self.list
func_tables.shadow[t] = self
end
local n_shadow = {__gc = function(t)
if not func_tables.shadow[t] then
print("func_tables.shadow[t] >> Something's wrong")
else
print("func_tables.shadow[t] >> everything's fine")
print("func_tables.shadow[t] >> " .. tostring(func_tables.shadow[t]))
end
if m_table[t] and m_table[t].__gc then
m_table[t].__gc(t)
end
m_table[t] = nil
if weak_tab.clear then
else
recycle(func_tables.shadow[t], t)
end
end, __mode='kv'}
local cache_clear = setmetatable({}, {__gc = function(t) weak_tab.clear = (not IsPlayerSlotState(GetLocalPlayer(), PLAYER_SLOT_STATE_PLAYING)) end})
function getmetatable(t)
if func_tables.shadow[t] then
-- This is a sub-table of a Child table
if m_table[t] then
return m_table[t].__metatable or m_table[t]
end
return nil
end
return meta_get(t)
end
function setmetatable(t, mt)
if func_tables.shadow[t] then
-- This is a sub-table of a Child table
if m_table[t] and m_table[t].__metatable then
return t
end
if mt ~= n_shadow then
m_table[t] = mt
end
return t
end
return meta_set(t, mt)
end
local function new_table(o)
o = o or {}
o.list = {index=setmetatable({}, weak_tab)}
o.total_list = setmetatable({}, weak_val)
o.var_table = setmetatable({}, weak_tab)
setmetatable(o, mtab)
return o
end
function mtab:create(size, constructor, destructor)
local is_func = {is_function(constructor), is_function(destructor)}
local o = new_table()
size = math.max(size or tab.PRELOAD_SIZE, 1)
if is_func[1] then func_tables.construct[o] = constructor end
if is_func[2] then func_tables.destroy[o] = destructor end
for i=1,size do
o.list[i] = {}
o.total_list[i] = o.list[i]
o.list.index[o.list[i]] = i
func_tables.shadow[o.list[i]] = o
meta_set(o.list[i], n_shadow)
end
return o
end
function mtab:new(size, constructor, destructor)
return self:create(size, constructor, destructor)
end
function mtab:__call(...)
if self == tab then
return self:create(...)
end
local o, func
if #self.list > 0 then
o = self.list[#self.list]
self.list[#self.list] = nil
self.list.index[o] = nil
else
o = {}
self.total_list[#self.total_list + 1] = o
func_tables.shadow[o] = self
meta_set(o, n_shadow)
end
func = func_tables.construct[self]
if func then func(o, ...) end
return o
end
function mtab:flush(t)
if func_tables.shadow[t] == self then
-- Forcibly collects the table.
n_shadow.__gc(t)
end
end
function mtab:constructor(constructor)
if is_function(constructor) then func_tables.construct[self] = constructor end
end
function mtab:destructor(destructor)
if is_function(destructor) then func_tables.destroy[self] = destructor end
end
function mtab:destroy()
if self == tab then return end
for i=1,#self.total_list do
self:flush(self.total_list[i])
end
func_tables.construct[self] = nil
func_tables.destroy[self] = nil
self.var_table = nil
self.total_list = nil
self.list.index = nil
self.list = nil
end
function n_shadow.__index(t, k)
if m_table[t] then
if m_table[t].__index then
if type(m_table[t].__index) == 'function' then
return m_table[t].__index(t, k)
else
return m_table[t].__index[k]
end
end
end
return func_tables.shadow[t].var_table[k][t] or nil
end
function n_shadow.__newindex(t, k, v)
if m_table[t] then
if m_table[t].__newindex then
m_table[t].__newindex(t, k, v)
return
end
end
if not func_tables.shadow[t].var_table[k] then
func_tables.shadow[t].var_table[k] = setmetatable({}, weak_tab)
end
func_tables.shadow[t].var_table[k][t] = v
end
function n_shadow.__call(t, ...)
if m_table[t] then
if m_table[t].__call then
return m_table[t].__call(t, ...)
end
end
return nil
end
function n_shadow.__tostring(t)
if m_table[t] then
if m_table[t].__tostring then
return m_table[t].__tostring(t)
end
end
return tostring(t)
end
function n_shadow.__unm(t)
if m_table[t] then
if m_table[t].__unm then
return m_table[t].__unm(t)
end
end
return nil
end
function n_shadow.__add(lhs, rhs)
if m_table[lhs] and (m_table[lhs] == getmetatable(rhs)) then
if m_table[lhs].__add then
return m_table[lhs].__add(lhs, rhs)
end
end
return nil
end
function n_shadow.__sub(lhs, rhs)
if m_table[lhs] and (m_table[lhs] == getmetatable(rhs)) then
if m_table[lhs].__sub then
return m_table[lhs].__sub(lhs, rhs)
end
end
return nil
end
function n_shadow.__mul(lhs, rhs)
if m_table[lhs] and (m_table[lhs] == getmetatable(rhs)) then
if m_table[lhs].__mul then
return m_table[lhs].__mul(lhs, rhs)
end
end
return nil
end
function n_shadow.__div(lhs, rhs)
if m_table[lhs] and (m_table[lhs] == getmetatable(rhs)) then
if m_table[lhs].__div then
return m_table[lhs].__div(lhs, rhs)
end
end
return nil
end
function n_shadow.__idiv(lhs, rhs)
if m_table[lhs] and (m_table[lhs] == getmetatable(rhs)) then
if m_table[lhs].__idiv then
return m_table[lhs].__idiv(lhs, rhs)
end
end
return nil
end
function n_shadow.__mod(lhs, rhs)
if m_table[lhs] and (m_table[lhs] == getmetatable(rhs)) then
if m_table[lhs].__mod then
return m_table[lhs].__mod(lhs, rhs)
end
end
return nil
end
function n_shadow.__pow(lhs, rhs)
if m_table[lhs] and (m_table[lhs] == getmetatable(rhs)) then
if m_table[lhs].__pow then
return m_table[lhs].__pow(lhs, rhs)
end
end
return nil
end
function n_shadow.__concat(lhs, rhs)
if m_table[lhs] and (m_table[lhs] == getmetatable(rhs)) then
if m_table[lhs].__concat then
return m_table[lhs].__concat(lhs, rhs)
end
end
return nil
end
end
Example Code: (Pseudo-code)
Code:
local t = NTable:create(24)
for i=1, 30 do
local t1 = t()
-- This will use all preloaded tables, and generate 6 more tables.
end
local t = NTable:create(10)
t:constructor(function(o, x, y) o.x = x; o.y = y end)
t:destructor(function(o) o.x, o.y = nil end)
-- One can explicitly define constructor and destructor functions outside of NTable:create.
for i = 1,15 do
local t1 = t(math.random(1,5), math.random(-5, -1))
-- This will use all preloaded tables, and generate 5 more tables.
end
The idea behind this is that when NTables are out of scope, they will be cleared and recycled, instead of being removed entirely. However, to combat crashes when leaving the game, a cache_clear event watcher will flag the NTables for removal, ensuring that no NTables are ever destroyed in-game for all players.
This is still kinda experimental, since I'm kinda new to lua garbage collection, so there's that.
Last edited: