--[[ -- -- Chronos -- Keeper of Time -- -- By Alexander Brazie, Thott and AnduinLothar -- -- Chronos manages time. You can schedule a function to be called -- in X seconds, with or without an id. You can request a timer, -- which tracks the elapsed duration since the timer was started. -- -- You can also create Tasks - functions which will perform a -- complex operation over time, reducing system lag if used properly. -- -- Please see below or see http://www.wowwiki.com/Chronos for details. -- -- $LastChangedBy: karlkfi $ -- $Date: 2005-11-14 09:01:30 -0800 (Mon, 14 Nov 2005) $ -- $Rev: 2789 $ -- --]] CHRONOS_REV = "$Rev: 2789 $"; CHRONOS_DEBUG = false; CH_DEBUG = "CHRONOS_DEBUG"; CHRONOS_DEBUG_WARNINGS = false; CH_DEBUG_T = "CHRONOS_DEBUG_WARNINGS"; -- Chronos Data ChronosData = { -- Initialize the startup time elaspedTime = 0; -- Initialize the VariablesLoaded flag variablesLoaded = false; -- Initialize the EnteredWorld flag enteredWorld = false; -- Last ID lastID = nil; -- Initialize the Timers timers = {}; -- Initialize the perform-over-time task list tasks = {}; }; -- Prototype Chronos Chronos = { -- Online or off online = true; -- Maximum items per frame MAX_TASKS_PER_FRAME = 100; -- Maximum steps per task MAX_STEPS_PER_TASK = 300; -- Maximum time delay per frame MAX_TIME_PER_STEP = .3; emptyTable = {}; getArgTable = function(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20) if (( a1 == nil ) and ( a2 == nil ) and ( a3 == nil ) and ( a4 == nil ) and ( a5 == nil ) and ( a6 == nil ) and ( a7 == nil ) and ( a8 == nil ) and ( a9 == nil ) and ( a10 == nil ) and ( a11 == nil ) and ( a12 == nil ) and ( a13 == nil ) and ( a14 == nil ) and ( a15 == nil ) and ( a16 == nil ) and ( a17 == nil ) and ( a18 == nil ) and ( a19 == nil ) and ( a20 == nil )) then return Chronos.emptyTable; else local args = {}; table.setn(args, 0); table.insert(args, a1); table.insert(args, a2); table.insert(args, a3); table.insert(args, a4); table.insert(args, a5); table.insert(args, a6); table.insert(args, a7); table.insert(args, a8); table.insert(args, a9); table.insert(args, a10); table.insert(args, a11); table.insert(args, a12); table.insert(args, a13); table.insert(args, a14); table.insert(args, a15); table.insert(args, a16); table.insert(args, a17); table.insert(args, a18); table.insert(args, a19); table.insert(args, a20); return args; end end; -- -- pop ( table ) -- -- Removes a value and returns it from the table -- Arg: -- table - the table -- -- duplicated from Sea pop = function (table1) if(not table1) then Chronos.printDebugError(nil, "Bad table passed to pop"); return nil; end local n = table.getn(table1); if(not n) then Chronos.printDebugError(nil, "Bad table.getn() passed to pop"); return nil; end if ( n == 0 ) then return nil; end local v = table1[n]; table.setn(table1, n-1) --Doesn't nil the entry like table.remove, so it's never garbace collected and can be replaced --v = table.remove(table1); return v; end; printError = function (text) ChatFrame1:AddMessage(text, RED_FONT_COLOR.r, RED_FONT_COLOR.g, RED_FONT_COLOR.b, 1.0, UIERRORS_HOLD_TIME); end; printDebugError = function (var, text) if (var) and (getglobal(var)) then Chronos.printError(text); end end; debug = function (enable) if (enable) then Chronos_OnUpdate = Chronos_OnUpdate_Debug; CHRONOS_DEBUG = true; CHRONOS_DEBUG_WARNINGS = true; else Chronos_OnUpdate = Chronos_OnUpdate_Quick; CHRONOS_DEBUG = false; CHRONOS_DEBUG_WARNINGS = false; end end; --[[ -- Scheduling functions -- Parts rewritten by AnduinLothar for efficiency -- Parts rewritten by Thott for speed -- Written by Alexander -- Original by Thott -- -- Usage: Chronos.schedule(when,handler,arg1,arg2,etc) -- -- After seconds pass (values less than one and fractional values are -- fine), handler is called with the specified arguments, i.e.: -- handler(arg1,arg2,etc) -- -- If you'd like to have something done every X seconds, reschedule -- it each time in the handler or preferably use scheduleRepeating. -- -- Also, please note that there is a limit to the number of -- scheduled tasks that can be performed per xml object at the -- same time. --]] schedule = function (when,handler,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20) if ( not Chronos.online ) then return; end if ( not handler) then Chronos.printError("ERROR: nil handler passed to Chronos.schedule()"); return; end --local memstart = gcinfo(); -- -- Assign an id -- local id = ""; -- if ( not this ) then -- id = "Keybinding"; -- else -- id = this:GetName(); -- end -- if ( not id ) then -- id = "_DEFAULT"; -- end -- if ( not when ) then -- Chronos.printDebugError(CH_DEBUG_T, "Chronos Error Detection: ", id , " has sent no interval for this function. ", when ); -- return; -- end -- -- Ensure we're not looping ChronosFrame -- if ( id == "ChronosFrame" and ChronosData.lastID ) then -- id = ChronosData.lastID; -- end local task; -- reuse task memory if possible to avoid excessive garbage collection --Thott if(not ChronosData.sched[ChronosData.sched.n+1]) then ChronosData.sched[ChronosData.sched.n+1] = {}; end ChronosData.sched.n = ChronosData.sched.n+1; local i = ChronosData.sched.n; -- ChronosData.sched[i].id = id; ChronosData.sched[i].time = when + GetTime(); ChronosData.sched[i].handler = handler; ChronosData.sched[i].args = Chronos.getArgTable(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20); -- task list is a heap, add new --Thott while(i > 1) do parent = floor(i/2); if(ChronosData.sched[parent].time > ChronosData.sched[i].time) then Chronos_Swap(i,parent); else break; end i = parent; end -- Debug print --Chronos.printDebugError(CH_DEBUG, "Scheduled ", handler," in ",when," seconds from ", id ); --Chronos.printError("Memory change in schedule: ",memstart,"->",memend," = ",memend-memstart); end; --[[ -- Chronos.scheduleByName(name, delay, function, arg1, ... ); -- -- Same as Chronos.schedule, except it takes a schedule name argument. -- Only one event can be scheduled with a given name at any one time. -- Thus if one exists, and another one is scheduled, the first one -- is deleted, then the second one added. -- --]] scheduleByName = function (name,when,handler,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20) if ( not name ) then Chronos.printDebugError(CH_DEBUG_T,"Chronos Error Detection: No name specified to Chronos.scheduleByName"); return; end if(ChronosData.byName[name] and handler) then Chronos.printDebugError(CH_DEBUG_T,"Chronos Error Detection: scheduleByName is reasigning '".. name.."'."); ChronosData.byName[name] = { time = when+GetTime(), handler = handler, arg = Chronos.getArgTable(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20); }; else if ( not handler ) then if ( not ChronosData.byName[name] ) then Chronos.printDebugError(CH_DEBUG_T,"Chronos Error Detection: No handler specified to Chronos.scheduleByName, no previous entry found for scheduled entry '".. name.."'."); return; end if ( not ChronosData.byName[name].handler ) then Chronos.printDebugError(CH_DEBUG_T,"Chronos Error Detection: No handler specified to Chronos.scheduleByName, no handler could be found in previous entry of '".. name.."' either."); return; end handler = ChronosData.byName[name].handler; Chronos.printDebugError(CH_DEBUG_T,"Chronos: scheduleByName is updating '".. name.."' to time: ".. when); else Chronos.printDebugError(CH_DEBUG_T,"Chronos: scheduleByName is asigning '".. name.."'."); end ChronosData.byName[name] = { time = when+GetTime(), handler = handler, arg = Chronos.getArgTable(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20); }; end end; --[[ -- unscheduleByName(name); -- -- Removes an entry that was created with scheduleByName() -- -- Args: -- name - the name used -- --]] unscheduleByName = function(name) if ( not Chronos.online ) then return; end if ( not name ) then Chronos.printError("No name specified to Chronos.unscheduleByName"); return; end if(ChronosData.byName[name]) then ChronosData.byName[name] = nil; end -- Debug print --Chronos.printDebugError(CH_DEBUG, "Cancelled scheduled timer of name ",name); end; --[[ -- isScheduledByName(name) -- Returns the amount of time left if it is indeed scheduled by name! -- -- returns: -- number - time remaining -- nil - not scheduled -- --]] isScheduledByName = function (name) if ( not Chronos.online ) then return; end if ( not name ) then Chronos.printError("No name specified to Chronos.isScheduledByName ", this:GetName()); return; end if(ChronosData.byName[name]) then return ChronosData.byName[name].time - GetTime(); end -- Debug print --Chronos.printDebugError(CH_DEBUG, "Did not find timer of name ",name); return nil; end; --[[ -- Chronos.scheduleRepeating(name, delay, function); -- -- Same as Chronos.scheduleByName, except it repeats without recalling and takes no arguments. -- --]] scheduleRepeating = function (name,when,handler) if ( not name ) then Chronos.printDebugError(CH_DEBUG_T,"Chronos Error Detection: No name specified to Chronos.scheduleRepeating"); return; end if(ChronosData.byName[name] and handler) then Chronos.printDebugError(CH_DEBUG_T,"Chronos Error Detection: scheduleRepeating is reasigning ".. name); ChronosData.byName[name] = { time = when+GetTime(), period = when, handler = handler, repeating = true }; else if ( not handler ) then if ( not ChronosData.byName[name] ) then Chronos.printDebugError(CH_DEBUG_T,"Chronos Error Detection: No handler specified to Chronos.scheduleRepeating, no previous entry found for scheduled entry '".. name.."'."); return; end if ( not ChronosData.byName[name].handler ) then Chronos.printDebugError(CH_DEBUG_T,"Chronos Error Detection: No handler specified to Chronos.scheduleRepeating, no handler could be found in previous entry '".. name.."' either."); return; end handler = ChronosData.byName[name].handler; Chronos.printDebugError(CH_DEBUG_T,"Chronos: scheduleRepeating is updating '".. name.."' to time: ".. when); else Chronos.printDebugError(CH_DEBUG_T,"Chronos: scheduleRepeating is asigning '".. name.."'."); end ChronosData.byName[name] = { time = when+GetTime(), period = when, handler = handler, repeating = true }; end end; --[[ -- Chronos.flushByName(name, when); -- -- Updates the ByName or Repeating event to flush at the time specified. If no time is specified flush will be immediate. If it is a Repeating event the timer will be reset. -- --]] flushByName = function (name,when) if ( not name ) then Chronos.printDebugError(CH_DEBUG_T,"Chronos Error Detection: No name specified to Chronos.flushByName"); return; elseif ( not ChronosData.byName[name] ) then Chronos.printDebugError(CH_DEBUG_T,"Chronos Error Detection: no previous entry found for Chronos.flushByName entry '".. name.."'."); return; end if (not when) then Chronos.printDebugError(CH_DEBUG_T,"Chronos: flushing '".. name.."'."); when = GetTime(); else Chronos.printDebugError(CH_DEBUG_T,"Chronos: flushing '".. name.."' in "..when.." seconds."); when = when+GetTime(); end ChronosData.byName[name].time = when; end; --[[ -- Chronos.startTimer([ID]); -- Starts a timer on a particular -- -- Args -- ID - optional parameter to identify who is asking for a timer. -- -- If ID does not exist, this:GetName() is used. -- -- When you want to get the amount of time passed since startTimer(ID) is called, -- call getTimer(ID) and it will return the number in seconds. -- --]] startTimer = function ( id ) if ( not Chronos.online ) then return; end if ( not id ) then id = this:GetName(); end -- Create a table for this id's timers if ( not ChronosData.timers[id] ) then ChronosData.timers[id] = {}; ChronosData.timers[id].n = 0; end -- Clear out an entry if the table is too big. if (ChronosData.timers[id].n > Chronos.MAX_TASKS_PER_FRAME) then Chronos.printError("Too many Chronos timers created for id ",id); return; end -- Add a new timer entry table.insert(ChronosData.timers[id],GetTime()); end; --[[ -- endTimer([id]); -- -- Ends the timer and returns the amount of time passed. -- -- args: -- id - ID for the timer. If not specified, then ID will -- be this:GetName() -- -- returns: -- (Number delta, Number start, Number end) -- -- delta - the amount of time passed in seconds. -- start - the starting time -- now - the time the endTimer was called. --]] endTimer = function( id ) if ( not Chronos.online ) then return; end if ( not id ) then id = this:GetName(); end if ( not ChronosData.timers[id] or ChronosData.timers[id].n == 0) then return nil; end local now = GetTime(); -- Grab the last timer called local startTime = Chronos.pop(ChronosData.timers[id]); return (now - startTime), startTime, now; end; --[[ -- getTimer([id]); -- -- Gets the timer and returns the amount of time passed. -- Does not terminate the timer. -- -- args: -- id - ID for the timer. If not specified, then ID will -- be this:GetName() -- -- returns: -- (Number delta, Number start, Number end) -- -- delta - the amount of time passed in seconds. -- start - the starting time -- now - the time the endTimer was called. --]] getTimer = function( id ) if ( not Chronos.online ) then return; end if ( not id ) then id = this:GetName(); end local now = GetTime(); if ( not ChronosData.timers[id] or ChronosData.timers[id].n == 0) then return 0,0,now; end -- Grab the last timer called local startTime = ChronosData.timers[id][ChronosData.timers[id].n]; return (now - startTime), startTime, now; end; --[[ -- isTimerActive([id]) -- returns true if the timer exists. -- -- args: -- id - ID for the timer. If not specified, then ID will -- be this:GetName() -- -- returns: -- true - exists -- false - does not --]] isTimerActive = function ( id ) if ( not Chronos.online ) then return; end if ( not id ) then id = this:GetName(); end -- Create a table for this id's timers if ( not ChronosData.timers[id] ) then return false; end return true; end; --[[ -- getTime() -- -- returns the Chronos internal elapsed time. -- -- returns: -- (elaspedTime) -- -- elapsedTime - time in seconds since Chronos initialized --]] getTime = function() return ChronosData.elapsedTime; end; --[[ -- Chronos.everyFrame(func,arg1,...) -- -- runs func(arg1,...) every frame until func returns true. -- This is the most effecient way to have something run -- every frame, either forever, or until some job is done. -- -- By Thott --]] everyFrame = function(func,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20) table.insert(ChronosData.everyFrame,{func=func,arg=Chronos.getArgTable(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20)}); end; --[[ -- performTask( TaskObject ); -- -- Queues up a task to be completed over time. -- Contains a before and after function -- to be called when the task is started and -- completed. -- -- Args: -- TaskObject - a table containing: -- { -- (Required:) -- step - function to be performed, must be fast -- -- isDone - function which determines if the -- task is completed. -- Returns true when done -- Returns false if the task should continue -- to call step() each frame. -- -- -- (Optional:) -- stepArgs - arguments to be passed to step -- doneArgs - arguments to be passed to isDone -- -- before - function called before the first step -- beforeArgs - arguments passed to Before -- -- after - function called when isDone returns true -- afterArgs - arguments passed -- -- limit - a number defining the maximum number -- of steps that will be peformed before -- the task is removed to prevent lag. -- (Defaults to 100) -- } --]] performTask = function (taskTable, name) if ( not Chronos.online ) then return; end -- Valid table? if ( not taskTable ) then Chronos.printError("Chronos Error Detection: Invalid table to Chronos.peformTask", this:GetName()); return nil; end -- Must contain a step function if ( not taskTable.step or type(taskTable.step) ~= "function" ) then Chronos.printDebugError(CH_DEBUG_T,"Chronos Error Detection: You must specify a step function to be called to perform the task. (",this:GetName(),")"); return nil; end -- Must contain a completion function if ( not taskTable.isDone or type(taskTable.isDone) ~= "function" ) then Chronos.printDebugError(CH_DEBUG_T,"Chronos Error Detection: You must specify an isDone function to be called to indicate if the task is complete. (",this:GetName(),")"); return nil; end -- Get an ID if ( not name ) then name = this:GetName(); end -- Set the limit if ( not taskTable.limit ) then taskTable.limit = Chronos.MAX_STEPS_PER_TASK; end local foundId = nil; for i=1,table.getn(ChronosData.tasks) do if ( ChronosData.tasks[i].name == id ) then foundId = i; end end -- Add it to the task list if ( not foundId ) then taskTable.name = name; table.insert(ChronosData.tasks, taskTable); return true; elseif ( not ChronosData.tasks[foundId].errorSent ) then ChronosData.tasks[foundId].errorSent = true; Chronos.printError("Chronos Error Detection: There's already a task with the ID: ", name ); return nil; end end; --[[ -- Chronos.afterInit(func, ...) -- Performs func after the game has truely started. -- By Thott --]] afterInit = function (func, a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20) local id; if(this) then id = this:GetName(); else id = "unknown"; end --if(id == "SkyFrame") then -- Chronos.printError("Ignoring Sky init"); -- return; --end if(ChronosData.initialized) then func(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20); else if(not ChronosData.afterInit) then ChronosData.afterInit = {}; ChronosData.afterInit.n = 0; Chronos.schedule(0.2,Chronos_InitCheck); end local n = ChronosData.afterInit.n+1; ChronosData.afterInit[n] = {}; ChronosData.afterInit[n].func = func; ChronosData.afterInit[n].args = Chronos.getArgTable(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20); ChronosData.afterInit[n].id = id; ChronosData.afterInit.n = n; end end; }; --[[ -- unscheduleRepeating(name); -- Mirrors unscheduleByName for backwards compatibility --]] Chronos.unscheduleRepeating = Chronos.unscheduleByName; --[[ -- isScheduledRepeating(name) -- Mirrors isScheduledByName for backwards compatibility --]] Chronos.isScheduledRepeating = Chronos.isScheduledByName; --[[ Event Handlers ]]-- function Chronos_OnLoad() Chronos.framecount = 0; ChronosData.byName = {}; ChronosData.repeating = {}; ChronosData.tasks = {}; ChronosData.tasks.n = 0; ChronosData.sched = {}; ChronosData.sched.n = 0; ChronosData.elapsedTime = 0; ChronosData.variablesLoaded = false; ChronosData.everyFrame = {}; ChronosData.everyFrame.n = 0; --[[ Convert Revision to Numeric ]]-- if (convertRev) then convertRev("CHRONOS_REV"); end Chronos.afterInit(Chronos_SkyRegister); this:RegisterEvent("VARIABLES_LOADED"); this:RegisterEvent("PLAYER_ENTERING_WORLD"); this:RegisterEvent("PLAYER_LEAVING_WORLD"); end function Chronos_OnEvent(event) if(event == "VARIABLES_LOADED") then ChronosData.variablesLoaded = true; ChronosFrame:Show(); elseif (event == "PLAYER_ENTERING_WORLD") then ChronosData.enteredWorld = true; ChronosData.online = true; elseif (event == "PLAYER_LEAVING_WORLD") then ChronosData.online = false; end end function Chronos_InitCheck() if(not ChronosData.initialized) then if(UnitName("player") and UnitName("player")~=UKNOWNBEING and UnitName("player")~=UNKNOWNBEING and UnitName("player")~=UNKNOWNOBJECT and ChronosData.variablesLoaded and ChronosData.enteredWorld) then ChronosData.initialized = true; Chronos.schedule(1,Chronos_InitCheck); return; else Chronos.schedule(0.2,Chronos_InitCheck); return; end end if(ChronosData.afterInit) then local i = ChronosData.afterInit_i; if(not i) then i = 1; end ChronosData.afterInit_i = i+1; --Chronos.printError("afterInit: processing ",i," of ",ChronosData.afterInit.n," initialization functions, id: ",ChronosData.afterInit[i].id); Chronos_Run(ChronosData.afterInit[i].func,ChronosData.afterInit[i].args); if(i == ChronosData.afterInit.n) then ChronosData.afterInit = nil; ChronosData.afterInit_i = nil; else Chronos.schedule(0.1,Chronos_InitCheck); return; end end end; function Chronos_Run(func,arg) if(func) then if(arg) then return func(unpack(arg)); else return func(); end end end function Chronos_Swap(i,j) local t = ChronosData.sched[i]; ChronosData.sched[i] = ChronosData.sched[j]; ChronosData.sched[j] = t; end function Chronos_PopTask() if(ChronosData.sched.n == 1) then ChronosData.sched.n = 0; return 1; end Chronos_Swap(1,ChronosData.sched.n); ChronosData.sched.n = ChronosData.sched.n - 1; local i = 1; local new; while(ChronosData.sched[i] and i <= ChronosData.sched.n) do new = i*2; if(new > ChronosData.sched.n) then break; elseif(new < ChronosData.sched.n) then if(ChronosData.sched[new+1].time < ChronosData.sched[new].time) then new = new + 1; end end if(ChronosData.sched[new].time < ChronosData.sched[i].time) then Chronos_Swap(i,new); i = new; else break; end end return ChronosData.sched.n+1; end function Chronos_OnUpdate_Quick(dt) --local memstart = gcinfo(); if ( not Chronos.online ) then return; end if ( ChronosData.variablesLoaded == false ) then return; end if ( ChronosData.elapsedTime ) then ChronosData.elapsedTime = ChronosData.elapsedTime + dt; else ChronosData.elapsedTime = dt; end -- execute all the everyFrame tasks, deleting any that return true. for i=1,ChronosData.everyFrame.n do if(Chronos_Run(ChronosData.everyFrame[i].func,ChronosData.everyFrame[i].arg)) then -- delete ChronosData.everyFrame[i] = Chronos.pop(ChronosData.everyFrame); end end local now = GetTime(); local i; -- Execute scheduled tasks that are ready, popping them off the heap. while(ChronosData.sched.n > 0) do if(ChronosData.sched[1].time <= now) then i = Chronos_PopTask(); Chronos_Run(ChronosData.sched[i].handler,ChronosData.sched[i].args); else break; end end -- Execute named scheduled tasks that are ready. local k,v = next(ChronosData.byName); local newK, newV; while (k ~= nil) do newK,newV = next(ChronosData.byName, k); if(v.time <= now) then if (v.repeating) then ChronosData.byName[k].time = now + v.period; v.handler(); else local y = v; ChronosData.byName[k] = nil; Chronos_Run(y.handler,y.arg); end end k,v = newK,newV; end local timeThisUpdate = 0; local largest = 0; local largestName = nil; -- Perform tasks if the time limit is not exceeded -- Only perform each task once at most per update -- for i=1, table.getn(ChronosData.tasks) do -- Perform a task runTime = Chronos_OnUpdate_Tasks(timeThisUpdate); timeThisUpdate = timeThisUpdate + runTime; -- Check if this was the biggest hog yet if ( runTime > largest ) then largest = runTime; largestName = i; end -- Check if we've overrun our limit if ( timeThisUpdate > Chronos.MAX_TIME_PER_STEP ) then Chronos.printDebugError(CH_DEBUG_T,"Chronos Warning: Maximum cpu usage time exceeded on task. Largest task was: ", largestName ); break; end end end Chronos_OnUpdate = Chronos_OnUpdate_Quick; function memcheck(memstart,where) local memend = gcinfo(); if(memstart ~= memend) then Chronos.printError("Memory change in ",where,": ",memstart,"->",memend," = ",memend-memstart); end return gcinfo(); end function Chronos_OnUpdate_Debug(dt) local memstart = gcinfo(); if ( not Chronos.online ) then return; end if ( ChronosData.variablesLoaded == false ) then return; end if ( ChronosData.elapsedTime ) then ChronosData.elapsedTime = ChronosData.elapsedTime + dt; else ChronosData.elapsedTime = dt; end local timeThisUpdate = 0; local largest = 0; local largestName = nil; -- execute all the everyFrame tasks, deleting any that return true. for i=1,ChronosData.everyFrame.n do if(Chronos_Run(ChronosData.everyFrame[i].func,ChronosData.everyFrame[i].arg)) then -- delete ChronosData.everyFrame[i] = Chronos.pop(ChronosData.everyFrame); end end local now = GetTime(); local i; -- Execute scheduled tasks that are ready, popping them off the heap. while(ChronosData.sched.n > 0) do if(ChronosData.sched[1].time <= now) then i = Chronos_PopTask(); Chronos_Run(ChronosData.sched[i].handler,ChronosData.sched[i].args); else break; end end -- Execute named scheduled tasks that are ready. local memstart = gcinfo(); local k,v = next(ChronosData.byName); local newK, newV; while (k ~= nil) do newK,newV = next(ChronosData.byName, k); if(v.time <= now) then local m = gcinfo(); if (v.repeating) then ChronosData.byName[k].time = now + v.period; v.handler(); else local y = v; ChronosData.byName[k] = nil; Chronos_Run(y.handler,y.arg); end local mm = gcinfo(); memstart = memstart + mm - m; end k,v = newK,newV; end local memend = gcinfo(); if(memend - memstart > 0) then Sea.io.print("gcmemleak from ChronosData.byName in OnUpdate: ",memend - memstart); end local largest = 0; local largestName = nil; -- Perform tasks if the time limit is not exceeded -- Only perform each task once at most per update -- memstart = memcheck(memstart,"OnUpdate, after all scheduled tasks, before Chronos tasks"); local ctn = table.getn(ChronosData.tasks); for i=1, ctn do -- Perform a task runTime = Chronos_OnUpdate_Tasks(timeThisUpdate); timeThisUpdate = timeThisUpdate + runTime; -- Check if this was the biggest hog yet if ( runTime > largest ) then largest = runTime; largestName = i; end -- Check if we've overrun our limit if ( timeThisUpdate > Chronos.MAX_TIME_PER_STEP ) then Chronos.printDebugError(CH_DEBUG_T,"Chronos Warning: Maximum cpu usage time exceeded on task. Largest task was: ", largestName ); break; end if ( largestName ) then -- ### Remove later for efficiency --Chronos.printDebugError(CH_DEBUG, " Largest named task: ", largestName ); end end memstart = memcheck(memstart,"OnUpdate, end"); end -- Updates a single task function Chronos_OnUpdate_Tasks(timeThisUpdate) if ( not Chronos.online ) then return; end -- Lets start the timer Chronos.startTimer(); -- Execute the first task if ( ChronosData.tasks[1] ) then -- Obtains the first task local task = table.remove(ChronosData.tasks, 1); -- Start the task if not yet started if ( not task.started ) then Chronos_Run(task.before,task.beforeArgs); -- Mark the task as started task.started = true; end -- Perform a step in the task Chronos_Run(task.step,task.stepArgs); -- Check if the task is completed. if ( task.isDone() ) then -- Call the after-task if ( task.after ) then if ( task.afterArgs ) then task.after(unpack(task.afterArgs) ); else task.after(); end end else if ( not task.count ) then task.count = 1; else task.count = task.count + 1; end if ( task.count < task.limit ) then -- Move them to the back of the list table.insert(ChronosData.tasks, task); else Chronos.printDebugError(CH_DEBUG_T, "Task killed due to limit-break: ", task.name ); end end end -- End the timer return Chronos.endTimer(); end -- Command Registration -- Notes to self: -- * Relocate to its own module? -- function Chronos_SkyRegister() local chronosFunc = function(msg) local _,_,seconds,command = string.find(msg,"([%d\.]+)%s+(.*)"); if(seconds and command) then Chronos.schedule(seconds,Chronos_SendChatCommand,command); else Chronos.printError(SCHEDULE_USAGE1); Chronos.printError(SCHEDULE_USAGE2); end end if(Sky) then Sky.registerSlashCommand( { id = "Schedule"; commands = SCHEDULE_COMM; onExecute = chronosFunc; helpText = SCHEDULE_DESC; } ); else SlashCmdList["CHRONOS_SCHEDULE"] = chronosFunc; for i = 1, table.getn(SCHEDULE_COMM) do setglobal("SLASH_CHRONOS_SCHEDULE"..i, SCHEDULE_COMM[i]); end end end --[[ -- Sends a chat command through the standard --]] function Chronos_SendChatCommand(command) local text = ChatFrameEditBox:GetText(); ChatFrameEditBox:SetText(command); ChatEdit_SendText(ChatFrameEditBox); ChatFrameEditBox:SetText(text); end