From 04cd9ce93ece89fc019787dc086d25299708fa14 Mon Sep 17 00:00:00 2001 From: gnembon Date: Fri, 13 Dec 2019 01:25:08 -0400 Subject: [PATCH 01/14] should that fix mapping issues? --- .../mixins/BlazeEntityShootFireballGoal_extremeMixin.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/carpet/mixins/BlazeEntityShootFireballGoal_extremeMixin.java b/src/main/java/carpet/mixins/BlazeEntityShootFireballGoal_extremeMixin.java index 8ec6217d..e0cebe60 100644 --- a/src/main/java/carpet/mixins/BlazeEntityShootFireballGoal_extremeMixin.java +++ b/src/main/java/carpet/mixins/BlazeEntityShootFireballGoal_extremeMixin.java @@ -11,7 +11,7 @@ import java.util.Random; @Mixin(targets = "net.minecraft.entity.mob.BlazeEntity$ShootFireballGoal") public class BlazeEntityShootFireballGoal_extremeMixin { - @Redirect(method = "tick", expect = 2, at = @At( + @Redirect(method = "tick()V", expect = 2, at = @At( value = "INVOKE", target = "Ljava/util/Random;nextGaussian()D" )) From 5d32213922734d8897544b9d4ef8e05e9536fcbd Mon Sep 17 00:00:00 2001 From: gnembon Date: Mon, 16 Dec 2019 08:56:22 -0400 Subject: [PATCH 02/14] 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); } From 9ba573afe6b92a2aeacb9ad818a5bad640f0b6ed Mon Sep 17 00:00:00 2001 From: gnembon Date: Tue, 17 Dec 2019 10:28:14 -0400 Subject: [PATCH 03/14] threading testing --- ...nager_scarpetThreadingProtectionMixin.java | 39 +++++++++++++ .../java/carpet/script/CarpetExpression.java | 5 ++ .../java/carpet/script/value/ThreadValue.java | 55 ++++++++++++++----- src/main/resources/carpet.mixins.json | 1 + 4 files changed, 86 insertions(+), 14 deletions(-) create mode 100644 src/main/java/carpet/mixins/ServerChunkManager_scarpetThreadingProtectionMixin.java diff --git a/src/main/java/carpet/mixins/ServerChunkManager_scarpetThreadingProtectionMixin.java b/src/main/java/carpet/mixins/ServerChunkManager_scarpetThreadingProtectionMixin.java new file mode 100644 index 00000000..897b53b7 --- /dev/null +++ b/src/main/java/carpet/mixins/ServerChunkManager_scarpetThreadingProtectionMixin.java @@ -0,0 +1,39 @@ +package carpet.mixins; + +import carpet.script.exception.InternalExpressionException; +import carpet.script.value.ThreadValue; +import net.minecraft.server.world.ServerChunkManager; +import net.minecraft.world.chunk.Chunk; +import net.minecraft.world.chunk.ChunkStatus; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.Redirect; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import java.util.function.Supplier; + +@Mixin(ServerChunkManager.class) +public class ServerChunkManager_scarpetThreadingProtectionMixin +{ + @Redirect(method = "getChunk(IILnet/minecraft/world/chunk/ChunkStatus;Z)Lnet/minecraft/world/chunk/Chunk;", at = @At( + value = "INVOKE", + target = "Ljava/util/concurrent/CompletableFuture;join()Ljava/lang/Object;", + ordinal = 0 + )) + private Object checkJoiningThreads(CompletableFuture completableFuture) + { + synchronized (ThreadValue.waitingTreads) + { + if (ThreadValue.waitingTreads.containsKey(Thread.currentThread())) + throw new InternalExpressionException("Cannot join/wait on world accessing thread"); + ThreadValue.waitingTreads.put(Thread.currentThread(), 1); + } + Chunk res = completableFuture.join(); + ThreadValue.waitingTreads.remove(Thread.currentThread()); + return res; + + } +} diff --git a/src/main/java/carpet/script/CarpetExpression.java b/src/main/java/carpet/script/CarpetExpression.java index e5a27ac0..8596f64b 100644 --- a/src/main/java/carpet/script/CarpetExpression.java +++ b/src/main/java/carpet/script/CarpetExpression.java @@ -944,14 +944,18 @@ public class CarpetExpression this.expr.addLazyFunction("set", -1, (c, t, lv) -> { + CarpetSettings.LOG.error("starting"); 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); + CarpetSettings.LOG.error("dafuq"); BlockState sourceBlockState = sourceLocator.block.getBlockState(); + CarpetSettings.LOG.error("finish dafuq"); BlockState targetBlockState = world.getBlockState(targetLocator.block.getPos()); + CarpetSettings.LOG.error("finish dafuq 2"); if (sourceLocator.offset < lv.size()) { StateFactory states = sourceBlockState.getBlock().getStateFactory(); @@ -965,6 +969,7 @@ public class CarpetExpression sourceBlockState = setProperty(property, paramString, paramValue, sourceBlockState); } } + CarpetSettings.LOG.error("finish properties got"); CompoundTag data = sourceLocator.block.getData(); diff --git a/src/main/java/carpet/script/value/ThreadValue.java b/src/main/java/carpet/script/value/ThreadValue.java index 382cd6b2..6c5409bd 100644 --- a/src/main/java/carpet/script/value/ThreadValue.java +++ b/src/main/java/carpet/script/value/ThreadValue.java @@ -1,5 +1,6 @@ package carpet.script.value; +import carpet.CarpetSettings; import carpet.script.Context; import carpet.script.Expression; import carpet.script.Tokenizer; @@ -9,6 +10,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executors; import java.util.concurrent.ThreadPoolExecutor; @@ -17,9 +19,13 @@ import java.util.function.Supplier; public class ThreadValue extends Value { private CompletableFuture taskFuture; + //public static ThreadLocal joining = ThreadLocal.withInitial(() -> false); + public final static Map waitingTreads = new ConcurrentHashMap<>(); + public static Map joinRequestedThreads = new ConcurrentHashMap<>(); private long id; private final Object lock; - private final boolean[] started; + //private final boolean[] started; + private Thread executingThread; private Suppliertask; private static long sequence = 0L; @@ -29,18 +35,20 @@ public class ThreadValue extends Value { id = sequence++; lock = new Object(); - started = new boolean[]{false}; + executingThread = null; + //started = new boolean[]{false}; task = () -> { - if (!started[0]) + if (executingThread == null) { - started[0] = true; + CarpetSettings.LOG.error("Setting the thread"); + executingThread = Thread.currentThread(); return function.lazyEval(ctx, Context.NONE, expr, token, Collections.emptyList()).evalValue(ctx); } return null; }; taskFuture = CompletableFuture.supplyAsync( - () -> { synchronized (lock) { return task.get(); } }, + () -> { synchronized (lock) { CarpetSettings.LOG.error("getting in future"); Value ret = task.get(); CarpetSettings.LOG.error("gotten in future"); return ret;} }, executorServices.computeIfAbsent(pool, (v) -> (ThreadPoolExecutor)Executors.newCachedThreadPool()) ); Thread.yield(); @@ -60,26 +68,45 @@ public class ThreadValue extends Value public Value join() { - if (!started[0]) + CarpetSettings.LOG.error("in join "+id); + if (executingThread == null) { - synchronized (lock) + CarpetSettings.LOG.error("not started "+id); + Value res; + waitingTreads.put(Thread.currentThread(), 1); + try { - if (!started[0]) - { - Value res = task.get(); - taskFuture.complete(res); - return res; - } + CarpetSettings.LOG.error(" getting result "+id); + res = task.get(); + CarpetSettings.LOG.error(" result got "+id); } + finally + { + waitingTreads.remove(Thread.currentThread()); + } + taskFuture.complete(res); + return res; } try { + synchronized (waitingTreads) + { + if (waitingTreads.containsKey(executingThread)) + throw new InternalExpressionException("Cannot wait/join on world accessing thread"); + waitingTreads.put(executingThread, 1); + } + CarpetSettings.LOG.error("getting future "+id); return taskFuture.get(); } catch (InterruptedException | ExecutionException e) { return Value.NULL; } + finally + { + waitingTreads.remove(executingThread); + CarpetSettings.LOG.error(" future gotten "+id); + } } public boolean isFinished() @@ -93,7 +120,7 @@ public class ThreadValue extends Value { if (!taskFuture.isDone()) { - started[0] = true; + executingThread = Thread.currentThread(); taskFuture.complete(Value.NULL); } } diff --git a/src/main/resources/carpet.mixins.json b/src/main/resources/carpet.mixins.json index 5ce2fe8d..90b4e437 100644 --- a/src/main/resources/carpet.mixins.json +++ b/src/main/resources/carpet.mixins.json @@ -143,6 +143,7 @@ "FallingBlockEntity_scarpetEventsMixin", "ChunkTicketManager_scarpetMixin", "ServerChunkManager_scarpetMixin", + "ServerChunkManager_scarpetThreadingProtectionMixin", "PistonBlock_movableTEMixin", From 423701d4d7c591baad17191be22f7f49470a0342 Mon Sep 17 00:00:00 2001 From: gnembon Date: Tue, 17 Dec 2019 11:58:35 -0400 Subject: [PATCH 04/14] Revert "threading testing" This reverts commit 9ba573af --- ...nager_scarpetThreadingProtectionMixin.java | 39 ------------------- 1 file changed, 39 deletions(-) delete mode 100644 src/main/java/carpet/mixins/ServerChunkManager_scarpetThreadingProtectionMixin.java diff --git a/src/main/java/carpet/mixins/ServerChunkManager_scarpetThreadingProtectionMixin.java b/src/main/java/carpet/mixins/ServerChunkManager_scarpetThreadingProtectionMixin.java deleted file mode 100644 index 897b53b7..00000000 --- a/src/main/java/carpet/mixins/ServerChunkManager_scarpetThreadingProtectionMixin.java +++ /dev/null @@ -1,39 +0,0 @@ -package carpet.mixins; - -import carpet.script.exception.InternalExpressionException; -import carpet.script.value.ThreadValue; -import net.minecraft.server.world.ServerChunkManager; -import net.minecraft.world.chunk.Chunk; -import net.minecraft.world.chunk.ChunkStatus; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.Redirect; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; - -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.Executor; -import java.util.function.Supplier; - -@Mixin(ServerChunkManager.class) -public class ServerChunkManager_scarpetThreadingProtectionMixin -{ - @Redirect(method = "getChunk(IILnet/minecraft/world/chunk/ChunkStatus;Z)Lnet/minecraft/world/chunk/Chunk;", at = @At( - value = "INVOKE", - target = "Ljava/util/concurrent/CompletableFuture;join()Ljava/lang/Object;", - ordinal = 0 - )) - private Object checkJoiningThreads(CompletableFuture completableFuture) - { - synchronized (ThreadValue.waitingTreads) - { - if (ThreadValue.waitingTreads.containsKey(Thread.currentThread())) - throw new InternalExpressionException("Cannot join/wait on world accessing thread"); - ThreadValue.waitingTreads.put(Thread.currentThread(), 1); - } - Chunk res = completableFuture.join(); - ThreadValue.waitingTreads.remove(Thread.currentThread()); - return res; - - } -} From 3295ca2af98db53691ed9c016bdb1203ead775e0 Mon Sep 17 00:00:00 2001 From: gnembon Date: Tue, 17 Dec 2019 11:59:23 -0400 Subject: [PATCH 05/14] Revert "threading testing" This reverts commit 9ba573af --- .../java/carpet/script/CarpetExpression.java | 5 -- .../java/carpet/script/value/ThreadValue.java | 55 +++++-------------- src/main/resources/carpet.mixins.json | 1 - 3 files changed, 14 insertions(+), 47 deletions(-) diff --git a/src/main/java/carpet/script/CarpetExpression.java b/src/main/java/carpet/script/CarpetExpression.java index 8596f64b..e5a27ac0 100644 --- a/src/main/java/carpet/script/CarpetExpression.java +++ b/src/main/java/carpet/script/CarpetExpression.java @@ -944,18 +944,14 @@ public class CarpetExpression this.expr.addLazyFunction("set", -1, (c, t, lv) -> { - CarpetSettings.LOG.error("starting"); 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); - CarpetSettings.LOG.error("dafuq"); BlockState sourceBlockState = sourceLocator.block.getBlockState(); - CarpetSettings.LOG.error("finish dafuq"); BlockState targetBlockState = world.getBlockState(targetLocator.block.getPos()); - CarpetSettings.LOG.error("finish dafuq 2"); if (sourceLocator.offset < lv.size()) { StateFactory states = sourceBlockState.getBlock().getStateFactory(); @@ -969,7 +965,6 @@ public class CarpetExpression sourceBlockState = setProperty(property, paramString, paramValue, sourceBlockState); } } - CarpetSettings.LOG.error("finish properties got"); CompoundTag data = sourceLocator.block.getData(); diff --git a/src/main/java/carpet/script/value/ThreadValue.java b/src/main/java/carpet/script/value/ThreadValue.java index 6c5409bd..382cd6b2 100644 --- a/src/main/java/carpet/script/value/ThreadValue.java +++ b/src/main/java/carpet/script/value/ThreadValue.java @@ -1,6 +1,5 @@ package carpet.script.value; -import carpet.CarpetSettings; import carpet.script.Context; import carpet.script.Expression; import carpet.script.Tokenizer; @@ -10,7 +9,6 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executors; import java.util.concurrent.ThreadPoolExecutor; @@ -19,13 +17,9 @@ import java.util.function.Supplier; public class ThreadValue extends Value { private CompletableFuture taskFuture; - //public static ThreadLocal joining = ThreadLocal.withInitial(() -> false); - public final static Map waitingTreads = new ConcurrentHashMap<>(); - public static Map joinRequestedThreads = new ConcurrentHashMap<>(); private long id; private final Object lock; - //private final boolean[] started; - private Thread executingThread; + private final boolean[] started; private Suppliertask; private static long sequence = 0L; @@ -35,20 +29,18 @@ public class ThreadValue extends Value { id = sequence++; lock = new Object(); - executingThread = null; - //started = new boolean[]{false}; + started = new boolean[]{false}; task = () -> { - if (executingThread == null) + if (!started[0]) { - CarpetSettings.LOG.error("Setting the thread"); - executingThread = Thread.currentThread(); + started[0] = true; return function.lazyEval(ctx, Context.NONE, expr, token, Collections.emptyList()).evalValue(ctx); } return null; }; taskFuture = CompletableFuture.supplyAsync( - () -> { synchronized (lock) { CarpetSettings.LOG.error("getting in future"); Value ret = task.get(); CarpetSettings.LOG.error("gotten in future"); return ret;} }, + () -> { synchronized (lock) { return task.get(); } }, executorServices.computeIfAbsent(pool, (v) -> (ThreadPoolExecutor)Executors.newCachedThreadPool()) ); Thread.yield(); @@ -68,45 +60,26 @@ public class ThreadValue extends Value public Value join() { - CarpetSettings.LOG.error("in join "+id); - if (executingThread == null) + if (!started[0]) { - CarpetSettings.LOG.error("not started "+id); - Value res; - waitingTreads.put(Thread.currentThread(), 1); - try + synchronized (lock) { - CarpetSettings.LOG.error(" getting result "+id); - res = task.get(); - CarpetSettings.LOG.error(" result got "+id); + if (!started[0]) + { + Value res = task.get(); + taskFuture.complete(res); + return res; + } } - finally - { - waitingTreads.remove(Thread.currentThread()); - } - taskFuture.complete(res); - return res; } try { - synchronized (waitingTreads) - { - if (waitingTreads.containsKey(executingThread)) - throw new InternalExpressionException("Cannot wait/join on world accessing thread"); - waitingTreads.put(executingThread, 1); - } - CarpetSettings.LOG.error("getting future "+id); return taskFuture.get(); } catch (InterruptedException | ExecutionException e) { return Value.NULL; } - finally - { - waitingTreads.remove(executingThread); - CarpetSettings.LOG.error(" future gotten "+id); - } } public boolean isFinished() @@ -120,7 +93,7 @@ public class ThreadValue extends Value { if (!taskFuture.isDone()) { - executingThread = Thread.currentThread(); + started[0] = true; taskFuture.complete(Value.NULL); } } diff --git a/src/main/resources/carpet.mixins.json b/src/main/resources/carpet.mixins.json index 90b4e437..5ce2fe8d 100644 --- a/src/main/resources/carpet.mixins.json +++ b/src/main/resources/carpet.mixins.json @@ -143,7 +143,6 @@ "FallingBlockEntity_scarpetEventsMixin", "ChunkTicketManager_scarpetMixin", "ServerChunkManager_scarpetMixin", - "ServerChunkManager_scarpetThreadingProtectionMixin", "PistonBlock_movableTEMixin", From 2ff7988251716c7e748e7754fed6b2f706fd558f Mon Sep 17 00:00:00 2001 From: gnembon Date: Tue, 17 Dec 2019 14:41:47 -0400 Subject: [PATCH 06/14] resolved joins from main thread as illegal --- .../java/carpet/script/CarpetExpression.java | 15 +++++++-- src/main/java/carpet/script/Expression.java | 12 +++++-- .../java/carpet/script/value/ThreadValue.java | 33 +++---------------- 3 files changed, 26 insertions(+), 34 deletions(-) diff --git a/src/main/java/carpet/script/CarpetExpression.java b/src/main/java/carpet/script/CarpetExpression.java index e5a27ac0..ea198f30 100644 --- a/src/main/java/carpet/script/CarpetExpression.java +++ b/src/main/java/carpet/script/CarpetExpression.java @@ -10,6 +10,7 @@ import carpet.script.exception.CarpetExpressionException; import carpet.script.exception.ExitStatement; import carpet.script.exception.ExpressionException; import carpet.script.exception.InternalExpressionException; +import carpet.CarpetSettings; import carpet.script.value.BlockValue; import carpet.script.value.EntityValue; import carpet.script.value.FunctionValue; @@ -19,8 +20,8 @@ import carpet.script.value.NBTSerializableValue; import carpet.script.value.NullValue; import carpet.script.value.NumericValue; import carpet.script.value.StringValue; +import carpet.script.value.ThreadValue; import carpet.script.value.Value; -import carpet.CarpetSettings; import carpet.utils.BlockInfo; import carpet.utils.Messenger; import com.google.common.collect.Sets; @@ -101,7 +102,6 @@ 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; @@ -3006,6 +3006,17 @@ public class CarpetExpression return (_c, _t) -> res; // pass through for variables }); + //"overidden" native call to cancel if on main thread + this.expr.addLazyFunction("join_task", 1, (c, t, lv) -> { + if (((CarpetContext)c).s.getMinecraftServer().isOnThread()) + throw new InternalExpressionException("'join_task' cannot be called from main thread to avoid deadlocks"); + Value v = lv.get(0).evalValue(c); + if (!(v instanceof ThreadValue)) + throw new InternalExpressionException("'join_task' could only be used with a task value"); + Value ret = ((ThreadValue) v).join(); + return (_c, _t) -> ret; + }); + this.expr.addLazyFunction("logger", 1, (c, t, lv) -> { Value res = lv.get(0).evalValue(c); diff --git a/src/main/java/carpet/script/Expression.java b/src/main/java/carpet/script/Expression.java index 9af5fa2f..facb3504 100644 --- a/src/main/java/carpet/script/Expression.java +++ b/src/main/java/carpet/script/Expression.java @@ -11,7 +11,13 @@ import carpet.script.Fluff.QuadFunction; import carpet.script.Fluff.QuinnFunction; import carpet.script.Fluff.SexFunction; import carpet.script.Fluff.TriFunction; -import carpet.script.exception.*; +import carpet.script.exception.BreakStatement; +import carpet.script.exception.ContinueStatement; +import carpet.script.exception.ExitStatement; +import carpet.script.exception.ExpressionException; +import carpet.script.exception.InternalExpressionException; +import carpet.script.exception.ReturnStatement; +import carpet.script.exception.ThrowStatement; import carpet.script.value.AbstractListValue; import carpet.script.value.ContainerValueInterface; import carpet.script.value.FunctionSignatureValue; @@ -3040,12 +3046,12 @@ public class Expression implements Cloneable return ((ThreadValue) v).join(); }); - addUnaryFunction("stop_task", (v) -> { + /*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)) diff --git a/src/main/java/carpet/script/value/ThreadValue.java b/src/main/java/carpet/script/value/ThreadValue.java index 382cd6b2..667e087a 100644 --- a/src/main/java/carpet/script/value/ThreadValue.java +++ b/src/main/java/carpet/script/value/ThreadValue.java @@ -12,36 +12,24 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executors; import java.util.concurrent.ThreadPoolExecutor; -import java.util.function.Supplier; public class ThreadValue extends Value { private CompletableFuture taskFuture; private long id; private final Object lock; - private final boolean[] started; - private Suppliertask; private static long sequence = 0L; - private static final Map executorServices = new HashMap<>(); 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( - () -> { synchronized (lock) { return task.get(); } }, - executorServices.computeIfAbsent(pool, (v) -> (ThreadPoolExecutor)Executors.newCachedThreadPool()) + () -> { synchronized (lock) { + return function.lazyEval(ctx, Context.NONE, expr, token, Collections.emptyList()).evalValue(ctx); + } }, + executorServices.computeIfAbsent(pool, (v) -> (ThreadPoolExecutor)Executors.newCachedThreadPool()) ); Thread.yield(); } @@ -60,18 +48,6 @@ 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(); @@ -93,7 +69,6 @@ public class ThreadValue extends Value { if (!taskFuture.isDone()) { - started[0] = true; taskFuture.complete(Value.NULL); } } From 9457ce741c55bb2eee737c459242d9fe3dc0fa23 Mon Sep 17 00:00:00 2001 From: gnembon Date: Wed, 18 Dec 2019 10:41:03 -0400 Subject: [PATCH 07/14] fixed te fillUpdates and client call fix. better camera --- .../mixins/WorldChunk_fillUpdatesMixin.java | 10 ----- .../java/carpet/script/value/EntityValue.java | 2 +- .../resources/assets/carpet/scripts/camera.sc | 45 ++++++++++--------- 3 files changed, 24 insertions(+), 33 deletions(-) diff --git a/src/main/java/carpet/mixins/WorldChunk_fillUpdatesMixin.java b/src/main/java/carpet/mixins/WorldChunk_fillUpdatesMixin.java index b780dd36..10eb373e 100644 --- a/src/main/java/carpet/mixins/WorldChunk_fillUpdatesMixin.java +++ b/src/main/java/carpet/mixins/WorldChunk_fillUpdatesMixin.java @@ -21,14 +21,4 @@ public class WorldChunk_fillUpdatesMixin if (!CarpetSettings.impendingFillSkipUpdates) blockState.onBlockAdded(world_1, blockPos_1, blockState_1, boolean_1); } - - @Redirect(method = "setBlockState", at = @At( - value = "INVOKE", - target = "Lnet/minecraft/block/BlockState;onBlockRemoved(Lnet/minecraft/world/World;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/block/BlockState;Z)V" - )) - private void onRemoved(BlockState blockState, World world_1, BlockPos blockPos_1, BlockState blockState_1, boolean boolean_1) - { - if (!CarpetSettings.impendingFillSkipUpdates) - blockState.onBlockRemoved(world_1, blockPos_1, blockState_1, boolean_1); - } } diff --git a/src/main/java/carpet/script/value/EntityValue.java b/src/main/java/carpet/script/value/EntityValue.java index 9c29cb09..e1aacde9 100644 --- a/src/main/java/carpet/script/value/EntityValue.java +++ b/src/main/java/carpet/script/value/EntityValue.java @@ -223,7 +223,7 @@ public class EntityValue extends Value put("dimension", (e, a) -> new StringValue(nameFromRegistryId(Registry.DIMENSION.getId(e.dimension)))); put("height", (e, a) -> new NumericValue(e.getDimensions(EntityPose.STANDING).height)); put("width", (e, a) -> new NumericValue(e.getDimensions(EntityPose.STANDING).width)); - put("eye_height", (e, a) -> new NumericValue(e.getEyeHeight(EntityPose.STANDING))); + put("eye_height", (e, a) -> new NumericValue(e.getStandingEyeHeight())); put("age", (e, a) -> new NumericValue(e.age)); put("despawn_timer", (e, a) -> e instanceof LivingEntity?new NumericValue(((LivingEntity) e).getDespawnCounter()):Value.NULL); put("item", (e, a) -> (e instanceof ItemEntity)?ListValue.fromItemStack(((ItemEntity) e).getStack()):Value.NULL); diff --git a/src/main/resources/assets/carpet/scripts/camera.sc b/src/main/resources/assets/carpet/scripts/camera.sc index 960ac306..f06c4948 100644 --- a/src/main/resources/assets/carpet/scripts/camera.sc +++ b/src/main/resources/assets/carpet/scripts/camera.sc @@ -153,28 +153,29 @@ play(fps) -> p = player(); __assert_valid_for_motion(); __prepare_path_if_needed(); - if ((fps % 20 != 0) || fps < 20, exit('FPS needs to be multiples of 20') ); - tpt = round(fps / 20); - mspt = 50 / tpt; - start_time = time(); - point = 0; - loop( length(global_points)-1, - segment = _; - start = global_points:segment:1; - end = global_points:(segment+1):1; - loop(end-start, - v = __get_path_at(segment, start, _); - modify(p, 'location', v); - point += 1; - if ((point % tpt == 0), game_tick()); - end_time = time(); - took = end_time - start_time; - if (took < mspt, sleep(mspt-took)); - start_time = time() - ) - ); - game_tick(1000); - 'Done!' + task( _(outer(fps), outer(p)) -> ( + //if ((fps % 20 != 0) || fps < 20, exit('FPS needs to be multiples of 20') ); + tpt = fps / 20; + mspt = 50 / tpt; + start_time = time(); + point = 0; + loop( length(global_points)-1, + segment = _; + start = global_points:segment:1; + end = global_points:(segment+1):1; + loop(end-start, + v = __get_path_at(segment, start, _); + modify(p, 'location', v); + point += 1; + end_time = time(); + took = end_time - start_time; + if (took < mspt, sleep(mspt-took)); + start_time = time() + ) + ); + 'Done!' + )); + ); _show_path_tick(particle_type, total) -> ( From 0e9566a58d73b55bf5a5f93a008bd749734b6379 Mon Sep 17 00:00:00 2001 From: gnembon Date: Thu, 19 Dec 2019 01:48:33 -0400 Subject: [PATCH 08/14] removed set_async as pointless --- .../java/carpet/script/CarpetExpression.java | 54 ------------------- 1 file changed, 54 deletions(-) diff --git a/src/main/java/carpet/script/CarpetExpression.java b/src/main/java/carpet/script/CarpetExpression.java index ea198f30..a66a9911 100644 --- a/src/main/java/carpet/script/CarpetExpression.java +++ b/src/main/java/carpet/script/CarpetExpression.java @@ -996,60 +996,6 @@ public class CarpetExpression 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); - } - } - - 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; - }); - this.expr.addLazyFunction("destroy", -1, (c, t, lv) -> { CarpetContext cc = (CarpetContext)c; From 54308bb131bbda4426c6a645390d85212c353531 Mon Sep 17 00:00:00 2001 From: gnembon Date: Thu, 19 Dec 2019 01:49:00 -0400 Subject: [PATCH 09/14] camera using threading --- .../resources/assets/carpet/scripts/camera.sc | 75 ++++++++++++------- 1 file changed, 49 insertions(+), 26 deletions(-) diff --git a/src/main/resources/assets/carpet/scripts/camera.sc b/src/main/resources/assets/carpet/scripts/camera.sc index f06c4948..1aaeb742 100644 --- a/src/main/resources/assets/carpet/scripts/camera.sc +++ b/src/main/resources/assets/carpet/scripts/camera.sc @@ -140,42 +140,65 @@ __get_path_at(segment, start, index) -> v ); __invalidate_points_cache() -> global_path_precalculated = map(range(global_points:(-1):1), null); + +global_showing_path = false; +global_playing_path = false; + +stop() -> +( + if (!global_playing_path && !global_showing_path, return('You can only stop playing or showing the path')); + if (global_showing_path, + global_showing_path = false; + print('stopped showing path'); + ); + if (global_playing_path, + global_playing_path = false; + print('stopped playing path'); + ); + '' +); + show() -> ( - loop(100, - _show_path_tick('dust 0.9 0.1 0.1 1', 100); - game_tick(50) - ); - 'Done!' + global_showing_path = true; + task( _() -> ( + loop(100, + if(!global_showing_path, break()); + _show_path_tick('dust 0.9 0.1 0.1 1', 100); + sleep(50); + ); + print('Done!'); + )); + ''; ); + play(fps) -> ( p = player(); __assert_valid_for_motion(); __prepare_path_if_needed(); + global_playing_path = true; task( _(outer(fps), outer(p)) -> ( - //if ((fps % 20 != 0) || fps < 20, exit('FPS needs to be multiples of 20') ); - tpt = fps / 20; - mspt = 50 / tpt; - start_time = time(); - point = 0; - loop( length(global_points)-1, - segment = _; - start = global_points:segment:1; - end = global_points:(segment+1):1; - loop(end-start, - v = __get_path_at(segment, start, _); - modify(p, 'location', v); - point += 1; - end_time = time(); - took = end_time - start_time; - if (took < mspt, sleep(mspt-took)); - start_time = time() - ) - ); - 'Done!' + mspt = 1000 / fps; + start_time = time(); + point = 0; + loop( length(global_points)-1, segment = _; + if (!global_playing_path, break()); + start = global_points:segment:1; + end = global_points:(segment+1):1; + loop(end-start, + v = __get_path_at(segment, start, _); + modify(p, 'location', v); + point += 1; + end_time = time(); + took = end_time - start_time; + if (took < mspt, sleep(mspt-took)); + start_time = time() + ) + ); + print('Done!'); )); - + ''; ); _show_path_tick(particle_type, total) -> ( From 8c95aab2c62fc645847d9c8cc0da8a1ead2c67b2 Mon Sep 17 00:00:00 2001 From: gnembon Date: Thu, 19 Dec 2019 18:11:13 -0400 Subject: [PATCH 10/14] scarpet thread safe pt1 --- .../java/carpet/script/CarpetExpression.java | 4 +- .../java/carpet/script/value/BlockValue.java | 12 ++++- .../java/carpet/script/value/EntityValue.java | 16 ++++++ .../script/value/NBTSerializableValue.java | 51 +++++++++++++++++-- 4 files changed, 77 insertions(+), 6 deletions(-) diff --git a/src/main/java/carpet/script/CarpetExpression.java b/src/main/java/carpet/script/CarpetExpression.java index a66a9911..c5cbe225 100644 --- a/src/main/java/carpet/script/CarpetExpression.java +++ b/src/main/java/carpet/script/CarpetExpression.java @@ -127,7 +127,7 @@ import static java.lang.Math.sqrt; * players using player('*'), which only returns players in current dimension, or use * in_dimension(expr) function.

*/ -public class CarpetExpression +public class CarpetExpression { private ServerCommandSource source; @@ -771,7 +771,7 @@ public class CarpetExpression booleanStateTest(c, "liquid", lv, (s, p) -> !s.getFluidState().isEmpty())); this.expr.addLazyFunction("flammable", -1, (c, t, lv) -> - booleanStateTest(c, "flammable", lv, (s, p) -> !s.getMaterial().isBurnable())); + booleanStateTest(c, "flammable", lv, (s, p) -> s.getMaterial().isBurnable())); this.expr.addLazyFunction("transparent", -1, (c, t, lv) -> booleanStateTest(c, "transparent", lv, (s, p) -> !s.getMaterial().isSolid())); diff --git a/src/main/java/carpet/script/value/BlockValue.java b/src/main/java/carpet/script/value/BlockValue.java index 485aa5e8..9a460338 100644 --- a/src/main/java/carpet/script/value/BlockValue.java +++ b/src/main/java/carpet/script/value/BlockValue.java @@ -21,6 +21,7 @@ import net.minecraft.util.math.Direction; import net.minecraft.util.math.Vec3d; import net.minecraft.util.registry.Registry; import net.minecraft.world.World; +import net.minecraft.world.chunk.WorldChunk; import java.util.Arrays; import java.util.HashMap; @@ -192,6 +193,15 @@ public class BlockValue extends Value throw new InternalExpressionException("Attempted to fetch block state without world or stored block state"); } + public static BlockEntity getBlockEntity(ServerWorld world, BlockPos pos) + { + if (world.getServer().isOnThread()) + return world.getBlockEntity(pos); + else + return world.getWorldChunk(pos).getBlockEntity(pos, WorldChunk.CreationType.IMMEDIATE); + } + + public CompoundTag getData() { if (data != null) @@ -202,7 +212,7 @@ public class BlockValue extends Value } if (world != null && pos != null) { - BlockEntity be = world.getBlockEntity(pos); + BlockEntity be = getBlockEntity(world, pos); CompoundTag tag = new CompoundTag(); if (be == null) { diff --git a/src/main/java/carpet/script/value/EntityValue.java b/src/main/java/carpet/script/value/EntityValue.java index e1aacde9..61760579 100644 --- a/src/main/java/carpet/script/value/EntityValue.java +++ b/src/main/java/carpet/script/value/EntityValue.java @@ -273,6 +273,22 @@ public class EntityValue extends Value } return Value.NULL; }); + + put("permission_level", (e, a) -> { + if (e instanceof ServerPlayerEntity) + { + ServerPlayerEntity spe = (ServerPlayerEntity) e; + for (int i=4; i>=0; i--) + { + if (spe.allowsPermissionLevel(i)) + return new NumericValue(i); + + } + return new NumericValue(0); + } + return Value.NULL; + }); + //spectating_entity // isGlowing put("effect", (e, a) -> diff --git a/src/main/java/carpet/script/value/NBTSerializableValue.java b/src/main/java/carpet/script/value/NBTSerializableValue.java index 40b058ea..7cbeaec9 100644 --- a/src/main/java/carpet/script/value/NBTSerializableValue.java +++ b/src/main/java/carpet/script/value/NBTSerializableValue.java @@ -5,6 +5,12 @@ import carpet.script.LazyValue; import carpet.script.exception.InternalExpressionException; import com.mojang.brigadier.StringReader; import com.mojang.brigadier.exceptions.CommandSyntaxException; +import net.minecraft.block.Block; +import net.minecraft.block.BlockState; +import net.minecraft.block.ChestBlock; +import net.minecraft.block.InventoryProvider; +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.block.entity.ChestBlockEntity; import net.minecraft.block.entity.HopperBlockEntity; import net.minecraft.command.arguments.ItemStackArgument; import net.minecraft.command.arguments.ItemStringReader; @@ -21,8 +27,12 @@ import net.minecraft.nbt.ListTag; import net.minecraft.nbt.StringNbtReader; import net.minecraft.nbt.StringTag; import net.minecraft.nbt.Tag; +import net.minecraft.predicate.entity.EntityPredicates; +import net.minecraft.server.world.ServerWorld; import net.minecraft.util.Identifier; import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Box; +import net.minecraft.world.World; import java.util.Collection; import java.util.HashMap; @@ -116,6 +126,41 @@ public class NBTSerializableValue extends Value implements ContainerValueInterfa return copy; } + // stolen from HopperBlockEntity, adjusted for threaded operation + public static Inventory getInventoryAt(ServerWorld world, BlockPos blockPos) + { + Inventory inventory = null; + BlockState blockState = world.getBlockState(blockPos); + Block block = blockState.getBlock(); + if (block instanceof InventoryProvider) { + inventory = ((InventoryProvider)block).getInventory(blockState, world, blockPos); + } else if (block.hasBlockEntity()) { + BlockEntity blockEntity = BlockValue.getBlockEntity(world, blockPos); + if (blockEntity instanceof Inventory) { + inventory = (Inventory)blockEntity; + if (inventory instanceof ChestBlockEntity && block instanceof ChestBlock) { + inventory = ChestBlock.getInventory(blockState, world, blockPos, true); + } + } + } + + if (inventory == null) { + List list = world.getEntities( + (Entity)null, + new Box( + blockPos.getX() - 0.5D, blockPos.getY() - 0.5D, blockPos.getZ() - 0.5D, + blockPos.getX() + 0.5D, blockPos.getY() + 0.5D, blockPos.getZ() + 0.5D), + EntityPredicates.VALID_INVENTORIES + ); + if (!list.isEmpty()) { + inventory = (Inventory)list.get(world.random.nextInt(list.size())); + } + } + + return inventory; + } + + public static InventoryLocator locateInventory(CarpetContext c, List params, int offset) { try @@ -139,7 +184,7 @@ public class NBTSerializableValue extends Value implements ContainerValueInterfa BlockPos pos = ((BlockValue) v1).getPos(); if (pos == null) throw new InternalExpressionException("Block to access inventory needs to be positioned in the world"); - inv = HopperBlockEntity.getInventoryAt(c.s.getWorld(), pos); + inv = getInventoryAt(c.s.getWorld(), pos); if (inv == null) return null; return new InventoryLocator(pos, pos, inv, offset + 1); @@ -151,7 +196,7 @@ public class NBTSerializableValue extends Value implements ContainerValueInterfa NumericValue.asNumber(args.get(0)).getDouble(), NumericValue.asNumber(args.get(1)).getDouble(), NumericValue.asNumber(args.get(2)).getDouble()); - inv = HopperBlockEntity.getInventoryAt(c.s.getWorld(), pos); + inv = getInventoryAt(c.s.getWorld(), pos); if (inv == null) return null; return new InventoryLocator(pos, pos, inv, offset + 1); @@ -160,7 +205,7 @@ public class NBTSerializableValue extends Value implements ContainerValueInterfa NumericValue.asNumber(v1).getDouble(), NumericValue.asNumber(params.get(1 + offset).evalValue(c)).getDouble(), NumericValue.asNumber(params.get(2 + offset).evalValue(c)).getDouble()); - inv = HopperBlockEntity.getInventoryAt(c.s.getWorld(), pos); + inv = getInventoryAt(c.s.getWorld(), pos); if (inv == null) return null; return new InventoryLocator(pos, pos, inv, offset + 3); From 1dc2308d2ece40ea114645494b9630f951c30b8b Mon Sep 17 00:00:00 2001 From: gnembon Date: Fri, 20 Dec 2019 19:02:33 -0400 Subject: [PATCH 11/14] camera threaded WIP --- .../resources/assets/carpet/scripts/camera.sc | 58 ++++++++++++++++--- 1 file changed, 50 insertions(+), 8 deletions(-) diff --git a/src/main/resources/assets/carpet/scripts/camera.sc b/src/main/resources/assets/carpet/scripts/camera.sc index 1aaeb742..4c61f8e9 100644 --- a/src/main/resources/assets/carpet/scripts/camera.sc +++ b/src/main/resources/assets/carpet/scripts/camera.sc @@ -69,9 +69,15 @@ __adjusted_rot(previous_rot, current_rot) -> current_rot ); +__print_path_size() -> +( + __assert_valid_for_motion(); + print("Path length: %d points, 0.2f secs at 60fps", length(global_points), global_points:(-1):1/60); +); repeat(times, last_section_duration) -> ( + __assert_valid_for_motion(); undef('global_path_precalculated'); positions = map(global_points, _:0); modes = map(global_points, _:(-1)); @@ -82,10 +88,12 @@ repeat(times, last_section_duration) -> __add_path_segment(positions:_, durations:_, modes:_) ) ); - str('Add %d points %d times', length(positions), times) + __print_path_size(); + ''; ); speed(percentage) -> ( + __assert_valid_for_motion(); undef('global_path_precalculated'); if (percentage < 25 || percentage > 400, exit('path speed can only be speed, or slowed down 4 times. Recall command for larger changes') @@ -94,10 +102,11 @@ speed(percentage) -> previous_path_length = global_points:(-1):1; for(global_points, _:1 = _:1*ratio ); undef('global_path_precalculated'); - str('path %s from %d to %d ticks', + str('path %s from %d to %d ticks (%.2f seconds with 60fps)', if(ratio<1,'shortened','extended'), previous_path_length, - global_points:(-1):1 + global_points:(-1):1, + global_points:(-1):1/60 ) ); select_interpolation(method) -> @@ -158,15 +167,45 @@ stop() -> '' ); +clear() -> +( + global_points = l(); + undef('global_path_precalculated'); +); + +global_needs_updating = false; +global_selected_point = null; + show() -> ( + p = player(); + __assert_valid_for_motion(); + __print_path_size(); global_showing_path = true; - task( _() -> ( - loop(100, - if(!global_showing_path, break()); - _show_path_tick('dust 0.9 0.1 0.1 1', 100); - sleep(50); + global_needs_updating= false; + def __create_markers()-> + ( + map(global_points, + m = create_marker(null, _:0, 'observer'); + if (global_selected_point && _i == global_selected_point, + modify(m,'effect','glowing',72000); + ); + m ); + ); + task( _(outer(p)) -> ( + markers = __create_markers(); + loop(7200, + if(!global_showing_path, break()); + if (global_needs_updating, + global_needs_updating = false; + for(markers, modify(_,'remove'); + markers = __create_markers(); + ); + _show_path_tick('dust 0.9 0.1 0.1 1', 100); + sleep(100); + ); + for(markers, modify(_,'remove'); print('Done!'); )); ''; @@ -179,10 +218,12 @@ play(fps) -> __prepare_path_if_needed(); global_playing_path = true; task( _(outer(fps), outer(p)) -> ( + sleep(1000); mspt = 1000 / fps; start_time = time(); point = 0; loop( length(global_points)-1, segment = _; + if (p~'sneaking', global_playing_path = false); if (!global_playing_path, break()); start = global_points:segment:1; end = global_points:(segment+1):1; @@ -196,6 +237,7 @@ play(fps) -> start_time = time() ) ); + sleep(1000); print('Done!'); )); ''; From 62529a352ce1b36a4f478c2612b808ed322f8790 Mon Sep 17 00:00:00 2001 From: gnembon Date: Sun, 22 Dec 2019 02:26:40 -0400 Subject: [PATCH 12/14] camera update --- .../java/carpet/script/CarpetExpression.java | 15 +- src/main/java/carpet/script/Expression.java | 16 +- .../java/carpet/script/value/BlockValue.java | 11 +- .../java/carpet/script/value/EntityValue.java | 33 ++ .../resources/assets/carpet/scripts/camera.sc | 357 ++++++++++++------ 5 files changed, 312 insertions(+), 120 deletions(-) diff --git a/src/main/java/carpet/script/CarpetExpression.java b/src/main/java/carpet/script/CarpetExpression.java index c5cbe225..95fe343c 100644 --- a/src/main/java/carpet/script/CarpetExpression.java +++ b/src/main/java/carpet/script/CarpetExpression.java @@ -106,7 +106,6 @@ import java.util.function.BiFunction; import java.util.function.Predicate; import java.util.stream.Collectors; -import static carpet.script.value.NBTSerializableValue.nameFromRegistryId; import static java.lang.Math.abs; import static java.lang.Math.max; import static java.lang.Math.min; @@ -2865,6 +2864,7 @@ public class CarpetExpression CarpetContext cc = (CarpetContext)c; BlockState targetBlock = null; BlockValue.VectorLocator pointLocator; + boolean interactable = true; String name; try { @@ -2872,8 +2872,14 @@ public class CarpetExpression name = nameValue instanceof NullValue ? "" : nameValue.getString(); pointLocator = BlockValue.locateVec(cc, lv, 1, true); if (lv.size()>pointLocator.offset) - targetBlock = BlockValue.fromParams(cc, lv, pointLocator.offset, true).block.getBlockState(); - + { + BlockValue.LocatorResult blockLocator = BlockValue.fromParams(cc, lv, pointLocator.offset, true, true); + if (blockLocator.block != null) targetBlock = blockLocator.block.getBlockState(); + if (lv.size() > blockLocator.offset) + { + interactable = lv.get(blockLocator.offset).evalValue(c, Context.BOOLEAN).getBoolean(); + } + } } catch (IndexOutOfBoundsException e) { @@ -2901,6 +2907,7 @@ public class CarpetExpression armorstand.setNoGravity(true); armorstand.setInvisible(true); armorstand.setInvulnerable(true); + armorstand.getDataTracker().set(ArmorStandEntity.ARMOR_STAND_FLAGS, (byte)(interactable?8 : 16|8)); cc.s.getWorld().spawnEntity(armorstand); EntityValue result = new EntityValue(armorstand); return (_c, _t) -> result; @@ -3023,7 +3030,7 @@ public class CarpetExpression this.expr.addLazyFunction("current_dimension", 0, (c, t, lv) -> { ServerCommandSource s = ((CarpetContext)c).s; - Value retval = new StringValue(nameFromRegistryId(Registry.DIMENSION.getId(s.getWorld().dimension.getType()))); + Value retval = new StringValue(NBTSerializableValue.nameFromRegistryId(Registry.DIMENSION.getId(s.getWorld().dimension.getType()))); return (cc, tt) -> retval; }); diff --git a/src/main/java/carpet/script/Expression.java b/src/main/java/carpet/script/Expression.java index facb3504..4677800f 100644 --- a/src/main/java/carpet/script/Expression.java +++ b/src/main/java/carpet/script/Expression.java @@ -1198,16 +1198,14 @@ public class Expression implements Cloneable { Value v1 = lv1.evalValue(c, Context.BOOLEAN); if (!v1.getBoolean()) return (cc, tt) -> v1; - Value v2 = lv2.evalValue(c, Context.BOOLEAN); - return v2.getBoolean() ? ((cc, tt) -> v2) : LazyValue.FALSE; + return lv2; }); addLazyBinaryOperator("||", precedence.get("or||"), false, (c, t, lv1, lv2) -> { Value v1 = lv1.evalValue(c, Context.BOOLEAN); if (v1.getBoolean()) return (cc, tt) -> v1; - Value v2 = lv2.evalValue(c, Context.BOOLEAN); - return v2.getBoolean() ? ((cc, tt) -> v2) : LazyValue.FALSE; + return lv2; }); addBinaryOperator("~", precedence.get("attribute~:"), true, Value::in); @@ -1928,7 +1926,7 @@ public class Expression implements Cloneable LazyValue expr = lv.get(1); //scoping LazyValue _val = c.getVariable("_"); - LazyValue _iter = c.getVariable("_i"); + LazyValue _ite = c.getVariable("_i"); int successCount = 0; for (int i=0; iterator.hasNext(); i++) { @@ -1959,7 +1957,7 @@ public class Expression implements Cloneable //revering scope ((AbstractListValue) rval).fatality(); c.setVariable("_", _val); - c.setVariable("_i", _iter); + c.setVariable("_i", _ite); long promiseWontChange = successCount; return (cc, tt) -> new NumericValue(promiseWontChange); }); @@ -1988,15 +1986,18 @@ public class Expression implements Cloneable //scoping LazyValue _val = c.getVariable("_"); LazyValue _acc = c.getVariable("_a"); + LazyValue _ite = c.getVariable("_i"); - while (iterator.hasNext()) + for (int i=0; iterator.hasNext(); i++) { Value next = iterator.next(); String var = next.boundVariable; next.bindTo("_"); Value promiseWontChangeYou = acc; + int seriously = i; c.setVariable("_a", (cc, tt) -> promiseWontChangeYou.bindTo("_a")); c.setVariable("_", (cc, tt) -> next); + c.setVariable("_i", (cc, tt) -> new NumericValue(seriously).bindTo("_i")); try { acc = expr.evalValue(c, t); @@ -2016,6 +2017,7 @@ public class Expression implements Cloneable ((AbstractListValue) rval).fatality(); c.setVariable("_a", _acc); c.setVariable("_", _val); + c.setVariable("_i", _ite); Value hopeItsEnoughPromise = acc; return (cc, tt) -> hopeItsEnoughPromise; diff --git a/src/main/java/carpet/script/value/BlockValue.java b/src/main/java/carpet/script/value/BlockValue.java index 9a460338..da9704d0 100644 --- a/src/main/java/carpet/script/value/BlockValue.java +++ b/src/main/java/carpet/script/value/BlockValue.java @@ -130,15 +130,24 @@ public class BlockValue extends Value public static LocatorResult fromParams(CarpetContext c, List params, int offset) { - return fromParams(c, params,offset, false); + return fromParams(c, params,offset, false, false); } public static LocatorResult fromParams(CarpetContext c, List params, int offset, boolean acceptString) + { + return fromParams(c, params,offset, acceptString, false); + } + + public static LocatorResult fromParams(CarpetContext c, List params, int offset, boolean acceptString, boolean optional) { try { Value v1 = params.get(0 + offset).evalValue(c); //add conditional from string name + if (optional && v1 instanceof NullValue) + { + return new LocatorResult(null, 1+offset); + } if (acceptString && v1 instanceof StringValue) { return new LocatorResult(fromString(v1.getString()), 1+offset); diff --git a/src/main/java/carpet/script/value/EntityValue.java b/src/main/java/carpet/script/value/EntityValue.java index 61760579..f1e44d99 100644 --- a/src/main/java/carpet/script/value/EntityValue.java +++ b/src/main/java/carpet/script/value/EntityValue.java @@ -772,6 +772,39 @@ public class EntityValue extends Value else e.noClip = v.getBoolean(); }); + put("effect", (e, v) -> + { + if (!(e instanceof LivingEntity)) return; + LivingEntity le = (LivingEntity)e; + if (v == null) + le.clearPotionEffects(); + else if (v instanceof ListValue) + { + List lv = ((ListValue) v).getItems(); + if (lv.size() >= 2 && lv.size() <= 5) + { + String effectName = lv.get(0).getString(); + if (effectName.startsWith("minecraft:")) + effectName = "minecraft:"+effectName; + StatusEffect effect = Registry.STATUS_EFFECT.get(new Identifier(effectName)); + if (effect == null) + throw new InternalExpressionException("Wrong effect name: "+effectName); + int duration = (int)NumericValue.asNumber(lv.get(1)).getLong(); + int amplifier = 0; + if (lv.size() > 2) + amplifier = (int)NumericValue.asNumber(lv.get(2)).getLong(); + boolean showParticles = true; + if (lv.size() > 3) + showParticles = lv.get(3).getBoolean(); + boolean showIcon = true; + if (lv.size() > 4) + showIcon = lv.get(4).getBoolean(); + le.addPotionEffect(new StatusEffectInstance(effect, duration, amplifier, showParticles, showIcon)); + return; + } + } + throw new InternalExpressionException("'effect' needs either no arguments (clear) or effect name, duration, and optional amplifier, show particles and show icon"); + }); // gamemode // spectate diff --git a/src/main/resources/assets/carpet/scripts/camera.sc b/src/main/resources/assets/carpet/scripts/camera.sc index 4c61f8e9..100f9b98 100644 --- a/src/main/resources/assets/carpet/scripts/camera.sc +++ b/src/main/resources/assets/carpet/scripts/camera.sc @@ -35,31 +35,66 @@ __command() -> '' ); +// camera position for the player sticks out of their eyes +__camera_position(player) -> (player~'location' + l(0, player~'eye_height',0,0,0)); + +__update() -> +( + undef('global_path_precalculated'); + global_needs_updating = true; +); + start() -> ( p = player(); - global_points = l(l(p ~ 'location', 0,'sharp')); - undef('global_path_precalculated'); - str('Started path at %.1f %.1f %.1f', p~'x', p~'y', p~'z') + global_points = l(l(__camera_position(p), 0,'sharp')); + __update(); + show(); + str('Started path at %.1f %.1f %.1f', p~'x', p~'y', p~'z'); ); add(delay) -> ( p = player(); - mode = 'sharp'; + mode = 'smooth'; //mode is currently unused, run_path does always sharp, gauss interpolator is always smooth // but this option could be used could be used at some point by more robust interpolators - __add_path_segment(p ~ 'location', delay, mode); - str('Added point %d: %.1f %.1f %.1f', length(global_points), p~'x', p~'y', p~'z') + __add_path_segment(__camera_position(p), round(60*delay), mode); + __print_path_size(); ); + +prepend(delay) -> +( + p = player(); + mode = 'smooth'; + __prepend_path_segment(__camera_position(p), round(60*delay), mode); + __print_path_size(); +); + __add_path_segment(vector, duration, mode) -> ( - undef('global_path_precalculated'); if ( (l('sharp','smooth') ~ mode) == null, exit('use smooth or sharp point')); if(!global_points, exit('Cannot add point to path that didn\'t started yet!')); l(v, end_time, m) = global_points:(-1); vector:(-2) = __adjusted_rot(v:(-2) , vector:(-2) ); - global_points += l(vector, end_time+duration, mode) + global_points += l(vector, end_time+duration, mode); + __update(); ); + +__prepend_path_segment(vector, duration, mode) -> +( + if ( (l('sharp','smooth') ~ mode) == null, exit('use smooth or sharp point')); + if(!global_points, exit('Cannot add point to path that didn\'t started yet!')); + l(v, start_time, m) = global_points:(-1); + vector:(-2) = __adjusted_rot(v:(-2), vector:(-2)); + new_points = l(l(vector, 0, mode)); + for (global_points, + _:1 += duration; + new_points += _; + ); + global_points = new_points; + __update(); +); + // adjusts current rotation so we don\'t spin around like crazy __adjusted_rot(previous_rot, current_rot) -> ( @@ -71,79 +106,80 @@ __adjusted_rot(previous_rot, current_rot) -> __print_path_size() -> ( - __assert_valid_for_motion(); - print("Path length: %d points, 0.2f secs at 60fps", length(global_points), global_points:(-1):1/60); + if (!__assert_valid_for_motion(), + print(str('%d points, %.1f secs', length(global_points), global_points:(-1):1/60)); + ); + ''; ); repeat(times, last_section_duration) -> ( - __assert_valid_for_motion(); - undef('global_path_precalculated'); + if (err = __assert_valid_for_motion(), exit(err)); positions = map(global_points, _:0); modes = map(global_points, _:(-1)); durations = map(global_points, global_points:(_i+1):1 - _:1 ); - durations:(-1) = last_section_duration; + durations:(-1) = round(60*last_section_duration); loop(times, loop( length(positions), __add_path_segment(positions:_, durations:_, modes:_) ) ); + __update(); __print_path_size(); ''; ); -speed(percentage) -> +stretch(percentage) -> ( - __assert_valid_for_motion(); + if (err = __assert_valid_for_motion(), exit(err)); undef('global_path_precalculated'); if (percentage < 25 || percentage > 400, - exit('path speed can only be speed, or slowed down 4 times. Recall command for larger changes') + exit('path speed can only be speed, or slowed down 4 times. Re-call command for larger changes') ); ratio = percentage/100; previous_path_length = global_points:(-1):1; for(global_points, _:1 = _:1*ratio ); - undef('global_path_precalculated'); - str('path %s from %d to %d ticks (%.2f seconds with 60fps)', + __update(); + str('path %s from %.2f to %.2f seconds', if(ratio<1,'shortened','extended'), - previous_path_length, - global_points:(-1):1, + previous_path_length/60, global_points:(-1):1/60 ) ); + select_interpolation(method) -> ( - undef('global_path_precalculated'); __prepare_path_if_needed() -> __prepare_path_if_needed_generic(); // each supported method needs to specify its __find_position_for_point to trace the path // accepting segment number, and position in the segment // or optionally __prepare_path_if_needed, if path is inefficient to compute point by point - if ( - method == 'linear', - ( - __find_position_for_point(s, p) -> __find_position_for_linear(s, p) - ), + global_interpolator = if ( + method == 'linear', '__interpolator_linear', + method == 'cr', '__interpolator_cr', + method == 'gauss', _(s, p) -> __interpolator_gauss(s, p, 0), method ~ '^gauss_', - ( - type = method - 'gauss_'; - global_interpol_option = if(type=='auto',0,number(type)); - __find_position_for_point(s, p) -> __find_position_for_gauss(s, p) - ), - - exit('Choose one of the following methods: linear, gauss:auto, gauss:') + ( + type = method - 'gauss_'; + variance = number(type); + _(s, p, outer(variance)) -> __interpolator_gauss(s, p, variance); + ), + exit('Choose one of the following methods: linear, gauss, gauss_, cr') ); + __update(); 'Ok' ); -select_interpolation('gauss_auto'); +select_interpolation('cr'); + __assert_valid_for_motion() -> ( - if(!global_points, exit('Path not defined yet')); - if(length(global_points)<2, exit('Path not complete - add more points')); + if(!global_points, return('Path not defined yet')); + if(length(global_points)<2, return('Path not complete - add more points')); null ); __get_path_at(segment, start, index) -> ( v = global_path_precalculated:(start+index); if(v == null, - v = __find_position_for_point(segment, index); + v = call(global_interpolator, segment, index); global_path_precalculated:(start+index) = v ); v @@ -153,16 +189,11 @@ __invalidate_points_cache() -> global_path_precalculated = map(range(global_poin global_showing_path = false; global_playing_path = false; -stop() -> +hide() -> ( - if (!global_playing_path && !global_showing_path, return('You can only stop playing or showing the path')); if (global_showing_path, global_showing_path = false; - print('stopped showing path'); - ); - if (global_playing_path, - global_playing_path = false; - print('stopped playing path'); + print('Stopped showing path'); ); '' ); @@ -170,97 +201,198 @@ stop() -> clear() -> ( global_points = l(); - undef('global_path_precalculated'); + __update(); ); global_needs_updating = false; global_selected_point = null; +global_markers = null; + +__distsq(vec1, vec2) -> reduce(vec1 - vec2, _a + _*_, 0); + +__closest_point_to_center(center, points) -> +( + dd = __distsq(points:0, center); + for(points, + d = __distsq(_, center); + if( d +( + if (!global_points, return()); + if (!global_showing_path, return()); + p = player(); + selected_point = __closest_point_to_center( + p~'pos'+l(0,p~'eye_height',0), + map(global_points, slice(_:0, 0, 3)) + ); + global_selected_point = if (global_selected_point == selected_point, null, selected_point); + global_needs_updating = true; + // no need to _update since path is still valid + +); + +__on_player_attacks_entity(p, e) -> +( + if (e~'type' == 'armor_stand' && query(e, 'has_tag', '__scarpet_marker_camera') && global_markers, + for (global_markers, + if(_==e, + global_selected_point = if (global_selected_point == _i, null, _i); + global_needs_updating = true; + return(); + ) + ); + ); +); + +global_show_thread = null; show() -> ( p = player(); - __assert_valid_for_motion(); __print_path_size(); + if (global_showing_path, return ()); global_showing_path = true; global_needs_updating= false; - def __create_markers()-> + __create_markers() -> ( - map(global_points, - m = create_marker(null, _:0, 'observer'); - if (global_selected_point && _i == global_selected_point, - modify(m,'effect','glowing',72000); + map(global_points || l(), + is_selected = global_selected_point != null && _i == global_selected_point; + caption = if (_i == 0, 'start', str('%.1fs', global_points:_i:1/60); ); + if (is_selected && _i > 0, + caption += str(' (%.1f current segment)', (global_points:_i:1 - global_points:(_i-1):1)/60); + ); + m = create_marker(caption, _:0, 'observer'); + if (is_selected, + modify(m,'effect','glowing',72000, 0, false, false); ); m ); ); + __show_path_tick() -> + ( + if (__assert_valid_for_motion(), return()); + __prepare_path_if_needed(); + loop(100, + segment = floor(rand(length(global_points)-1)); + particle_type = if ((segment+1) == global_selected_point, 'dust 0.1 0.9 0.1 1','dust 0.6 0.6 0.6 1'); + start = global_points:segment:1; + end = global_points:(segment+1):1; + index = floor(rand(end-start)); + l(x, y, z) = slice(__get_path_at(segment, start, index), 0, 3); + particle(particle_type, x, y, z, 1, 0, 0) + ); + null + ); + task( _(outer(p)) -> ( - markers = __create_markers(); + global_markers = __create_markers(); loop(7200, if(!global_showing_path, break()); if (global_needs_updating, global_needs_updating = false; - for(markers, modify(_,'remove'); - markers = __create_markers(); + for(global_markers, modify(_,'remove')); + global_markers = __create_markers(); ); - _show_path_tick('dust 0.9 0.1 0.1 1', 100); + __show_path_tick(); sleep(100); ); - for(markers, modify(_,'remove'); + for(global_markers, modify(_,'remove')); + global_markers = null; + global_showing_path = false; + )); + ''; +); + +move() -> +( + if (global_selected_point == null, return()); + if (!global_showing_path, return()); + global_points:global_selected_point:0 = __camera_position(player()); + __update(); +); + +duration(amount) -> +( + if (!global_selected_point, return()); // skip nulls, and 0 (first one) + if (!global_showing_path, return()); + duration = number(amount); + new_ticks = round(duration * 60); + if (new_ticks < 10, return()); + previous_ticks = global_points:global_selected_point:1-global_points:(global_selected_point-1):1; + delta = new_ticks - previous_ticks; + // adjust duration of all points after that. + for (range(global_selected_point, length(global_points)), + global_points:_:1 += delta; + ); + __update(); +); + +delete_point() -> +( + if (global_selected_point == null, return()); // skip nulls, and 0 (first one) + if (!global_showing_path, return()); + if (length(global_points) < 2, clear(); return()); + if (global_selected_point == 0, global_points:1:1 = 0); + global_points = filter(global_points, _i != global_selected_point); + __update(); +); + +trim_path() -> +( + if (global_selected_point == null, return()); // skip nulls, and 0 (first one) + if (!global_showing_path, return()); + global_points = slice(global_points, 0, global_selected_point); + __update(); +); + +play() -> +( + p = player(); + if (err = __assert_valid_for_motion(), exit(err)); + __prepare_path_if_needed(); + task( _(outer(fps), outer(p)) -> ( + sleep(1000); + global_playing_path = true; + mspt = 1000 / 60; + start_time = time(); + point = 0; + player_offset = l(0,p~'eye_height',0,0,0); + try ( + loop( length(global_points)-1, segment = _; + start = global_points:segment:1; + end = global_points:(segment+1):1; + loop(end-start, + if (p~'sneaking', global_playing_path = false); + if (!global_playing_path, throw()); + v = __get_path_at(segment, start, _)-player_offset; + modify(p, 'location', v); + point += 1; + end_time = time(); + took = end_time - start_time; + if (took < mspt, sleep(mspt-took)); + start_time = time() + ) + ); + ); + sleep(1000); + global_playing_path = false; print('Done!'); )); ''; ); -play(fps) -> -( - p = player(); - __assert_valid_for_motion(); - __prepare_path_if_needed(); - global_playing_path = true; - task( _(outer(fps), outer(p)) -> ( - sleep(1000); - mspt = 1000 / fps; - start_time = time(); - point = 0; - loop( length(global_points)-1, segment = _; - if (p~'sneaking', global_playing_path = false); - if (!global_playing_path, break()); - start = global_points:segment:1; - end = global_points:(segment+1):1; - loop(end-start, - v = __get_path_at(segment, start, _); - modify(p, 'location', v); - point += 1; - end_time = time(); - took = end_time - start_time; - if (took < mspt, sleep(mspt-took)); - start_time = time() - ) - ); - sleep(1000); - print('Done!'); - )); - ''; -); -_show_path_tick(particle_type, total) -> -( - __assert_valid_for_motion(); - __prepare_path_if_needed(); - loop(total, - segment = floor(rand(length(global_points)-1)); - start = global_points:segment:1; - end = global_points:(segment+1):1; - index = floor(rand(end-start)); - l(x, y, z) = slice(__get_path_at(segment, start, index), 0, 3); - particle(particle_type, x, y, z, 1, 0, 0) - ); - null -); __prepare_path_if_needed_generic() -> ( if(!global_path_precalculated, __invalidate_points_cache()) ); -__find_position_for_linear(segment, point) -> +__interpolator_linear(segment, point) -> ( l(va, start, mode_a) = global_points:segment; l(vb, end, mode_b) = global_points:(segment+1); @@ -271,16 +403,14 @@ __find_position_for_linear(segment, point) -> //(1/sqrt(2*pi*d*d))*euler^(-((x-miu)^2)/(2*d*d)) // but we will be normalizing anyways, so who cares __norm_prob(x, miu, d) -> euler^(-((x-miu)^2)/(2*d*d)); -__find_position_for_gauss(from_index, point) -> +__interpolator_gauss(from_index, point, deviation) -> ( - dev = global_interpol_option; components = l(); path_point = global_points:from_index:1; - try( for(range(from_index+1, length(global_points)), l(v,ptime,mode) = global_points:_; - dev = if (global_interpol_option > 0, global_interpol_option, + dev = if (deviation > 0, deviation, devs = l(); if (_+1 < length(global_points), devs += global_points:(_+1):1-ptime); if (_-1 >= 0, devs += ptime-global_points:(_-1):1); @@ -295,7 +425,7 @@ __find_position_for_gauss(from_index, point) -> try( for(range(from_index, -1, -1), l(v,ptime,mode) = global_points:_; - dev = if (global_interpol_option > 0, global_interpol_option, + dev = if (deviation > 0, deviation, devs = l(); if (_+1 < length(global_points), devs += global_points:(_+1):1-ptime); if (_-1 >= 0, devs += ptime-global_points:(_-1):1); @@ -309,4 +439,15 @@ __find_position_for_gauss(from_index, point) -> ); total = rtotal+ltotal; reduce(components, _a+_:0*(_:1/total), l(0,0,0,0,0)) -) \ No newline at end of file +); + +__interpolator_cr(from_index, point) -> +( + total = global_points:(from_index+1):1 - global_points:from_index:1; + p__1 = global_points:(if(from_index == 0, 0, from_index-1)):0; + p_0 = global_points:from_index:0; + p_1 = global_points:(from_index+1):0; + p_2 = global_points:(if(from_index == (length(global_points)-2), -1, from_index+2)):0; + r = point/total; // ratio within segment + (r*((2-r)*r-1) * p__1 + (r*r*(3*r-5)+2) * p_0 + r*((4 - 3*r)*r + 1) * p_1 + (r-1)*r*r * p_2) / 2 +); \ No newline at end of file From a4e6e7cb100150fee4ffe876fdcb448c14989078 Mon Sep 17 00:00:00 2001 From: gnembon Date: Wed, 25 Dec 2019 02:44:14 -0400 Subject: [PATCH 13/14] camera update and task clearing --- .../java/carpet/mixins/ContainerMixin.java | 18 +- .../ServerPlayerEntity_scarpetEventMixin.java | 10 + .../java/carpet/script/CarpetEventServer.java | 35 +- .../java/carpet/script/CarpetExpression.java | 2 +- .../java/carpet/script/CarpetScriptHost.java | 37 +- .../carpet/script/CarpetScriptServer.java | 51 +- src/main/java/carpet/script/Expression.java | 37 +- src/main/java/carpet/script/ScriptHost.java | 2 +- .../carpet/script/bundled/FileModule.java | 23 +- .../script/bundled/ModuleInterface.java | 36 +- .../java/carpet/script/value/ThreadValue.java | 22 +- .../resources/assets/carpet/scripts/camera.sc | 602 +++++++++++------- .../assets/carpet/scripts/event_test.sc | 5 + src/main/resources/carpet.mixins.json | 1 + 14 files changed, 545 insertions(+), 336 deletions(-) diff --git a/src/main/java/carpet/mixins/ContainerMixin.java b/src/main/java/carpet/mixins/ContainerMixin.java index f6cc0b29..2056d82f 100644 --- a/src/main/java/carpet/mixins/ContainerMixin.java +++ b/src/main/java/carpet/mixins/ContainerMixin.java @@ -26,26 +26,20 @@ public abstract class ContainerMixin @Shadow public abstract void sendContentUpdates(); @Inject( method = "onSlotClick", at = @At(value = "HEAD"), cancellable = true) - private void onThrowClick( - int int_1, - int int_2, - SlotActionType slotActionType_1, - PlayerEntity playerEntity_1, - CallbackInfoReturnable cir - ) + private void onThrowClick( int slotId, int clickData, SlotActionType actionType, PlayerEntity playerEntity, CallbackInfoReturnable cir) { - if (slotActionType_1 == SlotActionType.THROW && CarpetSettings.ctrlQCraftingFix && playerEntity_1.inventory.getCursorStack().isEmpty() && int_1 >= 0) + if (actionType == SlotActionType.THROW && CarpetSettings.ctrlQCraftingFix && playerEntity.inventory.getCursorStack().isEmpty() && slotId >= 0) { ItemStack itemStack_1 = ItemStack.EMPTY; - Slot slot_4 = slotList.get(int_1); - if (slot_4 != null && slot_4.hasStack() && slot_4.canTakeItems(playerEntity_1)) + Slot slot_4 = slotList.get(slotId); + if (slot_4 != null && slot_4.hasStack() && slot_4.canTakeItems(playerEntity)) { - if(int_1 == 0 && int_2 == 1) + if(slotId == 0 && clickData == 1) { Item craftedItem = slot_4.getStack().getItem(); while(slot_4.hasStack() && slot_4.getStack().getItem() == craftedItem) { - this.onSlotClick(int_1, 0, SlotActionType.THROW, playerEntity_1); + this.onSlotClick(slotId, 0, SlotActionType.THROW, playerEntity); } this.sendContentUpdates(); cir.setReturnValue(itemStack_1); diff --git a/src/main/java/carpet/mixins/ServerPlayerEntity_scarpetEventMixin.java b/src/main/java/carpet/mixins/ServerPlayerEntity_scarpetEventMixin.java index d291efb5..9c10007c 100644 --- a/src/main/java/carpet/mixins/ServerPlayerEntity_scarpetEventMixin.java +++ b/src/main/java/carpet/mixins/ServerPlayerEntity_scarpetEventMixin.java @@ -4,14 +4,18 @@ import com.mojang.authlib.GameProfile; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.item.ItemStack; import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.stat.Stat; import net.minecraft.util.Hand; import net.minecraft.world.World; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.Redirect; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import static carpet.script.CarpetEventServer.Event.PLAYER_FINISHED_USING_ITEM; +import static carpet.script.CarpetEventServer.Event.STATISTICS; @Mixin(ServerPlayerEntity.class) public abstract class ServerPlayerEntity_scarpetEventMixin extends PlayerEntity @@ -43,4 +47,10 @@ public abstract class ServerPlayerEntity_scarpetEventMixin extends PlayerEntity super.method_6040(); } } + + @Inject(method = "increaseStat", at = @At("HEAD")) + private void grabStat(Stat stat, int amount, CallbackInfo ci) + { + STATISTICS.onPlayerStatistic((ServerPlayerEntity) (Object)this, stat, amount); + } } diff --git a/src/main/java/carpet/script/CarpetEventServer.java b/src/main/java/carpet/script/CarpetEventServer.java index 1462b619..f132c638 100644 --- a/src/main/java/carpet/script/CarpetEventServer.java +++ b/src/main/java/carpet/script/CarpetEventServer.java @@ -5,6 +5,7 @@ import carpet.script.value.BlockValue; import carpet.script.value.EntityValue; import carpet.script.value.FunctionValue; import carpet.script.value.ListValue; +import carpet.script.value.NBTSerializableValue; import carpet.script.value.NumericValue; import carpet.script.value.StringValue; import carpet.script.value.Value; @@ -13,11 +14,15 @@ import net.minecraft.server.command.ServerCommandSource; import net.minecraft.entity.Entity; import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.item.ItemStack; +import net.minecraft.stat.Stat; +import net.minecraft.stat.Stats; import net.minecraft.util.Hand; +import net.minecraft.util.Identifier; import net.minecraft.util.hit.BlockHitResult; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Direction; import net.minecraft.util.math.Vec3d; +import net.minecraft.util.registry.Registry; import net.minecraft.world.dimension.DimensionType; import org.apache.commons.lang3.tuple.Pair; @@ -25,10 +30,12 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.function.Supplier; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -403,7 +410,32 @@ public class CarpetEventServer { handler.call( () -> Collections.singletonList(((c, t) -> new EntityValue(player))), player::getCommandSource); } - }; + }, + STATISTICS("statistic", new CallbackList(4)) + { + private Identifier getStatId(Stat stat) + { + return stat.getType().getRegistry().getId(stat.getValue()); + } + private Set skippedStats = new HashSet(){{ + add(Stats.TIME_SINCE_DEATH); + add(Stats.TIME_SINCE_REST); + add(Stats.PLAY_ONE_MINUTE); + }}; + @Override + public void onPlayerStatistic(ServerPlayerEntity player, Stat stat, int amount) + { + Identifier id = getStatId(stat); + if (skippedStats.contains(id)) return; + handler.call( () -> Arrays.asList( + ((c, t) -> new EntityValue(player)), + ((c, t) -> new StringValue(NBTSerializableValue.nameFromRegistryId(Registry.STAT_TYPE.getId(stat.getType())))), + ((c, t) -> new StringValue(NBTSerializableValue.nameFromRegistryId(id))), + ((c, t) -> new NumericValue(amount)) + ), player::getCommandSource); + } + } + ; // on projectile thrown (arrow from bows, crossbows, tridents, snoballs, e-pearls @@ -427,6 +459,7 @@ public class CarpetEventServer //stubs for calls just to ease calls in vanilla code so they don't need to deal with scarpet value types public void onTick() { } public void onPlayerEvent(ServerPlayerEntity player) { } + public void onPlayerStatistic(ServerPlayerEntity player, Stat stat, int amount) { } public void onMountControls(ServerPlayerEntity player, float strafeSpeed, float forwardSpeed, boolean jumping, boolean sneaking) { } public void onItemAction(ServerPlayerEntity player, Hand enumhand, ItemStack itemstack) { } public void onBlockAction(ServerPlayerEntity player, BlockPos blockpos, Direction facing) { } diff --git a/src/main/java/carpet/script/CarpetExpression.java b/src/main/java/carpet/script/CarpetExpression.java index 95fe343c..e6c682a3 100644 --- a/src/main/java/carpet/script/CarpetExpression.java +++ b/src/main/java/carpet/script/CarpetExpression.java @@ -3157,7 +3157,7 @@ public class CarpetExpression String file = null; if (lv.size()>1) { - String origfile = lv.get(0).evalValue(c).getString(); + String origfile = lv.get(1).evalValue(c).getString(); file = origfile.toLowerCase(Locale.ROOT).replaceAll("[^A-Za-z0-9]", ""); if (file.isEmpty()) { diff --git a/src/main/java/carpet/script/CarpetScriptHost.java b/src/main/java/carpet/script/CarpetScriptHost.java index 78905ee4..c7e890f8 100644 --- a/src/main/java/carpet/script/CarpetScriptHost.java +++ b/src/main/java/carpet/script/CarpetScriptHost.java @@ -20,6 +20,7 @@ import net.minecraft.util.math.BlockPos; import java.math.BigInteger; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Locale; @@ -217,6 +218,35 @@ public class CarpetScriptHost extends ScriptHost super.onClose(); if (this.saveTimeout > 0) dumpState(); + + FunctionValue closing = globalFunctions.get("__on_close"); + if (closing != null) + { + try + { + callUDF(BlockPos.ORIGIN, CarpetServer.minecraft_server.getCommandSource(), closing, Collections.emptyList()); + } + catch (InvalidCallbackException ignored) + { + } + } + userHosts.forEach((key, value) -> + { + FunctionValue userClosing = value.globalFunctions.get("__on_close"); + if (userClosing != null) + { + ServerPlayerEntity player = CarpetServer.minecraft_server.getPlayerManager().getPlayer(key); + ServerCommandSource source = (player != null)?player.getCommandSource():CarpetServer.minecraft_server.getCommandSource(); + try + { + ((CarpetScriptHost) value).callUDF(BlockPos.ORIGIN, source, userClosing, Collections.emptyList()); + } + catch (InvalidCallbackException ignored) + { + } + } + }); + String markerName = ExpressionInspector.MARKER_STRING+"_"+((getName()==null)?"":getName()); for (ServerWorld world : CarpetServer.minecraft_server.getWorlds()) { @@ -225,6 +255,7 @@ public class CarpetScriptHost extends ScriptHost e.remove(); } } + } private void dumpState() @@ -239,9 +270,9 @@ public class CarpetScriptHost extends ScriptHost public Tag getGlobalState(String file) { - if (getName() == null || myCode.isInternal()) return null; + if (getName() == null ) return null; if (file != null) - myCode.getData(file); + return myCode.getData(file); if (parent == null) return globalState; return ((CarpetScriptHost)parent).globalState; @@ -249,7 +280,7 @@ public class CarpetScriptHost extends ScriptHost public void setGlobalState(Tag tag, String file) { - if (getName() == null || myCode.isInternal()) return; + if (getName() == null ) return; if (file!= null) { diff --git a/src/main/java/carpet/script/CarpetScriptServer.java b/src/main/java/carpet/script/CarpetScriptServer.java index 1df304c6..75968456 100644 --- a/src/main/java/carpet/script/CarpetScriptServer.java +++ b/src/main/java/carpet/script/CarpetScriptServer.java @@ -43,7 +43,7 @@ public class CarpetScriptServer private Set holyMoly; public CarpetEventServer events; - public static List bundledModuleData = new ArrayList(){{ + private static List bundledModuleData = new ArrayList(){{ add(new BundledModule("camera")); add(new BundledModule("event_test")); }}; @@ -72,7 +72,7 @@ public class CarpetScriptServer } - ModuleInterface getModule(String name) + private ModuleInterface getModule(String name) { File folder = CarpetServer.minecraft_server.getLevelStorage().resolveFile( CarpetServer.minecraft_server.getLevelName(), "scripts"); @@ -218,25 +218,6 @@ public class CarpetScriptServer } return false; } - private void addEvents(ServerCommandSource source, String hostName) - { - if (1+2 == 3) throw new RuntimeException("This should run when code is evaluated"); - ScriptHost host = modules.get(hostName); - if (host == null) - { - return; - } - for (String fun : host.globalFunctions.keySet()) - { - if (!fun.startsWith("__on_")) - continue; - String event = fun.replaceFirst("__on_",""); - if (!CarpetEventServer.Event.byName.containsKey(event)) - continue; - events.addEvent(event, hostName, fun); - } - } - private void addCommand(ServerCommandSource source, String hostName) { @@ -261,7 +242,9 @@ public class CarpetScriptServer requires((player) -> modules.containsKey(hostName)). executes( (c) -> { - Messenger.m(c.getSource(), "gi "+modules.get(hostName).retrieveForExecution(c.getSource()).call(c.getSource(),"__command", null, "")); + String response = modules.get(hostName).retrieveForExecution(c.getSource()). + call(c.getSource(),"__command", null, ""); + if (!response.isEmpty()) Messenger.m(c.getSource(), "gi "+response); return 1; }); @@ -271,28 +254,16 @@ public class CarpetScriptServer then(literal(function). requires((player) -> modules.containsKey(hostName) && modules.get(hostName).getPublicFunctions().contains(function)). executes( (c) -> { - Messenger.m( - c.getSource(), - "gi "+modules.get(hostName).retrieveForExecution(c.getSource()).call( - c.getSource(), - function, - null, - "" - ) - ); + String response = modules.get(hostName).retrieveForExecution(c.getSource()). + call(c.getSource(), function,null,""); + if (!response.isEmpty()) Messenger.m(c.getSource(),"gi "+response); return 1; }). then(argument("args...", StringArgumentType.greedyString()). executes( (c) -> { - Messenger.m( - c.getSource(), - "gi "+modules.get(hostName).retrieveForExecution(c.getSource()).call( - c.getSource(), - function, - null, - StringArgumentType.getString(c, "args...") - ) - ); + String response = modules.get(hostName).retrieveForExecution(c.getSource()). + call(c.getSource(), function,null, StringArgumentType.getString(c, "args...")); + if (!response.isEmpty()) Messenger.m(c.getSource(), "gi "+response); return 1; }))); } diff --git a/src/main/java/carpet/script/Expression.java b/src/main/java/carpet/script/Expression.java index 4677800f..9ff9e44b 100644 --- a/src/main/java/carpet/script/Expression.java +++ b/src/main/java/carpet/script/Expression.java @@ -3002,22 +3002,9 @@ public class Expression implements Cloneable }); 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 (lv.size() == 0) + throw new InternalExpressionException("'task' requires at least function to call as a parameter"); + Value funcDesc = lv.get(0).evalValue(c); if (!(funcDesc instanceof FunctionValue)) { String name = funcDesc.getString(); @@ -3025,10 +3012,20 @@ public class Expression implements Cloneable 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); + FunctionValue fun = (FunctionValue)funcDesc; + int extraargs = lv.size() - fun.getArguments().size(); + if (extraargs != 1 && extraargs != 2) + { + throw new InternalExpressionException("Function takes "+fun.getArguments().size()+" arguments."); + } + List lvargs = new ArrayList<>(); + for (int i=0; i< fun.getArguments().size(); i++) + { + lvargs.add(lv.get(i+1)); + } + Value queue = Value.NULL; + if (extraargs == 2) queue = lv.get(lv.size()-1).evalValue(c); + ThreadValue thread = new ThreadValue(queue, fun, expr, tok, c, lvargs); Thread.yield(); return (cc, tt) -> thread; }); diff --git a/src/main/java/carpet/script/ScriptHost.java b/src/main/java/carpet/script/ScriptHost.java index fdb100f2..1a5c58d6 100644 --- a/src/main/java/carpet/script/ScriptHost.java +++ b/src/main/java/carpet/script/ScriptHost.java @@ -11,7 +11,7 @@ import java.util.stream.Collectors; public class ScriptHost { - private final Map userHosts = new Object2ObjectOpenHashMap<>(); + protected final Map userHosts = new Object2ObjectOpenHashMap<>(); public Map globalFunctions = new Object2ObjectOpenHashMap<>(); public Map globalVariables = new Object2ObjectOpenHashMap<>(); diff --git a/src/main/java/carpet/script/bundled/FileModule.java b/src/main/java/carpet/script/bundled/FileModule.java index b9d45b22..facded41 100644 --- a/src/main/java/carpet/script/bundled/FileModule.java +++ b/src/main/java/carpet/script/bundled/FileModule.java @@ -56,7 +56,7 @@ public class FileModule implements ModuleInterface } //copied private method from net.minecraft.nbt.NbtIo.read() - private static Tag read(File file) + public static Tag read(File file) { try (DataInputStream dataInput_1 = new DataInputStream(new FileInputStream(file))) { @@ -80,7 +80,7 @@ public class FileModule implements ModuleInterface } //copied private method from net.minecraft.nbt.NbtIo.write() and client method safe_write - private static void write(Tag tag_1, File file) + public static void write(Tag tag_1, File file) { File file_2 = new File(file.getAbsolutePath() + "_tmp"); if (file_2.exists()) file_2.delete(); @@ -101,23 +101,4 @@ public class FileModule implements ModuleInterface if (file.exists()) file.delete(); if (!file.exists()) file_2.renameTo(file); } - - @Override - public Tag getData(String file) - { - File dataFile =CarpetServer.minecraft_server.getLevelStorage().resolveFile( - CarpetServer.minecraft_server.getLevelName(), "scripts/"+name+".data"+(file==null?"":"."+file)+".nbt"); - if (!(dataFile.isFile())) - return null; - return read(dataFile); - } - - @Override - public void saveData(String file, Tag globalState) - { - File dataFile =CarpetServer.minecraft_server.getLevelStorage().resolveFile( - CarpetServer.minecraft_server.getLevelName(), "scripts/"+name+".data"+(file==null?"":"."+file)+".nbt"); - write(globalState, dataFile); - - } } diff --git a/src/main/java/carpet/script/bundled/ModuleInterface.java b/src/main/java/carpet/script/bundled/ModuleInterface.java index 009df028..dc7aa2ad 100644 --- a/src/main/java/carpet/script/bundled/ModuleInterface.java +++ b/src/main/java/carpet/script/bundled/ModuleInterface.java @@ -1,12 +1,44 @@ package carpet.script.bundled; +import carpet.CarpetServer; import net.minecraft.nbt.Tag; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; + +import static carpet.script.bundled.FileModule.read; +import static carpet.script.bundled.FileModule.write; + public interface ModuleInterface { String getName(); String getCode(); default boolean isInternal() {return true; } - default Tag getData(String file) {return null; } - default void saveData(String file, Tag globalState) {} + + default Tag getData(String file) + { + File dataFile = CarpetServer.minecraft_server.getLevelStorage().resolveFile( + CarpetServer.minecraft_server.getLevelName(), "scripts/"+getName()+".data"+(file==null?"":"."+file)+".nbt"); + if (!Files.exists(dataFile.toPath()) || !(dataFile.isFile())) return null; + return read(dataFile); + } + + default void saveData(String file, Tag globalState) + { + File dataFile =CarpetServer.minecraft_server.getLevelStorage().resolveFile( + CarpetServer.minecraft_server.getLevelName(), "scripts/"+getName()+".data"+(file==null?"":"."+file)+".nbt"); + if (!Files.exists(dataFile.toPath().getParent())) + { + try + { + Files.createDirectory(dataFile.toPath().getParent()); + } + catch (IOException ignored) + { + } + } + write(globalState, dataFile); + + } } diff --git a/src/main/java/carpet/script/value/ThreadValue.java b/src/main/java/carpet/script/value/ThreadValue.java index 667e087a..40500011 100644 --- a/src/main/java/carpet/script/value/ThreadValue.java +++ b/src/main/java/carpet/script/value/ThreadValue.java @@ -2,11 +2,12 @@ package carpet.script.value; import carpet.script.Context; import carpet.script.Expression; +import carpet.script.LazyValue; import carpet.script.Tokenizer; import carpet.script.exception.InternalExpressionException; -import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; @@ -17,18 +18,14 @@ public class ThreadValue extends Value { private CompletableFuture taskFuture; private long id; - private final Object lock; private static long sequence = 0L; private static final Map executorServices = new HashMap<>(); - public ThreadValue(Value pool, FunctionValue function, Expression expr, Tokenizer.Token token, Context ctx) + public ThreadValue(Value pool, FunctionValue function, Expression expr, Tokenizer.Token token, Context ctx, List args) { id = sequence++; - lock = new Object(); taskFuture = CompletableFuture.supplyAsync( - () -> { synchronized (lock) { - return function.lazyEval(ctx, Context.NONE, expr, token, Collections.emptyList()).evalValue(ctx); - } }, + () -> function.lazyEval(ctx, Context.NONE, expr, token, args).evalValue(ctx), executorServices.computeIfAbsent(pool, (v) -> (ThreadPoolExecutor)Executors.newCachedThreadPool()) ); Thread.yield(); @@ -63,17 +60,6 @@ public class ThreadValue extends Value return taskFuture.isDone(); } - public void stop() - { - synchronized (lock) - { - if (!taskFuture.isDone()) - { - taskFuture.complete(Value.NULL); - } - } - } - @Override public boolean equals(Object o) { diff --git a/src/main/resources/assets/carpet/scripts/camera.sc b/src/main/resources/assets/carpet/scripts/camera.sc index 100f9b98..ffd9130c 100644 --- a/src/main/resources/assets/carpet/scripts/camera.sc +++ b/src/main/resources/assets/carpet/scripts/camera.sc @@ -1,137 +1,210 @@ __command() -> ( - print('camera_path module.'); + print('camera: scarpet app.'); print('-------------------'); - print(' "/camera_path start" - set the starting point'); + print(' "/camera start" - set the starting point, resets the path'); print(''); - print(' "/camera_path add " - add another point frames later'); + print(' "/camera add " - add point to the end seconds later'); + print(' "/camera prepend " - prepend point seconds before'); + print(' "/camera clear" - removes entire path'); + print(' "/camera select" - or punch a point, to select it'); + print(' "/camera move" - moves selected camera to current player location'); + print(' "/camera duration " - resizes current selected path'); + print(' "/camera split_point>" - splits current selected path in half'); + print(' "/camera delete_point" - removes current keypoint'); + print(' "/camera trim_path" - removes all keypoints from selected'); print(''); - print(' "/camera_path select_interpolation < linear | gauss_? >"'); - print(' Select interpolation between points:'); + print(' "/camera store "'); + print(' "/camera load "'); + print(' - stores and loads path from nbt files in /scripts world folder'); + print(''); + print(' "/camera interpolation < linear | gauss | cr | gauss_ >"'); + print(' Selects interpolation between points:'); + print(' - cr: Catmull-Rom interpolation (default).'); + print(' smooth path that goes through all points.'); print(' - linear: straight paths between points.'); - print(' - gauss_auto: automatic smooth transitions.'); - print(' - gauss_: custom fixed variance '); - print(' (in points) for special effects.'); + print(' - gauss: automatic smooth transitions.'); + print(' - gauss_: custom fixed variance '); + print(' (in seconds) for special effects.'); + print(' use "_" for decimal separator, e.g. gauss_2_5'); + print(' gauss makes the smoothest path,'); + print(' but treating points as suggestions only'); print(''); - print(' "/camera_path repeat " - '); + print(' "/camera repeat " - '); print(' Repeat existing points configuration n-times'); - print(' using points to link path ends'); + print(' using seconds to link path ends'); print(''); - print(' "/camera_path speed - '); + print(' "/camera stretch " - '); print(' Change number of frames between points'); - print(' from 25 -> 4 times faster (less points),'); - print(' to 400 -> 4 times slower (more points)'); + print(' from 25 -> 4x faster, to 400 -> 4x slower,'); print(''); - print(' "/camera_path play : run a path with a player'); - print(' needs to be multiples of 20, (20 tps)'); + print(' "/camera play : run a path with a player'); + print(' use "sneak" to stop it prematurely'); + print(' run /camera hide and F1 to clear the view'); print(''); - print(' "/camera_path show": shows current path for a moment'); + print(' "/camera show": shows current path for a moment'); + print(' color of particles used is different for different players'); + print(' "/camera hide": hides path display'); print(''); - print(' "/script in camera_path invoke _show_path_tick ":'); - print(' place this in a repeating commandblock'); - print(' to display path continuously'); - print(' with set particle and number of particles per tick'); - print(' example: \'dust 0.9 0.1 0.1 1\' 100 '); '' ); -// camera position for the player sticks out of their eyes -__camera_position(player) -> (player~'location' + l(0, player~'eye_height',0,0,0)); +global_points = null; +global_dimension = null; +global_player = null; -__update() -> -( - undef('global_path_precalculated'); - global_needs_updating = true; -); +global_player_eye_offset = map(range(5), 0); -start() -> -( - p = player(); - global_points = l(l(__camera_position(p), 0,'sharp')); - __update(); - show(); - str('Started path at %.1f %.1f %.1f', p~'x', p~'y', p~'z'); -); -add(delay) -> -( - p = player(); - mode = 'smooth'; - //mode is currently unused, run_path does always sharp, gauss interpolator is always smooth - // but this option could be used could be used at some point by more robust interpolators - __add_path_segment(__camera_position(p), round(60*delay), mode); - __print_path_size(); -); +global_showing_path = false; +global_playing_path = false; -prepend(delay) -> +global_needs_updating = false; +global_selected_point = null; +global_markers = null; + +global_particle_density = 100; + +global_color_a = null; +global_color_b = null; + +global_path_precalculated = null; + +// starts the path with current player location +start() -> __start_with( _() -> l(l(__camera_position(), 0,'sharp')) ); + +// start path with customized initial points selection +__start_with(points_supplier) -> ( p = player(); - mode = 'smooth'; - __prepend_path_segment(__camera_position(p), round(60*delay), mode); - __print_path_size(); -); + global_player = str(p); + global_player_eye_offset = l(0, p~'eye_height',0,0,0); + global_dimension = p~'dimension'; -__add_path_segment(vector, duration, mode) -> -( - if ( (l('sharp','smooth') ~ mode) == null, exit('use smooth or sharp point')); - if(!global_points, exit('Cannot add point to path that didn\'t started yet!')); - l(v, end_time, m) = global_points:(-1); - vector:(-2) = __adjusted_rot(v:(-2) , vector:(-2) ); - global_points += l(vector, end_time+duration, mode); - __update(); -); + code = abs(hash_code(str(p))-123456); + global_color_a = str('dust %.1f %.1f %.1f 1',(code%10)/10,(((code/10)%10)/10), (((code/100)%10)/10) ); // 'dust 0.1 0.9 0.1 1', + global_color_b = str('dust %.1f %.1f %.1f 1',(((code/1000)%10)/10),(((code/10000)%10)/10), (((code/100000)%10)/10) );// 'dust 0.6 0.6 0.6 1' -__prepend_path_segment(vector, duration, mode) -> -( - if ( (l('sharp','smooth') ~ mode) == null, exit('use smooth or sharp point')); - if(!global_points, exit('Cannot add point to path that didn\'t started yet!')); - l(v, start_time, m) = global_points:(-1); - vector:(-2) = __adjusted_rot(v:(-2), vector:(-2)); - new_points = l(l(vector, 0, mode)); - for (global_points, - _:1 += duration; - new_points += _; - ); - global_points = new_points; + global_points = call(points_supplier); __update(); + show(); + str('Started path at %.1f %.1f %.1f', p~'x', p~'y', p~'z'); ); -// adjusts current rotation so we don\'t spin around like crazy -__adjusted_rot(previous_rot, current_rot) -> +// gets current player controlling the path, or fails +__get_player() -> ( - while( abs(previous_rot-current_rot) > 180, 1000, - current_rot += if(previous_rot < current_rot, -360, 360) + if (!global_player, exit('No player selected')); + p = player(global_player); + if (p == null, exit('Player logged out')); + p; +); + +// clears current path +clear() -> +( + global_points = l(); + global_dimension = null; + global_player = null; + global_showing_path = false; + global_playing_path = false; + __update(); + 'Path cleared'; +); + +// camera position for the player sticks out of their eyes +__camera_position() -> (__get_player()~'location' + global_player_eye_offset); + +// path changed, now notify all processes that they need to update; +__update() -> +( + global_path_precalculated = null; + global_playing_path = false; + global_needs_updating = true; +); + +// ensures that the current execution context can modify the path +__assert_can_modify_path() -> +( + if (!global_points, exit('Path is not setup for current player')); + if (!global_showing_path, exit('Turn on path showing to edit key points')); + if(!global_player || !global_dimension || __get_player()~'dimension' != global_dimension, + exit('Player not in dimension')); +); + +// make sure selected point is correct +__assert_point_selected(validator) -> +( + __assert_can_modify_path(); + if (!call(validator, global_selected_point), exit('No appropriate point selected')); +); + +//select a custom interpolation method +interpolation(method) -> __interpolation(method, true); +__interpolation(method, verbose) -> +( + __prepare_path_if_needed() -> __prepare_path_if_needed_generic(); + // each supported method needs to specify its __find_position_for_point to trace the path + // accepting segment number, and position in the segment + // or optionally __prepare_path_if_needed, if path is inefficient to compute point by point + global_interpolator = if ( + method == 'linear', '__interpolator_linear', + method == 'cr', '__interpolator_cr', + method == 'gauss', _(s, p) -> __interpolator_gauB(s, p, 0), + method ~ '^gauss_', + ( + type = method - 'gauss_'; + type = replace(type,'_','.'); + variance = round(60*number(type)); + _(s, p, outer(variance)) -> __interpolator_gauB(s, p, variance); + ), + exit('Choose one of the following methods: linear, gauss, gauss_, cr') ); - current_rot + __update(); + if(verbose, 'Interpolation changed to '+method, ''); +); +__interpolation('cr', false); + +// adds a point to the end of the path with delay in seconds +add(delay) -> +( + __assert_can_modify_path(); + //mode is currently unused, run_path does always sharp, gauss interpolator is always smooth + // but this option could be used could be used at some point by more robust interpolators + __add_path_segment(__camera_position(), round(60*delay), 'smooth', true); + __update(); + __get_path_size_string(); ); -__print_path_size() -> +// prepends the path with a new starting point, with a segment of specified delay +prepend(delay) -> ( - if (!__assert_valid_for_motion(), - print(str('%d points, %.1f secs', length(global_points), global_points:(-1):1/60)); - ); - ''; + __assert_can_modify_path(); + __add_path_segment(__camera_position(), round(60*delay), 'smooth', false); + __update(); + __get_path_size_string(); ); -repeat(times, last_section_duration) -> +// repeats existing points seveal times, using last section delay (seconds) to join points +repeat(times, last_section_delay) -> ( - if (err = __assert_valid_for_motion(), exit(err)); + if (err = __is_not_valid_for_motion(), exit(err)); positions = map(global_points, _:0); modes = map(global_points, _:(-1)); durations = map(global_points, global_points:(_i+1):1 - _:1 ); durations:(-1) = round(60*last_section_duration); loop(times, loop( length(positions), - __add_path_segment(positions:_, durations:_, modes:_) + __add_path_segment(positions:_, durations:_, modes:_, true) ) ); __update(); - __print_path_size(); - ''; + __get_path_size_string(); ); + +//stretches or shrinks current path to X percent of what it was before stretch(percentage) -> ( - if (err = __assert_valid_for_motion(), exit(err)); - undef('global_path_precalculated'); + if (err = __is_not_valid_for_motion(), exit(err)); if (percentage < 25 || percentage > 400, exit('path speed can only be speed, or slowed down 4 times. Re-call command for larger changes') ); @@ -146,98 +219,106 @@ stretch(percentage) -> ) ); -select_interpolation(method) -> +// moves current selected point to player location +move() -> ( - __prepare_path_if_needed() -> __prepare_path_if_needed_generic(); - // each supported method needs to specify its __find_position_for_point to trace the path - // accepting segment number, and position in the segment - // or optionally __prepare_path_if_needed, if path is inefficient to compute point by point - global_interpolator = if ( - method == 'linear', '__interpolator_linear', - method == 'cr', '__interpolator_cr', - method == 'gauss', _(s, p) -> __interpolator_gauss(s, p, 0), - method ~ '^gauss_', - ( - type = method - 'gauss_'; - variance = number(type); - _(s, p, outer(variance)) -> __interpolator_gauss(s, p, variance); - ), - exit('Choose one of the following methods: linear, gauss, gauss_, cr') - ); - __update(); - 'Ok' + __assert_point_selected(_(p) -> p != null); + global_points:global_selected_point:0 = __camera_position(); + __update(); + ''; ); -select_interpolation('cr'); -__assert_valid_for_motion() -> +// chenges duration of the current selected segment to X seconds +duration(amount) -> ( - if(!global_points, return('Path not defined yet')); - if(length(global_points)<2, return('Path not complete - add more points')); - null -); -__get_path_at(segment, start, index) -> -( - v = global_path_precalculated:(start+index); - if(v == null, - v = call(global_interpolator, segment, index); - global_path_precalculated:(start+index) = v + __assert_point_selected(_(p) -> p); // skips nulls and 0 - starting point + duration = number(amount); + new_ticks = round(duration * 60); + if (new_ticks < 10, return()); + previous_ticks = global_points:global_selected_point:1-global_points:(global_selected_point-1):1; + delta = new_ticks - previous_ticks; + // adjust duration of all points after that. + for (range(global_selected_point, length(global_points)), + global_points:_:1 += delta; ); - v -); -__invalidate_points_cache() -> global_path_precalculated = map(range(global_points:(-1):1), null); - -global_showing_path = false; -global_playing_path = false; - -hide() -> -( - if (global_showing_path, - global_showing_path = false; - print('Stopped showing path'); - ); - '' + __update(); + __get_path_size_string(); ); -clear() -> +// deletes current keypoint without changing the path length +delete_point() -> ( - global_points = l(); - __update(); + __assert_point_selected(_(p) -> p != null); + if (length(global_points) < 2, clear(); return()); + if (global_selected_point == 0, global_points:1:1 = 0); + global_points = filter(global_points, _i != global_selected_point); + if (global_selected_point >= length(global_points), global_selected_point = null); + __update(); + __get_path_size_string(); ); -global_needs_updating = false; -global_selected_point = null; -global_markers = null; - -__distsq(vec1, vec2) -> reduce(vec1 - vec2, _a + _*_, 0); - -__closest_point_to_center(center, points) -> +// splits current selected segment in half by adding a keypoint in between +split_point() -> ( - dd = __distsq(points:0, center); - for(points, - d = __distsq(_, center); - if( d p); // skips nulls and 0 - starting point + current_time = global_points:global_selected_point:1; + previous_time = global_points:(global_selected_point-1):1; + segment_duration = current_time-previous_time; + put( + global_points, + global_selected_point, + l( + __get_path_at(global_selected_point-1, previous_time, segment_duration/2), + previous_time+segment_duration/2, + global_points:global_selected_point:2 + ), + 'insert' ); - index; + __update(); + __get_path_size_string(); ); -select() -> +// removes all points in the path from the current point +trim_path() -> +( + __assert_point_selected(_(p) -> p != null); + global_points = slice(global_points, 0, global_selected_point); + global_selected_point = null; + __update(); + __get_path_size_string(); +); + +// moves entire camera path keeping the angles to player position being in the starting point +reposition() -> +( + __assert_can_modify_path(); + shift = pos(__get_player())-slice(global_points:0:0, 0, 3); + shift += 0; shift += 0; + shift = shift + global_player_eye_offset; + for(global_points, _:0 = _:0 + shift); + __update(); + __get_path_size_string(); +); + +// selects either a point of certain number (starting from 1), or closest point +select(num) -> ( if (!global_points, return()); if (!global_showing_path, return()); - p = player(); - selected_point = __closest_point_to_center( - p~'pos'+l(0,p~'eye_height',0), - map(global_points, slice(_:0, 0, 3)) + p = __get_player(); + num = (num+1000*(length(global_points)+1)) % (length(global_points)+1); + selected_point = if (num, num-1, + __closest_point_to_center( + p~'pos'+l(0,p~'eye_height',0), + map(global_points, slice(_:0, 0, 3)) + ) ); global_selected_point = if (global_selected_point == selected_point, null, selected_point); global_needs_updating = true; // no need to _update since path is still valid - ); +// player can also punch the mannequin to select/deselect it __on_player_attacks_entity(p, e) -> ( if (e~'type' == 'armor_stand' && query(e, 'has_tag', '__scarpet_marker_camera') && global_markers, @@ -251,12 +332,80 @@ __on_player_attacks_entity(p, e) -> ); ); -global_show_thread = null; +// adds new segment to the path +__add_path_segment(vector, duration, mode, append) -> +( + if ( (l('sharp','smooth') ~ mode) == null, exit('use smooth or sharp point')); + l(v, segment_time, m) = global_points:(if(append, -1, 0)); + vector:(-2) = __adjusted_rot(v:(-2), vector:(-2)); + if (append, + global_points += l(vector, segment_time+duration, mode); + , + new_points = l(l(vector, 0, mode)); + for (global_points, + _:1 += duration; + new_points += _; + ); + global_points = new_points; + ); + null; +); +// adjusts current rotation so we don't spin around like crazy +__adjusted_rot(previous_rot, current_rot) -> +( + while( abs(previous_rot-current_rot) > 180, 1000, + current_rot += if(previous_rot < current_rot, -360, 360) + ); + current_rot +); + +// returns current path size blurb +__get_path_size_string() -> +( + if (!__is_not_valid_for_motion(), + str('%d points, %.1f secs', length(global_points), global_points:(-1):1/60); + , + 'Path too small to run'; + ); +); + +// checks if the current path is valid for motion +__is_not_valid_for_motion() -> +( + if(!global_points, return('Path not defined yet')); + if(length(global_points)<2, return('Path not complete - add more points')); + if(!global_dimension || __get_player()~'dimension' != global_dimension, return('Wrong dimension')); + false +); + +// grabs position of a player for a given segment, which segment starts at start point, and offset by index points +__get_path_at(segment, start, index) -> +( + v = global_path_precalculated:(start+index); + if(v == null, + v = call(global_interpolator, segment, index); + global_path_precalculated:(start+index) = v + ); + v +); + +// squared euclidean distance between two points +__distsq(vec1, vec2) -> reduce(vec1 - vec2, _a + _*_, 0); + +//finds index of the closest point from a list to the center point +__closest_point_to_center(center, points) -> +( + reduce(points, + d = __distsq(_, center); if( d ( - p = player(); - __print_path_size(); + __get_path_size_string(); if (global_showing_path, return ()); global_showing_path = true; global_needs_updating= false; @@ -264,9 +413,9 @@ show() -> ( map(global_points || l(), is_selected = global_selected_point != null && _i == global_selected_point; - caption = if (_i == 0, 'start', str('%.1fs', global_points:_i:1/60); ); + caption = if (_i == 0, '1: Start', str('%d: %.1fs', _i+1, global_points:_i:1/60); ); if (is_selected && _i > 0, - caption += str(' (%.1f current segment)', (global_points:_i:1 - global_points:(_i-1):1)/60); + caption += str(' (%.1fs current segment)', (global_points:_i:1 - global_points:(_i-1):1)/60); ); m = create_marker(caption, _:0, 'observer'); if (is_selected, @@ -277,11 +426,11 @@ show() -> ); __show_path_tick() -> ( - if (__assert_valid_for_motion(), return()); + if (__is_not_valid_for_motion(), return()); __prepare_path_if_needed(); - loop(100, + loop(global_particle_density, segment = floor(rand(length(global_points)-1)); - particle_type = if ((segment+1) == global_selected_point, 'dust 0.1 0.9 0.1 1','dust 0.6 0.6 0.6 1'); + particle_type = if ((segment+1) == global_selected_point, global_color_a, global_color_b); //'dust 0.1 0.9 0.1 1', 'dust 0.6 0.6 0.6 1'); start = global_points:segment:1; end = global_points:(segment+1):1; index = floor(rand(end-start)); @@ -291,10 +440,11 @@ show() -> null ); - task( _(outer(p)) -> ( + task( _() -> ( global_markers = __create_markers(); loop(7200, if(!global_showing_path, break()); + __get_player(); if (global_needs_updating, global_needs_updating = false; for(global_markers, modify(_,'remove')); @@ -310,60 +460,36 @@ show() -> ''; ); -move() -> +// hides path display +hide() -> ( - if (global_selected_point == null, return()); - if (!global_showing_path, return()); - global_points:global_selected_point:0 = __camera_position(player()); - __update(); -); - -duration(amount) -> -( - if (!global_selected_point, return()); // skip nulls, and 0 (first one) - if (!global_showing_path, return()); - duration = number(amount); - new_ticks = round(duration * 60); - if (new_ticks < 10, return()); - previous_ticks = global_points:global_selected_point:1-global_points:(global_selected_point-1):1; - delta = new_ticks - previous_ticks; - // adjust duration of all points after that. - for (range(global_selected_point, length(global_points)), - global_points:_:1 += delta; - ); - __update(); -); - -delete_point() -> -( - if (global_selected_point == null, return()); // skip nulls, and 0 (first one) - if (!global_showing_path, return()); - if (length(global_points) < 2, clear(); return()); - if (global_selected_point == 0, global_points:1:1 = 0); - global_points = filter(global_points, _i != global_selected_point); - __update(); -); - -trim_path() -> -( - if (global_selected_point == null, return()); // skip nulls, and 0 (first one) - if (!global_showing_path, return()); - global_points = slice(global_points, 0, global_selected_point); - __update(); + if (global_showing_path, + global_showing_path = false; + 'Stopped showing path'; + , + '' + ); ); +// runs the player on the path play() -> ( - p = player(); - if (err = __assert_valid_for_motion(), exit(err)); + if (err = __is_not_valid_for_motion(), exit(err)); __prepare_path_if_needed(); - task( _(outer(fps), outer(p)) -> ( - sleep(1000); + if (!__get_player() || __get_player()~'dimension' != global_dimension, exit('No player in dimension')); + task( _() -> ( + if (global_playing_path, // we don't want to join_task not to lock it just in case. No need to panic here + global_playing_path = false; // cancel current path rendering + sleep(1500); // in case we interrupt previous playbacks + ); + showing_path = global_showing_path; + hide(); + sleep(2000); // so particles can discipate global_playing_path = true; mspt = 1000 / 60; start_time = time(); point = 0; - player_offset = l(0,p~'eye_height',0,0,0); + p = __get_player(); try ( loop( length(global_points)-1, segment = _; start = global_points:segment:1; @@ -371,7 +497,7 @@ play() -> loop(end-start, if (p~'sneaking', global_playing_path = false); if (!global_playing_path, throw()); - v = __get_path_at(segment, start, _)-player_offset; + v = __get_path_at(segment, start, _)-global_player_eye_offset; modify(p, 'location', v); point += 1; end_time = time(); @@ -383,15 +509,18 @@ play() -> ); sleep(1000); global_playing_path = false; - print('Done!'); + if (showing_path, show()); )); ''; ); +// prepares empty path to fit new points __prepare_path_if_needed_generic() -> ( - if(!global_path_precalculated, __invalidate_points_cache()) + if(!global_path_precalculated, global_path_precalculated = map(range(global_points:(-1):1), null)) ); + +// linear interpolator __interpolator_linear(segment, point) -> ( l(va, start, mode_a) = global_points:segment; @@ -400,10 +529,14 @@ __interpolator_linear(segment, point) -> dt = point/section; dt*vb+(1-dt)*va ); + +// normal distribution should look like that //(1/sqrt(2*pi*d*d))*euler^(-((x-miu)^2)/(2*d*d)) // but we will be normalizing anyways, so who cares __norm_prob(x, miu, d) -> euler^(-((x-miu)^2)/(2*d*d)); -__interpolator_gauss(from_index, point, deviation) -> + +//gauB interpolator +__interpolator_gauB(from_index, point, deviation) -> ( components = l(); path_point = global_points:from_index:1; @@ -417,7 +550,7 @@ __interpolator_gauss(from_index, point, deviation) -> 0.6*reduce(devs, _a+_, 0)/length(devs) ); impact = __norm_prob(path_point+point, ptime, dev); - if(rtotal && impact < 0.000001*rtotal, throw()); + //if(rtotal && impact < 0.000001*rtotal, throw()); // can work badly on segments with vastly diff lengths components += l(v, impact); rtotal += impact ) @@ -432,7 +565,7 @@ __interpolator_gauss(from_index, point, deviation) -> 0.6*reduce(devs, _a+_, 0)/length(devs) ); impact = __norm_prob(path_point+point, ptime, dev); - if(ltotal && impact < 0.000001*ltotal, throw()); + //if(ltotal && impact < 0.000001*ltotal, throw()); components += l(v, impact); ltotal += impact ) @@ -441,6 +574,7 @@ __interpolator_gauss(from_index, point, deviation) -> reduce(components, _a+_:0*(_:1/total), l(0,0,0,0,0)) ); +// Catmull-Rom spline __interpolator_cr(from_index, point) -> ( total = global_points:(from_index+1):1 - global_points:from_index:1; @@ -450,4 +584,38 @@ __interpolator_cr(from_index, point) -> p_2 = global_points:(if(from_index == (length(global_points)-2), -1, from_index+2)):0; r = point/total; // ratio within segment (r*((2-r)*r-1) * p__1 + (r*r*(3*r-5)+2) * p_0 + r*((4 - 3*r)*r + 1) * p_1 + (r-1)*r*r * p_2) / 2 -); \ No newline at end of file +); + +// store current path in a world file +store(file) -> +( + if (!global_points, exit('No path to save')); + path_nbt = nbt('{}'); + for (global_points, + point_nbt = nbt('{}'); + point_nbt:'duration' = _:1; + point_nbt:'type' = _:2; + for(_:0, put(point_nbt:'pos',_,_i)); + put(path_nbt:'points', point_nbt, _i); + ); + store_app_data(path_nbt, file); +); + +// loads path under the local file that +load(file) -> +( + path_nbt = load_app_data(file); + if (!path_nbt, exit('No path to load: '+file)); + new_points = map(get(path_nbt, 'points[]'), l(_:'pos[]', _:'duration', _:'type')); + if (!new_points || first(new_points, length(_:0) != 5), + exit('Incorrect data for :'+file); + ); + __start_with(_(outer(new_points)) -> new_points); +); + +// when closing - shut-down visualization and playback threads +__on_close() -> +( + global_playing_path = false; + global_showing_path = false; +); diff --git a/src/main/resources/assets/carpet/scripts/event_test.sc b/src/main/resources/assets/carpet/scripts/event_test.sc index a372c17b..4ac8d59e 100644 --- a/src/main/resources/assets/carpet/scripts/event_test.sc +++ b/src/main/resources/assets/carpet/scripts/event_test.sc @@ -158,4 +158,9 @@ __on_player_drops_stack(player) -> ( print(''); print('__on_player_drops_stack(player)'); print('Player '+player+' drops current stack.') +); + +__on_statistic(player, category, event, value) -> +( + print('__on_statistic(player, category, event, value) -> ['+join(', ',l(player, category, event, value))+']'); ); \ No newline at end of file diff --git a/src/main/resources/carpet.mixins.json b/src/main/resources/carpet.mixins.json index 5ce2fe8d..0ff0fc32 100644 --- a/src/main/resources/carpet.mixins.json +++ b/src/main/resources/carpet.mixins.json @@ -141,6 +141,7 @@ "DamageTracker_scarpetEventMixin", "ServerPlayerEntity_scarpetEventMixin", "FallingBlockEntity_scarpetEventsMixin", + "ChunkTicketManager_scarpetMixin", "ServerChunkManager_scarpetMixin", From ce744802cfd1033132d546b9da8cb45602ccb71a Mon Sep 17 00:00:00 2001 From: gnembon Date: Wed, 25 Dec 2019 03:10:32 -0400 Subject: [PATCH 14/14] cleanup --- src/main/java/carpet/script/Expression.java | 11 ++--------- src/main/resources/assets/carpet/scripts/camera.sc | 8 ++++---- 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/src/main/java/carpet/script/Expression.java b/src/main/java/carpet/script/Expression.java index 9ff9e44b..0bc27604 100644 --- a/src/main/java/carpet/script/Expression.java +++ b/src/main/java/carpet/script/Expression.java @@ -3045,16 +3045,9 @@ public class Expression implements Cloneable return ((ThreadValue) v).join(); }); - /*addUnaryFunction("stop_task", (v) -> { + addUnaryFunction("task_completed", (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"); + throw new InternalExpressionException("'task_completed' could only be used with a task value"); return new NumericValue(((ThreadValue) v).isFinished()); }); diff --git a/src/main/resources/assets/carpet/scripts/camera.sc b/src/main/resources/assets/carpet/scripts/camera.sc index ffd9130c..225f6aec 100644 --- a/src/main/resources/assets/carpet/scripts/camera.sc +++ b/src/main/resources/assets/carpet/scripts/camera.sc @@ -8,7 +8,7 @@ __command() -> print(' "/camera prepend " - prepend point seconds before'); print(' "/camera clear" - removes entire path'); print(' "/camera select" - or punch a point, to select it'); - print(' "/camera move" - moves selected camera to current player location'); + print(' "/camera move" - moves selected point toplayer location'); print(' "/camera duration " - resizes current selected path'); print(' "/camera split_point>" - splits current selected path in half'); print(' "/camera delete_point" - removes current keypoint'); @@ -16,9 +16,9 @@ __command() -> print(''); print(' "/camera store "'); print(' "/camera load "'); - print(' - stores and loads path from nbt files in /scripts world folder'); + print(' - stores and loads paths from files in /scripts folder'); print(''); - print(' "/camera interpolation < linear | gauss | cr | gauss_ >"'); + print(' "/camera interpolation < linear | gauss | cr | gauss_ >"'); print(' Selects interpolation between points:'); print(' - cr: Catmull-Rom interpolation (default).'); print(' smooth path that goes through all points.'); @@ -42,7 +42,7 @@ __command() -> print(' use "sneak" to stop it prematurely'); print(' run /camera hide and F1 to clear the view'); print(''); - print(' "/camera show": shows current path for a moment'); + print(' "/camera show": shows current path'); print(' color of particles used is different for different players'); print(' "/camera hide": hides path display'); print('');