vanilla-wow-addons – Rev 1
?pathlinks?
local weakK_mt = {__mode="k"};
local weakV_mt = {__mode="v"};
local TIMEX_UPDATE = 1;
local TIMEX_EVENT = 2;
local FUNCTION = 4;
local TT_UPDATE = "doTimexEvent";
local TT_UPDATE_EVENT = "TIMEX_UPDATE";
local function ARG_ID(t) return t.schedule.id; end;
local function ARG_COUNT(t) return t.schedule.c; end;
local function ARG_ELAPSED(t) return t.schedule.elapsed; end;
local DEBUG_DB = "Debug";
local timexDebug = nil;
local getTime = GetTime;
--<< ================================================= >>--
-- Section I: Initialize the AddOn Object. --
--<< ================================================= >>--
Timex = AceAddon:new({
name = TimexLocals.Title,
version = TimexLocals.Version,
description = TimexLocals.Desc,
author = "Rowne/facboy",
aceCompatible = "100",
category = ACE_CATEGORY_OTHERS,
cmd = AceChatCmd:new(TimexLocals.ChatCmd, (TimexLocals.ChatOpt or {})),
db = AceDatabase:new("TimexDB"),
TT_UPDATE = TT_UPDATE,
ARG_ID = ARG_ID,
ARG_COUNT = ARG_COUNT,
ARG_ELAPSED = ARG_ELAPSED,
weakV_mt = weakV_mt,
})
-- make all this stuff local...there should only be one Timex instance anyway, this will effectively make it a singleton
local scheduleDB = {};
local scheduleMap = setmetatable({}, weakV_mt);
local timerDB = {};
local initDB = {};
local onUpdateDB = {};
local onUpdateMap = setmetatable({}, weakV_mt);
--<< ================================================= >>--
-- Section II: Private utility functions. --
--<< ================================================= >>--
--------------------
-- table management
--------------------
local getn = table.getn;
local setn = table.setn;
local tinsert = table.insert;
local tremove = table.remove;
local unusedTables = setmetatable({}, weakK_mt);
local function newTable()
local new = next(unusedTables);
if new then
unusedTables[new] = nil;
else
new = {};
end
return new;
end
local function deleteTable(deleteTable)
unusedTables[deleteTable] = true;
end
--------------------
-- argument stuff
--------------------
local args_switch = {};
args_switch[ARG_ID] = ARG_ID;
args_switch[ARG_COUNT] = ARG_COUNT;
args_switch[ARG_ELAPSED] = ARG_ELAPSED;
local function buildArgs(args, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20)
local sub = args.sub;
local f = args_switch[a20]; args[20], sub[20] = not f and a20 or nil, f;
f = args_switch[a19]; args[19], sub[19] = not f and a19 or nil, f;
f = args_switch[a18]; args[18], sub[18] = not f and a18 or nil, f;
f = args_switch[a17]; args[17], sub[17] = not f and a17 or nil, f;
f = args_switch[a16]; args[16], sub[16] = not f and a16 or nil, f;
f = args_switch[a15]; args[15], sub[15] = not f and a15 or nil, f;
f = args_switch[a14]; args[14], sub[14] = not f and a14 or nil, f;
f = args_switch[a13]; args[13], sub[13] = not f and a13 or nil, f;
f = args_switch[a12]; args[12], sub[12] = not f and a12 or nil, f;
f = args_switch[a11]; args[11], sub[11] = not f and a11 or nil, f;
f = args_switch[a10]; args[10], sub[10] = not f and a10 or nil, f;
f = args_switch[a9]; args[9], sub[9] = not f and a9 or nil, f;
f = args_switch[a8]; args[8], sub[8] = not f and a8 or nil, f;
f = args_switch[a7]; args[7], sub[7] = not f and a7 or nil, f;
f = args_switch[a6]; args[6], sub[6] = not f and a6 or nil, f;
f = args_switch[a5]; args[5], sub[5] = not f and a5 or nil, f;
f = args_switch[a4]; args[4], sub[4] = not f and a4 or nil, f;
f = args_switch[a3]; args[3], sub[3] = not f and a3 or nil, f;
f = args_switch[a2]; args[2], sub[2] = not f and a2 or nil, f;
f = args_switch[a1]; args[1], sub[1] = not f and a1 or nil, f;
end
local args_mt = {
__index = function(t, k)
local f = t.sub[k];
if f then return f(t); end
end,
}
local function newArgs(schedule)
local args = newTable();
args.schedule, args.sub = schedule, newTable();
setmetatable(args, args_mt);
return args;
end
--------------------
-- schedule heap management
--------------------
--------------------
-- sifting functions
local function hSiftUp(heap, pos, schedule)
schedule = schedule or heap[pos];
local scheduleD = schedule.d;
local curr, i = pos, floor(pos/2);
local parent = heap[i];
while i > 0 and scheduleD < parent.d do
heap[curr], parent.i = parent, curr;
curr, i = i, floor(i/2);
parent = heap[i];
end
heap[curr], schedule.i = schedule, curr;
return pos ~= curr;
end
local function hSiftDown(heap, pos, schedule, size)
schedule, size = schedule or heap[pos], size or getn(heap);
local scheduleD = schedule.d;
local curr = pos;
repeat
local child, childD, c;
-- determine the child to compare with
local j = 2 * curr;
if j > size then
break;
end
local k = j + 1;
if k > size then
child = heap[j];
childD, c = child.d, j;
else
local childj, childk = heap[j], heap[k];
local jD, kD = childj.d, childk.d;
if jD < kD then
child, childD, c = childj, jD, j;
else
child, childD, c = childk, kD, k;
end
end
-- do the comparison
if scheduleD <= childD then
break;
end
heap[curr], child.i = child, curr;
curr = c;
until false;
heap[curr], schedule.i = schedule, curr;
return pos ~= curr;
end
--------------------
-- heap functions
local function hMaintain(heap, pos, schedule, size)
schedule, size = schedule or heap[pos], size or getn(heap);
if not hSiftUp(heap, pos, schedule) then
hSiftDown(heap, pos, schedule, size);
end
end
local function hPush(heap, schedule)
tinsert(heap, schedule);
hSiftUp(heap, getn(heap), schedule);
end
local function hPop(heap)
local head, tail = heap[1], tremove(heap);
local size = getn(heap);
if size == 1 then
heap[1], tail.i = tail, 1;
elseif size > 1 then
hSiftDown(heap, 1, tail, size);
end
return head;
end
local function hDelete(heap, pos)
local size = getn(heap);
local tail = tremove(heap);
if pos < size then
local size = size - 1;
if size == 1 then
heap[1], tail.i = tail, 1;
elseif size > 1 then
heap[pos] = tail;
hMaintain(heap, pos, tail, size);
end
end
end
--------------------
-- schedule management
--------------------
local unusedSchedules = setmetatable({}, weakK_mt);
local function newSchedule()
local schedule = next(unusedSchedules);
if schedule then
unusedSchedules[schedule] = nil;
else
schedule = newTable();
schedule.a = newArgs(schedule);
end
return schedule;
end
-- schedule should already have been removed from the heap
local function deleteSchedule(schedule)
schedule.del = nil;
scheduleMap[schedule.id] = nil;
unusedSchedules[schedule] = true;
end
local function deleteOnUpdate(onUpdate)
onUpdate.del = nil;
onUpdateMap[onUpdate.id] = nil;
unusedSchedules[onUpdate] = true;
end
--------------------
-- function callers
--------------------
local function callFunction(schedule)
local a = schedule.a;
local status, err = pcall(schedule.f, a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], a[11], a[12], a[13], a[14], a[15], a[16], a[17], a[18], a[19], a[20]);
if not status then
Timex.cmd:msg("Scheduled function '%s' failed with error: %s", tostring(schedule.id), tostring(err));
end
return err;
end
local function triggerEvent(schedule)
local a = schedule.a;
local status, err = pcall(schedule.f, self, schedule.e, a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], a[11], a[12], a[13], a[14], a[15], a[16], a[17], a[18], a[19], a[20]);
if not status then
Timex.cmd:msg("Scheduled function '%s' failed with error: %s", tostring(schedule.id), tostring(err));
end
return err;
end
local function triggerDeprecatedEvent(schedule)
local a = schedule.a;
local status, err = pcall(schedule.f, self, schedule.e, a, schedule.c, schedule.id, schedule.elapsed);
if not status then
Timex.cmd:msg("Scheduled function '%s' failed with error: %s", tostring(schedule.id), tostring(err));
end
return err;
end
local function runMultiple(schedule, runCount)
local runFunc = schedule.runF;
local result = runFunc(schedule);
if runCount > 1 then
local autoRemove = schedule.aR;
schedule.elapsed = 0;
for i = 2, runCount do
result = runFunc(schedule);
if autoRemove and result then break; end
end
end
return result;
end
--------------------
-- other stuff
--------------------
local function handleFrame()
local runCount = getn(scheduleDB) + getn(onUpdateDB);
if (runCount == 1) then
TimexUpdateFrame:Show();
elseif (runCount == 0) then
TimexUpdateFrame:Hide();
end
end
local function runStartupFunctions()
for k, v in pairs(initDB) do
if v.f then
v.f(v.a1, v.a2, v.a3, v.a4, v.a5, v.a6, v.a7, v.a8, v.a9, v.a10, v.a11, v.a12, v.a13, v.a14, v.a15, v.a16, v.a17, v.a18, v.a19, v.a20);
end
-- delete the table
v.f, v.a = nil, nil;
v.a1, v.a2, v.a3, v.a4, v.a5 = nil, nil, nil, nil, nil;
v.a6, v.a7, v.a8, v.a9, v.a10 = nil, nil, nil, nil, nil;
v.a11, v.a12, v.a13, v.a14, v.a15 = nil, nil, nil, nil, nil;
v.a16, v.a17, v.a18, v.a19, v.a20 = nil, nil, nil, nil, nil;
deleteTable(v);
initDB[k] = nil;
end
end
local function togOpt(timex, var)
return timex.db:toggle(timex.profilePath, var)
end
local function getOpt(timex, var)
return timex.db:get(timex.profilePath, var)
end
local function getNumber(n)
local num = tonumber(n);
if not num then
error("'" .. n .. "' is not a number.");
end
return num;
end
--<< ================================================= >>--
-- Section III: The Timex System Functions. --
--<< ================================================= >>--
function Timex:Enable()
TimexBar:Enable();
timexDebug = getOpt(self, DEBUG_DB);
TimexBar:Debug(timexDebug);
runStartupFunctions(self);
end
function Timex:AddStartupFunc(f, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20)
local init = newTable();
init.f, init.a = f, arg;
init.a1, init.a2, init.a3, init.a4, init.a5 = a1, a2, a3, a4, a5;
init.a6, init.a7, init.a8, init.a9, init.a10 = a6, a7, a8, a9, a10;
init.a11, init.a12, init.a13, init.a14, init.a15 = a11, a12, a13, a14, a15;
init.a16, init.a17, init.a18, init.a19, init.a20 = a16, a17, a18, a19, a20;
tinsert(initDB, init);
end
function Timex:Update(dt)
local now = getTime();
for k, onUpdate in pairs(onUpdateDB) do
onUpdate.del = true;
local a = onUpdate.a;
local ret = onUpdate.f(a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], a[11], a[12], a[13], a[14], a[15], a[16], a[17], a[18], a[19], a[20]);
if ret and onUpdate.aR and onUpdate.del then
onUpdateDB[k] = nil;
onUpdateMap[onUpdate.id] = nil;
unusedSchedules[onUpdate] = true;
end
onUpdate.del = nil;
end
local schedule = scheduleDB[1];
while schedule and now >= schedule.d do
local elapsed = now - schedule.l;
schedule.elapsed = elapsed;
local runCount;
local count, t = schedule.c, schedule.t;
local runCount;
if count then
runCount = t > 0 and floor((now - schedule.s)/t) or 1;
runCount = runCount < count and runCount or count;
count = count - runCount;
schedule.c = count;
if (count <= 0) then
-- mark schedule for deletion
schedule.del = true;
else
schedule.l, schedule.s, schedule.d = now, schedule.d, schedule.d + t;
hSiftDown(scheduleDB, 1, schedule);
end
elseif schedule.r then
schedule.l, schedule.s, schedule.d = now, schedule.d, schedule.d + t;
hSiftDown(scheduleDB, 1, schedule);
else
-- mark schedule for deletion
schedule.del = true;
end
-- run the function
local run = schedule.run;
if run then
local ret = run(schedule, runCount);
if ret and schedule.aR then
schedule.del = true;
end
end
if schedule.del then
-- remove from heap
hPop(scheduleDB);
if schedule.u then
deleteOnUpdate(schedule);
else
deleteSchedule(schedule);
end
end
schedule = scheduleDB[1];
end
handleFrame();
end
function Timex:Debug()
togOpt(self, DEBUG_DB);
timexDebug = getOpt(self, DEBUG_DB);
TimexBar:Debug(timexDebug);
if timexDebug then
self.cmd:msg("Debug: ON");
else
self.cmd:msg("Debug: OFF");
end
end
--<< ================================================= >>--
-- Section IV: The Timex Schedule Functions. --
--<< ================================================= >>--
-- changed to support max of 20 arguments (to prevent table creation)
function Timex:AddSchedule(id, t, r, c, f, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20)
if id or t then
local now = getTime();
-- look for named schedule
local schedule;
if id then
schedule = scheduleMap[id];
end
if not schedule then
-- create
schedule = newSchedule();
if id then
scheduleMap[id] = schedule;
end
tinsert(scheduleDB, schedule);
schedule.i = getn(scheduleDB);
end
schedule.id = id or this:GetName() or scheduleDB;
t = t and getNumber(t) or 0;
t = t >= 0 and t or 0;
schedule.t, schedule.r, schedule.c = t, r, c and getNumber(c) or nil;
if f then
local fType = type(f);
if fType == "function" then
schedule.f, schedule.run = f, callFunction;
elseif fType == "string" then
schedule.f = self.TriggerEvent;
if f == TT_UPDATE then
schedule.e, schedule.run = TT_UPDATE_EVENT, triggerDeprecatedEvent;
else
schedule.e, schedule.run = f, triggerEvent;
end
else
error("Timex:AddSchedule: param f is not a function or an event name");
end
-- check for multi
if c then
schedule.runF, schedule.run = schedule.run, runMultiple;
end
else
schedule.run = nil;
end
buildArgs(schedule.a, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20);
-- last update, start time, due time
schedule.l, schedule.s, schedule.d = now, now, now + t;
-- clear deletion, auto-remove (for onUpdate) flag
schedule.del = nil;
schedule.aR = nil;
-- onupdate flag;
schedule.u = nil;
-- place in scheduleDB
hMaintain(scheduleDB, schedule.i, schedule);
handleFrame();
end
end
function Timex:DeleteSchedule(id)
local schedule = scheduleMap[id];
if schedule then
hDelete(scheduleDB, schedule.i);
deleteSchedule(schedule);
handleFrame();
end
end
function Timex:CheckSchedule(id, r)
local schedule = scheduleMap[id];
if schedule then
if r then
-- shows the time remaining
return schedule.d - getTime();
else
return true;
end
end
end
function Timex:ElapsedScheduleTime(id)
local now = getTime();
local schedule = scheduleMap[id];
if schedule then
-- show the time elapsed
return now - schedule.s, schedule.s, now;
else
return 0, 0, now;
end
end
function Timex:RemainingScheduleTime(id)
local now = getTime();
local schedule = scheduleMap[id];
if schedule then
-- show the time remaining
return schedule.d - now, schedule.d, now;
else
return 0, 0, now;
end
end
function Timex:ChangeScheduleDuration(id, t)
local schedule = scheduleMap[id];
if schedule and t then
schedule.t, schedule.d = t, schedule.s + t;
hMaintain(scheduleDB, schedule.i, schedule);
end
end
function Timex:ChangeScheduleDue(id, d)
local schedule = scheduleMap[id];
if schedule then
schedule.d = d or getTime();
hMaintain(scheduleDB, schedule.i, schedule);
end
end
--<< ================================================= >>--
-- Section V: The Timex Timer Functions. --
--<< ================================================= >>--
-- largely for Chronos compatibility - plus it's a pretty cool idea!
function Timex:AddTimer(id)
local now = getTime();
id = id or this:GetName();
local timer = timerDB[id];
if not timer then
timer = newTable();
timerDB[id] = timer;
end
tinsert(timer, now);
end
function Timex:DeleteTimer(id)
local now = getTime();
id = id or this:GetName();
local timer = timerDB[id];
local start;
if timer then
local start = tremove(timer);
-- delete timer if it is empt
if getn(timer) == 0 then
deleteTable(timer);
timerDB[id] = nil;
end
return now - start, start, now;
else
return 0, 0, now;
end
end
function Timex:GetTimer(id)
local now = getTime();
id = id or this:GetName();
local timer = timerDB[id];
local start;
if timer then
local start = timer[getn(timer)];
return now - start, start, now;
else
return 0, 0, now;
end
end
function Timex:CheckTimer(id)
id = id or this:GetName();
if timerDB[id] then
return true;
else
return false;
end
end
--<< ================================================= >>--
-- Section VI: The Timex OnUpdate Functions. --
--<< ================================================= >>--
local function insertOnUpdate(onUpdate)
-- find first empty spot
local i, v = 1, onUpdateDB[1];
while v ~= nil do
i = i + 1;
v = onUpdateDB[i];
end
onUpdateDB[i], onUpdate.i = onUpdate, i;
end
-- moves the onUpdate to the correct table based on the new rate
local function maintainOnUpdate(onUpdate, rate)
if rate then
if not onUpdate.t then
-- remove from onUpdateDB
onUpdateDB[onUpdate.i] = nil;
-- add to scheduleDB
tinsert(scheduleDB, onUpdate);
onUpdate.i = getn(scheduleDB);
end
elseif onUpdate.t then
-- remove from scheduleDB and add to onUpdateDB
hDelete(scheduleDB, onUpdate.i, onUpdate);
insertOnUpdate(onUpdate);
end
end
local function setOnUpdateRate(onUpdate, rate, now)
if rate then
-- last update, start time, due time
onUpdate.t, onUpdate.l, onUpdate.s, onUpdate.d = rate, now, now, now + rate;
-- place in scheduleDB
hMaintain(scheduleDB, onUpdate.i, onUpdate);
else
onUpdate.t, onUpdate.l, onUpdate.s, onUpdate.d = nil, nil, nil, nil;
end
end
function Timex:AddOnUpdate(id, rate, autoRemove, f, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20)
if f then
local now = getTime();
-- look for named onUpdate
local onUpdate;
if id then
onUpdate = onUpdateMap[id];
end
if rate then
rate = getNumber(rate);
rate = rate > 0 and rate or nil;
end
if not onUpdate then
-- create
onUpdate = newSchedule();
if id then
onUpdateMap[id] = onUpdate;
end
-- insert into scheduleDB or onUpdateDB, depending on whether a rate is specced or not
if rate then
tinsert(scheduleDB, onUpdate);
onUpdate.i = getn(scheduleDB);
else
insertOnUpdate(onUpdate);
end
else
maintainOnUpdate(onUpdate, rate);
end
onUpdate.id = id or this:GetName() or onUpdateDB;
onUpdate.aR, onUpdate.f, onUpdate.r, onUpdate.del = autoRemove, f, true, nil;
-- maintain scheduleDB if necessary
setOnUpdateRate(onUpdate, rate, now);
-- flag as an update schedule
onUpdate.u = true;
buildArgs(onUpdate.a, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20);
handleFrame();
end
end
function Timex:DeleteOnUpdate(id)
local onUpdate = onUpdateMap[id];
if onUpdate then
if onUpdate.t then
hDelete(scheduleDB, onUpdate.i);
deleteOnUpdate(onUpdate);
else
onUpdate.del = nil;
onUpdateDB[onUpdate.i] = nil;
onUpdateMap[id] = nil;
unusedSchedules[onUpdate] = true;
end
handleFrame();
end
end
function Timex:ChangeOnUpdateRate(id, rate)
local onUpdate = onUpdateMap[id];
if onUpdate then
local now = getTime();
if rate then
rate = getNumber(rate);
rate = rate > 0 and rate or nil;
end
maintainOnUpdate(onUpdate, rate);
setOnUpdateRate(onUpdate, rate, now);
end
end
--<< ================================================= >>--
-- Section Omega: Register the AddOn Object. --
--<< ================================================= >>--
Timex:RegisterForLoad()