From 8632d052a918e09b9bb83b4f287583b05752d8fd Mon Sep 17 00:00:00 2001 From: Erica Marigold Date: Mon, 15 Apr 2024 12:42:56 +0530 Subject: [PATCH] docs(Future): include moonwave comments --- lib/future.luau | 130 ++++++++++++++++++++++++++++--- lib/{iter.luau => iter.luau.old} | 0 2 files changed, 119 insertions(+), 11 deletions(-) rename lib/{iter.luau => iter.luau.old} (100%) diff --git a/lib/future.luau b/lib/future.luau index b390568..dda79dd 100644 --- a/lib/future.luau +++ b/lib/future.luau @@ -9,10 +9,69 @@ type Result = result.Result local Ok = result.Ok local Err = result.Err +local option = require("option") +type Option = option.Option +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 won’t call poll directly, + but instead [Future:await] the value. + + ```lua + local net = require("@lune/net") + + local fut: Future> = 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 = 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 + @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, Status> -- Event for internal communication among threads post execution + @field _status Status -- The status of the Future + +]=] export type Future = typeof(Future) & { - _fn: (set: (value: T) -> ()) -> T, _ret: T, _thread: thread, _spawnEvt: Signal<()>, @@ -35,26 +94,57 @@ local function _constructor(fn: (Signal<()>, Signal) -> (), 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 -- The constructed future +]=] function Future.new(fn: (...any) -> T, args: { any }) return _constructor(function(spawnEvt: Signal<()>, retEvt: Signal) 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> -- The constructed future +]=] function Future.try(fn: (...any) -> T, args: { any }) return _constructor(function(spawnEvt: Signal<()>, retEvt: Signal, Status>) spawnEvt:Fire() local ok, ret = pcall(fn, table.unpack(args)) local result: Result = if ok then Ok(ret) else Err(ret) - retEvt:Fire(result, "done") + retEvt:Fire(result, "ready") end, args) end -function Future.poll(self: Future): (Status, T) +--[=[ + @within Future + + Polls a [Future] to completion. + + @param self Future + @return (Status, Option) -- Returns the [Status] and an optional return if completed +]=] +function Future.poll(self: Future): (Status, Option) if self._status == "initialized" then self._retEvt:Connect(function(firedRet, status: Status) self._status = status @@ -69,31 +159,49 @@ function Future.poll(self: Future): (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 +]=] function Future.cancel(self: Future) 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 + @return T -- The value returned by the function on completion +]=] function Future.await(self: Future): T while true do - local status: Status, ret: T = self:poll() + local status: Status, ret: Option = 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 diff --git a/lib/iter.luau b/lib/iter.luau.old similarity index 100% rename from lib/iter.luau rename to lib/iter.luau.old