-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathHotPathRuntime.lua
More file actions
282 lines (237 loc) · 8.15 KB
/
HotPathRuntime.lua
File metadata and controls
282 lines (237 loc) · 8.15 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
local QuestTogether = _G.QuestTogether
QuestTogether.runtimeRestrictionTypes = QuestTogether.runtimeRestrictionTypes or {
combat = true,
encounter = true,
challenge = true,
pvp = true,
}
if not QuestTogether.GetDeferredWorkStateStore then
QuestTogether.deferredWorkState = QuestTogether.deferredWorkState or {
generations = {},
entries = {},
}
end
QuestTogether.runtimeWorkDelayByClass = QuestTogether.runtimeWorkDelayByClass or {
quest_log_drain = 0,
task_area_refresh = 0.1,
quest_snapshot_refresh = 1,
nameplate_quest_refresh = 0,
nameplate_refresh = 0,
nameplate_tint_refresh = 0.05,
nameplate_tooltip_resolve = 0,
waypoint_mutation = 0.2,
}
local function SafeText(value, fallback)
if QuestTogether and QuestTogether.SafeToString then
return QuestTogether:SafeToString(value, fallback or "")
end
local ok, textValue = pcall(tostring, value)
if ok then
return textValue
end
return fallback or ""
end
local function BuildDeferredWorkKey(workClass, key)
return tostring(workClass or "work") .. "::" .. tostring(key or "global")
end
function QuestTogether:IsRuntimeRestrictionTypeActive(restrictionType)
local restrictionTypes = Enum and Enum.AddOnRestrictionType
local restrictedActions = _G.C_RestrictedActions
if not (restrictionTypes and restrictedActions and restrictedActions.GetAddOnRestrictionState) then
return false
end
local restrictionEnum = nil
local normalizedType = type(restrictionType) == "string" and string.lower(restrictionType) or nil
if normalizedType == "combat" then
restrictionEnum = restrictionTypes.Combat
elseif normalizedType == "encounter" then
restrictionEnum = restrictionTypes.Encounter
elseif normalizedType == "challenge" then
restrictionEnum = restrictionTypes.ChallengeMode
elseif normalizedType == "pvp" then
restrictionEnum = restrictionTypes.PvPMatch
elseif normalizedType == "map" then
restrictionEnum = restrictionTypes.Map
end
if restrictionEnum == nil then
return false
end
local ok, state = pcall(restrictedActions.GetAddOnRestrictionState, restrictionEnum)
return ok and state == 2
end
function QuestTogether:IsRuntimeRestricted()
if self.API and self.API.InCombatLockdown and self.API.InCombatLockdown() then
return true
end
for restrictionType in pairs(self.runtimeRestrictionTypes or {}) do
if self:IsRuntimeRestrictionTypeActive(restrictionType) then
return true
end
end
return false
end
function QuestTogether:IsMapTooltipSensitiveStateActive()
if not (self and self.API and type(self.API.IsWorldMapVisible) == "function") then
return false
end
local ok, isVisible = pcall(self.API.IsWorldMapVisible)
return ok and isVisible and true or false
end
function QuestTogether:IsWorkBlocked(workClass)
if workClass == "waypoint_mutation" then
return self:IsRuntimeRestricted()
end
if workClass == "foreign_frame_mutation" then
return self:IsRuntimeRestricted()
end
if workClass == "nameplate_tooltip_resolve" then
if self:IsMapTooltipSensitiveStateActive() then
return true
end
return false
end
if workClass == "quest_log_drain" or workClass == "task_area_refresh" or workClass == "quest_snapshot_refresh" then
if self:IsMapTooltipSensitiveStateActive() then
return true
end
return self:IsRuntimeRestricted()
end
if workClass == "nameplate_quest_refresh" or workClass == "nameplate_refresh" or workClass == "nameplate_tint_refresh" then
return self:IsRuntimeRestricted()
end
return self:IsRuntimeRestricted()
end
function QuestTogether:ScheduleDeferredWork(workClass, key, callback, delaySeconds, reason)
if type(callback) ~= "function" then
return false
end
local state = self:GetDeferredWorkStateStore()
local workKey = BuildDeferredWorkKey(workClass, key)
local generation = (state.generations[workKey] or 0) + 1
state.generations[workKey] = generation
local entry = {
workClass = workClass,
key = key,
callback = callback,
delaySeconds = delaySeconds,
reason = reason,
generation = generation,
}
state.entries[workKey] = entry
local delayFn = self.API and self.API.Delay
local hasDelayFn = type(delayFn) == "function"
local scheduledDelay = delaySeconds
if scheduledDelay == nil then
scheduledDelay = self.runtimeWorkDelayByClass[workClass] or 0
end
local function runEntry()
local liveEntry = state.entries[workKey]
if not liveEntry or liveEntry.generation ~= generation then
return
end
if not self.isEnabled then
return
end
if self:IsWorkBlocked(workClass) then
-- Keep the latest entry parked until an explicit flush or later enqueue retries it.
-- Immediate Delay stubs in tests would otherwise recurse indefinitely here.
return
end
state.entries[workKey] = nil
callback()
end
if not hasDelayFn or scheduledDelay <= 0 then
runEntry()
return true
end
delayFn(scheduledDelay, runEntry)
return true
end
function QuestTogether:RunOrDeferWork(workClass, key, callback, delaySeconds, reason)
if type(callback) ~= "function" then
return false
end
if self:IsWorkBlocked(workClass) then
self:ScheduleDeferredWork(workClass, key, callback, delaySeconds, reason)
return false
end
callback()
return true
end
function QuestTogether:FlushDeferredWork(reason)
local state = self:GetDeferredWorkStateStore()
if not state or self:IsWorkBlocked("quest_log_drain") then
return false
end
local pendingEntries = {}
for workKey, entry in pairs(state.entries or {}) do
pendingEntries[#pendingEntries + 1] = {
workKey = workKey,
entry = entry,
}
end
for index = 1, #pendingEntries do
local pending = pendingEntries[index]
local workKey = pending and pending.workKey or nil
local entry = pending and pending.entry or nil
if entry and type(entry.callback) == "function" then
self:ScheduleDeferredWork(entry.workClass, entry.key, entry.callback, 0, reason or entry.reason)
else
state.entries[workKey] = nil
end
end
return true
end
function QuestTogether:ADDON_RESTRICTION_STATE_CHANGED()
self:FlushDeferredWork("ADDON_RESTRICTION_STATE_CHANGED")
end
function QuestTogether:ScheduleQuestLogTaskDrain(reason)
return self:ScheduleDeferredWork("quest_log_drain", "quest_log_drain", function()
if self.DrainQueuedQuestLogTasks then
self:DrainQueuedQuestLogTasks()
end
end, 0, reason or "quest_log_drain")
end
function QuestTogether:ScheduleTaskAreaRefreshWork(shouldAnnounce, delaySeconds, reason)
if shouldAnnounce then
self:SetRuntimeFlag("pendingScheduledTaskAreaRefreshShouldAnnounce", true)
end
return self:ScheduleDeferredWork("task_area_refresh", "task_area_refresh", function()
local announce = self:GetRuntimeFlag("pendingScheduledTaskAreaRefreshShouldAnnounce", false) and true or false
self:SetRuntimeFlag("pendingScheduledTaskAreaRefreshShouldAnnounce", false)
if self.RefreshTaskAreaStates then
self:RefreshTaskAreaStates(announce)
end
end, delaySeconds, reason or "task_area_refresh")
end
function QuestTogether:ScheduleQuestStateRefreshWork(reason, delaySeconds)
return self:ScheduleDeferredWork("quest_snapshot_refresh", "quest_snapshot_refresh", function()
self:SetRuntimeFlag("pendingDeferredNameplateQuestStateRefresh", false)
if self.RebuildQuestSnapshotStore then
self:RebuildQuestSnapshotStore()
end
if self.RefreshNameplatesForQuestStateChange then
self:RefreshNameplatesForQuestStateChange(reason)
end
end, delaySeconds, reason or "quest_snapshot_refresh")
end
function QuestTogether:ScheduleNameplatePresentationRefresh(reason, delaySeconds)
return self:ScheduleDeferredWork("nameplate_refresh", "visible_nameplates", function()
if self.RefreshVisibleNameplates then
self:RefreshVisibleNameplates(reason)
end
end, delaySeconds, reason or "nameplate_refresh")
end
function QuestTogether:ScheduleNameplateTooltipResolution(unitToken, unitGuid, delaySeconds, reason)
local resolvedUnitToken = type(unitToken) == "string" and unitToken or "unknown"
local workKey = resolvedUnitToken
if type(unitGuid) == "string" and unitGuid ~= "" then
workKey = unitGuid
end
return self:ScheduleDeferredWork("nameplate_tooltip_resolve", workKey, function()
if not self.ResolveNameplateQuestStateForUnitToken then
return
end
self:ResolveNameplateQuestStateForUnitToken(resolvedUnitToken, unitGuid, reason)
end, delaySeconds, reason or "nameplate_tooltip_resolve")
end