1
0
mirror of https://github.com/Refactorio/RedMew.git synced 2024-12-12 10:04:40 +02:00
RedMew/utils/task.lua
James Gillham 7c0867d08b Fix PriorityQueue to no longer store function in global.
Changed the PriorityQueue to store the comparator function in a metatable to avoid serializing the comparator function to the global table.
This means that the PriorityQueue has to be restored on load.
Plus side is we can use comparators that are closures.
2020-05-29 12:20:49 +01:00

160 lines
5.4 KiB
Lua

-- Threading simulation module
-- Task.sleep()
-- @author Valansch and Grilledham
-- github: https://github.com/Refactorio/RedMew
-- ======================================================= --
local Queue = require 'utils.queue'
local PriorityQueue = require 'utils.priority_queue'
local Event = require 'utils.event'
local Token = require 'utils.token'
local ErrorLogging = require 'utils.error_logging'
local Global = require 'utils.global'
local floor = math.floor
local log10 = math.log10
local Token_get = Token.get
local pcall = pcall
local Queue_peek = Queue.peek
local Queue_pop = Queue.pop
local Queue_push = Queue.push
local PriorityQueue_peek = PriorityQueue.peek
local PriorityQueue_pop = PriorityQueue.pop
local PriorityQueue_push = PriorityQueue.push
local Task = {}
local function comparator(a, b)
return a.time < b.time
end
local callbacks = PriorityQueue.new(comparator)
local task_queue = Queue.new()
local primitives = {
next_async_callback_time = -1,
total_task_weight = 0,
task_queue_speed = 1,
task_per_tick = 1
}
Global.register(
{callbacks = callbacks, task_queue = task_queue, primitives = primitives},
function(tbl)
callbacks = tbl.callbacks
task_queue = tbl.task_queue
primitives = tbl.primitives
PriorityQueue.load(callbacks, comparator)
end
)
local function get_task_per_tick(tick)
if tick % 300 == 0 then
local size = primitives.total_task_weight
local task_per_tick = floor(log10(size + 1)) * primitives.task_queue_speed
if task_per_tick < 1 then
task_per_tick = 1
end
primitives.task_per_tick = task_per_tick
return task_per_tick
end
return primitives.task_per_tick
end
local function on_tick()
local tick = game.tick
for i = 1, get_task_per_tick(tick) do
local task = Queue_peek(task_queue)
if task ~= nil then
-- result is error if not success else result is a boolean for if the task should stay in the queue.
local success, result = pcall(Token_get(task.func_token), task.params)
if not success then
if _DEBUG then
error(result)
else
log(result)
ErrorLogging.generate_error_report(result)
end
Queue_pop(task_queue)
primitives.total_task_weight = primitives.total_task_weight - task.weight
elseif not result then
Queue_pop(task_queue)
primitives.total_task_weight = primitives.total_task_weight - task.weight
end
end
end
local callback = PriorityQueue_peek(callbacks)
while callback ~= nil and tick >= callback.time do
local success, result = pcall(Token_get(callback.func_token), callback.params)
if not success then
if _DEBUG then
error(result)
else
log(result)
ErrorLogging.generate_error_report(result)
end
end
PriorityQueue_pop(callbacks)
callback = PriorityQueue_peek(callbacks)
end
end
--- Allows you to set a timer (in ticks) after which the tokened function will be run with params given as an argument
-- Cannot be called before init
-- @param ticks <number>
-- @param func_token <number> a token for a function store via the token system
-- @param params <any> the argument to send to the tokened function
function Task.set_timeout_in_ticks(ticks, func_token, params)
if not game then
error('cannot call when game is not available', 2)
end
local time = game.tick + ticks
local callback = {time = time, func_token = func_token, params = params}
PriorityQueue_push(callbacks, callback)
end
--- Allows you to set a timer (in seconds) after which the tokened function will be run with params given as an argument
-- Cannot be called before init
-- @param sec <number>
-- @param func_token <number> a token for a function store via the token system
-- @param params <any> the argument to send to the tokened function
function Task.set_timeout(sec, func_token, params)
if not game then
error('cannot call when game is not available', 2)
end
Task.set_timeout_in_ticks(60 * sec, func_token, params)
end
--- Queueing allows you to split up heavy tasks which don't need to be completed in the same tick.
-- Queued tasks are generally run 1 per tick. If the queue backs up, more tasks will be processed per tick.
-- @param func_token <number> a token for a function stored via the token system
-- If this function returns `true` it will run again the next tick, delaying other queued tasks (see weight)
-- @param params <any> the argument to send to the tokened function
-- @param weight <number> (defaults to 1) weight is the number of ticks a task is expected to take.
-- Ex. if the task is expected to repeat multiple times (ie. the function returns true and loops several ticks)
function Task.queue_task(func_token, params, weight)
weight = weight or 1
primitives.total_task_weight = primitives.total_task_weight + weight
Queue_push(task_queue, {func_token = func_token, params = params, weight = weight})
end
function Task.get_queue_speed()
return primitives.task_queue_speed
end
function Task.set_queue_speed(value)
value = value or 1
if value < 0 then
value = 0
end
primitives.task_queue_speed = value
end
Event.add(defines.events.on_tick, on_tick)
return Task