diff options
11 files changed, 148 insertions, 32 deletions
diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/entrylist/EntryListSearchManager.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/entrylist/EntryListSearchManager.java index 11672222d..c23bf2b64 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/entrylist/EntryListSearchManager.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/entrylist/EntryListSearchManager.java @@ -177,6 +177,10 @@ public class EntryListSearchManager { return list; } + public AsyncSearchManager getSearchManager() { + return searchManager; + } + public boolean matches(EntryStack<?> stack) { return searchManager.matches(stack); } diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/search/AsyncSearchManager.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/search/AsyncSearchManager.java index 5c7836576..c1ab15fba 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/client/search/AsyncSearchManager.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/search/AsyncSearchManager.java @@ -31,13 +31,16 @@ import me.shedaniel.rei.api.client.search.SearchProvider; import me.shedaniel.rei.api.common.entry.EntryStack; import me.shedaniel.rei.api.common.util.CollectionUtils; import me.shedaniel.rei.impl.client.util.ThreadCreator; +import me.shedaniel.rei.impl.common.InternalLogger; import me.shedaniel.rei.impl.common.util.HashedEntryStackWrapper; +import net.minecraft.Util; import java.util.AbstractMap; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicInteger; import java.util.function.BiConsumer; import java.util.function.Predicate; import java.util.function.Supplier; @@ -48,8 +51,8 @@ public class AsyncSearchManager { private final Supplier<List<HashedEntryStackWrapper>> stacksProvider; private final Supplier<Predicate<HashedEntryStackWrapper>> additionalPredicateSupplier; private final UnaryOperator<HashedEntryStackWrapper> transformer; - private volatile ExecutorTuple executor; private volatile Map.Entry<List<HashedEntryStackWrapper>, SearchFilter> last; + public volatile ExecutorTuple executor; public volatile SearchFilter filter; public AsyncSearchManager(Supplier<List<HashedEntryStackWrapper>> stacksProvider, Supplier<Predicate<HashedEntryStackWrapper>> additionalPredicateSupplier, UnaryOperator<HashedEntryStackWrapper> transformer) { @@ -62,8 +65,15 @@ public class AsyncSearchManager { this.last = null; } - private record ExecutorTuple(SearchFilter filter, - CompletableFuture<Map.Entry<List<HashedEntryStackWrapper>, SearchFilter>> future) { + public record ExecutorTuple(SearchFilter filter, + CompletableFuture<Map.Entry<List<HashedEntryStackWrapper>, SearchFilter>> future, + Steps steps) { + } + + public static class Steps { + public long startTime = 0; + public AtomicInteger partitionsDone = new AtomicInteger(0); + public int totalPartitions = 0; } public void updateFilter(String filter) { @@ -85,7 +95,8 @@ public class AsyncSearchManager { if (this.executor != null) { this.executor.future().cancel(Platform.isFabric()); } - this.executor = new ExecutorTuple(filter, get(EXECUTOR_SERVICE)); + Steps steps = new Steps(); + this.executor = new ExecutorTuple(filter, get(EXECUTOR_SERVICE, steps), steps); } SearchFilter savedFilter = filter; return (this.executor = new ExecutorTuple(this.executor.filter(), this.executor.future().thenApplyAsync(result -> { @@ -94,12 +105,12 @@ public class AsyncSearchManager { } return result; - }, EXECUTOR_SERVICE))).future(); + }, EXECUTOR_SERVICE), executor.steps)).future(); } public List<HashedEntryStackWrapper> getNow() { try { - return get(Runnable::run).get().getKey(); + return get(Runnable::run, new Steps()).get().getKey(); } catch (ExecutionException e) { throw new RuntimeException(e); } catch (InterruptedException | CancellationException e) { @@ -107,12 +118,12 @@ public class AsyncSearchManager { } } - public CompletableFuture<Map.Entry<List<HashedEntryStackWrapper>, SearchFilter>> get(Executor executor) { + public CompletableFuture<Map.Entry<List<HashedEntryStackWrapper>, SearchFilter>> get(Executor executor, Steps steps) { if (isDirty()) { Map.Entry<List<HashedEntryStackWrapper>, SearchFilter> last; last = this.last; return get(this.filter, this.additionalPredicateSupplier.get(), this.transformer, - this.stacksProvider.get(), last, this, executor) + this.stacksProvider.get(), last, this, executor, steps) .thenApply(entry -> { this.last = entry; return entry; @@ -124,25 +135,34 @@ public class AsyncSearchManager { public static CompletableFuture<Map.Entry<List<HashedEntryStackWrapper>, SearchFilter>> get(SearchFilter filter, Predicate<HashedEntryStackWrapper> additionalPredicate, UnaryOperator<HashedEntryStackWrapper> transformer, List<HashedEntryStackWrapper> stacks, Map.Entry<List<HashedEntryStackWrapper>, SearchFilter> last, - AsyncSearchManager manager, Executor executor) { + AsyncSearchManager manager, Executor executor, Steps steps) { int searchPartitionSize = ConfigObject.getInstance().getAsyncSearchPartitionSize(); boolean shouldAsync = ConfigObject.getInstance().shouldAsyncSearch() && stacks.size() > searchPartitionSize * 4; + InternalLogger.getInstance().debug("Starting Search: \"" + filter.getFilter() + "\" with " + stacks.size() + " stacks, shouldAsync: " + shouldAsync + " on " + Thread.currentThread().getName()); if (!stacks.isEmpty()) { if (shouldAsync) { List<CompletableFuture<List<HashedEntryStackWrapper>>> futures = Lists.newArrayList(); - for (Iterable<HashedEntryStackWrapper> partitionStacks : CollectionUtils.partition(stacks, Math.max(searchPartitionSize, stacks.size() * 3 / Runtime.getRuntime().availableProcessors()))) { + int partitions = 0; + for (Iterable<HashedEntryStackWrapper> partitionStacks : CollectionUtils.partition(stacks, searchPartitionSize * 4)) { + final int finalPartitions = partitions; futures.add(CompletableFuture.supplyAsync(() -> { List<HashedEntryStackWrapper> filtered = Lists.newArrayList(); + if (manager.filter != filter) throw new CancellationException(); for (HashedEntryStackWrapper stack : partitionStacks) { - if (stack != null && filter.test(stack.unwrap(), stack.hashExact()) && additionalPredicate.test(stack)) { + if (stack != null && test(filter, stack.unwrap(), stack.hashExact()) && additionalPredicate.test(stack)) { filtered.add(transformer.apply(stack)); } if (manager.filter != filter) throw new CancellationException(); } + steps.partitionsDone.incrementAndGet(); return filtered; }, executor)); + partitions++; } + steps.startTime = Util.getEpochMillis(); + steps.totalPartitions = partitions; + InternalLogger.getInstance().debug("Async Search: " + partitions + " partitions for \"" + filter.getFilter() + "\""); return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])) .orTimeout(90, TimeUnit.SECONDS) .thenApplyAsync($ -> { @@ -163,7 +183,7 @@ public class AsyncSearchManager { List<HashedEntryStackWrapper> list = new ArrayList<>(); for (HashedEntryStackWrapper stack : stacks) { - if (filter.test(stack.unwrap(), stack.hashExact()) && additionalPredicate.test(stack)) { + if (test(filter, stack.unwrap(), stack.hashExact()) && additionalPredicate.test(stack)) { list.add(transformer.apply(stack)); } if (manager.filter != filter) throw new CancellationException(); @@ -176,6 +196,15 @@ public class AsyncSearchManager { return CompletableFuture.completedFuture(new AbstractMap.SimpleImmutableEntry<>(Lists.newArrayList(), filter)); } + private static boolean test(SearchFilter filter, EntryStack<?> stack, long hashExact) { + try { + return filter.test(stack, hashExact); + } catch (Throwable throwable) { + InternalLogger.getInstance().debug("Error while testing filter", throwable); + return false; + } + } + public boolean matches(EntryStack<?> stack) { return filter.test(stack); } diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/search/SearchProviderImpl.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/search/SearchProviderImpl.java index c19d3f0c1..6b1205d63 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/client/search/SearchProviderImpl.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/search/SearchProviderImpl.java @@ -34,11 +34,8 @@ 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 me.shedaniel.rei.impl.common.util.HashedEntryStackWrapper; -import net.minecraft.CrashReport; -import net.minecraft.CrashReportCategory; import java.util.Collection; import java.util.List; @@ -72,7 +69,7 @@ public class SearchProviderImpl implements SearchProvider { .map(Argument::getArgument) .distinct() .collect(Collectors.toList())); - InternalLogger.getInstance().debug("Created search filter with %s using %s", filter, inputMethod.getName().getString()); + InternalLogger.getInstance().debug("Created search filter with \"%s\" using %s", filter, inputMethod.getName().getString()); } @Override @@ -80,14 +77,7 @@ public class SearchProviderImpl implements SearchProvider { try { return Argument.matches(stack, hashExact, 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); + throw new RuntimeException("Failed to test search filter: \"" + filter + "\" with stack [" + stack.getType().getIdentifier() + "@" + stack.getIdentifier() + "!" + stack.getValue() + "]", throwable); } } diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/search/argument/Argument.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/search/argument/Argument.java index 7b8dc7d18..bd5034aa3 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/client/search/argument/Argument.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/search/argument/Argument.java @@ -55,7 +55,7 @@ import java.util.regex.Pattern; @ApiStatus.Internal @Environment(EnvType.CLIENT) public class Argument<T, R> { - private static final Object NO_CACHE = new Object(); + public static final Object NO_CACHE = new Object(); private static final AtomicReference<String> LAST_LANGUAGE = new AtomicReference<>(); public static ArgumentCache cache = new ArgumentCache(); private final ArgumentType<T, R> argumentType; @@ -81,13 +81,13 @@ public class Argument<T, R> { Collection<HashedEntryStackWrapper> stacks = new AbstractCollection<>() { @Override public Iterator<HashedEntryStackWrapper> iterator() { - return Iterators.transform(((EntryRegistryImpl) EntryRegistry.getInstance()).getPreFilteredComplexList().iterator(), + return Iterators.transform(((EntryRegistryImpl) EntryRegistry.getInstance()).getComplexList().iterator(), HashedEntryStackWrapper::normalize); } @Override public int size() { - return EntryRegistry.getInstance().getPreFilteredList().size(); + return ((EntryRegistryImpl) EntryRegistry.getInstance()).getComplexList().size(); } }; if (cache) { diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/search/argument/ArgumentCache.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/search/argument/ArgumentCache.java index d0a443d3c..033331360 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/client/search/argument/ArgumentCache.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/search/argument/ArgumentCache.java @@ -134,10 +134,10 @@ public class ArgumentCache { Long2ObjectMap<Object> out = new Long2ObjectArrayMap<>(stacks.size() + 1); for (HashedEntryStackWrapper stack : stacks) { if (cacheMap.get(stack.hashExact()) == null) { - Object data = argumentType.cacheData(stack.unwrap()); - - if (data != null) { - out.put(stack.hashExact(), data); + try { + Object data = argumentType.cacheData(stack.unwrap()); + out.put(stack.hashExact(), data == null ? Argument.NO_CACHE : data); + } catch (Throwable ignored) { } } } diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/util/ThreadCreator.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/util/ThreadCreator.java index d49b7c2cd..d373cee44 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/client/util/ThreadCreator.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/util/ThreadCreator.java @@ -54,7 +54,7 @@ public final class ThreadCreator { public ExecutorService asService(int poolSize) { return new ForkJoinPool(poolSize, pool -> { ForkJoinWorkerThread worker = ForkJoinPool.defaultForkJoinWorkerThreadFactory.newThread(pool); - worker.setName(group().getName() + "-" + worker.getPoolIndex()); + worker.setName(group().getName() + "-" + threadId().getAndIncrement()); worker.setContextClassLoader(getClass().getClassLoader()); return worker; }, ($, exception) -> { diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/common/entry/AbstractEntryStack.java b/runtime/src/main/java/me/shedaniel/rei/impl/common/entry/AbstractEntryStack.java index 73f54172f..cf9c5b865 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/common/entry/AbstractEntryStack.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/common/entry/AbstractEntryStack.java @@ -231,6 +231,7 @@ public abstract class AbstractEntryStack<A> implements EntryStack<A>, Renderer { } return tooltip.getValue(); } catch (Throwable throwable) { + if (context.isSearch()) throw throwable; CrashReport report = CrashReportUtils.essential(throwable, "Getting tooltips"); CrashReportUtils.renderer(report, this); throw CrashReportUtils.throwReport(report); diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/common/entry/type/EntryRegistryImpl.java b/runtime/src/main/java/me/shedaniel/rei/impl/common/entry/type/EntryRegistryImpl.java index a9d476878..942e61ad8 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/common/entry/type/EntryRegistryImpl.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/common/entry/type/EntryRegistryImpl.java @@ -120,6 +120,10 @@ public class EntryRegistryImpl implements EntryRegistry { return Collections.unmodifiableList(filteredList.getComplexList()); } + public List<HashedEntryStackWrapper> getComplexList() { + return Collections.unmodifiableList(registryList.collectHashed()); + } + @Override public void refilter() { List<HashedEntryStackWrapper> stacks = registryList.collectHashed(); diff --git a/runtime/src/main/java/me/shedaniel/rei/plugin/client/runtime/DefaultClientRuntimePlugin.java b/runtime/src/main/java/me/shedaniel/rei/plugin/client/runtime/DefaultClientRuntimePlugin.java index 96bfa0751..339f0d7b0 100644 --- a/runtime/src/main/java/me/shedaniel/rei/plugin/client/runtime/DefaultClientRuntimePlugin.java +++ b/runtime/src/main/java/me/shedaniel/rei/plugin/client/runtime/DefaultClientRuntimePlugin.java @@ -93,6 +93,7 @@ public class DefaultClientRuntimePlugin implements REIClientPlugin { REIRuntimeImpl.getInstance().addHintProvider(watcher); REIRuntimeImpl.getInstance().addHintProvider(new SearchBarHighlightWatcher()); REIRuntimeImpl.getInstance().addHintProvider(new SearchFilterPrepareWatcher()); + REIRuntimeImpl.getInstance().addHintProvider(new SearchFilterWatcher()); REIRuntimeImpl.getInstance().addHintProvider(new InputMethodWatcher()); } diff --git a/runtime/src/main/java/me/shedaniel/rei/plugin/client/runtime/SearchFilterWatcher.java b/runtime/src/main/java/me/shedaniel/rei/plugin/client/runtime/SearchFilterWatcher.java new file mode 100644 index 000000000..37aaab934 --- /dev/null +++ b/runtime/src/main/java/me/shedaniel/rei/plugin/client/runtime/SearchFilterWatcher.java @@ -0,0 +1,85 @@ +/* + * This file is licensed under the MIT License, part of Roughly Enough Items. + * Copyright (c) 2018, 2019, 2020, 2021, 2022, 2023 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.plugin.client.runtime; + +import com.google.common.collect.ImmutableList; +import me.shedaniel.math.Color; +import me.shedaniel.math.Point; +import me.shedaniel.rei.api.client.gui.widgets.Tooltip; +import me.shedaniel.rei.impl.client.gui.hints.HintProvider; +import me.shedaniel.rei.impl.client.gui.widget.entrylist.EntryListSearchManager; +import me.shedaniel.rei.impl.client.search.AsyncSearchManager; +import net.minecraft.Util; +import net.minecraft.network.chat.Component; +import org.jetbrains.annotations.Nullable; + +import java.util.Collections; +import java.util.List; + +public class SearchFilterWatcher implements HintProvider { + private Double lastProcess; + + @Override + public List<Component> provide() { + lastProcess = null; + try { + AsyncSearchManager searchManager = EntryListSearchManager.INSTANCE.getSearchManager(); + if (searchManager.executor != null) { + if (searchManager.executor.future().isDone()) return Collections.emptyList(); + if (searchManager.executor.future().isCancelled()) return Collections.emptyList(); + if (searchManager.executor.future().isCompletedExceptionally()) return Collections.emptyList(); + AsyncSearchManager.Steps steps = searchManager.executor.steps(); + if (steps.startTime == 0 || steps.totalPartitions == 0) return Collections.emptyList(); + if (Util.getEpochMillis() - steps.startTime < 1000) return Collections.emptyList(); + lastProcess = steps.partitionsDone.get() / (double) steps.totalPartitions; + return ImmutableList.of(Component.translatable("text.rei.searching"), + Component.translatable("text.rei.searching.step", Math.round(lastProcess * 100))); + } + } catch (NullPointerException ignored) { + } + return Collections.emptyList(); + } + + @Override + @Nullable + public Tooltip provideTooltip(Point mouse) { + return null; + } + + @Override + @Nullable + public Double getProgress() { + return lastProcess; + } + + @Override + public Color getColor() { + return Color.ofTransparent(0x5003fc24); + } + + @Override + public List<HintButton> getButtons() { + return Collections.emptyList(); + } +} diff --git a/runtime/src/main/resources/assets/roughlyenoughitems/lang/en_us.json b/runtime/src/main/resources/assets/roughlyenoughitems/lang/en_us.json index 680d7019f..35d9f23d0 100755 --- a/runtime/src/main/resources/assets/roughlyenoughitems/lang/en_us.json +++ b/runtime/src/main/resources/assets/roughlyenoughitems/lang/en_us.json @@ -19,6 +19,8 @@ "text.rei.inventory.highlighting.enabled.tooltip": "This makes slots that does not match\nthe search filter gray.\nDouble click the search bar to toggle this mode.", "text.rei.caching.search": "REI caching search results...", "text.rei.caching.search.step": "Step %d/%d (%s%%):", + "text.rei.searching": "REI searching results...", + "text.rei.searching.step": "This is slower on the first search.\nProgress: %s%%", "text.rei.config.menu.dark_theme": "Dark Theme", "text.rei.config.menu.craftable_filter": "Craftable Filter", "text.rei.config.menu.display": "Display Settings...", |
