| --- 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) | |