ZO_Refresh = ZO_InitializingObject:Subclass() function ZO_Refresh:Initialize() self.refreshGroups = {} self.currentlyRefreshing = false end function ZO_Refresh:AddRefreshGroup(refreshGroup, data) data.dirtySingles = {} data.wasShown = false self.refreshGroups[refreshGroup] = data end function ZO_Refresh:RefreshAll(refreshGroup) local refreshData = self.refreshGroups[refreshGroup] refreshData.allDirty = true end function ZO_Refresh:RefreshSingle(refreshGroup, ...) local refreshData = self.refreshGroups[refreshGroup] if(refreshData.allDirty) then return end local numInList = select("#", ...) for _, data in ipairs(refreshData.dirtySingles) do local alreadyHaveData = true for i = 1, numInList do if(data[i] ~= select(i, ...)) then alreadyHaveData = false break end end if(alreadyHaveData) then return end end table.insert(refreshData.dirtySingles, {...}) end function ZO_Refresh:UpdateRefreshGroups() if self.currentlyRefreshing then return end self.currentlyRefreshing = true for refreshGroup, refreshData in pairs(self.refreshGroups) do if(refreshData.IsShown) then local dataShown = refreshData.IsShown() if(dataShown ~= refreshData.wasShown) then refreshData.wasShown = dataShown refreshData.RefreshAll() refreshData.allDirty = false ZO_ClearNumericallyIndexedTable(refreshData.dirtySingles) end end if(refreshData.allDirty or #refreshData.dirtySingles > 0) then if(refreshData.IsShown == nil or refreshData.IsShown()) then if(refreshData.allDirty) then refreshData.RefreshAll() else for _, singleData in ipairs(refreshData.dirtySingles) do refreshData.RefreshSingle(unpack(singleData)) end end refreshData.allDirty = false ZO_ClearNumericallyIndexedTable(refreshData.dirtySingles) end end end self.currentlyRefreshing = false end --Represents a set of nested refresh operations. Each refresh operation should do what the one below it did plus some additional work. The main usecase for this is rebuilding an entire list from scratch vs. just refreshing visible controls. ZO_OrderedRefreshGroupManager = ZO_InitializingObject:Subclass() function ZO_OrderedRefreshGroupManager:Initialize() self.groupsWaitingForPerFrameClean = {} self.OnUpdateCallback = function() self:OnUpdate() end end function ZO_OrderedRefreshGroupManager:AddGroupForPerFrameClean(addGroup) if next(self.groupsWaitingForPerFrameClean) == nil then EVENT_MANAGER:RegisterForUpdate("ZO_OrderedRefreshGroupManager", 0, self.OnUpdateCallback) else for _, group in ipairs(self.groupsWaitingForPerFrameClean) do if group == addGroup then return end end end table.insert(self.groupsWaitingForPerFrameClean, addGroup) end function ZO_OrderedRefreshGroupManager:OnUpdate() for _, group in ipairs(self.groupsWaitingForPerFrameClean) do group:TryClean() end ZO_ClearNumericallyIndexedTable(self.groupsWaitingForPerFrameClean) EVENT_MANAGER:UnregisterForUpdate("ZO_OrderedRefreshGroupManager") end ORDERED_REFRESH_GROUP_MANAGER = ZO_OrderedRefreshGroupManager:New() ZO_OrderedRefreshGroup = ZO_InitializingObject:Subclass() ZO_ORDERED_REFRESH_GROUP_AUTO_CLEAN_IMMEDIATELY = "immediately" ZO_ORDERED_REFRESH_GROUP_AUTO_CLEAN_PER_FRAME = "perFrame" ZO_ORDERED_REFRESH_GROUP_AUTO_CLEAN_NEVER = "never" function ZO_OrderedRefreshGroup:Initialize(autoCleanMode) self.activeOrActiveFunction = true self.dirtyStates = {} self.autoCleanMode = autoCleanMode or ZO_ORDERED_REFRESH_GROUP_AUTO_CLEAN_IMMEDIATELY end --Clean is only allowed to happen when the group is active function ZO_OrderedRefreshGroup:SetActive(activeOrActiveFunction) if activeOrActiveFunction ~= self.activeOrActiveFunction then self.activeOrActiveFunction = activeOrActiveFunction self:TryScheduleClean() end end function ZO_OrderedRefreshGroup:IsActive() if type(self.activeOrActiveFunction) == "function" then return self.activeOrActiveFunction() else return self.activeOrActiveFunction end end --Order goes from 1 to N where 1 should be a full update and successive numbers are smaller and smaller updates function ZO_OrderedRefreshGroup:AddDirtyState(name, cleanFunction) table.insert(self.dirtyStates, { name = name, order = #self.dirtyStates + 1, cleanFunction = cleanFunction, }) end function ZO_OrderedRefreshGroup:IsDirty() return self.currentDirtyState ~= nil end --A function that prevents a state from being dirtied if the function returns false. Receives everything passed to MarkDirty after name. Can be used to filter out certain events. function ZO_OrderedRefreshGroup:SetDirtyStateGuard(name, guardFunction) local dirtyState = self:GetDirtyState(name) dirtyState.guardFunction = guardFunction end function ZO_OrderedRefreshGroup:GetDirtyState(name) for _, dirtyState in ipairs(self.dirtyStates) do if dirtyState.name == name then return dirtyState end end end --Marks the state dirty and tries to clean it immediately function ZO_OrderedRefreshGroup:MarkDirty(name, ...) local newDirtyState = self:GetDirtyState(name) if newDirtyState.guardFunction then if not newDirtyState.guardFunction(...) then return end end if self.currentDirtyState then --if we already have a dirty state that encompasses the new one then ignore it if newDirtyState.order < self.currentDirtyState.order then self.currentDirtyState = newDirtyState end else self.currentDirtyState = newDirtyState end self:TryScheduleClean() end function ZO_OrderedRefreshGroup:TryScheduleClean() if self:IsDirty() and self:IsActive() then if self.autoCleanMode == ZO_ORDERED_REFRESH_GROUP_AUTO_CLEAN_IMMEDIATELY then self:TryClean() elseif self.autoCleanMode == ZO_ORDERED_REFRESH_GROUP_AUTO_CLEAN_PER_FRAME then ORDERED_REFRESH_GROUP_MANAGER:AddGroupForPerFrameClean(self) end end end --This function can be called manually to clean dirty state that happended when the group wasn't active function ZO_OrderedRefreshGroup:TryClean() if self:IsDirty() and self:IsActive() then self.currentDirtyState.cleanFunction() self.currentDirtyState = nil end end