From 8984313c6cd387dbb9448d5b1d824ab147a3b003 Mon Sep 17 00:00:00 2001 From: shedaniel Date: Sat, 20 Nov 2021 15:12:25 +0800 Subject: Allow filtering the displays with filtering rules --- .../rei/impl/client/config/ConfigObjectImpl.java | 7 ++ .../client/config/entries/FilteringScreen.java | 5 + .../client/entry/filtering/FilteringCache.java | 31 ++++++ .../client/entry/filtering/FilteringCacheImpl.java | 44 ++++++++ .../entry/filtering/FilteringContextImpl.java | 42 +++++--- .../impl/client/entry/filtering/FilteringRule.java | 6 +- .../entry/filtering/rules/ManualFilteringRule.java | 52 +++++++-- .../entry/filtering/rules/SearchFilteringRule.java | 3 +- .../client/gui/widget/EntryStacksRegionWidget.java | 2 +- .../search/argument/type/TooltipArgumentType.java | 24 +++-- .../impl/common/entry/type/EntryRegistryImpl.java | 15 ++- .../client/FilteredStacksVisibilityHandler.java | 116 +++++++++++++++++++++ .../client/runtime/DefaultClientRuntimePlugin.java | 12 +++ 13 files changed, 330 insertions(+), 29 deletions(-) create mode 100644 runtime/src/main/java/me/shedaniel/rei/impl/client/entry/filtering/FilteringCache.java create mode 100644 runtime/src/main/java/me/shedaniel/rei/impl/client/entry/filtering/FilteringCacheImpl.java create mode 100644 runtime/src/main/java/me/shedaniel/rei/plugin/client/FilteredStacksVisibilityHandler.java (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 73521ae30..65997aae0 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 @@ -319,6 +319,12 @@ public class ConfigObjectImpl implements ConfigObject, ConfigData { return advanced.filtering.filteredStacks; } + @ApiStatus.Experimental + @Override + public boolean shouldFilterDisplays() { + return advanced.filtering.shouldFilterDisplays; + } + @ApiStatus.Internal public List> getFilteringRules() { return advanced.filtering.filteringRules; @@ -584,6 +590,7 @@ public class ConfigObjectImpl implements ConfigObject, ConfigData { public static class Filtering { @UseFilteringScreen private List> filteredStacks = new ArrayList<>(); + public boolean shouldFilterDisplays = true; @ConfigEntry.Gui.Excluded public List> filteringRules = new ArrayList<>(); } } diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/config/entries/FilteringScreen.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/config/entries/FilteringScreen.java index 9026127f3..2d70af051 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/client/config/entries/FilteringScreen.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/config/entries/FilteringScreen.java @@ -41,6 +41,7 @@ import me.shedaniel.rei.api.client.gui.widgets.Tooltip; import me.shedaniel.rei.api.client.registry.entry.EntryRegistry; import me.shedaniel.rei.api.client.search.SearchFilter; import me.shedaniel.rei.api.client.search.SearchProvider; +import me.shedaniel.rei.api.common.entry.EntrySerializer; import me.shedaniel.rei.api.common.entry.EntryStack; import me.shedaniel.rei.impl.client.gui.ScreenOverlayImpl; import me.shedaniel.rei.impl.client.gui.widget.BatchedEntryRendererManager; @@ -320,6 +321,10 @@ public class FilteringScreen extends Screen { } public boolean matches(EntryStack stack) { + EntrySerializer serializer = stack.getDefinition().getSerializer(); + if (serializer == null || !serializer.supportReading() || !serializer.supportSaving()) { + return false; + } return lastFilter.test(stack); } diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/entry/filtering/FilteringCache.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/entry/filtering/FilteringCache.java new file mode 100644 index 000000000..d2779211a --- /dev/null +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/entry/filtering/FilteringCache.java @@ -0,0 +1,31 @@ +/* + * This file is licensed under the MIT License, part of Roughly Enough Items. + * Copyright (c) 2018, 2019, 2020, 2021 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.entry.filtering; + +import org.jetbrains.annotations.Nullable; + +public interface FilteringCache { + @Nullable + Object getCache(FilteringRule rule); +} diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/entry/filtering/FilteringCacheImpl.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/entry/filtering/FilteringCacheImpl.java new file mode 100644 index 000000000..cd4030fec --- /dev/null +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/entry/filtering/FilteringCacheImpl.java @@ -0,0 +1,44 @@ +/* + * This file is licensed under the MIT License, part of Roughly Enough Items. + * Copyright (c) 2018, 2019, 2020, 2021 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.entry.filtering; + +import org.jetbrains.annotations.Nullable; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +public class FilteringCacheImpl implements FilteringCache { + private final Map, Optional> CACHE = new HashMap<>(); + + @Override + @Nullable + public Object getCache(FilteringRule rule) { + return CACHE.getOrDefault(rule, Optional.empty()).orElse(null); + } + + public void setCache(FilteringRule rule, @Nullable Object value) { + CACHE.put(rule, Optional.ofNullable(value)); + } +} diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/entry/filtering/FilteringContextImpl.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/entry/filtering/FilteringContextImpl.java index 1d52db2d9..adda65797 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/client/entry/filtering/FilteringContextImpl.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/entry/filtering/FilteringContextImpl.java @@ -43,20 +43,28 @@ import java.util.concurrent.TimeoutException; @Environment(EnvType.CLIENT) public class FilteringContextImpl implements FilteringContext { + private final boolean async; public final Map> stacks; private final Map>> cachedStacks; public FilteringContextImpl(Collection> allStacks) { + this(true, allStacks); + } + + public FilteringContextImpl(boolean async, Collection> allStacks) { + this.async = async; this.stacks = Maps.newHashMap(); this.cachedStacks = Maps.newHashMap(); for (FilteringContextType type : FilteringContextType.values()) { this.stacks.computeIfAbsent(type, t -> Sets.newHashSet()); } - this.stacks.get(FilteringContextType.DEFAULT).addAll(CollectionUtils.mapParallel(allStacks, HashedEntryStackWrapper::new)); + this.stacks.get(FilteringContextType.DEFAULT).addAll(async ? CollectionUtils.mapParallel(allStacks, HashedEntryStackWrapper::new) + : CollectionUtils.map(allStacks, HashedEntryStackWrapper::new)); fillCache(); } public FilteringContextImpl(Map> stacks) { + this.async = false; this.stacks = stacks; this.cachedStacks = Maps.newHashMap(); for (FilteringContextType type : FilteringContextType.values()) { @@ -81,24 +89,34 @@ public class FilteringContextImpl implements FilteringContext { Collection hiddenStacks = result.getHiddenStacks(); Collection shownStacks = result.getShownStacks(); - List> completableFutures = Lists.newArrayList(); - completableFutures.add(CompletableFuture.runAsync(() -> { + if (async) { + List> completableFutures = Lists.newArrayList(); + completableFutures.add(CompletableFuture.runAsync(() -> { + this.stacks.get(FilteringContextType.DEFAULT).removeAll(hiddenStacks); + this.stacks.get(FilteringContextType.DEFAULT).removeAll(shownStacks); + })); + completableFutures.add(CompletableFuture.runAsync(() -> { + this.stacks.get(FilteringContextType.SHOWN).removeAll(hiddenStacks); + this.stacks.get(FilteringContextType.SHOWN).addAll(shownStacks); + })); + completableFutures.add(CompletableFuture.runAsync(() -> { + this.stacks.get(FilteringContextType.HIDDEN).addAll(hiddenStacks); + this.stacks.get(FilteringContextType.HIDDEN).removeAll(shownStacks); + })); + try { + CompletableFuture.allOf(completableFutures.toArray(new CompletableFuture[0])).get(20, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + e.printStackTrace(); + } + } else { this.stacks.get(FilteringContextType.DEFAULT).removeAll(hiddenStacks); this.stacks.get(FilteringContextType.DEFAULT).removeAll(shownStacks); - })); - completableFutures.add(CompletableFuture.runAsync(() -> { this.stacks.get(FilteringContextType.SHOWN).removeAll(hiddenStacks); this.stacks.get(FilteringContextType.SHOWN).addAll(shownStacks); - })); - completableFutures.add(CompletableFuture.runAsync(() -> { this.stacks.get(FilteringContextType.HIDDEN).addAll(hiddenStacks); this.stacks.get(FilteringContextType.HIDDEN).removeAll(shownStacks); - })); - try { - CompletableFuture.allOf(completableFutures.toArray(new CompletableFuture[0])).get(20, TimeUnit.SECONDS); - } catch (InterruptedException | ExecutionException | TimeoutException e) { - e.printStackTrace(); } + fillCache(); } } diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/entry/filtering/FilteringRule.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/entry/filtering/FilteringRule.java index 122e2e3f7..40593dc66 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/client/entry/filtering/FilteringRule.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/entry/filtering/FilteringRule.java @@ -66,7 +66,7 @@ public interface FilteringRule> { T createFromTag(CompoundTag tag); - FilteringResult processFilteredStacks(FilteringContext context); + FilteringResult processFilteredStacks(FilteringContext context, FilteringCache cache, boolean async); @ApiStatus.Internal default Optional> createEntryScreen() { @@ -81,5 +81,9 @@ public interface FilteringRule> { return Component.nullToEmpty(null); } + default Object prepareCache(boolean async) { + return null; + } + T createNew(); } diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/entry/filtering/rules/ManualFilteringRule.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/entry/filtering/rules/ManualFilteringRule.java index c8fb61998..8ba2e83fb 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/client/entry/filtering/rules/ManualFilteringRule.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/entry/filtering/rules/ManualFilteringRule.java @@ -23,6 +23,7 @@ package me.shedaniel.rei.impl.client.entry.filtering.rules; +import com.google.common.collect.Lists; import it.unimi.dsi.fastutil.longs.LongOpenHashSet; import it.unimi.dsi.fastutil.longs.LongSet; import me.shedaniel.rei.api.client.config.ConfigObject; @@ -31,6 +32,7 @@ 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.entry.filtering.AbstractFilteringRule; +import me.shedaniel.rei.impl.client.entry.filtering.FilteringCache; import me.shedaniel.rei.impl.client.entry.filtering.FilteringContext; import me.shedaniel.rei.impl.client.entry.filtering.FilteringResult; import net.minecraft.nbt.CompoundTag; @@ -38,6 +40,11 @@ import net.minecraft.network.chat.Component; import net.minecraft.network.chat.TranslatableComponent; import java.util.Collection; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import java.util.stream.Collectors; public class ManualFilteringRule extends AbstractFilteringRule { @@ -52,16 +59,49 @@ public class ManualFilteringRule extends AbstractFilteringRule> completableFutures = Lists.newArrayList(); + for (Iterable> partitionStacks : CollectionUtils.partition(ConfigObject.getInstance().getFilteredStackProviders(), 100)) { + completableFutures.add(CompletableFuture.supplyAsync(() -> { + LongSet output = new LongOpenHashSet(); + for (EntryStackProvider provider : partitionStacks) { + if (provider != null && provider.isValid()) { + output.add(EntryStacks.hashExact(provider.provide())); + } + } + return output; + })); + } + try { + CompletableFuture.allOf(completableFutures.toArray(new CompletableFuture[0])).get(10, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + e.printStackTrace(); + } + for (CompletableFuture future : completableFutures) { + LongSet now = future.getNow(null); + if (now != null) { + all.addAll(now); + } + } + return all; + } else { + return ConfigObject.getInstance().getFilteredStackProviders().stream().filter(EntryStackProvider::isValid).map(provider -> EntryStacks.hashExact(provider.provide())).collect(Collectors.toCollection(LongOpenHashSet::new)); + } + } + + @Override + public FilteringResult processFilteredStacks(FilteringContext context, FilteringCache cache, boolean async) { + LongSet filteredStacks = (LongSet) cache.getCache(this); FilteringResult result = FilteringResult.create(); - processList(context.getShownStacks(), result); - processList(context.getUnsetStacks(), result); + processList(context.getShownStacks(), result, async, filteredStacks); + processList(context.getUnsetStacks(), result, async, filteredStacks); return result; } - private void processList(Collection> stacks, FilteringResult result) { - LongSet filteredStacks = CollectionUtils.filterAndMapParallel(ConfigObject.getInstance().getFilteredStackProviders(), EntryStackProvider::isValid, provider -> EntryStacks.hashExact(provider.provide()), LongOpenHashSet::new); - result.hide(stacks.parallelStream().filter(stack -> filteredStacks.contains(EntryStacks.hashExact(stack))).collect(Collectors.toList())); + private void processList(Collection> stacks, FilteringResult result, boolean async, LongSet filteredStacks) { + result.hide((async ? stacks.parallelStream() : stacks.stream()).filter(stack -> filteredStacks.contains(EntryStacks.hashExact(stack))).collect(Collectors.toList())); } @Override diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/entry/filtering/rules/SearchFilteringRule.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/entry/filtering/rules/SearchFilteringRule.java index 9e00dec60..b1e9b122f 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/client/entry/filtering/rules/SearchFilteringRule.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/entry/filtering/rules/SearchFilteringRule.java @@ -31,6 +31,7 @@ import me.shedaniel.rei.api.common.util.CollectionUtils; import me.shedaniel.rei.impl.client.config.entries.FilteringEntry; import me.shedaniel.rei.impl.client.config.entries.FilteringRuleOptionsScreen; import me.shedaniel.rei.impl.client.entry.filtering.AbstractFilteringRule; +import me.shedaniel.rei.impl.client.entry.filtering.FilteringCache; import me.shedaniel.rei.impl.client.entry.filtering.FilteringContext; import me.shedaniel.rei.impl.client.entry.filtering.FilteringResult; import net.fabricmc.api.EnvType; @@ -79,7 +80,7 @@ public class SearchFilteringRule extends AbstractFilteringRule>>> completableFutures = Lists.newArrayList(); processList(context.getUnsetStacks(), completableFutures); if (show) processList(context.getHiddenStacks(), completableFutures); diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/EntryStacksRegionWidget.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/EntryStacksRegionWidget.java index c68c7bb46..509293bb9 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/EntryStacksRegionWidget.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/EntryStacksRegionWidget.java @@ -237,7 +237,7 @@ public class EntryStacksRegionWidget> extends WidgetWit public Optional> checkDraggedStacks(DraggingContext context, DraggableStack stack) { EntrySerializer serializer = stack.getStack().getDefinition().getSerializer(); - if (stack instanceof RegionDraggableStack || (serializer.supportReading() && serializer.supportSaving())) { + if (serializer != null && (stack instanceof RegionDraggableStack || (serializer.supportReading() && serializer.supportSaving()))) { try { T regionEntry = stack instanceof RegionDraggableStack ? ((RegionDraggableStack) stack).getEntry().getEntry().copy() : listener.convertDraggableStack(context, stack); diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/search/argument/type/TooltipArgumentType.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/search/argument/type/TooltipArgumentType.java index aa5e35e6a..f5b6a12ab 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/client/search/argument/type/TooltipArgumentType.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/search/argument/type/TooltipArgumentType.java @@ -39,6 +39,7 @@ import org.apache.commons.lang3.mutable.Mutable; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nullable; +import java.util.ConcurrentModificationException; import java.util.Locale; @ApiStatus.Internal @@ -71,18 +72,29 @@ public final class TooltipArgumentType extends ArgumentType { @Override public boolean matches(Mutable data, EntryStack stack, String searchText, Unit filterData) { if (data.getValue() == null) { - data.setValue(tryGetEntryStackTooltip(stack).toLowerCase(Locale.ROOT)); + String tooltip = tryGetEntryStackTooltip(stack, 0); + if (tooltip == null) return false; + data.setValue(tooltip.toLowerCase(Locale.ROOT)); } String tooltip = data.getValue(); return tooltip.isEmpty() || tooltip.contains(searchText); } - public static String tryGetEntryStackTooltip(EntryStack stack) { - Tooltip tooltip = stack.getTooltip(new Point(), false); - if (tooltip != null) { - return CollectionUtils.mapAndJoinToString(tooltip.getText(), Component::getString, "\n"); + @Nullable + public static String tryGetEntryStackTooltip(EntryStack stack, int attempt) { + try { + Tooltip tooltip = stack.getTooltip(new Point(), false); + if (tooltip != null) { + return CollectionUtils.mapAndJoinToString(tooltip.getText(), Component::getString, "\n"); + } + return ""; + } catch (ConcurrentModificationException ignored) { + // yes, this is a hack + if (attempt < 10) { + return tryGetEntryStackTooltip(stack, attempt + 1); + } + return null; } - return ""; } @Override diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/common/entry/type/EntryRegistryImpl.java b/runtime/src/main/java/me/shedaniel/rei/impl/common/entry/type/EntryRegistryImpl.java index 1c6e9d67f..346cbb8ea 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/common/entry/type/EntryRegistryImpl.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/common/entry/type/EntryRegistryImpl.java @@ -40,6 +40,7 @@ import me.shedaniel.rei.api.common.util.CollectionUtils; import me.shedaniel.rei.api.common.util.EntryStacks; import me.shedaniel.rei.impl.client.REIRuntimeImpl; import me.shedaniel.rei.impl.client.config.ConfigObjectImpl; +import me.shedaniel.rei.impl.client.entry.filtering.FilteringCacheImpl; import me.shedaniel.rei.impl.client.entry.filtering.FilteringContextImpl; import me.shedaniel.rei.impl.client.entry.filtering.FilteringContextType; import me.shedaniel.rei.impl.client.entry.filtering.FilteringRule; @@ -65,6 +66,7 @@ import java.util.stream.Stream; @ApiStatus.Internal @Environment(EnvType.CLIENT) public class EntryRegistryImpl implements EntryRegistry { + public List refilterListener = Lists.newCopyOnWriteArrayList(); private List> preFilteredList = Lists.newCopyOnWriteArrayList(); private List> entries = Lists.newCopyOnWriteArrayList(); @Nullable @@ -83,6 +85,7 @@ public class EntryRegistryImpl implements EntryRegistry { @Override public void startReload() { + refilterListener.clear(); entries = Lists.newCopyOnWriteArrayList(); reloadingRegistry = Lists.newArrayListWithCapacity(Registry.ITEM.keySet().size() + 100); preFilteredList = Lists.newCopyOnWriteArrayList(); @@ -131,12 +134,14 @@ public class EntryRegistryImpl implements EntryRegistry { Stopwatch stopwatch = Stopwatch.createStarted(); FilteringContextImpl context = new FilteringContextImpl(entries); + FilteringCacheImpl cache = new FilteringCacheImpl(); List> rules = ((ConfigObjectImpl) ConfigObject.getInstance()).getFilteringRules(); Stopwatch innerStopwatch = Stopwatch.createStarted(); for (int i = rules.size() - 1; i >= 0; i--) { innerStopwatch.reset().start(); FilteringRule rule = rules.get(i); - context.handleResult(rule.processFilteredStacks(context)); + cache.setCache(rule, rule.prepareCache(true)); + context.handleResult(rule.processFilteredStacks(context, cache, true)); RoughlyEnoughItemsCore.LOGGER.debug("Refiltered rule [%s] in %s.", FilteringRule.REGISTRY.getKey(rule).toString(), innerStopwatch.stop().toString()); } @@ -152,6 +157,10 @@ public class EntryRegistryImpl implements EntryRegistry { } RoughlyEnoughItemsCore.LOGGER.debug("Refiltered %d entries with %d rules in %s.", entries.size() - preFilteredList.size(), rules.size(), stopwatch.stop().toString()); + + for (Runnable runnable : refilterListener) { + runnable.run(); + } } private static Predicate not(Predicate target) { @@ -224,10 +233,12 @@ public class EntryRegistryImpl implements EntryRegistry { } FilteringContextImpl context = new FilteringContextImpl(entries); + FilteringCacheImpl cache = new FilteringCacheImpl(); List> rules = ((ConfigObjectImpl) ConfigObject.getInstance()).getFilteringRules(); for (int i = rules.size() - 1; i >= 0; i--) { FilteringRule rule = rules.get(i); - context.handleResult(rule.processFilteredStacks(context)); + cache.setCache(rule, rule.prepareCache(true)); + context.handleResult(rule.processFilteredStacks(context, cache, true)); } Set hiddenStacks = context.stacks.get(FilteringContextType.HIDDEN); diff --git a/runtime/src/main/java/me/shedaniel/rei/plugin/client/FilteredStacksVisibilityHandler.java b/runtime/src/main/java/me/shedaniel/rei/plugin/client/FilteredStacksVisibilityHandler.java new file mode 100644 index 000000000..50bc8f277 --- /dev/null +++ b/runtime/src/main/java/me/shedaniel/rei/plugin/client/FilteredStacksVisibilityHandler.java @@ -0,0 +1,116 @@ +/* + * This file is licensed under the MIT License, part of Roughly Enough Items. + * Copyright (c) 2018, 2019, 2020, 2021 shedaniel + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.shedaniel.rei.plugin.client; + +import com.google.common.base.Stopwatch; +import it.unimi.dsi.fastutil.objects.Reference2BooleanMap; +import it.unimi.dsi.fastutil.objects.Reference2BooleanMaps; +import it.unimi.dsi.fastutil.objects.Reference2BooleanOpenHashMap; +import me.shedaniel.architectury.event.EventResult; +import me.shedaniel.rei.RoughlyEnoughItemsCore; +import me.shedaniel.rei.api.client.config.ConfigObject; +import me.shedaniel.rei.api.client.registry.display.DisplayCategory; +import me.shedaniel.rei.api.client.registry.display.DisplayRegistry; +import me.shedaniel.rei.api.client.registry.display.visibility.DisplayVisibilityPredicate; +import me.shedaniel.rei.api.common.display.Display; +import me.shedaniel.rei.api.common.entry.EntryIngredient; +import me.shedaniel.rei.impl.client.config.ConfigObjectImpl; +import me.shedaniel.rei.impl.client.entry.filtering.*; + +import java.util.List; +import java.util.function.Predicate; + +public class FilteredStacksVisibilityHandler implements DisplayVisibilityPredicate { + private boolean checkHiddenStacks; + private Reference2BooleanMap visible = Reference2BooleanMaps.synchronize(new Reference2BooleanOpenHashMap<>()); + private List> filteringRules; + private FilteringCacheImpl cache; + private final Predicate displayPredicate = this::checkHiddenStacks; + + @Override + public EventResult handleDisplay(DisplayCategory category, Display display) { + if (checkHiddenStacks) { + return visible.computeBooleanIfAbsent(display, displayPredicate) ? EventResult.pass() : EventResult.interruptFalse(); + } + + return EventResult.pass(); + } + + public void reset() { + checkHiddenStacks = ConfigObject.getInstance().shouldFilterDisplays(); + visible = Reference2BooleanMaps.synchronize(new Reference2BooleanOpenHashMap<>()); + + if (checkHiddenStacks) { + filteringRules = ((ConfigObjectImpl) ConfigObject.getInstance()).getFilteringRules(); + cache = new FilteringCacheImpl(); + for (int i = filteringRules.size() - 1; i >= 0; i--) { + FilteringRule rule = filteringRules.get(i); + cache.setCache(rule, rule.prepareCache(false)); + } + + cacheExisting(); + } else { + filteringRules = null; + cache = null; + } + } + + public void cacheExisting() { + Stopwatch stopwatch = Stopwatch.createStarted(); + DisplayRegistry.getInstance().getAll().values().parallelStream().map(displays -> { + Reference2BooleanMap current = new Reference2BooleanOpenHashMap<>(); + for (Display display : displays) { + current.put(display, checkHiddenStacks(display)); + } + return current; + }).forEach(map -> { + visible.putAll(map); + }); + RoughlyEnoughItemsCore.LOGGER.debug("Computed existing filtered displays with %d rules in %s", filteringRules.size(), stopwatch.stop()); + } + + private boolean checkHiddenStacks(Display display) { + for (EntryIngredient ingredient : display.getInputEntries()) { + if (isEntryIngredientAllHidden(ingredient, cache, filteringRules)) { + return false; + } + } + for (EntryIngredient ingredient : display.getOutputEntries()) { + if (isEntryIngredientAllHidden(ingredient, cache, filteringRules)) { + return false; + } + } + + return true; + } + + private static boolean isEntryIngredientAllHidden(EntryIngredient ingredient, FilteringCache cache, List> rules) { + FilteringContextImpl context = new FilteringContextImpl(false, ingredient); + for (int i = rules.size() - 1; i >= 0; i--) { + FilteringRule rule = rules.get(i); + context.handleResult(rule.processFilteredStacks(context, cache, false)); + } + return context.stacks.get(FilteringContextType.SHOWN).isEmpty() && context.stacks.get(FilteringContextType.DEFAULT).isEmpty(); + } +} diff --git a/runtime/src/main/java/me/shedaniel/rei/plugin/client/runtime/DefaultClientRuntimePlugin.java b/runtime/src/main/java/me/shedaniel/rei/plugin/client/runtime/DefaultClientRuntimePlugin.java index 3a174ffa4..260da518c 100644 --- a/runtime/src/main/java/me/shedaniel/rei/plugin/client/runtime/DefaultClientRuntimePlugin.java +++ b/runtime/src/main/java/me/shedaniel/rei/plugin/client/runtime/DefaultClientRuntimePlugin.java @@ -44,6 +44,7 @@ import me.shedaniel.rei.api.client.gui.widgets.Panel; import me.shedaniel.rei.api.client.gui.widgets.Tooltip; import me.shedaniel.rei.api.client.gui.widgets.Widgets; import me.shedaniel.rei.api.client.plugins.REIClientPlugin; +import me.shedaniel.rei.api.client.registry.display.DisplayRegistry; import me.shedaniel.rei.api.client.registry.entry.EntryRegistry; import me.shedaniel.rei.api.client.registry.screen.DisplayBoundsProvider; import me.shedaniel.rei.api.client.registry.screen.ExclusionZones; @@ -62,6 +63,7 @@ import me.shedaniel.rei.impl.client.gui.ScreenOverlayImpl; import me.shedaniel.rei.impl.client.gui.screen.AbstractDisplayViewingScreen; import me.shedaniel.rei.impl.client.gui.screen.DefaultDisplayViewingScreen; import me.shedaniel.rei.impl.client.gui.widget.FavoritesListWidget; +import me.shedaniel.rei.impl.common.entry.type.EntryRegistryImpl; import me.shedaniel.rei.plugin.autocrafting.DefaultCategoryHandler; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; @@ -82,6 +84,8 @@ import java.util.function.Function; @Environment(EnvType.CLIENT) @ApiStatus.Internal public class DefaultClientRuntimePlugin implements REIClientPlugin { + private final FilteredStacksVisibilityHandler filteredStacksVisibilityHandler = new FilteredStacksVisibilityHandler(); + @SuppressWarnings("rawtypes") public DefaultClientRuntimePlugin() { PluginStageExecutionWatcher watcher = new PluginStageExecutionWatcher(); @@ -111,6 +115,14 @@ public class DefaultClientRuntimePlugin implements REIClientPlugin { } })); } + + ((EntryRegistryImpl) registry).refilterListener.add(filteredStacksVisibilityHandler::reset); + } + + @Override + public void registerDisplays(DisplayRegistry registry) { + filteredStacksVisibilityHandler.reset(); + registry.registerVisibilityPredicate(filteredStacksVisibilityHandler); } @Override -- cgit From 242b0a3d140dae6eadade91e0ee5bfbcf403f3bf Mon Sep 17 00:00:00 2001 From: shedaniel Date: Sat, 20 Nov 2021 15:14:09 +0800 Subject: Add support for custom types for JEI plugins --- .../me/shedaniel/rei/impl/common/plugins/PluginManagerImpl.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'runtime/src/main/java/me') diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/common/plugins/PluginManagerImpl.java b/runtime/src/main/java/me/shedaniel/rei/impl/common/plugins/PluginManagerImpl.java index bda591fb1..f0680cd47 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/common/plugins/PluginManagerImpl.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/common/plugins/PluginManagerImpl.java @@ -178,10 +178,13 @@ public class PluginManagerImpl

> implements PluginManager< try (SectionClosable section = section(stage, sectionName + wrapper.getPluginProviderName() + "/")) { if (reloadable == null || !wrapper.plugin.shouldBeForcefullyDoneOnMainThread(reloadable)) { consumer.accept(wrapper); - } else if (Platform.getEnvironment() == Env.CLIENT) { - EnvExecutor.runInEnv(Env.CLIENT, () -> () -> queueExecutionClient(() -> consumer.accept(wrapper))); } else { - queueExecution(() -> consumer.accept(wrapper)); + RoughlyEnoughItemsCore.LOGGER.warn("Forcing plugin " + wrapper.getPluginProviderName() + " to run on the main thread for " + sectionName + "! This is extremely dangerous, and have large performance implications."); + if (Platform.getEnvironment() == Env.CLIENT) { + EnvExecutor.runInEnv(Env.CLIENT, () -> () -> queueExecutionClient(() -> consumer.accept(wrapper))); + } else { + queueExecution(() -> consumer.accept(wrapper)); + } } } catch (Throwable throwable) { RoughlyEnoughItemsCore.LOGGER.error(wrapper.getPluginProviderName() + " plugin failed to " + sectionName + "!", throwable); -- cgit From b92284180f3c58361180f9e5fb1062ba5560466e Mon Sep 17 00:00:00 2001 From: shedaniel Date: Sat, 20 Nov 2021 15:15:43 +0800 Subject: Round Dispose here bounds --- .../shedaniel/rei/impl/client/gui/widget/FavoritesListWidget.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'runtime/src/main/java/me') diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/FavoritesListWidget.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/FavoritesListWidget.java index aec7a5950..95c050e76 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/FavoritesListWidget.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/FavoritesListWidget.java @@ -255,7 +255,7 @@ public class FavoritesListWidget extends WidgetWithBounds implements DraggableSt this.trashBoundsHeight.update(delta); double trashBoundsHeight = this.trashBoundsHeight.value(); - if (trashBoundsHeight > 0) { + if (Math.round(trashBoundsHeight) > 0) { double trashBoundsHeightTarget = Math.min(150D, fullBounds.height * 0.15D); double progress = Math.pow(Mth.clamp(trashBoundsHeight / trashBoundsHeightTarget, 0, 1), 7); int y = this.fullBounds.getMaxY() - 4 - favoritePanel.getBounds().height; @@ -289,9 +289,9 @@ public class FavoritesListWidget extends WidgetWithBounds implements DraggableSt systemHeight += 4; } if (favoritePanel.getBounds().height > 20) - region.getBounds().setBounds(this.fullBounds.x, this.fullBounds.y + systemHeight, this.fullBounds.width, this.fullBounds.height - systemHeight - (this.fullBounds.getMaxY() - this.favoritePanel.bounds.y) - 4 - (trashBoundsHeight <= 0 ? 0 : trashBoundsHeight)); + region.getBounds().setBounds(this.fullBounds.x, this.fullBounds.y + systemHeight, this.fullBounds.width, this.fullBounds.height - systemHeight - (this.fullBounds.getMaxY() - this.favoritePanel.bounds.y) - 4 - (Math.round(trashBoundsHeight) <= 0 ? 0 : trashBoundsHeight)); else - region.getBounds().setBounds(this.fullBounds.x, this.fullBounds.y + systemHeight, this.fullBounds.width, this.fullBounds.height - systemHeight - (trashBoundsHeight <= 0 ? 0 : trashBoundsHeight + 24)); + region.getBounds().setBounds(this.fullBounds.x, this.fullBounds.y + systemHeight, this.fullBounds.width, this.fullBounds.height - systemHeight - (Math.round(trashBoundsHeight) <= 0 ? 0 : trashBoundsHeight + 24)); systemRegion.render(matrices, mouseX, mouseY, delta); region.render(matrices, mouseX, mouseY, delta); renderAddFavorite(matrices, mouseX, mouseY, delta); -- cgit From 69e26e78b69f7eeb745b604b2347ac30fe32666f Mon Sep 17 00:00:00 2001 From: shedaniel Date: Sat, 20 Nov 2021 17:50:10 +0800 Subject: Rewrite on how TransferHandler render errors, and fix #580 --- .../rei/impl/client/ClientHelperImpl.java | 9 - .../impl/client/gui/widget/InternalWidgets.java | 203 +++++++++++---------- .../search/argument/type/TooltipArgumentType.java | 19 +- .../rei/impl/common/transfer/InputSlotCrafter.java | 2 +- .../autocrafting/DefaultCategoryHandler.java | 37 +++- 5 files changed, 155 insertions(+), 115 deletions(-) (limited to 'runtime/src/main/java/me') diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/ClientHelperImpl.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/ClientHelperImpl.java index ee054dcd8..227389f37 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/client/ClientHelperImpl.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/ClientHelperImpl.java @@ -74,15 +74,6 @@ import java.util.function.Supplier; @ApiStatus.Internal @Environment(EnvType.CLIENT) public class ClientHelperImpl implements ClientHelper { - @ApiStatus.Internal - public final LazyLoadedValue isYog = new LazyLoadedValue<>(() -> { - try { - if (Minecraft.getInstance().getUser().getGameProfile().getId().equals(UUID.fromString("f9546389-9415-4358-9c29-2c26b25bff5b"))) - return true; - } catch (Throwable ignored) { - } - return false; - }); @ApiStatus.Internal public final LazyLoadedValue isAprilFools = new LazyLoadedValue<>(() -> { try { diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/InternalWidgets.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/InternalWidgets.java index 62b5960ad..5a9712575 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/InternalWidgets.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/gui/widget/InternalWidgets.java @@ -25,7 +25,6 @@ package me.shedaniel.rei.impl.client.gui.widget; import com.google.common.collect.Lists; import com.mojang.blaze3d.vertex.PoseStack; -import it.unimi.dsi.fastutil.ints.IntList; import me.shedaniel.math.Point; import me.shedaniel.math.Rectangle; import me.shedaniel.math.impl.PointHelper; @@ -34,13 +33,12 @@ import me.shedaniel.rei.api.client.config.ConfigObject; import me.shedaniel.rei.api.client.gui.DrawableConsumer; import me.shedaniel.rei.api.client.gui.widgets.*; import me.shedaniel.rei.api.client.registry.display.DisplayCategory; -import me.shedaniel.rei.api.client.registry.display.TransferDisplayCategory; import me.shedaniel.rei.api.client.registry.transfer.TransferHandler; +import me.shedaniel.rei.api.client.registry.transfer.TransferHandlerErrorRenderer; import me.shedaniel.rei.api.client.registry.transfer.TransferHandlerRegistry; import me.shedaniel.rei.api.common.display.Display; import me.shedaniel.rei.api.common.util.CollectionUtils; import me.shedaniel.rei.impl.ClientInternals; -import me.shedaniel.rei.impl.client.ClientHelperImpl; import me.shedaniel.rei.impl.client.gui.toast.CopyRecipeIdentifierToast; import me.shedaniel.rei.impl.client.gui.widget.basewidgets.*; import net.fabricmc.api.EnvType; @@ -48,14 +46,12 @@ import net.fabricmc.api.Environment; import net.minecraft.ChatFormatting; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.components.events.GuiEventListener; -import net.minecraft.client.gui.screens.Screen; import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; import net.minecraft.client.resources.language.I18n; -import net.minecraft.network.chat.Component; -import net.minecraft.network.chat.FormattedText; -import net.minecraft.network.chat.TextComponent; -import net.minecraft.network.chat.TranslatableComponent; +import net.minecraft.network.chat.*; import net.minecraft.resources.ResourceLocation; +import org.apache.commons.lang3.mutable.Mutable; +import org.apache.commons.lang3.mutable.MutableObject; import org.jetbrains.annotations.ApiStatus; import java.util.ArrayList; @@ -71,8 +67,7 @@ public final class InternalWidgets { public static Widget createAutoCraftingButtonWidget(Rectangle displayBounds, Rectangle rectangle, Component text, Supplier displaySupplier, Supplier> idsSupplier, List setupDisplay, DisplayCategory category) { AbstractContainerScreen containerScreen = REIRuntime.getInstance().getPreviousContainerScreen(); - boolean[] visible = {false}; - List[] errorTooltip = new List[]{null}; + Mutable> errorTooltip = new MutableObject<>(new ArrayList<>()); Button autoCraftingButton = Widgets.createButton(rectangle, text) .focusable(false) .onClick(button -> { @@ -92,93 +87,10 @@ public final class InternalWidgets { Minecraft.getInstance().setScreen(containerScreen); REIRuntime.getInstance().getOverlay().get().queueReloadOverlay(); }) - .onRender((matrices, button) -> { - button.setEnabled(false); - if (containerScreen == null) { - button.setTint(0); - return; - } - List error = null; - int color = 0; - visible[0] = false; - IntList redSlots = null; - TransferHandler.Context context = TransferHandler.Context.create(false, containerScreen, displaySupplier.get()); - for (TransferHandler transferHandler : TransferHandlerRegistry.getInstance()) { - try { - TransferHandler.Result result = transferHandler.handle(context); - if (result.isApplicable()) { - visible[0] = true; - } - if (result.isSuccessful()) { - button.setEnabled(true); - error = null; - color = result.getColor(); - redSlots = null; - } else if (result.isApplicable()) { - if (error == null) { - error = Lists.newArrayList(); - } - error.add(result.getError()); - color = result.getColor(); - if (result.getIntegers() != null && !result.getIntegers().isEmpty()) - redSlots = result.getIntegers(); - } - - if (result.isBlocking()) break; - } catch (Exception e) { - e.printStackTrace(); - } - } - if (!visible[0]) { - button.setEnabled(false); - if (error == null) { - error = Lists.newArrayList(); - } else { - error.clear(); - } - error.add(new TranslatableComponent("error.rei.no.handlers.applicable")); - } - if ((button.containsMouse(PointHelper.ofMouse()) || button.isFocused()) && category instanceof TransferDisplayCategory && redSlots != null) { - ((TransferDisplayCategory) category).renderRedSlots(matrices, setupDisplay, displayBounds, displaySupplier.get(), redSlots); - } - errorTooltip[0] = error == null || error.isEmpty() ? null : Lists.newArrayList(); - if (errorTooltip[0] != null) { - for (Component s : error) { - if (!CollectionUtils.anyMatch(errorTooltip[0], ss -> ss.getString().equalsIgnoreCase(s.getString()))) { - errorTooltip[0].add(s); - } - } - } - button.setTint(color); - }) - .textColor((button, mouse) -> { - if (!visible[0]) { - return 10526880; - } else if (button.isEnabled() && (button.containsMouse(mouse) || button.isFocused())) { - return 16777120; - } - return 14737632; - }) - .textureId((button, mouse) -> !visible[0] ? 0 : (button.containsMouse(mouse) || button.isFocused()) && button.isEnabled() ? 4 : 1) .tooltipSupplier(button -> { - List str = new ArrayList<>(); - if (errorTooltip[0] == null) { - if (ClientHelperImpl.getInstance().isYog.get()) { - str.add(new TranslatableComponent("text.auto_craft.move_items.yog")); - } else { - str.add(new TranslatableComponent("text.auto_craft.move_items")); - } - } else { - if (errorTooltip[0].size() > 1) { - str.add(new TranslatableComponent("error.rei.multi.errors").withStyle(ChatFormatting.RED)); - for (Component component : errorTooltip[0]) { - str.add(new TranslatableComponent("- ").withStyle(ChatFormatting.RED).append(component.copy().withStyle(ChatFormatting.RED))); - } - } else if (errorTooltip[0].size() == 1) { - str.add(errorTooltip[0].get(0).copy().withStyle(ChatFormatting.RED)); - } - } - if (Minecraft.getInstance().options.advancedItemTooltips || Screen.hasShiftDown()) { + List str = new ArrayList<>(errorTooltip.getValue()); + + if (Minecraft.getInstance().options.advancedItemTooltips) { Collection locations = idsSupplier.get(); if (!locations.isEmpty()) { str.add(new TextComponent(" ")); @@ -194,9 +106,104 @@ public final class InternalWidgets { return str.toArray(new Component[0]); }); return new DelegateWidget(autoCraftingButton) { + boolean didJustRender = false; + @Override + public void render(PoseStack poses, int mouseX, int mouseY, float delta) { + didJustRender = false; + autoCraftingButton.setEnabled(false); + autoCraftingButton.setTint(0); + + if (containerScreen == null) { + errorTooltip.setValue(Lists.newArrayList(new TranslatableComponent("error.rei.not.supported.move.items").withStyle(ChatFormatting.RED))); + renderIf(false, poses, mouseX, mouseY, delta); + return; + } + + List errors = new ArrayList<>(); + boolean hasApplicable = false; + TransferHandlerErrorRenderer errorRenderer = null; + TransferHandler.Context context = TransferHandler.Context.create(false, containerScreen, displaySupplier.get()); + for (TransferHandler transferHandler : TransferHandlerRegistry.getInstance()) { + try { + TransferHandler.Result result = transferHandler.handle(context); + if (result.isApplicable()) { + hasApplicable = true; + autoCraftingButton.setTint(result.getColor()); + + if (result.isSuccessful()) { + errors.clear(); + autoCraftingButton.setEnabled(true); + errorRenderer = null; + break; + } + + errors.add(result); + TransferHandlerErrorRenderer transferHandlerErrorRenderer = result.getErrorRenderer(transferHandler, context); + if (transferHandlerErrorRenderer != null) { + errorRenderer = transferHandlerErrorRenderer; + } + + if (result.isBlocking()) { + break; + } + } + } catch (Throwable e) { + e.printStackTrace(); + } + } + + if (!hasApplicable) { + errorTooltip.setValue(Lists.newArrayList(new TranslatableComponent("error.rei.not.supported.move.items").withStyle(ChatFormatting.RED))); + renderIf(false, poses, mouseX, mouseY, delta); + return; + } + + if ((containsMouse(mouseX, mouseY) || autoCraftingButton.isFocused()) && errorRenderer != null) { + errorRenderer.render(poses, mouseX, mouseY, delta, setupDisplay, displayBounds, displaySupplier.get()); + } + if (errors.isEmpty()) { + errorTooltip.setValue(Lists.newArrayList(new TranslatableComponent("text.auto_craft.move_items"))); + } else { + errorTooltip.setValue(Lists.newArrayList()); + List tooltipsFilled = new ArrayList<>(); + for (TransferHandler.Result error : errors) { + error.fillTooltip(tooltipsFilled); + } + + if (errors.size() == 1) { + for (Component tooltipFilled : tooltipsFilled) { + MutableComponent colored = tooltipFilled.copy().withStyle(ChatFormatting.RED); + if (!CollectionUtils.anyMatch(errorTooltip.getValue(), ss -> ss.getString().equalsIgnoreCase(tooltipFilled.getString()))) { + errorTooltip.getValue().add(colored); + } + } + } else { + errorTooltip.getValue().add(new TranslatableComponent("error.rei.multi.errors").withStyle(ChatFormatting.RED)); + for (Component tooltipFilled : tooltipsFilled) { + MutableComponent colored = new TextComponent("- ").withStyle(ChatFormatting.RED) + .append(tooltipFilled.copy().withStyle(ChatFormatting.RED)); + if (!CollectionUtils.anyMatch(errorTooltip.getValue(), ss -> ss.getString().equalsIgnoreCase(colored.getString()))) { + errorTooltip.getValue().add(colored); + } + } + } + } + renderIf(true, poses, mouseX, mouseY, delta); + } + + private void renderIf(boolean should, PoseStack poseStack, int mouseX, int mouseY, float delta) { + if (should) { + didJustRender = true; + this.widget.render(poseStack, mouseX, mouseY, delta); + } else if (Minecraft.getInstance().options.advancedItemTooltips) { + didJustRender = true; + this.widget.render(poseStack, mouseX, mouseY, delta); + } + } + @Override public boolean keyPressed(int keyCode, int scanCode, int modifiers) { - if (displaySupplier.get().getDisplayLocation().isPresent() && ConfigObject.getInstance().getCopyRecipeIdentifierKeybind().matchesKey(keyCode, scanCode) && containsMouse(PointHelper.ofMouse())) { + if (didJustRender && displaySupplier.get().getDisplayLocation().isPresent() && ConfigObject.getInstance().getCopyRecipeIdentifierKeybind().matchesKey(keyCode, scanCode) && containsMouse(PointHelper.ofMouse())) { minecraft.keyboardHandler.setClipboard(displaySupplier.get().getDisplayLocation().get().toString()); if (ConfigObject.getInstance().isToastDisplayedOnCopyIdentifier()) { CopyRecipeIdentifierToast.addToast(I18n.get("msg.rei.copied_recipe_id"), I18n.get("msg.rei.recipe_id_details", displaySupplier.get().getDisplayLocation().get().toString())); @@ -208,7 +215,7 @@ public final class InternalWidgets { @Override public boolean mouseClicked(double mouseX, double mouseY, int button) { - if (displaySupplier.get().getDisplayLocation().isPresent() && ConfigObject.getInstance().getCopyRecipeIdentifierKeybind().matchesMouse(button) && containsMouse(PointHelper.ofMouse())) { + if (didJustRender && displaySupplier.get().getDisplayLocation().isPresent() && ConfigObject.getInstance().getCopyRecipeIdentifierKeybind().matchesMouse(button) && containsMouse(PointHelper.ofMouse())) { minecraft.keyboardHandler.setClipboard(displaySupplier.get().getDisplayLocation().get().toString()); if (ConfigObject.getInstance().isToastDisplayedOnCopyIdentifier()) { CopyRecipeIdentifierToast.addToast(I18n.get("msg.rei.copied_recipe_id"), I18n.get("msg.rei.recipe_id_details", displaySupplier.get().getDisplayLocation().get().toString())); diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/search/argument/type/TooltipArgumentType.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/search/argument/type/TooltipArgumentType.java index f5b6a12ab..9dfbdb525 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/client/search/argument/type/TooltipArgumentType.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/search/argument/type/TooltipArgumentType.java @@ -88,12 +88,21 @@ public final class TooltipArgumentType extends ArgumentType { return CollectionUtils.mapAndJoinToString(tooltip.getText(), Component::getString, "\n"); } return ""; - } catch (ConcurrentModificationException ignored) { - // yes, this is a hack - if (attempt < 10) { - return tryGetEntryStackTooltip(stack, attempt + 1); + } catch (Throwable throwable) { + Throwable temp = throwable; + while (temp != null) { + temp = temp.getCause(); + if (temp instanceof ConcurrentModificationException) { + // yes, this is a hack + if (attempt < 10) { + return tryGetEntryStackTooltip(stack, attempt + 1); + } + + return null; + } } - return null; + + throw throwable; } } diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/common/transfer/InputSlotCrafter.java b/runtime/src/main/java/me/shedaniel/rei/impl/common/transfer/InputSlotCrafter.java index aa233ca85..84728d321 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/common/transfer/InputSlotCrafter.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/common/transfer/InputSlotCrafter.java @@ -88,7 +88,7 @@ public class InputSlotCrafter ingredients = NonNullList.create(); - for (List itemStacks : this.menuInfo.getInputs(this)) { + for (List itemStacks : this.menuInfo.getInputs(this, true)) { ingredients.add(CollectionUtils.toIngredient(itemStacks)); } diff --git a/runtime/src/main/java/me/shedaniel/rei/plugin/autocrafting/DefaultCategoryHandler.java b/runtime/src/main/java/me/shedaniel/rei/plugin/autocrafting/DefaultCategoryHandler.java index 1c9703d5e..ea4a98be7 100644 --- a/runtime/src/main/java/me/shedaniel/rei/plugin/autocrafting/DefaultCategoryHandler.java +++ b/runtime/src/main/java/me/shedaniel/rei/plugin/autocrafting/DefaultCategoryHandler.java @@ -30,6 +30,7 @@ import it.unimi.dsi.fastutil.ints.IntList; import me.shedaniel.rei.RoughlyEnoughItemsNetwork; import me.shedaniel.rei.api.client.ClientHelper; import me.shedaniel.rei.api.client.registry.transfer.TransferHandler; +import me.shedaniel.rei.api.client.registry.transfer.TransferHandlerErrorRenderer; import me.shedaniel.rei.api.common.category.CategoryIdentifier; import me.shedaniel.rei.api.common.display.Display; import me.shedaniel.rei.api.common.transfer.RecipeFinder; @@ -48,11 +49,26 @@ import net.minecraft.network.chat.TranslatableComponent; import net.minecraft.world.entity.player.Player; import net.minecraft.world.inventory.AbstractContainerMenu; import net.minecraft.world.item.ItemStack; +import org.jetbrains.annotations.Nullable; import java.util.List; @Environment(EnvType.CLIENT) public class DefaultCategoryHandler implements TransferHandler { + private static class ErrorData { + private MenuInfoContext menuInfoContext; + private MenuInfo menuInfo; + private List> inputs; + private IntList intList; + + public ErrorData(MenuInfoContext menuInfoContext, MenuInfo menuInfo, List> inputs, IntList intList) { + this.menuInfoContext = menuInfoContext; + this.menuInfo = menuInfo; + this.inputs = inputs; + this.intList = intList; + } + } + @Override public Result handle(Context context) { Display display = context.getDisplay(); @@ -75,10 +91,10 @@ public class DefaultCategoryHandler implements TransferHandler { return Result.createNotApplicable(); } } - List> input = menuInfo.getInputs(menuInfoContext); + List> input = menuInfo.getInputs(menuInfoContext, false); IntList intList = hasItems(menu, menuInfo, display, input); if (!intList.isEmpty()) { - return Result.createFailed(new TranslatableComponent("error.rei.not.enough.materials"), intList); + return Result.createFailed(new TranslatableComponent("error.rei.not.enough.materials")).errorRenderer(new ErrorData(menuInfoContext, menuInfo, input, intList)); } if (!ClientHelper.getInstance().canUseMovePackets()) { return Result.createFailed(new TranslatableComponent("error.rei.not.on.server")); @@ -100,6 +116,23 @@ public class DefaultCategoryHandler implements TransferHandler { return Result.createSuccessful(); } + @Override + @Environment(EnvType.CLIENT) + @Nullable + public TransferHandlerErrorRenderer provideErrorRenderer(Context context, Object data) { + if (data instanceof ErrorData) { + MenuInfoContext menuInfoContext = ((ErrorData) data).menuInfoContext; + MenuInfo menuInfo = (