From 146573803ce68e2fbe83555dde6e9cda16de9b47 Mon Sep 17 00:00:00 2001 From: gnembon <41132274+gnembon@users.noreply.github.com> Date: Wed, 22 Feb 2023 02:32:53 +0100 Subject: [PATCH] scarpet corountines idea --- .../java/carpet/script/CarpetScriptHost.java | 10 +- src/main/java/carpet/script/Context.java | 14 ++ src/main/java/carpet/script/ScriptHost.java | 5 + .../java/carpet/script/language/Loops.java | 18 ++ .../carpet/script/language/Threading.java | 59 +++++++ .../script/value/AbstractListValue.java | 10 +- .../carpet/script/value/FunctionValue.java | 12 +- .../carpet/script/value/LazyListValue.java | 7 +- .../java/carpet/script/value/ThreadValue.java | 164 ++++++++++++++++-- .../java/carpet/script/value/UndefValue.java | 1 + src/main/java/carpet/script/value/Value.java | 1 + 11 files changed, 284 insertions(+), 17 deletions(-) diff --git a/src/main/java/carpet/script/CarpetScriptHost.java b/src/main/java/carpet/script/CarpetScriptHost.java index eba43377..df94add2 100644 --- a/src/main/java/carpet/script/CarpetScriptHost.java +++ b/src/main/java/carpet/script/CarpetScriptHost.java @@ -866,7 +866,7 @@ public class CarpetScriptHost extends ScriptHost assertAppIntegrity(function.getModule()); Context context = new CarpetContext(this, source); return function.getExpression().evalValue( - () -> function.execute(context, Context.VOID, function.getExpression(), function.getToken(), argv), + () -> function.execute(context, Context.VOID, function.getExpression(), function.getToken(), argv, null), context, Context.VOID ); @@ -903,7 +903,7 @@ public class CarpetScriptHost extends ScriptHost assertAppIntegrity(fun.getModule()); Context context = new CarpetContext(this, source, origin); return fun.getExpression().evalValue( - () -> fun.execute(context, Context.VOID, fun.getExpression(), fun.getToken(), argv), + () -> fun.execute(context, Context.VOID, fun.getExpression(), fun.getToken(), argv, null), context, Context.VOID); } @@ -1215,4 +1215,10 @@ public class CarpetScriptHost extends ScriptHost } return false; } + + @Override + public boolean canSynchronouslyExecute() + { + return !scriptServer().server.isSameThread(); + } } diff --git a/src/main/java/carpet/script/Context.java b/src/main/java/carpet/script/Context.java index 35c46cd0..01c761ca 100644 --- a/src/main/java/carpet/script/Context.java +++ b/src/main/java/carpet/script/Context.java @@ -1,6 +1,7 @@ package carpet.script; import carpet.script.exception.InternalExpressionException; +import carpet.script.value.ThreadValue; import carpet.script.value.Value; import java.util.HashMap; @@ -30,6 +31,8 @@ public class Context public final ScriptHost host; + private ThreadValue threadContext = null; + public Context(ScriptHost host) { this.host = host; @@ -69,10 +72,21 @@ public class Context public Context recreate() { Context ctx = duplicate(); + ctx.threadContext = threadContext; ctx.initialize(); return ctx; } + public void setThreadContext(ThreadValue callingThread) + { + this.threadContext = callingThread; + } + + public ThreadValue getThreadContext() + { + return threadContext; + } + protected void initialize() { //special variables for second order functions so we don't need to check them all the time diff --git a/src/main/java/carpet/script/ScriptHost.java b/src/main/java/carpet/script/ScriptHost.java index 3fbf3121..91901953 100644 --- a/src/main/java/carpet/script/ScriptHost.java +++ b/src/main/java/carpet/script/ScriptHost.java @@ -58,6 +58,11 @@ public abstract class ScriptHost return scriptServer.resolveResource(suffix); } + public boolean canSynchronouslyExecute() + { + return true; + } + public static class ModuleData { Module parent; diff --git a/src/main/java/carpet/script/language/Loops.java b/src/main/java/carpet/script/language/Loops.java index ac42642b..5d6ac58f 100644 --- a/src/main/java/carpet/script/language/Loops.java +++ b/src/main/java/carpet/script/language/Loops.java @@ -143,6 +143,9 @@ public class Loops for (int i = 0; iterator.hasNext(); i++) { Value next = iterator.next(); + if(next == Value.EOL) { + continue; + } String variable = next.boundVariable; next.bindTo("_"); int doYouReally = i; @@ -197,6 +200,9 @@ public class Loops for (int i = 0; iterator.hasNext(); i++) { Value next = iterator.next(); + if(next == Value.EOL) { + continue; + } String veriable = next.boundVariable; next.bindTo("_"); int seriously = i; @@ -254,6 +260,9 @@ public class Loops for (int i = 0; iterator.hasNext(); i++) { Value next = iterator.next(); + if(next == Value.EOL) { + continue; + } String variable = next.boundVariable; next.bindTo("_"); int seriously = i; @@ -311,6 +320,9 @@ public class Loops for (int i = 0; iterator.hasNext(); i++) { Value next = iterator.next(); + if(next == Value.EOL) { + continue; + } String variable = next.boundVariable; next.bindTo("_"); int seriously = i; @@ -381,6 +393,9 @@ public class Loops for (int i = 0; iterator.hasNext(); i++) { Value next = iterator.next(); + if(next == Value.EOL) { + continue; + } String variable = next.boundVariable; next.bindTo("_"); int seriously = i; @@ -452,6 +467,9 @@ public class Loops for (int i = 0; iterator.hasNext(); i++) { Value next = iterator.next(); + if(next == Value.EOL) { + continue; + } String variable = next.boundVariable; next.bindTo("_"); Value promiseWontChangeYou = acc; diff --git a/src/main/java/carpet/script/language/Threading.java b/src/main/java/carpet/script/language/Threading.java index a6f4a0a7..a129d097 100644 --- a/src/main/java/carpet/script/language/Threading.java +++ b/src/main/java/carpet/script/language/Threading.java @@ -1,5 +1,6 @@ package carpet.script.language; +import carpet.script.Context; import carpet.script.Expression; import carpet.script.argument.FunctionArgument; import carpet.script.exception.ExitStatement; @@ -128,5 +129,63 @@ public class Threading } return (cc, tt) -> new NumericValue(time); // pass through for variables }); + + expression.addLazyFunction("yield", (c, t, lv) -> + { + if (c.getThreadContext() == null) + { + throw new InternalExpressionException("'yield' can only be used in a task"); + } + if (lv.isEmpty()) + { + throw new InternalExpressionException("'yield' requires at least one argument"); + } + boolean lock = lv.size() > 1 && lv.get(1).evalValue(c, Context.BOOLEAN).getBoolean(); + Value value = lv.get(0).evalValue(c); + Value ret = c.getThreadContext().ping(value, lock); + return (cc, tt) -> ret; + }); + + expression.addLazyFunction("task_send", 2, (c, t, lv) -> + { + Value threadValue = lv.get(0).evalValue(c); + if (!(threadValue instanceof ThreadValue thread)) + { + throw new InternalExpressionException("'task_next' requires a task value"); + } + if (!thread.isCoroutine) + { + throw new InternalExpressionException("'task_next' requires a coroutine task value"); + } + Value ret = lv.get(1).evalValue(c); + thread.send(ret); + return (cc, tt) -> Value.NULL; + }); + + expression.addLazyFunction("task_await", 1, (c, t, lv) -> + { + Value threadValue = lv.get(0).evalValue(c); + if (!(threadValue instanceof ThreadValue thread)) + { + throw new InternalExpressionException("'task_await' requires a task value"); + } + if (!thread.isCoroutine) + { + throw new InternalExpressionException("'task_await' requires a coroutine task value"); + } + Value ret = thread.next(); + return ret == Value.EOL ? ((cc, tt) -> Value.NULL) : ((cc, tt) -> ret); + }); + + expression.addLazyFunction("task_ready", 1, (c, t, lv) -> + { + Value threadValue = lv.get(0).evalValue(c); + if (!(threadValue instanceof ThreadValue thread)) + { + throw new InternalExpressionException("'task_ready' requires a task value"); + } + boolean ret = thread.isCoroutine && thread.hasNext(); + return (cc, tt) -> BooleanValue.of(ret); + }); } } diff --git a/src/main/java/carpet/script/value/AbstractListValue.java b/src/main/java/carpet/script/value/AbstractListValue.java index 511ccad0..b9d91764 100644 --- a/src/main/java/carpet/script/value/AbstractListValue.java +++ b/src/main/java/carpet/script/value/AbstractListValue.java @@ -3,13 +3,21 @@ package carpet.script.value; import carpet.script.exception.InternalExpressionException; import com.google.common.collect.Lists; +import java.util.ArrayList; import java.util.List; public abstract class AbstractListValue extends Value implements Iterable { public List unpack() { - List retVal = Lists.newArrayList(iterator()); + ArrayList retVal = Lists.newArrayList(); + for (Value value : this) + { + if (value != Value.EOL) + { + retVal.add(value); + } + } fatality(); return retVal; } diff --git a/src/main/java/carpet/script/value/FunctionValue.java b/src/main/java/carpet/script/value/FunctionValue.java index 010dedec..1a44c427 100644 --- a/src/main/java/carpet/script/value/FunctionValue.java +++ b/src/main/java/carpet/script/value/FunctionValue.java @@ -22,6 +22,8 @@ import java.util.function.Consumer; import net.minecraft.nbt.StringTag; import net.minecraft.nbt.Tag; +import javax.annotation.Nullable; + public class FunctionValue extends Value implements Fluff.ILazyFunction { private final Expression expression; @@ -158,7 +160,7 @@ public class FunctionValue extends Value implements Fluff.ILazyFunction { try { - return execute(c, type, expression, token, params); + return execute(c, type, expression, token, params, null); } catch (ExpressionException exc) { @@ -214,10 +216,10 @@ public class FunctionValue extends Value implements Fluff.ILazyFunction public LazyValue lazyEval(Context c, Context.Type type, Expression e, Tokenizer.Token t, List lazyParams) { List resolvedParams = unpackArgs(lazyParams, c); - return execute(c, type, e, t, resolvedParams); + return execute(c, type, e, t, resolvedParams, null); } - public LazyValue execute(Context c, Context.Type type, Expression e, Tokenizer.Token t, List params) + public LazyValue execute(Context c, Context.Type type, Expression e, Tokenizer.Token t, List params, @Nullable ThreadValue freshNewCallingThread) { assertArgsOk(params, fixedArgs -> { if (fixedArgs) // wrong number of args for fixed args @@ -237,6 +239,10 @@ public class FunctionValue extends Value implements Fluff.ILazyFunction ); }); Context newFrame = c.recreate(); + if (freshNewCallingThread != null) + { + newFrame.setThreadContext(freshNewCallingThread); + } if (outerState != null) { diff --git a/src/main/java/carpet/script/value/LazyListValue.java b/src/main/java/carpet/script/value/LazyListValue.java index f3984ac0..5a2eba0e 100644 --- a/src/main/java/carpet/script/value/LazyListValue.java +++ b/src/main/java/carpet/script/value/LazyListValue.java @@ -137,7 +137,12 @@ public abstract class LazyListValue extends AbstractListValue implements Iterato public List unroll() { List result = new ArrayList<>(); - this.forEachRemaining(result::add); + this.forEachRemaining(v -> { + if (v != Value.EOL) + { + result.add(v); + } + }); fatality(); return result; } diff --git a/src/main/java/carpet/script/value/ThreadValue.java b/src/main/java/carpet/script/value/ThreadValue.java index 4b1c856d..0922db97 100644 --- a/src/main/java/carpet/script/value/ThreadValue.java +++ b/src/main/java/carpet/script/value/ThreadValue.java @@ -7,34 +7,39 @@ import carpet.script.exception.ExitStatement; import carpet.script.exception.ExpressionException; import carpet.script.exception.InternalExpressionException; +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.Iterator; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; +import java.util.concurrent.atomic.AtomicReference; import net.minecraft.nbt.Tag; -public class ThreadValue extends Value +public class ThreadValue extends LazyListValue { private final CompletableFuture taskFuture; private final long id; private static long sequence = 0L; - - public ThreadValue(CompletableFuture taskFuture) - { - this.taskFuture = taskFuture; - this.id = sequence++; - } + private final Deque coState = new ArrayDeque<>(); + private final AtomicReference coLock = new AtomicReference<>(Value.EOL); + public final boolean isCoroutine; public ThreadValue(Value pool, FunctionValue function, Expression expr, Tokenizer.Token token, Context ctx, List args) { - this(getCompletableFutureFromFunction(pool, function, expr, token, ctx, args)); + this.id = sequence++; + this.isCoroutine = ctx.host.canSynchronouslyExecute(); + this.taskFuture = getCompletableFutureFromFunction(pool, function, expr, token, ctx, args); + Thread.yield(); } - public static CompletableFuture getCompletableFutureFromFunction(Value pool, FunctionValue function, Expression expr, Tokenizer.Token token, Context ctx, List args) + public CompletableFuture getCompletableFutureFromFunction(Value pool, FunctionValue function, Expression expr, Tokenizer.Token token, Context ctx, List args) { ExecutorService executor = ctx.host.getExecutor(pool); + ThreadValue callingThread = isCoroutine ? this : null; if (executor == null) { // app is shutting down - no more threads can be spawned. @@ -45,7 +50,7 @@ public class ThreadValue extends Value return CompletableFuture.supplyAsync(() -> { try { - return function.execute(ctx, Context.NONE, expr, token, args).evalValue(ctx); + return function.execute(ctx, Context.NONE, expr, token, args, callingThread).evalValue(ctx); } catch (ExitStatement exit) { @@ -137,4 +142,143 @@ public class ThreadValue extends Value { return "task"; } + + @Override + public void fatality() + { + // we signal that won't be interested in the co-thread anymore + // but threads run client code, so we can't just kill them + } + + @Override + public void reset() + { + //throw new InternalExpressionException("Illegal operation on a task"); + } + + + @Override + public Iterator iterator() + { + if (!isCoroutine) + { + throw new InternalExpressionException("Cannot iterate over this task"); + } + return this; + } + + @Override + public boolean hasNext() + { + return !(coState.isEmpty() && taskFuture.isDone()); + } + + @Override + public Value next() + { + Value popped = null; + synchronized (coState) + { + while (true) + { + if (!coState.isEmpty()) + { + popped = coState.pop(); + } + else if (taskFuture.isDone()) + { + popped = Value.EOL; + } + if (popped != null) + { + break; + } + try + { + coState.wait(1); + } + catch (InterruptedException ignored) + { + } + } + coState.notifyAll(); + } + return popped; + } + + public void send(Value value) + { + synchronized (coLock) + { + coLock.set(value); + coLock.notifyAll(); + } + } + + public Value ping(Value value, boolean lock) + { + synchronized (coState) + { + try + { + if (!lock) + { + coState.add(value); + return Value.NULL; + } + while (true) + { + if (coState.isEmpty()) + { + coState.add(value); + break; + } + try + { + coState.wait(1); + } + + catch (InterruptedException ignored) + { + } + } + } + finally + { + coState.notifyAll(); + } + } + + // locked mode + + synchronized (coLock) + { + Value ret; + try + { + while (true) + { + Value current = coLock.get(); + if (current != Value.EOL) + { + ret = current; + coLock.set(Value.EOL); + break; + } + try + { + coLock.wait(1); + } + catch (InterruptedException ignored) + { + } + } + } + finally + { + coLock.notifyAll(); + } + return ret; + } + } } diff --git a/src/main/java/carpet/script/value/UndefValue.java b/src/main/java/carpet/script/value/UndefValue.java index 69c96b27..9b6c8e79 100644 --- a/src/main/java/carpet/script/value/UndefValue.java +++ b/src/main/java/carpet/script/value/UndefValue.java @@ -7,6 +7,7 @@ import net.minecraft.nbt.Tag; public class UndefValue extends NullValue { public static final UndefValue UNDEF = new UndefValue(); + public static final UndefValue EOL = new UndefValue(); private RuntimeException getError() { diff --git a/src/main/java/carpet/script/value/Value.java b/src/main/java/carpet/script/value/Value.java index 2f1b89bf..ba629111 100644 --- a/src/main/java/carpet/script/value/Value.java +++ b/src/main/java/carpet/script/value/Value.java @@ -25,6 +25,7 @@ public abstract class Value implements Comparable, Cloneable public static final NullValue NULL = NullValue.NULL; public static final UndefValue UNDEF = UndefValue.UNDEF; + public static final UndefValue EOL = UndefValue.EOL; public String boundVariable;