Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add SecFilter, refactor Variable and Variables slightly to accomodate. #6912

Merged
merged 12 commits into from
Sep 1, 2024
2 changes: 1 addition & 1 deletion src/main/java/ch/njol/skript/expressions/ExprFilter.java
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
import java.util.regex.Pattern;
import java.util.stream.StreamSupport;

@Name("Filter")
@Name("Filter (Expression)")
@Description({
"Filters a list based on a condition. ",
"For example, if you ran 'broadcast \"something\" and \"something else\" where [string input is \"something\"]', ",
Expand Down
58 changes: 9 additions & 49 deletions src/main/java/ch/njol/skript/lang/Variable.java
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ public <R> Variable<R> getConvertedExpression(Class<R>... to) {
// prevents e.g. {%expr%} where "%expr%" ends with "::*" from returning a Map
if (name.endsWith(Variable.SEPARATOR + "*") != list)
return null;
Object value = !list ? convertIfOldPlayer(name, event, Variables.getVariable(name, event, local)) : Variables.getVariable(name, event, local);
Object value = !list ? convertIfOldPlayer(name, local, event, Variables.getVariable(name, event, local)) : Variables.getVariable(name, event, local);
if (value != null)
return value;

Expand Down Expand Up @@ -346,7 +346,7 @@ public <R> Variable<R> getConvertedExpression(Class<R>... to) {
else
value = variable.getValue();
if (value != null)
convertedValues.add(convertIfOldPlayer(name + variable.getKey(), event, value));
convertedValues.add(convertIfOldPlayer(name + variable.getKey(), local, event, value));
}
}
return convertedValues.toArray();
Expand All @@ -357,13 +357,13 @@ public <R> Variable<R> getConvertedExpression(Class<R>... to) {
* because the player object inside the variable will be a (kinda) dead variable
* as a new player object has been created by the server.
*/
@Nullable Object convertIfOldPlayer(String key, Event event, @Nullable Object object) {
if (SkriptConfig.enablePlayerVariableFix.value() && object instanceof Player) {
Player oldPlayer = (Player) object;
public static <T> @Nullable T convertIfOldPlayer(String key, boolean local, Event event, @Nullable T object) {
if (SkriptConfig.enablePlayerVariableFix.value() && object instanceof Player oldPlayer) {
if (!oldPlayer.isValid() && oldPlayer.isOnline()) {
Player newPlayer = Bukkit.getPlayer(oldPlayer.getUniqueId());
Variables.setVariable(key, newPlayer, event, local);
return newPlayer;
//noinspection unchecked
return (T) newPlayer;
}
}
return object;
Expand All @@ -372,48 +372,7 @@ public <R> Variable<R> getConvertedExpression(Class<R>... to) {
public Iterator<Pair<String, Object>> variablesIterator(Event event) {
if (!list)
throw new SkriptAPIException("Looping a non-list variable");
String name = StringUtils.substring(this.name.toString(event), 0, -1);
Object val = Variables.getVariable(name + "*", event, local);
if (val == null)
return new EmptyIterator<>();
assert val instanceof TreeMap;
// temporary list to prevent CMEs
@SuppressWarnings("unchecked")
Iterator<String> keys = new ArrayList<>(((Map<String, Object>) val).keySet()).iterator();
return new Iterator<>() {
private @Nullable String key;
private @Nullable Object next = null;

@Override
public boolean hasNext() {
if (next != null)
return true;
while (keys.hasNext()) {
key = keys.next();
if (key != null) {
next = convertIfOldPlayer(name + key, event, Variables.getVariable(name + key, event, local));
if (next != null && !(next instanceof TreeMap))
return true;
}
}
next = null;
return false;
}

@Override
public Pair<String, Object> next() {
if (!hasNext())
throw new NoSuchElementException();
Pair<String, Object> n = new Pair<>(key, next);
next = null;
return n;
}

@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
return Variables.getVariableIterator(name.toString(event), local, event);
}

@Override
Expand Down Expand Up @@ -441,8 +400,9 @@ public boolean hasNext() {
@Nullable String key = keys.next();
if (key != null) {
next = Converters.convert(Variables.getVariable(name + key, event, local), types);

//noinspection unchecked
next = (T) convertIfOldPlayer(name + key, event, next);
next = (T) convertIfOldPlayer(name + key, local, event, next);
if (next != null && !(next instanceof TreeMap))
return true;
}
Expand Down
192 changes: 192 additions & 0 deletions src/main/java/ch/njol/skript/sections/SecFilter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
package ch.njol.skript.sections;

import ch.njol.skript.ScriptLoader;
import ch.njol.skript.Skript;
import ch.njol.skript.config.Node;
import ch.njol.skript.config.SectionNode;
import ch.njol.skript.doc.Description;
import ch.njol.skript.doc.Examples;
import ch.njol.skript.doc.Name;
import ch.njol.skript.doc.Since;
import ch.njol.skript.expressions.ExprInput;
import ch.njol.skript.lang.Condition;
import ch.njol.skript.lang.Expression;
import ch.njol.skript.lang.InputSource;
import ch.njol.skript.lang.Section;
import ch.njol.skript.lang.SkriptParser.ParseResult;
import ch.njol.skript.lang.TriggerItem;
import ch.njol.skript.lang.Variable;
import ch.njol.skript.lang.parser.ParserInstance;
import ch.njol.skript.variables.Variables;
import ch.njol.util.Kleenean;
import ch.njol.util.Pair;
import ch.njol.util.StringUtils;
import org.bukkit.event.Event;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.UnknownNullability;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.stream.StreamSupport;

@Name("Filter (Section)")
@Description({
"Filters a variable list based on the supplied conditions. Unlike the filter expression, this effect " +
"maintains the indices of the filtered list.",
"It also supports filtering based on meeting any of the given criteria, rather than all, like multi-line if statements."
})
@Examples({
"set {_a::*} to integers between -10 and 10",
"",
"filter {_a::*} to match:",
"\tinput is a number",
"\tmod(input, 2) = 0",
"\tinput > 0",
"",
"send {_a::*} # sends 2, 4, 6, 8, and 10",
})
@Since("INSERT VERSION")
public class SecFilter extends Section implements InputSource {

static {
Skript.registerSection(SecFilter.class,
"filter %objects% to match [:any|all]");
if (!ParserInstance.isRegistered(InputSource.InputData.class))
ParserInstance.registerData(InputSource.InputData.class, InputSource.InputData::new);
}

@UnknownNullability
private Variable<?> unfilteredObjects;
private final List<Condition> conditions = new ArrayList<>();
private boolean isAny;

@Nullable
private Object currentValue;
@UnknownNullability
private String currentIndex;
sovdeeth marked this conversation as resolved.
Show resolved Hide resolved
private final Set<ExprInput<?>> dependentInputs = new HashSet<>();

@Override
public boolean init(Expression<?>[] expressions, int matchedPattern, Kleenean isDelayed, ParseResult parseResult, SectionNode sectionNode, List<TriggerItem> triggerItems) {
if (expressions[0].isSingle() || !(expressions[0] instanceof Variable)) {
Skript.error("You can only filter list variables!");
return false;
}
unfilteredObjects = (Variable<?>) expressions[0];
isAny = parseResult.hasTag("any");

// Code pulled from SecConditional
ParserInstance parser = getParser();
if (sectionNode.isEmpty()) {
Skript.error("filter sections must contain at least one condition");
return false;
}
InputSource.InputData inputData = getParser().getData(InputSource.InputData.class);
InputSource originalSource = inputData.getSource();
inputData.setSource(this);
try {
for (Node childNode : sectionNode) {
if (childNode instanceof SectionNode) {
sovdeeth marked this conversation as resolved.
Show resolved Hide resolved
Skript.error("filter sections may not contain other sections");
return false;
}
String childKey = childNode.getKey();
if (childKey != null) {
childKey = ScriptLoader.replaceOptions(childKey);
parser.setNode(childNode);
Condition condition = Condition.parse(childKey, "Can't understand this condition: '" + childKey + "'");
parser.setNode(sectionNode);
// if this condition was invalid, don't bother parsing the rest
if (condition == null)
return false;
conditions.add(condition);
}
}
} finally {
inputData.setSource(originalSource);
}

return true;
}

@Override
protected @Nullable TriggerItem walk(Event event) {
// get the name only once to avoid issues where the name may change between evaluations.
String varName = unfilteredObjects.getName().toString(event);
String varSubName = StringUtils.substring(varName, 0, -1);
boolean local = unfilteredObjects.isLocal();

// not ideal to get this AND the iterator, but using this value could be unreliable due to name change issue from above.
// since we just use it for a length optimization at the end, it's ok to be a little unreliable.
var rawVariable = ((Map<String, Object>) unfilteredObjects.getRaw(event));
if (rawVariable == null)
return getNext();
int initialSize = rawVariable.size();

// we save both because we don't yet know which will be cheaper to use.
List<Pair<String, Object>> toKeep = new ArrayList<>();
List<String> toRemove = new ArrayList<>();

var variableIterator = Variables.getVariableIterator(varName, local, event);
var stream = StreamSupport.stream(Spliterators.spliteratorUnknownSize(variableIterator, Spliterator.ORDERED), false);
if (isAny) {
stream.forEach(pair -> {
currentValue = pair.getValue();
currentIndex = pair.getKey();
if (conditions.stream().anyMatch(c -> c.check(event))) {
toKeep.add(pair);
} else {
toRemove.add(pair.getKey());
}
});
} else {
stream.forEach(pair -> {
currentValue = pair.getValue();
currentIndex = pair.getKey();
if (conditions.stream().allMatch(c -> c.check(event))) {
toKeep.add(pair);
} else {
toRemove.add(pair.getKey());
}
});
}

// optimize by either removing or clearing + adding depending on which is fewer operations
// for instances where only a handful of values are removed from a large list, this can be a 400% speedup
if (toKeep.size() < initialSize / 2) {
Variables.setVariable(varName, null, event, local);
for (Pair<String, Object> pair : toKeep)
Variables.setVariable(varSubName + pair.getKey(), pair.getValue(), event, local);
} else {
for (String index : toRemove)
Variables.setVariable(varSubName + index, null, event, local);
}
return getNext();
}

@Override
public Set<ExprInput<?>> getDependentInputs() {
return dependentInputs;
}

@Override
public @Nullable Object getCurrentValue() {
return currentValue;
}

@Override
public @UnknownNullability String getCurrentIndex() {
return currentIndex;
}

@Override
public String toString(@Nullable Event event, boolean debug) {
return "filter " + unfilteredObjects.toString(event, debug) + " to match " + (isAny ? "any" : "all");
}
sovdeeth marked this conversation as resolved.
Show resolved Hide resolved

}
Loading