Quantcast
--[[--------------------------------------------------------------------
    Copyright (C) 2013, 2014 Johnny C. Lam.
    See the file LICENSE.txt for copying permission.
--]]--------------------------------------------------------------------

--[[
	Reference-counting table pool.

	Tables from the pool have four properties added automatically:

	_refcount_pool_object	reference to pool from which the table came.
	GetReference()			increments the count of references to this table.
	ReleaseReference()		decrements the count of references to this table and
							releases the table back to the pool if then count is zero.
	ReferenceCount()		the count of references to this table.
--]]

local OVALE, Ovale = ...
local OvalePoolRefCount = {}
Ovale.OvalePoolRefCount = OvalePoolRefCount

--<private-static-properties>
local OvaleProfiler = Ovale.OvaleProfiler

local assert = assert
local pairs = pairs
local setmetatable = setmetatable
local tinsert = table.insert
local tostring = tostring
local tremove = table.remove
local wipe = wipe

-- Register for profiling.
OvaleProfiler:RegisterProfiling(OvalePoolRefCount, "OvalePoolRefCount")
--</private-static-properties>

--<public-static-properties>
OvalePoolRefCount.name = "OvalePoolRefCount"
OvalePoolRefCount.pool = nil
OvalePoolRefCount.refcount = nil
OvalePoolRefCount.size = 0
OvalePoolRefCount.unused = 0
OvalePoolRefCount.__index = OvalePoolRefCount
--</public-static-properties>

--<private-static-methods>
local function ReferenceCount(item)
	return item._refcount_pool_object.refcount[item] or 0
end

local function GetReference(item)
	local poolObject = item._refcount_pool_object
	OvalePoolRefCount:StartProfiling(poolObject.name)
	local refcount = item:ReferenceCount()
	poolObject.refcount[item] = refcount + 1
	OvalePoolRefCount:StopProfiling(poolObject.name)
	return item
end

local function ReleaseReference(item)
	local poolObject = item._refcount_pool_object
	OvalePoolRefCount:StartProfiling(poolObject.name)
	local refcount = item:ReferenceCount()
	if refcount > 1 then
		poolObject.refcount[item] = refcount - 1
	else
		poolObject.refcount[item] = nil
		poolObject:Clean(item)
		wipe(item)
		tinsert(poolObject.pool, item)
		poolObject.unused = poolObject.unused + 1
	end
	OvalePoolRefCount:StopProfiling(poolObject.name)
	return item
end
--</private-static-methods>

--<private-static-properties>
local itemPrototype = {
	_refcount_pool_object = nil,
	GetReference = GetReference,
	ReferenceCount = ReferenceCount,
	ReleaseReference = ReleaseReference,
}
--</private-static-properties>

--<public-static-methods>
do
	-- Class constructor
	setmetatable(OvalePoolRefCount, { __call = function(self, ...) return self:NewPool(...) end })
end

function OvalePoolRefCount:NewPool(name)
	name = name or self.name
	local obj = setmetatable({ name = name }, self)
	obj.refcount = {}
	obj:Drain()
	return obj
end

function OvalePoolRefCount:Get()
	OvalePoolRefCount:StartProfiling(self.name)
	assert(self.pool and self.refcount)
	local item = tremove(self.pool)
	if item then
		self.unused = self.unused - 1
	else
		self.size = self.size + 1
		item = {}
	end
	for name, method in pairs(itemPrototype) do
		item[name] = method
	end
	item._refcount_pool_object = self
	OvalePoolRefCount:StopProfiling(self.name)
	return item:GetReference()
end

function OvalePoolRefCount:Release(item)
	return item:ReleaseReference()
end

function OvalePoolRefCount:GetReference(item)
	return item:GetReference()
end

function OvalePoolRefCount:ReleaseReference(item)
	return item:ReleaseReference()
end

function OvalePoolRefCount:Clean(item)
	-- virtual function; override as needed.
end

function OvalePoolRefCount:Drain()
	OvalePoolRefCount:StartProfiling(self.name)
	self.pool = {}
	self.size = self.size - self.unused
	self.unused = 0
	OvalePoolRefCount:StopProfiling(self.name)
end

function OvalePoolRefCount:DebuggingInfo()
	Ovale:Print("Pool %s has size %d with %d item(s).", tostring(self.name), self.size, self.unused)
end
--</public-static-methods>