1 QuestHelper_File
["recycle.lua"] = "Development Version"
2 QuestHelper_Loadtime
["recycle.lua"] = GetTime()
6 "Zorba, why are you doing manual memory allocation in Lua? That's incredibly stupid! You, as well, must be incredibly stupid. Why are you so stupid?"
8 Yeah. Yeah, that's what I thought too. It turns out things are more complicated than I thought.
10 There's a few good reasons to do something this ugly.
12 First off, it makes it real, real easy to track where allocations are going. That's what the whole "tag" thing is for - all created tables are tagged. This is useful. This is very, very useful, as it lets me track down memory leaks extraordinarily easily. This is also obsoleted slightly by the technique in bst_pre.lua (check it out.)
14 Second, it deals far better with table churn. I don't know if this is a WoW issue, but in WoW at least, tables can hang around for quite a while before getting garbage-collected. If you're making a dozen tables per frame, you can rapidly eat 10 or 20 megs of RAM that you're not actually using. Rigging an explicit thing like this allows you to recycle those tables instead of just wasting them.
16 It's ugly. I'm not arguing that. But it really, really helps.
20 QuestHelper
.used_tables
= 0
21 QuestHelper
.free_tables
= setmetatable({}, {__mode
="k"}) -- But Zorba, the only thing you're storing here is unused table values! Yeah, that's right, *unused* table values, if the garbage collector wants to have a field day, go for it
23 local function crashy(tab
, name
)
24 QuestHelper
: Assert(false, "Tried to access " .. name
.. " from released table")
27 local unused_meta
= {__index
=crashy
, __newindex
=crashy
}
29 QuestHelper
.used_textures
= 0
30 QuestHelper
.free_textures
= {}
32 QuestHelper
.used_text
= 0
33 QuestHelper
.free_text
= {}
35 QuestHelper
.used_frames
= 0
36 QuestHelper
.free_frames
= {}
38 -- This little table rigs up a basic typing system to assist with debugging. It has weak-reference keys so it shouldn't ever lead to leaks of any kind.
39 QuestHelper
.recycle_tabletyping
= setmetatable({}, {__mode
="k"})
41 function QuestHelper
:CreateTable(tag)
42 local tbl
= next(self
.free_tables
)
43 self
.used_tables
= self
.used_tables
+ 1
48 self
.free_tables
[tbl
] = nil
49 setmetatable(tbl
, nil)
52 tag = tag or string.gsub(debugstack(2, 1, 1), "\n.*", "")
54 if QH_RegisterTable
then QH_RegisterTable(tbl
, true, tag) end
55 self
.recycle_tabletyping
[tbl
] = tag
60 local release_cycle
= 0
61 function QuestHelper
:ReleaseTable(tbl
)
62 QuestHelper
: Assert(type(tbl
) == "table")
63 QuestHelper
: Assert(not self
.free_tables
[tbl
])
65 for key
in pairs(tbl
) do
69 self
.used_tables
= self
.used_tables
- 1
70 self
.recycle_tabletyping
[tbl
] = nil
72 if QH_RegisterTable
or self
.used_tables
< 500 or release_cycle
< 100 then -- this is actually plenty. you'd be horrified how much table churn there is in this thing
73 self
.free_tables
[setmetatable(tbl
, unused_meta
)] = true
74 release_cycle
= release_cycle
+ 1
76 self
.recycle_tabletyping
[tbl
] = "((released))"
81 function QuestHelper
:RecycleClear()
82 local ct
= QuestHelper
:TableSize(QuestHelper
.free_tables
)
83 QuestHelper
.free_tables
= {}
87 function QuestHelper
:DumpTableTypeFrequencies(silent
)
89 for k
, v
in pairs(self
.recycle_tabletyping
) do
90 freq
[v
] = (freq
[v
] or 0) + 1
95 for k
, v
in pairs(freq
) do
96 table.insert(flist
, {count
=v
, name
=k
})
99 table.sort(flist
, function(a
, b
) return a
.count
< b
.count
end)
101 for k
, v
in pairs(flist
) do
102 self
:TextOut(v
.count
.. ": " .. v
.name
)
109 function QuestHelper
:CreateFrame(parent
)
110 self
.used_frames
= self
.used_frames
+ 1
111 local frame
= table.remove(self
.free_frames
)
114 frame
:SetParent(parent
)
116 frame
= CreateFrame("Button", string.format("QuestHelperFrame%d",self
.used_frames
), parent
)
119 frame
:SetFrameLevel((parent
or UIParent
):GetFrameLevel()+1)
120 frame
:SetFrameStrata("MEDIUM")
151 function QuestHelper
:ReleaseFrame(frame
)
152 assert(type(frame
) == "table")
153 for i
,t
in ipairs(self
.free_frames
) do assert(t
~= frame
) end
155 for key
in pairs(frame
) do
156 -- Remove all keys except 0, which seems to hold some special data.
162 for i
, script
in ipairs(frameScripts
) do
163 frame
:SetScript(script
, nil)
167 frame
:SetParent(QuestHelper
)
168 frame
:ClearAllPoints()
169 frame
:SetMovable(false)
170 frame
:RegisterForDrag()
171 frame
:RegisterForClicks()
172 frame
:SetBackdrop(nil)
176 self
.used_frames
= self
.used_frames
- 1
177 table.insert(self
.free_frames
, frame
)
180 function QuestHelper
:CreateText(parent
, text_str
, text_size
, text_font
, r
, g
, b
, a
)
181 self
.used_text
= self
.used_text
+ 1
182 local text
= table.remove(self
.free_text
)
185 text
:SetParent(parent
)
187 text
= parent
:CreateFontString()
190 text
:SetFont(text_font
or QuestHelper
.font
.sans
or ChatFontNormal
:GetFont(), text_size
or 12)
191 text
:SetDrawLayer("OVERLAY")
192 text
:SetJustifyH("CENTER")
193 text
:SetJustifyV("MIDDLE")
194 text
:SetTextColor(r
or 1, g
or 1, b
or 1, a
or 1)
195 text
:SetText(text_str
or "")
196 text
:SetShadowColor(0, 0, 0, 0.3)
197 text
:SetShadowOffset(1, -1)
203 function QuestHelper
:ReleaseText(text
)
204 assert(type(text
) == "table")
205 for i
,t
in ipairs(self
.free_text
) do assert(t
~= text
) end
207 for key
in pairs(text
) do
208 -- Remove all keys except 0, which seems to hold some special data.
215 text
:SetParent(UIParent
)
216 text
:ClearAllPoints()
217 self
.used_text
= self
.used_text
- 1
218 table.insert(self
.free_text
, text
)
221 function QuestHelper
:CreateTexture(parent
, r
, g
, b
, a
)
222 self
.used_textures
= self
.used_textures
+ 1
223 local tex
= table.remove(self
.free_textures
)
226 tex
:SetParent(parent
)
228 tex
= parent
:CreateTexture()
231 if not tex
:SetTexture(r
, g
, b
, a
) and
232 not tex
:SetTexture("Interface\\Icons\\Temp.blp") then
233 tex
:SetTexture(1, 0, 1, 0.5)
237 tex
:SetTexCoord(0, 1, 0, 1)
238 tex
:SetVertexColor(1, 1, 1, 1)
239 tex
:SetDrawLayer("ARTWORK")
240 tex
:SetBlendMode("BLEND")
248 function QuestHelper
:CreateIconTexture(parent
, id
)
249 local icon
= self
:CreateTexture(parent
, "Interface\\AddOns\\QuestHelper\\Art\\Icons.tga")
251 local w
, h
= 1/8, 1/8
252 local x
, y
= ((id
-1)%8)*w
, math
.floor((id
-1)/8)*h
254 icon
:SetTexCoord(x
, x
+w
, y
, y
+h
)
259 function QuestHelper
:CreateDotTexture(parent
)
260 local icon
= self
:CreateIconTexture(parent
, 13)
263 icon
:SetVertexColor(0, 0, 0, 0.35)
267 function QuestHelper
:CreateGlowTexture(parent
)
268 local tex
= self
:CreateTexture(parent
, "Interface\\Addons\\QuestHelper\\Art\\Glow.tga")
270 local angle
= math
.random()*6.28318530717958647692528676655900576839433879875021164
271 local x
, y
= math
.cos(angle
)*0.707106781186547524400844362104849039284835937688474036588339869,
272 math
.sin(angle
)*0.707106781186547524400844362104849039284835937688474036588339869
274 -- Randomly rotate the texture, so they don't all look the same.
275 tex
:SetTexCoord(x
+0.5, y
+0.5, y
+0.5, 0.5-x
, 0.5-y
, x
+0.5, 0.5-x
, 0.5-y
)
281 function QuestHelper
:ReleaseTexture(tex
)
282 assert(type(tex
) == "table")
283 for i
,t
in ipairs(self
.free_textures
) do assert(t
~= tex
) end
285 for key
in pairs(tex
) do
286 -- Remove all keys except 0, which seems to hold some special data.
293 tex
:SetParent(UIParent
)
295 self
.used_textures
= self
.used_textures
- 1
296 table.insert(self
.free_textures
, tex
)
299 QuestHelper
.recycle_active_cached_tables
= {}
300 QuestHelper
.recycle_decache_queue
= {}
302 function QuestHelper
:CacheRegister(obj
)
303 if not self
.recycle_active_cached_tables
[obj
] then
304 self
.recycle_active_cached_tables
[obj
] = true
305 table.insert(self
.recycle_decache_queue
, obj
)
309 function QuestHelper
:CacheCleanup(obj
)
310 local target
= self
.recycle_decache_queue
[1]
312 if not target
then return end
313 table.remove(self
.recycle_decache_queue
, 1)
314 self
.recycle_active_cached_tables
[target
] = nil
316 if target
.distance_cache
then
317 for k
, v
in pairs(target
.distance_cache
) do
320 self
:ReleaseTable(target
.distance_cache
)
321 target
.distance_cache
= self
:CreateTable("objective.distance_cache cleaned")
325 function QuestHelper
:DumpCacheData(obj
)
328 for k
, v
in pairs(self
.recycle_decache_queue
) do
330 if v
.distance_cache
then
331 for q
, w
in pairs(v
.distance_cache
) do
337 self
:TextOut(caches
.. " queued caches with a total of " .. cached
.. " cached items")