Expression functions (#1165)

This commit is contained in:
Julian Krings 2025-02-04 13:03:49 +01:00 committed by GitHub
parent c6963d0cd3
commit a778cc51a6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 126 additions and 11 deletions

View File

@ -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'

View File

@ -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<IrisExpressionLoad> variables = new KList<>();
@ArrayType(type = IrisExpressionFunction.class, min = 1)
@Desc("Functions to use in this expression")
private KList<IrisExpressionFunction> 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

View File

@ -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<FunctionContext, Provider> 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);
}
}

View File

@ -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<ProceduralStream<Double>> streamCache = new AtomicCache<>();
private transient AtomicCache<Double> valueCache = new AtomicCache<>();
@Getter(AccessLevel.NONE)
@Setter(AccessLevel.NONE)
private transient final KMap<Long, CNG> 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;