--- Initializes the AsyncOps table if it doesn't already exist. | |
-- This is executed when the file is first loaded. | |
-- @local | |
if FirstLoad then | |
AsyncOps = {} | |
end | |
--- Wakes up the async operation with the given ID and removes it from the AsyncOps table. | |
-- @param opid number The ID of the async operation to complete. | |
-- @param ... any The return values to pass to the async operation's callback. | |
function AsyncOpDone(opid, ...) | |
Wakeup(AsyncOps[opid], ...) | |
AsyncOps[opid] = nil | |
end | |
--- Wakes up an async operation with the given ID and removes it from the AsyncOps table. | |
-- If the operation has timed out, it removes the operation from AsyncOps and returns "timeout". | |
-- If the operation has been cancelled, it removes the operation from AsyncOps and returns "cancelled". | |
-- Otherwise, it returns the return values of the async operation's callback. | |
-- @param id number The ID of the async operation to complete. | |
-- @param ok boolean Whether the operation completed successfully. | |
-- @param ... any The return values to pass to the async operation's callback. | |
-- @return string|any Either "timeout", "cancelled", or the return values of the async operation's callback. | |
local function __wakeup(id, ok, ...) | |
if not ok then | |
AsyncOps[id] = nil | |
AsyncOpStop(id) | |
return "timeout" | |
end | |
if AsyncOps[id] then | |
assert(AsyncOps[id] == "cancelled", "Async op thread has been awaken before the computation is finished!") | |
AsyncOps[id] = nil | |
return "cancelled" | |
end | |
return ... | |
end | |
--- Checks if the current thread can yield. | |
-- This is a wrapper around the built-in `CanYield` function. | |
-- @return boolean True if the current thread can yield, false otherwise. | |
AsyncCanYield = CanYield | |
--- Wraps an asynchronous function to provide timeout and cancellation support. | |
-- The wrapped function will wait until the asynchronous operation is complete, fails, times out, or is cancelled. | |
-- @param func function The asynchronous function to wrap. | |
-- @return function The wrapped asynchronous function. | |
local function AsyncOpWrap(func) | |
return function(...) | |
-- async operations can be executed outside of threads or in real time threads only (cannot be persisted) | |
if not IsRealTimeThread() or not AsyncCanYield() then | |
-- exec syncronously | |
return func(nil, ...) | |
end | |
local id, res2, res3, res4 = func(true, ...) | |
if type(id) ~= "number" then | |
return id, res2, res3, res4 | |
end | |
AsyncOps[id] = CurrentThread() | |
return __wakeup(id, WaitWakeup()) | |
end | |
end | |
--- Wraps all asynchronous functions in the `async` table with a function that provides timeout and cancellation support. | |
-- The wrapped functions will wait until the asynchronous operation is complete, fails, times out, or is cancelled. | |
-- This allows for consistent handling of asynchronous operations throughout the codebase. | |
for op, func in pairs(async) do | |
_G[op] = AsyncOpWrap(func) | |
end | |
--- AsyncOp call with timeout and cancel support, the function waits until the async op is complete, fails, timeouts or is cancelled. | |
-- @cstyle err, ... AsyncOpWait(int nTimeout, table id_ref, string funcname, ...). | |
-- @param nTimeout int; timeout in milliseconds, can be nil. | |
-- @param id_ref table; table to store the id of the async operation; can be used to call AsyncOpCancel(id_ref), overwrites member "asyncop_id" - the same table should not be used for concurent AsyncOp calls; can be nil. | |
-- @param funcname string; name of the async function to be called, actual function is found in async[funcname]. | |
-- @param ...; arguments passed to the async function. | |
-- @return err, ...; if there is no err, the async op was successful and the rest of the return values are returned by the async op; | |
function AsyncOpWait(timeout, id_ref, funcname, ...) | |
-- async operations can be executed in real time threads only (cannot be persisted) | |
assert(IsRealTimeThread()) | |
local id, res2, res3, res4 = async[funcname](true, ...) | |
if type(id) ~= "number" then | |
return id, res2, res3, res4 | |
end | |
if id_ref then | |
rawset(id_ref, "asyncop_id", id) | |
end | |
AsyncOps[id] = CurrentThread() | |
return __wakeup(id, WaitWakeup(timeout)) | |
end | |
--- Cancels an AsyncOp started with AsyncOpWait. | |
-- @cstyle bool AsyncOpCancel(table id_ref). | |
-- @param id_ref table; a thread or a table passed to AsyncOpWait used to identify the async op call; the same table should not be used for concurent AsyncOp calls. | |
-- @return bool; true if the operation referenced in id_ref was waiting to complete. | |
function AsyncOpCancel(id_ref) | |
local id, thread | |
if type(id_ref) == "thread" then | |
for _id, _thread in pairs(AsyncOps) do | |
if _thread == id_ref then | |
id = _id | |
thread = _thread | |
end | |
end | |
elseif type(id_ref) == "table" then | |
id = rawget(id_ref, "asyncop_id") | |
rawset(id_ref, "asyncop_id", nil) | |
thread = id and AsyncOps[id] | |
end | |
if thread then | |
AsyncOps[id] = "cancelled" | |
AsyncOpStop(id) | |
return Wakeup(thread) | |
end | |
end | |
-- AsyncOpWait/AsyncOpCancel usage: | |
-- ... | |
-- local err, res1, res2, res3 = AsyncOpWait(5000, self, "WebRequest", ...) | |
-- if not err then ... end | |
-- if err == "cancelled" then ... end | |
-- if err == "timeout" then ... end | |
-- | |
-- in another thread/callback: | |
-- AsyncOpCancel(self) | |