I just wanted to see if anyone may see any edge cases with this. I tested it a bit, but I may have missed something. I also am not sure if I am using weak table correctly. I assume that by using weak tables, if the part is somehow destroyed somewhere else, instead of staying in the pool, it will be removed.
--!strict
export type Pool<T, A...> = {
acquire: (A...) -> T,
release: (T) -> (),
prefill: (number) -> (),
clear: () -> (),
idle: () -> number,
isCheckedOut: (T) -> boolean,
}
type Config<T, A...> = {
name: string?,
create: () -> T,
onAcquire: ((T, A...) -> ())?,
onRelease: ((T) -> ())?,
max: number?,
destroy: ((T) -> ())?,
}
local Pool = {}
function Pool.new<T, A...>(config: Config<T, A...>): Pool<T, A...>
local name = config.name or "unnamed"
local create = config.create
local max = config.max or 32
local onAcquire: (T, A...) -> () = config.onAcquire or function() end
local onRelease: (T) -> () = config.onRelease or function() end
local destroy: (T) -> () = config.destroy
or function(item: T)
if typeof(item) == "Instance" then
(item :: any):Destroy()
end
end
local idle: { T } = {}
local inUse: { [any]: boolean } = setmetatable({}, { __mode = "k" }) :: any
local warnedOverflow = false
local pool = {}
function pool.acquire(...: A...): T
local item: T? = table.remove(idle)
if item == nil then
item = create()
end
local value = item :: T
inUse[value] = true
onAcquire(value, ...)
return value
end
function pool.release(item: T)
if not inUse[item] then
warn(`[Pool:{name}] release() called on an item that is not checked out (double release?)`)
return
end
inUse[item] = nil
onRelease(item)
if #idle >= max then
if not warnedOverflow then
warnedOverflow = true
warn(`[Pool:{name}] idle count hit max ({max}); objects are being destroyed instead of reused. Raise max.`)
end
destroy(item)
return
end
table.insert(idle, item)
end
function pool.prefill(count: number)
for _ = 1, count do
local item: T = create()
onRelease(item)
table.insert(idle, item)
end
end
function pool.clear()
for _, item in idle do
destroy(item)
end
table.clear(idle)
end
function pool.idle(): number
return #idle
end
function pool.isCheckedOut(item: T): boolean
return inUse[item] == true
end
return pool
end
return Pool
I had AI make a little documentation so you can test it out:
const Pool = require(path.to.Pool)
local dropPool = Pool.new({
name = "Drops",
create = function()
return dropTemplate:Clone()
end,
onAcquire = function(part: BasePart, cframe: CFrame, parent: Instance)
part.CFrame = cframe
part.Parent = parent
end,
onRelease = function(part: BasePart)
part.Parent = nil
part.AssemblyLinearVelocity = Vector3.zero
part.CanCollide = true
end,
max = 64, -- please make sure this is set to a good amount. If you don't it will delete the overflow
})
-- get one
local drop = dropPool.acquire(attachment.WorldCFrame, dropsFolder)
-- ...later, when done with it (e.g. on Touched, or pickup)...
dropPool.release(drop)
dropPool.prefill(20) -- pre-warm 20 idle objects up front (e.g. on server start) so the
-- first wave of acquires doesn't pay `create()`'s cost mid-gameplay
dropPool.idle() -- how many objects are currently sitting idle, ready to hand out
dropPool.clear() -- destroys every idle object (checked-out ones are untouched) —
-- e.g. round reset, or tearing the whole system down
dropPool.isCheckedOut(drop) -- true if `drop` is currently out of the pool
-- calls out that `.Parent` is unreliable when something like
-- `Touched` can fire more than once for the same object.