try to track this thing down better
[QuestHelper.git] / timeslice.lua
blob57a6da783c55d8b75823ee7f18bf11d61e3a2d47
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 function QH_Timeslice_Yield()
33 if coroutine_running then
34 -- Check if we've run our alotted time
35 if GetTime() > coroutine_stop_time then
36 -- As a safety, reset stop time to 0. If somehow we fail to set it next time,
37 -- we'll be sure to yield promptly.
38 coroutine_stop_time = 0
39 coroutine.yield()
40 end
41 end
42 end
44 function QH_Timeslice_Bonus(quantity)
45 if coroutine_verbose then QuestHelper:TextOut(string.format("timeslice: %d bonus", quantity)) end
46 coroutine_route_pass = coroutine_route_pass + quantity
47 end
49 local prioritize = {
50 init = {100},
51 criteria = {10},
52 lzw = {-5},
53 compress = {-8, 5},
54 routing = {-10},
57 function QH_Timeslice_Add(workfunc, name)
58 QuestHelper: Assert(workfunc)
59 QuestHelper: Assert(name)
60 local priority = prioritize[name] and prioritize[name][1] or 0
61 local sharding = prioritize[name] and prioritize[name][2] or 1
62 if coroutine_verbose then QuestHelper:TextOut(string.format("timeslice: %s added (%s, %d)", name, tostring(workfunc), priority)) end
63 local ncoro = coroutine.create(workfunc)
64 QuestHelper: Assert(ncoro)
65 table.insert(coroutine_list, {priority = priority, sharding = sharding, name = name, coro = ncoro, active = true})
66 end
68 function QH_Timeslice_Toggle(name, flag)
69 --if coroutine_verbose then QuestHelper:TextOut(string.format("timeslice: %s toggled to %s", name, tostring(not not flag))) end
70 for _, v in pairs(coroutine_list) do
71 if v.name == name then v.active = flag end
72 end
73 end
75 local started = false
77 function QH_Timeslice_Doneinit()
78 if not started then
79 if debug_output then
80 QuestHelper:TextOut("Done with initialization step")
81 end
83 QuestHelper.loading_main = nil
84 QuestHelper.loading_flightpath = nil
85 QuestHelper.loading_preroll = nil
87 collectgarbage("collect") -- fuuuuuck youuuuuuuu
88 end
90 started = true
91 end
93 function QH_Timeslice_Work()
94 -- There's probably a better way to do this, but. Eh. Lua.
95 coro = nil
96 key = nil
97 for k, v in pairs(coroutine_list) do
98 if v.active then
99 --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
100 if (not v.sharding or bit.mod(time(), v.sharding) == 0) and (not coro or (v.priority > coro.priority)) then
101 coro = v
102 key = k
107 if coro then
108 --if coroutine_verbose then QuestHelper:TextOut(string.format("timeslice: %s running", coro.name)) end
110 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.
111 coroutine_list[key] = nil
112 QuestHelper: Assert(coroutine.status(coro.coro) ~= "dead")
115 local slicefactor = (QuestHelper_Pref.hide and 0.01 or (QuestHelper_Pref.perf_scale * math.min(coroutine_route_pass, 5)))
116 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
117 local coroutine_intended_stop_time = GetTime() + 2e-3 * slicefactor
118 coroutine_stop_time = coroutine_intended_stop_time - coroutine_time_exceeded
119 coroutine_route_pass = coroutine_route_pass - 5
120 if coroutine_route_pass <= 0 then coroutine_route_pass = 1 end
122 local start = GetTime()
123 local state, err = true, nil -- default values for "we're fine"
124 if start < coroutine_stop_time then -- We don't want to just return on failure because we want to credit the exceeded time properly.
125 coroutine_running = true
126 state, err = coroutine.resume(coro.coro)
127 coroutine_running = false
129 local total = GetTime() - start
131 local coroutine_this_cycle_exceeded = GetTime() - coroutine_intended_stop_time -- may be either positive or negative
132 coroutine_time_exceeded = coroutine_time_exceeded + coroutine_this_cycle_exceeded
134 coroutine_time_used[coro.name] = (coroutine_time_used[coro.name] or 0) + total
136 if not state then
137 if coroutine_verbose then QuestHelper:TextOut(string.format("timeslice: %s errored", coro.name)) end
138 QuestHelper_ErrorCatcher_ExplicitError(true, err, "", string.format("(Coroutine error in %s)\n", coro.name))
141 QuestHelper: Assert(coro.coro)
142 if coroutine.status(coro.coro) == "dead" then
143 if coroutine_verbose then QuestHelper:TextOut(string.format("timeslice: %s complete", coro.name)) end
144 coroutine_list[key] = nil
146 else
147 if coroutine_verbose then QuestHelper:TextOut(string.format("timeslice: no available tasks")) end
151 function QH_Timeslice_Increment(quantity, name)
152 local an = "(nc) " .. name
153 coroutine_time_used[an] = (coroutine_time_used[an] or 0) + quantity