initial commit
This commit is contained in:
217
LibTSM/Util/SmartMap.lua
Normal file
217
LibTSM/Util/SmartMap.lua
Normal file
@@ -0,0 +1,217 @@
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
-- TradeSkillMaster --
|
||||
-- https://tradeskillmaster.com --
|
||||
-- All Rights Reserved - Detailed license information included with addon. --
|
||||
-- ------------------------------------------------------------------------------ --
|
||||
|
||||
--- Smart Map.
|
||||
-- @module SmartMap
|
||||
|
||||
local _, TSM = ...
|
||||
local SmartMap = TSM.Init("Util.SmartMap")
|
||||
local private = {
|
||||
mapContext = {},
|
||||
readerContext = {},
|
||||
}
|
||||
local VALID_FIELD_TYPES = {
|
||||
string = true,
|
||||
number = true,
|
||||
boolean = true,
|
||||
}
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Metatable Methods
|
||||
-- ============================================================================
|
||||
|
||||
local SMART_MAP_MT = {
|
||||
-- getter
|
||||
__index = function(self, key)
|
||||
if key == nil then
|
||||
error("Attempt to get nil key")
|
||||
end
|
||||
if key == "ValueChanged" then
|
||||
return private.MapValueChanged
|
||||
elseif key == "SetCallbacksPaused" then
|
||||
return private.MapSetCallbacksPaused
|
||||
elseif key == "CreateReader" then
|
||||
return private.MapCreateReader
|
||||
elseif key == "GetKeyType" then
|
||||
return private.MapGetKeyType
|
||||
elseif key == "GetValueType" then
|
||||
return private.MapGetValueType
|
||||
elseif key == "Iterator" then
|
||||
return private.MapIterator
|
||||
else
|
||||
error("Invalid map method: "..tostring(key), 2)
|
||||
end
|
||||
end,
|
||||
|
||||
-- setter
|
||||
__newindex = function(self, key, value)
|
||||
error("Map cannot be written to directly", 2)
|
||||
end,
|
||||
|
||||
__tostring = function(self)
|
||||
return "SmartMap:"..strmatch(tostring(private.mapContext[self]), "table:[^0-9a-fA-F]*([0-9a-fA-F]+)")
|
||||
end,
|
||||
|
||||
__metatable = false,
|
||||
}
|
||||
|
||||
local READER_MT = {
|
||||
-- getter
|
||||
__index = function(self, key)
|
||||
-- check if the map already has the value for this key cached
|
||||
local readerContext = private.readerContext[self]
|
||||
local map = readerContext.map
|
||||
local mapContext = private.mapContext[map]
|
||||
if mapContext.data[key] ~= nil then
|
||||
return mapContext.data[key]
|
||||
end
|
||||
|
||||
-- get the value for this key
|
||||
local value = mapContext.func(key)
|
||||
if value == nil then
|
||||
error(format("No value for key (%s)", tostring(key)))
|
||||
elseif type(value) ~= mapContext.valueType then
|
||||
error(format("Invalid type of value (got %s, expected %s): %s", type(value), mapContext.valueType, tostring(value)))
|
||||
end
|
||||
|
||||
-- cache the value both on the map and on this reader
|
||||
mapContext.data[key] = value
|
||||
rawset(self, key, value)
|
||||
|
||||
return value
|
||||
end,
|
||||
|
||||
-- setter
|
||||
__newindex = function(self, key, value)
|
||||
error("Reader is read-only", 2)
|
||||
end,
|
||||
|
||||
__tostring = function(self)
|
||||
return "SmartMapReader:"..strmatch(tostring(private.readerContext[self]), "table:[^0-9a-fA-F]*([0-9a-fA-F]+)")
|
||||
end,
|
||||
|
||||
__metatable = false,
|
||||
}
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Module Functions
|
||||
-- ============================================================================
|
||||
|
||||
function SmartMap.New(keyType, valueType, callable)
|
||||
assert(VALID_FIELD_TYPES[keyType] and VALID_FIELD_TYPES[valueType])
|
||||
local map = setmetatable({}, SMART_MAP_MT)
|
||||
private.mapContext[map] = {
|
||||
keyType = keyType,
|
||||
valueType = valueType,
|
||||
func = callable,
|
||||
data = {},
|
||||
readers = {},
|
||||
callbacksPaused = 0,
|
||||
hasReaderCallback = false,
|
||||
}
|
||||
return map
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Private Helper Functions
|
||||
-- ============================================================================
|
||||
|
||||
function private.MapValueChanged(self, key)
|
||||
local mapContext = private.mapContext[self]
|
||||
local oldValue = mapContext.data[key]
|
||||
if oldValue == nil then
|
||||
-- nobody cares about this value
|
||||
return
|
||||
end
|
||||
|
||||
if not mapContext.hasReaderCallback then
|
||||
-- no reader has registered a callback, so just clear the value
|
||||
mapContext.data[key] = nil
|
||||
for _, reader in ipairs(mapContext.readers) do
|
||||
rawset(reader, key, nil)
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
-- get the new value
|
||||
local newValue = mapContext.func(key)
|
||||
if type(newValue) ~= mapContext.valueType then
|
||||
error(format("Invalid type (got %s, expected %s)", type(newValue), mapContext.valueType))
|
||||
end
|
||||
if oldValue == newValue then
|
||||
-- the value didn't change
|
||||
return
|
||||
end
|
||||
|
||||
-- update the data
|
||||
mapContext.data[key] = newValue
|
||||
|
||||
for _, reader in ipairs(mapContext.readers) do
|
||||
local readerContext = private.readerContext[reader]
|
||||
local prevValue = rawget(reader, key)
|
||||
if prevValue ~= nil then
|
||||
rawset(reader, key, newValue)
|
||||
if readerContext.callback then
|
||||
readerContext.pendingChanges[key] = prevValue
|
||||
if mapContext.callbacksPaused == 0 then
|
||||
readerContext.callback(reader, readerContext.pendingChanges)
|
||||
wipe(readerContext.pendingChanges)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function private.MapSetCallbacksPaused(self, paused)
|
||||
local mapContext = private.mapContext[self]
|
||||
if paused then
|
||||
mapContext.callbacksPaused = mapContext.callbacksPaused + 1
|
||||
else
|
||||
mapContext.callbacksPaused = mapContext.callbacksPaused - 1
|
||||
assert(mapContext.callbacksPaused >= 0)
|
||||
if mapContext.callbacksPaused == 0 then
|
||||
for _, reader in ipairs(mapContext.readers) do
|
||||
local readerContext = private.readerContext[reader]
|
||||
if readerContext.callback and next(readerContext.pendingChanges) then
|
||||
readerContext.callback(reader, readerContext.pendingChanges)
|
||||
wipe(readerContext.pendingChanges)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function private.MapCreateReader(self, callback)
|
||||
assert(callback == nil or type(callback) == "function")
|
||||
local reader = setmetatable({}, READER_MT)
|
||||
local mapContext = private.mapContext[self]
|
||||
tinsert(mapContext.readers, reader)
|
||||
mapContext.hasReaderCallback = mapContext.hasReaderCallback or (callback and true or false)
|
||||
private.readerContext[reader] = {
|
||||
map = self,
|
||||
callback = callback,
|
||||
pendingChanges = {},
|
||||
}
|
||||
return reader
|
||||
end
|
||||
|
||||
function private.MapGetKeyType(self)
|
||||
return private.mapContext[self].keyType
|
||||
end
|
||||
|
||||
function private.MapGetValueType(self)
|
||||
return private.mapContext[self].valueType
|
||||
end
|
||||
|
||||
function private.MapIterator(self)
|
||||
return pairs(private.mapContext[self].data)
|
||||
end
|
||||
Reference in New Issue
Block a user