Mirror von
https://github.com/IntellectualSites/FastAsyncWorldEdit.git
synchronisiert 2024-12-24 18:10:08 +01:00
Add better control over expression timeouts. (#451)
Add better control over expression timeouts. * //timeout command can be used to change player's current timeout. * Config now also has a max timeout, can be bypassed with permission * Timeout of < 0 will let expressions run indefinitely. * Said expressions won't run on a separate thread, slightly reducing the overhead from context switching. For large //gen commands, for example, this can actually increase speed.
Dieser Commit ist enthalten in:
Ursprung
f84f3c6f85
Commit
de08c8b8c7
@ -81,6 +81,7 @@ import com.sk89q.worldedit.history.changeset.BlockOptimizedHistory;
|
||||
import com.sk89q.worldedit.history.changeset.ChangeSet;
|
||||
import com.sk89q.worldedit.internal.expression.Expression;
|
||||
import com.sk89q.worldedit.internal.expression.ExpressionException;
|
||||
import com.sk89q.worldedit.internal.expression.runtime.ExpressionTimeoutException;
|
||||
import com.sk89q.worldedit.internal.expression.runtime.RValue;
|
||||
import com.sk89q.worldedit.math.BlockVector2;
|
||||
import com.sk89q.worldedit.math.BlockVector3;
|
||||
@ -1879,7 +1880,42 @@ public class EditSession implements Extent, AutoCloseable {
|
||||
return count.getDistribution();
|
||||
}
|
||||
|
||||
public int makeShape(final Region region, final Vector3 zero, final Vector3 unit, final Pattern pattern, final String expressionString, final boolean hollow) throws ExpressionException, MaxChangedBlocksException {
|
||||
/**
|
||||
* Generate a shape for the given expression.
|
||||
*
|
||||
* @param region the region to generate the shape in
|
||||
* @param zero the coordinate origin for x/y/z variables
|
||||
* @param unit the scale of the x/y/z/ variables
|
||||
* @param pattern the default material to make the shape from
|
||||
* @param expressionString the expression defining the shape
|
||||
* @param hollow whether the shape should be hollow
|
||||
* @return number of blocks changed
|
||||
* @throws ExpressionException
|
||||
* @throws MaxChangedBlocksException
|
||||
*/
|
||||
public int makeShape(final Region region, final Vector3 zero, final Vector3 unit,
|
||||
final Pattern pattern, final String expressionString, final boolean hollow)
|
||||
throws ExpressionException, MaxChangedBlocksException {
|
||||
return makeShape(region, zero, unit, pattern, expressionString, hollow, WorldEdit.getInstance().getConfiguration().calculationTimeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a shape for the given expression.
|
||||
*
|
||||
* @param region the region to generate the shape in
|
||||
* @param zero the coordinate origin for x/y/z variables
|
||||
* @param unit the scale of the x/y/z/ variables
|
||||
* @param pattern the default material to make the shape from
|
||||
* @param expressionString the expression defining the shape
|
||||
* @param hollow whether the shape should be hollow
|
||||
* @param timeout the time, in milliseconds, to wait for each expression evaluation before halting it. -1 to disable
|
||||
* @return number of blocks changed
|
||||
* @throws ExpressionException
|
||||
* @throws MaxChangedBlocksException
|
||||
*/
|
||||
public int makeShape(final Region region, final Vector3 zero, final Vector3 unit,
|
||||
final Pattern pattern, final String expressionString, final boolean hollow, final int timeout)
|
||||
throws ExpressionException, MaxChangedBlocksException {
|
||||
final Expression expression = Expression.compile(expressionString, "x", "y", "z", "type", "data");
|
||||
expression.optimize();
|
||||
|
||||
@ -1889,6 +1925,7 @@ public class EditSession implements Extent, AutoCloseable {
|
||||
final WorldEditExpressionEnvironment environment = new WorldEditExpressionEnvironment(this, unit, zero);
|
||||
expression.setEnvironment(environment);
|
||||
|
||||
final int[] timedOut = {0};
|
||||
final ArbitraryShape shape = new ArbitraryShape(region) {
|
||||
@Override
|
||||
protected BaseBlock getMaterial(int x, int y, int z, BaseBlock defaultMaterial) {
|
||||
@ -1906,27 +1943,42 @@ public class EditSession implements Extent, AutoCloseable {
|
||||
dataVar = legacy[1];
|
||||
}
|
||||
}
|
||||
if (expression.evaluate(scaled.getX(), scaled.getY(), scaled.getZ(), typeVar, dataVar) <= 0) {
|
||||
if (expression.evaluate(new double[]{scaled.getX(), scaled.getY(), scaled.getZ(), typeVar, dataVar}, timeout) <= 0) {
|
||||
return null;
|
||||
}
|
||||
int newType = (int) typeVariable.getValue();
|
||||
int newData = (int) dataVariable.getValue();
|
||||
if (newType != typeVar || newData != dataVar) {
|
||||
return LegacyMapper.getInstance().getBlockFromLegacy((int) typeVariable.getValue(), (int) dataVariable.getValue()).toBaseBlock();
|
||||
BlockState state = LegacyMapper.getInstance().getBlockFromLegacy(newType, newData);
|
||||
return state == null ? defaultMaterial : state.toBaseBlock();
|
||||
} else {
|
||||
return defaultMaterial;
|
||||
}
|
||||
} catch (ExpressionTimeoutException e) {
|
||||
timedOut[0] = timedOut[0] + 1;
|
||||
return null;
|
||||
} catch (Exception e) {
|
||||
log.log(Level.WARNING, "Failed to create shape", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return shape.generate(this, pattern, hollow);
|
||||
int changed = shape.generate(this, pattern, hollow);
|
||||
if (timedOut[0] > 0) {
|
||||
throw new ExpressionTimeoutException(
|
||||
String.format("%d blocks changed. %d blocks took too long to evaluate (increase with //timeout).",
|
||||
changed, timedOut[0]));
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
public int deformRegion(final Region region, final Vector3 zero, final Vector3 unit, final String expressionString) throws ExpressionException, MaxChangedBlocksException {
|
||||
public int deformRegion(final Region region, final Vector3 zero, final Vector3 unit, final String expressionString)
|
||||
throws ExpressionException, MaxChangedBlocksException {
|
||||
return deformRegion(region, zero, unit, expressionString, WorldEdit.getInstance().getConfiguration().calculationTimeout);
|
||||
}
|
||||
|
||||
public int deformRegion(final Region region, final Vector3 zero, final Vector3 unit, final String expressionString,
|
||||
final int timeout) throws ExpressionException, MaxChangedBlocksException {
|
||||
final Expression expression = Expression.compile(expressionString, "x", "y", "z");
|
||||
expression.optimize();
|
||||
|
||||
@ -1944,7 +1996,7 @@ public class EditSession implements Extent, AutoCloseable {
|
||||
final Vector3 scaled = position.toVector3().subtract(zero).divide(unit);
|
||||
|
||||
// transform
|
||||
expression.evaluate(scaled.getX(), scaled.getY(), scaled.getZ());
|
||||
expression.evaluate(new double[]{scaled.getX(), scaled.getY(), scaled.getZ()}, timeout);
|
||||
|
||||
final BlockVector3 sourcePosition = environment.toWorld(x.getValue(), y.getValue(), z.getValue());
|
||||
|
||||
@ -2131,7 +2183,8 @@ public class EditSession implements Extent, AutoCloseable {
|
||||
* @return number of blocks affected
|
||||
* @throws MaxChangedBlocksException thrown if too many blocks are changed
|
||||
*/
|
||||
public int drawSpline(Pattern pattern, List<BlockVector3> nodevectors, double tension, double bias, double continuity, double quality, double radius, boolean filled)
|
||||
public int drawSpline(Pattern pattern, List<BlockVector3> nodevectors, double tension, double bias,
|
||||
double continuity, double quality, double radius, boolean filled)
|
||||
throws MaxChangedBlocksException {
|
||||
|
||||
Set<BlockVector3> vset = new HashSet<>();
|
||||
@ -2231,7 +2284,15 @@ public class EditSession implements Extent, AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
public int makeBiomeShape(final Region region, final Vector3 zero, final Vector3 unit, final BiomeType biomeType, final String expressionString, final boolean hollow) throws ExpressionException, MaxChangedBlocksException {
|
||||
public int makeBiomeShape(final Region region, final Vector3 zero, final Vector3 unit, final BiomeType biomeType,
|
||||
final String expressionString, final boolean hollow)
|
||||
throws ExpressionException, MaxChangedBlocksException {
|
||||
return makeBiomeShape(region, zero, unit, biomeType, expressionString, hollow, WorldEdit.getInstance().getConfiguration().calculationTimeout);
|
||||
}
|
||||
|
||||
public int makeBiomeShape(final Region region, final Vector3 zero, final Vector3 unit, final BiomeType biomeType,
|
||||
final String expressionString, final boolean hollow, final int timeout)
|
||||
throws ExpressionException, MaxChangedBlocksException {
|
||||
final Vector2 zero2D = zero.toVector2();
|
||||
final Vector2 unit2D = unit.toVector2();
|
||||
|
||||
@ -2242,6 +2303,7 @@ public class EditSession implements Extent, AutoCloseable {
|
||||
final WorldEditExpressionEnvironment environment = new WorldEditExpressionEnvironment(editSession, unit, zero);
|
||||
expression.setEnvironment(environment);
|
||||
|
||||
final int[] timedOut = {0};
|
||||
final ArbitraryBiomeShape shape = new ArbitraryBiomeShape(region) {
|
||||
@Override
|
||||
protected BiomeType getBiome(int x, int z, BiomeType defaultBiomeType) {
|
||||
@ -2250,20 +2312,28 @@ public class EditSession implements Extent, AutoCloseable {
|
||||
final Vector2 scaled = current.subtract(zero2D).divide(unit2D);
|
||||
|
||||
try {
|
||||
if (expression.evaluate(scaled.getX(), scaled.getZ()) <= 0) {
|
||||
if (expression.evaluate(new double[]{scaled.getX(), scaled.getZ()}, timeout) <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// TODO: Allow biome setting via a script variable (needs BiomeType<->int mapping)
|
||||
return defaultBiomeType;
|
||||
} catch (ExpressionTimeoutException e) {
|
||||
timedOut[0] = timedOut[0] + 1;
|
||||
return null;
|
||||
} catch (Exception e) {
|
||||
log.log(Level.WARNING, "Failed to create shape", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return shape.generate(this, biomeType, hollow);
|
||||
int changed = shape.generate(this, biomeType, hollow);
|
||||
if (timedOut[0] > 0) {
|
||||
throw new ExpressionTimeoutException(
|
||||
String.format("%d blocks changed. %d blocks took too long to evaluate (increase time with //timeout)",
|
||||
changed, timedOut[0]));
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
private static final BlockVector3[] recurseDirections = {
|
||||
|
@ -67,6 +67,7 @@ public abstract class LocalConfiguration {
|
||||
public int navigationWandMaxDistance = 50;
|
||||
public int scriptTimeout = 3000;
|
||||
public int calculationTimeout = 100;
|
||||
public int maxCalculationTimeout = 300;
|
||||
public Set<String> allowedDataCycleBlocks = new HashSet<>();
|
||||
public String saveDir = "schematics";
|
||||
public String scriptsDir = "craftscripts";
|
||||
|
@ -85,6 +85,7 @@ public class LocalSession {
|
||||
private transient BlockTool pickaxeMode = new SinglePickaxe();
|
||||
private transient Map<ItemType, Tool> tools = new HashMap<>();
|
||||
private transient int maxBlocksChanged = -1;
|
||||
private transient int maxTimeoutTime;
|
||||
private transient boolean useInventory;
|
||||
private transient Snapshot snapshot;
|
||||
private transient boolean hasCUISupport = false;
|
||||
@ -415,6 +416,24 @@ public class LocalSession {
|
||||
this.maxBlocksChanged = maxBlocksChanged;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the maximum time allowed for certain executions to run before cancelling them, such as expressions.
|
||||
*
|
||||
* @return timeout time, in milliseconds
|
||||
*/
|
||||
public int getTimeout() {
|
||||
return maxTimeoutTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the maximum number of blocks that can be changed.
|
||||
*
|
||||
* @param timeout the time, in milliseconds, to limit certain executions to, or -1 to disable
|
||||
*/
|
||||
public void setTimeout(int timeout) {
|
||||
this.maxTimeoutTime = timeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the super pick axe is enabled.
|
||||
*
|
||||
|
@ -57,9 +57,9 @@ public class GeneralCommands {
|
||||
|
||||
@Command(
|
||||
aliases = { "/limit" },
|
||||
usage = "<limit>",
|
||||
usage = "[limit]",
|
||||
desc = "Modify block change limit",
|
||||
min = 1,
|
||||
min = 0,
|
||||
max = 1
|
||||
)
|
||||
@CommandPermissions("worldedit.limit")
|
||||
@ -68,7 +68,7 @@ public class GeneralCommands {
|
||||
LocalConfiguration config = worldEdit.getConfiguration();
|
||||
boolean mayDisable = player.hasPermission("worldedit.limit.unrestricted");
|
||||
|
||||
int limit = Math.max(-1, args.getInteger(0));
|
||||
int limit = args.argsLength() == 0 ? config.defaultChangeLimit : Math.max(-1, args.getInteger(0));
|
||||
if (!mayDisable && config.maxChangeLimit > -1) {
|
||||
if (limit > config.maxChangeLimit) {
|
||||
player.printError("Your maximum allowable limit is " + config.maxChangeLimit + ".");
|
||||
@ -78,13 +78,43 @@ public class GeneralCommands {
|
||||
|
||||
session.setBlockChangeLimit(limit);
|
||||
|
||||
if (limit != -1) {
|
||||
player.print("Block change limit set to " + limit + ". (Use //limit -1 to go back to the default.)");
|
||||
if (limit != config.defaultChangeLimit) {
|
||||
player.print("Block change limit set to " + limit + ". (Use //limit to go back to the default.)");
|
||||
} else {
|
||||
player.print("Block change limit set to " + limit + ".");
|
||||
}
|
||||
}
|
||||
|
||||
@Command(
|
||||
aliases = { "/timeout" },
|
||||
usage = "[time]",
|
||||
desc = "Modify evaluation timeout time.",
|
||||
min = 0,
|
||||
max = 1
|
||||
)
|
||||
@CommandPermissions("worldedit.timeout")
|
||||
public void timeout(Player player, LocalSession session, EditSession editSession, CommandContext args) throws WorldEditException {
|
||||
|
||||
LocalConfiguration config = worldEdit.getConfiguration();
|
||||
boolean mayDisable = player.hasPermission("worldedit.timeout.unrestricted");
|
||||
|
||||
int limit = args.argsLength() == 0 ? config.calculationTimeout : Math.max(-1, args.getInteger(0));
|
||||
if (!mayDisable && config.maxCalculationTimeout > -1) {
|
||||
if (limit > config.maxCalculationTimeout) {
|
||||
player.printError("Your maximum allowable timeout is " + config.maxCalculationTimeout + " ms.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
session.setTimeout(limit);
|
||||
|
||||
if (limit != config.calculationTimeout) {
|
||||
player.print("Timeout time set to " + limit + " ms. (Use //timeout to go back to the default.)");
|
||||
} else {
|
||||
player.print("Timeout time set to " + limit + " ms.");
|
||||
}
|
||||
}
|
||||
|
||||
@Command(
|
||||
aliases = { "/fast" },
|
||||
usage = "[on|off]",
|
||||
|
@ -306,7 +306,7 @@ public class GenerationCommands {
|
||||
}
|
||||
|
||||
try {
|
||||
final int affected = editSession.makeShape(region, zero, unit, pattern, expression, hollow);
|
||||
final int affected = editSession.makeShape(region, zero, unit, pattern, expression, hollow, session.getTimeout());
|
||||
player.findFreePosition();
|
||||
player.print(affected + " block(s) have been created.");
|
||||
} catch (ExpressionException e) {
|
||||
@ -333,7 +333,7 @@ public class GenerationCommands {
|
||||
min = 2,
|
||||
max = -1
|
||||
)
|
||||
@CommandPermissions({"worldedit.generation.shape", "worldedit.biome.set"})
|
||||
@CommandPermissions("worldedit.generation.shape.biome")
|
||||
@Logging(ALL)
|
||||
public void generateBiome(Player player, LocalSession session, EditSession editSession,
|
||||
@Selection Region region,
|
||||
@ -371,7 +371,7 @@ public class GenerationCommands {
|
||||
}
|
||||
|
||||
try {
|
||||
final int affected = editSession.makeBiomeShape(region, zero, unit, target, expression, hollow);
|
||||
final int affected = editSession.makeBiomeShape(region, zero, unit, target, expression, hollow, session.getTimeout());
|
||||
player.findFreePosition();
|
||||
player.print("" + affected + " columns affected.");
|
||||
} catch (ExpressionException e) {
|
||||
|
@ -404,7 +404,7 @@ public class RegionCommands {
|
||||
}
|
||||
|
||||
try {
|
||||
final int affected = editSession.deformRegion(region, zero, unit, expression);
|
||||
final int affected = editSession.deformRegion(region, zero, unit, expression, session.getTimeout());
|
||||
player.findFreePosition();
|
||||
player.print(affected + " block(s) have been deformed.");
|
||||
} catch (ExpressionException e) {
|
||||
|
@ -52,6 +52,7 @@ import com.sk89q.worldedit.math.BlockVector3;
|
||||
import com.sk89q.worldedit.regions.CuboidRegion;
|
||||
import com.sk89q.worldedit.regions.CylinderRegion;
|
||||
import com.sk89q.worldedit.regions.Region;
|
||||
import com.sk89q.worldedit.session.SessionOwner;
|
||||
import com.sk89q.worldedit.util.command.CommandCallable;
|
||||
import com.sk89q.worldedit.util.command.CommandMapping;
|
||||
import com.sk89q.worldedit.util.command.Dispatcher;
|
||||
@ -541,13 +542,18 @@ public class UtilityCommands {
|
||||
public void calc(Actor actor, @Text String input) throws CommandException {
|
||||
try {
|
||||
Expression expression = Expression.compile(input);
|
||||
actor.print("= " + expression.evaluate());
|
||||
if (actor instanceof SessionOwner) {
|
||||
actor.print("= " + expression.evaluate(
|
||||
new double[]{}, WorldEdit.getInstance().getSessionManager().get((SessionOwner) actor).getTimeout()));
|
||||
} else {
|
||||
actor.print("= " + expression.evaluate());
|
||||
}
|
||||
} catch (EvaluationException e) {
|
||||
actor.printError(String.format(
|
||||
"'%s' could not be parsed as a valid expression", input));
|
||||
"'%s' could not be evaluated (error: %s)", input, e.getMessage()));
|
||||
} catch (ExpressionException e) {
|
||||
actor.printError(String.format(
|
||||
"'%s' could not be evaluated (error: %s)", input, e.getMessage()));
|
||||
"'%s' could not be parsed as a valid expression", input));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -79,6 +79,7 @@ public class SelectionCommand extends SimpleCommand<Operation> {
|
||||
EditContext editContext = new EditContext();
|
||||
editContext.setDestination(locals.get(EditSession.class));
|
||||
editContext.setRegion(selection);
|
||||
editContext.setSession(session);
|
||||
|
||||
Operation operation = operationFactory.createFromContext(editContext);
|
||||
Operations.completeBlindly(operation);
|
||||
|
@ -75,7 +75,7 @@ public class ShapedBrushCommand extends SimpleCommand<Object> {
|
||||
BrushTool tool = session.getBrushTool(player.getItemInHand(HandSide.MAIN_HAND).getType());
|
||||
tool.setSize(radius);
|
||||
tool.setFill(null);
|
||||
tool.setBrush(new OperationFactoryBrush(factory, regionFactory), permission);
|
||||
tool.setBrush(new OperationFactoryBrush(factory, regionFactory, session), permission);
|
||||
} catch (MaxBrushRadiusException | InvalidToolBindException e) {
|
||||
WorldEdit.getInstance().getPlatformManager().getCommandManager().getExceptionConverter().convert(e);
|
||||
}
|
||||
|
@ -20,6 +20,7 @@
|
||||
package com.sk89q.worldedit.command.tool.brush;
|
||||
|
||||
import com.sk89q.worldedit.EditSession;
|
||||
import com.sk89q.worldedit.LocalSession;
|
||||
import com.sk89q.worldedit.MaxChangedBlocksException;
|
||||
import com.sk89q.worldedit.function.Contextual;
|
||||
import com.sk89q.worldedit.function.EditContext;
|
||||
@ -33,10 +34,16 @@ public class OperationFactoryBrush implements Brush {
|
||||
|
||||
private final Contextual<? extends Operation> operationFactory;
|
||||
private final RegionFactory regionFactory;
|
||||
private final LocalSession session;
|
||||
|
||||
public OperationFactoryBrush(Contextual<? extends Operation> operationFactory, RegionFactory regionFactory) {
|
||||
this(operationFactory, regionFactory, null);
|
||||
}
|
||||
|
||||
public OperationFactoryBrush(Contextual<? extends Operation> operationFactory, RegionFactory regionFactory, LocalSession session) {
|
||||
this.operationFactory = operationFactory;
|
||||
this.regionFactory = regionFactory;
|
||||
this.session = session;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -45,6 +52,7 @@ public class OperationFactoryBrush implements Brush {
|
||||
context.setDestination(editSession);
|
||||
context.setRegion(regionFactory.createCenteredAt(position, size));
|
||||
context.setFill(pattern);
|
||||
context.setSession(session);
|
||||
Operation operation = operationFactory.createFromContext(context);
|
||||
Operations.completeLegacy(operation);
|
||||
}
|
||||
|
@ -29,8 +29,11 @@ import com.sk89q.worldedit.internal.expression.ExpressionException;
|
||||
import com.sk89q.worldedit.internal.registry.InputParser;
|
||||
import com.sk89q.worldedit.math.Vector3;
|
||||
import com.sk89q.worldedit.regions.shape.WorldEditExpressionEnvironment;
|
||||
import com.sk89q.worldedit.session.SessionOwner;
|
||||
import com.sk89q.worldedit.session.request.Request;
|
||||
|
||||
import java.util.function.IntSupplier;
|
||||
|
||||
public class ExpressionMaskParser extends InputParser<Mask> {
|
||||
|
||||
public ExpressionMaskParser(WorldEdit worldEdit) {
|
||||
@ -48,6 +51,11 @@ public class ExpressionMaskParser extends InputParser<Mask> {
|
||||
WorldEditExpressionEnvironment env = new WorldEditExpressionEnvironment(
|
||||
Request.request().getEditSession(), Vector3.ONE, Vector3.ZERO);
|
||||
exp.setEnvironment(env);
|
||||
if (context.getActor() instanceof SessionOwner) {
|
||||
SessionOwner owner = (SessionOwner) context.getActor();
|
||||
IntSupplier timeout = () -> WorldEdit.getInstance().getSessionManager().get(owner).getTimeout();
|
||||
return new ExpressionMask(exp, timeout);
|
||||
}
|
||||
return new ExpressionMask(exp);
|
||||
} catch (ExpressionException e) {
|
||||
throw new InputParseException("Invalid expression: " + e.getMessage());
|
||||
|
@ -21,6 +21,7 @@ package com.sk89q.worldedit.function;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
import com.sk89q.worldedit.LocalSession;
|
||||
import com.sk89q.worldedit.extent.Extent;
|
||||
import com.sk89q.worldedit.function.pattern.Pattern;
|
||||
import com.sk89q.worldedit.regions.Region;
|
||||
@ -32,6 +33,7 @@ public class EditContext {
|
||||
private Extent destination;
|
||||
@Nullable private Region region;
|
||||
@Nullable private Pattern fill;
|
||||
@Nullable private LocalSession session;
|
||||
|
||||
public Extent getDestination() {
|
||||
return destination;
|
||||
@ -60,4 +62,12 @@ public class EditContext {
|
||||
this.fill = fill;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public LocalSession getSession() {
|
||||
return session;
|
||||
}
|
||||
|
||||
public void setSession(@Nullable LocalSession session) {
|
||||
this.session = session;
|
||||
}
|
||||
}
|
||||
|
@ -23,6 +23,8 @@ import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.sk89q.worldedit.util.GuavaUtil.firstNonNull;
|
||||
|
||||
import com.sk89q.worldedit.EditSession;
|
||||
import com.sk89q.worldedit.LocalSession;
|
||||
import com.sk89q.worldedit.WorldEdit;
|
||||
import com.sk89q.worldedit.WorldEditException;
|
||||
import com.sk89q.worldedit.extent.Extent;
|
||||
import com.sk89q.worldedit.extent.NullExtent;
|
||||
@ -147,7 +149,9 @@ public class Deform implements Contextual<Operation> {
|
||||
unit = Vector3.ONE;
|
||||
}
|
||||
|
||||
return new DeformOperation(context.getDestination(), region, zero, unit, expression);
|
||||
LocalSession session = context.getSession();
|
||||
return new DeformOperation(context.getDestination(), region, zero, unit, expression,
|
||||
session == null ? WorldEdit.getInstance().getConfiguration().calculationTimeout : session.getTimeout());
|
||||
}
|
||||
|
||||
private static final class DeformOperation implements Operation {
|
||||
@ -156,20 +160,22 @@ public class Deform implements Contextual<Operation> {
|
||||
private final Vector3 zero;
|
||||
private final Vector3 unit;
|
||||
private final String expression;
|
||||
private final int timeout;
|
||||
|
||||
private DeformOperation(Extent destination, Region region, Vector3 zero, Vector3 unit, String expression) {
|
||||
private DeformOperation(Extent destination, Region region, Vector3 zero, Vector3 unit, String expression, int timeout) {
|
||||
this.destination = destination;
|
||||
this.region = region;
|
||||
this.zero = zero;
|
||||
this.unit = unit;
|
||||
this.expression = expression;
|
||||
this.timeout = timeout;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Operation resume(RunContext run) throws WorldEditException {
|
||||
try {
|
||||
// TODO: Move deformation code
|
||||
((EditSession) destination).deformRegion(region, zero, unit, expression);
|
||||
((EditSession) destination).deformRegion(region, zero, unit, expression, timeout);
|
||||
return null;
|
||||
} catch (ExpressionException e) {
|
||||
throw new RuntimeException("Failed to execute expression", e); // TODO: Better exception to throw here?
|
||||
|
@ -21,6 +21,7 @@ package com.sk89q.worldedit.function.mask;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
import com.sk89q.worldedit.WorldEdit;
|
||||
import com.sk89q.worldedit.internal.expression.Expression;
|
||||
import com.sk89q.worldedit.internal.expression.ExpressionException;
|
||||
import com.sk89q.worldedit.internal.expression.runtime.EvaluationException;
|
||||
@ -28,6 +29,7 @@ import com.sk89q.worldedit.math.BlockVector3;
|
||||
import com.sk89q.worldedit.regions.shape.WorldEditExpressionEnvironment;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.function.IntSupplier;
|
||||
|
||||
/**
|
||||
* A mask that evaluates an expression.
|
||||
@ -38,6 +40,7 @@ import javax.annotation.Nullable;
|
||||
public class ExpressionMask extends AbstractMask {
|
||||
|
||||
private final Expression expression;
|
||||
private final IntSupplier timeout;
|
||||
|
||||
/**
|
||||
* Create a new instance.
|
||||
@ -46,8 +49,7 @@ public class ExpressionMask extends AbstractMask {
|
||||
* @throws ExpressionException thrown if there is an error with the expression
|
||||
*/
|
||||
public ExpressionMask(String expression) throws ExpressionException {
|
||||
checkNotNull(expression);
|
||||
this.expression = Expression.compile(expression, "x", "y", "z");
|
||||
this(Expression.compile(checkNotNull(expression), "x", "y", "z"));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -56,8 +58,13 @@ public class ExpressionMask extends AbstractMask {
|
||||
* @param expression the expression
|
||||
*/
|
||||
public ExpressionMask(Expression expression) {
|
||||
this(expression, null);
|
||||
}
|
||||
|
||||
public ExpressionMask(Expression expression, @Nullable IntSupplier timeout) {
|
||||
checkNotNull(expression);
|
||||
this.expression = expression;
|
||||
this.timeout = timeout;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -66,7 +73,12 @@ public class ExpressionMask extends AbstractMask {
|
||||
if (expression.getEnvironment() instanceof WorldEditExpressionEnvironment) {
|
||||
((WorldEditExpressionEnvironment) expression.getEnvironment()).setCurrentBlock(vector.toVector3());
|
||||
}
|
||||
return expression.evaluate(vector.getX(), vector.getY(), vector.getZ()) > 0;
|
||||
if (timeout == null) {
|
||||
return expression.evaluate(vector.getX(), vector.getY(), vector.getZ()) > 0;
|
||||
} else {
|
||||
return expression.evaluate(new double[]{vector.getX(), vector.getY(), vector.getZ()},
|
||||
timeout.getAsInt()) > 0;
|
||||
}
|
||||
} catch (EvaluationException e) {
|
||||
return false;
|
||||
}
|
||||
@ -75,7 +87,7 @@ public class ExpressionMask extends AbstractMask {
|
||||
@Nullable
|
||||
@Override
|
||||
public Mask2D toMask2D() {
|
||||
return new ExpressionMask2D(expression);
|
||||
return new ExpressionMask2D(expression, timeout);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -21,14 +21,19 @@ package com.sk89q.worldedit.function.mask;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
import com.sk89q.worldedit.WorldEdit;
|
||||
import com.sk89q.worldedit.internal.expression.Expression;
|
||||
import com.sk89q.worldedit.internal.expression.ExpressionException;
|
||||
import com.sk89q.worldedit.internal.expression.runtime.EvaluationException;
|
||||
import com.sk89q.worldedit.math.BlockVector2;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.function.IntSupplier;
|
||||
|
||||
public class ExpressionMask2D extends AbstractMask2D {
|
||||
|
||||
private final Expression expression;
|
||||
private final IntSupplier timeout;
|
||||
|
||||
/**
|
||||
* Create a new instance.
|
||||
@ -37,8 +42,7 @@ public class ExpressionMask2D extends AbstractMask2D {
|
||||
* @throws ExpressionException thrown if there is an error with the expression
|
||||
*/
|
||||
public ExpressionMask2D(String expression) throws ExpressionException {
|
||||
checkNotNull(expression);
|
||||
this.expression = Expression.compile(expression, "x", "z");
|
||||
this(Expression.compile(checkNotNull(expression), "x", "z"));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -47,14 +51,23 @@ public class ExpressionMask2D extends AbstractMask2D {
|
||||
* @param expression the expression
|
||||
*/
|
||||
public ExpressionMask2D(Expression expression) {
|
||||
this(expression, null);
|
||||
}
|
||||
|
||||
public ExpressionMask2D(Expression expression, @Nullable IntSupplier timeout) {
|
||||
checkNotNull(expression);
|
||||
this.expression = expression;
|
||||
this.timeout = timeout;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean test(BlockVector2 vector) {
|
||||
try {
|
||||
return expression.evaluate(vector.getX(), 0, vector.getZ()) > 0;
|
||||
if (timeout != null) {
|
||||
return expression.evaluate(vector.getX(), 0, vector.getZ()) > 0;
|
||||
} else {
|
||||
return expression.evaluate(new double[]{vector.getX(), 0, vector.getZ()}, timeout.getAsInt()) > 0;
|
||||
}
|
||||
} catch (EvaluationException e) {
|
||||
return false;
|
||||
}
|
||||
|
@ -27,6 +27,7 @@ import com.sk89q.worldedit.internal.expression.parser.Parser;
|
||||
import com.sk89q.worldedit.internal.expression.runtime.Constant;
|
||||
import com.sk89q.worldedit.internal.expression.runtime.EvaluationException;
|
||||
import com.sk89q.worldedit.internal.expression.runtime.ExpressionEnvironment;
|
||||
import com.sk89q.worldedit.internal.expression.runtime.ExpressionTimeoutException;
|
||||
import com.sk89q.worldedit.internal.expression.runtime.Functions;
|
||||
import com.sk89q.worldedit.internal.expression.runtime.RValue;
|
||||
import com.sk89q.worldedit.internal.expression.runtime.ReturnException;
|
||||
@ -36,7 +37,6 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Stack;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
@ -117,6 +117,10 @@ public class Expression {
|
||||
}
|
||||
|
||||
public double evaluate(double... values) throws EvaluationException {
|
||||
return evaluate(values, WorldEdit.getInstance().getConfiguration().calculationTimeout);
|
||||
}
|
||||
|
||||
public double evaluate(double[] values, int timeout) throws EvaluationException {
|
||||
for (int i = 0; i < values.length; ++i) {
|
||||
final String variableName = variableNames[i];
|
||||
final RValue invokable = variables.get(variableName);
|
||||
@ -127,34 +131,44 @@ public class Expression {
|
||||
((Variable) invokable).value = values[i];
|
||||
}
|
||||
|
||||
Future<Double> result = evalThread.submit(new Callable<Double>() {
|
||||
@Override
|
||||
public Double call() throws Exception {
|
||||
pushInstance();
|
||||
try {
|
||||
return root.getValue();
|
||||
} finally {
|
||||
popInstance();
|
||||
}
|
||||
}
|
||||
});
|
||||
try {
|
||||
return result.get(WorldEdit.getInstance().getConfiguration().calculationTimeout, TimeUnit.MILLISECONDS);
|
||||
if (timeout < 0) {
|
||||
return evaluateRoot();
|
||||
}
|
||||
return evaluateRootTimed(timeout);
|
||||
} catch (ReturnException e) {
|
||||
return e.getValue();
|
||||
} // other evaluation exceptions are thrown out of this method
|
||||
}
|
||||
|
||||
private double evaluateRootTimed(int timeout) throws EvaluationException {
|
||||
Future<Double> result = evalThread.submit(this::evaluateRoot);
|
||||
try {
|
||||
return result.get(timeout, TimeUnit.MILLISECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new RuntimeException(e);
|
||||
} catch (TimeoutException e) {
|
||||
result.cancel(true);
|
||||
throw new ExpressionTimeoutException("Calculations exceeded time limit.");
|
||||
} catch (ExecutionException e) {
|
||||
Throwable cause = e.getCause();
|
||||
if (cause instanceof ReturnException) {
|
||||
return ((ReturnException) cause).getValue();
|
||||
if (cause instanceof EvaluationException) {
|
||||
throw (EvaluationException) cause;
|
||||
}
|
||||
if (cause instanceof RuntimeException) {
|
||||
throw (RuntimeException) cause;
|
||||
}
|
||||
throw new RuntimeException(cause);
|
||||
} catch (TimeoutException e) {
|
||||
result.cancel(true);
|
||||
throw new EvaluationException(-1, "Calculations exceeded time limit.");
|
||||
}
|
||||
}
|
||||
|
||||
private Double evaluateRoot() throws EvaluationException {
|
||||
pushInstance();
|
||||
try {
|
||||
return root.getValue();
|
||||
} finally {
|
||||
popInstance();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,29 @@
|
||||
/*
|
||||
* WorldEdit, a Minecraft world manipulation toolkit
|
||||
* Copyright (C) sk89q <http://www.sk89q.com>
|
||||
* Copyright (C) WorldEdit team and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
||||
* for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.sk89q.worldedit.internal.expression.runtime;
|
||||
|
||||
/**
|
||||
* Thrown when an evaluation exceeds the timeout time.
|
||||
*/
|
||||
public class ExpressionTimeoutException extends EvaluationException {
|
||||
public ExpressionTimeoutException(String message) {
|
||||
super(-1, message);
|
||||
}
|
||||
}
|
@ -155,31 +155,20 @@ public class SessionManager {
|
||||
|
||||
session.setConfiguration(config);
|
||||
session.setBlockChangeLimit(config.defaultChangeLimit);
|
||||
session.setTimeout(config.calculationTimeout);
|
||||
|
||||
// Remember the session regardless of if it's currently active or not.
|
||||
// And have the SessionTracker FLUSH inactive sessions.
|
||||
sessions.put(getKey(owner), new SessionHolder(sessionKey, session));
|
||||
}
|
||||
|
||||
// Set the limit on the number of blocks that an operation can
|
||||
// change at once, or don't if the owner has an override or there
|
||||
// is no limit. There is also a default limit
|
||||
int currentChangeLimit = session.getBlockChangeLimit();
|
||||
|
||||
if (!owner.hasPermission("worldedit.limit.unrestricted") && config.maxChangeLimit > -1) {
|
||||
// If the default limit is infinite but there is a maximum
|
||||
// limit, make sure to not have it be overridden
|
||||
if (config.defaultChangeLimit < 0) {
|
||||
if (currentChangeLimit < 0 || currentChangeLimit > config.maxChangeLimit) {
|
||||
session.setBlockChangeLimit(config.maxChangeLimit);
|
||||
}
|
||||
} else {
|
||||
// Bound the change limit
|
||||
int maxChangeLimit = config.maxChangeLimit;
|
||||
if (currentChangeLimit == -1 || currentChangeLimit > maxChangeLimit) {
|
||||
session.setBlockChangeLimit(maxChangeLimit);
|
||||
}
|
||||
}
|
||||
if (shouldBoundLimit(owner.hasPermission("worldedit.limit.unrestricted"),
|
||||
session.getBlockChangeLimit(), config.maxChangeLimit)) {
|
||||
session.setBlockChangeLimit(config.maxChangeLimit);
|
||||
}
|
||||
if (shouldBoundLimit(owner.hasPermission("worldedit.timeout.unrestricted"),
|
||||
session.getTimeout(), config.maxCalculationTimeout)) {
|
||||
session.setTimeout(config.maxCalculationTimeout);
|
||||
}
|
||||
|
||||
// Have the session use inventory if it's enabled and the owner
|
||||
@ -192,6 +181,13 @@ public class SessionManager {
|
||||
return session;
|
||||
}
|
||||
|
||||
private boolean shouldBoundLimit(boolean mayBypass, int currentLimit, int maxLimit) {
|
||||
if (!mayBypass && maxLimit > -1) { // if player can't bypass and max is finite
|
||||
return currentLimit < 0 || currentLimit > maxLimit; // make sure current is finite and less than max
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a map of sessions to disk.
|
||||
*
|
||||
|
@ -112,6 +112,7 @@ public class PropertiesConfiguration extends LocalConfiguration {
|
||||
navigationUseGlass = getBool("nav-use-glass", navigationUseGlass);
|
||||
scriptTimeout = getInt("scripting-timeout", scriptTimeout);
|
||||
calculationTimeout = getInt("calculation-timeout", calculationTimeout);
|
||||
maxCalculationTimeout = getInt("max-calculation-timeout", maxCalculationTimeout);
|
||||
saveDir = getString("schematic-save-dir", saveDir);
|
||||
scriptsDir = getString("craftscript-dir", scriptsDir);
|
||||
butcherDefaultRadius = getInt("butcher-default-radius", butcherDefaultRadius);
|
||||
|
@ -108,6 +108,7 @@ public class YAMLConfiguration extends LocalConfiguration {
|
||||
scriptsDir = config.getString("scripting.dir", scriptsDir);
|
||||
|
||||
calculationTimeout = config.getInt("calculation.timeout", calculationTimeout);
|
||||
maxCalculationTimeout = config.getInt("calculation.max-timeout", maxCalculationTimeout);
|
||||
|
||||
saveDir = config.getString("saving.dir", saveDir);
|
||||
|
||||
|
@ -64,7 +64,7 @@ public class ExpressionTest {
|
||||
assertEquals(atan2(3, 4), simpleEval("atan2(3, 4)"), 0);
|
||||
|
||||
// check variables
|
||||
assertEquals(8, compile("foo+bar", "foo", "bar").evaluate(5, 3), 0);
|
||||
assertEquals(8, compile("foo+bar", "foo", "bar").evaluate(5D, 3D), 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -123,7 +123,7 @@ public class ExpressionTest {
|
||||
@Test
|
||||
public void testAssign() throws ExpressionException {
|
||||
Expression foo = compile("{a=x} b=y; c=z", "x", "y", "z", "a", "b", "c");
|
||||
foo.evaluate(2, 3, 5);
|
||||
foo.evaluate(2D, 3D, 5D);
|
||||
assertEquals(2, foo.getVariable("a", false).getValue(), 0);
|
||||
assertEquals(3, foo.getVariable("b", false).getValue(), 0);
|
||||
assertEquals(5, foo.getVariable("c", false).getValue(), 0);
|
||||
@ -136,13 +136,13 @@ public class ExpressionTest {
|
||||
|
||||
// test 'dangling else'
|
||||
final Expression expression1 = compile("if (1) if (0) x=4; else y=5;", "x", "y");
|
||||
expression1.evaluate(1, 2);
|
||||
expression1.evaluate(1D, 2D);
|
||||
assertEquals(1, expression1.getVariable("x", false).getValue(), 0);
|
||||
assertEquals(5, expression1.getVariable("y", false).getValue(), 0);
|
||||
|
||||
// test if the if construct is correctly recognized as a statement
|
||||
final Expression expression2 = compile("if (0) if (1) x=5; y=4;", "x", "y");
|
||||
expression2.evaluate(1, 2);
|
||||
expression2.evaluate(1D, 2D);
|
||||
assertEquals(4, expression2.getVariable("y", false).getValue(), 0);
|
||||
}
|
||||
|
||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren