mirror of
https://github.com/bryanthaboi/gen1recomp
synced 2026-10-08 01:34:06 -04:00
178 lines
4.6 KiB
Lua
178 lines
4.6 KiB
Lua
local ops = require("src.sync.SyncWorkOps")(require)
|
|
|
|
local Work = {}
|
|
Work.POOL = 2
|
|
Work.MIN_BYTES = 16 * 1024
|
|
Work.ops = ops
|
|
|
|
local CMD, RESULT, QUIT = "sync_work_cmd", "sync_work_result", "sync_work_quit"
|
|
local WORKER = "src/sync/sync_worker.lua"
|
|
|
|
local workers = {}
|
|
local cmdCh, resCh, quitCh
|
|
local ready
|
|
local results = {}
|
|
local nextId = 0
|
|
local owned = setmetatable({}, { __mode = "k" })
|
|
local waits = setmetatable({}, { __mode = "k" })
|
|
|
|
local function pack(...) return { n = select("#", ...), ... } end
|
|
|
|
local function start()
|
|
if ready ~= nil then return ready end
|
|
local thread = love and love.thread
|
|
if type(thread) ~= "table" or type(thread.newThread) ~= "function"
|
|
or type(thread.getChannel) ~= "function" then
|
|
ready = false
|
|
return false
|
|
end
|
|
local ok = pcall(function()
|
|
cmdCh, resCh, quitCh = thread.getChannel(CMD), thread.getChannel(RESULT), thread.getChannel(QUIT)
|
|
quitCh:clear(); cmdCh:clear(); resCh:clear()
|
|
end)
|
|
if not ok then
|
|
cmdCh, resCh, quitCh, ready = nil, nil, nil, false
|
|
return false
|
|
end
|
|
for _ = 1, Work.POOL do
|
|
local made, th = pcall(thread.newThread, WORKER)
|
|
if made and th and pcall(function() th:start() end) then workers[#workers + 1] = th end
|
|
end
|
|
ready = #workers > 0
|
|
return ready
|
|
end
|
|
|
|
function Work.available()
|
|
return start()
|
|
end
|
|
|
|
local function drain()
|
|
if not resCh then return end
|
|
local msg = resCh:pop()
|
|
while msg do
|
|
if type(msg) == "table" and msg.id and results[msg.id] == false then results[msg.id] = msg end
|
|
msg = resCh:pop()
|
|
end
|
|
for _, th in ipairs(workers) do
|
|
local err = th.getError and th:getError()
|
|
if err then
|
|
for id, r in pairs(results) do
|
|
if r == false then results[id] = { id = id, lost = true } end
|
|
end
|
|
ready = false
|
|
break
|
|
end
|
|
end
|
|
end
|
|
|
|
function Work.submit(op, ...)
|
|
if not ops[op] or not start() then return nil end
|
|
nextId = nextId + 1
|
|
local id = nextId
|
|
if not pcall(cmdCh.push, cmdCh, { id = id, op = op, args = pack(...) }) then return nil end
|
|
results[id] = false
|
|
return id
|
|
end
|
|
|
|
function Work.poll(id)
|
|
drain()
|
|
local r = results[id]
|
|
if r == false then return nil end
|
|
results[id] = nil
|
|
return r or { id = id, lost = true }
|
|
end
|
|
|
|
function Work.forget(id)
|
|
if id ~= nil then results[id] = nil end
|
|
end
|
|
|
|
function Work.finish(r, op, args)
|
|
if not r or r.lost then return ops[op](unpack(args, 1, args.n)) end
|
|
if r.err then error(r.err, 0) end
|
|
local values = r.values or { n = 0 }
|
|
return unpack(values, 1, values.n or #values)
|
|
end
|
|
|
|
function Work.spawn(fn)
|
|
local co = coroutine.create(fn)
|
|
owned[co] = true
|
|
return co
|
|
end
|
|
|
|
function Work.abandon(co)
|
|
for id in pairs(waits[co] or {}) do Work.forget(id) end
|
|
waits[co] = nil
|
|
end
|
|
|
|
function Work.async()
|
|
local co = coroutine.running()
|
|
return co ~= nil and owned[co] == true and start() == true
|
|
end
|
|
|
|
local function heavy(args)
|
|
local bytes = 0
|
|
for i = 1, args.n do
|
|
local v = args[i]
|
|
if type(v) == "table" then return true end
|
|
if type(v) == "string" then bytes = bytes + #v end
|
|
end
|
|
return bytes >= Work.MIN_BYTES
|
|
end
|
|
|
|
local function await(co, ids)
|
|
local mine = waits[co] or {}
|
|
waits[co] = mine
|
|
for _, id in pairs(ids) do mine[id] = true end
|
|
local out = {}
|
|
while true do
|
|
local pending = false
|
|
for i, id in pairs(ids) do
|
|
if out[i] == nil then
|
|
local r = Work.poll(id)
|
|
if r then out[i] = r; mine[id] = nil else pending = true end
|
|
end
|
|
end
|
|
if not pending then return out end
|
|
coroutine.yield()
|
|
end
|
|
end
|
|
|
|
function Work.call(op, ...)
|
|
local args = pack(...)
|
|
if not heavy(args) or not Work.async() then return ops[op](...) end
|
|
local id = Work.submit(op, ...)
|
|
if not id then return ops[op](...) end
|
|
return Work.finish(await(coroutine.running(), { id })[1], op, args)
|
|
end
|
|
|
|
function Work.batch(op, list)
|
|
local out, ids = {}, {}
|
|
local async = Work.async()
|
|
for i, args in ipairs(list) do
|
|
args.n = args.n or #args
|
|
local id = async and heavy(args) and Work.submit(op, unpack(args, 1, args.n)) or nil
|
|
if id then ids[i] = id else out[i] = pack(ops[op](unpack(args, 1, args.n))) end
|
|
end
|
|
if next(ids) then
|
|
local done = await(coroutine.running(), ids)
|
|
for i, r in pairs(done) do out[i] = pack(Work.finish(r, op, list[i])) end
|
|
end
|
|
return out
|
|
end
|
|
|
|
function Work.shutdown()
|
|
if quitCh then quitCh:push(true) end
|
|
if cmdCh then
|
|
cmdCh:clear()
|
|
for _ = 1, #workers do cmdCh:push({ op = "quit" }) end
|
|
end
|
|
for _, th in ipairs(workers) do pcall(function() th:wait() end) end
|
|
workers = {}
|
|
results = {}
|
|
cmdCh, resCh, quitCh, ready = nil, nil, nil, nil
|
|
end
|
|
|
|
pcall(function() require("src.core.SessionLifecycle").registerProcessShutdown(Work.shutdown) end)
|
|
|
|
return Work
|