--[[ $Id: userfunction_events.lua 226 2013-04-16 17:18:43Z $ Event manager Handle registration and callbacks of events, both Micromacro, RoMBot and user defined ones This will NOT interfere with any of the normal event management in Micromacro or RoMBot but come as an addition. Note, when its loaded it will register the internal RoMBot and Micromacro functions as events with appropriate event handlers. Main interface: event.throw(eventName) event.register(eventName, callback, priority) Note: Im using coroutine directly as MM thread interface is shut down before atExit processing Note: The arguments given in a throw is packed in the event structure given to callback as 1st arg, any arguments given when defining the callback is given after the event structure. Recommendation: priority is a number from 1-..., normal waypoint files should use default which is 300, 1-100 and 500- is typically reservered for RoMBot, 100-200 and 400-500 for userfunctions. The priority defines the order in which the callbacks is done; all callbacks with priority 1 is called 1st (no ordering within same priority), after which callbacks are called in number priority order. TODO: - Add timestamp on event throwing - Stats on event processing delay - How to stop event processing, like "take over error handling"? Use return value from callback handler to stop? - Add the waypoint on* callbacks as events (onLoad, onDeath, onLeaveCombat, onSkillCast) Is this even nessesary/feasible/wanted? Will require mods in bot code - Add new events like onWaypoint, onPlayerClose, Will possibly require mods in bot code - Add events from client? Possible/feasible? --]] require "os" SYS_EXIT = "SYS_EXIT" SYS_RESUME = "SYS_RESUME" SYS_SUSPEND = "SYS_SUSPEND" SYS_ERROR = "SYS_ERROR" CEvent = class( function (self) self.queue = {} -- Event queue self.callbacks = {} -- Registered callbacks self.default_priority = 300 -- default priority self.total_events = 0 -- Number of processed events -- Setup processing coroutine self.thread = coroutine.create(self.runner) local status, err = coroutine.resume(self.thread, self) if not status then error("CEvent: "..err, 0) end end ) -- The event runner, executing as coroutine thread function CEvent:runner() while true do -- Process all events on queue while (#(self.queue) > 0) do -- Pop an event local event = table.remove(self.queue, 1) self.total_events = self.total_events + 1 -- Is event a valid one? if not self.callbacks[event.name] then error(string.format("Event name in queue not valid! <%s>", event.name), 1) end -- Run the callbacks for k,cb in pairs(self.callbacks[event.name]) do local status, errmsg = pcall(cb.callback, event, unpack(cb.arg)) if not status then printf("Error in CEvent:runner() executing callback! <%s>\n", event.name) printf(" %s\n", errmsg) end end -- We finished doing callbacks for exit -- Assuming only one of this event :) if (event.name == SYS_EXIT) then return end end coroutine.yield() end end -- Throw an event function CEvent:throw(eventName, ...) -- Valid event were trying to throw? if self.callbacks[eventName] then -- Insert into queue and resume runner thread table.insert(self.queue, {name = eventName, timestamp = os.time(), arg = arg}) coroutine.resume(self.thread) -- Event not known else error(string.format("Thrown event is not defined [%s]", eventName), 1) end end -- Register a callback for an event function CEvent:register(eventName, callback, priority, ...) local priority = priority or self.default_priority -- Valid event name? if type(eventName) ~= "string" then error("CEvent:register(eventName, callback, priority): eventName not a string", 0) end -- Event exists? if not self.callbacks[eventName] then error(string.format("Event doesnt exist [%s]", eventName), 1) end -- Insert callback and resort table.insert(self.callbacks[eventName], {callback = callback, priority = priority, arg = arg}) table.sort(self.callbacks[eventName], function(a,b) return a.priority < b.priority end) end -- Define an event into system function CEvent:define(eventName) if self.callbacks[eventName] then error(string.format("Event already exists [%s]", eventName), 1) else self.callbacks[eventName] = {} end end -- -- Misc functions for debug and test -- function CEvent:dump() print("\nEvent statistics:") printf("Total processed events: %d\n", self.total_events) print() end -- -- Make the global Event queue -- Events = CEvent() -- -- Add the basic events -- Events:define(SYS_EXIT) Events:define(SYS_RESUME) Events:define(SYS_SUSPEND) Events:define(SYS_ERROR) -- -- Register the default RoMBot event handlers -- Events:register(SYS_EXIT, exitCallback, 999) -- Do RoMBot atExit handler last Events:register(SYS_RESUME, resumeCallback, 1) -- Resume RoMBot before any other handlers Events:register(SYS_SUSPEND, pauseCallback, 999) -- Do RoMBot pause handler last Events:register(SYS_ERROR, errorCallback, 999) -- Do RoMBot error handler last -- -- Now hoook the original callbacks in RoM -- -- Special as RoMBot/Micromacro is shutting down function throwSysExit() Events:throw(SYS_EXIT) coroutine.resume(Events.thread, self) while coroutine.status(Events.thread) ~= "dead" do yrest(200) end end -- Hook exit callback atExit(throwSysExit) -- Rest is simple throws of events atResume( function () Events:throw(SYS_RESUME) end) atPause( function () Events:throw(SYS_SUSPEND) end) atError( function () Events:throw(SYS_ERROR) end)