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.

144 lines
4.5 KiB

-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- https://tradeskillmaster.com --
-- All Rights Reserved - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
local TSM = select(2, ...) ---@type TSM
local ObjectPool = TSM.Init("Util.ObjectPool") ---@class Util.ObjectPool
local Environment = TSM.Include("Environment")
local Debug = TSM.Include("Util.Debug")
local private = {
debugLeaks = nil,
instances = {}, ---@type table<string, Pool>
}
local DEBUG_STATS_MIN_COUNT = 1
-- ============================================================================
-- Module Loading
-- ============================================================================
ObjectPool:OnModuleLoad(function()
private.debugLeaks = Environment.IsTest()
end)
-- ============================================================================
-- Class Definition
-- ============================================================================
---@class Util.ObjectPool.Pool
local Pool = TSM.Include("LibTSMClass").DefineClass("ObjectPool")
function Pool.__init(self, createFunc, extraStackOffset)
self._createFunc = createFunc
self._extraStackOffset = extraStackOffset
self._freeList = {}
self._state = {}
self._numCreated = 0
end
-- ============================================================================
-- Public Class Methods
-- ============================================================================
--- Either returns a recycled instance of the object or creates a new one as applicable.
---@generic T
---@return T @The object instance
function Pool:Get()
local obj = tremove(self._freeList)
if not obj then
self._numCreated = self._numCreated + 1
obj = self._createFunc()
assert(obj)
end
if private.debugLeaks then
self._state[obj] = (Debug.GetStackLevelLocation(2 + self._extraStackOffset) or "?").." -> "..(Debug.GetStackLevelLocation(3 + self._extraStackOffset) or "?")
else
self._state[obj] = "???"
end
return obj
end
--- Recycles an instance of the object back into the pool.
---@generic T
---@param obj T @The object to recycle
function Pool:Recycle(obj)
assert(self._state[obj])
self._state[obj] = nil
tinsert(self._freeList, obj)
end
-- ============================================================================
-- Private Class Methods
-- ============================================================================
---@return Util.ObjectPool.PoolDebugInfo
function Pool:_GetDebugInfo()
local counts = {}
local totalCount = 0
for _, caller in pairs(self._state) do
counts[caller] = (counts[caller] or 0) + 1
totalCount = totalCount + 1
end
local debugInfo = {}
for info, count in pairs(counts) do
if count > DEBUG_STATS_MIN_COUNT then
tinsert(debugInfo, format("[%d] %s", count, info))
end
end
if #debugInfo == 0 then
tinsert(debugInfo, "<none>")
end
---@class Util.ObjectPool.PoolDebugInfo
---@field numCreated number The total number of objects created
---@field numInUse number The number of objects currently in use
---@field info string[] The debug info strings
return {
numCreated = self._numCreated,
numInUse = totalCount,
info = debugInfo,
}
end
-- ============================================================================
-- Module Functions
-- ============================================================================
---Create a new object pool.
---@param name string The name of the object pool for debug purposes
---@param createFunc function The function which is called to create a new object
---@param extraStackOffset? number The extra stack offset for tracking where objects are being used from or nil to
---disable stack info
---@return Util.ObjectPool.Pool # The object pool
function ObjectPool.New(name, createFunc, extraStackOffset)
assert(createFunc)
assert(not private.instances[name])
local pool = Pool(createFunc, extraStackOffset or 0)
private.instances[name] = pool
return pool
end
---Enables leak debugging.
function ObjectPool.EnableLeakDebug()
private.debugLeaks = true
end
---Gets debug information which represents the current state of all the created object pools
---@return table<string,Util.ObjectPool.PoolDebugInfo>
function ObjectPool.GetDebugInfo()
local debugInfo = {}
for name, pool in pairs(private.instances) do
debugInfo[name] = pool:_GetDebugInfo()
end
return debugInfo
end