aboutsummaryrefslogtreecommitdiff
path: root/runtime-engine/search/src/main/java
diff options
context:
space:
mode:
Diffstat (limited to 'runtime-engine/search/src/main/java')
-rw-r--r--runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/IntRange.java27
-rw-r--r--runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/SearchProviderImpl.java129
-rw-r--r--runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/argument/AlternativeArgument.java74
-rw-r--r--runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/argument/Argument.java360
-rw-r--r--runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/argument/CompoundArgument.java88
-rw-r--r--runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/argument/IndexSet.java173
-rw-r--r--runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/argument/InputMethodMatcher.java129
-rw-r--r--runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/argument/type/AlwaysMatchingArgumentType.java60
-rw-r--r--runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/argument/type/ArgumentType.java101
-rw-r--r--runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/argument/type/ArgumentTypesRegistry.java52
-rw-r--r--runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/argument/type/IdentifierArgumentType.java88
-rw-r--r--runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/argument/type/MatchType.java41
-rw-r--r--runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/argument/type/ModArgumentType.java103
-rw-r--r--runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/argument/type/RegexArgumentType.java99
-rw-r--r--runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/argument/type/TagArgumentType.java92
-rw-r--r--runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/argument/type/TextArgumentType.java75
-rw-r--r--runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/argument/type/TooltipArgumentType.java123
-rw-r--r--runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/method/DefaultInputMethod.java76
-rw-r--r--runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/method/InputMethodRegistryImpl.java110
-rw-r--r--runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/result/ArgumentApplicableResult.java122
-rw-r--r--runtime-engine/search/src/main/java/me/shedaniel/rei/plugin/client/runtime/SearchFilterPrepareWatcher.java91
21 files changed, 2213 insertions, 0 deletions
diff --git a/runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/IntRange.java b/runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/IntRange.java
new file mode 100644
index 000000000..94c259b8c
--- /dev/null
+++ b/runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/IntRange.java
@@ -0,0 +1,27 @@
+/*
+ * This file is licensed under the MIT License, part of Roughly Enough Items.
+ * Copyright (c) 2018, 2019, 2020, 2021, 2022 shedaniel
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package me.shedaniel.rei.impl.client.search;
+
+public record IntRange(int min, int max) {
+}
diff --git a/runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/SearchProviderImpl.java b/runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/SearchProviderImpl.java
new file mode 100644
index 000000000..894153ddc
--- /dev/null
+++ b/runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/SearchProviderImpl.java
@@ -0,0 +1,129 @@
+/*
+ * This file is licensed under the MIT License, part of Roughly Enough Items.
+ * Copyright (c) 2018, 2019, 2020, 2021, 2022 shedaniel
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package me.shedaniel.rei.impl.client.search;
+
+import com.google.common.base.Suppliers;
+import me.shedaniel.rei.api.client.search.SearchFilter;
+import me.shedaniel.rei.api.client.search.SearchProvider;
+import me.shedaniel.rei.api.client.search.method.InputMethod;
+import me.shedaniel.rei.api.common.entry.EntryStack;
+import me.shedaniel.rei.impl.client.search.argument.AlternativeArgument;
+import me.shedaniel.rei.impl.client.search.argument.Argument;
+import me.shedaniel.rei.impl.client.search.argument.CompoundArgument;
+import me.shedaniel.rei.impl.client.search.argument.type.ArgumentType;
+import me.shedaniel.rei.impl.client.util.CrashReportUtils;
+import me.shedaniel.rei.impl.common.InternalLogger;
+import net.minecraft.CrashReport;
+import net.minecraft.CrashReportCategory;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Objects;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+
+public class SearchProviderImpl implements SearchProvider {
+ @Override
+ public void startReload() {
+ }
+
+ @Override
+ public SearchFilter createFilter(String filter, InputMethod<?> inputMethod) {
+ return new SearchFilterImpl(filter, inputMethod);
+ }
+
+ @Override
+ public void clearCache() {
+ Argument.SEARCH_CACHE.clear();
+ }
+
+ @Override
+ public boolean hasCache() {
+ return !Argument.SEARCH_CACHE.isEmpty();
+ }
+
+ public static class SearchFilterImpl implements SearchFilter {
+ private final String filter;
+ private final InputMethod<?> inputMethod;
+ private final Supplier<List<CompoundArgument>> arguments;
+ private final Supplier<List<ArgumentType<?, ?>>> argumentTypes;
+
+ public SearchFilterImpl(String filter, InputMethod<?> inputMethod) {
+ this.filter = filter;
+ this.inputMethod = inputMethod;
+ this.arguments = Suppliers.memoize(() -> Argument.bakeArguments(filter));
+ this.argumentTypes = Suppliers.memoize(() -> this.arguments.get().stream()
+ .flatMap(CompoundArgument::stream)
+ .flatMap(AlternativeArgument::stream)
+ .map(Argument::getArgument)
+ .distinct()
+ .collect(Collectors.toList()));
+ InternalLogger.getInstance().debug("Created search filter with %s using %s", filter, inputMethod.getName().getString());
+ }
+
+ @Override
+ public boolean test(EntryStack<?> stack) {
+ try {
+ return Argument.matches(stack, arguments.get(), inputMethod);
+ } catch (Throwable throwable) {
+ CrashReport report = CrashReportUtils.essential(throwable, "Testing entry with search filter");
+ CrashReportCategory category = report.addCategory("Search entry details");
+ try {
+ stack.fillCrashReport(report, category);
+ } catch (Throwable throwable1) {
+ category.setDetailError("Filling Report", throwable1);
+ }
+ throw CrashReportUtils.throwReport(report);
+ }
+ }
+
+ @Override
+ public void prepareFilter(Collection<EntryStack<?>> stacks) {
+ Argument.prepareFilter(stacks, argumentTypes.get());
+ }
+
+ @Override
+ public void processDecoration(ParseDecorationSink sink) {
+ Argument.bakeArguments(filter, sink);
+ }
+
+ @Override
+ public String getFilter() {
+ return filter;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ SearchFilterImpl that = (SearchFilterImpl) o;
+ return Objects.equals(filter, that.filter);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(filter);
+ }
+ }
+}
diff --git a/runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/argument/AlternativeArgument.java b/runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/argument/AlternativeArgument.java
new file mode 100644
index 000000000..01b169f6b
--- /dev/null
+++ b/runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/argument/AlternativeArgument.java
@@ -0,0 +1,74 @@
+/*
+ * This file is licensed under the MIT License, part of Roughly Enough Items.
+ * Copyright (c) 2018, 2019, 2020, 2021, 2022 shedaniel
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package me.shedaniel.rei.impl.client.search.argument;
+
+import com.google.common.collect.ForwardingList;
+import org.jetbrains.annotations.ApiStatus;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+@ApiStatus.Internal
+public class AlternativeArgument extends ForwardingList<Argument<?, ?>> {
+ static final AlternativeArgument EMPTY = new AlternativeArgument(Collections.emptyList());
+
+ private final List<Argument<?, ?>> arguments;
+
+ public AlternativeArgument(List<Argument<?, ?>> arguments) {
+ this.arguments = arguments;
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ @Override
+ protected List<Argument<?, ?>> delegate() {
+ return arguments;
+ }
+
+ public static class Builder {
+ private List<Argument<?, ?>> arguments;
+
+ public <T, R> Builder add(Argument<T, R> argument) {
+ if (arguments == null) {
+ this.arguments = new ArrayList<>();
+ }
+
+ arguments.add(argument);
+ return this;
+ }
+
+ public boolean isEmpty() {
+ return arguments == null;
+ }
+
+ public AlternativeArgument build() {
+ if (arguments == null) return AlternativeArgument.EMPTY;
+ if (arguments.size() == 1) return new AlternativeArgument(Collections.singletonList(arguments.get(0)));
+ return new AlternativeArgument(arguments);
+ }
+ }
+}
diff --git a/runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/argument/Argument.java b/runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/argument/Argument.java
new file mode 100644
index 000000000..8724af7d1
--- /dev/null
+++ b/runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/argument/Argument.java
@@ -0,0 +1,360 @@
+/*
+ * This file is licensed under the MIT License, part of Roughly Enough Items.
+ * Copyright (c) 2018, 2019, 2020, 2021, 2022 shedaniel
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package me.shedaniel.rei.impl.client.search.argument;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.Lists;
+import it.unimi.dsi.fastutil.Pair;
+import it.unimi.dsi.fastutil.ints.IntIntMutablePair;
+import it.unimi.dsi.fastutil.ints.IntIntPair;
+import it.unimi.dsi.fastutil.ints.IntList;
+import it.unimi.dsi.fastutil.longs.Long2ObjectArrayMap;
+import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
+import it.unimi.dsi.fastutil.longs.Long2ObjectMaps;
+import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
+import it.unimi.dsi.fastutil.shorts.Short2ObjectMap;
+import it.unimi.dsi.fastutil.shorts.Short2ObjectMaps;
+import it.unimi.dsi.fastutil.shorts.Short2ObjectOpenHashMap;
+import me.shedaniel.rei.api.client.config.ConfigObject;
+import me.shedaniel.rei.api.client.gui.config.SearchMode;
+import me.shedaniel.rei.api.client.search.SearchFilter;
+import me.shedaniel.rei.api.client.search.method.CharacterUnpackingInputMethod;
+import me.shedaniel.rei.api.client.search.method.InputMethod;
+import me.shedaniel.rei.api.common.entry.EntryStack;
+import me.shedaniel.rei.api.common.util.CollectionUtils;
+import me.shedaniel.rei.api.common.util.EntryStacks;
+import me.shedaniel.rei.impl.client.search.argument.type.ArgumentType;
+import me.shedaniel.rei.impl.client.search.argument.type.ArgumentTypesRegistry;
+import me.shedaniel.rei.impl.client.search.result.ArgumentApplicableResult;
+import me.shedaniel.rei.impl.common.util.HashedEntryStackWrapper;
+import net.fabricmc.api.EnvType;
+import net.fabricmc.api.Environment;
+import net.minecraft.Util;
+import net.minecraft.client.Minecraft;
+import org.apache.commons.lang3.StringUtils;
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Locale;
+import java.util.Objects;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+@ApiStatus.Internal
+@Environment(EnvType.CLIENT)
+public class Argument<T, R> {
+ public static final Short2ObjectMap<Long2ObjectMap<Object>> SEARCH_CACHE = Short2ObjectMaps.synchronize(new Short2ObjectOpenHashMap<>());
+ private static final Object NO_CACHE = new Object();
+ private static final AtomicReference<String> lastLanguage = new AtomicReference<>();
+ private ArgumentType<T, R> argumentType;
+ private String text;
+ private T filterData;
+ private boolean regular;
+ private final int start;
+ private final int end;
+ private static final Pattern SPLIT_PATTERN = Pattern.compile("(?:\"([^\"]*)\")|([^\\s]+)");
+
+ public Argument(ArgumentType<T, R> argumentType, String text, boolean regular, int start, int end, boolean lowercase) {
+ this.argumentType = argumentType;
+ this.text = lowercase ? text.toLowerCase(Locale.ROOT) : text;
+ this.regular = regular;
+ this.filterData = null;
+ this.start = start;
+ this.end = end;
+ }
+
+ public int start() {
+ return start;
+ }
+
+ public int end() {
+ return end;
+ }
+
+ public static List<CompoundArgument> bakeArguments(String filter) {
+ return bakeArguments(filter, null);
+ }
+
+ public static List<CompoundArgument> bakeArguments(String filter, @Nullable SearchFilter.ParseDecorationSink sink) {
+ List<CompoundArgument> compoundArguments = Lists.newArrayList();
+ int tokenStartIndex = 0;
+
+ for (String token : StringUtils.splitByWholeSeparatorPreserveAllTokens(filter, "|")) {
+ Matcher terms = SPLIT_PATTERN.matcher(token);
+ CompoundArgument.Builder builder = CompoundArgument.builder();
+ while (terms.find()) {
+ AlternativeArgument.Builder alternativeBuilder = AlternativeArgument.builder();
+
+ for (ArgumentType<?, ?> type : ArgumentTypesRegistry.ARGUMENT_TYPE_LIST) {
+ applyArgument(type, filter, terms, tokenStartIndex, alternativeBuilder, true, sink);
+ if (!alternativeBuilder.isEmpty()) {
+ break;
+ }
+ }
+
+ if (alternativeBuilder.isEmpty()) {
+ for (ArgumentType<?, ?> type : ArgumentTypesRegistry.ARGUMENT_TYPE_LIST) {
+ applyArgument(type, filter, terms, tokenStartIndex, alternativeBuilder, false, sink);
+ }
+ }
+
+ builder.add(alternativeBuilder);
+ }
+ compoundArguments.add(builder.build());
+ tokenStartIndex += 1 + token.length();
+ if (sink != null && tokenStartIndex - 1 < filter.length()) {
+ sink.addSplitter(tokenStartIndex - 1);
+ }
+ }
+ prepareSearchFilter(compoundArguments);
+ return compoundArguments;
+ }
+
+ private static void prepareSearchFilter(List<CompoundArgument> compoundArguments) {
+ for (CompoundArgument arguments : compoundArguments) {
+ for (AlternativeArgument alternativeArgument : arguments) {
+ for (Argument<?, ?> argument : alternativeArgument) {
+ //noinspection RedundantCast
+ ((Argument<Object, Object>) argument).filterData = argument.argumentType.prepareSearchFilter(argument.getText());
+ }
+ }
+ }
+ }
+
+ private static void applyArgument(ArgumentType<?, ?> type, String filter, Matcher terms, int tokenStartIndex, AlternativeArgument.Builder alternativeBuilder, boolean forceGrammar, @Nullable SearchFilter.ParseDecorationSink sink) {
+ String term = MoreObjects.firstNonNull(terms.group(1), terms.group(2));
+ if (type.getSearchMode() == SearchMode.NEVER) return;
+ ArgumentApplicableResult result = type.checkApplicable(term, forceGrammar);
+
+ if (result.isApplicable()) {
+ int group = terms.group(1) != null ? 1 : 2;
+ Argument<?, ?> argument = new Argument<>(type, result.getText(), !result.isInverted(),
+ terms.start(group) + tokenStartIndex, terms.end(group) + tokenStartIndex, !result.shouldPreserveCasing());
+ alternativeBuilder.add(argument);
+ if (sink != null) {
+ if (group == 1) {
+ sink.addQuote(terms.start() + tokenStartIndex);
+ if (terms.end() - 1 + tokenStartIndex < filter.length()) {
+ sink.addQuote(terms.end() - 1 + tokenStartIndex);
+ }
+ }
+ sink.addPart(IntIntPair.of(argument.start(), argument.end()), argument.argumentType.getHighlightedStyle(), result.isUsingGrammar(),
+ CollectionUtils.map(result.grammarRanges(), range -> IntIntPair.of(range.min(), range.max())), terms.start() + tokenStartIndex);
+ }
+ }
+ }
+
+ public static boolean matches(EntryStack<?> stack, List<CompoundArgument> compoundArguments, InputMethod<?> inputMethod) {
+ if (compoundArguments.isEmpty()) return true;
+ String newLanguage = Minecraft.getInstance().options.languageCode;
+ if (!Objects.equals(lastLanguage.getAndSet(newLanguage), newLanguage)) {
+ SEARCH_CACHE.clear();
+ }
+
+ a:
+ for (CompoundArgument arguments : compoundArguments) {
+ for (AlternativeArgument argument : arguments) {
+ if (!matches(stack, argument, inputMethod)) {
+ continue a;
+ }
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+
+ private static <T> boolean matches(EntryStack<?> stack, AlternativeArgument alternativeArgument, InputMethod<T> inputMethod) {
+ if (alternativeArgument.isEmpty()) return true;
+ long hashExact = EntryStacks.hashExact(stack);
+ ResultSinkImpl<T> sink = new ResultSinkImpl<>(inputMethod);
+
+ for (Argument<?, ?> argument : alternativeArgument) {
+ sink.filters = inputMethod.expendFilter(argument.getText());
+ if (matches(argument.getArgument(), stack, hashExact, argument.filterData, sink) == argument.isRegular()) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private static Long2ObjectMap<Object> getSearchCache(ArgumentType<?, ?> argumentType) {
+ short argumentIndex = (short) argumentType.getIndex();
+ Long2ObjectMap<Object> map = SEARCH_CACHE.get(argumentIndex);
+ if (map == null) {
+ SEARCH_CACHE.put(argumentIndex, map = Long2ObjectMaps.synchronize(new Long2ObjectOpenHashMap<>()));
+ }
+ return map;
+ }
+
+ private static <T, R, B> boolean matches(ArgumentType<T, B> argumentType, EntryStack<?> stack, long hashExact, R filterData, ResultSinkImpl<?> sink) {
+ Long2ObjectMap<Object> map = getSearchCache(argumentType);
+ Object value = map.get(hashExact);
+ if (value == null) {
+ value = argumentType.cacheData(stack);
+ map.put(hashExact, value == null ? NO_CACHE : value);
+ }
+ sink.matches = false;
+ argumentType.matches(value == NO_CACHE ? null : (B) value, stack, (T) filterData, sink);
+ return sink.matches;
+ }
+
+ private static class ResultSinkImpl<T> implements ArgumentType.ResultSink {
+ private final InputMethod<T> inputMethod;
+ private boolean matches;
+ private Iterable<T> filters;
+
+ public ResultSinkImpl(InputMethod<T> inputMethod) {
+ this.inputMethod = inputMethod;
+ }
+
+ @Override
+ public boolean testTrue() {
+ return matches = true;
+ }
+
+ @Override
+ public boolean testString(String text) {
+ if (matches) return true;
+ if (inputMethod instanceof CharacterUnpackingInputMethod im) {
+ for (T filter : filters) {
+ if (InputMethodMatcher.contains(im, IntList.of(text.codePoints().toArray()), (IntList) filter)) {
+ return matches = true;
+ }
+ }
+ } else {
+ for (T filter : filters) {
+ if (inputMethod.contains(text, filter)) {
+ return matches = true;
+ }
+ }
+ }
+ return false;
+ }
+ }
+
+ public static Long prepareStart = null;
+ public static Collection<EntryStack<?>> prepareStacks = null;
+ public static IntIntPair prepareStage = null;
+ public static IntIntPair[] currentStages = null;
+
+ public static void prepareFilter(Collection<EntryStack<?>> stacks, Collection<ArgumentType<?, ?>> argumentTypes) {
+ if (prepareStage != null || currentStages != null) return;
+ try {
+ prepareStart = Util.getEpochMillis();
+ prepareStacks = stacks;
+ prepareStage = new IntIntMutablePair(0, argumentTypes.size());
+ currentStages = new IntIntPair[argumentTypes.size()];
+ List<HashedEntryStackWrapper> hashedStacks = CollectionUtils.map(stacks, HashedEntryStackWrapper::new);
+ int searchPartitionSize = ConfigObject.getInstance().getAsyncSearchPartitionSize();
+ boolean async = ConfigObject.getInstance().shouldAsyncSearch() && stacks.size() > searchPartitionSize * 4;
+ List<CompletableFuture<Long2ObjectMap<Object>>> futures = Lists.newArrayList();
+ List<Pair<ArgumentType<?, ?>, CompletableFuture<Long2ObjectMap<Object>>>> pairs = Lists.newArrayList();
+
+ for (ArgumentType<?, ?> argumentType : argumentTypes) {
+ prepareStage.first(prepareStage.firstInt() + 1);
+ Long2ObjectMap<Object> map = getSearchCache(argumentType);
+ IntIntPair currentStage = currentStages[prepareStage.firstInt() - 1] = new IntIntMutablePair(0, hashedStacks.size());
+
+ if (async) {
+ for (Collection<HashedEntryStackWrapper> partitionStacks : CollectionUtils.partition(hashedStacks, searchPartitionSize)) {
+ CompletableFuture<Long2ObjectMap<Object>> future = CompletableFuture.supplyAsync(() -> {
+ Long2ObjectMap<Object> out = new Long2ObjectArrayMap<>(searchPartitionSize + 1);
+ for (HashedEntryStackWrapper stack : partitionStacks) {
+ if (map.get(stack.hashExact()) == null) {
+ Object data = argumentType.cacheData(stack.unwrap());
+
+ if (data != null) {
+ out.put(stack.hashExact(), data);
+ }
+ }
+ }
+ return out;
+ }).whenComplete((objectLong2ObjectMap, throwable) -> {
+ currentStage.first(currentStage.firstInt() + partitionStacks.size());
+ });
+ futures.add(future);
+ pairs.add(Pair.of(argumentType, future));
+ }
+ } else {
+ for (HashedEntryStackWrapper stack : hashedStacks) {
+ currentStage.first(currentStage.firstInt() + 1);
+
+ if (map.get(stack.hashExact()) == null) {
+ Object data = argumentType.cacheData(stack.unwrap());
+
+ if (data != null) {
+ map.put(stack.hashExact(), data);
+ }
+ }
+ }
+ }
+ }
+
+ if (async) {
+ try {
+ CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).get(30, TimeUnit.SECONDS);
+ } catch (InterruptedException | ExecutionException | TimeoutException e) {
+ e.printStackTrace();
+ }
+ for (Pair<ArgumentType<?, ?>, CompletableFuture<Long2ObjectMap<Object>>> pair : pairs) {
+ Long2ObjectMap<Object> now = pair.second().getNow(null);
+ if (now != null) getSearchCache(pair.left()).putAll(now);
+ }
+ }
+ } finally {
+ prepareStart = null;
+ prepareStacks = null;
+ prepareStage = null;
+ currentStages = null;
+ }
+ }
+
+ public ArgumentType<?, ?> getArgument() {
+ return argumentType;
+ }
+
+ public String getText() {
+ return text;
+ }
+
+ public boolean isRegular() {
+ return regular;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("Argument[%s]: name = %s, regular = %b", argumentType.getName(), text, regular);
+ }
+
+}
diff --git a/runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/argument/CompoundArgument.java b/runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/argument/CompoundArgument.java
new file mode 100644
index 000000000..bcb3c0bf3
--- /dev/null
+++ b/runtime-engine/search/src/main/java/me/shedaniel/rei/impl/client/search/argument/CompoundArgument.java
@@ -0,0 +1,88 @@
+/*
+ * This file is licensed under the MIT License, part of Roughly Enough Items.
+ * Copyright (c) 2018, 2019, 2020, 2021, 2022 shedaniel
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package me.shedaniel.rei.impl.client.search.argument;
+
+import com.google.common.collect.ForwardingList;
+import org.jetbrains.annotations.ApiStatus;
+
+import java.util.ArrayList;
+import java.util.Arrays;