Files
libdogtag-3-0/Events.lua

740 lines
22 KiB
Lua

local MAJOR_VERSION = "LibDogTag-3.0"
local MINOR_VERSION = tonumber(("$Revision$"):match("%d+")) or 0
if MINOR_VERSION > _G.DogTag_MINOR_VERSION then
_G.DogTag_MINOR_VERSION = MINOR_VERSION
end
-- #AUTODOC_NAMESPACE DogTag
DogTag_funcs[#DogTag_funcs+1] = function(DogTag)
local newList, del, deepCopy = DogTag.newList, DogTag.del, DogTag.deepCopy
local fixNamespaceList = DogTag.fixNamespaceList
local memoizeTable = DogTag.memoizeTable
local select2 = DogTag.select2
local kwargsToKwargTypes = DogTag.kwargsToKwargTypes
local codeToFunction, codeEvaluationTime, evaluate, fsToKwargs, fsToFrame, fsToNSList, fsToCode, updateFontString, updateFontStrings
local fsNeedUpdate, fsNeedQuickUpdate
local _clearCodes
DogTag_funcs[#DogTag_funcs+1] = function()
codeToFunction = DogTag.codeToFunction
codeEvaluationTime = DogTag.codeEvaluationTime
evaluate = DogTag.evaluate
fsToFrame = DogTag.fsToFrame
fsToKwargs = DogTag.fsToKwargs
fsToNSList = DogTag.fsToNSList
fsToCode = DogTag.fsToCode
updateFontString = DogTag.updateFontString
updateFontStrings = DogTag.updateFontStrings
for fs in pairs(fsToFrame) do
fsNeedQuickUpdate[fs] = true
end
_clearCodes = DogTag._clearCodes
end
local EventHandlers
if DogTag.oldLib then
fsNeedUpdate = DogTag.oldLib.fsNeedUpdate
for k in pairs(fsNeedUpdate) do
fsNeedUpdate[k] = nil
end
fsNeedQuickUpdate = DogTag.oldLib.fsNeedQuickUpdate
for k in pairs(fsNeedQuickUpdate) do
fsNeedQuickUpdate[k] = nil
end
EventHandlers = DogTag.oldLib.EventHandlers or {}
TimerHandlers = DogTag.oldLib.TimerHandlers or {}
else
fsNeedUpdate = {}
fsNeedQuickUpdate = {}
EventHandlers = {}
TimerHandlers = {}
end
DogTag.fsNeedUpdate = fsNeedUpdate
DogTag.fsNeedQuickUpdate = fsNeedQuickUpdate
DogTag.EventHandlers = EventHandlers
DogTag.TimerHandlers = TimerHandlers
local frame
if DogTag.oldLib then
frame = DogTag.oldLib.frame
frame:SetScript("OnEvent", nil)
frame:SetScript("OnUpdate", nil)
frame:Show()
frame:UnregisterAllEvents()
else
frame = CreateFrame("Frame")
end
DogTag.frame = frame
frame:RegisterAllEvents()
local codeToEventList
do
local codeToEventList_mt = {__index = function(self, kwargTypes)
local t = newList()
t[""] = false
self[kwargTypes] = t
return t
end}
codeToEventList = setmetatable({}, {__index = function(self, nsList)
local t = setmetatable(newList(), codeToEventList_mt)
self[nsList] = t
return t
end})
end
DogTag.codeToEventList = codeToEventList
DogTag.callback_num = 0
local callbackToNSList, callbackToKwargs, callbackToFunction, callbackToCode
if DogTag.oldLib and DogTag.oldLib.callbackToNSList then
local oldLib = DogTag.oldLib
DogTag.callback_num = oldLib.callback_num
callbackToNSList = oldLib.callbackToNSList
callbackToKwargs = {}
for uid, kwargs in pairs(oldLib.callbackToKwargs) do
callbackToKwargs[uid] = memoizeTable(deepCopy(kwargs))
end
callbackToFunction = oldLib.callbackToFunction
callbackToCode = oldLib.callbackToCode
callbackToExtraArg = oldLib.callbackToExtraArg
else
callbackToNSList = {}
callbackToKwargs = {}
callbackToFunction = {}
callbackToCode = {}
callbackToExtraArg = {}
if DogTag.oldLib and DogTag.oldLib.callbacks then
for nsList, callbacks_nsList in pairs(DogTag.oldLib.callbacks) do
for kwargTypes, callbacks_nsList_kwargTypes in pairs(callbacks_nsList) do
for kwargs, callbacks_nsList_kwargTypes_kwargs in pairs(callbacks_nsList_kwargTypes) do
for code, callbacks_nsList_kwargTypes_kwargs_code in pairs(callbacks_nsList_kwargTypes_kwargs) do
if type(callbacks_nsList_kwargTypes_kwargs_code) == "function" then
local uid = DogTag.callback_num + 1
DogTag.callback_num = uid
callbackToNSList[uid] = nsList
callbackToKwargs[uid] = memoizeTable(deepCopy(kwargs))
callbackToFunction[uid] = callbacks_nsList_kwargTypes_kwargs_code
callbackToCode[uid] = code
else -- table
for k in pairs(callbacks_nsList_kwargTypes_kwargs_code) do
local uid = DogTag.callback_num + 1
DogTag.callback_num = uid
callbackToNSList[uid] = nsList
callbackToKwargs[uid] = memoizeTable(deepCopy(kwargs))
callbackToFunction[uid] = k
callbackToCode[uid] = code
end
end
end
end
end
end
end
end
local callbackToKwargTypes = {}
for uid, kwargs in pairs(callbackToKwargs) do
callbackToKwargTypes[uid] = kwargsToKwargTypes[kwargs]
end
DogTag.callbackToNSList = callbackToNSList
DogTag.callbackToKwargs = callbackToKwargs
DogTag.callbackToFunction = callbackToFunction
DogTag.callbackToCode = callbackToCode
DogTag.callbackToKwargTypes = callbackToKwargTypes
DogTag.callbackToExtraArg = callbackToExtraArg
local eventData = setmetatable({}, {__index = function(self, key)
local t = newList()
self[key] = t
return t
end})
DogTag.eventData = eventData
function DogTag.hasEvent(event)
local hasEvent = not not rawget(eventData, event)
if hasEvent then
return true
end
for uid, nsList in pairs(callbackToNSList) do
local kwargTypes = callbackToKwargTypes[uid]
local code = callbackToCode[uid]
local eventList = codeToEventList[nsList][kwargTypes][code]
if eventList then
local eventList_event = eventList[event]
if eventList_event then
return true
end
end
end
return false
end
--[[
Notes:
Adds a callback that will be called if the code in question is to be updated.
Arguments:
string - the tag sequence
function - the function to be called
[optional] string - a semicolon-separated list of namespaces. Base is implied
[optional] table - a dictionary of default kwargs for all tags in the code to receive
[optional] value - a value that will be passed into the callback
Example:
LibStub("LibDogTag-3.0"):AddCallback("[Name]", function(code, kwargs)
-- do something here
end, "Unit", { unit = 'player' })
]]
function DogTag:AddCallback(code, callback, nsList, kwargs, extraArg)
if type(code) ~= "string" then
error(("Bad argument #2 to `AddCallback'. Expected %q, got %q."):format("string", type(code)), 2)
elseif type(callback) ~= "function" then
error(("Bad argument #3 to `AddCallback'. Expected %q, got %q."):format("function", type(callback)), 2)
elseif nsList and type(nsList) ~= "string" then
error(("Bad argument #4 to `AddCallback'. Expected %q, got %q."):format("string", type(nsList)), 2)
elseif kwargs and type(kwargs) ~= "table" then
error(("Bad argument #5 to `AddCallback'. Expected %q, got %q."):format("table", type(kwargs)), 2)
end
nsList = fixNamespaceList[nsList]
local kwargTypes = kwargsToKwargTypes[kwargs]
local codeToEventList_nsList_kwargTypes = codeToEventList[nsList][kwargTypes]
local eventList = codeToEventList_nsList_kwargTypes[code]
if eventList == nil then
local _ = codeToFunction[nsList][kwargTypes][code]
eventList = codeToEventList_nsList_kwargTypes[code]
assert(eventList ~= nil)
end
local uid = DogTag.callback_num + 1
DogTag.callback_num = uid
kwargs = memoizeTable(deepCopy(kwargs or false))
callbackToNSList[uid] = nsList
callbackToKwargs[uid] = kwargs
callbackToCode[uid] = code
callbackToFunction[uid] = callback
callbackToKwargTypes[uid] = kwargTypes
callbackToExtraArg[uid] = extraArg
end
--[[
Notes:
Remove a callback that has been previously added
Arguments:
string - the tag sequence
function - the function to be called
[optional] string - a semicolon-separated list of namespaces. Base is implied
[optional] table - a dictionary of default kwargs for all tags in the code to receive
Example:
LibStub("LibDogTag-3.0"):RemoveCallback("[Name]", func, "Unit", { unit = 'player' })
]]
function DogTag:RemoveCallback(code, callback, nsList, kwargs, extraArg)
if type(code) ~= "string" then
error(("Bad argument #2 to `RemoveCallback'. Expected %q, got %q."):format("string", type(code)), 2)
elseif type(callback) ~= "function" then
error(("Bad argument #3 to `RemoveCallback'. Expected %q, got %q."):format("function", type(callback)), 2)
elseif nsList and type(nsList) ~= "string" then
error(("Bad argument #4 to `RemoveCallback'. Expected %q, got %q."):format("string", type(nsList)), 2)
elseif kwargs and type(kwargs) ~= "table" then
error(("Bad argument #5 to `RemoveCallback'. Expected %q, got %q."):format("table", type(kwargs)), 2)
end
nsList = fixNamespaceList[nsList]
kwargs = memoizeTable(deepCopy(kwargs or false))
for uid, n in pairs(callbackToNSList) do
if n == nsList and callbackToKwargs[uid] == kwargs and callbackToCode[uid] == code and callbackToFunction[uid] == callback and callbackToExtraArg[uid] == extraArg then
callbackToNSList[uid] = nil
callbackToCode[uid] = nil
callbackToKwargs[uid] = nil
callbackToKwargTypes[uid] = nil
callbackToFunction[uid] = nil
callbackToExtraArg[uid] = nil
break
end
end
end
local function OnEvent(this, event, ...)
if DogTag[event] then
DogTag[event](DogTag, event, ...)
end
for namespace, data in pairs(EventHandlers) do
if data[event] then
for func in pairs(data[event]) do
func(event, ...)
end
end
end
local arg1 = (...)
for uid, nsList in pairs(callbackToNSList) do
local kwargTypes = callbackToKwargTypes[uid]
local code = callbackToCode[uid]
local eventList = codeToEventList[nsList][kwargTypes][code]
if eventList then
local eventList_event = eventList[event]
if eventList_event then
local good = false
local checkKwargs = false
local mustEvaluate = false
local checkTable = false
local multiArg = false
if eventList_event == true then
good = true
elseif type(eventList_event) == "table" then
good = true
checkTable = true
else
local tab = newList(("#"):split(eventList_event))
if #tab == 1 then
if eventList_event == arg1 then
good = true
elseif eventList_event:match("^%$") then
good = true
checkKwargs = eventList_event:sub(2)
elseif eventList_event:match("^%[.*%]$") then
good = true
mustEvaluate = eventList_event
end
tab = del(tab)
else
good = true
multiArg = tab
end
end
if good then
local kwargs = callbackToKwargs[uid]
good = true
if multiArg then
good = false
for i, v in ipairs(multiArg) do
local arg = select(i, ...)
if not arg then
good = false
elseif v == arg then
good = true
elseif v:match("^%$") then
good = kwargs[v:sub(2)] == arg
elseif v:match("^%[.*%]$") then
good = evaluate(v, nsList, kwargs) == arg
else
good = false
end
if not good then
break
end
end
multiArg = del(multiArg)
elseif checkTable then
good = false
for k in pairs(eventList_event) do
if k == arg1 then
good = true
else
local multiArg = newList(("#"):split(k))
for i, v in ipairs(multiArg) do
local arg = select(i, ...)
if not arg then
good = false
elseif v == arg then
good = true
elseif v:match("^%$") then
good = kwargs[v:sub(2)] == arg
elseif v:match("^%[.*%]$") then
good = evaluate(v, nsList, kwargs) == arg
else
good = false
end
if not good then
break
end
end
multiArg = del(multiArg)
end
if good then
break
end
end
elseif mustEvaluate then
good = evaluate(mustEvaluate, nsList, kwargs) == arg1
elseif checkKwargs then
good = kwargs[checkKwargs] == arg1
end
if good then
local func = callbackToFunction[uid]
local extraArg = callbackToExtraArg[uid]
if extraArg ~= nil then
func(extraArg, code, nsList, kwargs or nil)
else
func(code, nsList, kwargs or nil)
end
end
end
end
end
end
local eventData_event = eventData[event]
for fs, param in pairs(eventData_event) do
local kwargs = fsToKwargs[fs]
local nsList = fsToNSList[fs]
local good = false
local checkKwargs = false
local mustEvaluate = false
local checkTable = false
local multiArg = false
if param == true then
good = true
elseif type(param) == "table" then
good = true
checkTable = true
else
local tab = newList(("#"):split(param))
if #tab == 1 then
if param == arg1 then
good = true
elseif param:match("^%$") then
good = true
checkKwargs = param:sub(2)
elseif param:match("^%[.*%]$") then
good = true
mustEvaluate = param
end
tab = del(tab)
else
good = true
multiArg = tab
end
end
if good then
good = true
if multiArg then
good = false
for i, v in ipairs(multiArg) do
local arg = select(i, ...)
if not arg then
good = false
elseif v == arg then
good = true
elseif v:match("^%$") then
good = kwargs[v:sub(2)] == arg
elseif v:match("^%[.*%]$") then
good = evaluate(v, nsList, kwargs) == arg
else
good = false
end
if not good then
break
end
end
elseif checkTable then
good = false
for k in pairs(param) do
if k == arg1 then
good = true
else
local multiArg = newList(("#"):split(k))
for i, v in ipairs(multiArg) do
local arg = select(i, ...)
if not arg then
good = false
elseif v == arg then
good = true
elseif v:match("^%$") then
good = kwargs[v:sub(2)] == arg
elseif v:match("^%[.*%]$") then
good = evaluate(v, nsList, kwargs) == arg
else
good = false
end
if not good then
break
end
end
multiArg = del(multiArg)
end
if good then
break
end
end
elseif mustEvaluate then
good = evaluate(mustEvaluate, nsList, kwargs) == arg1
elseif checkKwargs then
good = kwargs[checkKwargs] == arg1
end
if good then
fsNeedUpdate[fs] = true
end
end
end
end
frame:SetScript("OnEvent", OnEvent)
local GetMilliseconds
local GetTime = _G.GetTime
if DogTag_DEBUG then
function GetMilliseconds()
return math.floor(GetTime() * 1000 + 0.5)
end
else
function GetMilliseconds()
return GetTime() * 1000
end
end
local nextTime = 0
local nextUpdateTime = 0
local nextSlowUpdateTime = 0
local nextCacheInvalidationTime = 0
local num = 0
local function OnUpdate(this, elapsed)
_clearCodes()
num = num + 1
local currentTime = GetMilliseconds()
local oldMouseover = DogTag.__lastMouseover
local newMouseover = GetMouseFocus()
DogTag.__lastMouseover = newMouseover
if oldMouseover ~= DogTag.__lastMouseover then
for fs, frame in pairs(fsToFrame) do
if frame == oldMouseover or frame == newMouseover then
-- TODO: only update if has a mouseover event
fsNeedQuickUpdate[fs] = true
end
end
end
if currentTime >= nextTime then
local currentTime_1000 = currentTime/1000
DogTag:FireEvent("FastUpdate")
if currentTime >= nextUpdateTime then
nextUpdateTime = currentTime + 150
DogTag:FireEvent("Update")
end
if currentTime >= nextSlowUpdateTime then
nextSlowUpdateTime = currentTime + 10000
DogTag:FireEvent("SlowUpdate")
end
if currentTime >= nextCacheInvalidationTime then
nextCacheInvalidationTime = currentTime + 15000
if not InCombatLockdown() then
local oldTime = currentTime_1000 - 180
for nsList, codeToFunction_nsList in pairs(codeToFunction) do
for kwargTypes, codeToFunction_nsList_kwargTypes in pairs(codeToFunction_nsList) do
if kwargTypes ~= 1 then
for code in pairs(codeToFunction_nsList_kwargTypes) do
if code ~= 1 and code ~= 2 then
local x = codeEvaluationTime[nsList][kwargTypes][code]
local good = false
if x and x > oldTime then
good = true
else
for fs, c in pairs(fsToCode) do
if c == code and fsToNSList[fs] == nsList then
good = true
break
end
end
if not good then
for uid, c in pairs(callbackToCode) do
if c == code and callbackToNSList[uid] == nsList then
good = true
break
end
end
end
end
if not good then
codeToFunction_nsList_kwargTypes[code] = nil
codeToEventList[nsList][kwargTypes][code] = nil
end
end
end
end
end
end
end
end
nextTime = currentTime + 50
for i = 1, 9 do
for ns, data in pairs(TimerHandlers) do
local data_i = data[i]
if data_i then
for func in pairs(data_i) do
func(num, currentTime_1000)
end
end
end
end
for fs in pairs(fsNeedUpdate) do
fsNeedQuickUpdate[fs] = true
fsNeedUpdate[fs] = nil
end
end
local finish_time = GetTime() + 1/1000
local num = 0
for fs in pairs(fsNeedQuickUpdate) do
num = num + 1
if num%10 == 0 and GetTime() >= finish_time then
break
end
updateFontString(fs)
end
end
frame:SetScript("OnUpdate", OnUpdate)
--[[
Notes:
Register a function to be called when the event is fired
This should only be called by sublibraries
Arguments:
string - the namespace to mark ownership with
string - the name of the event
function - the function to be called
Example:
LibStub("LibDogTag-3.0"):AddEventHandler("MyNamespace", "PLAYER_LOGIN", function(event, ...)
-- do something here.
end)
]]
function DogTag:AddEventHandler(namespace, event, func)
if type(namespace) ~= "string" then
error(("Bad argument #2 to `AddEventHandler'. Expected %q, got %q"):format("string", type(namespace)), 2)
end
if type(event) ~= "string" then
error(("Bad argument #3 to `AddEventHandler'. Expected %q, got %q"):format("string", type(event)), 2)
end
if type(func) ~= "function" then
error(("Bad argument #4 to `AddEventHandler'. Expected %q, got %q"):format("function", type(func)), 2)
end
if not EventHandlers[namespace] then
EventHandlers[namespace] = newList()
end
if not EventHandlers[namespace][event] then
EventHandlers[namespace][event] = newList()
end
EventHandlers[namespace][event][func] = true
end
--[[
Notes:
Remove an event handler that has been previously added
This should only be called by sublibraries
Arguments:
string - the namespace to mark ownership with
string - the name of the event
function - the function to be called
Example:
LibStub("LibDogTag-3.0"):RemoveEventHandler("MyNamespace", "PLAYER_LOGIN", func)
]]
function DogTag:RemoveEventHandler(namespace, event, func)
if type(namespace) ~= "string" then
error(("Bad argument #2 to `RemoveEventHandler'. Expected %q, got %q"):format("string", type(namespace)), 2)
end
if type(event) ~= "string" then
error(("Bad argument #3 to `RemoveEventHandler'. Expected %q, got %q"):format("string", type(event)), 2)
end
if type(func) ~= "function" then
error(("Bad argument #4 to `RemoveEventHandler'. Expected %q, got %q"):format("function", type(func)), 2)
end
local EventHandlers_namespace = EventHandlers[namespace]
if not EventHandlers_namespace then
return
end
local EventHandlers_namespace_event = EventHandlers_namespace[event]
if not EventHandlers_namespace_event then
return
end
EventHandlers_namespace_event[func] = nil
if not next(EventHandlers_namespace_event) then
EventHandlers_namespace[event] = del(EventHandlers_namespace_event)
end
if not next(EventHandlers_namespace) then
EventHandlers[namespace] = del(EventHandlers_namespace)
end
end
--[[
Notes:
Fire an event that any tags, handlers, or callbacks will see.
Arguments:
string - name of the event
tuple - a tuple of arguments
Example:
LibStub("LibDogTag-3.0"):FireEvent("MyEvent", "Data", "goes", "here", 52)
]]
function DogTag:FireEvent(event, ...)
OnEvent(frame, event, ...)
end
--[[
Notes:
Register a function to be called roughly every 0.05 seconds
This should only be called by sublibraries
Arguments:
string - the namespace to mark ownership with
function - the function to be called
[optional] number - a number from 1 to 9 specifying the priority it will be called compared to other timers. 1 being called first and 9 being called last. Is 5 by default.
Example:
LibStub("LibDogTag-3.0"):AddTimerHandler("MyNamespace", function(num, currentTime)
-- do something here.
end)
]]
function DogTag:AddTimerHandler(namespace, func, priority)
if type(namespace) ~= "string" then
error(("Bad argument #2 to `AddTimerHandler'. Expected %q, got %q"):format("string", type(namespace)), 2)
end
if type(func) ~= "function" then
error(("Bad argument #3 to `AddTimerHandler'. Expected %q, got %q"):format("function", type(func)), 2)
end
if not priority then
priority = 5
elseif type(priority) ~= "number" then
error(("Bad argument #4 to `AddTimerHandler'. Expected %q, got %q"):format("number", type(priority)), 2)
elseif math.floor(priority) ~= priority then
error("Bad argument #4 to `AddTimerHandler'. Expected integer, got number", 2)
elseif priority < 1 or priority > 9 then
error(("Bad argument #4 to `AddTimerHandler'. Expected [1, 9], got %d"):format(priority), 2)
end
self:RemoveTimerHandler(namespace, func)
if not TimerHandlers[namespace] then
TimerHandlers[namespace] = newList()
end
if not TimerHandlers[namespace][priority] then
TimerHandlers[namespace][priority] = newList()
end
TimerHandlers[namespace][priority][func] = true
end
--[[
Notes:
Remove a timer handler that has previously been added
This should only be called by sublibraries
Arguments:
string - the namespace to mark ownership with
function - the function to be called
Example:
LibStub("LibDogTag-3.0"):RemoveTimerHandler("MyNamespace", func)
]]
function DogTag:RemoveTimerHandler(namespace, func)
if type(namespace) ~= "string" then
error(("Bad argument #2 to `RemoveTimerHandler'. Expected %q, got %q"):format("string", type(namespace)), 2)
end
if type(func) ~= "function" then
error(("Bad argument #3 to `RemoveTimerHandler'. Expected %q, got %q"):format("function", type(func)), 2)
end
if not TimerHandlers[namespace] then
return
end
for k, v in pairs(TimerHandlers[namespace]) do
v[func] = nil
if not next(v) then
TimerHandlers[namespace][k] = del(v)
end
end
if not next(TimerHandlers[namespace]) then
TimerHandlers[namespace] = del(TimerHandlers[namespace])
end
end
end