From a778cc51a6955c6935b5633e97e1afaa9ac3b0f3 Mon Sep 17 00:00:00 2001 From: Julian Krings <47589149+CrazyDev05@users.noreply.github.com> Date: Tue, 4 Feb 2025 13:03:49 +0100 Subject: [PATCH] Expression functions (#1165) --- build.gradle | 2 +- .../iris/engine/object/IrisExpression.java | 18 +++- .../engine/object/IrisExpressionFunction.java | 101 ++++++++++++++++++ .../engine/object/IrisExpressionLoad.java | 16 +-- 4 files changed, 126 insertions(+), 11 deletions(-) create mode 100644 core/src/main/java/com/volmit/iris/engine/object/IrisExpressionFunction.java diff --git a/build.gradle b/build.gradle index a08c889bf..f3823e8bc 100644 --- a/build.gradle +++ b/build.gradle @@ -131,7 +131,7 @@ allprojects { annotationProcessor 'org.projectlombok:lombok:1.18.36' // Shaded - implementation 'com.dfsek:Paralithic:0.4.0' + implementation 'com.dfsek:paralithic:0.8.1' implementation 'io.papermc:paperlib:1.0.5' implementation "net.kyori:adventure-text-minimessage:4.17.0" implementation 'net.kyori:adventure-platform-bukkit:4.3.4' diff --git a/core/src/main/java/com/volmit/iris/engine/object/IrisExpression.java b/core/src/main/java/com/volmit/iris/engine/object/IrisExpression.java index 98cb0ed0f..0fbd1c48a 100644 --- a/core/src/main/java/com/volmit/iris/engine/object/IrisExpression.java +++ b/core/src/main/java/com/volmit/iris/engine/object/IrisExpression.java @@ -24,6 +24,7 @@ import com.dfsek.paralithic.eval.parser.Scope; import com.volmit.iris.Iris; import com.volmit.iris.core.loader.IrisRegistrant; import com.volmit.iris.engine.data.cache.AtomicCache; +import com.volmit.iris.engine.object.IrisExpressionFunction.FunctionContext; import com.volmit.iris.engine.object.annotations.ArrayType; import com.volmit.iris.engine.object.annotations.Desc; import com.volmit.iris.engine.object.annotations.Required; @@ -46,12 +47,14 @@ import lombok.experimental.Accessors; @Data @EqualsAndHashCode(callSuper = false) public class IrisExpression extends IrisRegistrant { - private static final Parser parser = new Parser(); - @ArrayType(type = IrisExpressionLoad.class, min = 1) @Desc("Variables to use in this expression") private KList variables = new KList<>(); + @ArrayType(type = IrisExpressionFunction.class, min = 1) + @Desc("Functions to use in this expression") + private KList functions = new KList<>(); + @Required @Desc("The expression. Inherited variables are x, y and z. Avoid using those variable names.") private String expression; @@ -62,6 +65,7 @@ public class IrisExpression extends IrisRegistrant { private Expression expression() { return expressionCache.aquire(() -> { Scope scope = new Scope(); // Create variable scope. This scope can hold both constants and invocation variables. + Parser parser = new Parser(); try { for (IrisExpressionLoad i : variables) { @@ -76,6 +80,12 @@ public class IrisExpression extends IrisRegistrant { Iris.error("Script Variable load error in " + getLoadFile().getPath()); } + for (IrisExpressionFunction f : functions) { + if (!f.isValid()) continue; + f.setData(getLoader()); + parser.registerFunction(f.getName(), f); + } + try { return parser.parse(getExpression(), scope); } catch (Throwable e) { @@ -103,7 +113,7 @@ public class IrisExpression extends IrisRegistrant { g[m++] = z; g[m] = -1; - return expression().evaluate(g); + return expression().evaluate(new FunctionContext(rng), g); } public double evaluate(RNG rng, double x, double y, double z) { @@ -117,7 +127,7 @@ public class IrisExpression extends IrisRegistrant { g[m++] = y; g[m] = z; - return expression().evaluate(g); + return expression().evaluate(new FunctionContext(rng), g); } @Override diff --git a/core/src/main/java/com/volmit/iris/engine/object/IrisExpressionFunction.java b/core/src/main/java/com/volmit/iris/engine/object/IrisExpressionFunction.java new file mode 100644 index 000000000..d56cbec7c --- /dev/null +++ b/core/src/main/java/com/volmit/iris/engine/object/IrisExpressionFunction.java @@ -0,0 +1,101 @@ +package com.volmit.iris.engine.object; + +import com.dfsek.paralithic.functions.dynamic.Context; +import com.dfsek.paralithic.functions.dynamic.DynamicFunction; +import com.dfsek.paralithic.node.Statefulness; +import com.volmit.iris.core.loader.IrisData; +import com.volmit.iris.engine.object.annotations.Desc; +import com.volmit.iris.engine.object.annotations.MinNumber; +import com.volmit.iris.engine.object.annotations.Required; +import com.volmit.iris.engine.object.annotations.Snippet; +import com.volmit.iris.util.collection.KMap; +import com.volmit.iris.util.math.RNG; +import lombok.*; +import lombok.experimental.Accessors; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +@Snippet("expression-function") +@Accessors(chain = true) +@NoArgsConstructor +@AllArgsConstructor +@Desc("Represents a function to use in your expression. Do not set the name to x, y, or z, also don't duplicate names.") +@Data +@EqualsAndHashCode(callSuper = false) +public class IrisExpressionFunction implements DynamicFunction { + @Required + @Desc("The function to assign this value to. Do not set the name to x, y, or z") + private String name; + + @Desc("If defined, this variable will use a generator style as it's value") + private IrisGeneratorStyle styleValue = null; + + @Desc("If defined, iris will use an internal stream from the engine as it's value") + private IrisEngineStreamType engineStreamValue = null; + + @MinNumber(2) + @Desc("Number of arguments for the function") + private int args = 2; + + @Getter(AccessLevel.NONE) + @Setter(AccessLevel.NONE) + private transient final KMap cache = new KMap<>(); + private transient IrisData data; + + public boolean isValid() { + return styleValue != null || engineStreamValue != null; + } + + @Override + public int getArgNumber() { + if (engineStreamValue != null) return 2; + return Math.max(args, 2); + } + + @NotNull + @Override + public Statefulness statefulness() { + return Statefulness.STATEFUL; + } + + @Override + public double eval(double... doubles) { + return 0; + } + + @Override + public double eval(@Nullable Context raw, double... args) { + return cache.computeIfAbsent((FunctionContext) raw, context -> { + assert context != null; + if (engineStreamValue != null) { + var stream = engineStreamValue.get(data.getEngine()); + return d -> stream.get(d[0], d[1]); + } + + if (styleValue != null) { + return styleValue.createNoCache(context.rng, data)::noise; + } + + return d -> Double.NaN; + }).eval(args); + } + + public record FunctionContext(@NonNull RNG rng) implements Context { + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) return false; + FunctionContext that = (FunctionContext) o; + return rng.getSeed() == that.rng.getSeed(); + } + + @Override + public int hashCode() { + return Long.hashCode(rng.getSeed()); + } + } + + @FunctionalInterface + private interface Provider { + double eval(double... args); + } +} diff --git a/core/src/main/java/com/volmit/iris/engine/object/IrisExpressionLoad.java b/core/src/main/java/com/volmit/iris/engine/object/IrisExpressionLoad.java index 9a7b8a26a..c539cc3f3 100644 --- a/core/src/main/java/com/volmit/iris/engine/object/IrisExpressionLoad.java +++ b/core/src/main/java/com/volmit/iris/engine/object/IrisExpressionLoad.java @@ -23,12 +23,11 @@ import com.volmit.iris.engine.data.cache.AtomicCache; import com.volmit.iris.engine.object.annotations.Desc; import com.volmit.iris.engine.object.annotations.Required; import com.volmit.iris.engine.object.annotations.Snippet; +import com.volmit.iris.util.collection.KMap; import com.volmit.iris.util.math.RNG; +import com.volmit.iris.util.noise.CNG; import com.volmit.iris.util.stream.ProceduralStream; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.NoArgsConstructor; +import lombok.*; import lombok.experimental.Accessors; @Snippet("expression-load") @@ -57,6 +56,9 @@ public class IrisExpressionLoad { private transient AtomicCache> streamCache = new AtomicCache<>(); private transient AtomicCache valueCache = new AtomicCache<>(); + @Getter(AccessLevel.NONE) + @Setter(AccessLevel.NONE) + private transient final KMap styleCache = new KMap<>(); public double getValue(RNG rng, IrisData data, double x, double z) { if (engineValue != null) { @@ -68,7 +70,8 @@ public class IrisExpressionLoad { } if (styleValue != null) { - return styleValue.create(rng, data).noise(x, z); + return styleCache.computeIfAbsent(rng.getSeed(), k -> styleValue.createNoCache(new RNG(k), data)) + .noise(x, z); } return staticValue; @@ -84,7 +87,8 @@ public class IrisExpressionLoad { } if (styleValue != null) { - return styleValue.create(rng, data).noise(x, y, z); + return styleCache.computeIfAbsent(rng.getSeed(), k -> styleValue.createNoCache(new RNG(k), data)) + .noise(x, y, z); } return staticValue;