Merge branch 'master' into achievements
[QuestHelper.git] / collect.lua
blob0fb57b66242d03b157a06da0e544e1301f1ab7cd
1 QuestHelper_File["collect.lua"] = "Development Version"
2 QuestHelper_Loadtime["collect.lua"] = GetTime()
4 local debug_output = false
5 if QuestHelper_File["collect.lua"] == "Development Version" then debug_output = true end
7 local QuestHelper_Collector_Version_Current = 8
9 QuestHelper_Collector = {}
10 QuestHelper_Collector_Version = QuestHelper_Collector_Version_Current
12 local OnUpdateRegistrar = {}
13 local TooltipRegistrar = {}
15 local function OnUpdateHookRegistrar(func)
16 QuestHelper: Assert(func)
17 table.insert(OnUpdateRegistrar, func)
18 end
20 local suppress = false
22 -- real tooltips don't use this function
23 local SetTextScript = GameTooltip.SetText
24 GameTooltip.SetText = function (...)
25 suppress = true
26 SetTextScript(...)
27 suppress = false
28 end
30 local function CollectTooltippery(self)
31 if not self then self = GameTooltip end
33 for k, v in pairs(TooltipRegistrar) do
34 v(self)
35 end
37 -- anything past here is not my fault
38 end
40 local ottsu = GameTooltip:GetScript("OnTooltipSetUnit")
41 QH_Hook(GameTooltip, "OnTooltipSetUnit", function (self, ...)
42 CollectTooltippery(self)
43 if ottsu then return QH_Hook_NotMyFault(ottsu, self, ...) end
44 end, "collection OnTooltipSetUnit")
46 local ottsi = GameTooltip:GetScript("OnTooltipSetItem")
47 QH_Hook(GameTooltip, "OnTooltipSetItem", function (self, ...)
48 CollectTooltippery(self)
49 if ottsi then return QH_Hook_NotMyFault(ottsi, self, ...) end
50 end, "collection OnTooltipSetItem")
52 local ottsh = GameTooltip:GetScript("OnShow")
53 QH_Hook(GameTooltip, "OnShow", function (self, ...)
54 CollectTooltippery(self)
55 if ottsh then return QH_Hook_NotMyFault(ottsh, self, ...) end
56 end, "collection OnShow")
59 local function TooltipHookRegistrar(func)
60 QuestHelper: Assert(func)
61 table.insert(TooltipRegistrar, func)
62 end
64 local API = {
65 Registrar_OnUpdateHook = OnUpdateHookRegistrar,
66 Registrar_TooltipHook = TooltipHookRegistrar,
67 Callback_Location_Raw = function () return QuestHelper:Location_RawRetrieve() end,
68 Callback_Location_Absolute = function () return QuestHelper:Location_AbsoluteRetrieve() end,
71 -- We do these early, because some things that aren't in collect may rely on these. Yes I realize that avoiding this was one of my main goals in the separate collect system, shut up go away I hate you (in all seriousness: crunch mode + lazy = some nasty bits.)
72 -- TODO: Make a common collect/mainmodule system, then rely on that better.
73 QH_Collect_Util_Init(nil, API) -- Some may actually add their own functions to the API, and should go first. There's no real formalized order, I just know which depend on others, and it's heavily asserted so it will break if it goes in the wrong order.
74 QH_Collect_Merger_Init(nil, API)
75 QH_Collect_Bitstream_Init(nil, API)
76 QH_Collect_Location_Init(nil, API)
77 QH_Collect_Patterns_Init(nil, API)
78 QH_Collect_Notifier_Init(nil, API)
79 QH_Collect_Spec_Init(nil, API)
80 QH_Collect_LZW_Init(nil, API)
82 local CompressCollection
84 function QH_Collector_Init()
85 QH_Collector_UpgradeAll(QuestHelper_Collector)
87 for _, v in pairs(QuestHelper_Collector) do
88 if not v.modified then v.modified = time() - 7 * 24 * 60 * 60 end -- eugh. Yeah, we set it to be a week ago. It's pretty grim.
89 end
91 QuestHelper_Collector_Version = QuestHelper_Collector_Version_Current
93 local sig = string.format("%s on %s/%s/%d", GetAddOnMetadata("QuestHelper", "Version"), GetBuildInfo(), GetLocale(), QuestHelper:PlayerFaction())
94 local sig_altfaction = string.format("%s on %s/%s/%d", GetAddOnMetadata("QuestHelper", "Version"), GetBuildInfo(), GetLocale(), (QuestHelper:PlayerFaction() == 1) and 2 or 1)
95 if not QuestHelper_Collector[sig] or QuestHelper_Collector[sig].compressed then QuestHelper_Collector[sig] = {version = QuestHelper_Collector_Version} end -- fuckin' bullshit, man
96 local QHCData = QuestHelper_Collector[sig]
97 QuestHelper: Assert(not QHCData.compressed)
98 QuestHelper: Assert(QHCData.version == QuestHelper_Collector_Version_Current)
99 QHCData.modified = time()
101 QH_Collect_Achievement_Init(QHCData, API)
102 QH_Collect_Traveled_Init(QHCData, API)
103 QH_Collect_Zone_Init(QHCData, API)
104 QH_Collect_Monster_Init(QHCData, API)
105 QH_Collect_Item_Init(QHCData, API)
106 QH_Collect_Object_Init(QHCData, API)
107 QH_Collect_Flight_Init(QHCData, API)
108 QH_Collect_Quest_Init(QHCData, API)
109 QH_Collect_Warp_Init(QHCData, API)
111 QH_Collect_Loot_Init(QHCData, API)
112 QH_Collect_Equip_Init(QHCData, API)
113 QH_Collect_Merchant_Init(QHCData, API)
115 if not QHCData.realms then QHCData.realms = {} end
116 QHCData.realms[GetRealmName()] = (QHCData.realms[GetRealmName()] or 0) + 1 -- I'm not entirely sure why I'm counting
118 if false then -- this will be disabled in most public releases, or set to a very rare probabalistic thing
119 if not QHCData.routing_dump then QHCData.routing_dump = {} end
120 local nt = {}
121 table.insert(QHCData.routing_dump, nt)
122 QH_Collect_Routing_Dump = nt
125 do -- Clean some stuff up!
126 local obliterate = {}
127 for k, v in pairs(QuestHelper_Collector) do
128 if not v.modified or v.modified + 30 * 24 * 60 * 60 < GetTime() then
129 table.insert(obliterate, k)
133 for _, v in ipairs(obliterate) do
134 QuestHelper_Collector[v] = nil
138 -- So, why do we delay it?
139 -- It's simple. People are gonna update to this version, and then they're going to look at the memory usage. Then they will panic because omg this version uses so much more memory, I bet that will somehow hurt my framerates in a way which is not adequately explained!
140 -- So instead, we just wait half an hour before compressing. Compression will still get done, and I won't have to deal with panicked comments about how bloated QH has gotten.
141 -- addendum: yeah naturally I'm getting all sorts of panicked comments about how bloated qh has gotten, sigh
142 API.Utility_Notifier(GetTime() + (debug_output and 0 or (30 * 60)), function() CompressCollection(QHCData, QuestHelper_Collector[sig_altfaction], API.Utility_Merger, API.Utility_LZW.Compress) end)
145 QH_OnUpdate(function ()
146 local tstart = GetTime()
147 for _, v in pairs(OnUpdateRegistrar) do
150 QH_Timeslice_Increment(GetTime() - tstart, "collect_update")
151 end)
155 --- I've tossed the compression stuff down here just 'cause I don't feel like making an entire file for it (even though I probably should.)
157 local noncompressible = {
158 modified = true,
159 version = true,
162 local squarify
164 local seritem
166 local serializers = {
167 ["nil"] = function(item, add)
168 add("nil")
169 end,
170 ["number"] = function(item, add)
171 add(tostring(item))
172 end,
173 ["string"] = function(item, add)
174 add(string.format("%q", item))
175 end,
176 ["boolean"] = function(item, add)
177 add(item and "true" or "false")
178 end,
179 ["table"] = function(item, add)
180 add("{")
181 local first = true
182 for k, v in pairs(item) do
183 if not first then add(",") end
184 first = false
185 add("[")
186 seritem(k, add)
187 add("]=")
188 seritem(v, add)
190 add("}")
191 end,
194 seritem = function(item, add)
195 QH_Timeslice_Yield()
196 serializers[type(item)](item, add)
199 local function DoCompress(item, merger, comp)
200 if debug_output then QuestHelper: TextOut("Item condensing") end
201 local ts = GetTime()
203 local target = {}
204 for k, v in pairs(item) do
205 if not noncompressible[k] then
206 target[k] = v
210 local mg = {}
211 seritem(target, function(dat) merger.Add(mg, dat) end)
213 local tg = merger.Finish(mg)
214 if debug_output then QuestHelper: TextOut(string.format("Item condensed to %d bytes, %f taken so far", #tg, GetTime() - ts)) end
215 mg = nil
217 local cmp = {}
218 local cmptot = 0
220 local doublecheck = ""
221 for chunk = 1, #tg, 1048576 do
222 local fragment = tg:sub(chunk, chunk + 1048575)
223 doublecheck = doublecheck .. fragment
224 local ite = comp(fragment, 256, 8)
225 cmptot = cmptot + #ite
226 table.insert(cmp, ite)
228 QuestHelper: Assert(doublecheck == tg)
230 if #cmp == 1 then cmp = cmp[1] end
232 for k, v in pairs(target) do
233 if not noncompressible[k] then
234 item[k] = nil
237 item.compressed = cmp
239 if debug_output then QuestHelper: TextOut(string.format("Item compressed to %d bytes in %d shards (previously %d), %f taken", cmptot, type(cmp) == "table" and #cmp or 1, #tg, GetTime() - ts)) end
242 CompressCollection = function(active, active2, merger, comp)
243 for _, v in pairs(QuestHelper_Collector) do
244 if v ~= active and v ~= active2 and not v.compressed then
245 QH_Timeslice_Add(function ()
246 DoCompress(v, merger, comp)
247 CompressCollection(active, active2, merger, comp)
248 end, "compress")
249 break