Skip to content

Commit

Permalink
Improved item load processing and support for BetterBags / Baganator.
Browse files Browse the repository at this point in the history
  • Loading branch information
Caerdon committed May 10, 2024
1 parent a0f611b commit b231f43
Show file tree
Hide file tree
Showing 11 changed files with 526 additions and 142 deletions.
149 changes: 149 additions & 0 deletions CaerdonAsyncCallbackSystem.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
-- Pulled from FrameXML / ObjectAPI / AsyncCallbackSystem.lua
-- This implementation ensures a callback can receive load failures as well
--[[
Queries some data retrieval API (specifically where the data may not be currently available) and when it becomes available
calls a user-supplied function. The callback can be canceled if necessary (e.g. the frame that would use the data becomes
hidden before the data arrives).
The API is managed so that arbitrary query functions cannot be executed.
--]]

-- AsyncCallbackAPIType = {
-- ASYNC_QUEST = 1,
-- ASYNC_ITEM = 2,
-- ASYNC_SPELL = 3,
-- }

local permittedAPI =
{
[AsyncCallbackAPIType.ASYNC_QUEST] = { event = "QUEST_DATA_LOAD_RESULT", accessor = C_QuestLog.RequestLoadQuestByID },
[AsyncCallbackAPIType.ASYNC_ITEM] = { event = "ITEM_DATA_LOAD_RESULT", accessor = C_Item.RequestLoadItemDataByID },
[AsyncCallbackAPIType.ASYNC_SPELL] = { event = "SPELL_DATA_LOAD_RESULT", accessor = C_Spell.RequestLoadSpellData },
};

CaerdonAsyncCallbackSystemMixin = {};

function CaerdonAsyncCallbackSystemMixin:Init(apiType)
self.successCallbacks = {};
self.failureCallbacks = {};

-- API Type should be set up from key value pairs before OnLoad.
self.api = permittedAPI[apiType];

self:SetScript("OnEvent",
function(self, event, ...)
if event == self.api.event then
local id, success = ...;
if success then
self:FireSuccessCallbacks(id);
else
self:FireFailureCallbacks(id);
end
end
end
);
self:RegisterEvent(self.api.event);
end

local CANCELED_SENTINEL = -1;

function CaerdonAsyncCallbackSystemMixin:AddCallback(id, successCallbackFunction, failureCallbackFunction)
local successCallbacks = self:GetOrCreateSuccessCallbacks(id);
local failureCallbacks = self:GetOrCreateFailureCallbacks(id);
table.insert(successCallbacks, successCallbackFunction);
table.insert(failureCallbacks, failureCallbackFunction);
local needsAccessorCall = #successCallbacks == 1;
if needsAccessorCall then
self.api.accessor(id);
end

return #successCallbacks, successCallbacks, #failureCallbacks, failureCallbacks;
end

function CaerdonAsyncCallbackSystemMixin:AddCancelableCallback(id, successCallbackFunction, failureCallbackFunction)
-- NOTE: If the data is currently availble then the callback will be executed and callbacks cleared, so there will be nothing to cancel.
local successIndex, successCallbacks, failureIndex, failureCallbacks = self:AddCallback(id, successCallbackFunction, failureCallbackFunction);
return function()
if #successCallbacks > 0 and successCallbacks[successIndex] ~= CANCELED_SENTINEL then
successCallbacks[successIndex] = CANCELED_SENTINEL;
failureCallbacks[failureIndex] = CANCELED_SENTINEL;
return true;
end
return false;
end;
end

function CaerdonAsyncCallbackSystemMixin:FireSuccessCallbacks(id)
local callbacks = self:GetSuccessCallbacks(id);
if callbacks then
self:ClearCallbacks(id);
for i, callback in ipairs(callbacks) do
if callback ~= CANCELED_SENTINEL then
xpcall(callback, CallErrorHandler);
end
end

-- The cancel functions have a reference to this table, so ensure that it's cleared out.
for i = #callbacks, 1, -1 do
callbacks[i] = nil;
end
end
end

function CaerdonAsyncCallbackSystemMixin:FireFailureCallbacks(id)
local callbacks = self:GetFailureCallbacks(id);
if callbacks then
self:ClearCallbacks(id);
for i, callback in ipairs(callbacks) do
if callback ~= CANCELED_SENTINEL then
xpcall(callback, CallErrorHandler);
end
end

-- The cancel functions have a reference to this table, so ensure that it's cleared out.
for i = #callbacks, 1, -1 do
callbacks[i] = nil;
end
end
end

function CaerdonAsyncCallbackSystemMixin:ClearCallbacks(id)
self.successCallbacks[id] = nil;
self.failureCallbacks[id] = nil;
end

function CaerdonAsyncCallbackSystemMixin:GetSuccessCallbacks(id)
return self.successCallbacks[id]
end

function CaerdonAsyncCallbackSystemMixin:GetFailureCallbacks(id)
return self.failureCallbacks[id]
end

function CaerdonAsyncCallbackSystemMixin:GetOrCreateSuccessCallbacks(id)
local callbacks = self.successCallbacks[id];
if not callbacks then
callbacks = {};
self.successCallbacks[id] = callbacks;
end
return callbacks;
end

function CaerdonAsyncCallbackSystemMixin:GetOrCreateFailureCallbacks(id)
local callbacks = self.failureCallbacks[id];
if not callbacks then
callbacks = {};
self.failureCallbacks[id] = callbacks;
end
return callbacks;
end

local function CreateListener(apiType)
local listener = Mixin(CreateFrame("Frame"), AsyncCallbackSystemMixin);
listener:Init(apiType);
return listener;
end

CaerdonItemEventListener = CreateListener(AsyncCallbackAPIType.ASYNC_ITEM);
CaerdonSpellEventListener = CreateListener(AsyncCallbackAPIType.ASYNC_SPELL);
CaerdonQuestEventListener = CreateListener(AsyncCallbackAPIType.ASYNC_QUEST);
35 changes: 28 additions & 7 deletions CaerdonItem.lua
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,11 @@ function CaerdonItemMixin:IsItemCached()
end

if not isItemCached and not self:IsItemEmpty() then
isItemCached = C_Item.IsItemDataCached(self:GetItemLocation());
if self:HasItemLocation() then
isItemCached = C_Item.IsItemDataCached(self:GetItemLocation());
else
isItemCached = false
end
end

-- print("Item ID: " .. self:GetItemID() .. " IsItemCached: " .. tostring(isItemCached))
Expand Down Expand Up @@ -165,13 +169,20 @@ function CaerdonItemMixin:ContinueOnItemLoad(callbackFunction)
end
end

function FailTheItem()
self.customDataLoaded = false
-- TODO: Should I provide an errorCallback in this case?
print("ITEM LOAD FAILED: " .. self:GetItemID())
callbackFunction()
end

if not self:IsItemCached() then
if not self:IsItemEmpty() then
ItemEventListener:AddCallback(self:GetItemID(), GenerateClosure(ProcessTheItem))
CaerdonItemEventListener:AddCallback(self:GetItemID(), GenerateClosure(ProcessTheItem), FailTheItem)
elseif self:GetCaerdonItemType() == CaerdonItemType.Quest then
local linkType, linkOptions, name = LinkUtil.ExtractLink(self:GetItemLink());
local questID = tonumber(strsplit(":", linkOptions), 10)
QuestEventListener:AddCallback(questID, GenerateClosure(ProcessTheItem))
CaerdonQuestEventListener:AddCallback(questID, GenerateClosure(ProcessTheItem), FailTheItem)
else
ProcessTheItem()
end
Expand All @@ -188,7 +199,7 @@ function CaerdonItemMixin:ContinueWithCancelOnItemLoad(callbackFunction)

if not self:IsItemEmpty() then
local itemDataCancel
local itemCancel = ItemEventListener:AddCancelableCallback(self:GetItemID(), function ()
local itemCancel = CaerdonItemEventListener:AddCancelableCallback(self:GetItemID(), function ()
-- TODO: Update things and delay callback if needed for tooltip data
local itemData = self:GetItemData()
if itemData then
Expand All @@ -200,7 +211,12 @@ function CaerdonItemMixin:ContinueWithCancelOnItemLoad(callbackFunction)
self.customDataLoaded = true
callbackFunction()
end
end);
end, function ()
self.customDataLoaded = false
-- TODO: Should I provide an errorCallback in this case?
print("ITEM LOAD FAILED: " .. self:GetItemID())
callbackFunction()
end);

return function()
if type(itemDataCancel) == "function" then
Expand All @@ -214,7 +230,7 @@ function CaerdonItemMixin:ContinueWithCancelOnItemLoad(callbackFunction)
local questID = tonumber(strsplit(":", linkOptions), 10);

local itemDataCancel
local itemCancel = QuestEventListener:AddCancelableCallback(questID, function ()
local itemCancel = CaerdonQuestEventListener:AddCancelableCallback(questID, function ()
-- TODO: Update things and delay callback if needed for tooltip data
local itemData = self:GetItemData()
if itemData then
Expand All @@ -226,7 +242,12 @@ function CaerdonItemMixin:ContinueWithCancelOnItemLoad(callbackFunction)
self.customDataLoaded = true
callbackFunction()
end
end);
end, function ()
self.customDataLoaded = false
-- TODO: Should I provide an errorCallback in this case?
print("QUEST ITEM LOAD FAILED: " .. self:GetItemID())
callbackFunction()
end);

return function()
if type(itemDataCancel) == "function" then
Expand Down
Loading

0 comments on commit b231f43

Please sign in to comment.