v9.7.2: Ebonhold Database integration and core logic refinements
This commit is contained in:
@@ -0,0 +1,311 @@
|
||||
---@class MessageHandlerFactory
|
||||
local MessageHandlerFactory = setmetatable(QuestieLoader:CreateModule("MessageHandlerFactory"),
|
||||
{ __call = function(self) return self.New() end })
|
||||
|
||||
---@alias Event string
|
||||
---@alias Callback fun(...:any):any
|
||||
|
||||
--- COMPATIBILITY ---
|
||||
local C_Timer = QuestieCompat.C_Timer
|
||||
|
||||
--- Localize functions
|
||||
local yield = coroutine.yield
|
||||
local insert, remove = table.insert, table.remove
|
||||
local wipe = wipe
|
||||
|
||||
--- Creates a new MessageHandler
|
||||
---@return MessageHandler
|
||||
function MessageHandlerFactory.New()
|
||||
---@class MessageHandler
|
||||
local handler = {}
|
||||
|
||||
--- Contains all the events that are fired repeatably
|
||||
---@type table<Event,Callback[]>
|
||||
handler.repeatEvents = {}
|
||||
|
||||
--- Contains all the events that are fired once
|
||||
---@type table<Event,Callback[]>
|
||||
handler.onceEvents = {}
|
||||
|
||||
-- Used when asyncronously calling events
|
||||
---@type table<Event, boolean>
|
||||
handler.executing = {}
|
||||
|
||||
--- The local function for both Async and Sync callbacks
|
||||
---@param eventName Event
|
||||
---@param asyncCount number? @How many events to fire per yield
|
||||
---@param ... any @Input arguments for the callback
|
||||
---@return table? @Returns a table of all the return values from the callbacks
|
||||
local function fire(eventName, asyncCount, ...)
|
||||
if handler.executing[eventName] then
|
||||
error("Event '" .. eventName .. "' is already being executed!", 2)
|
||||
end
|
||||
|
||||
handler.executing[eventName] = true
|
||||
|
||||
--* Fire once tables
|
||||
if handler.onceEvents[eventName] then
|
||||
local eventList = handler.onceEvents[eventName]
|
||||
for callbackIndex = 1, #eventList do
|
||||
-- Function call
|
||||
eventList[callbackIndex](...)
|
||||
end
|
||||
wipe(eventList)
|
||||
end
|
||||
|
||||
--* Fire repeat tables
|
||||
local returnValues = nil
|
||||
if handler.repeatEvents[eventName] then
|
||||
local eventList = handler.repeatEvents[eventName]
|
||||
for callbackIndex = 1, #eventList do
|
||||
-- Function call
|
||||
local retValue = eventList[callbackIndex](...)
|
||||
|
||||
-- If we have a return value we save it to the return table
|
||||
if retValue then
|
||||
if not returnValues then returnValues = {} end
|
||||
returnValues[#returnValues + 1] = retValue
|
||||
end
|
||||
|
||||
--If we are a async function we yield after each asyncCount
|
||||
if asyncCount and callbackIndex % asyncCount == 0 then
|
||||
yield()
|
||||
end
|
||||
end
|
||||
end
|
||||
handler.executing[eventName] = nil
|
||||
|
||||
return returnValues
|
||||
end
|
||||
|
||||
--- Fire a callback event
|
||||
---@param eventName Event
|
||||
---@param ... any @Input arguments for the callback
|
||||
---@return table? @A table containing all the return values
|
||||
function handler:Fire(eventName, ...)
|
||||
return fire(eventName, nil, ...)
|
||||
end
|
||||
|
||||
--- Fire a async callback event which invokes coroutine yield
|
||||
---@param eventName Event
|
||||
---@param asyncCount number? @How many events to fire per yield
|
||||
---@param ... any @Input arguments for the callback
|
||||
---@return table? @A table containing all the return values
|
||||
function handler:FireAsync(eventName, asyncCount, ...)
|
||||
local returnValues = fire(eventName, asyncCount, ...)
|
||||
--? We call yield here returning the value to the calling resume, makes it take one more resume to finish
|
||||
yield(returnValues)
|
||||
return returnValues
|
||||
end
|
||||
|
||||
---Register a callback
|
||||
---@param eventName Event
|
||||
---@param callback Callback
|
||||
function handler:RegisterRepeating(eventName, callback)
|
||||
if not callback or type(callback) ~= "function" then
|
||||
error("Usage: Register(eventName, callback): 'callback' - function expected.", 2)
|
||||
elseif not eventName or type(eventName) ~= "string" then
|
||||
error("Usage: Register(eventName, callback): 'eventName' - a string expected.", 2)
|
||||
end
|
||||
if not self.repeatEvents[eventName] then
|
||||
self.repeatEvents[eventName] = {}
|
||||
end
|
||||
insert(self.repeatEvents[eventName], callback)
|
||||
end
|
||||
|
||||
--- Register a callback that will only be called once
|
||||
---@param eventName Event
|
||||
---@param callback Callback
|
||||
function handler:RegisterOnce(eventName, callback)
|
||||
if not callback or type(callback) ~= "function" then
|
||||
error("Usage: RegisterOnce(eventName, callback): 'callback' - function expected.", 2)
|
||||
elseif not eventName or type(eventName) ~= "string" then
|
||||
error("Usage: RegisterOnce(eventName, callback): 'eventName' - a string expected.", 2)
|
||||
end
|
||||
if not self.onceEvents[eventName] then
|
||||
self.onceEvents[eventName] = {}
|
||||
end
|
||||
insert(self.onceEvents[eventName], callback)
|
||||
end
|
||||
|
||||
---Unregister a callback by function
|
||||
---@param eventName Event
|
||||
---@param callback Callback
|
||||
function handler:UnregisterRepeating(eventName, callback)
|
||||
if not callback or type(callback) ~= "function" then
|
||||
error("Usage: Unregister(eventName, callback): 'callback' - function expected.", 2)
|
||||
elseif not eventName or type(eventName) ~= "string" then
|
||||
error("Usage: Unregister(eventName, callback): 'eventName' - a string expected.", 2)
|
||||
end
|
||||
local eventList = self.repeatEvents[eventName]
|
||||
if eventList then
|
||||
for callbackIndex = 1, #eventList do
|
||||
if eventList[callbackIndex] == callback then
|
||||
remove(self.repeatEvents[eventName], callbackIndex)
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---Unregisters all events for a given event name in repeat and once-lists
|
||||
---@param eventName Event
|
||||
function handler:UnregisterAll(eventName)
|
||||
if not eventName or type(eventName) ~= "string" then
|
||||
error("Usage: UnregisterAll(eventName): 'eventName' - a string expected.", 2)
|
||||
end
|
||||
if self.repeatEvents[eventName] then
|
||||
wipe(self.repeatEvents[eventName])
|
||||
end
|
||||
if self.onceEvents[eventName] then
|
||||
wipe(self.onceEvents[eventName])
|
||||
end
|
||||
end
|
||||
|
||||
return handler
|
||||
end
|
||||
|
||||
----- Tests -----
|
||||
do
|
||||
--? This is the tests for MessageHandlerFactory
|
||||
local function RunMessageHandlerTests()
|
||||
Questie:Debug(Questie.DEBUG_CRITICAL, " -- Running " .. Questie:Colorize("MessageHandlerFactory", "yellow") .. " tests --")
|
||||
local testEvent = "EVENT_TEST"
|
||||
|
||||
--- Test simple usage
|
||||
do
|
||||
local MessageHandler = MessageHandlerFactory:New()
|
||||
local returnedCount = 0
|
||||
|
||||
local incrementFunction = function()
|
||||
returnedCount = returnedCount + 1
|
||||
end
|
||||
|
||||
-- Add and fire
|
||||
MessageHandler:RegisterRepeating(testEvent, incrementFunction)
|
||||
MessageHandler:Fire(testEvent)
|
||||
assert(returnedCount == 1, Questie:Colorize(" -- FAILED: Event was not fired", "red"))
|
||||
|
||||
-- Unregister and fire
|
||||
MessageHandler:UnregisterRepeating(testEvent, incrementFunction)
|
||||
MessageHandler:Fire(testEvent)
|
||||
assert(returnedCount == 1, Questie:Colorize(" -- FAILED: Event was fired after unregistering", "red"))
|
||||
|
||||
-- Register two events and fire
|
||||
MessageHandler:RegisterRepeating(testEvent, incrementFunction)
|
||||
MessageHandler:RegisterRepeating(testEvent, incrementFunction)
|
||||
MessageHandler:Fire(testEvent)
|
||||
assert(returnedCount == 3, Questie:Colorize(" -- FAILED: Event was not fired twice", "red"))
|
||||
|
||||
-- Unregister all events and fire
|
||||
MessageHandler:UnregisterAll(testEvent)
|
||||
MessageHandler:Fire(testEvent)
|
||||
assert(returnedCount == 3, Questie:Colorize(" -- FAILED: Event was fired after unregistering all", "red"))
|
||||
|
||||
-- Register once and fire
|
||||
MessageHandler:RegisterOnce(testEvent, incrementFunction)
|
||||
MessageHandler:Fire(testEvent)
|
||||
MessageHandler:Fire(testEvent)
|
||||
assert(returnedCount == 4, Questie:Colorize(" -- FAILED: Event was not fired once", "red"))
|
||||
end
|
||||
|
||||
--- Test multiple registered events
|
||||
do
|
||||
local MessageHandler = MessageHandlerFactory:New()
|
||||
local returnedCount = 0
|
||||
local incrementFunction = function()
|
||||
returnedCount = returnedCount + 1
|
||||
end
|
||||
local incrementFunction2 = function()
|
||||
returnedCount = returnedCount + 1
|
||||
end
|
||||
|
||||
local testEvent2 = "EVENT_TEST2"
|
||||
|
||||
MessageHandler:RegisterRepeating(testEvent, incrementFunction)
|
||||
MessageHandler:RegisterRepeating(testEvent2, incrementFunction2)
|
||||
MessageHandler:Fire(testEvent)
|
||||
assert(returnedCount == 1, Questie:Colorize(" -- FAILED: Event 1 was not fired", "red"))
|
||||
MessageHandler:Fire(testEvent2)
|
||||
assert(returnedCount == 2, Questie:Colorize(" -- FAILED: Event 2 was not fired", "red"))
|
||||
|
||||
-- Unregister and fire
|
||||
MessageHandler:UnregisterRepeating(testEvent, incrementFunction)
|
||||
MessageHandler:Fire(testEvent)
|
||||
assert(returnedCount == 2, Questie:Colorize(" -- FAILED: Event 1 was fired after unregistering", "red"))
|
||||
MessageHandler:Fire(testEvent2)
|
||||
assert(returnedCount == 3, Questie:Colorize(" -- FAILED: Event 2 was not fired", "red"))
|
||||
MessageHandler:UnregisterRepeating(testEvent2, incrementFunction2)
|
||||
MessageHandler:Fire(testEvent2)
|
||||
assert(returnedCount == 3, Questie:Colorize(" -- FAILED: Event 2 was fired after unregistering", "red"))
|
||||
end
|
||||
|
||||
--- Test return
|
||||
do
|
||||
local MessageHandler = MessageHandlerFactory:New()
|
||||
local returnedCount = 0
|
||||
local incrementReturnFunction = function()
|
||||
returnedCount = returnedCount + 1
|
||||
return returnedCount
|
||||
end
|
||||
|
||||
-- Register mutliple events and fire
|
||||
for _ = 1, 5 do
|
||||
MessageHandler:RegisterRepeating(testEvent, incrementReturnFunction)
|
||||
end
|
||||
local retVal = MessageHandler:Fire(testEvent)
|
||||
assert(retVal, Questie:Colorize(" -- FAILED: Return value was nil", "red"))
|
||||
assert(retVal[1] == 1, Questie:Colorize(" -- FAILED: 1 Function value was not returned", "red"))
|
||||
assert(retVal[2] == 2, Questie:Colorize(" -- FAILED: 2 Function value was not returned", "red"))
|
||||
assert(retVal[3] == 3, Questie:Colorize(" -- FAILED: 3 Function value was not returned", "red"))
|
||||
assert(retVal[4] == 4, Questie:Colorize(" -- FAILED: 4 Function value was not returned", "red"))
|
||||
assert(retVal[5] == 5, Questie:Colorize(" -- FAILED: 5 Function value was not returned", "red"))
|
||||
end
|
||||
|
||||
--- Test async and async return
|
||||
do
|
||||
local MessageHandler = MessageHandlerFactory:New()
|
||||
local returnedCount = 0
|
||||
|
||||
local incrementReturnFunction = function()
|
||||
returnedCount = returnedCount + 1
|
||||
return returnedCount
|
||||
end
|
||||
|
||||
-- Register mutliple events and fire
|
||||
for _ = 1, 5 do
|
||||
MessageHandler:RegisterRepeating(testEvent, incrementReturnFunction)
|
||||
end
|
||||
|
||||
local routine = coroutine.create(
|
||||
function()
|
||||
MessageHandler:FireAsync(testEvent, 2)
|
||||
assert(returnedCount == 5, Questie:Colorize(" -- FAILED: Event was not fired the correct amount of times", "red"))
|
||||
end
|
||||
)
|
||||
local timer
|
||||
timer = C_Timer.NewTicker(0, function()
|
||||
local success, retVal = coroutine.resume(routine)
|
||||
if retVal then
|
||||
assert(retVal[1] == 1, Questie:Colorize(" -- FAILED: 1 Function value was not returned", "red"))
|
||||
assert(retVal[2] == 2, Questie:Colorize(" -- FAILED: 2 Function value was not returned", "red"))
|
||||
assert(retVal[3] == 3, Questie:Colorize(" -- FAILED: 3 Function value was not returned", "red"))
|
||||
assert(retVal[4] == 4, Questie:Colorize(" -- FAILED: 4 Function value was not returned", "red"))
|
||||
assert(retVal[5] == 5, Questie:Colorize(" -- FAILED: 5 Function value was not returned", "red"))
|
||||
end
|
||||
assert(success, Questie:Colorize(" -- FAILED: Coroutine failed", "red"), retVal)
|
||||
|
||||
-- Kill the timer when the coroutine is dead.
|
||||
if (coroutine.status(routine) == "dead") then
|
||||
Questie:Debug(Questie.DEBUG_CRITICAL, "- MessageHandlerFactory - |cFF00FF00SUCCESS!|r")
|
||||
timer:Cancel()
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
-- Run it after all files has been loaded
|
||||
C_Timer.After(2, RunMessageHandlerTests)
|
||||
|
||||
end
|
||||
-----------------
|
||||
Reference in New Issue
Block a user