/
debugstack = debug.traceback |
strmatch = string.match |
loadfile("../LibStub.lua")() |
local lib, oldMinor = LibStub:NewLibrary("Pants", 1) -- make a new thingy |
assert(lib) -- should return the library table |
assert(not oldMinor) -- should not return the old minor, since it didn't exist |
-- the following is to create data and then be able to check if the same data exists after the fact |
function lib:MyMethod() |
end |
local MyMethod = lib.MyMethod |
lib.MyTable = {} |
local MyTable = lib.MyTable |
local newLib, newOldMinor = LibStub:NewLibrary("Pants", 1) -- try to register a library with the same version, should silently fail |
assert(not newLib) -- should not return since out of date |
local newLib, newOldMinor = LibStub:NewLibrary("Pants", 0) -- try to register a library with a previous, should silently fail |
assert(not newLib) -- should not return since out of date |
local newLib, newOldMinor = LibStub:NewLibrary("Pants", 2) -- register a new version |
assert(newLib) -- library table |
assert(rawequal(newLib, lib)) -- should be the same reference as the previous |
assert(newOldMinor == 1) -- should return the minor version of the previous version |
assert(rawequal(lib.MyMethod, MyMethod)) -- verify that values were saved |
assert(rawequal(lib.MyTable, MyTable)) -- verify that values were saved |
local newLib, newOldMinor = LibStub:NewLibrary("Pants", "Blah 3 Blah") -- register a new version with a string minor version (instead of a number) |
assert(newLib) -- library table |
assert(newOldMinor == 2) -- previous version was 2 |
local newLib, newOldMinor = LibStub:NewLibrary("Pants", "Blah 4 and please ignore 15 Blah") -- register a new version with a string minor version (instead of a number) |
assert(newLib) |
assert(newOldMinor == 3) -- previous version was 3 (even though it gave a string) |
local newLib, newOldMinor = LibStub:NewLibrary("Pants", 5) -- register a new library, using a normal number instead of a string |
assert(newLib) |
assert(newOldMinor == 4) -- previous version was 4 (even though it gave a string) |
debugstack = debug.traceback |
strmatch = string.match |
loadfile("../LibStub.lua")() |
for major, library in LibStub:IterateLibraries() do |
-- check that MyLib doesn't exist yet, by iterating through all the libraries |
assert(major ~= "MyLib") |
end |
assert(not LibStub:GetLibrary("MyLib", true)) -- check that MyLib doesn't exist yet by direct checking |
assert(not pcall(LibStub.GetLibrary, LibStub, "MyLib")) -- don't silently fail, thus it should raise an error. |
local lib = LibStub:NewLibrary("MyLib", 1) -- create the lib |
assert(lib) -- check it exists |
assert(rawequal(LibStub:GetLibrary("MyLib"), lib)) -- verify that :GetLibrary("MyLib") properly equals the lib reference |
assert(LibStub:NewLibrary("MyLib", 2)) -- create a new version |
local count=0 |
for major, library in LibStub:IterateLibraries() do |
-- check that MyLib exists somewhere in the libraries, by iterating through all the libraries |
if major == "MyLib" then -- we found it! |
count = count +1 |
assert(rawequal(library, lib)) -- verify that the references are equal |
end |
end |
assert(count == 1) -- verify that we actually found it, and only once |
debugstack = debug.traceback |
strmatch = string.match |
loadfile("../LibStub.lua")() |
local proxy = newproxy() -- non-string |
assert(not pcall(LibStub.NewLibrary, LibStub, proxy, 1)) -- should error, proxy is not a string, it's userdata |
local success, ret = pcall(LibStub.GetLibrary, proxy, true) |
assert(not success or not ret) -- either error because proxy is not a string or because it's not actually registered. |
assert(not pcall(LibStub.NewLibrary, LibStub, "Something", "No number in here")) -- should error, minor has no string in it. |
assert(not LibStub:GetLibrary("Something", true)) -- shouldn't've created it from the above statement |
debugstack = debug.traceback |
strmatch = string.match |
loadfile("../LibStub.lua")() |
-- Pretend like loaded libstub is old and doesn't have :IterateLibraries |
assert(LibStub.minor) |
LibStub.minor = LibStub.minor - 0.0001 |
LibStub.IterateLibraries = nil |
loadfile("../LibStub.lua")() |
assert(type(LibStub.IterateLibraries)=="function") |
-- Now pretend that we're the same version -- :IterateLibraries should NOT be re-created |
LibStub.IterateLibraries = 123 |
loadfile("../LibStub.lua")() |
assert(LibStub.IterateLibraries == 123) |
-- Now pretend that a newer version is loaded -- :IterateLibraries should NOT be re-created |
LibStub.minor = LibStub.minor + 0.0001 |
loadfile("../LibStub.lua")() |
assert(LibStub.IterateLibraries == 123) |
-- Again with a huge number |
LibStub.minor = LibStub.minor + 1234567890 |
loadfile("../LibStub.lua")() |
assert(LibStub.IterateLibraries == 123) |
print("OK") |
-- $Id: LibStub.lua 76 2007-09-03 01:50:17Z mikk $ |
-- LibStub is a simple versioning stub meant for use in Libraries. http://www.wowace.com/wiki/LibStub for more info |
-- LibStub is hereby placed in the Public Domain |
-- Credits: Kaelten, Cladhaire, ckknight, Mikk, Ammo, Nevcairiel, joshborke |
local LIBSTUB_MAJOR, LIBSTUB_MINOR = "LibStub", 2 -- NEVER MAKE THIS AN SVN REVISION! IT NEEDS TO BE USABLE IN ALL REPOS! |
local LibStub = _G[LIBSTUB_MAJOR] |
-- Check to see is this version of the stub is obsolete |
if not LibStub or LibStub.minor < LIBSTUB_MINOR then |
LibStub = LibStub or {libs = {}, minors = {} } |
_G[LIBSTUB_MAJOR] = LibStub |
LibStub.minor = LIBSTUB_MINOR |
-- LibStub:NewLibrary(major, minor) |
-- major (string) - the major version of the library |
-- minor (string or number ) - the minor version of the library |
-- |
-- returns nil if a newer or same version of the lib is already present |
-- returns empty library object or old library object if upgrade is needed |
function LibStub:NewLibrary(major, minor) |
assert(type(major) == "string", "Bad argument #2 to `NewLibrary' (string expected)") |
minor = assert(tonumber(strmatch(minor, "%d+")), "Minor version must either be a number or contain a number.") |
local oldminor = self.minors[major] |
if oldminor and oldminor >= minor then return nil end |
self.minors[major], self.libs[major] = minor, self.libs[major] or {} |
return self.libs[major], oldminor |
end |
-- LibStub:GetLibrary(major, [silent]) |
-- major (string) - the major version of the library |
-- silent (boolean) - if true, library is optional, silently return nil if its not found |
-- |
-- throws an error if the library can not be found (except silent is set) |
-- returns the library object if found |
function LibStub:GetLibrary(major, silent) |
if not self.libs[major] and not silent then |
error(("Cannot find a library instance of %q."):format(tostring(major)), 2) |
end |
return self.libs[major], self.minors[major] |
end |
-- LibStub:IterateLibraries() |
-- |
-- Returns an iterator for the currently registered libraries |
function LibStub:IterateLibraries() |
return pairs(self.libs) |
end |
setmetatable(LibStub, { __call = LibStub.GetLibrary }) |
end |
package-as: LibStub |
## Interface: 20400 |
## Title: Lib: LibStub |
## Notes: Universal Library Stub |
## Credits: Kaelten, Cladhaire, ckknight, Mikk, Ammo, Nevcairiel |
## X-Website: http://jira.wowace.com/browse/LS |
## X-Category: Library |
## X-License: Public Domain |
LibStub.lua |
LibDataBroker is a small WoW addon library designed to provide a "MVC":http://en.wikipedia.org/wiki/Model-view-controller interface for use in various addons. |
LDB's primary goal is to "detach" plugins for TitanPanel and FuBar from the display addon. |
Plugins can provide data into a simple table, and display addons can receive callbacks to refresh their display of this data. |
LDB also provides a place for addons to register "quicklaunch" functions, removing the need for authors to embed many large libraries to create minimap buttons. |
Users who do not wish to be "plagued" by these buttons simply do not install an addon to render them. |
Due to it's simple generic design, LDB can be used for any design where you wish to have an addon notified of changes to a table. |
h2. Links |
* "API documentation":http://github.com/tekkub/libdatabroker-1-1/wikis/api |
* "Data specifications":http://github.com/tekkub/libdatabroker-1-1/wikis/data-specifications |
* "Addons using LDB":http://github.com/tekkub/libdatabroker-1-1/wikis/addons-using-ldb |
assert(LibStub, "LibDataBroker-1.1 requires LibStub") |
assert(LibStub:GetLibrary("CallbackHandler-1.0", true), "LibDataBroker-1.1 requires CallbackHandler-1.0") |
local lib, oldminor = LibStub:NewLibrary("LibDataBroker-1.1", 4) |
if not lib then return end |
oldminor = oldminor or 0 |
lib.callbacks = lib.callbacks or LibStub:GetLibrary("CallbackHandler-1.0"):New(lib) |
lib.attributestorage, lib.namestorage, lib.proxystorage = lib.attributestorage or {}, lib.namestorage or {}, lib.proxystorage or {} |
local attributestorage, namestorage, callbacks = lib.attributestorage, lib.namestorage, lib.callbacks |
if oldminor < 2 then |
lib.domt = { |
__metatable = "access denied", |
__index = function(self, key) return attributestorage[self] and attributestorage[self][key] end, |
} |
end |
if oldminor < 3 then |
lib.domt.__newindex = function(self, key, value) |
if not attributestorage[self] then attributestorage[self] = {} end |
if attributestorage[self][key] == value then return end |
attributestorage[self][key] = value |
local name = namestorage[self] |
if not name then return end |
callbacks:Fire("LibDataBroker_AttributeChanged", name, key, value, self) |
callbacks:Fire("LibDataBroker_AttributeChanged_"..name, name, key, value, self) |
callbacks:Fire("LibDataBroker_AttributeChanged_"..name.."_"..key, name, key, value, self) |
callbacks:Fire("LibDataBroker_AttributeChanged__"..key, name, key, value, self) |
end |
end |
if oldminor < 2 then |
function lib:NewDataObject(name, dataobj) |
if self.proxystorage[name] then return end |
if dataobj then |
assert(type(dataobj) == "table", "Invalid dataobj, must be nil or a table") |
self.attributestorage[dataobj] = {} |
for i,v in pairs(dataobj) do |
self.attributestorage[dataobj][i] = v |
dataobj[i] = nil |
end |
end |
dataobj = setmetatable(dataobj or {}, self.domt) |
self.proxystorage[name], self.namestorage[dataobj] = dataobj, name |
self.callbacks:Fire("LibDataBroker_DataObjectCreated", name, dataobj) |
return dataobj |
end |
end |
if oldminor < 1 then |
function lib:DataObjectIterator() |
return pairs(self.proxystorage) |
end |
function lib:GetDataObjectByName(dataobjectname) |
return self.proxystorage[dataobjectname] |
end |
function lib:GetNameByDataObject(dataobject) |
return self.namestorage[dataobject] |
end |
end |
if oldminor < 4 then |
local next = pairs(attributestorage) |
function lib:pairs(dataobject_or_name) |
local t = type(dataobject_or_name) |
assert(t == "string" or t == "table", "Usage: ldb:pairs('dataobjectname') or ldb:pairs(dataobject)") |
local dataobj = self.proxystorage[dataobject_or_name] or dataobject_or_name |
assert(attributestorage[dataobj], "Data object not found") |
return next, attributestorage[dataobj], nil |
end |
local ipairs_iter = ipairs(attributestorage) |
function lib:ipairs(dataobject_or_name) |
local t = type(dataobject_or_name) |
assert(t == "string" or t == "table", "Usage: ldb:ipairs('dataobjectname') or ldb:ipairs(dataobject)") |
local dataobj = self.proxystorage[dataobject_or_name] or dataobject_or_name |
assert(attributestorage[dataobj], "Data object not found") |
return ipairs_iter, attributestorage[dataobj], 0 |
end |
end |
<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/ |
..\FrameXML\UI.xsd"> |
<Script file="CallbackHandler-1.0.lua"/> |
</Ui> |
--[[ $Id: CallbackHandler-1.0.lua 13 2009-12-06 21:56:53Z nevcairiel $ ]] |
local MAJOR, MINOR = "CallbackHandler-1.0", 5 |
local CallbackHandler = LibStub:NewLibrary(MAJOR, MINOR) |
if not CallbackHandler then return end -- No upgrade needed |
local meta = {__index = function(tbl, key) tbl[key] = {} return tbl[key] end} |
-- Lua APIs |
local tconcat = table.concat |
local assert, error, loadstring = assert, error, loadstring |
local setmetatable, rawset, rawget = setmetatable, rawset, rawget |
local next, select, pairs, type, tostring = next, select, pairs, type, tostring |
-- Global vars/functions that we don't upvalue since they might get hooked, or upgraded |
-- List them here for Mikk's FindGlobals script |
-- GLOBALS: geterrorhandler |
local xpcall = xpcall |
local function errorhandler(err) |
return geterrorhandler()(err) |
end |
local function CreateDispatcher(argCount) |
local code = [[ |
local next, xpcall, eh = ... |
local method, ARGS |
local function call() method(ARGS) end |
local function dispatch(handlers, ...) |
local index |
index, method = next(handlers) |
if not method then return end |
local OLD_ARGS = ARGS |
ARGS = ... |
repeat |
xpcall(call, eh) |
index, method = next(handlers, index) |
until not method |
ARGS = OLD_ARGS |
end |
return dispatch |
]] |
local ARGS, OLD_ARGS = {}, {} |
for i = 1, argCount do ARGS[i], OLD_ARGS[i] = "arg"..i, "old_arg"..i end |
code = code:gsub("OLD_ARGS", tconcat(OLD_ARGS, ", ")):gsub("ARGS", tconcat(ARGS, ", ")) |
return assert(loadstring(code, "safecall Dispatcher["..argCount.."]"))(next, xpcall, errorhandler) |
end |
local Dispatchers = setmetatable({}, {__index=function(self, argCount) |
local dispatcher = CreateDispatcher(argCount) |
rawset(self, argCount, dispatcher) |
return dispatcher |
end}) |
-------------------------------------------------------------------------- |
-- CallbackHandler:New |
-- |
-- target - target object to embed public APIs in |
-- RegisterName - name of the callback registration API, default "RegisterCallback" |
-- UnregisterName - name of the callback unregistration API, default "UnregisterCallback" |
-- UnregisterAllName - name of the API to unregister all callbacks, default "UnregisterAllCallbacks". false == don't publish this API. |
function CallbackHandler:New(target, RegisterName, UnregisterName, UnregisterAllName, OnUsed, OnUnused) |
-- TODO: Remove this after beta has gone out |
assert(not OnUsed and not OnUnused, "ACE-80: OnUsed/OnUnused are deprecated. Callbacks are now done to registry.OnUsed and registry.OnUnused") |
RegisterName = RegisterName or "RegisterCallback" |
UnregisterName = UnregisterName or "UnregisterCallback" |
if UnregisterAllName==nil then -- false is used to indicate "don't want this method" |
UnregisterAllName = "UnregisterAllCallbacks" |
end |
-- we declare all objects and exported APIs inside this closure to quickly gain access |
-- to e.g. function names, the "target" parameter, etc |
-- Create the registry object |
local events = setmetatable({}, meta) |
local registry = { recurse=0, events=events } |
-- registry:Fire() - fires the given event/message into the registry |
function registry:Fire(eventname, ...) |
if not rawget(events, eventname) or not next(events[eventname]) then return end |
local oldrecurse = registry.recurse |
registry.recurse = oldrecurse + 1 |
Dispatchers[select('#', ...) + 1](events[eventname], eventname, ...) |
registry.recurse = oldrecurse |
if registry.insertQueue and oldrecurse==0 then |
-- Something in one of our callbacks wanted to register more callbacks; they got queued |
for eventname,callbacks in pairs(registry.insertQueue) do |
local first = not rawget(events, eventname) or not next(events[eventname]) -- test for empty before. not test for one member after. that one member may have been overwritten. |
for self,func in pairs(callbacks) do |
events[eventname][self] = func |
-- fire OnUsed callback? |
if first and registry.OnUsed then |
registry.OnUsed(registry, target, eventname) |
first = nil |
end |
end |
end |
registry.insertQueue = nil |
end |
end |
-- Registration of a callback, handles: |
-- self["method"], leads to self["method"](self, ...) |
-- self with function ref, leads to functionref(...) |
-- "addonId" (instead of self) with function ref, leads to functionref(...) |
-- all with an optional arg, which, if present, gets passed as first argument (after self if present) |
target[RegisterName] = function(self, eventname, method, ... --[[actually just a single arg]]) |
if type(eventname) ~= "string" then |
error("Usage: "..RegisterName.."(eventname, method[, arg]): 'eventname' - string expected.", 2) |
end |
method = method or eventname |
local first = not rawget(events, eventname) or not next(events[eventname]) -- test for empty before. not test for one member after. that one member may have been overwritten. |
if type(method) ~= "string" and type(method) ~= "function" then |
error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): 'methodname' - string or function expected.", 2) |
end |
local regfunc |
if type(method) == "string" then |
-- self["method"] calling style |
if type(self) ~= "table" then |
error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): self was not a table?", 2) |
elseif self==target then |
error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): do not use Library:"..RegisterName.."(), use your own 'self'", 2) |
elseif type(self[method]) ~= "function" then |
error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): 'methodname' - method '"..tostring(method).."' not found on self.", 2) |
end |
if select("#",...)>=1 then -- this is not the same as testing for arg==nil! |
local arg=select(1,...) |
regfunc = function(...) self[method](self,arg,...) end |
else |
regfunc = function(...) self[method](self,...) end |
end |
else |
-- function ref with self=object or self="addonId" |
if type(self)~="table" and type(self)~="string" then |
error("Usage: "..RegisterName.."(self or \"addonId\", eventname, method): 'self or addonId': table or string expected.", 2) |
end |
if select("#",...)>=1 then -- this is not the same as testing for arg==nil! |
local arg=select(1,...) |
regfunc = function(...) method(arg,...) end |
else |
regfunc = method |
end |
end |
if events[eventname][self] or registry.recurse<1 then |
-- if registry.recurse<1 then |
-- we're overwriting an existing entry, or not currently recursing. just set it. |
events[eventname][self] = regfunc |
-- fire OnUsed callback? |
if registry.OnUsed and first then |
registry.OnUsed(registry, target, eventname) |
end |
else |
-- we're currently processing a callback in this registry, so delay the registration of this new entry! |
-- yes, we're a bit wasteful on garbage, but this is a fringe case, so we're picking low implementation overhead over garbage efficiency |
registry.insertQueue = registry.insertQueue or setmetatable({},meta) |
registry.insertQueue[eventname][self] = regfunc |
end |
end |
-- Unregister a callback |
target[UnregisterName] = function(self, eventname) |
if not self or self==target then |
error("Usage: "..UnregisterName.."(eventname): bad 'self'", 2) |
end |
if type(eventname) ~= "string" then |
error("Usage: "..UnregisterName.."(eventname): 'eventname' - string expected.", 2) |
end |
if rawget(events, eventname) and events[eventname][self] then |
events[eventname][self] = nil |
-- Fire OnUnused callback? |
if registry.OnUnused and not next(events[eventname]) then |
registry.OnUnused(registry, target, eventname) |
end |
end |
if registry.insertQueue and rawget(registry.insertQueue, eventname) and registry.insertQueue[eventname][self] then |
registry.insertQueue[eventname][self] = nil |
end |
end |
-- OPTIONAL: Unregister all callbacks for given selfs/addonIds |
if UnregisterAllName then |
target[UnregisterAllName] = function(...) |
if select("#",...)<1 then |
error("Usage: "..UnregisterAllName.."([whatFor]): missing 'self' or \"addonId\" to unregister events for.", 2) |
end |
if select("#",...)==1 and ...==target then |
error("Usage: "..UnregisterAllName.."([whatFor]): supply a meaningful 'self' or \"addonId\"", 2) |
end |
for i=1,select("#",...) do |
local self = select(i,...) |
if registry.insertQueue then |
for eventname, callbacks in pairs(registry.insertQueue) do |
if callbacks[self] then |
callbacks[self] = nil |
end |
end |
end |
for eventname, callbacks in pairs(events) do |
if callbacks[self] then |
callbacks[self] = nil |
-- Fire OnUnused callback? |
if registry.OnUnused and not next(callbacks) then |
registry.OnUnused(registry, target, eventname) |
end |
end |
end |
end |
end |
end |
return registry |
end |
-- CallbackHandler purposefully does NOT do explicit embedding. Nor does it |
-- try to upgrade old implicit embeds since the system is selfcontained and |
-- relies on closures to work. |
<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/ |
..\FrameXML\UI.xsd"> |
<Script file="AceTimer-3.0.lua"/> |
</Ui> |
--- **AceTimer-3.0** provides a central facility for registering timers. |
-- AceTimer supports one-shot timers and repeating timers. All timers are stored in an efficient |
-- data structure that allows easy dispatching and fast rescheduling. Timers can be registered, rescheduled |
-- or canceled at any time, even from within a running timer, without conflict or large overhead.\\ |
-- AceTimer is currently limited to firing timers at a frequency of 0.1s. This constant may change |
-- in the future, but for now it seemed like a good compromise in efficiency and accuracy. |
-- |
-- All `:Schedule` functions will return a handle to the current timer, which you will need to store if you |
-- need to cancel or reschedule the timer you just registered. |
-- |
-- **AceTimer-3.0** can be embeded into your addon, either explicitly by calling AceTimer:Embed(MyAddon) or by |
-- specifying it as an embeded library in your AceAddon. All functions will be available on your addon object |
-- and can be accessed directly, without having to explicitly call AceTimer itself.\\ |
-- It is recommended to embed AceTimer, otherwise you'll have to specify a custom `self` on all calls you |
-- make into AceTimer. |
-- @class file |
-- @name AceTimer-3.0 |
-- @release $Id: AceTimer-3.0.lua 895 2009-12-06 16:28:55Z nevcairiel $ |
--[[ |
Basic assumptions: |
* In a typical system, we do more re-scheduling per second than there are timer pulses per second |
* Regardless of timer implementation, we cannot guarantee timely delivery due to FPS restriction (may be as low as 10) |
This implementation: |
CON: The smallest timer interval is constrained by HZ (currently 1/10s). |
PRO: It will still correctly fire any timer slower than HZ over a length of time, e.g. 0.11s interval -> 90 times over 10 seconds |
PRO: In lag bursts, the system simly skips missed timer intervals to decrease load |
CON: Algorithms depending on a timer firing "N times per minute" will fail |
PRO: (Re-)scheduling is O(1) with a VERY small constant. It's a simple linked list insertion in a hash bucket. |
CAUTION: The BUCKETS constant constrains how many timers can be efficiently handled. With too many hash collisions, performance will decrease. |
Major assumptions upheld: |
- ALLOWS scheduling multiple timers with the same funcref/method |
- ALLOWS scheduling more timers during OnUpdate processing |
- ALLOWS unscheduling ANY timer (including the current running one) at any time, including during OnUpdate processing |
]] |
local MAJOR, MINOR = "AceTimer-3.0", 5 |
local AceTimer, oldminor = LibStub:NewLibrary(MAJOR, MINOR) |
if not AceTimer then return end -- No upgrade needed |
AceTimer.hash = AceTimer.hash or {} -- Array of [0..BUCKET-1] = linked list of timers (using .next member) |
-- Linked list gets around ACE-88 and ACE-90. |
AceTimer.selfs = AceTimer.selfs or {} -- Array of [self]={[handle]=timerobj, [handle2]=timerobj2, ...} |
AceTimer.frame = AceTimer.frame or CreateFrame("Frame", "AceTimer30Frame") |
-- Lua APIs |
local assert, error, loadstring = assert, error, loadstring |
local setmetatable, rawset, rawget = setmetatable, rawset, rawget |
local select, pairs, type, next, tostring = select, pairs, type, next, tostring |
local floor, max, min = math.floor, math.max, math.min |
local tconcat = table.concat |
-- WoW APIs |
local GetTime = GetTime |
-- Global vars/functions that we don't upvalue since they might get hooked, or upgraded |
-- List them here for Mikk's FindGlobals script |
-- GLOBALS: DEFAULT_CHAT_FRAME, geterrorhandler |
-- Simple ONE-SHOT timer cache. Much more efficient than a full compost for our purposes. |
local timerCache = nil |
--[[ |
Timers will not be fired more often than HZ-1 times per second. |
Keep at intended speed PLUS ONE or we get bitten by floating point rounding errors (n.5 + 0.1 can be n.599999) |
If this is ever LOWERED, all existing timers need to be enforced to have a delay >= 1/HZ on lib upgrade. |
If this number is ever changed, all entries need to be rehashed on lib upgrade. |
]] |
local HZ = 11 |
--[[ |
Prime for good distribution |
If this number is ever changed, all entries need to be rehashed on lib upgrade. |
]] |
local BUCKETS = 131 |
local hash = AceTimer.hash |
for i=1,BUCKETS do |
hash[i] = hash[i] or false -- make it an integer-indexed array; it's faster than hashes |
end |
--[[ |
xpcall safecall implementation |
]] |
local xpcall = xpcall |
local function errorhandler(err) |
return geterrorhandler()(err) |
end |
local function CreateDispatcher(argCount) |
local code = [[ |
local xpcall, eh = ... -- our arguments are received as unnamed values in "..." since we don't have a proper function declaration |
local method, ARGS |
local function call() return method(ARGS) end |
local function dispatch(func, ...) |
method = func |
if not method then return end |
ARGS = ... |
return xpcall(call, eh) |
end |
return dispatch |
]] |
local ARGS = {} |
for i = 1, argCount do ARGS[i] = "arg"..i end |
code = code:gsub("ARGS", tconcat(ARGS, ", ")) |
return assert(loadstring(code, "safecall Dispatcher["..argCount.."]"))(xpcall, errorhandler) |
end |
local Dispatchers = setmetatable({}, { |
__index=function(self, argCount) |
local dispatcher = CreateDispatcher(argCount) |
rawset(self, argCount, dispatcher) |
return dispatcher |
end |
}) |
Dispatchers[0] = function(func) |
return xpcall(func, errorhandler) |
end |
local function safecall(func, ...) |
return Dispatchers[select('#', ...)](func, ...) |
end |
local lastint = floor(GetTime() * HZ) |
-- -------------------------------------------------------------------- |
-- OnUpdate handler |
-- |
-- traverse buckets, always chasing "now", and fire timers that have expired |
local function OnUpdate() |
local now = GetTime() |
local nowint = floor(now * HZ) |
-- Have we passed into a new hash bucket? |
if nowint == lastint then return end |
local soon = now + 1 -- +1 is safe as long as 1 < HZ < BUCKETS/2 |
-- Pass through each bucket at most once |
-- Happens on e.g. instance loads, but COULD happen on high local load situations also |
for curint = (max(lastint, nowint - BUCKETS) + 1), nowint do -- loop until we catch up with "now", usually only 1 iteration |
local curbucket = (curint % BUCKETS)+1 |
-- Yank the list of timers out of the bucket and empty it. This allows reinsertion in the currently-processed bucket from callbacks. |
local nexttimer = hash[curbucket] |
hash[curbucket] = false -- false rather than nil to prevent the array from becoming a hash |
while nexttimer do |
local timer = nexttimer |
nexttimer = timer.next |
local when = timer.when |
if when < soon then |
-- Call the timer func, either as a method on given object, or a straight function ref |
local callback = timer.callback |
if type(callback) == "string" then |
safecall(timer.object[callback], timer.object, timer.arg) |
elseif callback then |
safecall(callback, timer.arg) |
else |
-- probably nilled out by CancelTimer |
timer.delay = nil -- don't reschedule it |
end |
local delay = timer.delay -- NOW make a local copy, can't do it earlier in case the timer cancelled itself in the callback |
if not delay then |
-- single-shot timer (or cancelled) |
AceTimer.selfs[timer.object][tostring(timer)] = nil |
timerCache = timer |
else |
-- repeating timer |
local newtime = when + delay |
if newtime < now then -- Keep lag from making us firing a timer unnecessarily. (Note that this still won't catch too-short-delay timers though.) |
newtime = now + delay |
end |
timer.when = newtime |
-- add next timer execution to the correct bucket |
local bucket = (floor(newtime * HZ) % BUCKETS) + 1 |
timer.next = hash[bucket] |
hash[bucket] = timer |
end |
else -- if when>=soon |
-- reinsert (yeah, somewhat expensive, but shouldn't be happening too often either due to hash distribution) |
timer.next = hash[curbucket] |
hash[curbucket] = timer |
end -- if when<soon ... else |
end -- while nexttimer do |
end -- for curint=lastint,nowint |
lastint = nowint |
end |
-- --------------------------------------------------------------------- |
-- Reg( callback, delay, arg, repeating ) |
-- |
-- callback( function or string ) - direct function ref or method name in our object for the callback |
-- delay(int) - delay for the timer |
-- arg(variant) - any argument to be passed to the callback function |
-- repeating(boolean) - repeating timer, or oneshot |
-- |
-- returns the handle of the timer for later processing (canceling etc) |
local function Reg(self, callback, delay, arg, repeating) |
if type(callback) ~= "string" and type(callback) ~= "function" then |
local error_origin = repeating and "ScheduleRepeatingTimer" or "ScheduleTimer" |
error(MAJOR..": " .. error_origin .. "(callback, delay, arg): 'callback' - function or method name expected.", 3) |
end |
if type(callback) == "string" then |
if type(self)~="table" then |
local error_origin = repeating and "ScheduleRepeatingTimer" or "ScheduleTimer" |
error(MAJOR..": " .. error_origin .. "(\"methodName\", delay, arg): 'self' - must be a table.", 3) |
end |
if type(self[callback]) ~= "function" then |
local error_origin = repeating and "ScheduleRepeatingTimer" or "ScheduleTimer" |
error(MAJOR..": " .. error_origin .. "(\"methodName\", delay, arg): 'methodName' - method not found on target object.", 3) |
end |
end |
if delay < (1 / (HZ - 1)) then |
delay = 1 / (HZ - 1) |
end |
-- Create and stuff timer in the correct hash bucket |
local now = GetTime() |
local timer = timerCache or {} -- Get new timer object (from cache if available) |
timerCache = nil |
timer.object = self |
timer.callback = callback |
timer.delay = (repeating and delay) |
timer.arg = arg |
timer.when = now + delay |
local bucket = (floor((now+delay)*HZ) % BUCKETS) + 1 |
timer.next = hash[bucket] |
hash[bucket] = timer |
-- Insert timer in our self->handle->timer registry |
local handle = tostring(timer) |
local selftimers = AceTimer.selfs[self] |
if not selftimers then |
selftimers = {} |
AceTimer.selfs[self] = selftimers |
end |
selftimers[handle] = timer |
selftimers.__ops = (selftimers.__ops or 0) + 1 |
return handle |
end |
--- Schedule a new one-shot timer. |
-- The timer will fire once in `delay` seconds, unless canceled before. |
-- @param callback Callback function for the timer pulse (funcref or method name). |
-- @param delay Delay for the timer, in seconds. |
-- @param arg An optional argument to be passed to the callback function. |
-- @usage |
-- MyAddon = LibStub("AceAddon-3.0"):NewAddon("TimerTest", "AceTimer-3.0") |
-- |
-- function MyAddon:OnEnable() |
-- self:ScheduleTimer("TimerFeedback", 5) |
-- end |
-- |
-- function MyAddon:TimerFeedback() |
-- print("5 seconds passed") |
-- end |
function AceTimer:ScheduleTimer(callback, delay, arg) |
return Reg(self, callback, delay, arg) |
end |
--- Schedule a repeating timer. |
-- The timer will fire every `delay` seconds, until canceled. |
-- @param callback Callback function for the timer pulse (funcref or method name). |
-- @param delay Delay for the timer, in seconds. |
-- @param arg An optional argument to be passed to the callback function. |
-- @usage |
-- MyAddon = LibStub("AceAddon-3.0"):NewAddon("TimerTest", "AceTimer-3.0") |
-- |
-- function MyAddon:OnEnable() |
-- self.timerCount = 0 |
-- self.testTimer = self:ScheduleRepeatingTimer("TimerFeedback", 5) |
-- end |
-- |
-- function MyAddon:TimerFeedback() |
-- self.timerCount = self.timerCount + 1 |
-- print(("%d seconds passed"):format(5 * self.timerCount)) |
-- -- run 30 seconds in total |
-- if self.timerCount == 6 then |
-- self:CancelTimer(self.testTimer) |
-- end |
-- end |
function AceTimer:ScheduleRepeatingTimer(callback, delay, arg) |
return Reg(self, callback, delay, arg, true) |
end |
--- Cancels a timer with the given handle, registered by the same addon object as used for `:ScheduleTimer` |
-- Both one-shot and repeating timers can be canceled with this function, as long as the `handle` is valid |
-- and the timer has not fired yet or was canceled before. |
-- @param handle The handle of the timer, as returned by `:ScheduleTimer` or `:ScheduleRepeatingTimer` |
-- @param silent If true, no error is raised if the timer handle is invalid (expired or already canceled) |
-- @return True if the timer was successfully cancelled. |
function AceTimer:CancelTimer(handle, silent) |
if not handle then return end -- nil handle -> bail out without erroring |
if type(handle) ~= "string" then |
error(MAJOR..": CancelTimer(handle): 'handle' - expected a string", 2) -- for now, anyway |
end |
local selftimers = AceTimer.selfs[self] |
local timer = selftimers and selftimers[handle] |
if silent then |
if timer then |
timer.callback = nil -- don't run it again |
timer.delay = nil -- if this is the currently-executing one: don't even reschedule |
-- The timer object is removed in the OnUpdate loop |
end |
return not not timer -- might return "true" even if we double-cancel. we'll live. |
else |
if not timer then |
geterrorhandler()(MAJOR..": CancelTimer(handle[, silent]): '"..tostring(handle).."' - no such timer registered") |
return false |
end |
if not timer.callback then |
geterrorhandler()(MAJOR..": CancelTimer(handle[, silent]): '"..tostring(handle).."' - timer already cancelled or expired") |
return false |
end |
timer.callback = nil -- don't run it again |
timer.delay = nil -- if this is the currently-executing one: don't even reschedule |
return true |
end |
end |
--- Cancels all timers registered to the current addon object ('self') |
function AceTimer:CancelAllTimers() |
if not(type(self) == "string" or type(self) == "table") then |
error(MAJOR..": CancelAllTimers(): 'self' - must be a string or a table",2) |
end |
if self == AceTimer then |
error(MAJOR..": CancelAllTimers(): supply a meaningful 'self'", 2) |
end |
local selftimers = AceTimer.selfs[self] |
if selftimers then |
for handle,v in pairs(selftimers) do |
if type(v) == "table" then -- avoid __ops, etc |
AceTimer.CancelTimer(self, handle, true) |
end |
end |
end |
end |
--- Returns the time left for a timer with the given handle, registered by the current addon object ('self'). |
-- This function will raise a warning when the handle is invalid, but not stop execution. |
-- @param handle The handle of the timer, as returned by `:ScheduleTimer` or `:ScheduleRepeatingTimer` |
-- @return The time left on the timer, or false if the handle is invalid. |
function AceTimer:TimeLeft(handle) |
if not handle then return end |
if type(handle) ~= "string" then |
error(MAJOR..": TimeLeft(handle): 'handle' - expected a string", 2) -- for now, anyway |
end |
local selftimers = AceTimer.selfs[self] |
local timer = selftimers and selftimers[handle] |
if not timer then |
geterrorhandler()(MAJOR..": TimeLeft(handle): '"..tostring(handle).."' - no such timer registered") |
return false |
end |
return timer.when - GetTime() |
end |
-- --------------------------------------------------------------------- |
-- PLAYER_REGEN_ENABLED: Run through our .selfs[] array step by step |
-- and clean it out - otherwise the table indices can grow indefinitely |
-- if an addon starts and stops a lot of timers. AceBucket does this! |
-- |
-- See ACE-94 and tests/AceTimer-3.0-ACE-94.lua |
local lastCleaned = nil |
local function OnEvent(this, event) |
if event~="PLAYER_REGEN_ENABLED" then |
return |
end |
-- Get the next 'self' to process |
local selfs = AceTimer.selfs |
local self = next(selfs, lastCleaned) |
if not self then |
self = next(selfs) |
end |
lastCleaned = self |
if not self then -- should only happen if .selfs[] is empty |
return |
end |
-- Time to clean it out? |
local list = selfs[self] |
if (list.__ops or 0) < 250 then -- 250 slosh indices = ~10KB wasted (max!). For one 'self'. |
return |
end |
-- Create a new table and copy all members over |
local newlist = {} |
local n=0 |
for k,v in pairs(list) do |
newlist[k] = v |
n=n+1 |
end |
newlist.__ops = 0 -- Reset operation count |
-- And since we now have a count of the number of live timers, check that it's reasonable. Emit a warning if not. |
if n>BUCKETS then |
DEFAULT_CHAT_FRAME:AddMessage(MAJOR..": Warning: The addon/module '"..tostring(self).."' has "..n.." live timers. Surely that's not intended?") |
end |
selfs[self] = newlist |
end |
-- --------------------------------------------------------------------- |
-- Embed handling |
AceTimer.embeds = AceTimer.embeds or {} |
local mixins = { |
"ScheduleTimer", "ScheduleRepeatingTimer", |
"CancelTimer", "CancelAllTimers", |
"TimeLeft" |
} |
function AceTimer:Embed(target) |
AceTimer.embeds[target] = true |
for _,v in pairs(mixins) do |
target[v] = AceTimer[v] |
end |
return target |
end |
-- AceTimer:OnEmbedDisable( target ) |
-- target (object) - target object that AceTimer is embedded in. |
-- |
-- cancel all timers registered for the object |
function AceTimer:OnEmbedDisable( target ) |
target:CancelAllTimers() |
end |
for addon in pairs(AceTimer.embeds) do |
AceTimer:Embed(addon) |
end |
-- --------------------------------------------------------------------- |
-- Debug tools (expose copies of internals to test suites) |
AceTimer.debug = AceTimer.debug or {} |
AceTimer.debug.HZ = HZ |
AceTimer.debug.BUCKETS = BUCKETS |
-- --------------------------------------------------------------------- |
-- Finishing touchups |
AceTimer.frame:SetScript("OnUpdate", OnUpdate) |
AceTimer.frame:SetScript("OnEvent", OnEvent) |
AceTimer.frame:RegisterEvent("PLAYER_REGEN_ENABLED") |
-- In theory, we should hide&show the frame based on there being timers or not. |
-- However, this job is fairly expensive, and the chance that there will |
-- actually be zero timers running is diminuitive to say the lest. |
--- AceEvent-3.0 provides event registration and secure dispatching. |
-- All dispatching is done using **CallbackHandler-1.0**. AceEvent is a simple wrapper around |
-- CallbackHandler, and dispatches all game events or addon message to the registrees. |
-- |
-- **AceEvent-3.0** can be embeded into your addon, either explicitly by calling AceEvent:Embed(MyAddon) or by |
-- specifying it as an embeded library in your AceAddon. All functions will be available on your addon object |
-- and can be accessed directly, without having to explicitly call AceEvent itself.\\ |
-- It is recommended to embed AceEvent, otherwise you'll have to specify a custom `self` on all calls you |
-- make into AceEvent. |
-- @class file |
-- @name AceEvent-3.0 |
-- @release $Id: AceEvent-3.0.lua 877 2009-11-02 15:56:50Z nevcairiel $ |
local MAJOR, MINOR = "AceEvent-3.0", 3 |
local AceEvent = LibStub:NewLibrary(MAJOR, MINOR) |
if not AceEvent then return end |
-- Lua APIs |
local pairs = pairs |
local CallbackHandler = LibStub:GetLibrary("CallbackHandler-1.0") |
AceEvent.frame = AceEvent.frame or CreateFrame("Frame", "AceEvent30Frame") -- our event frame |
AceEvent.embeds = AceEvent.embeds or {} -- what objects embed this lib |
-- APIs and registry for blizzard events, using CallbackHandler lib |
if not AceEvent.events then |
AceEvent.events = CallbackHandler:New(AceEvent, |
"RegisterEvent", "UnregisterEvent", "UnregisterAllEvents") |
end |
function AceEvent.events:OnUsed(target, eventname) |
AceEvent.frame:RegisterEvent(eventname) |
end |
function AceEvent.events:OnUnused(target, eventname) |
AceEvent.frame:UnregisterEvent(eventname) |
end |
-- APIs and registry for IPC messages, using CallbackHandler lib |
if not AceEvent.messages then |
AceEvent.messages = CallbackHandler:New(AceEvent, |
"RegisterMessage", "UnregisterMessage", "UnregisterAllMessages" |
) |
AceEvent.SendMessage = AceEvent.messages.Fire |
end |
--- embedding and embed handling |
local mixins = { |
"RegisterEvent", "UnregisterEvent", |
"RegisterMessage", "UnregisterMessage", |
"SendMessage", |
"UnregisterAllEvents", "UnregisterAllMessages", |
} |
--- Register for a Blizzard Event. |
-- The callback will always be called with the event as the first argument, and if supplied, the `arg` as second argument. |
-- Any arguments to the event will be passed on after that. |
-- @name AceEvent:RegisterEvent |
-- @class function |
-- @paramsig event[, callback [, arg]] |
-- @param event The event to register for |
-- @param callback The callback function to call when the event is triggered (funcref or method, defaults to a method with the event name) |
-- @param arg An optional argument to pass to the callback function |
--- Unregister an event. |
-- @name AceEvent:UnregisterEvent |
-- @class function |
-- @paramsig event |
-- @param event The event to unregister |
--- Register for a custom AceEvent-internal message. |
-- The callback will always be called with the event as the first argument, and if supplied, the `arg` as second argument. |
-- Any arguments to the event will be passed on after that. |
-- @name AceEvent:RegisterMessage |
-- @class function |
-- @paramsig message[, callback [, arg]] |
-- @param message The message to register for |
-- @param callback The callback function to call when the message is triggered (funcref or method, defaults to a method with the event name) |
-- @param arg An optional argument to pass to the callback function |
--- Unregister a message |
-- @name AceEvent:UnregisterMessage |
-- @class function |
-- @paramsig message |
-- @param message The message to unregister |
--- Send a message over the AceEvent-3.0 internal message system to other addons registered for this message. |
-- @name AceEvent:SendMessage |
-- @class function |
-- @paramsig message, ... |
-- @param message The message to send |
-- @param ... Any arguments to the message |
-- Embeds AceEvent into the target object making the functions from the mixins list available on target:.. |
-- @param target target object to embed AceEvent in |
function AceEvent:Embed(target) |
for k, v in pairs(mixins) do |
target[v] = self[v] |
end |
self.embeds[target] = true |
return target |
end |
-- AceEvent:OnEmbedDisable( target ) |
-- target (object) - target object that is being disabled |
-- |
-- Unregister all events messages etc when the target disables. |
-- this method should be called by the target manually or by an addon framework |
function AceEvent:OnEmbedDisable(target) |
target:UnregisterAllEvents() |
target:UnregisterAllMessages() |
end |
-- Script to fire blizzard events into the event listeners |
local events = AceEvent.events |
AceEvent.frame:SetScript("OnEvent", function(this, event, ...) |
events:Fire(event, ...) |
end) |
--- Finally: upgrade our old embeds |
for target, v in pairs(AceEvent.embeds) do |
AceEvent:Embed(target) |
end |
<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/ |
..\FrameXML\UI.xsd"> |
<Script file="AceEvent-3.0.lua"/> |
</Ui> |
<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/ |
..\FrameXML\UI.xsd"> |
<Script file="AceAddon-3.0.lua"/> |
</Ui> |
--- **AceAddon-3.0** provides a template for creating addon objects. |
-- It'll provide you with a set of callback functions that allow you to simplify the loading |
-- process of your addon.\\ |
-- Callbacks provided are:\\ |
-- * **OnInitialize**, which is called directly after the addon is fully loaded. |
-- * **OnEnable** which gets called during the PLAYER_LOGIN event, when most of the data provided by the game is already present. |
-- * **OnDisable**, which is only called when your addon is manually being disabled. |
-- @usage |
-- -- A small (but complete) addon, that doesn't do anything, |
-- -- but shows usage of the callbacks. |
-- local MyAddon = LibStub("AceAddon-3.0"):NewAddon("MyAddon") |
-- |
-- function MyAddon:OnInitialize() |
-- -- do init tasks here, like loading the Saved Variables, |
-- -- or setting up slash commands. |
-- end |
-- |
-- function MyAddon:OnEnable() |
-- -- Do more initialization here, that really enables the use of your addon. |
-- -- Register Events, Hook functions, Create Frames, Get information from |
-- -- the game that wasn't available in OnInitialize |
-- end |
-- |
-- function MyAddon:OnDisable() |
-- -- Unhook, Unregister Events, Hide frames that you created. |
-- -- You would probably only use an OnDisable if you want to |
-- -- build a "standby" mode, or be able to toggle modules on/off. |
-- end |
-- @class file |
-- @name AceAddon-3.0.lua |
-- @release $Id: AceAddon-3.0.lua 895 2009-12-06 16:28:55Z nevcairiel $ |
local MAJOR, MINOR = "AceAddon-3.0", 5 |
local AceAddon, oldminor = LibStub:NewLibrary(MAJOR, MINOR) |
if not AceAddon then return end -- No Upgrade needed. |
AceAddon.frame = AceAddon.frame or CreateFrame("Frame", "AceAddon30Frame") -- Our very own frame |
AceAddon.addons = AceAddon.addons or {} -- addons in general |
AceAddon.statuses = AceAddon.statuses or {} -- statuses of addon. |
AceAddon.initializequeue = AceAddon.initializequeue or {} -- addons that are new and not initialized |
AceAddon.enablequeue = AceAddon.enablequeue or {} -- addons that are initialized and waiting to be enabled |
AceAddon.embeds = AceAddon.embeds or setmetatable({}, {__index = function(tbl, key) tbl[key] = {} return tbl[key] end }) -- contains a list of libraries embedded in an addon |
-- Lua APIs |
local tinsert, tconcat, tremove = table.insert, table.concat, table.remove |
local fmt, tostring = string.format, tostring |
local select, pairs, next, type, unpack = select, pairs, next, type, unpack |
local loadstring, assert, error = loadstring, assert, error |
local setmetatable, getmetatable, rawset, rawget = setmetatable, getmetatable, rawset, rawget |
-- Global vars/functions that we don't upvalue since they might get hooked, or upgraded |
-- List them here for Mikk's FindGlobals script |
-- GLOBALS: LibStub, IsLoggedIn, geterrorhandler |
--[[ |
xpcall safecall implementation |
]] |
local xpcall = xpcall |
local function errorhandler(err) |
return geterrorhandler()(err) |
end |
local function CreateDispatcher(argCount) |
local code = [[ |
local xpcall, eh = ... |
local method, ARGS |
local function call() return method(ARGS) end |
local function dispatch(func, ...) |
method = func |
if not method then return end |
ARGS = ... |
return xpcall(call, eh) |
end |
return dispatch |
]] |
local ARGS = {} |
for i = 1, argCount do ARGS[i] = "arg"..i end |
code = code:gsub("ARGS", tconcat(ARGS, ", ")) |
return assert(loadstring(code, "safecall Dispatcher["..argCount.."]"))(xpcall, errorhandler) |
end |
local Dispatchers = setmetatable({}, {__index=function(self, argCount) |
local dispatcher = CreateDispatcher(argCount) |
rawset(self, argCount, dispatcher) |
return dispatcher |
end}) |
Dispatchers[0] = function(func) |
return xpcall(func, errorhandler) |
end |
local function safecall(func, ...) |
-- we check to see if the func is passed is actually a function here and don't error when it isn't |
-- this safecall is used for optional functions like OnInitialize OnEnable etc. When they are not |
-- present execution should continue without hinderance |
if type(func) == "function" then |
return Dispatchers[select('#', ...)](func, ...) |
end |
end |
-- local functions that will be implemented further down |
local Enable, Disable, EnableModule, DisableModule, Embed, NewModule, GetModule, GetName, SetDefaultModuleState, SetDefaultModuleLibraries, SetEnabledState, SetDefaultModulePrototype |
-- used in the addon metatable |
local function addontostring( self ) return self.name end |
--- Create a new AceAddon-3.0 addon. |
-- Any libraries you specified will be embeded, and the addon will be scheduled for |
-- its OnInitialize and OnEnable callbacks. |
-- The final addon object, with all libraries embeded, will be returned. |
-- @paramsig [object ,]name[, lib, ...] |
-- @param object Table to use as a base for the addon (optional) |
-- @param name Name of the addon object to create |
-- @param lib List of libraries to embed into the addon |
-- @usage |
-- -- Create a simple addon object |
-- MyAddon = LibStub("AceAddon-3.0"):NewAddon("MyAddon", "AceEvent-3.0") |
-- |
-- -- Create a Addon object based on the table of a frame |
-- local MyFrame = CreateFrame("Frame") |
-- MyAddon = LibStub("AceAddon-3.0"):NewAddon(MyFrame, "MyAddon", "AceEvent-3.0") |
function AceAddon:NewAddon(objectorname, ...) |
local object,name |
local i=1 |
if type(objectorname)=="table" then |
object=objectorname |
name=... |
i=2 |
else |
name=objectorname |
end |
if type(name)~="string" then |
error(("Usage: NewAddon([object,] name, [lib, lib, lib, ...]): 'name' - string expected got '%s'."):format(type(name)), 2) |
end |
if self.addons[name] then |
error(("Usage: NewAddon([object,] name, [lib, lib, lib, ...]): 'name' - Addon '%s' already exists."):format(name), 2) |
end |
object = object or {} |
object.name = name |
local addonmeta = {} |
local oldmeta = getmetatable(object) |
if oldmeta then |
for k, v in pairs(oldmeta) do addonmeta[k] = v end |
end |
addonmeta.__tostring = addontostring |
setmetatable( object, addonmeta ) |
self.addons[name] = object |
object.modules = {} |
object.defaultModuleLibraries = {} |
Embed( object ) -- embed NewModule, GetModule methods |
self:EmbedLibraries(object, select(i,...)) |
-- add to queue of addons to be initialized upon ADDON_LOADED |
tinsert(self.initializequeue, object) |
return object |
end |
--- Get the addon object by its name from the internal AceAddon registry. |
-- Throws an error if the addon object cannot be found (except if silent is set). |
-- @param name unique name of the addon object |
-- @param silent if true, the addon is optional, silently return nil if its not found |
-- @usage |
-- -- Get the Addon |
-- MyAddon = LibStub("AceAddon-3.0"):GetAddon("MyAddon") |
function AceAddon:GetAddon(name, silent) |
if not silent and not self.addons[name] then |
error(("Usage: GetAddon(name): 'name' - Cannot find an AceAddon '%s'."):format(tostring(name)), 2) |
end |
return self.addons[name] |
end |
-- - Embed a list of libraries into the specified addon. |
-- This function will try to embed all of the listed libraries into the addon |
-- and error if a single one fails. |
-- |
-- **Note:** This function is for internal use by :NewAddon/:NewModule |
-- @paramsig addon, [lib, ...] |
-- @param addon addon object to embed the libs in |
-- @param lib List of libraries to embed into the addon |
function AceAddon:EmbedLibraries(addon, ...) |
for i=1,select("#", ... ) do |
local libname = select(i, ...) |
self:EmbedLibrary(addon, libname, false, 4) |
end |
end |
-- - Embed a library into the addon object. |
-- This function will check if the specified library is registered with LibStub |
-- and if it has a :Embed function to call. It'll error if any of those conditions |
-- fails. |
-- |
-- **Note:** This function is for internal use by :EmbedLibraries |
-- @paramsig addon, libname[, silent[, offset]] |
-- @param addon addon object to embed the library in |
-- @param libname name of the library to embed |
-- @param silent marks an embed to fail silently if the library doesn't exist (optional) |
-- @param offset will push the error messages back to said offset, defaults to 2 (optional) |
function AceAddon:EmbedLibrary(addon, libname, silent, offset) |
local lib = LibStub:GetLibrary(libname, true) |
if not lib and not silent then |
error(("Usage: EmbedLibrary(addon, libname, silent, offset): 'libname' - Cannot find a library instance of %q."):format(tostring(libname)), offset or 2) |
elseif lib and type(lib.Embed) == "function" then |
lib:Embed(addon) |
tinsert(self.embeds[addon], libname) |
return true |
elseif lib then |
error(("Usage: EmbedLibrary(addon, libname, silent, offset): 'libname' - Library '%s' is not Embed capable"):format(libname), offset or 2) |
end |
end |
--- Return the specified module from an addon object. |
-- Throws an error if the addon object cannot be found (except if silent is set) |
-- @name //addon//:GetModule |
-- @paramsig name[, silent] |
-- @param name unique name of the module |
-- @param silent if true, the module is optional, silently return nil if its not found (optional) |
-- @usage |
-- -- Get the Addon |
-- MyAddon = LibStub("AceAddon-3.0"):GetAddon("MyAddon") |
-- -- Get the Module |
-- MyModule = MyAddon:GetModule("MyModule") |
function GetModule(self, name, silent) |
if not self.modules[name] and not silent then |
error(("Usage: GetModule(name, silent): 'name' - Cannot find module '%s'."):format(tostring(name)), 2) |
end |
return self.modules[name] |
end |
local function IsModuleTrue(self) return true end |
--- Create a new module for the addon. |
-- The new module can have its own embeded libraries and/or use a module prototype to be mixed into the module.\\ |
-- A module has the same functionality as a real addon, it can have modules of its own, and has the same API as |
-- an addon object. |
-- @name //addon//:NewModule |
-- @paramsig name[, prototype|lib[, lib, ...]] |
-- @param name unique name of the module |
-- @param prototype object to derive this module from, methods and values from this table will be mixed into the module (optional) |
-- @param lib List of libraries to embed into the addon |
-- @usage |
-- -- Create a module with some embeded libraries |
-- MyModule = MyAddon:NewModule("MyModule", "AceEvent-3.0", "AceHook-3.0") |
-- |
-- -- Create a module with a prototype |
-- local prototype = { OnEnable = function(self) print("OnEnable called!") end } |
-- MyModule = MyAddon:NewModule("MyModule", prototype, "AceEvent-3.0", "AceHook-3.0") |
function NewModule(self, name, prototype, ...) |
if type(name) ~= "string" then error(("Usage: NewModule(name, [prototype, [lib, lib, lib, ...]): 'name' - string expected got '%s'."):format(type(name)), 2) end |
if type(prototype) ~= "string" and type(prototype) ~= "table" and type(prototype) ~= "nil" then error(("Usage: NewModule(name, [prototype, [lib, lib, lib, ...]): 'prototype' - table (prototype), string (lib) or nil expected got '%s'."):format(type(prototype)), 2) end |
if self.modules[name] then error(("Usage: NewModule(name, [prototype, [lib, lib, lib, ...]): 'name' - Module '%s' already exists."):format(name), 2) end |
-- modules are basically addons. We treat them as such. They will be added to the initializequeue properly as well. |
-- NewModule can only be called after the parent addon is present thus the modules will be initialized after their parent is. |
local module = AceAddon:NewAddon(fmt("%s_%s", self.name or tostring(self), name)) |
module.IsModule = IsModuleTrue |
module:SetEnabledState(self.defaultModuleState) |
module.moduleName = name |
if type(prototype) == "string" then |
AceAddon:EmbedLibraries(module, prototype, ...) |
else |
AceAddon:EmbedLibraries(module, ...) |
end |
AceAddon:EmbedLibraries(module, unpack(self.defaultModuleLibraries)) |
if not prototype or type(prototype) == "string" then |
prototype = self.defaultModulePrototype or nil |
end |
if type(prototype) == "table" then |
local mt = getmetatable(module) |
mt.__index = prototype |
setmetatable(module, mt) -- More of a Base class type feel. |
end |
safecall(self.OnModuleCreated, self, module) -- Was in Ace2 and I think it could be a cool thing to have handy. |
self.modules[name] = module |
return module |
end |
--- Returns the real name of the addon or module, without any prefix. |
-- @name //addon//:GetName |
-- @paramsig |
-- @usage |
-- print(MyAddon:GetName()) |
-- -- prints "MyAddon" |
function GetName(self) |
return self.moduleName or self.name |
end |
--- Enables the Addon, if possible, return true or false depending on success. |
-- This internally calls AceAddon:EnableAddon(), thus dispatching a OnEnable callback |
-- and enabling all modules of the addon (unless explicitly disabled).\\ |
-- :Enable() also sets the internal `enableState` variable to true |
-- @name //addon//:Enable |
-- @paramsig |
-- @usage |
-- -- Enable MyModule |
-- MyAddon = LibStub("AceAddon-3.0"):GetAddon("MyAddon") |
-- MyModule = MyAddon:GetModule("MyModule") |
-- MyModule:Enable() |
function Enable(self) |
self:SetEnabledState(true) |
return AceAddon:EnableAddon(self) |
end |
--- Disables the Addon, if possible, return true or false depending on success. |
-- This internally calls AceAddon:DisableAddon(), thus dispatching a OnDisable callback |
-- and disabling all modules of the addon.\\ |
-- :Disable() also sets the internal `enableState` variable to false |
-- @name //addon//:Disable |
-- @paramsig |
-- @usage |
-- -- Disable MyAddon |
-- MyAddon = LibStub("AceAddon-3.0"):GetAddon("MyAddon") |
-- MyAddon:Disable() |
function Disable(self) |
self:SetEnabledState(false) |
return AceAddon:DisableAddon(self) |
end |
--- Enables the Module, if possible, return true or false depending on success. |
-- Short-hand function that retrieves the module via `:GetModule` and calls `:Enable` on the module object. |
-- @name //addon//:EnableModule |
-- @paramsig name |
-- @usage |
-- -- Enable MyModule using :GetModule |
-- MyAddon = LibStub("AceAddon-3.0"):GetAddon("MyAddon") |
-- MyModule = MyAddon:GetModule("MyModule") |
-- MyModule:Enable() |
-- |
-- -- Enable MyModule using the short-hand |
-- MyAddon = LibStub("AceAddon-3.0"):GetAddon("MyAddon") |
-- MyAddon:EnableModule("MyModule") |
function EnableModule(self, name) |
local module = self:GetModule( name ) |
return module:Enable() |
end |
--- Disables the Module, if possible, return true or false depending on success. |
-- Short-hand function that retrieves the module via `:GetModule` and calls `:Disable` on the module object. |
-- @name //addon//:DisableModule |
-- @paramsig name |
-- @usage |
-- -- Disable MyModule using :GetModule |
-- MyAddon = LibStub("AceAddon-3.0"):GetAddon("MyAddon") |
-- MyModule = MyAddon:GetModule("MyModule") |
-- MyModule:Disable() |
-- |
-- -- Disable MyModule using the short-hand |
-- MyAddon = LibStub("AceAddon-3.0"):GetAddon("MyAddon") |
-- MyAddon:DisableModule("MyModule") |
function DisableModule(self, name) |
local module = self:GetModule( name ) |
return module:Disable() |
end |
--- Set the default libraries to be mixed into all modules created by this object. |
-- Note that you can only change the default module libraries before any module is created. |
-- @name //addon//:SetDefaultModuleLibraries |
-- @paramsig lib[, lib, ...] |
-- @param lib List of libraries to embed into the addon |
-- @usage |
-- -- Create the addon object |
-- MyAddon = LibStub("AceAddon-3.0"):NewAddon("MyAddon") |
-- -- Configure default libraries for modules (all modules need AceEvent-3.0) |
-- MyAddon:SetDefaultModuleLibraries("AceEvent-3.0") |
-- -- Create a module |
-- MyModule = MyAddon:NewModule("MyModule") |
function SetDefaultModuleLibraries(self, ...) |
if next(self.modules) then |
error("Usage: SetDefaultModuleLibraries(...): cannot change the module defaults after a module has been registered.", 2) |
end |
self.defaultModuleLibraries = {...} |
end |
--- Set the default state in which new modules are being created. |
-- Note that you can only change the default state before any module is created. |
-- @name //addon//:SetDefaultModuleState |
-- @paramsig state |
-- @param state Default state for new modules, true for enabled, false for disabled |
-- @usage |
-- -- Create the addon object |
-- MyAddon = LibStub("AceAddon-3.0"):NewAddon("MyAddon") |
-- -- Set the default state to "disabled" |
-- MyAddon:SetDefaultModuleState(false) |
-- -- Create a module and explicilty enable it |
-- MyModule = MyAddon:NewModule("MyModule") |
-- MyModule:Enable() |
function SetDefaultModuleState(self, state) |
if next(self.modules) then |
error("Usage: SetDefaultModuleState(state): cannot change the module defaults after a module has been registered.", 2) |
end |
self.defaultModuleState = state |
end |
--- Set the default prototype to use for new modules on creation. |
-- Note that you can only change the default prototype before any module is created. |
-- @name //addon//:SetDefaultModulePrototype |
-- @paramsig prototype |
-- @param prototype Default prototype for the new modules (table) |
-- @usage |
-- -- Define a prototype |
-- local prototype = { OnEnable = function(self) print("OnEnable called!") end } |
-- -- Set the default prototype |
-- MyAddon:SetDefaultModulePrototype(prototype) |
-- -- Create a module and explicitly Enable it |
-- MyModule = MyAddon:NewModule("MyModule") |
-- MyModule:Enable() |
-- -- should print "OnEnable called!" now |
-- @see NewModule |
function SetDefaultModulePrototype(self, prototype) |
if next(self.modules) then |
error("Usage: SetDefaultModulePrototype(prototype): cannot change the module defaults after a module has been registered.", 2) |
end |
if type(prototype) ~= "table" then |
error(("Usage: SetDefaultModulePrototype(prototype): 'prototype' - table expected got '%s'."):format(type(prototype)), 2) |
end |
self.defaultModulePrototype = prototype |
end |
--- Set the state of an addon or module |
-- This should only be called before any enabling actually happend, e.g. in/before OnInitialize. |
-- @name //addon//:SetEnabledState |
-- @paramsig state |
-- @param state the state of an addon or module (enabled=true, disabled=false) |
function SetEnabledState(self, state) |
self.enabledState = state |
end |
--- Return an iterator of all modules associated to the addon. |
-- @name //addon//:IterateModules |
-- @paramsig |
-- @usage |
-- -- Enable all modules |
-- for name, module in MyAddon:IterateModules() do |
-- module:Enable() |
-- end |
local function IterateModules(self) return pairs(self.modules) end |
-- Returns an iterator of all embeds in the addon |
-- @name //addon//:IterateEmbeds |
-- @paramsig |
local function IterateEmbeds(self) return pairs(AceAddon.embeds[self]) end |
--- Query the enabledState of an addon. |
-- @name //addon//:IsEnabled |
-- @paramsig |
-- @usage |
-- if MyAddon:IsEnabled() then |
-- MyAddon:Disable() |
-- end |
local function IsEnabled(self) return self.enabledState end |
local mixins = { |
NewModule = NewModule, |
GetModule = GetModule, |
Enable = Enable, |
Disable = Disable, |
EnableModule = EnableModule, |
DisableModule = DisableModule, |
IsEnabled = IsEnabled, |
SetDefaultModuleLibraries = SetDefaultModuleLibraries, |
SetDefaultModuleState = SetDefaultModuleState, |
SetDefaultModulePrototype = SetDefaultModulePrototype, |
SetEnabledState = SetEnabledState, |
IterateModules = IterateModules, |
IterateEmbeds = IterateEmbeds, |
GetName = GetName, |
} |
local function IsModule(self) return false end |
local pmixins = { |
defaultModuleState = true, |
enabledState = true, |
IsModule = IsModule, |
} |
-- Embed( target ) |
-- target (object) - target object to embed aceaddon in |
-- |
-- this is a local function specifically since it's meant to be only called internally |
function Embed(target) |
for k, v in pairs(mixins) do |
target[k] = v |
end |
for k, v in pairs(pmixins) do |
target[k] = target[k] or v |
end |
end |
-- - Initialize the addon after creation. |
-- This function is only used internally during the ADDON_LOADED event |
-- It will call the **OnInitialize** function on the addon object (if present), |
-- and the **OnEmbedInitialize** function on all embeded libraries. |
-- |
-- **Note:** Do not call this function manually, unless you're absolutely sure that you know what you are doing. |
-- @param addon addon object to intialize |
function AceAddon:InitializeAddon(addon) |
safecall(addon.OnInitialize, addon) |
local embeds = self.embeds[addon] |
for i = 1, #embeds do |
local lib = LibStub:GetLibrary(embeds[i], true) |
if lib then safecall(lib.OnEmbedInitialize, lib, addon) end |
end |
-- we don't call InitializeAddon on modules specifically, this is handled |
-- from the event handler and only done _once_ |
end |
-- - Enable the addon after creation. |
-- Note: This function is only used internally during the PLAYER_LOGIN event, or during ADDON_LOADED, |
-- if IsLoggedIn() already returns true at that point, e.g. for LoD Addons. |
-- It will call the **OnEnable** function on the addon object (if present), |
-- and the **OnEmbedEnable** function on all embeded libraries.\\ |
-- This function does not toggle the enable state of the addon itself, and will return early if the addon is disabled. |
-- |
-- **Note:** Do not call this function manually, unless you're absolutely sure that you know what you are doing. |
-- Use :Enable on the addon itself instead. |
-- @param addon addon object to enable |
function AceAddon:EnableAddon(addon) |
if type(addon) == "string" then addon = AceAddon:GetAddon(addon) end |
if self.statuses[addon.name] or not addon.enabledState then return false end |
-- set the statuses first, before calling the OnEnable. this allows for Disabling of the addon in OnEnable. |
self.statuses[addon.name] = true |
safecall(addon.OnEnable, addon) |
-- make sure we're still enabled before continueing |
if self.statuses[addon.name] then |
local embeds = self.embeds[addon] |
for i = 1, #embeds do |
local lib = LibStub:GetLibrary(embeds[i], true) |
if lib then safecall(lib.OnEmbedEnable, lib, addon) end |
end |
-- enable possible modules. |
for name, module in pairs(addon.modules) do |
self:EnableAddon(module) |
end |
end |
return self.statuses[addon.name] -- return true if we're disabled |
end |
-- - Disable the addon |
-- Note: This function is only used internally. |
-- It will call the **OnDisable** function on the addon object (if present), |
-- and the **OnEmbedDisable** function on all embeded libraries.\\ |
-- This function does not toggle the enable state of the addon itself, and will return early if the addon is still enabled. |
-- |
-- **Note:** Do not call this function manually, unless you're absolutely sure that you know what you are doing. |
-- Use :Disable on the addon itself instead. |
-- @param addon addon object to enable |
function AceAddon:DisableAddon(addon) |
if type(addon) == "string" then addon = AceAddon:GetAddon(addon) end |
if not self.statuses[addon.name] then return false end |
-- set statuses first before calling OnDisable, this allows for aborting the disable in OnDisable. |
self.statuses[addon.name] = false |
safecall( addon.OnDisable, addon ) |
-- make sure we're still disabling... |
if not self.statuses[addon.name] then |
local embeds = self.embeds[addon] |
for i = 1, #embeds do |
local lib = LibStub:GetLibrary(embeds[i], true) |
if lib then safecall(lib.OnEmbedDisable, lib, addon) end |
end |
-- disable possible modules. |
for name, module in pairs(addon.modules) do |
self:DisableAddon(module) |
end |
end |
return not self.statuses[addon.name] -- return true if we're disabled |
end |
--- Get an iterator over all registered addons. |
-- @usage |
-- -- Print a list of all installed AceAddon's |
-- for name, addon in AceAddon:IterateAddons() do |
-- print("Addon: " .. name) |
-- end |
function AceAddon:IterateAddons() return pairs(self.addons) end |
--- Get an iterator over the internal status registry. |
-- @usage |
-- -- Print a list of all enabled addons |
-- for name, status in AceAddon:IterateAddonStatus() do |
-- if status then |
-- print("EnabledAddon: " .. name) |
-- end |
-- end |
function AceAddon:IterateAddonStatus() return pairs(self.statuses) end |
-- Following Iterators are deprecated, and their addon specific versions should be used |
-- e.g. addon:IterateEmbeds() instead of :IterateEmbedsOnAddon(addon) |
function AceAddon:IterateEmbedsOnAddon(addon) return pairs(self.embeds[addon]) end |
function AceAddon:IterateModulesOfAddon(addon) return pairs(addon.modules) end |
-- Event Handling |
local function onEvent(this, event, arg1) |
if event == "ADDON_LOADED" or event == "PLAYER_LOGIN" then |
-- if a addon loads another addon, recursion could happen here, so we need to validate the table on every iteration |
while(#AceAddon.initializequeue > 0) do |
local addon = tremove(AceAddon.initializequeue, 1) |
-- this might be an issue with recursion - TODO: validate |
if event == "ADDON_LOADED" then addon.baseName = arg1 end |
AceAddon:InitializeAddon(addon) |
tinsert(AceAddon.enablequeue, addon) |
end |
if IsLoggedIn() then |
while(#AceAddon.enablequeue > 0) do |
local addon = tremove(AceAddon.enablequeue, 1) |
AceAddon:EnableAddon(addon) |
end |
end |
end |
end |
AceAddon.frame:RegisterEvent("ADDON_LOADED") |
AceAddon.frame:RegisterEvent("PLAYER_LOGIN") |
AceAddon.frame:SetScript("OnEvent", onEvent) |
-- upgrade embeded |
for name, addon in pairs(AceAddon.addons) do |
Embed(addon) |
end |
<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/ |
..\FrameXML\UI.xsd"> |
<Script file="AceDB-3.0.lua"/> |
</Ui> |
--- **AceDB-3.0** manages the SavedVariables of your addon. |
-- It offers profile management, smart defaults and namespaces for modules.\\ |
-- Data can be saved in different data-types, depending on its intended usage. |
-- The most common data-type is the `profile` type, which allows the user to choose |
-- the active profile, and manage the profiles of all of his characters.\\ |
-- The following data types are available: |
-- * **char** Character-specific data. Every character has its own database. |
-- * **realm** Realm-specific data. All of the players characters on the same realm share this database. |
-- * **class** Class-specific data. All of the players characters of the same class share this database. |
-- * **race** Race-specific data. All of the players characters of the same race share this database. |
-- * **faction** Faction-specific data. All of the players characters of the same faction share this database. |
-- * **factionrealm** Faction and realm specific data. All of the players characters on the same realm and of the same faction share this database. |
-- * **global** Global Data. All characters on the same account share this database. |
-- * **profile** Profile-specific data. All characters using the same profile share this database. The user can control which profile should be used. |
-- |
-- Creating a new Database using the `:New` function will return a new DBObject. A database will inherit all functions |
-- of the DBObjectLib listed here. \\ |
-- If you create a new namespaced child-database (`:RegisterNamespace`), you'll get a DBObject as well, but note |
-- that the child-databases cannot individually change their profile, and are linked to their parents profile - and because of that, |
-- the profile related APIs are not available. Only `:RegisterDefaults` and `:ResetProfile` are available on child-databases. |
-- |
-- For more details on how to use AceDB-3.0, see the [[AceDB-3.0 Tutorial]]. |
-- |
-- You may also be interested in [[libdualspec-1-0|LibDualSpec-1.0]] to do profile switching automatically when switching specs. |
-- |
-- @usage |
-- MyAddon = LibStub("AceAddon-3.0"):NewAddon("DBExample") |
-- |
-- -- declare defaults to be used in the DB |
-- local defaults = { |
-- profile = { |
-- setting = true, |
-- } |
-- } |
-- |
-- function MyAddon:OnInitialize() |
-- -- Assuming the .toc says ## SavedVariables: MyAddonDB |
-- self.db = LibStub("AceDB-3.0"):New("MyAddonDB", defaults, true) |
-- end |
-- @class file |
-- @name AceDB-3.0.lua |
-- @release $Id: AceDB-3.0.lua 940 2010-06-19 08:01:47Z nevcairiel $ |
local ACEDB_MAJOR, ACEDB_MINOR = "AceDB-3.0", 21 |
local AceDB, oldminor = LibStub:NewLibrary(ACEDB_MAJOR, ACEDB_MINOR) |
if not AceDB then return end -- No upgrade needed |
-- Lua APIs |
local type, pairs, next, error = type, pairs, next, error |
local setmetatable, getmetatable, rawset, rawget = setmetatable, getmetatable, rawset, rawget |
-- WoW APIs |
local _G = _G |
-- Global vars/functions that we don't upvalue since they might get hooked, or upgraded |
-- List them here for Mikk's FindGlobals script |
-- GLOBALS: LibStub |
AceDB.db_registry = AceDB.db_registry or {} |
AceDB.frame = AceDB.frame or CreateFrame("Frame") |
local CallbackHandler |
local CallbackDummy = { Fire = function() end } |
local DBObjectLib = {} |
--[[------------------------------------------------------------------------- |
AceDB Utility Functions |
---------------------------------------------------------------------------]] |
-- Simple shallow copy for copying defaults |
local function copyTable(src, dest) |
if type(dest) ~= "table" then dest = {} end |
if type(src) == "table" then |
for k,v in pairs(src) do |
if type(v) == "table" then |
-- try to index the key first so that the metatable creates the defaults, if set, and use that table |
v = copyTable(v, dest[k]) |
end |
dest[k] = v |
end |
end |
return dest |
end |
-- Called to add defaults to a section of the database |
-- |
-- When a ["*"] default section is indexed with a new key, a table is returned |
-- and set in the host table. These tables must be cleaned up by removeDefaults |
-- in order to ensure we don't write empty default tables. |
local function copyDefaults(dest, src) |
-- this happens if some value in the SV overwrites our default value with a non-table |
--if type(dest) ~= "table" then return end |
for k, v in pairs(src) do |
if k == "*" or k == "**" then |
if type(v) == "table" then |
-- This is a metatable used for table defaults |
local mt = { |
-- This handles the lookup and creation of new subtables |
__index = function(t,k) |
if k == nil then return nil end |
local tbl = {} |
copyDefaults(tbl, v) |
rawset(t, k, tbl) |
return tbl |
end, |
} |
setmetatable(dest, mt) |
-- handle already existing tables in the SV |
for dk, dv in pairs(dest) do |
if not rawget(src, dk) and type(dv) == "table" then |
copyDefaults(dv, v) |
end |
end |
else |
-- Values are not tables, so this is just a simple return |
local mt = {__index = function(t,k) return k~=nil and v or nil end} |
setmetatable(dest, mt) |
end |
elseif type(v) == "table" then |
if not rawget(dest, k) then rawset(dest, k, {}) end |
if type(dest[k]) == "table" then |
copyDefaults(dest[k], v) |
if src['**'] then |
copyDefaults(dest[k], src['**']) |
end |
end |
else |
if rawget(dest, k) == nil then |
rawset(dest, k, v) |
end |
end |
end |
end |
-- Called to remove all defaults in the default table from the database |
local function removeDefaults(db, defaults, blocker) |
-- remove all metatables from the db, so we don't accidentally create new sub-tables through them |
setmetatable(db, nil) |
-- loop through the defaults and remove their content |
for k,v in pairs(defaults) do |
if k == "*" or k == "**" then |
if type(v) == "table" then |
-- Loop through all the actual k,v pairs and remove |
for key, value in pairs(db) do |
if type(value) == "table" then |
-- if the key was not explicitly specified in the defaults table, just strip everything from * and ** tables |
if defaults[key] == nil and (not blocker or blocker[key] == nil) then |
removeDefaults(value, v) |
-- if the table is empty afterwards, remove it |
if next(value) == nil then |
db[key] = nil |
end |
-- if it was specified, only strip ** content, but block values which were set in the key table |
elseif k == "**" then |
removeDefaults(value, v, defaults[key]) |
end |
end |
end |
elseif k == "*" then |
-- check for non-table default |
for key, value in pairs(db) do |
if defaults[key] == nil and v == value then |
db[key] = nil |
end |
end |
end |
elseif type(v) == "table" and type(db[k]) == "table" then |
-- if a blocker was set, dive into it, to allow multi-level defaults |
removeDefaults(db[k], v, blocker and blocker[k]) |
if next(db[k]) == nil then |
db[k] = nil |
end |
else |
-- check if the current value matches the default, and that its not blocked by another defaults table |
if db[k] == defaults[k] and (not blocker or blocker[k] == nil) then |
db[k] = nil |
end |
end |
end |
end |
-- This is called when a table section is first accessed, to set up the defaults |
local function initSection(db, section, svstore, key, defaults) |
local sv = rawget(db, "sv") |
local tableCreated |
if not sv[svstore] then sv[svstore] = {} end |
if not sv[svstore][key] then |
sv[svstore][key] = {} |
tableCreated = true |
end |
local tbl = sv[svstore][key] |
if defaults then |
copyDefaults(tbl, defaults) |
end |
rawset(db, section, tbl) |
return tableCreated, tbl |
end |
-- Metatable to handle the dynamic creation of sections and copying of sections. |
local dbmt = { |
__index = function(t, section) |
local keys = rawget(t, "keys") |
local key = keys[section] |
if key then |
local defaultTbl = rawget(t, "defaults") |
local defaults = defaultTbl and defaultTbl[section] |
if section == "profile" then |
local new = initSection(t, section, "profiles", key, defaults) |
if new then |
-- Callback: OnNewProfile, database, newProfileKey |
t.callbacks:Fire("OnNewProfile", t, key) |
end |
elseif section == "profiles" then |
local sv = rawget(t, "sv") |
if not sv.profiles then sv.profiles = {} end |
rawset(t, "profiles", sv.profiles) |
elseif section == "global" then |
local sv = rawget(t, "sv") |
if not sv.global then sv.global = {} end |
if defaults then |
copyDefaults(sv.global, defaults) |
end |
rawset(t, section, sv.global) |
else |
initSection(t, section, section, key, defaults) |
end |
end |
return rawget(t, section) |
end |
} |
local function validateDefaults(defaults, keyTbl, offset) |
if not defaults then return end |
offset = offset or 0 |
for k in pairs(defaults) do |
if not keyTbl[k] or k == "profiles" then |
error(("Usage: AceDBObject:RegisterDefaults(defaults): '%s' is not a valid datatype."):format(k), 3 + offset) |
end |
end |
end |
local preserve_keys = { |
["callbacks"] = true, |
["RegisterCallback"] = true, |
["UnregisterCallback"] = true, |
["UnregisterAllCallbacks"] = true, |
["children"] = true, |
} |
local realmKey = GetRealmName() |
local charKey = UnitName("player") .. " - " .. realmKey |
local _, classKey = UnitClass("player") |
local _, raceKey = UnitRace("player") |
local factionKey = UnitFactionGroup("player") |
local factionrealmKey = factionKey .. " - " .. realmKey |
-- Actual database initialization function |
local function initdb(sv, defaults, defaultProfile, olddb, parent) |
-- Generate the database keys for each section |
-- map "true" to our "Default" profile |
if defaultProfile == true then defaultProfile = "Default" end |
local profileKey |
if not parent then |
-- Make a container for profile keys |
if not sv.profileKeys then sv.profileKeys = {} end |
-- Try to get the profile selected from the char db |
profileKey = sv.profileKeys[charKey] or defaultProfile or charKey |
-- save the selected profile for later |
sv.profileKeys[charKey] = profileKey |
else |
-- Use the profile of the parents DB |
profileKey = parent.keys.profile or defaultProfile or charKey |
-- clear the profileKeys in the DB, namespaces don't need to store them |
sv.profileKeys = nil |
end |
-- This table contains keys that enable the dynamic creation |
-- of each section of the table. The 'global' and 'profiles' |
-- have a key of true, since they are handled in a special case |
local keyTbl= { |
["char"] = charKey, |
["realm"] = realmKey, |
["class"] = classKey, |
["race"] = raceKey, |
["faction"] = factionKey, |
["factionrealm"] = factionrealmKey, |
["profile"] = profileKey, |
["global"] = true, |
["profiles"] = true, |
} |
validateDefaults(defaults, keyTbl, 1) |
-- This allows us to use this function to reset an entire database |
-- Clear out the old database |
if olddb then |
for k,v in pairs(olddb) do if not preserve_keys[k] then olddb[k] = nil end end |
end |
-- Give this database the metatable so it initializes dynamically |
local db = setmetatable(olddb or {}, dbmt) |
if not rawget(db, "callbacks") then |
-- try to load CallbackHandler-1.0 if it loaded after our library |
if not CallbackHandler then CallbackHandler = LibStub:GetLibrary("CallbackHandler-1.0", true) end |
db.callbacks = CallbackHandler and CallbackHandler:New(db) or CallbackDummy |
end |
-- Copy methods locally into the database object, to avoid hitting |
-- the metatable when calling methods |
if not parent then |
for name, func in pairs(DBObjectLib) do |
db[name] = func |
end |
else |
-- hack this one in |
db.RegisterDefaults = DBObjectLib.RegisterDefaults |
db.ResetProfile = DBObjectLib.ResetProfile |
end |
-- Set some properties in the database object |
db.profiles = sv.profiles |
db.keys = keyTbl |
db.sv = sv |
--db.sv_name = name |
db.defaults = defaults |
db.parent = parent |
-- store the DB in the registry |
AceDB.db_registry[db] = true |
return db |
end |
-- handle PLAYER_LOGOUT |
-- strip all defaults from all databases |
-- and cleans up empty sections |
local function logoutHandler(frame, event) |
if event == "PLAYER_LOGOUT" then |
for db in pairs(AceDB.db_registry) do |
db.callbacks:Fire("OnDatabaseShutdown", db) |
db:RegisterDefaults(nil) |
-- cleanup sections that are empty without defaults |
local sv = rawget(db, "sv") |
for section in pairs(db.keys) do |
if rawget(sv, section) then |
-- global is special, all other sections have sub-entrys |
-- also don't delete empty profiles on main dbs, only on namespaces |
if section ~= "global" and (section ~= "profiles" or rawget(db, "parent")) then |
for key in pairs(sv[section]) do |
if not next(sv[section][key]) then |
sv[section][key] = nil |
end |
end |
end |
if not next(sv[section]) then |
sv[section] = nil |
end |
end |
end |
end |
end |
end |
AceDB.frame:RegisterEvent("PLAYER_LOGOUT") |
AceDB.frame:SetScript("OnEvent", logoutHandler) |
--[[------------------------------------------------------------------------- |
AceDB Object Method Definitions |
---------------------------------------------------------------------------]] |
--- Sets the defaults table for the given database object by clearing any |
-- that are currently set, and then setting the new defaults. |
-- @param defaults A table of defaults for this database |
function DBObjectLib:RegisterDefaults(defaults) |
if defaults and type(defaults) ~= "table" then |
error("Usage: AceDBObject:RegisterDefaults(defaults): 'defaults' - table or nil expected.", 2) |
end |
validateDefaults(defaults, self.keys) |
-- Remove any currently set defaults |
if self.defaults then |
for section,key in pairs(self.keys) do |
if self.defaults[section] and rawget(self, section) then |
removeDefaults(self[section], self.defaults[section]) |
end |
end |
end |
-- Set the DBObject.defaults table |
self.defaults = defaults |
-- Copy in any defaults, only touching those sections already created |
if defaults then |
for section,key in pairs(self.keys) do |
if defaults[section] and rawget(self, section) then |
copyDefaults(self[section], defaults[section]) |
end |
end |
end |
end |
--- Changes the profile of the database and all of it's namespaces to the |
-- supplied named profile |
-- @param name The name of the profile to set as the current profile |
function DBObjectLib:SetProfile(name) |
if type(name) ~= "string" then |
error("Usage: AceDBObject:SetProfile(name): 'name' - string expected.", 2) |
end |
-- changing to the same profile, dont do anything |
if name == self.keys.profile then return end |
local oldProfile = self.profile |
local defaults = self.defaults and self.defaults.profile |
-- Callback: OnProfileShutdown, database |
self.callbacks:Fire("OnProfileShutdown", self) |
if oldProfile and defaults then |
-- Remove the defaults from the old profile |
removeDefaults(oldProfile, defaults) |
end |
self.profile = nil |
self.keys["profile"] = name |
-- if the storage exists, save the new profile |
-- this won't exist on namespaces. |
if self.sv.profileKeys then |
self.sv.profileKeys[charKey] = name |
end |
-- populate to child namespaces |
if self.children then |
for _, db in pairs(self.children) do |
DBObjectLib.SetProfile(db, name) |
end |
end |
-- Callback: OnProfileChanged, database, newProfileKey |
self.callbacks:Fire("OnProfileChanged", self, name) |
end |
--- Returns a table with the names of the existing profiles in the database. |
-- You can optionally supply a table to re-use for this purpose. |
-- @param tbl A table to store the profile names in (optional) |
function DBObjectLib:GetProfiles(tbl) |
if tbl and type(tbl) ~= "table" then |
error("Usage: AceDBObject:GetProfiles(tbl): 'tbl' - table or nil expected.", 2) |
end |
-- Clear the container table |
if tbl then |
for k,v in pairs(tbl) do tbl[k] = nil end |
else |
tbl = {} |
end |
local curProfile = self.keys.profile |
local i = 0 |
for profileKey in pairs(self.profiles) do |
i = i + 1 |
tbl[i] = profileKey |
if curProfile and profileKey == curProfile then curProfile = nil end |
end |
-- Add the current profile, if it hasn't been created yet |
if curProfile then |
i = i + 1 |
tbl[i] = curProfile |
end |
return tbl, i |
end |
--- Returns the current profile name used by the database |
function DBObjectLib:GetCurrentProfile() |
return self.keys.profile |
end |
--- Deletes a named profile. This profile must not be the active profile. |
-- @param name The name of the profile to be deleted |
-- @param silent If true, do not raise an error when the profile does not exist |
function DBObjectLib:DeleteProfile(name, silent) |
if type(name) ~= "string" then |
error("Usage: AceDBObject:DeleteProfile(name): 'name' - string expected.", 2) |
end |
if self.keys.profile == name then |
error("Cannot delete the active profile in an AceDBObject.", 2) |
end |
if not rawget(self.profiles, name) and not silent then |
error("Cannot delete profile '" .. name .. "'. It does not exist.", 2) |
end |
self.profiles[name] = nil |
-- populate to child namespaces |
if self.children then |
for _, db in pairs(self.children) do |
DBObjectLib.DeleteProfile(db, name, true) |
end |
end |
-- Callback: OnProfileDeleted, database, profileKey |
self.callbacks:Fire("OnProfileDeleted", self, name) |
end |
--- Copies a named profile into the current profile, overwriting any conflicting |
-- settings. |
-- @param name The name of the profile to be copied into the current profile |
-- @param silent If true, do not raise an error when the profile does not exist |
function DBObjectLib:CopyProfile(name, silent) |
if type(name) ~= "string" then |
error("Usage: AceDBObject:CopyProfile(name): 'name' - string expected.", 2) |
end |
if name == self.keys.profile then |
error("Cannot have the same source and destination profiles.", 2) |
end |
if not rawget(self.profiles, name) and not silent then |
error("Cannot copy profile '" .. name .. "'. It does not exist.", 2) |
end |
-- Reset the profile before copying |
DBObjectLib.ResetProfile(self, nil, true) |
local profile = self.profile |
local source = self.profiles[name] |
copyTable(source, profile) |
-- populate to child namespaces |
if self.children then |
for _, db in pairs(self.children) do |
DBObjectLib.CopyProfile(db, name, true) |
end |
end |
-- Callback: OnProfileCopied, database, sourceProfileKey |
self.callbacks:Fire("OnProfileCopied", self, name) |
end |
--- Resets the current profile to the default values (if specified). |
-- @param noChildren if set to true, the reset will not be populated to the child namespaces of this DB object |
-- @param noCallbacks if set to true, won't fire the OnProfileReset callback |
function DBObjectLib:ResetProfile(noChildren, noCallbacks) |
local profile = self.profile |
for k,v in pairs(profile) do |
profile[k] = nil |
end |
local defaults = self.defaults and self.defaults.profile |
if defaults then |
copyDefaults(profile, defaults) |
end |
-- populate to child namespaces |
if self.children and not noChildren then |
for _, db in pairs(self.children) do |
DBObjectLib.ResetProfile(db, nil, noCallbacks) |
end |
end |
-- Callback: OnProfileReset, database |
if not noCallbacks then |
self.callbacks:Fire("OnProfileReset", self) |
end |
end |
--- Resets the entire database, using the string defaultProfile as the new default |
-- profile. |
-- @param defaultProfile The profile name to use as the default |
function DBObjectLib:ResetDB(defaultProfile) |
if defaultProfile and type(defaultProfile) ~= "string" then |
error("Usage: AceDBObject:ResetDB(defaultProfile): 'defaultProfile' - string or nil expected.", 2) |
end |
local sv = self.sv |
for k,v in pairs(sv) do |
sv[k] = nil |
end |
local parent = self.parent |
initdb(sv, self.defaults, defaultProfile, self) |
-- fix the child namespaces |
if self.children then |
if not sv.namespaces then sv.namespaces = {} end |
for name, db in pairs(self.children) do |
if not sv.namespaces[name] then sv.namespaces[name] = {} end |
initdb(sv.namespaces[name], db.defaults, self.keys.profile, db, self) |
end |
end |
-- Callback: OnDatabaseReset, database |
self.callbacks:Fire("OnDatabaseReset", self) |
-- Callback: OnProfileChanged, database, profileKey |
self.callbacks:Fire("OnProfileChanged", self, self.keys["profile"]) |
return self |
end |
--- Creates a new database namespace, directly tied to the database. This |
-- is a full scale database in it's own rights other than the fact that |
-- it cannot control its profile individually |
-- @param name The name of the new namespace |
-- @param defaults A table of values to use as defaults |
function DBObjectLib:RegisterNamespace(name, defaults) |
if type(name) ~= "string" then |
error("Usage: AceDBObject:RegisterNamespace(name, defaults): 'name' - string expected.", 2) |
end |
if defaults and type(defaults) ~= "table" then |
error("Usage: AceDBObject:RegisterNamespace(name, defaults): 'defaults' - table or nil expected.", 2) |
end |
if self.children and self.children[name] then |
error ("Usage: AceDBObject:RegisterNamespace(name, defaults): 'name' - a namespace with that name already exists.", 2) |
end |
local sv = self.sv |
if not sv.namespaces then sv.namespaces = {} end |
if not sv.namespaces[name] then |
sv.namespaces[name] = {} |
end |
local newDB = initdb(sv.namespaces[name], defaults, self.keys.profile, nil, self) |
if not self.children then self.children = {} end |
self.children[name] = newDB |
return newDB |
end |
--- Returns an already existing namespace from the database object. |
-- @param name The name of the new namespace |
-- @param silent if true, the addon is optional, silently return nil if its not found |
-- @usage |
-- local namespace = self.db:GetNamespace('namespace') |
-- @return the namespace object if found |
function DBObjectLib:GetNamespace(name, silent) |
if type(name) ~= "string" then |
error("Usage: AceDBObject:GetNamespace(name): 'name' - string expected.", 2) |
end |
if not silent and not (self.children and self.children[name]) then |
error ("Usage: AceDBObject:GetNamespace(name): 'name' - namespace does not exist.", 2) |
end |
if not self.children then self.children = {} end |
return self.children[name] |
end |
--[[------------------------------------------------------------------------- |
AceDB Exposed Methods |
---------------------------------------------------------------------------]] |
--- Creates a new database object that can be used to handle database settings and profiles. |
-- By default, an empty DB is created, using a character specific profile. |
-- |
-- You can override the default profile used by passing any profile name as the third argument, |
-- or by passing //true// as the third argument to use a globally shared profile called "Default". |
-- |
-- Note that there is no token replacement in the default profile name, passing a defaultProfile as "char" |
-- will use a profile named "char", and not a character-specific profile. |
-- @param tbl The name of variable, or table to use for the database |
-- @param defaults A table of database defaults |
-- @param defaultProfile The name of the default profile. If not set, a character specific profile will be used as the default. |
-- You can also pass //true// to use a shared global profile called "Default". |
-- @usage |
-- -- Create an empty DB using a character-specific default profile. |
-- self.db = LibStub("AceDB-3.0"):New("MyAddonDB") |
-- @usage |
-- -- Create a DB using defaults and using a shared default profile |
-- self.db = LibStub("AceDB-3.0"):New("MyAddonDB", defaults, true) |
function AceDB:New(tbl, defaults, defaultProfile) |
if type(tbl) == "string" then |
local name = tbl |
tbl = _G[name] |
if not tbl then |
tbl = {} |
_G[name] = tbl |
end |
end |
if type(tbl) ~= "table" then |
error("Usage: AceDB:New(tbl, defaults, defaultProfile): 'tbl' - table expected.", 2) |
end |
if defaults and type(defaults) ~= "table" then |
error("Usage: AceDB:New(tbl, defaults, defaultProfile): 'defaults' - table expected.", 2) |
end |
if defaultProfile and type(defaultProfile) ~= "string" and defaultProfile ~= true then |
error("Usage: AceDB:New(tbl, defaults, defaultProfile): 'defaultProfile' - string or true expected.", 2) |
end |
return initdb(tbl, defaults, defaultProfile) |
end |
-- upgrade existing databases |
for db in pairs(AceDB.db_registry) do |
if not db.parent then |
for name,func in pairs(DBObjectLib) do |
db[name] = func |
end |
else |
db.RegisterDefaults = DBObjectLib.RegisterDefaults |
db.ResetProfile = DBObjectLib.ResetProfile |
end |
end |
## Interface: 30300 |
## Title: Broker Leveltime |
## Notes: Records time for each level while levelling |
## Author: Michael Irving |
## X-Category: Broker |
## SavedVariables: LeveltimeDB |
libs\LibStub\LibStub.lua |
libs\CallbackHandler-1.0\CallbackHandler-1.0.xml |
libs\libdatabroker\LibDataBroker-1.1.lua |
libs\AceAddon-3.0\AceAddon-3.0.xml |
libs\AceDB-3.0\AceDB-3.0.xml |
libs\AceEvent-3.0\AceEvent-3.0.xml |
libs\AceTimer-3.0\AceTimer-3.0.xml |
Leveltime.lua |
local addon = LibStub("AceAddon-3.0"):NewAddon("Broker_Leveltime","AceTimer-3.0","AceEvent-3.0") |
addon.totaltime = 0 |
addon.playedthislevel = 0 |
addon.basetime = 0 |
addon.difftime = 0 |
local f = CreateFrame("frame") |
local ldb = LibStub:GetLibrary("LibDataBroker-1.1") |
local dataobj = ldb:NewDataObject("Broker_Leveltime", { |
type = "data source", |
icon = "Interface\\Icons\\Spell_Holy_BorrowedTime", |
text = "Broker_Leveltime", |
}) |
function dataobj:OnTooltipShow() |
local tottime = addon.totaltime + addon.difftime |
local lvltime = addon.playedthislevel + addon.difftime |
local totdays = floor( tottime / (60*60*24)) |
local tothours = floor( (tottime-(totdays*60*60*24)) / (60*60)) |
local totmins = floor( (tottime-(totdays*60*60*24)-(tothours*60*60)) / 60 ) |
if totdays > 0 then |
self:AddLine("Total time played: " .. totdays .. " day(s) " .. tothours .. " hour(s) " .. totmins .. " min(s)") |
elseif tothours > 0 then |
self:AddLine("Total time played: " .. tothours .. " hour(s) " .. totmins .. " min(s)") |
else |
self:AddLine("Total time played: " .. totmins .. " min(s)") |
end |
local lvldays = floor( lvltime / (60*60*24)) |
local lvlhours = floor( (lvltime-(lvldays*60*60*24)) / (60*60)) |
local lvlmins = floor( (lvltime-(lvldays*60*60*24)-(lvlhours*60*60)) / 60 ) |
if lvldays > 0 then |
self:AddLine("Time played this level: " .. lvldays .. " day(s) " .. lvlhours .. " hour(s) " .. lvlmins .. " min(s)") |
elseif lvlhours > 0 then |
self:AddLine("Time played this level: " .. lvlhours .. " hour(s) " .. lvlmins .. " min(s)") |
else |
self:AddLine("Time played this level: " .. lvlmins .. " min(s)") |
end |
if addon.db.char.level then |
if addon.db.char.level[UnitLevel("player")] then |
if addon.db.char.level[UnitLevel("player")].time then |
self:AddLine("Current level was achieved ".. date("%c",addon.db.char.level[UnitLevel("player")].time)) |
end |
end |
local printlevel=UnitLevel("player") - 1 |
if printlevel < 0 then printlevel = 1 end |
local printtolevel = UnitLevel("player") - 5 |
if printtolevel < 0 then printtolevel = 1 end |
while printlevel >= printtolevel do |
if addon.db.char.level[printlevel] then |
if addon.db.char.level[printlevel].time then |
self:AddLine("Level "..printlevel.." was achieved ".. date("%c",addon.db.char.level[printlevel].time)) |
end |
if addon.db.char.level[printlevel].playtime then |
local tmptime = addon.db.char.level[printlevel].playtime |
local ptdays = floor(tmptime / (60*60*24)) |
local pthours = floor((tmptime - (ptdays * 60*60*24)) / (60*60)) |
local ptmins = floor((tmptime - (ptdays * 60*60*24) - (pthours *60*60)) / 60) |
local ptstr = "" |
if ptdays>0 then ptstr = ptdays.." day(s) " end |
if pthours>0 then ptstr = ptstr..pthours.." hour(s) " end |
ptstr = ptstr..ptmins.." min(s)" |
self:AddLine("Level "..printlevel.." playtime was "..ptstr) |
end |
end |
printlevel = printlevel-1 |
end |
end |
end |
function dataobj:OnEnter() |
GameTooltip:SetOwner(self, "ANCHOR_NONE") |
GameTooltip:SetPoint("TOPLEFT", self, "BOTTOMLEFT") |
GameTooltip:ClearLines() |
dataobj.OnTooltipShow(GameTooltip) |
GameTooltip:Show() |
end |
function dataobj:OnLeave() |
GameTooltip:Hide() |
end |
function addon:TIME_PLAYED_MSG(eventName, total, level) |
self.totaltime = total |
self.playedthislevel = level |
self.basetime = time() |
end |
function addon:PLAYER_LEVEL_UP(eventName,level, hp, mp, talentPoints, strength, agility, stamina, intellect, spirit) |
if not self.db.char.level then self.db.char.level={} end |
if not self.db.char.level[level] then self.db.char.level[level]={} end |
if not self.db.char.level[level-1] then self.db.char.level[level-1]={} end |
self.db.char.level[level].time = time() |
self.db.char.level[level-1].playtime = self.playedthislevel + self.difftime |
RequestTimePlayed() |
end |
function addon:MinTimer() |
self.difftime = time() - self.basetime |
end |
function addon:OnInitialize() |
self.db = LibStub("AceDB-3.0"):New("LeveltimeDB") |
self:RegisterEvent("TIME_PLAYED_MSG") |
self:RegisterEvent("PLAYER_LEVEL_UP") |
self.minuteTimer = self:ScheduleRepeatingTimer("MinTimer", 1) |
RequestTimePlayed() |
end |