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.
219 lines
6.4 KiB
219 lines
6.4 KiB
-- ------------------------------------------------------------------------------ --
|
|
-- TradeSkillMaster --
|
|
-- https://tradeskillmaster.com --
|
|
-- All Rights Reserved - Detailed license information included with addon. --
|
|
-- ------------------------------------------------------------------------------ --
|
|
|
|
local TSM = select(2, ...) ---@type TSM
|
|
local SmartMap = TSM.Init("Util.SmartMap") ---@class Util.SmartMap
|
|
local SmartMapObject = TSM.Include("LibTSMClass").DefineClass("SmartMapObject") ---@class SmartMapObject
|
|
local private = {
|
|
readerContext = {},
|
|
}
|
|
---@alias SmartMapKeyType
|
|
---|'"string"'
|
|
---|'"number"'
|
|
local VALID_KEY_TYPES = {
|
|
string = true,
|
|
number = true,
|
|
}
|
|
---@alias SmartMapValueType
|
|
---|'"string"'
|
|
---|'"number"'
|
|
---|'"boolean"'
|
|
local VALID_VALUE_TYPES = {
|
|
string = true,
|
|
number = true,
|
|
boolean = true,
|
|
}
|
|
|
|
|
|
|
|
-- ============================================================================
|
|
-- Module Functions
|
|
-- ============================================================================
|
|
|
|
---Create a new smart map object.
|
|
---@generic K, V
|
|
---@param keyType SmartMapKeyType The type of the keys
|
|
---@param valueType SmartMapValueType The type of the values
|
|
---@param lookupFunc fun(key: K): V A function which looks up the value for a specific key
|
|
---@return SmartMapObject
|
|
function SmartMap.New(keyType, valueType, lookupFunc)
|
|
assert(VALID_KEY_TYPES[keyType] and VALID_VALUE_TYPES[valueType])
|
|
return SmartMapObject(keyType, valueType, lookupFunc)
|
|
end
|
|
|
|
|
|
|
|
-- ============================================================================
|
|
-- SmartMapReader Metatable
|
|
-- ============================================================================
|
|
|
|
---@class SmartMapReader
|
|
|
|
local READER_MT = {
|
|
__index = function(self, key)
|
|
-- check if the map already has the value for this key cached
|
|
local readerContext = private.readerContext[self]
|
|
local value = readerContext.map:_Get(key)
|
|
-- Cache the value on this reader
|
|
rawset(self, key, value)
|
|
return value
|
|
end,
|
|
__call = function(self, key)
|
|
return self[key]
|
|
end,
|
|
__newindex = function()
|
|
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,
|
|
}
|
|
|
|
|
|
|
|
-- ============================================================================
|
|
-- SmartMapObject Class Methods
|
|
-- ============================================================================
|
|
|
|
function SmartMapObject:__init(keyType, valueType, lookupFunc)
|
|
self._keyType = keyType
|
|
self._valueType = valueType
|
|
self._func = lookupFunc
|
|
self._data = {}
|
|
self._readers = {}
|
|
self._callbacksPaused = 0
|
|
self._hasReaderCallback = false
|
|
end
|
|
|
|
---Called when the value has changed for a given key to fetch the new one and notify the readers.
|
|
---@param key string|number The key which changed
|
|
function SmartMapObject:ValueChanged(key)
|
|
local oldValue = self._data[key]
|
|
if oldValue == nil then
|
|
-- nobody cares about this value
|
|
return
|
|
end
|
|
|
|
if not self._hasReaderCallback then
|
|
-- no reader has registered a callback, so just clear the value
|
|
self._data[key] = nil
|
|
for _, reader in ipairs(self._readers) do
|
|
rawset(reader, key, nil)
|
|
end
|
|
return
|
|
end
|
|
|
|
-- get the new value
|
|
local newValue = self._func(key)
|
|
if type(newValue) ~= self._valueType then
|
|
error(format("Invalid type (got %s, expected %s)", type(newValue), self._valueType))
|
|
end
|
|
if oldValue == newValue then
|
|
-- the value didn't change
|
|
return
|
|
end
|
|
|
|
-- update the data
|
|
self._data[key] = newValue
|
|
|
|
for _, reader in ipairs(self._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 self._callbacksPaused == 0 then
|
|
readerContext.callback(reader, readerContext.pendingChanges)
|
|
wipe(readerContext.pendingChanges)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
---Pausese or unpauses reader callbacks.
|
|
---@param paused boolean Whether or not callbacks are paused
|
|
function SmartMapObject:SetCallbacksPaused(paused)
|
|
if paused then
|
|
self._callbacksPaused = self._callbacksPaused + 1
|
|
else
|
|
self._callbacksPaused = self._callbacksPaused - 1
|
|
assert(self._callbacksPaused >= 0)
|
|
if self._callbacksPaused == 0 then
|
|
for _, reader in ipairs(self._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
|
|
|
|
---Creates a new reader.
|
|
---@param callback? fun(reader: SmartMapReader, pendingChanges: table) The function to call when a value within the map changes
|
|
---@return SmartMapReader @The reader object
|
|
function SmartMapObject:CreateReader(callback)
|
|
assert(callback == nil or type(callback) == "function")
|
|
local reader = setmetatable({}, READER_MT)
|
|
tinsert(self._readers, reader)
|
|
self._hasReaderCallback = self._hasReaderCallback or (callback and true or false)
|
|
private.readerContext[reader] = {
|
|
map = self,
|
|
callback = callback,
|
|
pendingChanges = {},
|
|
}
|
|
return reader
|
|
end
|
|
|
|
---Gets the type of the smart map's keys.
|
|
---@return SmartMapKeyType
|
|
function SmartMapObject:GetKeyType()
|
|
return self._keyType
|
|
end
|
|
|
|
---Gets the type of the smart map's values.
|
|
---@return SmartMapValueType
|
|
function SmartMapObject:GetValueType()
|
|
return self._valueType
|
|
end
|
|
|
|
---Iterates over all data in the smart map.
|
|
---@return fun(): string|number, string|number|boolean @An iterator with fields: `key`, `value`
|
|
function SmartMapObject:Iterator()
|
|
return pairs(self._data)
|
|
end
|
|
|
|
---Invalidates all data in the smart map.
|
|
function SmartMapObject:Invalidate()
|
|
self:SetCallbacksPaused(true)
|
|
for key in pairs(self._data) do
|
|
self:ValueChanged(key)
|
|
end
|
|
self:SetCallbacksPaused(false)
|
|
end
|
|
|
|
function SmartMapObject:_Get(key)
|
|
local value = self._data[key]
|
|
if value ~= nil then
|
|
return value
|
|
end
|
|
|
|
-- Use the function to get the value for this key
|
|
value = self._func(key)
|
|
if value == nil then
|
|
error(format("No value for key (%s)", tostring(key)))
|
|
elseif type(value) ~= self._valueType then
|
|
error(format("Invalid type of value (got %s, expected %s): %s", type(value), self._valueType, tostring(value)))
|
|
end
|
|
|
|
-- Cache the value on the map
|
|
self._data[key] = value
|
|
return value
|
|
end
|
|
|