From 3c9eb139444779b5df6fd986e79f8e5969f5e69c Mon Sep 17 00:00:00 2001 From: shedaniel Date: Fri, 12 Apr 2024 21:16:58 +0900 Subject: Make REI only do wildcard match if no results are found --- .../shedaniel/rei/impl/client/view/ViewsImpl.java | 271 ++++++++++++--------- 1 file changed, 155 insertions(+), 116 deletions(-) (limited to 'runtime/src/main/java/me') diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/view/ViewsImpl.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/view/ViewsImpl.java index b815882c3..cd44c7434 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/client/view/ViewsImpl.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/view/ViewsImpl.java @@ -26,10 +26,10 @@ package me.shedaniel.rei.impl.client.view; import com.google.common.base.Stopwatch; import com.google.common.collect.Iterables; import com.google.common.collect.Maps; -import com.google.common.collect.Sets; import it.unimi.dsi.fastutil.longs.Long2LongMap; import it.unimi.dsi.fastutil.longs.Long2LongMaps; import it.unimi.dsi.fastutil.longs.Long2LongOpenHashMap; +import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; import me.shedaniel.rei.api.client.REIRuntime; import me.shedaniel.rei.api.client.config.ConfigObject; import me.shedaniel.rei.api.client.registry.category.CategoryRegistry; @@ -108,7 +108,7 @@ public class ViewsImpl implements Views { Stopwatch stopwatch = Stopwatch.createStarted(); boolean processingVisibilityHandlers = builder.isProcessingVisibilityHandlers(); - Set> categories = builder.getCategories(); + Set> categories = new HashSet<>(builder.getCategories()); Set> filteringCategories = builder.getFilteringCategories(); List> recipesForStacks = builder.getRecipesFor(); List> usagesForStacks = builder.getUsagesFor(); @@ -122,27 +122,20 @@ public class ViewsImpl implements Views { DisplayRegistry displayRegistry = DisplayRegistry.getInstance(); DisplaysHolder displaysHolder = ((DisplayRegistryImpl) displayRegistry).displaysHolder(); - Map, List> result = Maps.newLinkedHashMap(); - for (CategoryRegistry.CategoryConfiguration categoryConfiguration : CategoryRegistry.getInstance()) { - DisplayCategory category = categoryConfiguration.getCategory(); - if (processingVisibilityHandlers && CategoryRegistry.getInstance().isCategoryInvisible(category)) continue; - CategoryIdentifier categoryId = categoryConfiguration.getCategoryIdentifier(); - if (!filteringCategories.isEmpty() && !filteringCategories.contains(categoryId)) continue; - List allRecipesFromCategory = displayRegistry.get((CategoryIdentifier) categoryId); - - Set set = Sets.newLinkedHashSet(); - if (categories.contains(categoryId)) { - for (Display display : allRecipesFromCategory) { + Map, Set> result = Maps.newLinkedHashMap(); + forCategories(processingVisibilityHandlers, filteringCategories, displayRegistry, result, (configuration, categoryId, displays, set) -> { + if (categories.contains(categoryId)) { // If the category is in the search, add all displays + for (Display display : displays) { if (!processingVisibilityHandlers || displayRegistry.isDisplayVisible(display)) { set.add(display); } } if (!set.isEmpty()) { - CollectionUtils.getOrPutEmptyList(result, category).addAll(set); + getOrPutEmptyLinkedSet(result, configuration.getCategory()).addAll(set); } - continue; + return; } - for (Display display : allRecipesFromCategory) { + for (Display display : displays) { if (processingVisibilityHandlers && !displayRegistry.isDisplayVisible(display)) continue; if (!recipesForStacks.isEmpty()) { if (isRecipesFor(displaysHolder, recipesForStacks, display)) { @@ -153,42 +146,12 @@ public class ViewsImpl implements Views { if (!usagesForStacks.isEmpty()) { if (isUsagesFor(displaysHolder, usagesForStacks, display)) { set.add(display); - continue; } } } - if (set.isEmpty() && (!recipesForStacksWildcard.isEmpty() || !usagesForStacksWildcard.isEmpty())) { - for (Display display : allRecipesFromCategory) { - if (processingVisibilityHandlers && !displayRegistry.isDisplayVisible(display)) continue; - if (!recipesForStacksWildcard.isEmpty()) { - if (isRecipesFor(displaysHolder, recipesForStacksWildcard, display)) { - set.add(display); - continue; - } - } - if (!usagesForStacksWildcard.isEmpty()) { - if (isUsagesFor(displaysHolder, usagesForStacksWildcard, display)) { - set.add(display); - continue; - } - } - } - } - for (EntryStack usagesFor : Iterables.concat(usagesForStacks, usagesForStacksWildcard)) { - if (isStackWorkStationOfCategory(categoryConfiguration, usagesFor)) { - if (processingVisibilityHandlers) { - set.addAll(CollectionUtils.filterToSet(allRecipesFromCategory, displayRegistry::isDisplayVisible)); - } else { - set.addAll(allRecipesFromCategory); - } - break; - } - } - if (!set.isEmpty()) { - CollectionUtils.getOrPutEmptyList(result, category).addAll(set); - } - } + }); + // Generate live displays per category int generatorsCount = 0; for (Map.Entry, List>> entry : displayRegistry.getCategoryDisplayGenerators().entrySet()) { @@ -204,90 +167,65 @@ public class ViewsImpl implements Views { } if (!set.isEmpty()) { - CollectionUtils.getOrPutEmptyList(result, category).addAll(set); + getOrPutEmptyLinkedSet(result, category).addAll(set); } } Consumer displayConsumer = display -> { CategoryIdentifier categoryIdentifier = display.getCategoryIdentifier(); if (!filteringCategories.isEmpty() && !filteringCategories.contains(categoryIdentifier)) return; - CollectionUtils.getOrPutEmptyList(result, CategoryRegistry.getInstance().get(categoryIdentifier).getCategory()).add(display); + getOrPutEmptyLinkedSet(result, CategoryRegistry.getInstance().get(categoryIdentifier).getCategory()).add(display); }; for (DynamicDisplayGenerator generator : (List>) (List>) displayRegistry.getGlobalDisplayGenerators()) { generatorsCount++; generateLiveDisplays(displayRegistry, wrapForError(generator), builder, displayConsumer); } - Map, List> resultSpeced = (Map, List>) (Map) new LinkedHashMap<>(result); - // optimize displays - if (builder.isMergingDisplays() && ConfigObject.getInstance().doMergeDisplayUnderOne()) { - for (Map.Entry, List> entry : result.entrySet()) { - DisplayMerger merger = (DisplayMerger) entry.getKey().getDisplayMerger(); - - if (merger != null) { - class Wrapped implements DisplaySpec { - private final Display display; - private List ids = null; - private final int hash; - - public Wrapped(Display display) { - this.display = display; - this.hash = merger.hashOf(display); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof Wrapped)) return false; - Wrapped wrapped = (Wrapped) o; - return hash == wrapped.hash && merger.canMerge(display, wrapped.display); - } - - @Override - public int hashCode() { - return hash; - } - - @Override - public Display provideInternalDisplay() { - return display; - } - - @Override - public Collection provideInternalDisplayIds() { - if (ids == null) { - ids = new ArrayList<>(); - Optional location = display.getDisplayLocation(); - if (location.isPresent()) { - ids.add(location.get()); - } - } - return ids; - } - - public void add(Display display) { - Optional location = display.getDisplayLocation(); - if (location.isPresent()) { - provideInternalDisplayIds().add(location.get()); - } + if (CollectionUtils.allMatch(result.values(), Set::isEmpty) && (!recipesForStacksWildcard.isEmpty() || !usagesForStacksWildcard.isEmpty())) { + // Run wildcard search because no displays were found + forCategories(processingVisibilityHandlers, filteringCategories, displayRegistry, result, (configuration, categoryId, displays, set) -> { + if (categories.contains(categoryId)) return; + for (Display display : displays) { + if (processingVisibilityHandlers && !displayRegistry.isDisplayVisible(display)) continue; + if (!recipesForStacksWildcard.isEmpty()) { + if (isRecipesFor(displaysHolder, recipesForStacksWildcard, display)) { + set.add(display); + continue; } } - Map wrappedSet = new LinkedHashMap<>(); - List wrappeds = new ArrayList<>(); - - for (Display display : sortAutoCrafting(entry.getValue())) { - Wrapped wrapped = new Wrapped(display); - if (wrappedSet.containsKey(wrapped)) { - wrappedSet.get(wrapped).add(display); - } else { - wrappedSet.put(wrapped, wrapped); - wrappeds.add(wrapped); + if (!usagesForStacksWildcard.isEmpty()) { + if (isUsagesFor(displaysHolder, usagesForStacksWildcard, display)) { + set.add(display); } } - - resultSpeced.put(entry.getKey(), (List) (List) wrappeds); + } + }); + } + + forCategories(processingVisibilityHandlers, filteringCategories, displayRegistry, result, (configuration, categoryId, displays, set) -> { + if (categories.contains(categoryId)) return; + for (EntryStack usagesFor : Iterables.concat(usagesForStacks, usagesForStacksWildcard)) { + if (isStackWorkStationOfCategory(configuration, usagesFor)) { + categories.add(categoryId); + if (processingVisibilityHandlers) { + set.addAll(CollectionUtils.filterToSet(displays, displayRegistry::isDisplayVisible)); + } else { + set.addAll(displays); + } + break; } } + }); + + Map, List> resultSpec = (Map, List>) (Map) new LinkedHashMap<>(); + for (CategoryRegistry.CategoryConfiguration configuration : CategoryRegistry.getInstance()) { + Set displays = result.get(configuration.getCategory()); + if (displays == null) continue; + resultSpec.put(configuration.getCategory(), new ArrayList<>(displays)); + } + // optimize displays + if (builder.isMergingDisplays() && ConfigObject.getInstance().doMergeDisplayUnderOne()) { + mergeAndOptimize(result, resultSpec); } String message = String.format("Built Recipe View in %s for %d categories, %d recipes for, %d usages for and %d live recipe generators.", @@ -297,7 +235,30 @@ public class ViewsImpl implements Views { } else { InternalLogger.getInstance().trace(message); } - return resultSpeced; + return resultSpec; + } + + private static void forCategories(boolean processingVisibilityHandlers, Set> filteringCategories, DisplayRegistry displayRegistry, Map, Set> result, QuadConsumer, CategoryIdentifier, List, Set> displayConsumer) { + for (CategoryRegistry.CategoryConfiguration configuration : CategoryRegistry.getInstance()) { + if (processingVisibilityHandlers && CategoryRegistry.getInstance().isCategoryInvisible(configuration.getCategory())) continue; + CategoryIdentifier categoryId = configuration.getCategoryIdentifier(); + if (!filteringCategories.isEmpty() && !filteringCategories.contains(categoryId)) continue; + List allRecipesFromCategory = displayRegistry.get((CategoryIdentifier) categoryId); + Set set = new LinkedHashSet<>(); + displayConsumer.accept(configuration, categoryId, allRecipesFromCategory, set); + if (!set.isEmpty()) { + getOrPutEmptyLinkedSet(result, configuration.getCategory()).addAll(set); + } + } + } + + public static Set getOrPutEmptyLinkedSet(Map> map, A key) { + Set b = map.get(key); + if (b != null) { + return b; + } + map.put(key, new ReferenceOpenHashSet<>()); + return map.get(key); } public static boolean isRecipesFor(@Nullable DisplaysHolder displaysHolder, List> stacks, Display display) { @@ -334,7 +295,7 @@ public class ViewsImpl implements Views { return false; } - private static Iterable sortAutoCrafting(List displays) { + private static Iterable sortAutoCrafting(Iterable displays) { Set successfulDisplays = new LinkedHashSet<>(); Set applicableDisplays = new LinkedHashSet<>(); @@ -555,6 +516,84 @@ public class ViewsImpl implements Views { @Override public void startReload() { + + } + + private static void mergeAndOptimize(Map, Set> displays, Map, List> resultSpec) { + for (Map.Entry, Set> entry : displays.entrySet()) { + DisplayMerger merger = (DisplayMerger) entry.getKey().getDisplayMerger(); + + if (merger != null) { + Map wrappedSet = new LinkedHashMap<>(); + List specs = new ArrayList<>(); + + for (Display display : sortAutoCrafting(entry.getValue())) { + WrappedDisplaySpec wrapped = new WrappedDisplaySpec(merger, display); + if (wrappedSet.containsKey(wrapped)) { + wrappedSet.get(wrapped).add(display); + } else { + wrappedSet.put(wrapped, wrapped); + specs.add(wrapped); + } + } + + resultSpec.put(entry.getKey(), (List) (List) specs); + } + } + } + + private static class WrappedDisplaySpec implements DisplaySpec { + private final DisplayMerger merger; + private final Display display; + private List ids = null; + private final int hash; + + public WrappedDisplaySpec(DisplayMerger merger, Display display) { + this.merger = merger; + this.display = display; + this.hash = merger.hashOf(display); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof WrappedDisplaySpec)) return false; + WrappedDisplaySpec wrapped = (WrappedDisplaySpec) o; + return hash == wrapped.hash && merger.canMerge(display, wrapped.display); + } + + @Override + public int hashCode() { + return hash; + } + + @Override + public Display provideInternalDisplay() { + return display; + } + + @Override + public Collection provideInternalDisplayIds() { + if (ids == null) { + ids = new ArrayList<>(); + Optional location = display.getDisplayLocation(); + if (location.isPresent()) { + ids.add(location.get()); + } + } + return ids; + } + + public void add(Display display) { + Optional location = display.getDisplayLocation(); + if (location.isPresent()) { + provideInternalDisplayIds().add(location.get()); + } + } + } + @FunctionalInterface + private interface QuadConsumer { + void accept(P1 p1, P2 p2, P3 p3, P4 p4); } } -- cgit From 8f8db7a604a86d17b88a621a18656e0791d87aeb Mon Sep 17 00:00:00 2001 From: shedaniel Date: Sun, 18 Dec 2022 19:34:26 +0800 Subject: Fix #668 --- .../shedaniel/rei/impl/client/view/ViewsImpl.java | 38 +++++++++++++++++----- 1 file changed, 29 insertions(+), 9 deletions(-) (limited to 'runtime/src/main/java/me') diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/view/ViewsImpl.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/view/ViewsImpl.java index cd44c7434..c42594b3b 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/client/view/ViewsImpl.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/view/ViewsImpl.java @@ -29,6 +29,8 @@ import com.google.common.collect.Maps; import it.unimi.dsi.fastutil.longs.Long2LongMap; import it.unimi.dsi.fastutil.longs.Long2LongMaps; import it.unimi.dsi.fastutil.longs.Long2LongOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; import me.shedaniel.rei.api.client.REIRuntime; import me.shedaniel.rei.api.client.config.ConfigObject; @@ -122,7 +124,7 @@ public class ViewsImpl implements Views { DisplayRegistry displayRegistry = DisplayRegistry.getInstance(); DisplaysHolder displaysHolder = ((DisplayRegistryImpl) displayRegistry).displaysHolder(); - Map, Set> result = Maps.newLinkedHashMap(); + Map, Set> result = Maps.newHashMap(); forCategories(processingVisibilityHandlers, filteringCategories, displayRegistry, result, (configuration, categoryId, displays, set) -> { if (categories.contains(categoryId)) { // If the category is in the search, add all displays for (Display display : displays) { @@ -217,17 +219,23 @@ public class ViewsImpl implements Views { } }); - Map, List> resultSpec = (Map, List>) (Map) new LinkedHashMap<>(); - for (CategoryRegistry.CategoryConfiguration configuration : CategoryRegistry.getInstance()) { - Set displays = result.get(configuration.getCategory()); - if (displays == null) continue; - resultSpec.put(configuration.getCategory(), new ArrayList<>(displays)); + // Merging displays + Stopwatch mergingStopwatch = Stopwatch.createStarted(), sortingStopwatch = Stopwatch.createUnstarted(); + Map, List> merged = (Map, List>) (Map) new LinkedHashMap<>(); + for (Map.Entry, Set> entry : result.entrySet()) { + merged.put(entry.getKey(), new ArrayList<>(entry.getValue())); } - // optimize displays + if (builder.isMergingDisplays() && ConfigObject.getInstance().doMergeDisplayUnderOne()) { - mergeAndOptimize(result, resultSpec); + mergeAndOptimize(result, merged); } + mergingStopwatch.stop(); + // Sorting displays + sortingStopwatch.start(); + Map, List> sorted = sortDisplays(merged); + sortingStopwatch.stop(); + String message = String.format("Built Recipe View in %s for %d categories, %d recipes for, %d usages for and %d live recipe generators.", stopwatch.stop(), categories.size(), recipesForStacks.size(), usagesForStacks.size(), generatorsCount); if (ConfigObject.getInstance().doDebugSearchTimeRequired()) { @@ -235,7 +243,19 @@ public class ViewsImpl implements Views { } else { InternalLogger.getInstance().trace(message); } - return resultSpec; + return sorted; + } + + private static Map, List> sortDisplays(Map, List> unsorted) { + Object2IntMap> categoryOrder = new Object2IntOpenHashMap<>(); + categoryOrder.defaultReturnValue(Integer.MAX_VALUE); + int i = 0; + for (CategoryRegistry.CategoryConfiguration configuration : CategoryRegistry.getInstance()) { + categoryOrder.put(configuration.getCategoryIdentifier(), i++); + } + Map, List> result = new TreeMap<>(Comparator.comparingInt(category -> categoryOrder.getInt(category.getCategoryIdentifier()))); + result.putAll(unsorted); + return result; } private static void forCategories(boolean processingVisibilityHandlers, Set> filteringCategories, DisplayRegistry displayRegistry, Map, Set> result, QuadConsumer, CategoryIdentifier, List, Set> displayConsumer) { -- cgit From e6687c01b254bf903d4895aa0bb631b1679d465c Mon Sep 17 00:00:00 2001 From: shedaniel Date: Sun, 18 Dec 2022 20:24:23 +0800 Subject: Close #913 --- .../me/shedaniel/rei/impl/client/config/ConfigObjectImpl.java | 11 +++++++++++ .../impl/client/gui/widget/CraftableFilterButtonWidget.java | 8 ++++++++ .../client/gui/widget/entrylist/EntryListSearchManager.java | 11 ++++++++++- .../shedaniel/rei/impl/client/search/AsyncSearchManager.java | 11 ++++------- 4 files changed, 33 insertions(+), 8 deletions(-) (limited to 'runtime/src/main/java/me') 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 47f84a7c4..2ef46744b 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 @@ -60,6 +60,7 @@ import java.util.Map; @ApiStatus.Internal @Config(name = "roughlyenoughitems/config") @Environment(EnvType.CLIENT) +@SuppressWarnings("FieldMayBeFinal") public class ConfigObjectImpl implements ConfigObject, ConfigData { @ConfigEntry.Category("basics") @ConfigEntry.Gui.TransitiveObject @DontApplyFieldName public Basics basics = new Basics(); @@ -139,6 +140,15 @@ public class ConfigObjectImpl implements ConfigObject, ConfigData { appearance.scrollingEntryListWidget = scrollingEntryListWidget; } + @Override + public boolean isHidingEntryPanelIfIdle() { + return appearance.hideEntryPanelIfIdle; + } + + public void setHidingEntryPanelIfIdle(boolean hideEntryPanelIfIdle) { + appearance.hideEntryPanelIfIdle = hideEntryPanelIfIdle; + } + @Override public boolean shouldAppendModNames() { return advanced.tooltips.appendModNames; @@ -625,6 +635,7 @@ public class ConfigObjectImpl implements ConfigObject, ConfigData { @Comment("Declares the appearance of recipe's border.") @ConfigEntry.Gui.EnumHandler(option = ConfigEntry.Gui.EnumHandler.EnumDisplayOption.BUTTON) private RecipeBorderType recipeBorder = RecipeBorderType.DEFAULT; @Comment("Declares whether entry panel is scrolled.") private boolean scrollingEntryListWidget = false; + @Comment("Declares whether entry panel should be invisible when not searching") private boolean hideEntryPanelIfIdle = false; public static class Layout { @Comment("Declares the position of the search field.") @ConfigEntry.Gui.EnumHandler(option = ConfigEntry.Gui.EnumHandler.EnumDisplayOption.BUTTON) diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/CraftableFilterButtonWidget.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/CraftableFilterButtonWidget.java index a4926f524..d9e3f2531 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/CraftableFilterButtonWidget.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/CraftableFilterButtonWidget.java @@ -28,6 +28,7 @@ import dev.architectury.platform.Platform; import dev.architectury.utils.value.BooleanValue; import me.shedaniel.math.Point; import me.shedaniel.math.Rectangle; +import me.shedaniel.rei.api.client.REIRuntime; import me.shedaniel.rei.api.client.config.ConfigManager; import me.shedaniel.rei.api.client.config.ConfigObject; import me.shedaniel.rei.api.client.favorites.FavoriteMenuEntry; @@ -113,6 +114,13 @@ public class CraftableFilterButtonWidget { entries.add(new SubMenuEntry(Component.translatable("text.rei.config.menu.search_field.input_method"), createInputMethodEntries(access, applicableInputMethods))); } + entries.add(ToggleMenuEntry.of(new TranslatableComponent("text.rei.config.menu.search_field.hide_entry_panel_idle"), + config::isHidingEntryPanelIfIdle, + hideEntryPanelIfIdle -> { + config.setHidingEntryPanelIfIdle(hideEntryPanelIfIdle); + REIRuntime.getInstance().getOverlay().orElseThrow().queueReloadSearch(); + })); + return entries; } 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 0b1bf2662..50fd71d78 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 @@ -32,6 +32,7 @@ import me.shedaniel.rei.api.client.config.ConfigObject; import me.shedaniel.rei.api.client.gui.config.EntryPanelOrdering; import me.shedaniel.rei.api.client.registry.entry.CollapsibleEntryRegistry; import me.shedaniel.rei.api.client.registry.entry.EntryRegistry; +import me.shedaniel.rei.api.client.search.SearchFilter; import me.shedaniel.rei.api.client.view.Views; import me.shedaniel.rei.api.common.entry.EntryStack; import me.shedaniel.rei.api.common.entry.type.VanillaEntryTypes; @@ -67,7 +68,7 @@ public class EntryListSearchManager { public static final EntryListSearchManager INSTANCE = new EntryListSearchManager(); - private final AsyncSearchManager searchManager = new AsyncSearchManager(((EntryRegistryImpl) EntryRegistry.getInstance())::getPreFilteredComplexList, () -> { + private final AsyncSearchManager searchManager = new AsyncSearchManager(EntryListSearchManager::getAllEntriesContextually, () -> { boolean checkCraftable = ConfigManager.getInstance().isCraftableOnlyEnabled(); LongSet workingItems = checkCraftable ? new LongOpenHashSet() : null; if (checkCraftable) { @@ -78,6 +79,14 @@ public class EntryListSearchManager { return checkCraftable ? stack -> workingItems.contains(stack.hashExact()) : stack -> true; }, HashedEntryStackWrapper::normalize); + private static List> getAllEntriesContextually(SearchFilter filter) { + if (ConfigObject.getInstance().isHidingEntryPanelIfIdle() && filter.getFilter().isEmpty()) { + return List.of(); + } + + return EntryRegistry.getInstance().getPreFilteredList(); + } + public void update(String searchTerm, boolean ignoreLastSearch, Consumer | CollapsedStack*/ Object>> update) { Stopwatch stopwatch = Stopwatch.createStarted(); if (ignoreLastSearch) searchManager.markDirty(); 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 8d1f6f056..381831c42 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 @@ -41,21 +41,18 @@ 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; -import java.util.function.UnaryOperator; +import java.util.function.*; public class AsyncSearchManager { private static final ExecutorService EXECUTOR_SERVICE = new ThreadCreator("REI-AsyncSearchManager").asService(Math.min(3, Runtime.getRuntime().availableProcessors())); - private final Supplier> stacksProvider; + private final Function> stacksProvider; private final Supplier> additionalPredicateSupplier; private final UnaryOperator transformer; private volatile Map.Entry, SearchFilter> last; public volatile ExecutorTuple executor; public volatile SearchFilter filter; - public AsyncSearchManager(Supplier> stacksProvider, Supplier> additionalPredicateSupplier, UnaryOperator transformer) { + public AsyncSearchManager(Function> stacksProvider, Supplier> additionalPredicateSupplier, UnaryOperator transformer) { this.stacksProvider = stacksProvider; this.additionalPredicateSupplier = additionalPredicateSupplier; this.transformer = transformer; @@ -123,7 +120,7 @@ public class AsyncSearchManager { Map.Entry, SearchFilter> last; last = this.last; return get(this.filter, this.additionalPredicateSupplier.get(), this.transformer, - this.stacksProvider.get(), last, this, executor, steps) + this.stacksProvider.apply(filter), last, this, executor, steps) .thenApply(entry -> { this.last = entry; return entry; -- cgit From ffe21652b40a93a00f33a27a5ecf41479b48bcd9 Mon Sep 17 00:00:00 2001 From: shedaniel Date: Sun, 18 Dec 2022 20:50:56 +0800 Subject: Close #1131 --- .../registry/display/DisplayRegistryImpl.java | 47 +++++++++++++++------- 1 file changed, 33 insertions(+), 14 deletions(-) (limited to 'runtime/src/main/java/me') 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 f8f303408..655466211 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 @@ -145,15 +145,32 @@ public class DisplayRegistryImpl extends RecipeManagerContextImpl typeClass.isInstance(o) && ((Predicate) predicate).test((T) o), o -> ((Function) filler).apply((T) o)); } + @Override + public void registerDisplaysFiller(Class typeClass, Predicate predicate, Function> filler) { + registerDisplaysFiller(o -> typeClass.isInstance(o) && ((Predicate) predicate).test((T) o), o -> ((Function>) filler).apply((T) o)); + } + @Override public void registerFiller(Class typeClass, BiPredicate predicate, Function filler) { - fillers.add(new DisplayFiller<>((o, s) -> typeClass.isInstance(o) && ((BiPredicate) predicate).test(o, s), (Function) filler)); + fillers.add(DisplayFiller.of((o, s) -> typeClass.isInstance(o) && ((BiPredicate) predicate).test(o, s), (Function) filler)); + InternalLogger.getInstance().debug("Added display filter: %s for %s", filler, typeClass.getName()); + } + + @Override + public void registerDisplaysFiller(Class typeClass, BiPredicate predicate, Function> filler) { + fillers.add(new DisplayFiller<>((o, s) -> typeClass.isInstance(o) && ((BiPredicate) predicate).test(o, s), (Function>) filler)); InternalLogger.getInstance().debug("Added display filter: %s for %s", filler, typeClass.getName()); } @Override public void registerFiller(Predicate predicate, Function filler) { - fillers.add(new DisplayFiller<>((o, s) -> ((Predicate) predicate).test(o), (Function) filler)); + fillers.add(DisplayFiller.of((o, s) -> ((Predicate) predicate).test(o), (Function) filler)); + InternalLogger.getInstance().debug("Added display filter: %s", filler); + } + + @Override + public void registerDisplaysFiller(Predicate predicate, Function> filler) { + fillers.add(new DisplayFiller<>((o, s) -> ((Predicate) predicate).test(o), (Function>) filler)); InternalLogger.getInstance().debug("Added display filter: %s", filler); } @@ -194,28 +211,27 @@ public class DisplayRegistryImpl extends RecipeManagerContextImpl Collection tryFillDisplay(T value, DisplayAdditionReason... reason) { if (value instanceof Display) return Collections.singleton((Display) value); - List displays = null; + List out = null; DisplayAdditionReasons reasons = reason.length == 0 ? DisplayAdditionReasons.Impl.EMPTY : new DisplayAdditionReasons.Impl(reason); for (DisplayFiller filler : fillers) { - Display display = tryFillDisplayGenerics(filler, value, reasons); - if (display != null) { - if (displays == null) displays = Collections.singletonList(display); - else { - if (!(displays instanceof ArrayList)) displays = new ArrayList<>(displays); - displays.add(display); + Collection displays = tryFillDisplayGenerics(filler, value, reasons); + if (displays != null && !displays.isEmpty()) { + if (out == null) out = new ArrayList<>(); + for (Display display : displays) { + if (display != null) out.add(display); } } } - if (displays != null) { - return displays; + if (out != null) { + return out; } return Collections.emptyList(); } - private D tryFillDisplayGenerics(DisplayFiller filler, Object value, DisplayAdditionReasons reasons) { + private Collection tryFillDisplayGenerics(DisplayFiller filler, Object value, DisplayAdditionReasons reasons) { try { if (filler.predicate.test(value, reasons)) { - return filler.mappingFunction.apply(value); + return (Collection) filler.mappingFunction.apply(value); } } catch (Throwable e) { throw new RuntimeException("Failed to fill displays!", e); @@ -233,7 +249,10 @@ public class DisplayRegistryImpl extends RecipeManagerContextImpl( BiPredicate predicate, - Function mappingFunction + Function> mappingFunction ) { + public static DisplayFiller of(BiPredicate predicate, Function mappingFunction) { + return new DisplayFiller<>(predicate, o -> Collections.singleton(mappingFunction.apply(o))); + } } } -- cgit From c93345b568ba0c6231986cac30485e689212c731 Mon Sep 17 00:00:00 2001 From: shedaniel Date: Sun, 25 Dec 2022 00:10:10 +0800 Subject: Implement Unified Ingredients --- .../gui/screen/AbstractDisplayViewingScreen.java | 190 ++++++++++- .../gui/screen/CompositeDisplayViewingScreen.java | 1 + .../gui/screen/DefaultDisplayViewingScreen.java | 2 + .../rei/impl/client/gui/widget/EntryWidget.java | 96 +++--- .../client/util/AbstractIndexedCyclingList.java | 94 ++++++ .../rei/impl/client/util/ClientTickCounter.java | 40 +++ .../impl/client/util/ConcatenatedListIterator.java | 189 +++++++++++ .../rei/impl/client/util/CyclingList.java | 353 +++++++++++++++++++++ .../client/util/OriginalRetainingCyclingList.java | 176 ++++++++++ .../rei/impl/common/entry/EntryIngredientImpl.java | 67 +++- 10 files changed, 1159 insertions(+), 49 deletions(-) create mode 100644 runtime/src/main/java/me/shedaniel/rei/impl/client/util/AbstractIndexedCyclingList.java create mode 100644 runtime/src/main/java/me/shedaniel/rei/impl/client/util/ClientTickCounter.java create mode 100644 runtime/src/main/java/me/shedaniel/rei/impl/client/util/ConcatenatedListIterator.java create mode 100644 runtime/src/main/java/me/shedaniel/rei/impl/client/util/CyclingList.java create mode 100644 runtime/src/main/java/me/shedaniel/rei/impl/client/util/OriginalRetainingCyclingList.java (limited to 'runtime/src/main/java/me') diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/screen/AbstractDisplayViewingScreen.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/screen/AbstractDisplayViewingScreen.java index b2d26599a..91e4bc3af 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/screen/AbstractDisplayViewingScreen.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/screen/AbstractDisplayViewingScreen.java @@ -30,6 +30,9 @@ import com.mojang.datafixers.util.Pair; import com.mojang.math.Matrix4f; import dev.architectury.fluid.FluidStack; import dev.architectury.utils.value.IntValue; +import it.unimi.dsi.fastutil.longs.LongOpenHashSet; +import it.unimi.dsi.fastutil.longs.LongSet; +import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; import me.shedaniel.math.Rectangle; import me.shedaniel.rei.api.client.REIRuntime; import me.shedaniel.rei.api.client.config.ConfigObject; @@ -45,7 +48,9 @@ import me.shedaniel.rei.api.client.registry.display.DisplayCategoryView; import me.shedaniel.rei.api.client.registry.entry.EntryRegistry; 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.entry.settings.EntryIngredientSetting; import me.shedaniel.rei.api.common.entry.type.EntryType; import me.shedaniel.rei.api.common.entry.type.VanillaEntryTypes; import me.shedaniel.rei.api.common.util.CollectionUtils; @@ -53,6 +58,8 @@ import me.shedaniel.rei.impl.client.REIRuntimeImpl; import me.shedaniel.rei.impl.client.gui.widget.EntryWidget; import me.shedaniel.rei.impl.client.gui.widget.TabContainerWidget; import me.shedaniel.rei.impl.client.gui.widget.entrylist.EntryListWidget; +import me.shedaniel.rei.impl.client.util.ClientTickCounter; +import me.shedaniel.rei.impl.client.util.CyclingList; import me.shedaniel.rei.impl.display.DisplaySpec; import net.minecraft.ChatFormatting; import net.minecraft.client.Minecraft; @@ -221,13 +228,30 @@ public abstract class AbstractDisplayViewingScreen extends Screen implements Dis private static void transformNotice(int marker, List setupDisplay, List> noticeStacks) { if (noticeStacks.isEmpty()) return; + Map, LongSet> noticeSet = new HashMap<>(); for (EntryWidget widget : Widgets.walk(setupDisplay, EntryWidget.class::isInstance)) { - if (widget.getNoticeMark() == marker && widget.getEntries().size() > 1) { + List> entries = widget.getEntries(); + if (widget.getNoticeMark() == marker && entries.size() > 1) { for (EntryStack noticeStack : noticeStacks) { - EntryStack stack = CollectionUtils.findFirstOrNullEqualsExact(widget.getEntries(), noticeStack); + EntryStack stack = CollectionUtils.findFirstOrNullEqualsExact(entries, noticeStack); if (stack != null) { widget.clearStacks(); widget.entry(stack); + if (entries instanceof EntryIngredient ingredient) noticeSet.computeIfAbsent(stack, $ -> new LongOpenHashSet()) + .add(hashFocusIngredient(ingredient)); + break; + } + } + } + } + for (EntryWidget widget : Widgets.walk(setupDisplay, EntryWidget.class::isInstance)) { + List> entries = widget.getEntries(); + if (widget.getNoticeMark() != marker && entries.size() > 1 && entries instanceof EntryIngredient ingredient) { + long hashFocusIngredient = hashFocusIngredient(ingredient); + for (Map.Entry, LongSet> entry : noticeSet.entrySet()) { + if (entry.getValue().contains(hashFocusIngredient)) { + widget.clearStacks(); + widget.entry(entry.getKey()); break; } } @@ -235,18 +259,31 @@ public abstract class AbstractDisplayViewingScreen extends Screen implements Dis } } + @SuppressWarnings("RedundantCast") protected void transformFiltering(List setupDisplay) { for (EntryWidget widget : Widgets.walk(setupDisplay, EntryWidget.class::isInstance)) { if (widget.getEntries().size() > 1) { Collection> refiltered = EntryRegistry.getInstance().refilterNew(false, widget.getEntries()); - if (!refiltered.isEmpty()) { + EntryIngredient asEntryIngredient = widget.getEntries() instanceof EntryIngredient ingredient ? ingredient : null; + if (!refiltered.isEmpty() && !widget.getEntries().equals(refiltered)) { widget.clearStacks(); - widget.entries(refiltered); + EntryIngredient newIngredient = EntryIngredient.of(refiltered); + if (asEntryIngredient != null && (Object) asEntryIngredient.getSetting(EntryIngredientSetting.FOCUS_UUID) instanceof UUID uuid) { + newIngredient.setting(EntryIngredientSetting.FOCUS_UUID, + new UUID(uuid.getMostSignificantBits() ^ refiltered.size(), uuid.getLeastSignificantBits() ^ refiltered.size())); + } + widget.entries(newIngredient); } } } } + protected static long hashFocusIngredient(EntryIngredient ingredient) { + UUID uuid = ingredient.getSetting(EntryIngredientSetting.FOCUS_UUID); + if (uuid == null) return System.identityHashCode(ingredient); + return uuid.hashCode() ^ ingredient.size(); + } + protected void setupTags(List widgets) { outer: for (EntryWidget widget : Widgets.walk(widgets, EntryWidget.class::isInstance)) { @@ -283,6 +320,32 @@ public abstract class AbstractDisplayViewingScreen extends Screen implements Dis } } + protected void unifyIngredients(List widgets) { + Map> slots = new TreeMap<>(Comparator.comparingLong(AbstractDisplayViewingScreen::hashFocusIngredient)); + for (EntryWidget slot : Widgets.walk(widgets, EntryWidget.class::isInstance)) { + CyclingList> entries = slot.getBackingCyclingEntries(); + if (entries.get() instanceof EntryIngredient ingredient) { + slots.computeIfAbsent(ingredient, key -> new ArrayList<>()).add(slot); + } + } + for (Map.Entry> entry : slots.entrySet()) { + List slotList = entry.getValue(); + if (slotList.size() > 1) { + List>> all = new ArrayList<>(); + Limiter>> limiter = new TickCountLimiter<>(all); + for (EntryWidget slot : slotList) { + CyclingList> limited; + CyclingList> backing = slot.getBackingCyclingEntries(); + if (backing instanceof CyclingList.Mutable> mutable) + limited = new LimitedCyclingList.Mutable<>(mutable, limiter); + else limited = new LimitedCyclingList<>(backing, limiter); + slot.entries(limited); + all.add(backing); + } + } + } + } + private static final int MAX_WIDTH = 200; private void addCyclingTooltip(EntryWidget widget) { @@ -443,4 +506,123 @@ public abstract class AbstractDisplayViewingScreen extends Screen implements Dis public boolean charTyped(char character, int modifiers) { return super.charTyped(character, modifiers) || (getOverlay().charTyped(character, modifiers) && handleFocuses()); } + + private interface Limiter { + boolean canExecute(T t); + + List getEntries(); + } + + private static class TickCountLimiter implements Limiter { + private int ticks = -1; + private final List list; + private final Set set = new ReferenceOpenHashSet<>(); + + public TickCountLimiter(List list) { + this.list = list; + } + + @Override + public boolean canExecute(T t) { + int currentTick = ClientTickCounter.getTicks(); + if (this.ticks != currentTick) { + this.ticks = currentTick; + this.set.clear(); + } + return this.set.add(t); + } + + @Override + public List getEntries() { + return list; + } + } + + private static class LimitedCyclingList implements CyclingList { + protected final CyclingList provider; + private final Limiter> limiter; + + public LimitedCyclingList(CyclingList provider, Limiter> limiter) { + this.provider = provider; + this.limiter = limiter; + } + + @Override + public T peek() { + return provider.peek(); + } + + @Override + public void resetToStart() { + provider.resetToStart(); + } + + @Override + public int size() { + return provider.size(); + } + + @Override + public int currentIndex() { + return provider.currentIndex(); + } + + @Override + public T previous() { + if (this.limiter.canExecute(provider)) { + for (CyclingList list : this.limiter.getEntries()) { + list.previous(); + } + } + + return provider.peek(); + } + + @Override + public int nextIndex() { + return provider.nextIndex(); + } + + @Override + public int previousIndex() { + return provider.previousIndex(); + } + + @Override + public T next() { + if (this.limiter.canExecute(provider)) { + for (CyclingList list : this.limiter.getEntries()) { + list.next(); + } + } + + return provider.peek(); + } + + @Override + public List get() { + return provider.get(); + } + + private static class Mutable extends LimitedCyclingList implements CyclingList.Mutable { + public Mutable(CyclingList.Mutable provider, Limiter> limiter) { + super(provider, limiter); + } + + @Override + public void add(T entry) { + ((CyclingList.Mutable) provider).add(entry); + } + + @Override + public void addAll(Collection entries) { + ((CyclingList.Mutable) provider).addAll(entries); + } + + @Override + public void clear() { + ((CyclingList.Mutable) provider).clear(); + } + } + } } diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/screen/CompositeDisplayViewingScreen.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/screen/CompositeDisplayViewingScreen.java index c0e9e2b01..f92275424 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/screen/CompositeDisplayViewingScreen.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/screen/CompositeDisplayViewingScreen.java @@ -148,6 +148,7 @@ public class CompositeDisplayViewingScreen extends AbstractDisplayViewingScreen transformFiltering(setupDisplay); transformIngredientNotice(setupDisplay, ingredientStackToNotice); transformResultNotice(setupDisplay, resultStackToNotice); + unifyIngredients(setupDisplay); for (EntryWidget widget : Widgets.walk(widgets, EntryWidget.class::isInstance)) { widget.removeTagMatch = true; } diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/screen/DefaultDisplayViewingScreen.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/screen/DefaultDisplayViewingScreen.java index c4535757a..b47c9d825 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/screen/DefaultDisplayViewingScreen.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/screen/DefaultDisplayViewingScreen.java @@ -255,6 +255,7 @@ public class DefaultDisplayViewingScreen extends AbstractDisplayViewingScreen { transformFiltering(setupDisplay); transformIngredientNotice(setupDisplay, ingredientStackToNotice); transformResultNotice(setupDisplay, resultStackToNotice); + unifyIngredients(setupDisplay); for (EntryWidget widget : Widgets.walk(widgets, EntryWidget.class::isInstance)) { widget.removeTagMatch = true; } @@ -446,6 +447,7 @@ public class DefaultDisplayViewingScreen extends AbstractDisplayViewingScreen { transformFiltering(setupDisplay); transformIngredientNotice(setupDisplay, ingredientStackToNotice); transformResultNotice(setupDisplay, resultStackToNotice); + unifyIngredients(setupDisplay); for (EntryWidget widget : Widgets.walk(widgets(), EntryWidget.class::isInstance)) { widget.removeTagMatch = true; } 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 f5369814c..be2de31eb 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 @@ -44,6 +44,7 @@ import me.shedaniel.rei.api.client.gui.screen.DisplayScreen; import me.shedaniel.rei.api.client.gui.widgets.Slot; import me.shedaniel.rei.api.client.gui.widgets.Tooltip; import me.shedaniel.rei.api.client.gui.widgets.TooltipContext; +import me.shedaniel.rei.api.client.gui.widgets.Widgets; import me.shedaniel.rei.api.client.overlay.ScreenOverlay; import me.shedaniel.rei.api.client.registry.category.CategoryRegistry; import me.shedaniel.rei.api.client.registry.display.DisplayRegistry; @@ -63,6 +64,8 @@ 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.util.CyclingList; +import me.shedaniel.rei.impl.client.util.OriginalRetainingCyclingList; import me.shedaniel.rei.impl.client.view.ViewsImpl; import net.minecraft.ChatFormatting; import net.minecraft.CrashReport; @@ -77,7 +80,6 @@ import net.minecraft.client.resources.sounds.SimpleSoundInstance; import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceLocation; import net.minecraft.sounds.SoundEvents; -import net.minecraft.util.Mth; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nullable; @@ -87,10 +89,8 @@ import java.util.function.UnaryOperator; import java.util.stream.Collectors; import java.util.stream.Stream; +@SuppressWarnings("UnstableApiUsage") public class EntryWidget extends Slot implements DraggableStackProviderWidget { - @ApiStatus.Internal - public static long stackDisplayOffset = 0; - @ApiStatus.Internal private byte noticeMark = 0; protected boolean highlight = true; @@ -99,8 +99,9 @@ public class EntryWidget extends Slot implements DraggableStackProviderWidget { protected boolean interactable = true; protected boolean interactableFavorites = true; protected boolean wasClicked = false; - private Rectangle bounds; - private List> entryStacks; + private final Rectangle bounds; + private final OriginalRetainingCyclingList> stacks = new OriginalRetainingCyclingList<>(EntryStack::empty); + private long lastCycleTime = -1; @Nullable private Set> tooltipProcessors; public ResourceLocation tagMatch; @@ -117,7 +118,6 @@ public class EntryWidget extends Slot implements DraggableStackProviderWidget { public EntryWidget(Rectangle bounds) { this.bounds = bounds; - this.entryStacks = Collections.emptyList(); } @Override @@ -244,60 +244,64 @@ public class EntryWidget extends Slot implements DraggableStackProviderWidget { return background; } - public EntryWidget clearStacks() { - entryStacks = Collections.emptyList(); + @Override + public Slot clearEntries() { + this.getCyclingEntries().clear(); + if (removeTagMatch) tagMatch = null; return this; } - @Override - public Slot clearEntries() { - return clearStacks(); + public EntryWidget clearStacks() { + return (EntryWidget) this.clearEntries(); } @Override public EntryWidget entry(EntryStack stack) { Objects.requireNonNull(stack); - if (entryStacks.isEmpty()) { - entryStacks = Collections.singletonList(stack); - } else { - if (!(entryStacks instanceof ArrayList)) { - entryStacks = new ArrayList<>(entryStacks); - } - entryStacks.add(stack); - } + this.getCyclingEntries().add(stack); if (removeTagMatch) tagMatch = null; return this; } @Override public EntryWidget entries(Collection> stacks) { - if (!stacks.isEmpty()) { - if (!(entryStacks instanceof ArrayList)) { - entryStacks = new ArrayList<>(entryStacks); - } - entryStacks.addAll(stacks); - if (removeTagMatch) tagMatch = null; - } + Objects.requireNonNull(stacks); + this.getCyclingEntries().addAll(stacks); + if (removeTagMatch) tagMatch = null; return this; } - @Override - public EntryStack getCurrentEntry() { - int size = entryStacks.size(); - if (size == 0) - return EntryStack.empty(); - if (size == 1) - return entryStacks.get(0); - return entryStacks.get(Mth.floor(((System.currentTimeMillis() + stackDisplayOffset) / getCyclingInterval() % (double) size))); + public Slot entries(CyclingList> stacks) { + this.getCyclingEntries().setBacking(stacks); + if (removeTagMatch) tagMatch = null; + return this; } - protected long getCyclingInterval() { - return 1000; + public CyclingList> getBackingCyclingEntries() { + return this.stacks.getBacking(); + } + + public OriginalRetainingCyclingList> getCyclingEntries() { + return this.stacks; + } + + @Override + public EntryStack getCurrentEntry() { + if (this.lastCycleTime == -1) this.lastCycleTime = System.currentTimeMillis(); + if (System.currentTimeMillis() > this.lastCycleTime + getCyclingInterval()) { + this.lastCycleTime = System.currentTimeMillis(); + this.getCyclingEntries().next(); + } + return this.getCyclingEntries().peek(); } @Override public List> getEntries() { - return entryStacks; + return getCyclingEntries().get(); + } + + protected long getCyclingInterval() { + return 1000; } @Override @@ -562,12 +566,18 @@ public class EntryWidget extends Slot implements DraggableStackProviderWidget { @Override public boolean mouseScrolled(double mouseX, double mouseY, double amount) { - if (REIRuntimeImpl.isWithinRecipeViewingScreen && entryStacks.size() > 1 && containsMouse(mouseX, mouseY)) { + if (REIRuntimeImpl.isWithinRecipeViewingScreen && this.getCyclingEntries().get().size() > 1 && containsMouse(mouseX, mouseY)) { if (amount < 0) { - EntryWidget.stackDisplayOffset = ((System.currentTimeMillis() + stackDisplayOffset) / 1000 - 1) * 1000; + for (EntryWidget slot : Widgets.walk(minecraft.screen.children(), EntryWidget.class::isInstance)) { + slot.getCyclingEntries().previous(); + slot.lastCycleTime = System.currentTimeMillis(); + } return true; } else if (amount > 0) { - EntryWidget.stackDisplayOffset = ((System.currentTimeMillis() + stackDisplayOffset) / 1000 + 1) * 1000; + for (EntryWidget slot : Widgets.walk(minecraft.screen.children(), EntryWidget.class::isInstance)) { + slot.getCyclingEntries().next(); + slot.lastCycleTime = System.currentTimeMillis(); + } return true; } } @@ -708,7 +718,7 @@ public class EntryWidget extends Slot implements DraggableStackProviderWidget { public DraggableStack getHoveredStack(DraggingContext context, double mouseX, double mouseY) { if (!getCurrentEntry().isEmpty() && containsMouse(mouseX, mouseY)) { return new DraggableStack() { - EntryStack stack = getCurrentEntry().copy() + final EntryStack stack = getCurrentEntry().copy() .removeSetting(EntryStack.Settings.RENDERER) .removeSetting(EntryStack.Settings.FLUID_RENDER_RATIO); @@ -750,7 +760,7 @@ public class EntryWidget extends Slot implements DraggableStackProviderWidget { category.setDetail("Highlight enabled", () -> String.valueOf(isHighlightEnabled())); category.setDetail("Tooltip enabled", () -> String.valueOf(isTooltipsEnabled())); category.setDetail("Background enabled", () -> String.valueOf(isBackgroundEnabled())); - category.setDetail("Entries count", () -> String.valueOf(entryStacks.size())); + category.setDetail("Entries count", () -> String.valueOf(getEntries().size())); EntryStack currentEntry = getCurrentEntry(); CrashReportCategory entryCategory = report.addCategory("Current Rendering Entry"); diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/util/AbstractIndexedCyclingList.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/util/AbstractIndexedCyclingList.java new file mode 100644 index 000000000..199afbb29 --- /dev/null +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/util/AbstractIndexedCyclingList.