local MAJOR_VERSION = "LibDogTag-3.0" local MINOR_VERSION = ((tonumber(("@project-date-integer@"):match("%d+")) or 33333333333333)) if MINOR_VERSION > _G.DogTag_MINOR_VERSION then _G.DogTag_MINOR_VERSION = MINOR_VERSION end local type, error, math, next, pairs, ipairs, select, rawget, setmetatable, _G, assert = type, error, math, next, pairs, ipairs, select, rawget, setmetatable, _G, assert -- #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 kwargsToKwargTypesWithTableCache = DogTag.kwargsToKwargTypesWithTableCache 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, TimerHandlers 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() if DogTag.oldLib.UnregisterCustomClassColors then DogTag.oldLib:UnregisterCustomClassColors() end else frame = CreateFrame("Frame") end DogTag.frame = frame frame:RegisterEvent("ADDON_LOADED") frame:RegisterEvent("PLAYER_LOGIN") -- Keep track of which events we've attempted to register -- so we aren't constantly trying to re-register them. local usedEvents = {} DogTag.usedEvents = usedEvents -- Declare that a particular event is being listened to through DogTag -- and should therefore be registered with the main event handler frame -- (if it is a WoW event). -- Also notifies sublibraries of the event listner request via the -- EventRequested event. Events all the way down. function DogTag.eventUsed(event) -- Always notify of the event request -- because sublibraries may have since upgraded, -- or new sublibraries loaded: DogTag:FireEvent("EventRequested", event) if not usedEvents[event] then usedEvents[event] = true pcall(frame.RegisterEvent, frame, event) end end 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, callbackToExtraArg 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] = kwargsToKwargTypesWithTableCache[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 kwargs = memoizeTable(deepCopy(kwargs or false)) nsList = fixNamespaceList[nsList] local kwargTypes = kwargsToKwargTypesWithTableCache[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 for event in pairs(eventList) do DogTag.eventUsed(event) end local uid = DogTag.callback_num + 1 DogTag.callback_num = uid 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 type(param) == "string" then if param:match("^%$") then good = true checkKwargs = param:sub(2) elseif param:match("^%[.*%]$") then good = true mustEvaluate = param end end tab = del(tab) else good = true multiArg = tab end end if good then 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 tonumber(v) and type(arg) == "number" then good = tonumber(v) == arg 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(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 tonumber(v) and type(arg) == "number" then good = tonumber(v) == arg 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 GetTime = _G.GetTime --[[ -- I'm sorry, but this shit is just stupidly wasteful of CPU time. local GetMilliseconds if DogTag_DEBUG then function GetMilliseconds() return math.floor(GetTime() * 1000 + 0.5) end else function GetMilliseconds() return GetTime() * 1000 end end]] do -- OnUpdate local nextTime = 0 local nextUpdateTime = 0 local nextSlowUpdateTime = 0 local nextCacheInvalidationTime = 0 local num = 0 local start -- start time of each cycle -- Limit in milliseconds for each OnUpdate cycle. -- Under max load, this will let FPS drop no lower than 33 FPS (1000/30) -- as a result of the code in this function. local CoroutineLimit = 30 -- Checks to see if we need to yield because the CoroutineLimit was exceeded. -- Call this after every significant call -- (especially external calls that will take an unknown amount of time) -- You can also call this externally through DogTag.checkYield() -- if you have an event handler that might take particularly long. local function checkYield() if running and InCombatLockdown() and debugprofilestop() - start > CoroutineLimit then coroutine.yield() end end DogTag.checkYield = checkYield local GetMouseFocus = GetMouseFocus if not GetMouseFocus and GetMouseFoci then local GetMouseFoci = GetMouseFoci GetMouseFocus = function() return GetMouseFoci()[1] end end local function OnUpdate_Coroutine() while true do running = true _clearCodes() num = num + 1 local currentTime = GetTime() 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 DogTag:FireEvent("FastUpdate") checkYield() if currentTime >= nextUpdateTime then nextUpdateTime = currentTime + 0.15 DogTag:FireEvent("Update") checkYield() end if currentTime >= nextSlowUpdateTime then nextSlowUpdateTime = currentTime + 10 DogTag:FireEvent("SlowUpdate") checkYield() end if currentTime >= nextCacheInvalidationTime then nextCacheInvalidationTime = currentTime + 15 -- The following code only happens out of combat, -- so there is no need to check for yields here. if not InCombatLockdown() then local oldTime = currentTime - 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 + 0.05 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) checkYield() end end end end for fs in pairs(fsNeedUpdate) do fsNeedQuickUpdate[fs] = true fsNeedUpdate[fs] = nil end end -- debugprofilestop is used now instead of GetTime because -- GetTime isn't updated until each frame is drawn. (recent change, WoW 4.3.0 i think?) -- Since this whole process takes place within one frame, -- GetTime will have the same value throughout. -- debugprofilestop is always updated (unless some jerkface resets it with debugprofilestart), so we have to use it instead -- Addendum to this explanation 8/1/2013 (r251): -- This part of the code now yields out of the coroutine instead of breaking the loop -- It still uses its own execution cap because the time spent updating font strings should be more tightly limited -- By yielding out instead of breaking the loop, -- we prevent the problem of only having the front strings that are in the top of the table get updated -- while some sit in the bottom and never get seen. -- Don't define this until we loop through at least one frame -- so that we don't call debugprofilestop unless we need to: local finish_time --= debugprofilestop() + 10 local n = 0 for fs in pairs(fsNeedQuickUpdate) do n = n + 1 if not finish_time then finish_time = debugprofilestop() + 10 -- 10 as in 10 miliseconds elseif n%20 == 0 and debugprofilestop() >= finish_time then -- Set finish_time to nil so that it will be recalculated when we resume. finish_time = nil coroutine.yield() end updateFontString(fs) end running = false -- Since this function is one continual while(true) loop, -- yield at the end each time and wait for the next resume -- triggered by the script handler. coroutine.yield() end end local Coroutine local function OnUpdate() start = debugprofilestop() -- Recreate the coroutine if it is dead (or create it if we haven't yet) -- (It will die if there was an error thrown in the middle of the last cycle). if not Coroutine or coroutine.status(Coroutine) == "dead" then Coroutine = coroutine.create(OnUpdate_Coroutine) end assert(coroutine.resume(Coroutine)) end frame:SetScript("OnUpdate", OnUpdate) end --[[ 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 DogTag.eventUsed(event) 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