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()
22 for k
, v
in pairs(coroutine_time_used
) do
23 table.insert(sortable
, {name
= k
, amount
= v
})
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
))
29 QuestHelper
:TextOut(string.format("%s: %f", QuestHelper
:HighlightText("poweron"), GetTime() - coroutine_power_up
))
32 local last_stack
= nil
34 local GetTime
= GetTime
36 function QH_Timeslice_PushUnyieldable()
37 unyieldable
= unyieldable
+ 1
39 function QH_Timeslice_PopUnyieldable()
40 unyieldable
= unyieldable
- 1
41 QuestHelper
: Assert(unyieldable
>= 0)
43 function QH_Timeslice_Yield()
44 if unyieldable
> 0 then return end
45 if coroutine_running
then
46 -- Check if we've run our alotted time
47 yield_ct
= yield_ct
+ 1
48 if GetTime() > coroutine_stop_time
then
49 local sti
= debugstack(2, 5, 5) -- string.gsub(debugstack(2, 1, 1), "\n.*", "")
50 if qh_loud_and_annoying
and GetTime() > coroutine_stop_time
+ 0.0015 then
51 print(yield_ct
, (GetTime() - coroutine_stop_time
) * 1000, "took too long", sti
, "------ from", last_stack
, "------")
54 -- As a safety, reset stop time to 0. If somehow we fail to set it next time,
55 -- we'll be sure to yield promptly.
56 coroutine_stop_time
= 0
64 function QH_Timeslice_Bonus(quantity
)
65 if coroutine_verbose
then QuestHelper
:TextOut(string.format("timeslice: %d bonus", quantity
)) end
66 coroutine_route_pass
= coroutine_route_pass
+ quantity
78 local bumped_priority
= {}
80 function QH_Timeslice_Add(workfunc
, name
)
81 QuestHelper
: Assert(workfunc
)
82 QuestHelper
: Assert(name
)
83 local priority
= prioritize
[name
] and prioritize
[name
][1] or 0
84 local sharding
= prioritize
[name
] and prioritize
[name
][2] or 1
85 if coroutine_verbose
then QuestHelper
:TextOut(string.format("timeslice: %s added (%s, %d)", name
, tostring(workfunc
), priority
)) end
86 local ncoro
= coroutine
.create(workfunc
)
87 QuestHelper
: Assert(ncoro
)
88 table.insert(coroutine_list
, {priority
= priority
, sharding
= sharding
, name
= name
, coro
= ncoro
, active
= true})
91 function QH_Timeslice_Toggle(name
, flag
)
92 --if coroutine_verbose then QuestHelper:TextOut(string.format("timeslice: %s toggled to %s", name, tostring(not not flag))) end
93 for _
, v
in pairs(coroutine_list
) do
94 if v
.name
== name
then v
.active
= flag
end
100 function QH_Timeslice_Doneinit()
103 QuestHelper
:TextOut("Done with initialization step")
106 QuestHelper
.loading_main
= nil
107 QuestHelper
.loading_flightpath
= nil
108 QuestHelper
.loading_preroll
= nil
110 collectgarbage("collect") -- fuuuuuck youuuuuuuu
116 function QH_Timeslice_Bump(type, newpri
)
117 bumped_priority
[type] = newpri
119 function QH_Timeslice_Bump_Reset(type)
120 bumped_priority
[type] = nil
123 local startacu
= GetTime()
127 --[[function qhtacureset()
133 local thrown_away_excess
= 0
136 function QH_Timeslice_Work(time_used
, time_this_frame
, bonus_time
, verbose
)
137 -- There's probably a better way to do this, but. Eh. Lua.
138 QuestHelper
: Assert(unyieldable
== 0)
143 for k
, v
in pairs(coroutine_list
) do
145 --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
146 local pri
= bumped_priority
[v
.name
] or v
.priority
147 if (not v
.sharding
or bit
.mod(time(), v
.sharding
) == 0) and (not coro
or (pri
> cpri
)) then
156 --if coroutine_verbose then QuestHelper:TextOut(string.format("timeslice: %s running", coro.name)) end
158 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.
159 coroutine_list
[key
] = nil
161 QuestHelper
: Assert(coroutine
.status(coro
.coro
) ~= "dead")
163 local slicefactor
= (QuestHelper_Pref
.hide
and 0.01 or (QuestHelper_Pref
.perf_scale_2
* math
.min(coroutine_route_pass
, 5)))
164 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
165 local time_to_use
= slicefactor
* time_this_frame
* 0.075 -- We want to use 7.5% of the system CPU
166 if InCombatLockdown() then time_to_use
= time_to_use
/ 5 end
167 local coroutine_intended_stop_time
= time_to_use
168 coroutine_stop_time
= coroutine_intended_stop_time
- coroutine_time_exceeded
- time_used
+ bonus_time
169 coroutine_route_pass
= coroutine_route_pass
- 5
170 if coroutine_route_pass
<= 0 then coroutine_route_pass
= 1 end
173 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
))
174 print(string.format("thrown_away_excess %f, used %f, maxi %f", thrown_away_excess
, total_used
, slicefactor
* 0.075 * 5))
177 local start
= GetTime()
178 coroutine_stop_time
, coroutine_intended_stop_time
= coroutine_stop_time
+ start
, coroutine_intended_stop_time
+ start
179 local state
, err
= true, nil -- default values for "we're fine"
181 totalacu
= totalacu
+ math
.max(0, coroutine_stop_time
- start
)
182 --[[if lacu + 0.1 < totalacu then
184 print(string.format("%f realtime, %f runtime, %f%%", start - startacu, totalacu, (totalacu / (start - startacu)) * 100))
187 if start
< coroutine_stop_time
then -- We don't want to just return on failure because we want to credit the exceeded time properly.
188 coroutine_running
= true
189 QuestHelper
: Assert(unyieldable
== 0)
190 state
, err
= coroutine
.resume(coro
.coro
)
191 QuestHelper
: Assert(unyieldable
== 0)
192 coroutine_running
= false
194 local stop
= GetTime()
196 local total
= stop
- start
197 local coroutine_this_cycle_exceeded
= stop
- coroutine_intended_stop_time
-- may be either positive or negative
199 local origcte
= coroutine_time_exceeded
+ coroutine_this_cycle_exceeded
200 coroutine_time_exceeded
= min(origcte
, slicefactor
* 0.075 * 5) -- honestly, waiting for more than five seconds to recover from a stutter is just dumb
201 thrown_away_excess
= thrown_away_excess
+ (origcte
- coroutine_time_exceeded
)
202 total_used
= total_used
+ total
204 coroutine_time_used
[coro
.name
] = (coroutine_time_used
[coro
.name
] or 0) + total
207 if coroutine_verbose
then QuestHelper
:TextOut(string.format("timeslice: %s errored", coro
.name
)) end
208 QuestHelper_ErrorCatcher_ExplicitError(true, err
, "", string.format("(Coroutine error in %s)\n", coro
.name
))
211 QuestHelper
: Assert(coro
.coro
)
212 if coroutine
.status(coro
.coro
) == "dead" then
213 if coroutine_verbose
then QuestHelper
:TextOut(string.format("timeslice: %s complete", coro
.name
)) end
214 coroutine_list
[key
] = nil
217 if coroutine_verbose
then QuestHelper
:TextOut(string.format("timeslice: no available tasks")) end
221 function QH_Timeslice_Increment(quantity
, name
)
222 local an
= "(nc) " .. name
223 coroutine_time_used
[an
] = (coroutine_time_used
[an
] or 0) + quantity