From 5d32213922734d8897544b9d4ef8e05e9536fcbd Mon Sep 17 00:00:00 2001 From: gnembon Date: Mon, 16 Dec 2019 08:56:22 -0400 Subject: [PATCH] threading --- .../java/carpet/script/CarpetExpression.java | 107 ++++++++++++++---- .../carpet/script/CarpetScriptServer.java | 4 +- src/main/java/carpet/script/Context.java | 20 ++-- src/main/java/carpet/script/Expression.java | 62 ++++++++++ .../java/carpet/script/value/ListValue.java | 2 +- .../java/carpet/script/value/ThreadValue.java | 77 +++++++++++-- src/main/java/carpet/script/value/Value.java | 2 +- 7 files changed, 230 insertions(+), 44 deletions(-) diff --git a/src/main/java/carpet/script/CarpetExpression.java b/src/main/java/carpet/script/CarpetExpression.java index 7c972297..e5a27ac0 100644 --- a/src/main/java/carpet/script/CarpetExpression.java +++ b/src/main/java/carpet/script/CarpetExpression.java @@ -101,6 +101,7 @@ import java.util.Locale; import java.util.Map; import java.util.Optional; import java.util.UUID; +import java.util.concurrent.CompletableFuture; import java.util.function.BiFunction; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -969,27 +970,83 @@ public class CarpetExpression if (sourceBlockState == targetBlockState && data == null) return (c_, t_) -> Value.FALSE; - CarpetSettings.impendingFillSkipUpdates = !CarpetSettings.fillUpdates; + BlockState finalSourceBlockState = sourceBlockState; BlockPos targetPos = targetLocator.block.getPos(); - Clearable.clear(world.getBlockEntity(targetPos)); - world.setBlockState(targetPos, sourceBlockState, 2); - - if ( data != null) + cc.s.getMinecraftServer().executeSync( () -> { - BlockEntity be = world.getBlockEntity(targetPos); - if (be != null) + CarpetSettings.impendingFillSkipUpdates = !CarpetSettings.fillUpdates; + Clearable.clear(world.getBlockEntity(targetPos)); + world.setBlockState(targetPos, finalSourceBlockState, 2); + if (data != null) { - CompoundTag destTag = data.method_10553(); - destTag.putInt("x", targetPos.getX()); - destTag.putInt("y", targetPos.getY()); - destTag.putInt("z", targetPos.getZ()); - be.fromTag(destTag); - be.markDirty(); + BlockEntity be = world.getBlockEntity(targetPos); + if (be != null) + { + CompoundTag destTag = data.method_10553(); + destTag.putInt("x", targetPos.getX()); + destTag.putInt("y", targetPos.getY()); + destTag.putInt("z", targetPos.getZ()); + be.fromTag(destTag); + be.markDirty(); + } + } + CarpetSettings.impendingFillSkipUpdates = false; + }); + Value retval = new BlockValue(finalSourceBlockState, world, targetLocator.block.getPos()); + return (c_, t_) -> retval; + }); + + this.expr.addLazyFunction("set_async", -1, (c, t, lv) -> + { + CarpetContext cc = (CarpetContext)c; + ServerWorld world = cc.s.getWorld(); + if (lv.size() < 2 || lv.size() % 2 == 1) + throw new InternalExpressionException("'set' should have at least 2 params and odd attributes"); + BlockValue.LocatorResult targetLocator = BlockValue.fromParams(cc, lv, 0); + BlockValue.LocatorResult sourceLocator = BlockValue.fromParams(cc, lv, targetLocator.offset, true); + BlockState sourceBlockState = sourceLocator.block.getBlockState(); + BlockState targetBlockState = world.getBlockState(targetLocator.block.getPos()); + if (sourceLocator.offset < lv.size()) + { + StateFactory states = sourceBlockState.getBlock().getStateFactory(); + for (int i = sourceLocator.offset; i < lv.size(); i += 2) + { + String paramString = lv.get(i).evalValue(c).getString(); + Property property = states.getProperty(paramString); + if (property == null) + throw new InternalExpressionException("Property " + paramString + " doesn't apply to " + sourceLocator.block.getString()); + String paramValue = lv.get(i + 1).evalValue(c).getString(); + sourceBlockState = setProperty(property, paramString, paramValue, sourceBlockState); } } - CarpetSettings.impendingFillSkipUpdates = false; - Value retval = new BlockValue(sourceBlockState, world, targetLocator.block.getPos()); + CompoundTag data = sourceLocator.block.getData(); + + if (sourceBlockState == targetBlockState && data == null) + return (c_, t_) -> Value.FALSE; + BlockState finalSourceBlockState = sourceBlockState; + BlockPos targetPos = targetLocator.block.getPos(); + cc.s.getMinecraftServer().execute( () -> + { + CarpetSettings.impendingFillSkipUpdates = !CarpetSettings.fillUpdates; + Clearable.clear(world.getBlockEntity(targetPos)); + world.setBlockState(targetPos, finalSourceBlockState, 2); + if (data != null) + { + BlockEntity be = world.getBlockEntity(targetPos); + if (be != null) + { + CompoundTag destTag = data.method_10553(); + destTag.putInt("x", targetPos.getX()); + destTag.putInt("y", targetPos.getY()); + destTag.putInt("z", targetPos.getZ()); + be.fromTag(destTag); + be.markDirty(); + } + } + CarpetSettings.impendingFillSkipUpdates = false; + }); + Value retval = new BlockValue(finalSourceBlockState, world, targetLocator.block.getPos()); return (c_, t_) -> retval; }); @@ -2982,6 +3039,7 @@ public class CarpetExpression this.expr.addLazyFunction("game_tick", -1, (c, t, lv) -> { ServerCommandSource s = ((CarpetContext)c).s; + if (!s.getMinecraftServer().isOnThread()) throw new InternalExpressionException("Unable to run ticks from threads"); ((MinecraftServerInterface)s.getMinecraftServer()).forceTick( () -> System.nanoTime()- CarpetServer.scriptServer.tickStart<50000000L); if (lv.size()>0) { @@ -3064,12 +3122,19 @@ public class CarpetExpression if (lv.size() <= locator.offset) throw new InternalExpressionException("'plop' needs extra argument indicating what to plop"); String what = lv.get(locator.offset).evalValue(c).getString(); - Boolean res = FeatureGenerator.spawn(what, ((CarpetContext)c).s.getWorld(), locator.block.getPos()); - if (res == null) - return (c_, t_) -> Value.NULL; - if (what.equalsIgnoreCase("boulder")) // there might be more of those - this.forceChunkUpdate(locator.block.getPos(), ((CarpetContext)c).s.getWorld()); - return (c_, t_) -> new NumericValue(res); + Value [] result = new Value[]{Value.NULL}; + ((CarpetContext)c).s.getMinecraftServer().executeSync( () -> + { + + Boolean res = FeatureGenerator.spawn(what, ((CarpetContext) c).s.getWorld(), locator.block.getPos()); + if (res == null) + return; + if (what.equalsIgnoreCase("boulder")) // there might be more of those + this.forceChunkUpdate(locator.block.getPos(), ((CarpetContext) c).s.getWorld()); + result[0] = new NumericValue(res); + }); + Value ret = result[0]; // preventing from lazy evaluating of the result in case a future completes later + return (_c, _t) -> ret; }); this.expr.addLazyFunction("schedule", -1, (c, t, lv) -> { diff --git a/src/main/java/carpet/script/CarpetScriptServer.java b/src/main/java/carpet/script/CarpetScriptServer.java index 47cb273f..1df304c6 100644 --- a/src/main/java/carpet/script/CarpetScriptServer.java +++ b/src/main/java/carpet/script/CarpetScriptServer.java @@ -38,9 +38,9 @@ public class CarpetScriptServer //make static for now, but will change that later: public CarpetScriptHost globalHost; public Map modules; - long tickStart; + public long tickStart; public boolean stopAll; - Set holyMoly; + private Set holyMoly; public CarpetEventServer events; public static List bundledModuleData = new ArrayList(){{ diff --git a/src/main/java/carpet/script/Context.java b/src/main/java/carpet/script/Context.java index fb9d5ff2..092a5697 100644 --- a/src/main/java/carpet/script/Context.java +++ b/src/main/java/carpet/script/Context.java @@ -6,16 +6,16 @@ import java.util.Set; public class Context { - static final int NONE = 0; - static final int VOID = 1; - static final int BOOLEAN = 2; - static final int NUMBER = 3; - static final int STRING = 4; - static final int LIST = 5; - static final int ITERATOR = 6; - static final int SIGNATURE = 7; - static final int LOCALIZATION = 8; - static final int LVALUE = 9; + public static final int NONE = 0; + public static final int VOID = 1; + public static final int BOOLEAN = 2; + public static final int NUMBER = 3; + public static final int STRING = 4; + public static final int LIST = 5; + public static final int ITERATOR = 6; + public static final int SIGNATURE = 7; + public static final int LOCALIZATION = 8; + public static final int LVALUE = 9; public Map variables = new HashMap<>(); diff --git a/src/main/java/carpet/script/Expression.java b/src/main/java/carpet/script/Expression.java index 7b80332f..9af5fa2f 100644 --- a/src/main/java/carpet/script/Expression.java +++ b/src/main/java/carpet/script/Expression.java @@ -23,6 +23,7 @@ import carpet.script.value.ListValue; import carpet.script.value.MapValue; import carpet.script.value.NumericValue; import carpet.script.value.StringValue; +import carpet.script.value.ThreadValue; import carpet.script.value.Value; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import org.apache.commons.lang3.text.WordUtils; @@ -2992,6 +2993,67 @@ public class Expression implements Cloneable return (cc, tt) -> retval; }); + addLazyFunctionWithDelegation("task", -1, (c, t, expr, tok, lv) -> { + Value queue = Value.NULL; + Value funcDesc; + if (lv.size()==1) + { + funcDesc = lv.get(0).evalValue(c); + } + else if (lv.size() == 2) + { + queue = lv.get(0).evalValue(c); + funcDesc = lv.get(1).evalValue(c); + } + else + { + throw new InternalExpressionException("'task' takes one or two parameters"); + } + FunctionValue fun; + if (!(funcDesc instanceof FunctionValue)) + { + String name = funcDesc.getString(); + funcDesc = c.host.globalFunctions.get(name); + if (funcDesc == null) + throw new InternalExpressionException("Function "+name+" is not defined yet"); + } + fun = (FunctionValue)funcDesc; + if (fun.getArguments().size() > 0) + throw new InternalExpressionException("tasks can only accept functions that don't take any arguments"); + ThreadValue thread = new ThreadValue(queue, fun, expr, tok, c); + Thread.yield(); + return (cc, tt) -> thread; + }); + + addFunction("task_count", (lv) -> + { + if (lv.size() > 0) + { + return new NumericValue(ThreadValue.taskCount(lv.get(0))); + } + return new NumericValue(ThreadValue.taskCount()); + }); + + addUnaryFunction("join_task", (v) -> { + if (!(v instanceof ThreadValue)) + throw new InternalExpressionException("'join_task' could only be used with a task value"); + return ((ThreadValue) v).join(); + }); + + addUnaryFunction("stop_task", (v) -> { + if (!(v instanceof ThreadValue)) + throw new InternalExpressionException("'join_task' could only be used with a task value"); + ((ThreadValue) v).stop(); + return Value.NULL; + }); + + addUnaryFunction("is_task_completed", (v) -> { + if (!(v instanceof ThreadValue)) + throw new InternalExpressionException("'join_task' could only be used with a task value"); + return new NumericValue(((ThreadValue) v).isFinished()); + }); + + } static Expression none = new Expression("null"); diff --git a/src/main/java/carpet/script/value/ListValue.java b/src/main/java/carpet/script/value/ListValue.java index acb657a7..80ef40f7 100644 --- a/src/main/java/carpet/script/value/ListValue.java +++ b/src/main/java/carpet/script/value/ListValue.java @@ -246,7 +246,7 @@ public class ListValue extends AbstractListValue implements ContainerValueInterf return items; } - public Iterator iterator() { return new ArrayList<>(items).iterator(); } + public Iterator iterator() { return new ArrayList<>(items).iterator(); } // should be thread safe public void extend(List subList) { diff --git a/src/main/java/carpet/script/value/ThreadValue.java b/src/main/java/carpet/script/value/ThreadValue.java index 3fd07772..382cd6b2 100644 --- a/src/main/java/carpet/script/value/ThreadValue.java +++ b/src/main/java/carpet/script/value/ThreadValue.java @@ -1,29 +1,49 @@ package carpet.script.value; import carpet.script.Context; -import carpet.script.LazyValue; +import carpet.script.Expression; +import carpet.script.Tokenizer; import carpet.script.exception.InternalExpressionException; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.function.Supplier; public class ThreadValue extends Value { - CompletableFuture taskFuture; + private CompletableFuture taskFuture; private long id; + private final Object lock; + private final boolean[] started; + private Suppliertask; private static long sequence = 0L; - private static final ExecutorService executorService = Executors.newCachedThreadPool(); + private static final Map executorServices = new HashMap<>(); - public ThreadValue(LazyValue expression, Context ctx) + public ThreadValue(Value pool, FunctionValue function, Expression expr, Tokenizer.Token token, Context ctx) { id = sequence++; + lock = new Object(); + started = new boolean[]{false}; + task = () -> + { + if (!started[0]) + { + started[0] = true; + return function.lazyEval(ctx, Context.NONE, expr, token, Collections.emptyList()).evalValue(ctx); + } + return null; + }; taskFuture = CompletableFuture.supplyAsync( - () -> expression.evalValue(ctx), - executorService + () -> { synchronized (lock) { return task.get(); } }, + executorServices.computeIfAbsent(pool, (v) -> (ThreadPoolExecutor)Executors.newCachedThreadPool()) ); + Thread.yield(); } @Override @@ -40,6 +60,18 @@ public class ThreadValue extends Value public Value join() { + if (!started[0]) + { + synchronized (lock) + { + if (!started[0]) + { + Value res = task.get(); + taskFuture.complete(res); + return res; + } + } + } try { return taskFuture.get(); @@ -57,7 +89,14 @@ public class ThreadValue extends Value public void stop() { - taskFuture.complete(Value.NULL); + synchronized (lock) + { + if (!taskFuture.isDone()) + { + started[0] = true; + taskFuture.complete(Value.NULL); + } + } } @Override @@ -72,7 +111,27 @@ public class ThreadValue extends Value public int compareTo(Value o) { if (!(o instanceof ThreadValue)) - throw new InternalExpressionException("Cannot compare "); + throw new InternalExpressionException("Cannot compare tasks to other types"); return (int) (this.id - ((ThreadValue) o).id); } + + @Override + public int hashCode() + { + return Long.hashCode(id); + } + + public static int taskCount() + { + return executorServices.values().stream().map(ThreadPoolExecutor::getActiveCount).reduce(0, Integer::sum); + } + + public static int taskCount(Value pool) + { + if (executorServices.containsKey(pool)) + { + return executorServices.get(pool).getActiveCount(); + } + return 0; + } } diff --git a/src/main/java/carpet/script/value/Value.java b/src/main/java/carpet/script/value/Value.java index 8faf4c15..3755ca8a 100644 --- a/src/main/java/carpet/script/value/Value.java +++ b/src/main/java/carpet/script/value/Value.java @@ -110,7 +110,7 @@ public abstract class Value implements Comparable, Cloneable @Override public int compareTo(final Value o) { - if (o instanceof NumericValue || o instanceof ListValue) + if (o instanceof NumericValue || o instanceof ListValue || o instanceof ThreadValue) { return -o.compareTo(this); }