aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorshedaniel <daniel@shedaniel.me>2023-05-31 23:18:02 +0800
committershedaniel <daniel@shedaniel.me>2023-06-01 16:55:08 +0800
commit15ff1dcfe181d3469b5a1d28986cf965f0e95df4 (patch)
treece5801c45facc01b5245bce7b3f519c39e4c9312
parentc14a1cc216730037319f2a18370fd29b1993fe71 (diff)
downloadRoughlyEnoughItems-15ff1dcfe181d3469b5a1d28986cf965f0e95df4.tar.gz
RoughlyEnoughItems-15ff1dcfe181d3469b5a1d28986cf965f0e95df4.tar.bz2
RoughlyEnoughItems-15ff1dcfe181d3469b5a1d28986cf965f0e95df4.zip
Fix #1396 and fix #998
-rw-r--r--api/src/main/java/me/shedaniel/rei/api/client/config/ConfigObject.java3
-rw-r--r--api/src/main/java/me/shedaniel/rei/api/common/util/CollectionUtils.java25
-rw-r--r--runtime/src/main/java/me/shedaniel/rei/RoughlyEnoughItemsCoreClient.java2
-rw-r--r--runtime/src/main/java/me/shedaniel/rei/impl/client/REIRuntimeImpl.java3
-rw-r--r--runtime/src/main/java/me/shedaniel/rei/impl/client/config/ConfigObjectImpl.java6
-rw-r--r--runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/EntryWidget.java35
-rw-r--r--runtime/src/main/java/me/shedaniel/rei/impl/client/registry/display/DisplayRegistryImpl.java153
-rw-r--r--runtime/src/main/java/me/shedaniel/rei/impl/client/registry/display/DisplaysHolder.java96
-rw-r--r--runtime/src/main/java/me/shedaniel/rei/impl/client/registry/display/DisplaysHolderImpl.java193
-rw-r--r--runtime/src/main/java/me/shedaniel/rei/impl/client/registry/display/RemappingMap.java124
-rw-r--r--runtime/src/main/java/me/shedaniel/rei/impl/client/search/SearchRuntime.java46
-rw-r--r--runtime/src/main/java/me/shedaniel/rei/impl/client/view/ViewsImpl.java27
-rwxr-xr-xruntime/src/main/resources/assets/roughlyenoughitems/lang/en_us.json1
13 files changed, 550 insertions, 164 deletions
diff --git a/api/src/main/java/me/shedaniel/rei/api/client/config/ConfigObject.java b/api/src/main/java/me/shedaniel/rei/api/client/config/ConfigObject.java
index 45d66c860..02afcd7f4 100644
--- a/api/src/main/java/me/shedaniel/rei/api/client/config/ConfigObject.java
+++ b/api/src/main/java/me/shedaniel/rei/api/client/config/ConfigObject.java
@@ -272,6 +272,9 @@ public interface ConfigObject {
boolean doesCacheEntryRendering();
+ @ApiStatus.Experimental
+ boolean doesCacheDisplayLookup();
+
boolean doDebugRenderTimeRequired();
boolean doMergeDisplayUnderOne();
diff --git a/api/src/main/java/me/shedaniel/rei/api/common/util/CollectionUtils.java b/api/src/main/java/me/shedaniel/rei/api/common/util/CollectionUtils.java
index eb0449f48..bd8376e53 100644
--- a/api/src/main/java/me/shedaniel/rei/api/common/util/CollectionUtils.java
+++ b/api/src/main/java/me/shedaniel/rei/api/common/util/CollectionUtils.java
@@ -23,12 +23,10 @@
package me.shedaniel.rei.api.common.util;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Sets;
-import com.google.common.collect.UnmodifiableIterator;
+import com.google.common.collect.*;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
+import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet;
import me.shedaniel.rei.api.common.entry.EntryStack;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.MutableComponent;
@@ -550,4 +548,23 @@ public class CollectionUtils {
return size;
}
}
+
+ public static <T> Iterable<T> distinctReferenceOf(Iterable<T> iterable) {
+ return () -> new AbstractIterator<T>() {
+ private final Set<T> set = new ReferenceOpenHashSet<>();
+ private final Iterator<T> iterator = iterable.iterator();
+
+ @Override
+ protected T computeNext() {
+ while (iterator.hasNext()) {
+ T next = iterator.next();
+ if (set.add(next)) {
+ return next;
+ }
+ }
+
+ return endOfData();
+ }
+ };
+ }
}
diff --git a/runtime/src/main/java/me/shedaniel/rei/RoughlyEnoughItemsCoreClient.java b/runtime/src/main/java/me/shedaniel/rei/RoughlyEnoughItemsCoreClient.java
index f1b1110e8..46f6589f2 100644
--- a/runtime/src/main/java/me/shedaniel/rei/RoughlyEnoughItemsCoreClient.java
+++ b/runtime/src/main/java/me/shedaniel/rei/RoughlyEnoughItemsCoreClient.java
@@ -78,6 +78,7 @@ import me.shedaniel.rei.impl.client.registry.category.CategoryRegistryImpl;
import me.shedaniel.rei.impl.client.registry.display.DisplayRegistryImpl;
import me.shedaniel.rei.impl.client.registry.screen.ScreenRegistryImpl;
import me.shedaniel.rei.impl.client.search.SearchProviderImpl;
+import me.shedaniel.rei.impl.client.search.SearchRuntime;
import me.shedaniel.rei.impl.client.search.method.InputMethodRegistryImpl;
import me.shedaniel.rei.impl.client.subsets.SubsetsRegistryImpl;
import me.shedaniel.rei.impl.client.transfer.TransferHandlerRegistryImpl;
@@ -231,6 +232,7 @@ public class RoughlyEnoughItemsCoreClient {
new FavoriteEntryTypeRegistryImpl(),
new SubsetsRegistryImpl(),
new TransferHandlerRegistryImpl(),
+ new SearchRuntime(),
new REIRuntimeImpl(),
new ConfigAddonRegistryImpl()), "clientPluginManager");
}
diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/REIRuntimeImpl.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/REIRuntimeImpl.java
index 2e7018ad8..fad1fdf6c 100644
--- a/runtime/src/main/java/me/shedaniel/rei/impl/client/REIRuntimeImpl.java
+++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/REIRuntimeImpl.java
@@ -47,7 +47,6 @@ import me.shedaniel.rei.impl.client.gui.ScreenOverlayImpl;
import me.shedaniel.rei.impl.client.gui.hints.HintProvider;
import me.shedaniel.rei.impl.client.gui.widget.CachedEntryListRender;
import me.shedaniel.rei.impl.client.gui.widget.search.OverlaySearchField;
-import me.shedaniel.rei.impl.client.search.argument.Argument;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.client.Minecraft;
@@ -255,7 +254,6 @@ public class REIRuntimeImpl implements REIRuntime {
@Override
public void startReload() {
- Argument.resetCache(false);
getOverlay().ifPresent(ScreenOverlay::queueReloadOverlay);
lastDisplayScreen.clear();
if (!RenderSystem.isOnRenderThread()) {
@@ -272,7 +270,6 @@ public class REIRuntimeImpl implements REIRuntime {
@Override
public void endReload(ReloadStage stage) {
- Argument.resetCache(true);
getOverlay().ifPresent(ScreenOverlay::queueReloadOverlay);
}
diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/config/ConfigObjectImpl.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/config/ConfigObjectImpl.java
index f8e1f02d0..47f84a7c4 100644
--- a/runtime/src/main/java/me/shedaniel/rei/impl/client/config/ConfigObjectImpl.java
+++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/config/ConfigObjectImpl.java
@@ -303,6 +303,11 @@ public class ConfigObjectImpl implements ConfigObject, ConfigData {
}
@Override
+ public boolean doesCacheDisplayLookup() {
+ return advanced.miscellaneous.cachingDisplayLookup;
+ }
+
+ @Override
public boolean doDebugRenderTimeRequired() {
return advanced.layout.debugRenderTimeRequired;
}
@@ -726,6 +731,7 @@ public class ConfigObjectImpl implements ConfigObject, ConfigData {
private boolean newFastEntryRendering = true;
@ConfigEntry.Gui.PrefixText
private boolean cachingFastEntryRendering = false;
+ private boolean cachingDisplayLookup = true;
}
public static class Filtering {
diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/EntryWidget.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/EntryWidget.java
index 7d23a89d7..777b44d06 100644
--- a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/EntryWidget.java
+++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/EntryWidget.java
@@ -60,6 +60,8 @@ import me.shedaniel.rei.impl.client.gui.InternalTextures;
import me.shedaniel.rei.impl.client.gui.ScreenOverlayImpl;
import me.shedaniel.rei.impl.client.gui.dragging.CurrentDraggingStack;
import me.shedaniel.rei.impl.client.gui.widget.favorites.FavoritesListWidget;
+import me.shedaniel.rei.impl.client.registry.display.DisplayRegistryImpl;
+import me.shedaniel.rei.impl.client.registry.display.DisplaysHolder;
import me.shedaniel.rei.impl.client.view.ViewsImpl;
import net.minecraft.ChatFormatting;
import net.minecraft.CrashReport;
@@ -317,22 +319,24 @@ public class EntryWidget extends Slot implements DraggableStackProviderWidget {
try {
DisplayRegistry displayRegistry = DisplayRegistry.getInstance();
+ DisplaysHolder displaysHolder = ((DisplayRegistryImpl) displayRegistry).displaysHolder();
CategoryRegistry categoryRegistry = CategoryRegistry.getInstance();
Map<CategoryIdentifier<?>, Boolean> filteringQuickCraftCategories = ConfigObject.getInstance().getFilteringQuickCraftCategories();
- for (Map.Entry<CategoryIdentifier<?>, List<Display>> entry : displayRegistry.getAll().entrySet()) {
+ boolean shouldFilterDisplays = ConfigObject.getInstance().shouldFilterDisplays();
+
+ for (Display display : displaysHolder.getAllDisplaysByOutputs(getEntries())) {
+ CategoryIdentifier<?> categoryIdentifier = display.getCategoryIdentifier();
Optional<? extends CategoryRegistry.CategoryConfiguration<?>> configuration;
- if ((configuration = categoryRegistry.tryGet(entry.getKey())).isEmpty()
- || categoryRegistry.isCategoryInvisible(configuration.get().getCategory())) continue;
- if (!filteringQuickCraftCategories.getOrDefault(entry.getKey(), configuration.get().isQuickCraftingEnabledByDefault())) continue;
- for (Display display : entry.getValue()) {
- if ((!ConfigObject.getInstance().shouldFilterDisplays() || displayRegistry.isDisplayVisible(display))
- && ViewsImpl.isRecipesFor(getEntries(), display)) {
- AutoCraftingEvaluator.AutoCraftingResult result = AutoCraftingEvaluator.evaluateAutoCrafting(false, false, display, null);
- if (result.successful) {
- this.display = display;
- this.displayTooltipComponent = Suppliers.memoize(() -> new DisplayTooltipComponent(display));
- return result.successfulHandler;
- }
+ if ((configuration = categoryRegistry.tryGet(categoryIdentifier)).isEmpty()
+ || categoryRegistry.isCategoryInvisible(configuration.get().getCategory())) continue;
+ if (!filteringQuickCraftCategories.getOrDefault(categoryIdentifier, configuration.get().isQuickCraftingEnabledByDefault()))
+ continue;
+ if ((!shouldFilterDisplays || displayRegistry.isDisplayVisible(display))) {
+ AutoCraftingEvaluator.AutoCraftingResult result = AutoCraftingEvaluator.evaluateAutoCrafting(false, false, display, null);
+ if (result.successful) {
+ this.display = display;
+ this.displayTooltipComponent = Suppliers.memoize(() -> new DisplayTooltipComponent(display));
+ return result.successfulHandler;
}
}
}
@@ -351,7 +355,7 @@ public class EntryWidget extends Slot implements DraggableStackProviderWidget {
}
if (display != null) {
- if (ViewsImpl.isRecipesFor(getEntries(), display)) {
+ if (ViewsImpl.isRecipesFor(null, getEntries(), display)) {
AutoCraftingEvaluator.AutoCraftingResult result = AutoCraftingEvaluator.evaluateAutoCrafting(false, false, display, null);
if (result.successful) {
return result.successfulHandler;
@@ -428,7 +432,8 @@ public class EntryWidget extends Slot implements DraggableStackProviderWidget {
}
}
- protected void drawExtra(PoseStack matrices, int mouseX, int mouseY, float delta) {}
+ protected void drawExtra(PoseStack matrices, int mouseX, int mouseY, float delta) {
+ }
@Override
@Nullable
diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/registry/display/DisplayRegistryImpl.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/registry/display/DisplayRegistryImpl.java
index b875b8530..1aff7f720 100644
--- a/runtime/src/main/java/me/shedaniel/rei/impl/client/registry/display/DisplayRegistryImpl.java
+++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/registry/display/DisplayRegistryImpl.java
@@ -24,10 +24,6 @@
package me.shedaniel.rei.impl.client.registry.display;
import com.google.common.base.Preconditions;
-import com.google.common.collect.ForwardingMap;
-import com.google.common.collect.ForwardingMapEntry;
-import com.google.common.collect.Iterators;
-import com.google.common.collect.Sets;
import dev.architectury.event.EventResult;
import me.shedaniel.rei.api.client.plugins.REIClientPlugin;
import me.shedaniel.rei.api.client.registry.category.CategoryRegistry;
@@ -43,9 +39,7 @@ import me.shedaniel.rei.api.common.plugins.PluginManager;
import me.shedaniel.rei.impl.common.InternalLogger;
import me.shedaniel.rei.impl.common.registry.RecipeManagerContextImpl;
import net.minecraft.world.item.crafting.Recipe;
-import org.apache.commons.lang3.mutable.MutableInt;
import org.apache.commons.lang3.mutable.MutableLong;
-import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
@@ -53,127 +47,17 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BiPredicate;
import java.util.function.Function;
import java.util.function.Predicate;
-import java.util.function.UnaryOperator;
public class DisplayRegistryImpl extends RecipeManagerContextImpl<REIClientPlugin> implements DisplayRegistry {
- private final WeakHashMap<Display, Object> displaysBase = new WeakHashMap<>();
- private final Map<CategoryIdentifier<?>, DisplaysList> displays = new ConcurrentHashMap<>();
- private final Map<CategoryIdentifier<?>, List<Display>> unmodifiableDisplays;
private final Map<CategoryIdentifier<?>, List<DynamicDisplayGenerator<?>>> displayGenerators = new ConcurrentHashMap<>();
private final List<DynamicDisplayGenerator<?>> globalDisplayGenerators = new ArrayList<>();
private final List<DisplayVisibilityPredicate> visibilityPredicates = new ArrayList<>();
private final List<DisplayFiller<?>> fillers = new ArrayList<>();
- private final MutableInt displayCount = new MutableInt(0);
+ private final MutableLong lastAddWarning = new MutableLong(-1);
+ private DisplaysHolder displaysHolder = new DisplaysHolderImpl(false);
public DisplayRegistryImpl() {
super(RecipeManagerContextImpl.supplier());
-
- this.unmodifiableDisplays = new RemappingMap<>(Collections.unmodifiableMap(displays), list -> {
- if (list == null) {
- return null;
- } else {
- return ((DisplaysList) list).unmodifiableList;
- }
- }, key -> CategoryRegistry.getInstance().tryGet(key).isPresent());
- }
-
- private static class RemappingMap<K, V> extends ForwardingMap<K, V> {
- protected final Map<K, V> map;
- protected final UnaryOperator<V> remapper;
- protected final Predicate<K> keyPredicate;
-
- public RemappingMap(Map<K, V> map, UnaryOperator<V> remapper, Predicate<K> keyPredicate) {
- this.map = map;
- this.remapper = remapper;
- this.keyPredicate = keyPredicate;
- }
-
- @Override
- @NotNull
- protected Map<K, V> delegate() {
- return map;
- }
-
- @Override
- public V get(Object key) {
- if (keyPredicate.test((K) key)) {
- return remapper.apply(super.get(key));
- } else {
- return null;
- }
- }
-
- @Override
- public boolean containsKey(@Nullable Object key) {
- return super.containsKey(key) && keyPredicate.test((K) key);
- }
-
- @Override
- public Set<K> keySet() {
- return Sets.filter(super.keySet(), keyPredicate::test);
- }
-
- @SuppressWarnings("UnstableApiUsage")
- @Override
- @NotNull
- public Set<Entry<K, V>> entrySet() {
- return this.new StandardEntrySet() {
- @Override
- public Iterator<Entry<K, V>> iterator() {
- return mapIterator(map.entrySet().iterator());
- }
- };
- }
-
- @Override
- public int size() {
- return keySet().size();
- }
-
- @Override
- public Collection<V> values() {
- return new AbstractCollection<V>() {
- @Override
- public Iterator<V> iterator() {
- return Iterators.transform(entrySet().iterator(), Entry::getValue);
- }
-
- @Override
- public int size() {
- return RemappingMap.this.size();
- }
- };
- }
-
- private Iterator<Entry<K, V>> mapIterator(Iterator<Entry<K, V>> iterator) {
- return Iterators.transform(Iterators.filter(iterator, entry -> this.keyPredicate.test(entry.getKey())),
- this::mapEntry);
- }
-
- private Entry<K, V> mapEntry(Entry<K, V> entry) {
- return new ForwardingMapEntry<>() {
- @Override
- @NotNull
- protected Entry<K, V> delegate() {
- return entry;
- }
-
- @Override
- public V getValue() {
- return remapper.apply(entry.getValue());
- }
- };
- }
- }
-
- private static class DisplaysList extends ArrayList<Display> {
- private final List<Display> unmodifiableList;
- private final List<Display> synchronizedList;
-
- public DisplaysList() {
- this.synchronizedList = Collections.synchronizedList(this);
- this.unmodifiableList = Collections.unmodifiableList(synchronizedList);
- }
}
@Override
@@ -183,11 +67,9 @@ public class DisplayRegistryImpl extends RecipeManagerContextImpl<REIClientPlugi
@Override
public int displaySize() {
- return displayCount.getValue();
+ return this.displaysHolder.size();
}
- private MutableLong lastAddWarning = new MutableLong(-1);
-
@Override
public void add(Display display, @Nullable Object origin) {
if (!PluginManager.areAnyReloading()) {
@@ -199,19 +81,12 @@ public class DisplayRegistryImpl extends RecipeManagerContextImpl<REIClientPlugi
}
}
- displays.computeIfAbsent(display.getCategoryIdentifier(), location -> new DisplaysList())
- .add(display);
- displayCount.increment();
- if (origin != null) {
- synchronized (displaysBase) {
- displaysBase.put(display, origin);
- }
- }
+ this.displaysHolder.add(display, origin);
}
@Override
public Map<CategoryIdentifier<?>, List<Display>> getAll() {
- return unmodifiableDisplays;
+ return this.displaysHolder.get();
}
@Override
@@ -287,11 +162,10 @@ public class DisplayRegistryImpl extends RecipeManagerContextImpl<REIClientPlugi
@Override
public void startReload() {
super.startReload();
- this.displays.clear();
+ this.displaysHolder = new DisplaysHolderImpl(true);
this.displayGenerators.clear();
this.visibilityPredicates.clear();
this.fillers.clear();
- this.displayCount.setValue(0);
}
@Override
@@ -304,13 +178,19 @@ public class DisplayRegistryImpl extends RecipeManagerContextImpl<REIClientPlugi
}
}
- for (CategoryIdentifier<?> identifier : displays.keySet()) {
+ for (CategoryIdentifier<?> identifier : getAll().keySet()) {
if (CategoryRegistry.getInstance().tryGet(identifier).isEmpty()) {
InternalLogger.getInstance().error("Found displays registered for unknown registry", new IllegalStateException(identifier.toString()));
}
}
- InternalLogger.getInstance().debug("Registered %d displays", displayCount.getValue());
+ this.displaysHolder.endReload();
+
+ InternalLogger.getInstance().debug("Registered %d displays", displaySize());
+ }
+
+ public DisplaysHolder displaysHolder() {
+ return displaysHolder;
}
@Override
@@ -349,12 +229,13 @@ public class DisplayRegistryImpl extends RecipeManagerContextImpl<REIClientPlugi
@Override
@Nullable
public Object getDisplayOrigin(Display display) {
- return displaysBase.get(display);
+ return this.displaysHolder.getDisplayOrigin(display);
}
private record DisplayFiller<D extends Display>(
BiPredicate<Object, DisplayAdditionReasons> predicate,
Function<Object, D> mappingFunction
- ) {}
+ ) {
+ }
}
diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/registry/display/DisplaysHolder.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/registry/display/DisplaysHolder.java
new file mode 100644
index 000000000..07b5879cf
--- /dev/null
+++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/registry/display/DisplaysHolder.java
@@ -0,0 +1,96 @@
+/*
+ * 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.registry.display;
+
+import com.google.common.collect.Iterables;
+import me.shedaniel.rei.api.common.category.CategoryIdentifier;
+import me.shedaniel.rei.api.common.display.Display;
+import me.shedaniel.rei.api.common.entry.EntryStack;
+import me.shedaniel.rei.api.common.util.CollectionUtils;
+import me.shedaniel.rei.impl.client.view.ViewsImpl;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public interface DisplaysHolder {
+ boolean doesCache();
+
+ void add(Display display, @Nullable Object origin);
+
+ int size();
+
+ Map<CategoryIdentifier<?>, List<Display>> get();
+
+ @Nullable
+ Object getDisplayOrigin(Display display);
+
+ void endReload();
+
+ boolean isCached(Display display);
+
+ Set<Display> getDisplaysNotCached();
+
+ Set<Display> getDisplaysByInput(EntryStack<?> stack);
+
+ Set<Display> getDisplaysByOutput(EntryStack<?> stack);
+
+ default Iterable<Display> getAllDisplaysByInputs(List<EntryStack<?>> stacks) {
+ if (stacks.isEmpty()) return List.of();
+ Iterable<Display> inputCached = null;
+ if (doesCache()) {
+ for (EntryStack<?> stack : stacks) {
+ Set<Display> set = getDisplaysByInput(stack);
+ inputCached = inputCached == null ? set : Iterables.concat(inputCached, set);
+ }
+ if (stacks.size() > 1) inputCached = CollectionUtils.distinctReferenceOf(inputCached);
+ }
+ Collection<Display> notCached = this.getDisplaysNotCached();
+ if (notCached.isEmpty()) return inputCached == null ? List.of() : inputCached;
+ Iterable<Display> filteredNotCached = Iterables.filter(notCached, display ->
+ ViewsImpl.isUsagesFor(null, stacks, display));
+ if (inputCached == null) return filteredNotCached;
+ return Iterables.concat(inputCached, filteredNotCached);
+ }
+
+ default Iterable<Display> getAllDisplaysByOutputs(List<EntryStack<?>> stacks) {
+ if (stacks.isEmpty()) return List.of();
+ Iterable<Display> outputCached = null;
+ if (doesCache()) {
+ for (EntryStack<?> stack : stacks) {
+ Set<Display> set = getDisplaysByOutput(stack);
+ outputCached = outputCached == null ? set : Iterables.concat(outputCached, set);
+ }
+ if (stacks.size() > 1) outputCached = CollectionUtils.distinctReferenceOf(outputCached);
+ }
+ Collection<Display> notCached = this.getDisplaysNotCached();
+ if (notCached.isEmpty()) return outputCached == null ? List.of() : outputCached;
+ Iterable<Display> filteredNotCached = Iterables.filter(notCached, display ->
+ ViewsImpl.isRecipesFor(null, stacks, display));
+ if (outputCached == null) return filteredNotCached;
+ return Iterables.concat(outputCached, filteredNotCached);
+ }
+}
diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/registry/display/DisplaysHolderImpl.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/registry/display/DisplaysHolderImpl.java
new file mode 100644
index 000000000..f1f930639
--- /dev/null
+++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/registry/display/DisplaysHolderImpl.java
@@ -0,0 +1,193 @@
+/*
+ * 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.registry.display;
+
+import com.google.common.base.Stopwatch;
+import com.google.common.collect.Multimaps;
+import com.google.common.collect.SetMultimap;
+import it.unimi.dsi.fastutil.Hash;
+import it.unimi.dsi.fastutil.objects.Object2ObjectOpenCustomHashMap;
+import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet;
+import me.shedaniel.rei.api.client.config.ConfigObject;
+import me.shedaniel.rei.api.client.registry.category.CategoryRegistry;
+import me.shedaniel.rei.api.common.category.CategoryIdentifier;
+import me.shedaniel.rei.api.common.display.Display;
+import me.shedaniel.rei.api.common.entry.EntryIngredient;
+import me.shedaniel.rei.api.common.entry.EntryStack;
+import me.shedaniel.rei.api.common.util.EntryStacks;
+import me.shedaniel.rei.impl.common.InternalLogger;
+import org.apache.commons.lang3.mutable.MutableInt;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class DisplaysHolderImpl implements DisplaysHolder {
+ private final boolean cache;
+ private final Map<CategoryIdentifier<?>, DisplaysList> displays = new ConcurrentHashMap<>();
+ private final Map<CategoryIdentifier<?>, List<Display>> unmodifiableDisplays;
+ private final WeakHashMap<Display, Object> displaysBase = new WeakHashMap<>();
+ private Set<Display> displaysCached = new ReferenceOpenHashSet<>();
+ private Set<Display> displaysNotCached = Collections.synchronizedSet(new ReferenceOpenHashSet<>());
+ private boolean preprocessed = false;
+ private SetMultimap<EntryStack<?>, Display> displaysByInput;
+ private SetMultimap<EntryStack<?>, Display> displaysByOutput;
+ private final MutableInt displayCount = new MutableInt(0);
+
+ public DisplaysHolderImpl(boolean init) {
+ this.cache = init && ConfigObject.getInstance().doesCacheDisplayLookup();
+ this.unmodifiableDisplays = new RemappingMap<>(Collections.unmodifiableMap(displays), list -> {
+ if (list == null) {
+ return null;
+ } else {
+ return ((DisplaysList) list).unmodifiableList;
+ }
+ }, key -> CategoryRegistry.getInstance().tryGet(key).isPresent());
+ this.displaysByInput = createSetMultimap();
+ this.displaysByOutput = createSetMultimap();
+ }
+
+ @Override
+ public boolean doesCache() {
+ return this.cache;
+ }
+
+ @Override
+ public void add(Display display, @Nullable Object origin) {
+ this.displays.computeIfAbsent(display.getCategoryIdentifier(), location -> new DisplaysList())
+ .add(display);
+ this.displayCount.increment();
+ if (origin != null) {
+ synchronized (this.displaysBase) {
+ this.displaysBase.put(display, origin);
+ }
+ }
+ if (this.cache) {
+ if (!preprocessed) {
+ this.displaysNotCached.add(display);
+ } else {
+ this.process(display);
+ this.displaysCached.add(display);
+ }
+ } else {
+ this.displaysNotCached.add(display);
+ }
+ }
+
+ @Override
+ public int size() {
+ return this.displayCount.intValue();
+ }
+
+ @Override
+ public Map<CategoryIdentifier<?>, List<Display>> get() {
+ return this.unmodifiableDisplays;
+ }
+
+ @Override
+ public void endReload() {
+ if (this.cache) {
+ InternalLogger.getInstance().debug("Processing %d displays for optimal lookup performance...", this.size());
+ Stopwatch stopwatch = Stopwatch.createStarted();
+ this.displaysCached = new ReferenceOpenHashSet<>(this.size());
+ this.displaysByInput = createSetMultimap();
+ this.displaysByOutput = createSetMultimap();
+ for (Display display : this.displaysNotCached) {
+ this.process(display);
+ }
+ this.displaysCached.addAll(this.displaysNotCached);
+ this.displaysNotCached = Set.of();
+ this.preprocessed = true;
+ InternalLogger.getInstance().debug("Processed displays for optimal lookup performance in %s.", stopwatch.stop());
+ }
+ }
+
+ private void process(Display display) {
+ for (EntryIngredient input : display.getInputEntries()) {
+ for (EntryStack<?> stack : input) {
+ this.displaysByInput.put(stack, display);
+ }
+ }
+ for (EntryIngredient output : display.getOutputEntries()) {
+ for (EntryStack<?> stack : output) {
+ this.displaysByOutput.put(stack, display);
+ }
+ }
+ }
+
+ @Override
+ public boolean isCached(Display display) {
+ return this.cache && this.displaysCached.contains(display);
+ }
+
+ @Override
+ public Set<Display> getDisplaysNotCached() {
+ return this.displaysNotCached;
+ }
+
+ @Override
+ public Set<Display> getDisplaysByInput(EntryStack<?> stack) {
+ return this.displaysByInput.get(stack);
+ }
+
+ @Override
+ public Set<Display> getDisplaysByOutput(EntryStack<?> stack) {
+ return this.displaysByOutput.get(stack);
+ }
+
+ @Override
+