update to 1.1.10
[QuestHelper.git] / timeslice.lua
blob38293826e093ca589451850c30f86812146f82b9
1 QuestHelper_File["timeslice.lua"] = "Development Version"
2 QuestHelper_Loadtime["timeslice.lua"] = GetTime()
4 local debug_output = (QuestHelper_File["timeslice.lua"] == "Development Version")
6 -- Any non-local item here is part of an available public interface.
8 local coroutine_running = false
9 local coroutine_stop_time = 0
10 local coroutine_list = {}
11 local coroutine_route_pass = 1
13 local coroutine_verbose = false
15 local coroutine_time_used = {}
16 local coroutine_power_up = GetTime()
18 local coroutine_time_exceeded = 0
20 function QH_Timeslice_DumpPerf()
21 local sortable = {}
22 for k, v in pairs(coroutine_time_used) do
23 table.insert(sortable, {name = k, amount = v})
24 end
25 table.sort(sortable, function(a, b) return a.name < b.name end)
26 for _, v in pairs(sortable) do
27 QuestHelper:TextOut(string.format("%s: %f", QuestHelper:HighlightText(v.name), v.amount))
28 end
29 QuestHelper:TextOut(string.format("%s: %f", QuestHelper:HighlightText("poweron"), GetTime() - coroutine_power_up))
30 end
32 local last_stack = nil
33 local yield_ct = 0
34 function QH_Timeslice_Yield()
35 if coroutine_running then
36 -- Check if we've run our alotted time
37 yield_ct = yield_ct + 1
38 if GetTime() > coroutine_stop_time then
39 local sti = debugstack(2, 5, 5) -- string.gsub(debugstack(2, 1, 1), "\n.*", "")
40 if qh_loud_and_annoying and GetTime() > coroutine_stop_time + 0.0015 then
41 print(yield_ct, (GetTime() - coroutine_stop_time) * 1000, "took too long", sti, "------ from", last_stack, "------")
42 end
44 -- As a safety, reset stop time to 0. If somehow we fail to set it next time,
45 -- we'll be sure to yield promptly.
46 coroutine_stop_time = 0
47 coroutine.yield()
48 last_stack = sti
49 yield_ct = 0
50 end
51 end
52 end
54 function QH_Timeslice_Bonus(quantity)
55 if coroutine_verbose then QuestHelper:TextOut(string.format("timeslice: %d bonus", quantity)) end
56 coroutine_route_pass = coroutine_route_pass + quantity
57 end
59 local prioritize = {
60 init = {100},
61 criteria = {10},
62 lzw = {-5},
63 compress = {-8, 5},
64 routing = {-10},
67 function QH_Timeslice_Add(workfunc, name)
68 QuestHelper: Assert(workfunc)
69 QuestHelper: Assert(name)
70 local priority = prioritize[name] and prioritize[name][1] or 0
71 local sharding = prioritize[name] and prioritize[name][2] or 1
72 if coroutine_verbose then QuestHelper:TextOut(string.format("timeslice: %s added (%s, %d)", name, tostring(workfunc), priority)) end
73 local ncoro = coroutine.create(workfunc)
74 QuestHelper: Assert(ncoro)
75 table.insert(coroutine_list, {priority = priority, sharding = sharding, name = name, coro = ncoro, active = true})
76 end
78 function QH_Timeslice_Toggle(name, flag)
79 --if coroutine_verbose then QuestHelper:TextOut(string.format("timeslice: %s toggled to %s", name, tostring(not not flag))) end
80 for _, v in pairs(coroutine_list) do
81 if v.name == name then v.active = flag end
82 end
83 end
85 local started = false
87 function QH_Timeslice_Doneinit()
88 if not started then
89 if debug_output then
90 QuestHelper:TextOut("Done with initialization step")
91 end
93 QuestHelper.loading_main = nil
94 QuestHelper.loading_flightpath = nil
95 QuestHelper.loading_preroll = nil
97 collectgarbage("collect") -- fuuuuuck youuuuuuuu
98 end
100 started = true
103 local startacu = GetTime()
104 local totalacu = 0
105 local lacu = 0
107 --[[function qhtacureset()
108 startacu = GetTime()
109 totalacu = 0
110 lacu = 0
111 end]]
113 local thrown_away_excess = 0
114 local total_used = 0
116 function QH_Timeslice_Work(time_used, time_this_frame, bonus_time, verbose)
117 -- There's probably a better way to do this, but. Eh. Lua.
118 coro = nil
119 key = nil
120 for k, v in pairs(coroutine_list) do
121 if v.active then
122 --if v.sharding then QuestHelper:TextOut(string.format("%d mod %d is %d, %s", time(), v.sharding, bit.mod(time(), v.sharding), tostring(bit.mod(time(), v.sharding) == 0))) end
123 if (not v.sharding or bit.mod(time(), v.sharding) == 0) and (not coro or (v.priority > coro.priority)) then
124 coro = v
125 key = k
130 if coro then
131 --if coroutine_verbose then QuestHelper:TextOut(string.format("timeslice: %s running", coro.name)) end
133 if coroutine.status(coro.coro) == "dead" then -- Someone was claiming to get an infinite loop with this. I don't see how it's possible, but this should at least fix the infinite loop.
134 coroutine_list[key] = nil
136 QuestHelper: Assert(coroutine.status(coro.coro) ~= "dead")
138 local slicefactor = (QuestHelper_Pref.hide and 0.01 or (QuestHelper_Pref.perf_scale_2 * math.min(coroutine_route_pass, 5)))
139 if not started then slicefactor = 5 * QuestHelper_Pref.perfload_scale * math.min(coroutine_route_pass, 5) end -- the init process gets much higher priority so we get done with it faster
140 local time_to_use = slicefactor * time_this_frame * 0.075 -- We want to use 7.5% of the system CPU
141 if InCombatLockdown() then time_to_use = time_to_use / 5 end
142 local coroutine_intended_stop_time = time_to_use
143 coroutine_stop_time = coroutine_intended_stop_time - coroutine_time_exceeded - time_used + bonus_time
144 coroutine_route_pass = coroutine_route_pass - 5
145 if coroutine_route_pass <= 0 then coroutine_route_pass = 1 end
147 if verbose then
148 print(string.format("time_to_use %f, time_already_used %f, bonus_time %f, coroutine_time_exceeded %f, total_time_to_use %f", time_to_use, time_used, bonus_time, coroutine_time_exceeded, coroutine_stop_time))
149 print(string.format("thrown_away_excess %f, used %f, maxi %f", thrown_away_excess, total_used, slicefactor * 0.075 * 5))
152 local start = GetTime()
153 coroutine_stop_time, coroutine_intended_stop_time = coroutine_stop_time + start, coroutine_intended_stop_time + start
154 local state, err = true, nil -- default values for "we're fine"
156 totalacu = totalacu + math.max(0, coroutine_stop_time - start)
157 --[[if lacu + 0.1 < totalacu then
158 lacu = totalacu
159 print(string.format("%f realtime, %f runtime, %f%%", start - startacu, totalacu, (totalacu / (start - startacu)) * 100))
160 end]]
162 if start < coroutine_stop_time then -- We don't want to just return on failure because we want to credit the exceeded time properly.
163 coroutine_running = true
164 state, err = coroutine.resume(coro.coro)
165 coroutine_running = false
167 local stop = GetTime()
169 local total = stop - start
170 local coroutine_this_cycle_exceeded = stop - coroutine_intended_stop_time -- may be either positive or negative
172 local origcte = coroutine_time_exceeded + coroutine_this_cycle_exceeded
173 coroutine_time_exceeded = min(origcte, slicefactor * 0.075 * 5) -- honestly, waiting for more than five seconds to recover from a stutter is just dumb
174 thrown_away_excess = thrown_away_excess + (origcte - coroutine_time_exceeded)
175 total_used = total_used + total
177 coroutine_time_used[coro.name] = (coroutine_time_used[coro.name] or 0) + total
179 if not state then
180 if coroutine_verbose then QuestHelper:TextOut(string.format("timeslice: %s errored", coro.name)) end
181 QuestHelper_ErrorCatcher_ExplicitError(true, err, "", string.format("(Coroutine error in %s)\n", coro.name))
184 QuestHelper: Assert(coro.coro)
185 if coroutine.status(coro.coro) == "dead" then
186 if coroutine_verbose then QuestHelper:TextOut(string.format("timeslice: %s complete", coro.name)) end
187 coroutine_list[key] = nil
189 else
190 if coroutine_verbose then QuestHelper:TextOut(string.format("timeslice: no available tasks")) end
194 function QH_Timeslice_Increment(quantity, name)
195 local an = "(nc) " .. name
196 coroutine_time_used[an] = (coroutine_time_used[an] or 0) + quantity