docs(Future): include moonwave comments

This commit is contained in:
Erica Marigold 2024-04-15 12:42:56 +05:30
parent 64f73540ed
commit 8632d052a9
No known key found for this signature in database
GPG key ID: 2768CC0C23D245D1
2 changed files with 119 additions and 11 deletions

View file

@ -9,10 +9,69 @@ type Result<T, E> = result.Result<T, E>
local Ok = result.Ok
local Err = result.Err
local option = require("option")
type Option<T> = option.Option<T>
local None = option.None
local Some = option.Some
--[=[
@class Future
A future represents an asynchronous computation.
A future is a value that might not have finished computing yet. This kind of asynchronous value
makes it possible for a thread to continue doing useful work while it waits for the value to
become available.
### The [Future:poll] Method
The core method of future, poll, attempts to resolve the future into a final value. This method does
not block if the value is not ready. Instead, the current task is executed in the background, and
its progress is reported when polled. When using a future, you generally wont call poll directly,
but instead [Future:await] the value.
```lua
local net = require("@lune/net")
local fut: Future<Result<string, string>> = Future.try(function(url)
local resp = net.request({
url = url,
method = "GET",
})
assert(resp.ok)
return resp.body
end, { "https://jsonplaceholder.typicode.com/posts/1" })
local resp: Result<string, string> = fut:await()
print(net.jsonDecode(resp:unwrap()))
```
]=]
local Future = {}
export type Status = "initialized" | "running" | "cancelled" | "done"
--[=[
@private
@type Status "initialized" | "pending" | "cancelled" | "ready"
@within Future
Represents the status of a [Future].
]=]
export type Status = "initialized" | "pending" | "cancelled" | "ready"
--[=[
@private
@interface Future<T>
@within Future
This is a dictionary that may contain one or more of the following values:
@field _thread thread -- The background coroutine spawned for execution
@field _ret T -- The value returned once execution has halted
@field _spawnEvt Signal<()> -- Event for internal communication among threads pre execution
@field _retEvt Signal<T | Result<T, string>, Status> -- Event for internal communication among threads post execution
@field _status Status -- The status of the Future
]=]
export type Future<T> = typeof(Future) & {
_fn: (set: (value: T) -> ()) -> T,
_ret: T,
_thread: thread,
_spawnEvt: Signal<()>,
@ -35,26 +94,57 @@ local function _constructor<T>(fn: (Signal<()>, Signal<T, Status>) -> (), args:
)
end
--[=[
@within Future
Constructs a [Future] from a function to be run asynchronously.
:::caution
If a the provided function has the possibility to throw an error, instead of any
other rusty-luau types like [Result] or [Option], use [Future:try] instead.
:::
@param fn -- The function to be executed asynchronously
@param args -- The arguments table to be passed to to the function
@return Future<T> -- The constructed future
]=]
function Future.new<T>(fn: (...any) -> T, args: { any })
return _constructor(function(spawnEvt: Signal<()>, retEvt: Signal<T, Status>)
spawnEvt:Fire()
local ret = fn(table.unpack(args))
retEvt:Fire(ret, "done")
retEvt:Fire(ret, "ready")
end, args)
end
--[=[
@within Future
Constructs a fallible [Future] from a function to be run asynchronously.
@param fn -- The fallible function to be executed asynchronously
@param args -- The arguments table to be passed to to the function
@return Future<Result<T>> -- The constructed future
]=]
function Future.try<T>(fn: (...any) -> T, args: { any })
return _constructor(function(spawnEvt: Signal<()>, retEvt: Signal<Result<T, string>, Status>)
spawnEvt:Fire()
local ok, ret = pcall(fn, table.unpack(args))
local result: Result<T, string> = if ok then Ok(ret) else Err(ret)
retEvt:Fire(result, "done")
retEvt:Fire(result, "ready")
end, args)
end
function Future.poll<T>(self: Future<T>): (Status, T)
--[=[
@within Future
Polls a [Future] to completion.
@param self Future<T>
@return (Status, Option<T>) -- Returns the [Status] and an optional return if completed
]=]
function Future.poll<T>(self: Future<T>): (Status, Option<T>)
if self._status == "initialized" then
self._retEvt:Connect(function(firedRet, status: Status)
self._status = status
@ -69,31 +159,49 @@ function Future.poll<T>(self: Future<T>): (Status, T)
end)
self._spawnEvt:Connect(function()
self._status = "running"
self._status = "pending"
end)
coroutine.resume(self._thread, self._spawnEvt, self._retEvt)
end
if self._status == "running" then
if self._status == "pending" then
-- Just wait a bit more for the signal to fire
task.wait(0.01)
end
return self._status, self._ret
local retOpt = if self._ret == nil then None() else Some(self._ret)
return self._status, retOpt
end
--[=[
@within Future
Cancels a [Future].
@param self Future<T>
]=]
function Future.cancel<T>(self: Future<T>)
self._retEvt:Fire(nil :: any, "cancelled")
self._status = "cancelled"
end
--[=[
@within Future
Suspend execution until the result of a [Future] is ready.
This method continuosly polls a [Future] until it reaches completion.
@param self Future<T>
@return T -- The value returned by the function on completion
]=]
function Future.await<T>(self: Future<T>): T
while true do
local status: Status, ret: T = self:poll()
local status: Status, ret: Option<T> = self:poll()
if status == "done" then
return ret
if status == "ready" then
-- Safe to unwrap, we know it must not be nil
return ret:unwrap()
end
end
end