From 4b8675eda54b16c5b5f4a03eed9eb31ea5cadd4f Mon Sep 17 00:00:00 2001 From: shedaniel Date: Mon, 29 May 2023 12:38:01 +0800 Subject: Improve search performance --- .../rei/api/client/search/SearchFilter.java | 6 + forge/build.gradle | 4 +- .../widget/entrylist/EntryListSearchManager.java | 63 +++-- .../OverlaySearchFieldSyntaxHighlighter.java | 4 +- .../rei/impl/client/search/AsyncSearchManager.java | 108 +++------ .../rei/impl/client/search/SearchProviderImpl.java | 14 +- .../search/argument/AlternativeArgument.java | 9 +- .../rei/impl/client/search/argument/Argument.java | 256 +++++++-------------- .../impl/client/search/argument/ArgumentCache.java | 172 ++++++++++++++ .../client/search/argument/CompoundArgument.java | 16 +- .../search/collapsed/CollapsedEntriesCache.java | 85 +++++++ .../rei/impl/client/util/ThreadCreator.java | 29 +-- .../impl/common/entry/type/EntryRegistryImpl.java | 4 + .../impl/common/entry/type/FilteredEntryList.java | 3 + .../common/entry/type/PreFilteredEntryList.java | 55 +++-- .../collapsed/CollapsibleEntryRegistryImpl.java | 63 ++--- .../impl/common/util/HashedEntryStackWrapper.java | 12 +- .../client/runtime/SearchFilterPrepareWatcher.java | 19 +- 18 files changed, 569 insertions(+), 353 deletions(-) create mode 100644 runtime/src/main/java/me/shedaniel/rei/impl/client/search/argument/ArgumentCache.java create mode 100644 runtime/src/main/java/me/shedaniel/rei/impl/client/search/collapsed/CollapsedEntriesCache.java diff --git a/api/src/main/java/me/shedaniel/rei/api/client/search/SearchFilter.java b/api/src/main/java/me/shedaniel/rei/api/client/search/SearchFilter.java index f94266447..7fb37e351 100644 --- a/api/src/main/java/me/shedaniel/rei/api/client/search/SearchFilter.java +++ b/api/src/main/java/me/shedaniel/rei/api/client/search/SearchFilter.java @@ -26,6 +26,7 @@ package me.shedaniel.rei.api.client.search; import me.shedaniel.rei.api.common.entry.EntryStack; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; +import org.jetbrains.annotations.ApiStatus; import java.util.Collection; import java.util.function.Predicate; @@ -80,4 +81,9 @@ public interface SearchFilter extends Predicate> { */ default void prepareFilter(Collection> stacks) { } + + @ApiStatus.Experimental + default boolean test(EntryStack stack, long hashExact) { + return this.test(stack); + } } diff --git a/forge/build.gradle b/forge/build.gradle index 2aa93e09e..88fbffa0d 100644 --- a/forge/build.gradle +++ b/forge/build.gradle @@ -76,9 +76,9 @@ dependencies { // modRuntime("curse.maven:immersive-engineering-231951:3721708") // modRuntime("curse.maven:autoreglib-250363:3326041") // modRuntime("curse.maven:ars-nouveau-401955:3814106") - // modRuntime("curse.maven:patchouli-306770:3809917") + // modRuntime("curse.maven:patchouli-306770:3843443") // modRuntime("curse.maven:curios-309927:3748873") - // modRuntime("software.bernie.geckolib:geckolib-1.18-forge:3.0.22") + // modRuntime("software.bernie.geckolib:geckolib-forge-1.18:3.0.57") // modRuntime("curse.maven:little-logistics-570050:3818773") // modRuntime("curse.maven:refined-storage-243076:3623324") // modRuntime("appeng:appliedenergistics2:10.0.1") 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 b18ddf9d2..3252a2dd8 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 @@ -24,6 +24,7 @@ package me.shedaniel.rei.impl.client.gui.widget.entrylist; import com.google.common.base.Stopwatch; +import com.google.common.collect.Iterators; import it.unimi.dsi.fastutil.longs.LongOpenHashSet; import it.unimi.dsi.fastutil.longs.LongSet; import me.shedaniel.rei.api.client.config.ConfigManager; @@ -36,10 +37,14 @@ import me.shedaniel.rei.api.common.entry.EntryStack; import me.shedaniel.rei.api.common.entry.type.VanillaEntryTypes; import me.shedaniel.rei.api.common.util.EntryStacks; import me.shedaniel.rei.impl.client.search.AsyncSearchManager; +import me.shedaniel.rei.impl.client.search.collapsed.CollapsedEntriesCache; import me.shedaniel.rei.impl.common.InternalLogger; +import me.shedaniel.rei.impl.common.entry.type.EntryRegistryImpl; import me.shedaniel.rei.impl.common.entry.type.collapsed.CollapsedStack; import me.shedaniel.rei.impl.common.entry.type.collapsed.CollapsibleEntryRegistryImpl; +import me.shedaniel.rei.impl.common.util.HashedEntryStackWrapper; import net.minecraft.client.Minecraft; +import net.minecraft.resources.ResourceLocation; import net.minecraft.world.item.CreativeModeTab; import net.minecraft.world.item.ItemStack; import org.apache.logging.log4j.Level; @@ -50,10 +55,10 @@ import java.util.function.BooleanSupplier; import java.util.function.Consumer; public class EntryListSearchManager { - private static final Comparator> ENTRY_NAME_COMPARER = Comparator.comparing(stack -> stack.asFormatStrippedText().getString()); - private static final Comparator> ENTRY_GROUP_COMPARER = Comparator.comparingInt(stack -> { - if (stack.getType() == VanillaEntryTypes.ITEM) { - CreativeModeTab group = ((ItemStack) stack.getValue()).getItem().getItemCategory(); + private static final Comparator ENTRY_NAME_COMPARER = Comparator.comparing(stack -> stack.unwrap().asFormatStrippedText().getString()); + private static final Comparator ENTRY_GROUP_COMPARER = Comparator.comparingInt(stack -> { + if (stack.unwrap().getType() == VanillaEntryTypes.ITEM) { + CreativeModeTab group = ((ItemStack) stack.unwrap().getValue()).getItem().getItemCategory(); if (group != null) return group.getId(); } @@ -62,7 +67,7 @@ public class EntryListSearchManager { public static final EntryListSearchManager INSTANCE = new EntryListSearchManager(); - private AsyncSearchManager searchManager = new AsyncSearchManager(EntryRegistry.getInstance()::getPreFilteredList, () -> { + private final AsyncSearchManager searchManager = new AsyncSearchManager(((EntryRegistryImpl) EntryRegistry.getInstance())::getPreFilteredComplexList, () -> { boolean checkCraftable = ConfigManager.getInstance().isCraftableOnlyEnabled(); LongSet workingItems = checkCraftable ? new LongOpenHashSet() : null; if (checkCraftable) { @@ -70,8 +75,8 @@ public class EntryListSearchManager { workingItems.add(EntryStacks.hashExact(stack)); } } - return checkCraftable ? stack -> workingItems.contains(EntryStacks.hashExact(stack)) : stack -> true; - }, EntryStack::normalize); + return checkCraftable ? stack -> workingItems.contains(stack.hashExact()) : stack -> true; + }, HashedEntryStackWrapper::normalize); public void update(String searchTerm, boolean ignoreLastSearch, Consumer | CollapsedStack*/ Object>> update) { Stopwatch stopwatch = Stopwatch.createStarted(); @@ -80,21 +85,21 @@ public class EntryListSearchManager { if (searchManager.isDirty()) { searchManager.getAsync((list, filter) -> { if (!filter.getFilter().equals(searchTerm)) return; - if (searchManager.filter() == null || searchManager.filter() != filter) return; + if (searchManager.filter == null || searchManager.filter != filter) return; InternalLogger.getInstance().log(ConfigObject.getInstance().doDebugSearchTimeRequired() ? Level.INFO : Level.TRACE, "Search \"%s\" Used [%s]: %s", filter.getFilter(), Thread.currentThread().toString(), stopwatch.toString()); - List | CollapsedStack*/ Object> finalList = collapse(copyAndOrder(list), () -> searchManager.filter() != null && searchManager.filter() == filter); + List | CollapsedStack*/ Object> finalList = collapse(copyAndOrder(list), () -> searchManager.filter != null && searchManager.filter == filter); InternalLogger.getInstance().log(ConfigObject.getInstance().doDebugSearchTimeRequired() ? Level.INFO : Level.TRACE, "Search \"%s\" Used and Applied [%s]: %s", filter.getFilter(), Thread.currentThread().toString(), stopwatch.stop().toString()); Minecraft.getInstance().submit(() -> { - if (searchManager.filter() == null || searchManager.filter() != filter) return; + if (searchManager.filter == null || searchManager.filter != filter) return; update.accept(finalList); }); }); } } - private List> copyAndOrder(List> list) { + private List copyAndOrder(List list) { list = new ArrayList<>(list); EntryPanelOrdering ordering = ConfigObject.getInstance().getItemListOrdering(); if (ordering == EntryPanelOrdering.NAME) @@ -108,7 +113,7 @@ public class EntryListSearchManager { return list; } - private List | CollapsedStack*/ Object> collapse(List> stacks, BooleanSupplier isValid) { + private List | CollapsedStack*/ Object> collapse(List stacks, BooleanSupplier isValid) { CollapsibleEntryRegistryImpl collapsibleRegistry = (CollapsibleEntryRegistryImpl) CollapsibleEntryRegistry.getInstance(); Map entries = new HashMap<>(); @@ -116,20 +121,46 @@ public class EntryListSearchManager { entries.put(entry, null); } + if (entries.isEmpty()) return (List) (List) new AbstractList>() { + + @Override + public int size() { + return stacks.size(); + } + + @Override + public EntryStack get(int i) { + return stacks.get(i).unwrap(); + } + + @Override + public Iterator> iterator() { + return Iterators.transform(stacks.iterator(), HashedEntryStackWrapper::unwrap); + } + }; if (!isValid.getAsBoolean()) return List.of(); - List | CollapsedStack*/ Object> list = new ArrayList<>(); + List | CollapsedStack*/ Object> list = new ArrayList<>(stacks.size() + 10); int i = 0; - for (EntryStack stack : stacks) { - long hashExact = EntryStacks.hashExact(stack); + for (HashedEntryStackWrapper wrapper : stacks) { + long hashExact = wrapper.hashExact(); + EntryStack stack = wrapper.unwrap(); boolean matchedAny = false; + Set locations = CollapsedEntriesCache.getInstance().getEntries(hashExact); for (Map.Entry mapEntry : entries.entrySet()) { CollapsibleEntryRegistryImpl.Entry entry = mapEntry.getKey(); + boolean matches; + + if (!entry.canCache()) { + matches = entry.getMatcher().matches(stack, hashExact); + } else { + matches = locations != null && locations.contains(entry.getId()); + } - if (entry.getMatcher().matches(stack, hashExact)) { + if (matches) { CollapsedStack collapsed = mapEntry.getValue(); if (collapsed == null) { diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/search/OverlaySearchFieldSyntaxHighlighter.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/search/OverlaySearchFieldSyntaxHighlighter.java index 9391660c6..e039ad7e9 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/search/OverlaySearchFieldSyntaxHighlighter.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/search/OverlaySearchFieldSyntaxHighlighter.java @@ -54,9 +54,9 @@ public class OverlaySearchFieldSyntaxHighlighter implements Consumer { } @Override - public void addPart(Argument argument, boolean usingGrammar, Collection grammarRanges, int index) { + public void addPart(Argument.Builder argument, boolean usingGrammar, Collection grammarRanges, int index) { if (usingGrammar) { - int argIndex = ArgumentTypesRegistry.ARGUMENT_TYPE_LIST.indexOf(argument.getArgument()) * 2 + 1; + int argIndex = ArgumentTypesRegistry.ARGUMENT_TYPE_LIST.indexOf(argument.getType()) * 2 + 1; for (int i = argument.start(); i < argument.end(); i++) { highlighted[i] = (byte) argIndex; } 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 9d418453c..30028d56d 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 @@ -30,10 +30,8 @@ import me.shedaniel.rei.api.client.search.SearchFilter; 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.search.argument.Argument; -import me.shedaniel.rei.impl.client.search.argument.type.ArgumentType; import me.shedaniel.rei.impl.client.util.ThreadCreator; -import org.jetbrains.annotations.Nullable; +import me.shedaniel.rei.impl.common.util.HashedEntryStackWrapper; import java.util.AbstractMap; import java.util.ArrayList; @@ -46,32 +44,26 @@ import java.util.function.Supplier; import java.util.function.UnaryOperator; public class AsyncSearchManager { - private static final ExecutorService EXECUTOR_SERVICE = new ThreadCreator("REI-AsyncSearchManager").asService(); - private final Supplier>> stacksProvider; - private final Supplier>> additionalPredicateSupplier; - private final UnaryOperator> transformer; - private ExecutorTuple executor; - private SearchFilter filter; - private Map.Entry>, SearchFilter> last; + private static final ExecutorService EXECUTOR_SERVICE = new ThreadCreator("REI-AsyncSearchManager").asService(Math.min(3, Runtime.getRuntime().availableProcessors())); + private final Supplier> stacksProvider; + private final Supplier> additionalPredicateSupplier; + private final UnaryOperator transformer; + private volatile ExecutorTuple executor; + private volatile Map.Entry, SearchFilter> last; + public volatile SearchFilter filter; - public AsyncSearchManager(Supplier>> stacksProvider, Supplier>> additionalPredicateSupplier, UnaryOperator> transformer) { + public AsyncSearchManager(Supplier> stacksProvider, Supplier> additionalPredicateSupplier, UnaryOperator transformer) { this.stacksProvider = stacksProvider; this.additionalPredicateSupplier = additionalPredicateSupplier; this.transformer = transformer; } public void markDirty() { - synchronized (AsyncSearchManager.this) { - this.last = null; - } - } - - @Nullable - public SearchFilter filter() { - return this.filter; + this.last = null; } - private record ExecutorTuple(SearchFilter filter, CompletableFuture>, SearchFilter>> future) { + private record ExecutorTuple(SearchFilter filter, + CompletableFuture, SearchFilter>> future) { } public void updateFilter(String filter) { @@ -85,12 +77,10 @@ public class AsyncSearchManager { } public boolean isDirty() { - synchronized (AsyncSearchManager.this) { - return this.last == null || this.last.getValue() != this.filter; - } + return this.last == null || this.last.getValue() != this.filter; } - public Future getAsync(BiConsumer>, SearchFilter> consumer) { + public Future getAsync(BiConsumer, SearchFilter> consumer) { if (this.executor == null || this.executor.filter() != filter || isDirty()) { if (this.executor != null) { this.executor.future().cancel(Platform.isFabric()); @@ -107,7 +97,7 @@ public class AsyncSearchManager { }, EXECUTOR_SERVICE))).future(); } - public List> getNow() { + public List getNow() { try { return get(Runnable::run).get().getKey(); } catch (ExecutionException e) { @@ -117,18 +107,14 @@ public class AsyncSearchManager { } } - public CompletableFuture>, SearchFilter>> get(Executor executor) { + public CompletableFuture, SearchFilter>> get(Executor executor) { if (isDirty()) { - Map.Entry>, SearchFilter> last; - synchronized (AsyncSearchManager.this) { - last = this.last; - } + Map.Entry, SearchFilter> last; + last = this.last; return get(this.filter, this.additionalPredicateSupplier.get(), this.transformer, this.stacksProvider.get(), last, this, executor) .thenApply(entry -> { - synchronized (AsyncSearchManager.this) { - this.last = entry; - } + this.last = entry; return entry; }); } @@ -136,39 +122,20 @@ public class AsyncSearchManager { return CompletableFuture.completedFuture(last); } - public static CompletableFuture>, SearchFilter>> get(SearchFilter filter, Predicate> additionalPredicate, - UnaryOperator> transformer, List> stacks, Map.Entry>, SearchFilter> last, + public static CompletableFuture, SearchFilter>> get(SearchFilter filter, Predicate additionalPredicate, + UnaryOperator transformer, List stacks, Map.Entry, SearchFilter> last, AsyncSearchManager manager, Executor executor) { int searchPartitionSize = ConfigObject.getInstance().getAsyncSearchPartitionSize(); boolean shouldAsync = ConfigObject.getInstance().shouldAsyncSearch() && stacks.size() > searchPartitionSize * 4; if (!stacks.isEmpty()) { - CompletableFuture preparationFuture = CompletableFuture.completedFuture(null); - - if (last == null || last.getValue() != filter) { - Runnable prepare = () -> { - if (manager.filter == filter) { - List> argumentTypes = ((SearchProviderImpl.SearchFilterImpl) filter).getArgumentTypes(); - Argument.prepareFilter(stacks, argumentTypes, () -> manager.filter() != null && manager.filter() == filter, executor); - } else { - throw new CancellationException(); - } - }; - if (shouldAsync) { - preparationFuture = CompletableFuture.runAsync(prepare, executor); - } else { - prepare.run(); - preparationFuture = CompletableFuture.completedFuture(null); - } - } - if (shouldAsync) { - List>>> futures = Lists.newArrayList(); - for (Iterable> partitionStacks : CollectionUtils.partition(stacks, Math.max(searchPartitionSize, stacks.size() * 3 / Runtime.getRuntime().availableProcessors()))) { + List>> futures = Lists.newArrayList(); + for (Iterable partitionStacks : CollectionUtils.partition(stacks, Math.max(searchPartitionSize, stacks.size() * 3 / Runtime.getRuntime().availableProcessors()))) { futures.add(CompletableFuture.supplyAsync(() -> { - List> filtered = Lists.newArrayList(); - for (EntryStack stack : partitionStacks) { - if (stack != null && filter.test(stack) && additionalPredicate.test(stack)) { + List filtered = Lists.newArrayList(); + for (HashedEntryStackWrapper stack : partitionStacks) { + if (stack != null && filter.test(stack.unwrap(), stack.hashExact()) && additionalPredicate.test(stack)) { filtered.add(transformer.apply(stack)); } if (manager.filter != filter) throw new CancellationException(); @@ -176,19 +143,16 @@ public class AsyncSearchManager { return filtered; }, executor)); } - return preparationFuture.thenCompose($ -> CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])) - .orTimeout(30, TimeUnit.SECONDS)) + return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])) + .orTimeout(30, TimeUnit.SECONDS) .thenApplyAsync($ -> { - List> list = new ArrayList<>(); + List list = new ArrayList<>(); - if (manager.filter == filter) { - for (CompletableFuture>> future : futures) { - List> now = future.getNow(null); - if (now != null) list.addAll(now); - } - } else { - throw new CancellationException(); + for (CompletableFuture> future : futures) { + List now = future.getNow(null); + if (now != null) list.addAll(now); } + if (manager.filter != filter) throw new CancellationException(); return list; }, executor) @@ -196,10 +160,10 @@ public class AsyncSearchManager { return new AbstractMap.SimpleImmutableEntry<>(result, filter); }); } else { - List> list = new ArrayList<>(); + List list = new ArrayList<>(); - for (EntryStack stack : stacks) { - if (filter.test(stack) && additionalPredicate.test(stack)) { + for (HashedEntryStackWrapper stack : stacks) { + if (filter.test(stack.unwrap(), stack.hashExact()) && additionalPredicate.test(stack)) { list.add(transformer.apply(stack)); } if (manager.filter != filter) throw new CancellationException(); 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 06057497a..c19d3f0c1 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 @@ -28,12 +28,15 @@ 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.api.common.util.CollectionUtils; +import me.shedaniel.rei.api.common.util.EntryStacks; 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; @@ -73,9 +76,9 @@ public class SearchProviderImpl implements SearchProvider { } @Override - public boolean test(EntryStack stack) { + public boolean test(EntryStack stack, long hashExact) { try { - return Argument.matches(stack, arguments.get(), inputMethod); + 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"); @@ -88,9 +91,14 @@ public class SearchProviderImpl implements SearchProvider { } } + @Override + public boolean test(EntryStack stack) { + return this.test(stack, EntryStacks.hashExact(stack)); + } + @Override public void prepareFilter(Collection> stacks) { - Argument.prepareFilter(stacks, argumentTypes.get()); + Argument.cache.prepareFilter(CollectionUtils.map(stacks, HashedEntryStackWrapper::new), argumentTypes.get()); } @Override diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/search/argument/AlternativeArgument.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/search/argument/AlternativeArgument.java index 02af1c414..195210322 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/client/search/argument/AlternativeArgument.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/search/argument/AlternativeArgument.java @@ -24,6 +24,7 @@ package me.shedaniel.rei.impl.client.search.argument; import com.google.common.collect.ForwardingList; +import me.shedaniel.rei.api.common.util.CollectionUtils; import org.jetbrains.annotations.ApiStatus; import java.util.ArrayList; @@ -50,9 +51,9 @@ public class AlternativeArgument extends ForwardingList> { } public static class Builder { - private List> arguments; + private List> arguments; - public Builder add(Argument argument) { + public Builder add(Argument.Builder argument) { if (arguments == null) { this.arguments = new ArrayList<>(); } @@ -67,8 +68,8 @@ public class AlternativeArgument extends ForwardingList> { 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); + if (arguments.size() == 1) return new AlternativeArgument(List.of(arguments.get(0).build())); + return new AlternativeArgument(CollectionUtils.map(arguments, Argument.Builder::build)); } } } 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 50165f8eb..7b8dc7d18 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 @@ -26,92 +26,78 @@ package me.shedaniel.rei.impl.client.search.argument; import com.google.common.base.MoreObjects; import com.google.common.collect.Iterators; 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.registry.entry.EntryRegistry; 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.IntRange; 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.collapsed.CollapsedEntriesCache; import me.shedaniel.rei.impl.client.search.result.ArgumentApplicableResult; -import me.shedaniel.rei.impl.client.util.ThreadCreator; -import me.shedaniel.rei.impl.common.InternalLogger; +import me.shedaniel.rei.impl.common.entry.type.EntryRegistryImpl; 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.apache.logging.log4j.Level; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nullable; import java.util.*; -import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicReference; -import java.util.function.BooleanSupplier; import java.util.regex.Matcher; import java.util.regex.Pattern; @ApiStatus.Internal @Environment(EnvType.CLIENT) public class Argument { - private static final ExecutorService EXECUTOR_SERVICE = new ThreadCreator("REI-ArgumentCache").asService(); - private static final Short2ObjectMap> SEARCH_CACHE = Short2ObjectMaps.synchronize(new Short2ObjectOpenHashMap<>()); private static final Object NO_CACHE = new Object(); - private static final AtomicReference lastLanguage = new AtomicReference<>(); - private ArgumentType argumentType; - private String text; - private T filterData; - private boolean regular; + private static final AtomicReference LAST_LANGUAGE = new AtomicReference<>(); + public static ArgumentCache cache = new ArgumentCache(); + private final ArgumentType argumentType; + private final String text; + private final T filterData; + private final boolean regular; private final int start; private final int end; private static final Pattern SPLIT_PATTERN = Pattern.compile("(?:\"([^\"]*)\")|([^\\s]+)"); - public Argument(ArgumentType argumentType, String text, boolean regular, int start, int end, boolean lowercase) { + public Argument(ArgumentType argumentType, String text, boolean regular, T filterData, int start, int end, boolean lowercase) { this.argumentType = argumentType; this.text = lowercase ? text.toLowerCase(Locale.ROOT) : text; this.regular = regular; - this.filterData = null; + this.filterData = filterData; this.start = start; this.end = end; } public static void resetCache(boolean cache) { - SEARCH_CACHE.clear(); + Argument.cache = new ArgumentCache(); + CollapsedEntriesCache.reset(); + Collection stacks = new AbstractCollection<>() { + @Override + public Iterator iterator() { + return Iterators.transform(((EntryRegistryImpl) EntryRegistry.getInstance()).getPreFilteredComplexList().iterator(), + HashedEntryStackWrapper::normalize); + } + + @Override + public int size() { + return EntryRegistry.getInstance().getPreFilteredList().size(); + } + }; if (cache) { - Argument.prepareFilter(new AbstractCollection<>() { - @Override - public Iterator> iterator() { - return Iterators.transform(EntryRegistry.getInstance().getPreFilteredList().iterator(), - EntryStack::normalize); - } - - @Override - public int size() { - return EntryRegistry.getInstance().getPreFilteredList().size(); - } - }, ArgumentTypesRegistry.ARGUMENT_TYPE_LIST, () -> true, EXECUTOR_SERVICE); + Argument.cache.prepareFilter(stacks, ArgumentTypesRegistry.ARGUMENT_TYPE_LIST, ArgumentCache.EXECUTOR_SERVICE); } + CollapsedEntriesCache.getInstance().prepare(stacks); } public static boolean hasCache() { - return !SEARCH_CACHE.isEmpty(); + return !Argument.cache.isEmpty(); } public int start() { @@ -127,7 +113,7 @@ public class Argument { void addSplitter(int index); - void addPart(Argument argument, boolean usingGrammar, Collection grammarRanges, int index); + void addPart(Argument.Builder argument, boolean usingGrammar, Collection grammarRanges, int index); } public static List bakeArguments(String filter) { @@ -165,21 +151,9 @@ public class Argument { sink.addSplitter(tokenStartIndex - 1); } } - prepareSearchFilter(compoundArguments); return compoundArguments; } - private static void prepareSearchFilter(List compoundArguments) { - for (CompoundArgument arguments : compoundArguments) { - for (AlternativeArgument alternativeArgument : arguments) { - for (Argument argument : alternativeArgument) { - //noinspection RedundantCast - ((Argument) 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 ProcessedSink sink) { String term = MoreObjects.firstNonNull(terms.group(1), terms.group(2)); if (type.getSearchMode() == SearchMode.NEVER) return; @@ -187,7 +161,7 @@ public class Argument { if (result.isApplicable()) { int group = terms.group(1) != null ? 1 : 2; - Argument argument = new Argument<>(type, result.getText(), !result.isInverted(), + Argument.Builder argument = new Argument.Builder<>(type, result.getText(), !result.isInverted(), terms.start(group) + tokenStartIndex, terms.end(group) + tokenStartIndex, !result.shouldPreserveCasing()); alternativeBuilder.add(argument); if (sink != null) { @@ -202,17 +176,17 @@ public class Argument { } } - public static boolean matches(EntryStack stack, List compoundArguments, InputMethod inputMethod) { + public static boolean matches(EntryStack stack, long hashExact, List compoundArguments, InputMethod inputMethod) { if (compoundArguments.isEmpty()) return true; String newLanguage = Minecraft.getInstance().options.languageCode; - if (!Objects.equals(lastLanguage.getAndSet(newLanguage), newLanguage)) { + if (!Objects.equals(LAST_LANGUAGE.getAndSet(newLanguage), newLanguage)) { resetCache(false); } a: for (CompoundArgument arguments : compoundArguments) { for (AlternativeArgument argument : arguments) { - if (!matches(stack, argument, inputMethod)) { + if (!matches(stack, hashExact, argument, inputMethod)) { continue a; } } @@ -223,9 +197,8 @@ public class Argument { return false; } - private static boolean matches(EntryStack stack, AlternativeArgument alternativeArgument, InputMethod inputMethod) { + private static boolean matches(EntryStack stack, long hashExact, AlternativeArgument alternativeArgument, InputMethod inputMethod) { if (alternativeArgument.isEmpty()) return true; - long hashExact = EntryStacks.hashExact(stack); ResultSinkImpl sink = new ResultSinkImpl<>(inputMethod); for (Argument argument : alternativeArgument) { @@ -238,17 +211,8 @@ public class Argument { return false; } - private static Long2ObjectMap getSearchCache(ArgumentType argumentType) { - short argumentIndex = (short) argumentType.getIndex(); - Long2ObjectMap map = SEARCH_CACHE.get(argumentIndex); - if (map == null) { - SEARCH_CACHE.put(argumentIndex, map = Long2ObjectMaps.synchronize(new Long2ObjectOpenHashMap<>())); - } - return map; - } - private static boolean matches(ArgumentType argumentType, EntryStack stack, long hashExact, R filterData, ResultSinkImpl sink) { - Long2ObjectMap map = getSearchCache(argumentType); + Long2ObjectMap map = Argument.cache.getSearchCache(argumentType); Object value = map.get(hashExact); if (value == null) { value = argumentType.cacheData(stack); @@ -293,112 +257,6 @@ public class Argument { } } - public static Long prepareStart = null; - public static List prepareStacks = null; - public static IntIntPair prepareStage = null; - public static IntIntPair[] currentStages = null; - - public static void prepareFilter(Collection> stacks, Collection> argumentTypes) { - Argument.prepareFilter(stacks, argumentTypes, () -> true, null); - } - - public static void prepareFilter(Collection> stacks, Collection> argumentTypes, BooleanSupplier isValid, @Nullable Executor executor) { - if (prepareStage != null || currentStages != null) return; - try { - prepareStart = Util.getEpochMillis(); - Long2ObjectMap[] caches = CollectionUtils.map(argumentTypes, Argument::getSearchCache).toArray(Long2ObjectMap[]::new); - prepareStacks = CollectionUtils.mapAndFilter(stacks, stack -> { - for (Long2ObjectMap cache : caches) { - if (!cache.containsKey(stack.hashExact())) { - return true; - } - } - - return false; - }, HashedEntryStackWrapper::new); - if (prepareStacks.isEmpty() && !isValid.getAsBoolean()) { - return; - } - InternalLogger.getInstance().log(ConfigObject.getInstance().doDebugSearchTimeRequired() ? Level.INFO : Level.TRACE, "Preparing " + (prepareStacks.size() * argumentTypes.size()) + " stacks for search arguments"); - prepareStage = new IntIntMutablePair(0, argumentTypes.size()); - currentStages = new IntIntPair[argumentTypes.size()]; - int searchPartitionSize = ConfigObject.getInstance().getAsyncSearchPartitionSize(); - boolean async = ConfigObject.getInstance().shouldAsyncSearch() && prepareStacks.size() > searchPartitionSize * 4; - List>> futures = Lists.newArrayList(); - List, CompletableFuture>>> pairs = Lists.newArrayList(); - - for (ArgumentType argumentType : argumentTypes) { - prepareStage.first(prepareStage.firstInt() + 1); - Long2ObjectMap map = getSearchCache(argumentType); - IntIntPair currentStage = currentStages[prepareStage.firstInt() - 1] = new IntIntMutablePair(0, prepareStacks.size()); - if (!isValid.getAsBoolean()) return; - - if (async) { - for (Collection partitionStacks : CollectionUtils.partition(prepareStacks, searchPartitionSize)) { - CompletableFuture> future = CompletableFuture.supplyAsync(() -> { - Long2ObjectMap out = new Long2ObjectArrayMap<>(searchPartitionSize + 1); - int i = 0; - for (HashedEntryStackWrapper stack : partitionStacks) { - if (map.get(stack.hashExact()) == null) { - Object data = argumentType.cacheData(stack.unwrap()); - - if (data != null) { - out.put(stack.hashExact(), data); - } - } - if (i++ % 40 == 0) if (!isValid.getAsBoolean()) return Long2ObjectMaps.emptyMap(); - } - if (!isValid.getAsBoolean()) return Long2ObjectMaps.emptyMap(); - return out; - }, Objects.requireNonNullElse(executor, EXECUTOR_SERVICE)).whenComplete((objectLong2ObjectMap, throwable) -> { - currentStage.first(currentStage.firstInt() + partitionStacks.size()); - }); - futures.add(future); - pairs.add(Pair.of(argumentType, future)); - } - } else { - for (HashedEntryStackWrapper stack : prepareStacks) { - 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 (ExecutionException | TimeoutException e) { - e.printStackTrace(); - } catch (InterruptedException ignore) { - } finally { - int sum = 0; - for (Pair, CompletableFuture>> pair : pairs) { - Long2ObjectMap now = pair.second().getNow(null); - if (now != null) { - getSearchCache(pair.left()).putAll(now); - sum += now.size(); - } - } - InternalLogger.getInstance().log(ConfigObject.getInstance().doDebugSearchTimeRequired() ? Level.INFO : Level.TRACE, "Prepared " + sum + " / " + (prepareStacks.size() * argumentTypes.size()) + " stacks for search arguments in " + (Util.getEpochMillis() - prepareStart) + "ms"); - } - } else { - InternalLogger.getInstance().log(ConfigObject.getInstance().doDebugSearchTimeRequired() ? Level.INFO : Level.TRACE, "Prepared " + (prepareStacks.size() * argumentTypes.size()) + " stacks for search arguments in " + (Util.getEpochMillis() - prepareStart) + "ms"); - } - } finally { - prepareStart = null; - prepareStacks = null; - prepareStage = null; - currentStages = null; - } - } - public ArgumentType getArgument() { return argumentType; } @@ -416,4 +274,50 @@ public class Argument { return String.format("Argument[%s]: name = %s, regular = %b", argumentType.getName(), text, regular); } + public static class Builder { + private final ArgumentType argumentType; + private final String text; + private final boolean regular; + private final int start; + private final int end; + private final boolean lowercase; + + public Builder(ArgumentType argumentType, String text, boolean regular, int start, int end, boolean lowercase) { + this.argumentType = argumentType; + this.text = text; + this.regular = regular; + this.start = start; + this.end = end; + this.lowercase = lowercase; + } + + public Argument build() { + return new Argument<>(argumentType, text, regular, argumentType.prepareSearchFilter(text), + start, end, lowercase); + } + + public ArgumentType getType() { + return argumentType; + } + + public int start() { + return start; + } + + public int end() { + return end; + } + + public String getText() { + return text; + } + + public boolean isRegular() { + return regular; + } + + public boolean isLowercase() { + return lowercase; + } + } } 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 new file mode 100644 index 000000000..1d7949859 --- /dev/null +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/search/argument/ArgumentCache.java @@ -0,0 +1,172 @@ +/* + * 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.impl.client.search.argument; + +import com.google.common.collect.Lists; +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.common.util.CollectionUtils; +import me.shedaniel.rei.impl.client.search.argument.type.ArgumentType; +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 org.apache.logging.log4j.Level; +import org.jetbrains.annotations.Nullable; + +import java.util.Collection; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.*; + +public class ArgumentCache { + public static final ExecutorService EXECUTOR_SERVICE = new ThreadCreator("REI-Cache").asService(2); + private final Short2ObjectMap> cache = Short2ObjectMaps.synchronize(new Short2ObjectOpenHashMap<>()); + public Long prepareStart = null; + public List prepareStacks = null; + public CurrentStep currentStep = null; + + public Long2ObjectMap getSearchCache(ArgumentType argumentType) { + short argumentIndex = (short) argumentType.getIndex(); + Long2ObjectMap map = cache.get(argumentIndex); + if (map == null) { + cache.put(argumentIndex, map = Long2ObjectMaps.synchronize(new Long2ObjectOpenHashMap<>())); + } + return map; + } + + public void prepareFilter(Collection stacks, Collection> argumentTypes) { + this.prepareFilter(stacks, argumentTypes, null); + } + + public void prepareFilter(Collection stacks, Collection> argumentTypes, @Nullable Executor executor) { + if (currentStep != null) return; + try { + prepareStart = Util.getEpochMillis(); + List> caches = CollectionUtils.map(argumentTypes, this::getSearchCache); + prepareStacks = CollectionUtils.filterToList(stacks, stack -> { + for (Long2ObjectMap cache : caches) { + if (!cache.containsKey(stack.hashExact())) { + return true; + } + } + + return false; + }); + if (prepareStacks.isEmpty()) { + return; + } + InternalLogger.getInstance().log(ConfigObject.getInstance().doDebugSearchTimeRequired() ? Level.INFO : Level.TRACE, "Preparing " + (prepareStacks.size() * argumentTypes.size()) + " stacks for search arguments"); + currentStep = new CurrentStep(0, argumentTypes.size()); + int searchPartitionSize = ConfigObject.getInstance().getAsyncSearchPartitionSize(); + boolean async = ConfigObject.getInstance().shouldAsyncSearch() && prepareStacks.size() > searchPartitionSize * 4; + this.cache(argumentTypes, async ? executor : Runnable::run); + } finally { + prepareStart = null; + prepareStacks = null; + currentStep = null; + } + } + + private void cache(Collection> argumentTypes, @Nullable Executor executor) { + int searchPartitionSize = ConfigObject.getInstance().getAsyncSearchPartitionSize(); + List>> futures = Lists.newArrayList(); + int[] sum = {0}; + + for (ArgumentType argumentType : argumentTypes) { + Long2ObjectMap cacheMap = getSearchCache(argumentType); + CurrentStep.Step currentStage = currentStep.steps[currentStep.step] = new CurrentStep.Step(0, prepareStacks.size()); + currentStep.step++; + + for (Collection partitionStacks : CollectionUtils.partition(prepareStacks, searchPartitionSize)) { + futures.add(CompletableFuture.supplyAsync(() -> { + return cacheStacks(argumentType, cacheMap, partitionStacks); + }, Objects.requireNonNullElse(executor, EXECUTOR_SERVICE)) + .whenComplete((map, throwable) -> { + if (map != null) { + currentStage.stacks += map.size(); + cacheMap.putAll(map); + sum[0] += map.size(); + } + })); + } + } + + try { + CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).get(30, TimeUnit.SECONDS); + } catch (ExecutionException | TimeoutException e) { + e.printStackTrace(); + } catch (InterruptedException ignore) { + } finally { + InternalLogger.getInstance().log(ConfigObject.getInstance().doDebugSearchTimeRequired() ? Level.INFO : Level.TRACE, "Prepared " + sum[0] + " / " + (prepareStacks.size() * argumentTypes.size()) + " stacks for search arguments in " + (Util.getEpochMillis() - prepareStart) + "ms"); + } + } + + private static Long2ObjectMap cacheStacks(ArgumentType argumentType, Long2ObjectMap cacheMap, + Collection stacks) { + Long2ObjectMap 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); + } + } + } + return out; + } + + public boolean isEmpty() { + return cache.isEmpty(); + } + + public static class CurrentStep { + public int step; + public final int totalSteps; + public final Step[] steps; + + public CurrentStep(int step, int totalSteps) { + this.step = step; + this.totalSteps = totalSteps; + this.steps = new Step[totalSteps]; + } + + public static class Step { + public int stacks; + public int totalStacks; + + public Step(int stacks, int totalStacks) { + this.stacks = stacks; + this.totalStacks = totalStacks; + } + } + } +} diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/search/argument/CompoundArgument.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/search/argument/CompoundArgument.java index 5aca58039..a0407d037 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/client/search/argument/CompoundArgument.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/search/argument/CompoundArgument.java @@ -28,7 +28,6 @@ import org.jetbrains.annotations.ApiStatus; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.List; @ApiStatus.Internal @@ -61,17 +60,13 @@ public class CompoundArgument extends ForwardingList { } public static class Builder { - private List arguments; + private List arguments; - public Builder add(Argument argument) { - return add(new AlternativeArgument(Collections.singletonList(argument))); + public Builder add(Argument.Builder argument) { + return add(new AlternativeArgument.Builder().add(argument)); } - public Builder add(AlternativeArgument.Builder builder) { - return add(builder.build()); - } - - public Builder add(AlternativeArgument argument) { + public Builder add(AlternativeArgument.Builder argument) { if (arguments == null) { this.arguments = new ArrayList<>(); } @@ -82,7 +77,8 @@ public class CompoundArgument extends ForwardingList { public CompoundArgument build() { if (arguments == null) return CompoundArgument.ALWAYS; - return CompoundArgument.of(arguments.toArray(new AlternativeArgument[0])); + return CompoundArgument.of(arguments.stream().map(AlternativeArgument.Builder::build) + .toArray(AlternativeArgument[]::new)); } } } diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/search/collapsed/CollapsedEntriesCache.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/search/collapsed/CollapsedEntriesCache.java new file mode 100644 index 000000000..fc7ada4e8 --- /dev/null +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/search/collapsed/CollapsedEntriesCache.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.impl.client.search.collapsed; + +import com.google.common.collect.Lists; +import it.unimi.dsi.fastutil.longs.Long2ObjectMap; +import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; +import me.shedaniel.rei.api.client.registry.entry.CollapsibleEntryRegistry; +import me.shedaniel.rei.impl.common.InternalLogger; +import me.shedaniel.rei.impl.common.entry.type.collapsed.CollapsibleEntryRegistryImpl; +import me.shedaniel.rei.impl.common.util.HashedEntryStackWrapper; +import net.minecraft.resources.ResourceLocation; + +import javax.annotation.Nullable; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.CompletableFuture; + +public class CollapsedEntriesCache { + private static CollapsedEntriesCache instance = new CollapsedEntriesCache(); + private final Long2ObjectMap> cache = new Long2ObjectOpenHashMap<>(); + + public static void reset() { + CollapsedEntriesCache.instance = new CollapsedEntriesCache(); + } + + public static CollapsedEntriesCache getInstance() { + return instance; + } + + public void prepare(Collection stacks) { + Collection entries = ((CollapsibleEntryRegistryImpl) CollapsibleEntryRegistry.getInstance()).getEntries(); + InternalLogger.getInstance().debug("Preparing collapsed entry groups cache with %d entries and %d stacks", entries.size(), stacks.size()); + + List> futures = Lists.newArrayList(); + for (CollapsibleEntryRegistryImpl.Entry entry : entries) { + if (entry.canCache()) { + for (HashedEntryStackWrapper stack : stacks) { + if (entry.getMatcher().matches(stack.unwrap(), stack.hashExact())) { + cache.computeIfAbsent(stack.hashExact(), $ -> new HashSet<>()) + .add(entry.getId()); + } else { + Set locations = cache.get(stack.hashExact()); + if (locations != null) { + locations.remove(entry.getId()); + if (locations.isEmpty()) { + cache.remove(stack.hashExact()); + } + } + } + } + } + } + + InternalLogger.getInstance().debug("Prepared collapsed entry groups cache with %d entries and %d stacks", entries.size(), stacks.size()); + } + + @Nullable + public Set getEntries(long hash) { + return cache.get(hash); + } +} 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 d6ff8aa69..b55c0dab6 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 @@ -25,7 +25,10 @@ package me.shedaniel.rei.impl.client.util; import me.shedaniel.rei.impl.common.InternalLogger; -import java.util.concurrent.*; +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.ForkJoinWorkerThread; import java.util.concurrent.atomic.AtomicInteger; public final class ThreadCreator { @@ -44,21 +47,19 @@ public final class ThreadCreator { return threadId; } - public Thread create(Runnable task) { - Thread thread = new Thread(this.group(), task, this.group().getName() + "-" + this.threadId().getAndIncrement()); - thread.setDaemon(true); - thread.setUncaughtExceptionHandler(($, exception) -> { + public ExecutorService asService() { + return this.asService(Runtime.getRuntime().availableProcessors()); + } + + public ExecutorService asService(int poolSize) { + return new ForkJoinPool(poolSize, pool -> { + ForkJoinWorkerThread worker = ForkJoinPool.defaultForkJoinWorkerThre