You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

158 lines
5.0 KiB

-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- https://tradeskillmaster.com --
-- All Rights Reserved - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
local TSM = select(2, ...) ---@type TSM
local TempTable = TSM.Init("Util.TempTable") ---@class Util.TempTable
local Environment = TSM.Include("Environment")
local Debug = TSM.Include("Util.Debug")
local private = {
debugLeaks = nil,
freeTempTables = {},
tempTableState = {},
}
local NUM_TEMP_TABLES = 100
local RELEASED_TEMP_TABLE_MT = {
__newindex = function(self, key, value)
error("Attempt to access temp table after release")
end,
__index = function(self, key)
error("Attempt to access temp table after release")
end,
}
-- ============================================================================
-- Module Loading
-- ============================================================================
TempTable:OnModuleLoad(function()
private.debugLeaks = Environment.IsTest()
end)
-- ============================================================================
-- Module Functions
-- ============================================================================
---Acquires a temporary table.
---
---Temporary tables are recycled tables which can be used instead of creating a new table every time one is needed for a
---defined lifecycle. This avoids relying on the garbage collector and improves overall performance.
---@param ... any Any number of valuse to insert into the table initially
---@return table @The temporary table
function TempTable.Acquire(...)
local tbl = tremove(private.freeTempTables)
if not tbl then
error("Could not acquire temp table")
end
setmetatable(tbl, nil)
if private.debugLeaks then
private.tempTableState[tbl] = (Debug.GetStackLevelLocation(2) or "?").." -> "..(Debug.GetStackLevelLocation(3) or "?")
else
private.tempTableState[tbl] = true
end
for i = 1, select("#", ...) do
tbl[i] = select(i, ...)
end
return tbl
end
---Iterators over a temporary table, releasing it when done.
---
---**NOTE:** This iterator must be run to completion and not be interrupted (i.e. with a `break` or `return`).
---@param tbl table The temporary table to iterator over
---@param numFields? number The number of fields to unpack with each iteration (defaults to 1)
---@return fun(): number, ... @An iterator with fields: `index, {numFields...}`
function TempTable.Iterator(tbl, numFields)
numFields = numFields or 1
assert(numFields >= 1 and #tbl % numFields == 0)
assert(private.tempTableState[tbl])
tbl.__iterNumFields = numFields
return private.TempTableIteratorHelper, tbl, 1 - numFields
end
---Releases a temporary table.
---
---The temporary table will be returned to the pool and must not be accessed after being released.
---@param tbl table The temporary table to release
function TempTable.Release(tbl)
private.TempTableReleaseHelper(tbl)
end
---Releases the temporary table and returns its unpacked values.
---@param tbl table The temporary table to release and unpack
---@return ... @The result of calling `unpack` on the table
function TempTable.UnpackAndRelease(tbl)
return private.TempTableReleaseHelper(tbl, unpack(tbl))
end
---Enables tracking of where temp tables are created from in order to debug leaks.
function TempTable.EnableLeakDebug()
private.debugLeaks = true
end
---Gets debug information describing allocated and free temp tables.
---@return string[] @The debug information
function TempTable.GetDebugInfo()
local debugInfo = {}
local counts = {}
for _, info in pairs(private.tempTableState) do
counts[info] = (counts[info] or 0) + 1
end
for info, count in pairs(counts) do
tinsert(debugInfo, format("[%d] %s", count, type(info) == "string" and info or "?"))
end
if #debugInfo == 0 then
tinsert(debugInfo, "<none>")
end
return debugInfo
end
-- ============================================================================
-- Private Helper Functions
-- ============================================================================
function private.TempTableIteratorHelper(tbl, index)
local numFields = tbl.__iterNumFields
index = index + numFields
if index > #tbl then
TempTable.Release(tbl)
return
end
if numFields == 1 then
return index, tbl[index]
else
return index, unpack(tbl, index, index + numFields - 1)
end
end
function private.TempTableReleaseHelper(tbl, ...)
if not private.tempTableState[tbl] then
error("Invalid table")
end
wipe(tbl)
tinsert(private.freeTempTables, tbl)
private.tempTableState[tbl] = nil
setmetatable(tbl, RELEASED_TEMP_TABLE_MT)
return ...
end
-- ============================================================================
-- Temp Table Setup
-- ============================================================================
do
for _ = 1, NUM_TEMP_TABLES do
local tempTbl = setmetatable({}, RELEASED_TEMP_TABLE_MT)
tinsert(private.freeTempTables, tempTbl)
end
end