From d55fc93e1e7847d5e657674863b5ca32e679264c Mon Sep 17 00:00:00 2001 From: PoolloverNathan Date: Thu, 4 Apr 2024 00:29:04 -0400 Subject: [PATCH 1/9] code review version 2 electric boogalo --- .../org/figuramc/figura/lua/api/FileAPI.java | 2 +- .../figura/lua/api/data/FiguraFuture.java | 105 +++++++++++++++++- .../lua/api/data/FiguraInputStream.java | 12 +- .../figura/lua/api/data/LuaCloseable.java | 14 +++ .../figura/lua/api/data/ResourcesAPI.java | 2 +- .../figura/lua/api/net/HttpRequestsAPI.java | 4 +- 6 files changed, 130 insertions(+), 9 deletions(-) create mode 100644 common/src/main/java/org/figuramc/figura/lua/api/data/LuaCloseable.java diff --git a/common/src/main/java/org/figuramc/figura/lua/api/FileAPI.java b/common/src/main/java/org/figuramc/figura/lua/api/FileAPI.java index 08dfd2a46..d985c6b53 100644 --- a/common/src/main/java/org/figuramc/figura/lua/api/FileAPI.java +++ b/common/src/main/java/org/figuramc/figura/lua/api/FileAPI.java @@ -137,7 +137,7 @@ public FiguraInputStream openReadStream(@LuaNotNil String path) { Path p = securityCheck(path); File f = p.toFile(); FileInputStream fis = new FileInputStream(f); - return new FiguraInputStream(fis); + return new FiguraInputStream(fis, parent); } catch (FileNotFoundException e) { throw new LuaError(e); } diff --git a/common/src/main/java/org/figuramc/figura/lua/api/data/FiguraFuture.java b/common/src/main/java/org/figuramc/figura/lua/api/data/FiguraFuture.java index ff2627822..a1d687ce1 100644 --- a/common/src/main/java/org/figuramc/figura/lua/api/data/FiguraFuture.java +++ b/common/src/main/java/org/figuramc/figura/lua/api/data/FiguraFuture.java @@ -1,19 +1,35 @@ package org.figuramc.figura.lua.api.data; +import org.figuramc.figura.avatar.Avatar; import org.figuramc.figura.lua.LuaWhitelist; import org.figuramc.figura.lua.docs.LuaMethodDoc; import org.figuramc.figura.lua.docs.LuaMethodOverload; import org.figuramc.figura.lua.docs.LuaTypeDoc; import org.luaj.vm2.LuaError; +import org.luaj.vm2.LuaFunction; +import org.luaj.vm2.LuaValue; +import org.luaj.vm2.Varargs; + +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.function.Consumer; +import java.util.function.Function; @LuaWhitelist @LuaTypeDoc(value = "future", name = "Future") public class FiguraFuture { + private final Avatar avatar; private boolean isDone; private boolean hasError; private LuaError errorObject; + private final Deque> onFinish = new ArrayDeque<>(); + private final Deque> onFinishError = new ArrayDeque<>(); private T value; + public FiguraFuture(Avatar avatar) { + this.avatar = avatar; + } + public void handle(T value, Throwable error) { if (error != null) error(error); else complete(value); @@ -23,6 +39,45 @@ public void complete(T value) { if (!isDone) { this.value = value; isDone = true; + for (var f: onFinish) { + f.accept(value); + } + onFinish.clear(); + onFinishError.clear(); + } + } + + public AutoCloseable onFinish(Consumer f) { + if (isDone && !hasError) { + f.accept(value); + return () -> {}; + } else { + onFinish.add(f); + return () -> onFinish.remove(f); + } + } + @LuaWhitelist + public LuaCloseable onFinish(LuaFunction f) { + if (avatar == null) { + throw new LuaError("Future.onFinish unavailable for legal reasons"); + } else { + final var mgr = avatar.luaRuntime.typeManager; + return new LuaCloseable(onFinish(value -> f.invoke(mgr.javaToLua(f)))); + } + } + + @LuaWhitelist + public LuaCloseable onFinishError(LuaFunction f) { + return new LuaCloseable(onFinishError(err -> f.invoke(err.getMessageObject()))); + } + + public AutoCloseable onFinishError(Consumer f) { + if (hasError) { + f.accept(errorObject); + return () -> {}; + } else { + onFinishError.add(f); + return () -> onFinishError.remove(f); } } @@ -31,6 +86,11 @@ public void error(Throwable t) { hasError = true; isDone = true; errorObject = t instanceof LuaError e ? e : new LuaError(t); + for (var f: onFinishError) { + f.accept(errorObject); + } + onFinish.clear(); + onFinishError.clear(); } } @@ -45,6 +105,7 @@ public boolean isDone() { return isDone; } + @LuaWhitelist @LuaMethodDoc( value = "future.has_error", overloads = @LuaMethodOverload( @@ -86,8 +147,50 @@ public void throwError() { if (errorObject != null) throw errorObject; } + public FiguraFuture map(Function mapper) { + final var fut = new FiguraFuture(avatar); + onFinish(v -> fut.complete(mapper.apply(v))); + onFinishError(fut::error); + return fut; + } + + @LuaWhitelist + public FiguraFuture map(LuaFunction mapper) { + return map(wrapLua(mapper)); + } + + public Function wrapLua(LuaFunction f) { + return a -> f.invoke(avatar.luaRuntime.typeManager.javaToLua(a)).arg1(); + } + + public FiguraFuture handle(Function handler) { + final var fut = new FiguraFuture(avatar); + onFinish(fut::complete); + onFinishError(e -> { + try { + fut.complete(handler.apply(e)); + } catch (Throwable t) { + fut.error(t); + } + }); + return fut; + } + + @LuaWhitelist + public FiguraFuture handle(LuaFunction handler) { + return map(((Function) avatar.luaRuntime.typeManager::javaToLua).andThen(Varargs::arg1)).handle(err -> handler.invoke(err.getMessageObject()).arg1()); + } + @Override public String toString() { - return "Future(isDone=%s)".formatted(isDone); + if (isDone) { + if (hasError) { + return "Future(error: " + errorObject.toString() + ")"; + } else { + return "Future(value: " + value.toString() + ")"; + } + } else { + return "Future(pending)"; + } } } diff --git a/common/src/main/java/org/figuramc/figura/lua/api/data/FiguraInputStream.java b/common/src/main/java/org/figuramc/figura/lua/api/data/FiguraInputStream.java index 1047c6416..ff28fe1b5 100644 --- a/common/src/main/java/org/figuramc/figura/lua/api/data/FiguraInputStream.java +++ b/common/src/main/java/org/figuramc/figura/lua/api/data/FiguraInputStream.java @@ -1,5 +1,6 @@ package org.figuramc.figura.lua.api.data; +import org.figuramc.figura.avatar.Avatar; import org.figuramc.figura.lua.LuaWhitelist; import org.figuramc.figura.lua.docs.LuaMethodDoc; import org.figuramc.figura.lua.docs.LuaMethodOverload; @@ -18,13 +19,16 @@ public class FiguraInputStream extends InputStream { private final InputStream sourceStream; private final boolean asyncOnly; - public FiguraInputStream(InputStream sourceStream) { - this(sourceStream, false); + private final Avatar avatar; + + public FiguraInputStream(InputStream sourceStream, Avatar avatar) { + this(sourceStream, false, avatar); } - public FiguraInputStream(InputStream sourceStream, boolean asyncOnly) { + public FiguraInputStream(InputStream sourceStream, boolean asyncOnly, Avatar avatar) { this.sourceStream = sourceStream; this.asyncOnly = asyncOnly; + this.avatar = avatar; } @Override @@ -44,7 +48,7 @@ public int read() { public FiguraFuture readAsync(Integer limit) { final int finalLimit = limit != null ? limit : available(); // Future handle that will be returned - FiguraFuture future = new FiguraFuture<>(); + FiguraFuture future = new FiguraFuture<>(avatar); // Calling an async read that will be put in a future results CompletableFuture.supplyAsync(() -> { try { diff --git a/common/src/main/java/org/figuramc/figura/lua/api/data/LuaCloseable.java b/common/src/main/java/org/figuramc/figura/lua/api/data/LuaCloseable.java new file mode 100644 index 000000000..13618770c --- /dev/null +++ b/common/src/main/java/org/figuramc/figura/lua/api/data/LuaCloseable.java @@ -0,0 +1,14 @@ +package org.figuramc.figura.lua.api.data; + +public class LuaCloseable implements AutoCloseable { + final AutoCloseable inner; + + public LuaCloseable(AutoCloseable inner) { + this.inner = inner; + } + + @Override + public void close() throws Exception { + inner.close(); + } +} diff --git a/common/src/main/java/org/figuramc/figura/lua/api/data/ResourcesAPI.java b/common/src/main/java/org/figuramc/figura/lua/api/data/ResourcesAPI.java index 64d511e0f..ff64ae0fd 100644 --- a/common/src/main/java/org/figuramc/figura/lua/api/data/ResourcesAPI.java +++ b/common/src/main/java/org/figuramc/figura/lua/api/data/ResourcesAPI.java @@ -37,7 +37,7 @@ public FiguraInputStream get(@LuaNotNil String path) { try { if (parent.resources.containsKey(path)) { ByteArrayInputStream bais = new ByteArrayInputStream(parent.resources.get(path)); - return new FiguraInputStream(new GZIPInputStream(bais)); + return new FiguraInputStream(new GZIPInputStream(bais), parent); } } catch (IOException e) { throw new LuaError(e); diff --git a/common/src/main/java/org/figuramc/figura/lua/api/net/HttpRequestsAPI.java b/common/src/main/java/org/figuramc/figura/lua/api/net/HttpRequestsAPI.java index 054feca34..aae4584ce 100644 --- a/common/src/main/java/org/figuramc/figura/lua/api/net/HttpRequestsAPI.java +++ b/common/src/main/java/org/figuramc/figura/lua/api/net/HttpRequestsAPI.java @@ -283,11 +283,11 @@ public FiguraFuture send() { } parent.parent.log(NetworkingAPI.LogSource.HTTP, Component.literal("Sent %s request to %s".formatted(method, uri))); HttpRequest req = this.getRequest(); - FiguraFuture future = new FiguraFuture<>(); + FiguraFuture future = new FiguraFuture<>(parent.parent.owner); var asyncResponse = parent.httpClient.sendAsync(req, java.net.http.HttpResponse.BodyHandlers.ofInputStream()); asyncResponse.whenCompleteAsync((response, t) -> { if (t != null) future.error(t); - else future.complete(new HttpResponse(new FiguraInputStream(response.body()), + else future.complete(new HttpResponse(new FiguraInputStream(response.body(), parent.parent.owner), response.statusCode(), response.headers().map())); }); return future; From cfae37e11b4e0d03e71b297ba32cdd5714dc0e5c Mon Sep 17 00:00:00 2001 From: PoolloverNathan Date: Thu, 4 Apr 2024 01:10:08 -0400 Subject: [PATCH 2/9] add FiguraFuture::andThen --- .../figura/lua/api/data/FiguraFuture.java | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/common/src/main/java/org/figuramc/figura/lua/api/data/FiguraFuture.java b/common/src/main/java/org/figuramc/figura/lua/api/data/FiguraFuture.java index a1d687ce1..9201e0c89 100644 --- a/common/src/main/java/org/figuramc/figura/lua/api/data/FiguraFuture.java +++ b/common/src/main/java/org/figuramc/figura/lua/api/data/FiguraFuture.java @@ -159,6 +159,32 @@ public FiguraFuture map(LuaFunction mapper) { return map(wrapLua(mapper)); } + public FiguraFuture andThen(Function> f) { + final var fut = new FiguraFuture(avatar); + onFinishError(fut::error); + onFinish(v -> { + final var r = f.apply(v); + r.onFinish(fut::complete); + r.onFinishError(fut::error); + }); + return fut; + } + @LuaWhitelist + public FiguraFuture andThen(LuaFunction f) { + return andThen(v -> { + final var res = f.invoke().arg1(); + if (res.isuserdata(FiguraFuture.class)) { + return (FiguraFuture) res.checkuserdata(FiguraFuture.class); + } else { + // do not rely on this behavior + // FIXME: will be optimized sometime soon - I wrote this at 1:08 AM + final var fut = new FiguraFuture(avatar); + fut.complete(res); + return fut; + } + }); + } + public Function wrapLua(LuaFunction f) { return a -> f.invoke(avatar.luaRuntime.typeManager.javaToLua(a)).arg1(); } From 9d2b08488d070d30782ee01ac9b320472d8d7595 Mon Sep 17 00:00:00 2001 From: PoolloverNathan Date: Thu, 4 Apr 2024 10:36:54 -0400 Subject: [PATCH 3/9] add docs keys --- .../java/org/figuramc/figura/lua/api/data/FiguraFuture.java | 5 +++++ common/src/main/resources/assets/figura/lang/en_us.json | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/common/src/main/java/org/figuramc/figura/lua/api/data/FiguraFuture.java b/common/src/main/java/org/figuramc/figura/lua/api/data/FiguraFuture.java index 9201e0c89..16c5a6831 100644 --- a/common/src/main/java/org/figuramc/figura/lua/api/data/FiguraFuture.java +++ b/common/src/main/java/org/figuramc/figura/lua/api/data/FiguraFuture.java @@ -57,6 +57,7 @@ public AutoCloseable onFinish(Consumer f) { } } @LuaWhitelist + @LuaMethodDoc("future.on_finish") public LuaCloseable onFinish(LuaFunction f) { if (avatar == null) { throw new LuaError("Future.onFinish unavailable for legal reasons"); @@ -67,6 +68,7 @@ public LuaCloseable onFinish(LuaFunction f) { } @LuaWhitelist + @LuaMethodDoc("future.on_finish_error") public LuaCloseable onFinishError(LuaFunction f) { return new LuaCloseable(onFinishError(err -> f.invoke(err.getMessageObject()))); } @@ -155,6 +157,7 @@ public FiguraFuture map(Function mapper) { } @LuaWhitelist + @LuaMethodDoc("future.map") public FiguraFuture map(LuaFunction mapper) { return map(wrapLua(mapper)); } @@ -170,6 +173,7 @@ public FiguraFuture andThen(Function> f) { return fut; } @LuaWhitelist + @LuaMethodDoc("future.and_then") public FiguraFuture andThen(LuaFunction f) { return andThen(v -> { final var res = f.invoke().arg1(); @@ -203,6 +207,7 @@ public FiguraFuture handle(Function handler) { } @LuaWhitelist + @LuaMethodDoc("future.handle") public FiguraFuture handle(LuaFunction handler) { return map(((Function) avatar.luaRuntime.typeManager::javaToLua).andThen(Varargs::arg1)).handle(err -> handler.invoke(err.getMessageObject()).arg1()); } diff --git a/common/src/main/resources/assets/figura/lang/en_us.json b/common/src/main/resources/assets/figura/lang/en_us.json index 6eb28a8ab..fe6fca5ea 100644 --- a/common/src/main/resources/assets/figura/lang/en_us.json +++ b/common/src/main/resources/assets/figura/lang/en_us.json @@ -1821,11 +1821,16 @@ "figura.docs.http_request_builder.get_headers": "Returns table with all headers set for this request", "figura.docs.http_request_builder.send": "Sends this request and returns Future object that will contain response object once request is done", "figura.docs.future": "Object that contains result of operation that cant be finished immediately", + "figura.docs.future.on_finish": "", + "figura.docs.future.on_finish_error": "", "figura.docs.future.is_done": "Checks if future is done, either successfully or with error", "figura.docs.future.has_error": "Checks if error occurred while this future execution", "figura.docs.future.get_value": "Returns value of this future object if future was executed successfully", "figura.docs.future.get_or_error": "Throws error if it occurred while execution of this future, returns value otherwise", "figura.docs.future.throw_error": "Throws an error if it occurred while execution of this future.", + "figura.docs.future.map": "", + "figura.docs.future.and_then": "", + "figura.docs.future.handle": "", "figura.docs.http_response": "Object that contains HTTP response", "figura.docs.http_response.get_data": "Returns input stream with response data", "figura.docs.http_response.get_response_code": "Returns response code", From 594a7fa0f55a2e7b116250bac21ddaceb84bae45 Mon Sep 17 00:00:00 2001 From: PoolloverNathan Date: Thu, 4 Apr 2024 11:43:00 -0400 Subject: [PATCH 4/9] actual docs info --- .../src/main/resources/assets/figura/lang/en_us.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/common/src/main/resources/assets/figura/lang/en_us.json b/common/src/main/resources/assets/figura/lang/en_us.json index fe6fca5ea..17359e678 100644 --- a/common/src/main/resources/assets/figura/lang/en_us.json +++ b/common/src/main/resources/assets/figura/lang/en_us.json @@ -1821,16 +1821,16 @@ "figura.docs.http_request_builder.get_headers": "Returns table with all headers set for this request", "figura.docs.http_request_builder.send": "Sends this request and returns Future object that will contain response object once request is done", "figura.docs.future": "Object that contains result of operation that cant be finished immediately", - "figura.docs.future.on_finish": "", - "figura.docs.future.on_finish_error": "", + "figura.docs.future.on_finish": "Runs the given function when the future is complete\nIf the future is already complete, the function is called immediately\nIf the future errors, the callback is silently discarded\nThe function can be disconnected early by calling :close() on the returned closeable", + "figura.docs.future.on_finish_error": "Runs the given function when the future fails with an error\nIf the future has already failed, the function is called immediately\nIf the future completes successfully, thd callback is silently discarded\nThe function can be disconnected early by calling :close() on the returned closeable", "figura.docs.future.is_done": "Checks if future is done, either successfully or with error", "figura.docs.future.has_error": "Checks if error occurred while this future execution", "figura.docs.future.get_value": "Returns value of this future object if future was executed successfully", "figura.docs.future.get_or_error": "Throws error if it occurred while execution of this future, returns value otherwise", "figura.docs.future.throw_error": "Throws an error if it occurred while execution of this future.", - "figura.docs.future.map": "", - "figura.docs.future.and_then": "", - "figura.docs.future.handle": "", + "figura.docs.future.map": "Returns a new future that applies the given function to the return value\nIf the original future errored, the new future will have the same error\nIf you want to have the returned future waited for (like Haskell >>=), use :andThen", + "figura.docs.future.and_then": "Returns a new future that applies the given function to the return value and then waits for the returned future\nIf the function does not return a future, this will act like :map\nAny error in either the first or second future will cause the resulting future to have that error\nIf you do not want to wait for a returned future, use :map", + "figura.docs.future.handle": "Handles errors thrown by the future\nAny successful values will be preserved\nThe given function will be called with any error the source future\nThe resulting future will error if both the source future and supplied function error\n", "figura.docs.http_response": "Object that contains HTTP response", "figura.docs.http_response.get_data": "Returns input stream with response data", "figura.docs.http_response.get_response_code": "Returns response code", From c4598934117d2dbae010d99a7f72e3a18b39a120 Mon Sep 17 00:00:00 2001 From: PoolloverNathan Date: Thu, 4 Apr 2024 11:50:51 -0400 Subject: [PATCH 5/9] rewrite FiguraFuture::andThen and silence warning --- .../figura/lua/api/data/FiguraFuture.java | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/common/src/main/java/org/figuramc/figura/lua/api/data/FiguraFuture.java b/common/src/main/java/org/figuramc/figura/lua/api/data/FiguraFuture.java index 16c5a6831..5b5acc501 100644 --- a/common/src/main/java/org/figuramc/figura/lua/api/data/FiguraFuture.java +++ b/common/src/main/java/org/figuramc/figura/lua/api/data/FiguraFuture.java @@ -174,19 +174,17 @@ public FiguraFuture andThen(Function> f) { } @LuaWhitelist @LuaMethodDoc("future.and_then") - public FiguraFuture andThen(LuaFunction f) { - return andThen(v -> { - final var res = f.invoke().arg1(); - if (res.isuserdata(FiguraFuture.class)) { - return (FiguraFuture) res.checkuserdata(FiguraFuture.class); - } else { - // do not rely on this behavior - // FIXME: will be optimized sometime soon - I wrote this at 1:08 AM - final var fut = new FiguraFuture(avatar); - fut.complete(res); - return fut; + public FiguraFuture andThen(LuaFunction f) { + final var fut = new FiguraFuture<>(avatar); + onFinish(v -> { + final var res = f.invoke(avatar.luaRuntime.typeManager.javaToLua(v)).arg1(); + if (res.isuserdata() && res.checkuserdata() instanceof FiguraFuture fut2) { + fut2.onFinish(fut::complete); + fut2.onFinishError(fut::error); } }); + onFinishError(fut::error); + return fut; } public Function wrapLua(LuaFunction f) { From c2fdd037d01bd342bb6d85d21f65118523fff224 Mon Sep 17 00:00:00 2001 From: PoolloverNathan Date: Thu, 4 Apr 2024 15:11:14 -0400 Subject: [PATCH 6/9] logic overload --- .../java/org/figuramc/figura/lua/api/data/FiguraFuture.java | 2 +- .../org/figuramc/figura/lua/api/data/FiguraInputStream.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/common/src/main/java/org/figuramc/figura/lua/api/data/FiguraFuture.java b/common/src/main/java/org/figuramc/figura/lua/api/data/FiguraFuture.java index 5b5acc501..e12c3d93e 100644 --- a/common/src/main/java/org/figuramc/figura/lua/api/data/FiguraFuture.java +++ b/common/src/main/java/org/figuramc/figura/lua/api/data/FiguraFuture.java @@ -30,7 +30,7 @@ public FiguraFuture(Avatar avatar) { this.avatar = avatar; } - public void handle(T value, Throwable error) { + public void complete(T value, Throwable error) { if (error != null) error(error); else complete(value); } diff --git a/common/src/main/java/org/figuramc/figura/lua/api/data/FiguraInputStream.java b/common/src/main/java/org/figuramc/figura/lua/api/data/FiguraInputStream.java index ff28fe1b5..a98a83bee 100644 --- a/common/src/main/java/org/figuramc/figura/lua/api/data/FiguraInputStream.java +++ b/common/src/main/java/org/figuramc/figura/lua/api/data/FiguraInputStream.java @@ -63,7 +63,7 @@ public FiguraFuture readAsync(Integer limit) { } catch (IOException e) { throw new LuaError(e); } - }).whenCompleteAsync(future::handle); + }).whenCompleteAsync(future::complete); return future; } From e373a00319e96b1137a1ad2676565c7bd299026a Mon Sep 17 00:00:00 2001 From: PoolloverNathan Date: Thu, 4 Apr 2024 15:24:48 -0400 Subject: [PATCH 7/9] my hands are typing words on a keyboard --- .../figura/lua/api/data/FiguraFuture.java | 31 +++++++++++++------ 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/common/src/main/java/org/figuramc/figura/lua/api/data/FiguraFuture.java b/common/src/main/java/org/figuramc/figura/lua/api/data/FiguraFuture.java index e12c3d93e..c95c4e5ae 100644 --- a/common/src/main/java/org/figuramc/figura/lua/api/data/FiguraFuture.java +++ b/common/src/main/java/org/figuramc/figura/lua/api/data/FiguraFuture.java @@ -5,6 +5,7 @@ import org.figuramc.figura.lua.docs.LuaMethodDoc; import org.figuramc.figura.lua.docs.LuaMethodOverload; import org.figuramc.figura.lua.docs.LuaTypeDoc; +import org.jetbrains.annotations.Nullable; import org.luaj.vm2.LuaError; import org.luaj.vm2.LuaFunction; import org.luaj.vm2.LuaValue; @@ -48,22 +49,32 @@ public void complete(T value) { } public AutoCloseable onFinish(Consumer f) { - if (isDone && !hasError) { - f.accept(value); - return () -> {}; - } else { - onFinish.add(f); - return () -> onFinish.remove(f); - } + if (isDone && !hasError) { + f.accept(value); + return () -> {}; + } else { + onFinish.add(f); + return () -> onFinish.remove(f); + } + } + public AutoCloseable onFinish(Consumer f, Consumer g) { + final var t = onFinish(f); + final var h = onFinishError(g); + return () -> { t.close(); h.close(); }; } @LuaWhitelist @LuaMethodDoc("future.on_finish") - public LuaCloseable onFinish(LuaFunction f) { + public LuaCloseable onFinish(LuaFunction f, @Nullable LuaFunction g) { if (avatar == null) { - throw new LuaError("Future.onFinish unavailable for legal reasons"); + throw new LuaError("Future::onFinish unavailable for legal reasons"); } else { final var mgr = avatar.luaRuntime.typeManager; - return new LuaCloseable(onFinish(value -> f.invoke(mgr.javaToLua(f)))); + AutoCloseable ab = onFinish(value -> f.invoke(mgr.javaToLua(f))); + AutoCloseable ot = g != null ? onFinishError(g) : null; + return new LuaCloseable(() -> { + ab.close(); + if (ot != null) ot.close(); + }); } } From 535a9d0a52082b9b1e43db2dc463d6fa9855b61d Mon Sep 17 00:00:00 2001 From: PoolloverNathan Date: Thu, 4 Apr 2024 21:15:38 -0400 Subject: [PATCH 8/9] the future is now old man --- .../figura/lua/api/net/NetworkingAPI.java | 37 ++++++++++++++++++- .../resources/assets/figura/lang/en_us.json | 1 + 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/common/src/main/java/org/figuramc/figura/lua/api/net/NetworkingAPI.java b/common/src/main/java/org/figuramc/figura/lua/api/net/NetworkingAPI.java index bca9a495f..971881aff 100644 --- a/common/src/main/java/org/figuramc/figura/lua/api/net/NetworkingAPI.java +++ b/common/src/main/java/org/figuramc/figura/lua/api/net/NetworkingAPI.java @@ -4,10 +4,10 @@ import net.minecraft.network.chat.Component; import net.minecraft.network.chat.MutableComponent; import org.figuramc.figura.FiguraMod; +import org.figuramc.figura.lua.api.data.FiguraFuture; import org.figuramc.figura.lua.docs.LuaMethodOverload; import org.figuramc.figura.utils.ColorUtils; -import org.luaj.vm2.LuaError; -import org.luaj.vm2.LuaValue; +import org.luaj.vm2.*; import org.figuramc.figura.avatar.Avatar; import org.figuramc.figura.config.Configs; import org.figuramc.figura.lua.LuaWhitelist; @@ -15,6 +15,8 @@ import org.figuramc.figura.lua.docs.LuaMethodDoc; import org.figuramc.figura.lua.docs.LuaTypeDoc; import org.figuramc.figura.permissions.Permissions; +import org.luaj.vm2.lib.OneArgFunction; +import org.luaj.vm2.lib.ZeroArgFunction; import java.io.*; import java.net.MalformedURLException; @@ -25,6 +27,7 @@ import java.time.LocalTime; import java.util.ArrayList; import java.util.Locale; +import java.util.function.Consumer; @LuaWhitelist @LuaTypeDoc( @@ -238,6 +241,36 @@ public String toString() { } } + @LuaWhitelist + @LuaMethodDoc("net.new_future") + public Varargs newFuture(LuaFunction k) { + final var fut = new FiguraFuture(owner); + final var mgr = owner.luaRuntime.typeManager; + final var ret = mgr.javaToLua(fut).arg1(); + final var onc = new OneArgFunction() { + @Override + public LuaValue call(LuaValue val) { + fut.complete(val); + return ret; + } + }; + final var one = new OneArgFunction() { + @Override + public LuaValue call(LuaValue err) { + fut.error(new LuaError(err)); + return ret; + } + }; + if (k != null) { + try { + k.call(onc, one); + } catch (Throwable t) { + fut.error(t); + } + } + return LuaValue.varargsOf(new LuaValue[] {ret, onc, one}); + } + @Override public String toString() { return "NetworkingAPI"; diff --git a/common/src/main/resources/assets/figura/lang/en_us.json b/common/src/main/resources/assets/figura/lang/en_us.json index 17359e678..34e62e46d 100644 --- a/common/src/main/resources/assets/figura/lang/en_us.json +++ b/common/src/main/resources/assets/figura/lang/en_us.json @@ -1808,6 +1808,7 @@ "figura.docs.net.socket": "Instance of SocketAPI", "figura.docs.net.is_networking_allowed": "Checks if your avatar can use networking features. Always false if networking is OFF in settings", "figura.docs.net.is_link_allowed": "Checks if specified link allowed for usage in networking api", + "figura.docs.net.new_future": "Creates a new future controlled by Lua code\nReturns 3 values: the created future, a function to complete the future, and a function to make the future error\nIf an (optional) function is supplied, it will be called with the latter two functions, like JavaScript\nAny error that this function throws will automatically cause the future to fail", "figura.docs.http": "A global API that contains HTTP related features", "figura.docs.http.request": "Creates request builder for specified URI", "figura.docs.http_request_builder": "A builder for HTTP request", From 8966c3bd9bea1f455caaf259258da061b11b31fb Mon Sep 17 00:00:00 2001 From: PoolloverNathan Date: Sun, 17 Nov 2024 12:45:14 -0500 Subject: [PATCH 9/9] luaj is very threadsafe --- common/src/main/java/org/figuramc/figura/avatar/Avatar.java | 4 ++++ .../java/org/figuramc/figura/lua/api/data/FiguraFuture.java | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/common/src/main/java/org/figuramc/figura/avatar/Avatar.java b/common/src/main/java/org/figuramc/figura/avatar/Avatar.java index 66b5f3a9f..065eb41e1 100644 --- a/common/src/main/java/org/figuramc/figura/avatar/Avatar.java +++ b/common/src/main/java/org/figuramc/figura/avatar/Avatar.java @@ -301,6 +301,10 @@ public void runPing(int id, byte[] data) { }); } + public void submit(Runnable r) { + events.offer(r); + } + public LuaValue loadScript(String name, String chunk) { return scriptError || luaRuntime == null || !loaded ? null : luaRuntime.load(name, chunk); } diff --git a/common/src/main/java/org/figuramc/figura/lua/api/data/FiguraFuture.java b/common/src/main/java/org/figuramc/figura/lua/api/data/FiguraFuture.java index c95c4e5ae..a531af0bc 100644 --- a/common/src/main/java/org/figuramc/figura/lua/api/data/FiguraFuture.java +++ b/common/src/main/java/org/figuramc/figura/lua/api/data/FiguraFuture.java @@ -69,7 +69,7 @@ public LuaCloseable onFinish(LuaFunction f, @Nullable LuaFunction g) { throw new LuaError("Future::onFinish unavailable for legal reasons"); } else { final var mgr = avatar.luaRuntime.typeManager; - AutoCloseable ab = onFinish(value -> f.invoke(mgr.javaToLua(f))); + AutoCloseable ab = onFinish(value -> avatar.submit(() -> f.invoke(mgr.javaToLua(f)))); AutoCloseable ot = g != null ? onFinishError(g) : null; return new LuaCloseable(() -> { ab.close(); @@ -81,7 +81,7 @@ public LuaCloseable onFinish(LuaFunction f, @Nullable LuaFunction g) { @LuaWhitelist @LuaMethodDoc("future.on_finish_error") public LuaCloseable onFinishError(LuaFunction f) { - return new LuaCloseable(onFinishError(err -> f.invoke(err.getMessageObject()))); + return new LuaCloseable(onFinishError(err -> avatar.submit(() -> f.invoke(err.getMessageObject())))); } public AutoCloseable onFinishError(Consumer f) {