From 2123544fcf3ad7ec6d89e671a993a75737e3a44e Mon Sep 17 00:00:00 2001 From: shedaniel Date: Sun, 27 Nov 2022 17:50:11 +0800 Subject: Make filtering more immediate --- .../config/entries/FilteringRulesScreen.java | 20 +- .../entry/filtering/FilteringContextImpl.java | 51 +++- .../filtering/rules/BasicFilteringRuleImpl.java | 199 ++++++++++----- .../impl/common/entry/type/EntryRegistryImpl.java | 73 +++--- .../impl/common/entry/type/EntryRegistryList.java | 13 +- .../common/entry/type/EntryRegistryListImpl.java | 206 ++++++++++++++++ .../common/entry/type/EntryRegistryListener.java | 6 +- .../impl/common/entry/type/FilteredEntryList.java | 43 ++++ .../rei/impl/common/entry/type/FilteringLogic.java | 107 +++++++++ .../common/entry/type/NormalEntryRegistryList.java | 115 --------- .../common/entry/type/PreFilteredEntryList.java | 266 +++++++++++++-------- .../entry/type/ReloadingEntryRegistryList.java | 107 --------- .../client/runtime/DefaultClientRuntimePlugin.java | 3 +- .../shedaniel/rei/plugin/test/REITestPlugin.java | 42 +++- 14 files changed, 812 insertions(+), 439 deletions(-) create mode 100644 runtime/src/main/java/me/shedaniel/rei/impl/common/entry/type/EntryRegistryListImpl.java create mode 100644 runtime/src/main/java/me/shedaniel/rei/impl/common/entry/type/FilteredEntryList.java create mode 100644 runtime/src/main/java/me/shedaniel/rei/impl/common/entry/type/FilteringLogic.java delete mode 100644 runtime/src/main/java/me/shedaniel/rei/impl/common/entry/type/NormalEntryRegistryList.java delete mode 100644 runtime/src/main/java/me/shedaniel/rei/impl/common/entry/type/ReloadingEntryRegistryList.java (limited to 'runtime/src/main/java') diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/config/entries/FilteringRulesScreen.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/config/entries/FilteringRulesScreen.java index 624ba0315..6c6d4cceb 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/client/config/entries/FilteringRulesScreen.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/config/entries/FilteringRulesScreen.java @@ -33,12 +33,13 @@ import me.shedaniel.rei.api.client.entry.filtering.FilteringRuleType; import me.shedaniel.rei.api.client.gui.widgets.Widgets; import me.shedaniel.rei.api.client.registry.entry.EntryRegistry; import me.shedaniel.rei.api.common.util.CollectionUtils; -import me.shedaniel.rei.impl.client.entry.filtering.FilteringContextImpl; -import me.shedaniel.rei.impl.client.entry.filtering.FilteringResultImpl; +import me.shedaniel.rei.impl.client.entry.filtering.FilteringContextType; import me.shedaniel.rei.impl.client.entry.filtering.rules.ManualFilteringRule; import me.shedaniel.rei.impl.client.entry.filtering.rules.SearchFilteringRuleType; import me.shedaniel.rei.impl.client.gui.InternalTextures; import me.shedaniel.rei.impl.client.gui.widget.EntryWidget; +import me.shedaniel.rei.impl.common.entry.type.FilteringLogic; +import me.shedaniel.rei.impl.common.util.HashedEntryStackWrapper; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.components.Button; import net.minecraft.client.gui.components.events.GuiEventListener; @@ -54,6 +55,7 @@ import net.minecraft.sounds.SoundEvents; import java.util.*; import java.util.function.Consumer; import java.util.function.Function; +import java.util.stream.Collectors; public class FilteringRulesScreen extends Screen { private final FilteringEntry entry; @@ -268,21 +270,19 @@ public class FilteringRulesScreen extends Screen { public void addEntries(Consumer entryConsumer) { addEmpty(entryConsumer, 10); Function function = bool -> { - return new TranslatableComponent("rule.roughlyenoughitems.filtering.search.show." + bool); + return Component.translatable("rule.roughlyenoughitems.filtering.search.show." + bool); }; - FilteringContextImpl context = new FilteringContextImpl(EntryRegistry.getInstance().getEntryStacks().toList()); - rule.processFilteredStacks(context, () -> new FilteringResultImpl(new ArrayList<>(), new ArrayList<>()), - rule.prepareCache(false), false); + Map> stacks = FilteringLogic.hidden(FilteringLogic.getRules(), false, false, EntryRegistry.getInstance().getEntryStacks().collect(Collectors.toList())); entryConsumer.accept(new SubRulesEntry(rule, () -> function.apply(true), Collections.singletonList(new SearchFilteringRuleType.EntryStacksRuleEntry(rule, - Suppliers.ofInstance(CollectionUtils.map(context.getShownStacks(), - stack -> (EntryWidget) Widgets.createSlot(new Rectangle(0, 0, 18, 18)).disableBackground().entry(stack.normalize()))))))); + Suppliers.ofInstance(CollectionUtils.map(stacks.get(FilteringContextType.SHOWN), + stack -> (EntryWidget) Widgets.createSlot(new Rectangle(0, 0, 18, 18)).disableBackground().entry(stack.unwrap().normalize()))))))); addEmpty(entryConsumer, 10); entryConsumer.accept(new SubRulesEntry(rule, () -> function.apply(false), Collections.singletonList(new SearchFilteringRuleType.EntryStacksRuleEntry(rule, - Suppliers.ofInstance(CollectionUtils.map(context.getHiddenStacks(), - stack -> (EntryWidget) Widgets.createSlot(new Rectangle(0, 0, 18, 18)).disableBackground().entry(stack.normalize()))))))); + Suppliers.ofInstance(CollectionUtils.map(stacks.get(FilteringContextType.HIDDEN), + stack -> (EntryWidget) Widgets.createSlot(new Rectangle(0, 0, 18, 18)).disableBackground().entry(stack.unwrap().normalize()))))))); } @Override 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 d0e26a9fc..697bcde63 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 @@ -27,6 +27,7 @@ import com.google.common.collect.Iterators; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; +import it.unimi.dsi.fastutil.longs.*; import me.shedaniel.rei.api.client.entry.filtering.FilteringContext; import me.shedaniel.rei.api.common.entry.EntryStack; import me.shedaniel.rei.api.common.util.CollectionUtils; @@ -69,20 +70,35 @@ public class FilteringContextImpl implements FilteringContext { @Override public Collection> getHiddenStacks() { - return getPublicFacing(FilteringContextType.HIDDEN); + return getStacksFacing(FilteringContextType.HIDDEN); } @Override public Collection> getShownStacks() { - return getPublicFacing(FilteringContextType.SHOWN); + return getStacksFacing(FilteringContextType.SHOWN); } @Override public Collection> getUnsetStacks() { - return getPublicFacing(FilteringContextType.DEFAULT); + return getStacksFacing(FilteringContextType.DEFAULT); } - private Collection> getPublicFacing(FilteringContextType type) { + @Override + public LongCollection getHiddenExactHashes() { + return getHashesFacing(FilteringContextType.HIDDEN); + } + + @Override + public LongCollection getShownExactHashes() { + return getHashesFacing(FilteringContextType.SHOWN); + } + + @Override + public LongCollection getUnsetExactHashes() { + return getHashesFacing(FilteringContextType.DEFAULT); + } + + private Collection> getStacksFacing(FilteringContextType type) { Set wrappers = this.stacks.get(type); if (wrappers == null || wrappers.isEmpty()) return List.of(); return new AbstractSet<>() { @@ -98,6 +114,33 @@ public class FilteringContextImpl implements FilteringContext { }; } + private LongCollection getHashesFacing(FilteringContextType type) { + Set wrappers = this.stacks.get(type); + if (wrappers == null || wrappers.isEmpty()) return LongSets.emptySet(); + return new AbstractLongSet() { + @Override + public LongIterator iterator() { + Iterator iterator = wrappers.iterator(); + return new AbstractLongIterator() { + @Override + public long nextLong() { + return iterator.next().hashExact(); + } + + @Override + public boolean hasNext() { + return iterator.hasNext(); + } + }; + } + + @Override + public int size() { + return wrappers.size(); + } + }; + } + public void handleResult(FilteringResultImpl result) { Collection hiddenStacks = result.hiddenStacks; Collection shownStacks = result.shownStacks; diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/client/entry/filtering/rules/BasicFilteringRuleImpl.java b/runtime/src/main/java/me/shedaniel/rei/impl/client/entry/filtering/rules/BasicFilteringRuleImpl.java index 1d51eb0f2..81aa7b3ab 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/client/entry/filtering/rules/BasicFilteringRuleImpl.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/client/entry/filtering/rules/BasicFilteringRuleImpl.java @@ -23,120 +23,162 @@ package me.shedaniel.rei.impl.client.entry.filtering.rules; -import com.google.common.collect.Lists; import com.mojang.datafixers.util.Pair; +import com.mojang.datafixers.util.Unit; +import it.unimi.dsi.fastutil.longs.LongCollection; +import it.unimi.dsi.fastutil.longs.LongIterator; import it.unimi.dsi.fastutil.longs.LongOpenHashSet; import it.unimi.dsi.fastutil.longs.LongSet; +import me.shedaniel.clothconfig2.api.LazyResettable; import me.shedaniel.rei.api.client.entry.filtering.*; import me.shedaniel.rei.api.client.entry.filtering.base.BasicFilteringRule; import me.shedaniel.rei.api.client.plugins.REIClientPlugin; import me.shedaniel.rei.api.common.entry.EntryStack; import me.shedaniel.rei.api.common.registry.ReloadStage; -import me.shedaniel.rei.api.common.util.CollectionUtils; import me.shedaniel.rei.api.common.util.EntryStacks; import me.shedaniel.rei.impl.client.util.ThreadCreator; -import org.jetbrains.annotations.NotNull; import java.util.ArrayList; import java.util.Collection; import java.util.List; -import java.util.concurrent.*; +import java.util.concurrent.ExecutorService; +import java.util.function.Supplier; import java.util.stream.Collectors; -public enum BasicFilteringRuleImpl implements BasicFilteringRule> { +public enum BasicFilteringRuleImpl implements BasicFilteringRule { INSTANCE; private static final ExecutorService EXECUTOR_SERVICE = new ThreadCreator("REI-BasicFiltering").asService(); - private final List> hidden = new ArrayList<>(), shown = new ArrayList<>(); + private final LongSet hiddenHashes = new LongOpenHashSet(), shownHashes = new LongOpenHashSet(); + private final List hiddenProviders = new ArrayList<>(), shownProviders = new ArrayList<>(); @Override - public FilteringRuleType>> getType() { + public FilteringRuleType> getType() { return BasicFilteringRuleType.INSTANCE; } @Override - public Pair prepareCache(boolean async) { - return new Pair<>(prepareCacheFor(hidden, async), prepareCacheFor(shown, async)); - } - - @NotNull - private static LongSet prepareCacheFor(List> stacks, boolean async) { - if (async) { - LongSet all = new LongOpenHashSet(); - List> completableFutures = Lists.newArrayList(); - for (Iterable> partitionStacks : CollectionUtils.partition(stacks, 100)) { - completableFutures.add(CompletableFuture.supplyAsync(() -> { - LongSet output = new LongOpenHashSet(); - for (EntryStack stack : partitionStacks) { - if (stack != null && !stack.isEmpty()) { - output.add(EntryStacks.hashExact(stack)); - } - } - return output; - }, EXECUTOR_SERVICE)); - } - try { - CompletableFuture.allOf(completableFutures.toArray(new CompletableFuture[0])).get(5, TimeUnit.MINUTES); - } 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 stacks.stream().map(EntryStacks::hashExact).collect(Collectors.toCollection(LongOpenHashSet::new)); + public Unit prepareCache(boolean async) { + for (CachedProvider provider : hiddenProviders) { + provider.get(); + } + for (CachedProvider provider : shownProviders) { + provider.get(); } + return Unit.INSTANCE; } @Override - public FilteringResult processFilteredStacks(FilteringContext context, FilteringResultFactory resultFactory, Pair cache, boolean async) { + public FilteringResult processFilteredStacks(FilteringContext context, FilteringResultFactory resultFactory, Unit cache, boolean async) { FilteringResult result = resultFactory.create(); - hideList(context.getShownStacks(), result, async, cache.getFirst()); - hideList(context.getUnsetStacks(), result, async, cache.getFirst()); - showList(context.getHiddenStacks(), result, async, cache.getSecond()); - showList(context.getUnsetStacks(), result, async, cache.getSecond()); + hideList(context.getShownStacks(), context.getShownExactHashes(), result, async, hiddenHashes); + hideList(context.getUnsetStacks(), context.getUnsetExactHashes(), result, async, hiddenHashes); + + for (CachedProvider provider : hiddenProviders) { + hideList(context.getShownStacks(), context.getShownExactHashes(), result, async, provider.getExactHashes()); + hideList(context.getUnsetStacks(), context.getUnsetExactHashes(), result, async, provider.getExactHashes()); + } + + showList(context.getHiddenStacks(), context.getHiddenExactHashes(), result, async, shownHashes); + showList(context.getUnsetStacks(), context.getUnsetExactHashes(), result, async, shownHashes); + + for (CachedProvider provider : shownProviders) { + showList(context.getHiddenStacks(), context.getHiddenExactHashes(), result, async, provider.getExactHashes()); + showList(context.getUnsetStacks(), context.getUnsetExactHashes(), result, async, provider.getExactHashes()); + } + return result; } - private void hideList(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())); + private void hideList(Collection> stacks, LongCollection hashes, FilteringResult result, boolean async, LongSet filteredStacks) { + LongIterator iterator = hashes.iterator(); + result.hide(stacks.stream() + .filter(stack -> filteredStacks.contains(iterator.nextLong())) + .collect(Collectors.toList())); } - private void showList(Collection> stacks, FilteringResult result, boolean async, LongSet filteredStacks) { - result.show((async ? stacks.parallelStream() : stacks.stream()).filter(stack -> filteredStacks.contains(EntryStacks.hashExact(stack))).collect(Collectors.toList())); + private void showList(Collection> stacks, LongCollection hashes, FilteringResult result, boolean async, LongSet filteredStacks) { + LongIterator iterator = hashes.iterator(); + result.show(stacks.stream() + .filter(stack -> filteredStacks.contains(iterator.nextLong())) + .collect(Collectors.toList())); } @Override public FilteringResult hide(EntryStack stack) { - hidden.add(stack); - shown.remove(stack); + long hashExact = EntryStacks.hashExact(stack); + hiddenHashes.add(hashExact); + shownHashes.remove(hashExact); + + if (!isReloading()) { + markDirty(List.of(stack), null); + } + return this; } @Override public FilteringResult hide(Collection> stacks) { - hidden.addAll(stacks); - shown.removeAll(stacks); + for (EntryStack stack : stacks) { + long hashExact = EntryStacks.hashExact(stack); + hiddenHashes.add(hashExact); + shownHashes.remove(hashExact); + } + + if (!isReloading()) { + markDirty((Collection>) stacks, null); + } + return this; } @Override public FilteringResult show(EntryStack stack) { - shown.add(stack); - hidden.remove(stack); + long hashExact = EntryStacks.hashExact(stack); + shownHashes.add(hashExact); + hiddenHashes.remove(hashExact); + + if (!isReloading()) { + markDirty(List.of(stack), null); + } + return this; } @Override public FilteringResult show(Collection> stacks) { - shown.addAll(stacks); - hidden.removeAll(stacks); + for (EntryStack stack : stacks) { + long hashExact = EntryStacks.hashExact(stack); + shownHashes.add(hashExact); + hiddenHashes.remove(hashExact); + } + + if (!isReloading()) { + markDirty((Collection>) stacks, null); + } + return this; } + @Override + public MarkDirty hide(Supplier>> provider) { + CachedProvider cachedProvider = new CachedProvider(provider); + shownProviders.remove(cachedProvider); + hiddenProviders.add(cachedProvider); + + cachedProvider.markDirty(); + return cachedProvider; + } + + @Override + public MarkDirty show(Supplier>> provider) { + CachedProvider cachedProvider = new CachedProvider(provider); + hiddenProviders.remove(cachedProvider); + shownProviders.add(cachedProvider); + + cachedProvider.markDirty(); + return cachedProvider; + } + @Override public ReloadStage getStage() { return ReloadStage.START; @@ -144,12 +186,49 @@ public enum BasicFilteringRuleImpl implements BasicFilteringRule>> provider; + private final LazyResettable>, LongSet>> cache = new LazyResettable<>(this::compose); + + private CachedProvider(Supplier>> provider) { + this.provider = provider; + } + + @Override + public void markDirty() { + Pair>, LongSet> prev = this.cache.get(); + this.cache.reset(); + Pair>, LongSet> next = this.cache.get(); + BasicFilteringRuleImpl.this.markDirty(prev.getFirst(), prev.getSecond()); + BasicFilteringRuleImpl.this.markDirty(next.getFirst(), next.getSecond()); + } + + public Collection> get() { + return this.cache.get().getFirst(); + } + + public LongSet getExactHashes() { + return this.cache.get().getSecond(); + } + + private Pair>, LongSet> compose() { + Collection> stacks = this.provider.get(); + LongSet hashes = new LongOpenHashSet(stacks.size()); + for (EntryStack stack : stacks) { + hashes.add(EntryStacks.hashExact(stack)); + } + return Pair.of(stacks, hashes); + } + } } 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 af9f4ddb3..11d8e0501 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 @@ -24,11 +24,9 @@ package me.shedaniel.rei.impl.common.entry.type; import com.google.common.collect.Lists; -import it.unimi.dsi.fastutil.longs.LongArrayList; -import it.unimi.dsi.fastutil.longs.LongList; -import it.unimi.dsi.fastutil.longs.LongOpenHashSet; -import it.unimi.dsi.fastutil.longs.LongSet; +import it.unimi.dsi.fastutil.longs.*; import me.shedaniel.rei.api.client.REIRuntime; +import me.shedaniel.rei.api.client.entry.filtering.FilteringRule; import me.shedaniel.rei.api.client.overlay.ScreenOverlay; import me.shedaniel.rei.api.client.plugins.REIClientPlugin; import me.shedaniel.rei.api.client.registry.entry.EntryRegistry; @@ -40,6 +38,7 @@ import me.shedaniel.rei.api.common.registry.ReloadStage; import me.shedaniel.rei.api.common.util.CollectionUtils; import me.shedaniel.rei.api.common.util.EntryStacks; import me.shedaniel.rei.impl.common.InternalLogger; +import me.shedaniel.rei.impl.common.util.HashedEntryStackWrapper; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.minecraft.core.NonNullList; @@ -58,16 +57,15 @@ import java.util.stream.Stream; @Environment(EnvType.CLIENT) public class EntryRegistryImpl implements EntryRegistry { public List listeners = Lists.newCopyOnWriteArrayList(); - private PreFilteredEntryList preFilteredList; - private EntryRegistryList registryList; + private final EntryRegistryList registryList = new EntryRegistryListImpl(); + private FilteredEntryList filteredList; private LongSet entriesHash; private boolean reloading; public EntryRegistryImpl() { - registryList = new NormalEntryRegistryList(); - entriesHash = new LongOpenHashSet(); - preFilteredList = new PreFilteredEntryList(this); - listeners.add(preFilteredList); + this.entriesHash = new LongOpenHashSet(); + this.filteredList = new PreFilteredEntryList(this, this.registryList); + this.listeners.add(this.filteredList); } @Override @@ -82,21 +80,17 @@ public class EntryRegistryImpl implements EntryRegistry { @Override public void startReload() { - listeners.clear(); - registryList = new ReloadingEntryRegistryList(); - entriesHash = new LongOpenHashSet(); - preFilteredList = new PreFilteredEntryList(this); - listeners.add(preFilteredList); - reloading = true; + this.listeners.clear(); + this.registryList.collectHashed().clear(); + this.entriesHash = new LongOpenHashSet(); + this.filteredList = new PreFilteredEntryList(this, this.registryList); + this.listeners.add(filteredList); + this.reloading = true; } @Override public void endReload() { - reloading = false; - if (!(registryList instanceof ReloadingEntryRegistryList)) { - throw new IllegalStateException("Expected ReloadingEntryRegistryList, got " + registryList.getClass().getName()); - } - registryList = new NormalEntryRegistryList(registryList.stream().filter(((Predicate>) EntryStack::isEmpty).negate())); + this.reloading = false; refilter(); REIRuntime.getInstance().getOverlay().ifPresent(ScreenOverlay::queueReloadOverlay); InternalLogger.getInstance().debug("Reloaded entry registry with %d entries and %d filtered entries", size(), getPreFilteredList().size()); @@ -107,6 +101,11 @@ public class EntryRegistryImpl implements EntryRegistry { return reloading; } + @Override + public void markFilteringRuleDirty(FilteringRule cacheFilteringRule, Collection> stacks, @Nullable LongCollection hashes) { + this.filteredList.refreshFilteringFor(Set.of(cacheFilteringRule), stacks, hashes); + } + @Override public int size() { return registryList.size(); @@ -119,12 +118,12 @@ public class EntryRegistryImpl implements EntryRegistry { @Override public List> getPreFilteredList() { - return Collections.unmodifiableList(preFilteredList.getList()); + return Collections.unmodifiableList(filteredList.getList()); } @Override public void refilter() { - List> stacks = registryList.collect(); + List stacks = registryList.collectHashed(); for (EntryRegistryListener listener : listeners) { listener.onReFilter(stacks); @@ -161,7 +160,8 @@ public class EntryRegistryImpl implements EntryRegistry { @ApiStatus.Internal @Override public Collection> refilterNew(boolean warn, Collection> entries) { - return preFilteredList.refilterNew(warn, entries); + if (warn) FilteringLogic.warnFiltering(); + return FilteringLogic.filter(FilteringLogic.getRules(), false, true, entries); } @Override @@ -236,9 +236,8 @@ public class EntryRegistryImpl implements EntryRegistry { List> removedStacks = new ArrayList<>(); LongList hashes = registryList.needsHash() ? new LongArrayList() : null; - boolean removed = registryList.removeIf(stack -> { + boolean removed = registryList.removeExactIf((stack, hashExact) -> { if (((Predicate>) predicate).test(stack)) { - long hashExact = EntryStacks.hashExact(stack); entriesHash.remove(hashExact); removedStacks.add(stack); if (hashes != null) hashes.add(hashExact); @@ -259,37 +258,35 @@ public class EntryRegistryImpl implements EntryRegistry { @Override public boolean removeEntryExactHashIf(LongPredicate predicate) { - LongPredicate entryStackPredicate = hash -> { + EntryRegistryList.StackFilteringPredicate entryStackPredicate = (stack, hash) -> { if (predicate.test(hash)) { entriesHash.remove(hash); + for (EntryRegistryListener listener : listeners) { + listener.removeEntry(stack, hash); + } return true; } return false; }; - for (EntryRegistryListener listener : listeners) { - listener.removeEntriesIf(stack -> predicate.test(EntryStacks.hashExact(stack))); - } - return registryList.removeExactIf(entryStackPredicate); } @Override public boolean removeEntryFuzzyHashIf(LongPredicate predicate) { - Predicate> entryStackPredicate = stack -> { + EntryRegistryList.StackFilteringPredicate entryStackPredicate = (stack, hashExact) -> { if (predicate.test(EntryStacks.hashFuzzy(stack))) { - entriesHash.remove(EntryStacks.hashExact(stack)); + entriesHash.remove(hashExact); + for (EntryRegistryListener listener : listeners) { + listener.removeEntry(stack, hashExact); + } return true; } return false; }; - for (EntryRegistryListener listener : listeners) { - listener.removeEntriesIf(entryStackPredicate); - } - - return registryList.removeIf(entryStackPredicate); + return registryList.removeExactIf(entryStackPredicate); } } diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/common/entry/type/EntryRegistryList.java b/runtime/src/main/java/me/shedaniel/rei/impl/common/entry/type/EntryRegistryList.java index 2672b077a..4d146f94b 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/common/entry/type/EntryRegistryList.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/common/entry/type/EntryRegistryList.java @@ -25,10 +25,9 @@ package me.shedaniel.rei.impl.common.entry.type; import it.unimi.dsi.fastutil.longs.LongList; import me.shedaniel.rei.api.common.entry.EntryStack; +import me.shedaniel.rei.impl.common.util.HashedEntryStackWrapper; import java.util.List; -import java.util.function.LongPredicate; -import java.util.function.Predicate; import java.util.stream.Stream; public interface EntryRegistryList { @@ -38,6 +37,8 @@ public interface EntryRegistryList { List> collect(); + List collectHashed(); + int indexOf(EntryStack stack); int lastIndexOf(EntryStack stack); @@ -52,9 +53,11 @@ public interface EntryRegistryList { void remove(EntryStack stack, long hashExact); - boolean removeIf(Predicate> predicate); - - boolean removeExactIf(LongPredicate predicate); + boolean removeExactIf(StackFilteringPredicate predicate); boolean needsHash(); + + interface StackFilteringPredicate { + boolean test(EntryStack stack, long hashExact); + } } diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/common/entry/type/EntryRegistryListImpl.java b/runtime/src/main/java/me/shedaniel/rei/impl/common/entry/type/EntryRegistryListImpl.java new file mode 100644 index 000000000..d43627aca --- /dev/null +++ b/runtime/src/main/java/me/shedaniel/rei/impl/common/entry/type/EntryRegistryListImpl.java @@ -0,0 +1,206 @@ +/* + * This file is licensed under the MIT License, part of Roughly Enough Items. + * Copyright (c) 2018, 2019, 2020, 2021, 2022 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.common.entry.type; + +import it.unimi.dsi.fastutil.longs.LongList; +import me.shedaniel.rei.api.common.entry.EntryStack; +import me.shedaniel.rei.impl.common.util.HashedEntryStackWrapper; +import net.minecraft.core.Registry; + +import java.util.AbstractList; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class EntryRegistryListImpl implements EntryRegistryList { + private final List hashedList = new ArrayList<>(Registry.ITEM.keySet().size() + 100); + private final List> list = createMappedList(hashedList); + + public EntryRegistryListImpl() { + } + + public EntryRegistryListImpl(Stream> list) { + list.collect(Collectors.toCollection(() -> this.list)); + } + + @Override + public int size() { + return list.size(); + } + + @Override + public Stream> stream() { + return list.stream(); + } + + @Override + public List> collect() { + return list; + } + + @Override + public List collectHashed() { + return hashedList; + } + + @Override + public int indexOf(EntryStack stack) { + return list.indexOf(stack); + } + + @Override + public int lastIndexOf(EntryStack stack) { + return list.lastIndexOf(stack); + } + + @Override + public void add(EntryStack stack, long hashExact) { + hashedList.add(new HashedEntryStackWrapper(stack, hashExact)); + } + + @Override + public void add(int index, EntryStack stack, long hashExact) { + hashedList.add(index, new HashedEntryStackWrapper(stack, hashExact)); + } + + @Override + public void addAll(List> stacks, LongList hashes) { + hashedList.addAll(new AbstractList<>() { + @Override + public HashedEntryStackWrapper get(int index) { + return new HashedEntryStackWrapper(stacks.get(index), hashes.getLong(index)); + } + + @Override + public int size() { + return stacks.size(); + } + }); + } + + @Override + public void addAll(int index, List> stacks, LongList hashes) { + hashedList.addAll(index, new AbstractList<>() { + @Override + public HashedEntryStackWrapper get(int index) { + return new HashedEntryStackWrapper(stacks.get(index), hashes.getLong(index)); + } + + @Override + public int size() { + return stacks.size(); + } + }); + } + + @Override + public void remove(EntryStack stack, long hashExact) { + hashedList.remove(new HashedEntryStackWrapper(stack, hashExact)); + } + + @Override + public boolean removeExactIf(StackFilteringPredicate predicate) { + return hashedList.removeIf(stack -> predicate.test(stack.unwrap(), stack.hashExact())); + } + + @Override + public boolean needsHash() { + return true; + } + + public List> getList() { + return list; + } + + private static List> createMappedList(List hashedList) { + return new AbstractList<>() { + @Override + public EntryStack get(int index) { + return hashedList.get(index).unwrap(); + } + + @Override + public int size() { + return hashedList.size(); + } + + @Override + public void add(int index, EntryStack element) { + hashedList.add(index, new HashedEntryStackWrapper(element)); + } + + @Override + public EntryStack set(int index, EntryStack element) { + return hashedList.set(index, new HashedEntryStackWrapper(element)).unwrap(); + } + + @Override + public boolean remove(Object o) { + if (o instanceof EntryStack) { + return hashedList.remove(new HashedEntryStackWrapper((EntryStack) o)); + } else { + return false; + } + } + + @Override + public EntryStack remove(int index) { + return hashedList.remove(index).unwrap(); + } + + @Override + public void clear() { + hashedList.clear(); + } + + @Override + public int indexOf(Object o) { + if (o instanceof EntryStack stack) { + return hashedList.indexOf(new HashedEntryStackWrapper(stack)); + } else { + return -1; + } + } + + @Override + public int lastIndexOf(Object o) { + if (o instanceof EntryStack stack) { + return hashedList.lastIndexOf(new HashedEntryStackWrapper(stack)); + } else { + return -1; + } + } + + @Override + public boolean contains(Object o) { + if (o instanceof EntryStack stack) { + return hashedList.contains(new HashedEntryStackWrapper(stack)); + } else { + return false; + } + } + }; + } +} diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/common/entry/type/EntryRegistryListener.java b/runtime/src/main/java/me/shedaniel/rei/impl/common/entry/type/EntryRegistryListener.java index e7b99493c..6ab348430 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/common/entry/type/EntryRegistryListener.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/common/entry/type/EntryRegistryListener.java @@ -25,10 +25,10 @@ package me.shedaniel.rei.impl.common.entry.type; import it.unimi.dsi.fastutil.longs.LongList; import me.shedaniel.rei.api.common.entry.EntryStack; +import me.shedaniel.rei.impl.common.util.HashedEntryStackWrapper; import org.jetbrains.annotations.Nullable; import java.util.List; -import java.util.function.Predicate; public interface EntryRegistryListener { default void addEntryAfter(@Nullable EntryStack afterEntry, EntryStack stack, long stackHashExact) {} @@ -39,7 +39,5 @@ public interface EntryRegistryListener { default void removeEntries(List> stacks, @Nullable LongList hashes) {} - default void removeEntriesIf(Predicate> predicate) {} - - default void onReFilter(List> stacks) {} + default void onReFilter(List stacks) {} } diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/common/entry/type/FilteredEntryList.java b/runtime/src/main/java/me/shedaniel/rei/impl/common/entry/type/FilteredEntryList.java new file mode 100644 index 000000000..c90e1b924 --- /dev/null +++ b/runtime/src/main/java/me/shedaniel/rei/impl/common/entry/type/FilteredEntryList.java @@ -0,0 +1,43 @@ +/* + * This file is licensed under the MIT License, part of Roughly Enough Items. + * Copyright (c) 2018, 2019, 2020, 2021, 2022 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.common.entry.type; + +import it.unimi.dsi.fastutil.longs.LongCollection; +import me.shedaniel.rei.api.client.entry.filtering.FilteringRule; +import me.shedaniel.rei.api.common.entry.EntryStack; +import org.jetbrains.annotations.Nullable; + +import java.util.Collection; +import java.util.List; +import java.util.Set; + +public interface FilteredEntryList extends EntryRegistryListener { + void refreshFilteringFor(@Nullable Set> refilterRules, Collection> stack, @Nullable LongCollection hashes); + + void refreshFilteringFor(boolean log, @Nullable Set> refilterRules, Collection> stacks, @Nullable LongCollection hashes); + + List> getList(); + + boolean isFiltered(EntryStack stack, long hashExact); +} diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/common/entry/type/FilteringLogic.java b/runtime/src/main/java/me/shedaniel/rei/impl/common/entry/type/FilteringLogic.java new file mode 100644 index 000000000..38cb81ea4 --- /dev/null +++ b/runtime/src/main/java/me/shedaniel/rei/impl/common/entry/type/FilteringLogic.java @@ -0,0 +1,107 @@ +/* + * This file is licensed under the MIT License, part of Roughly Enough Items. + * Copyright (c) 2018, 2019, 2020, 2021, 2022 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.common.entry.type; + +import com.google.common.base.Stopwatch; +import me.shedaniel.rei.api.client.config.ConfigObject; +import me.shedaniel.rei.api.client.entry.filtering.FilteringRule; +import me.shedaniel.rei.api.common.entry.EntryStack; +import me.shedaniel.rei.api.common.util.CollectionUtils; +import me.shedaniel.rei.impl.client.config.ConfigObjectImpl; +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.FilteringResultImpl; +import me.shedaniel.rei.impl.common.InternalLogger; +import me.shedaniel.rei.impl.common.util.HashedEntryStackWrapper; +import org.apache.commons.lang3.mutable.MutableLong; +import org.jetbrains.annotations.ApiStatus; + +import java.util.*; +import java.util.stream.Collectors; + +@ApiStatus.Internal +public class FilteringLogic { + private static final MutableLong LAST_WARNING = new MutableLong(-1); + + public static void warnFiltering() { + if (LAST_WARNING.getValue() > 0 && System.currentTimeMillis() - LAST_WARNING.getValue() > 5000) { + InternalLogger.getInstance().warn("Detected runtime EntryRegistry modification, this can be extremely dangerous, or be extremely inefficient!"); + } + LAST_WARNING.setValue(System.currentTimeMillis()); + } + + public static List> getRules() { + return ((ConfigObjectImpl) ConfigObject.getInstance()).getFilteringRules(); + } + + private static LinkedHashMap, Object> prepareCache(List> rules, boolean async, Collection> entries) { + LinkedHashMap, Object> cache = new LinkedHashMap<>(); + for (int i = rules.size() - 1; i >= 0; i--) { + FilteringRule rule = rules.get(i); + cache.put(rule, rule.prepareCache(async)); + } + return cache; + } + + public static List> filter(List> rules, boolean log, boolean async, List> entries) { + return (List>) filter(rules, log, async, (Collection>) entries); + } + + public static Collection> filter(List> rules, boolean log, boolean async, Collection> entries) { + Set hiddenStacks = hidden(rules, log, async, entries).get(FilteringContextType.HIDDEN); + if (hiddenStacks.isEmpty()) { + return entries; + } else if (async) { + return entries.parallelStream() + .filter(stack -> !hiddenStacks.contains(new HashedEntryStackWrapper(stack))) + .collect(Collectors.toList()); + } else { + return CollectionUtils.filterToList(entries, stack -> !hiddenStacks.contains(new HashedEntryStackWrapper(stack))); + } + } + + public static Map> hidden(List> rules, boolean log, boolean async, Collection> entries) { + async = entries.size() > 100 && async; + FilteringContextImpl context = new FilteringContextImpl(async, entries); + LinkedHashMap, Object> cache = prepareCache(rules, async, entries); + filter0(log, context, cache, entries); + + return context.stacks; + } + + private static void filter0(boolean log, FilteringContextImpl context, LinkedHashMap, Object> cache, Collection> entries) { + Stopwatch stopwatch = Stopwatch.createStarted(); + for (Map.Entry, Object> entry : cache.entrySet()) { + stopwatch.reset().start(); + FilteringRule rule = entry.getKey(); + Object cacheObject = entry.getValue(); + context.handleResult((FilteringResultImpl) ((FilteringRule) rule).processFilteredStacks(context, + () -> new FilteringResultImpl(new ArrayList<>(), new ArrayList<>()), + cache.get(rule), true)); + if (log) { + InternalLogger.getInstance().debug("Refiltered rule [%s] in %s.", rule.getType().toString(), stopwatch.stop().toString()); + } + } + } +} diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/common/entry/type/NormalEntryRegistryList.java b/runtime/src/main/java/me/shedaniel/rei/impl/common/entry/type/NormalEntryRegistryList.java deleted file mode 100644 index a6e752c6e..000000000 --- a/runtime/src/main/java/me/shedaniel/rei/impl/common/entry/type/NormalEntryRegistryList.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * This file is licensed under the MIT License, part of Roughly Enough Items. - * Copyright (c) 2018, 2019, 2020, 2021, 2022 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.common.entry.type; - -import it.unimi.dsi.fastutil.longs.LongList; -import me.shedaniel.rei.api.common.entry.EntryStack; -import me.shedaniel.rei.api.common.util.EntryStacks; - -import java.util.ArrayList; -import java.util.List; -import java.util.function.LongPredicate; -import java.util.function.Predicate; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -public class NormalEntryRegistryList implements EntryRegistryList { - private List> list = new ArrayList<>(); - - public NormalEntryRegistryList() { - } - - public NormalEntryRegistryList(Stream> list) { - list.collect(Collectors.toCollection(() -> this.list)); - } - - @Override - public int size() { - return list.size(); - } - - @Override - public Stream> stream() { - return list.stream(); - } - - @Override - public List> collect() { - return list; - } - - @Override - public int indexOf(EntryStack stack) { - return list.indexOf(stack); - } - - @Override - public int lastIndexOf(EntryStack stack) { - return list.lastIndexOf(stack); - } - - @Override - public void add(EntryStack stack, long hashExact) { - list.add(stack); - } - - @Override - public void add(int index, EntryStack stack, long hashExact) { - list.add(index, stack); - } - - @Override - public void addAll(List> stacks, LongList hashes) { - list.addAll(stacks); - } - - @Override - public void addAll(int index, List> stacks, LongList hashes) { - list.addAll(index, stacks); - } - - @Override - public void remove(EntryStack stack, long hashExact) { - list.remove(stack); - } - - @Override - public boolean removeIf(Predicate> predicate) { - return list.removeIf((Predicate>) predicate); - } - - @Override - public boolean removeExactIf(LongPredicate predicate) { - return list.removeIf(stack -> predicate.test(EntryStacks.hashExact(stack))); - } - - @Override - public boolean needsHash() { - return false; - } - - public List> getList() { - return list; - } -} diff --git a/runtime/src/main/java/me/shedaniel/rei/impl/common/entry/type/PreFilteredEntryList.java b/runtime/src/main/java/me/shedaniel/rei/impl/common/entry/type/PreFilteredEntryList.java index 977a33066..944d9bde0 100644 --- a/runtime/src/main/java/me/shedaniel/rei/impl/common/entry/type/PreFilteredEntryList.java +++ b/runtime/src/main/java/me/shedaniel/rei/impl/common/entry/type/PreFilteredEntryList.java @@ -24,8 +24,10 @@ package me.shedaniel.rei.impl.common.entry.type; import com.google.common.base.Stopwatch; +import com.google.common.collect.AbstractIterator; +import com.google.common.collect.Iterators; import com.google.common.collect.Lists; -import it.unimi.dsi.fastutil.longs.LongList; +import it.unimi.dsi.fastutil.longs.*; import me.shedaniel.rei.api.client.REIRuntime; import me.shedaniel.rei.api.client.config.ConfigObject; import me.shedaniel.rei.api.client.config.entry.EntryStackProvider; @@ -34,26 +36,26 @@ import me.shedaniel.rei.api.client.overlay.ScreenOverlay; import me.shedaniel.rei.api.client.registry.entry.EntryRegistry; import me.shedaniel.rei.api.common.entry.EntryStack; import me.shedaniel.rei.api.common.util.CollectionUtils; -import me.shedaniel.rei.impl.client.config.ConfigObjectImpl; -import me.shedaniel.rei.impl.client.entry.filtering.FilteringContextImpl; +import me.shedaniel.rei.api.common.util.EntryStacks; import me.shedaniel.rei.impl.client.entry.filtering.FilteringContextType; -import me.shedaniel.rei.impl.client.entry.filtering.FilteringResultImpl; import me.shedaniel.rei.impl.common.InternalLogger; import me.shedaniel.rei.impl.common.util.HashedEntryStackWrapper; -import org.apache.commons.lang3.mutable.MutableLong; import org.jetbrains.annotations.Nullable; import java.util.*; import java.util.function.Predicate; -import java.util.stream.Collectors; -public class PreFilteredEntryList implements EntryRegistryListener { +public class PreFilteredEntryList implements FilteredEntryList { private final EntryRegistry registry; - private final MutableLong lastRefilterWarning = new MutableLong(-1); - private List> preFilteredList = Lists.newCopyOnWriteArrayList(); + private final EntryRegistryList list; + private final Map, DataPair> filteringData = new HashMap<>(); + private final Long2BooleanMap cached = new Long2BooleanOpenHashMap(); + private final List> listView = new InternalListView(); + private long mod = 0; - public PreFilteredEntryList(EntryRegistry registry) { + public PreFilteredEntryList(EntryRegistry registry, EntryRegistryList list) { this.registry = registry; + this.list = list; } private static Predicate not(Predicate target) { @@ -64,65 +66,33 @@ public class PreFilteredEntryList implements EntryRegistryListener { @Override public void addEntryAfter(@Nullable EntryStack afterEntry, EntryStack stack, long stackHashExact) { if (!registry.isReloading()) { - Collection> refilterNew = refilterNew(true, Collections.singletonList(stack)); - if (afterEntry != null) { - int index = preFilteredList.lastIndexOf(afterEntry); - if (index >= 0) { - preFilteredList.addAll(index, refilterNew); - queueSearchUpdate(); - return; - } - } - - preFilteredList.addAll(refilterNew); - queueSearchUpdate(); + refreshFilteringFor(null, List.of(stack), LongList.of(stackHashExact)); } } @Override public void addEntriesAfter(@Nullable EntryStack afterEntry, List> stacks, @Nullable LongList hashes) { if (!registry.isReloading()) { - Collection> refilterNew = refilterNew(true, stacks); - if (afterEntry != null) { - int index = preFilteredList.lastIndexOf(afterEntry); - if (index >= 0) { - preFilteredList.addAll(index, refilterNew); - queueSearchUpdate(); - return; - } - } - - preFilteredList.addAll(refilterNew); - queueSearchUpdate(); + refreshFilteringFor(null, stacks, hashes); } } @Override public void removeEntry(EntryStack stack, long hashExact) { if (!registry.isReloading()) { - preFilteredList.remove(stack); - queueSearchUpdate(); + removeFilteringFor(List.of(stack), LongList.of(hashExact)); } } @Override public void removeEntries(List> stacks, @Nullable LongList hashes) { if (!registry.isReloading()) { - preFilteredList.removeAll(stacks); - queueSearchUpdate(); - } - } - - @Override - public void removeEntriesIf(Predicate> predicate) { - if (!registry.isReloading()) { - preFilteredList.removeIf(predicate); - queueSearchUpdate(); + removeFilteringFor(stacks, hashes); } } @Override - public void onReFilter(List> stacks) { + public void onReFilter(List stacks) { ConfigObject config = ConfigObject.getInstance(); if (config.getFilteredStackProviders() != null) { List> normalizedFilteredStacks = CollectionUtils.map(config.getFilteredStackProviders(), EntryStackProvider::provide); @@ -132,71 +102,179 @@ public class PreFilteredEntryList implements EntryRegistryListener { } Stopwatch stopwatch = Stopwatch.createStarted(); + refreshFilteringFor(true, null, Lists.transform(stacks, HashedEntryStackWrapper::unwrap), new AbstractLongList() { + @Override + public long getLong(int index) { + return stacks.get(index).hashExact(); + } + + @Override + public int size() { + return stacks.size(); + } + }); + InternalLogger.getInstance().debug("Refiltered entries with %d rules in %s.", FilteringLogic.getRules().size(), stopwatch.stop().toString()); + } + + private void queueSearchUpdate() { + REIRuntime.getInstance().getOverlay().ifPresent(ScreenOverlay::queueReloadSearch); + } + + @Override + public void refreshFilteringFor(@Nullable Set> refilterRules, Collection> stack, @Nullable LongCollection hashes) { + refreshFilteringFor(false, refilterRules, stack, hashes); + } + + @Override + public void refreshFilteringFor(boolean log, @Nullable Set> refilterRules, Collection> stacks, @Nullable LongCollection hashes) { + if (hashes == null) { + hashes = new LongArrayList(stacks.size()); + for (EntryStack stack : stacks) { + hashes.add(EntryStacks.hashExact(stack)); + } + } + + LongIterator hashIterator = hashes.iterator(); + while (hashIterator.hasNext()) { + long hash = hashIterator.nextLong(); + cached.remove(hash); + } - FilteringContextImpl context = new FilteringContextImpl(stacks); - Map, Object> cache = new HashMap<>(); - List> rules = ((ConfigObjectImpl) ConfigObject.getInstance()).getFilteringRules(); - Stopwatch innerStopwatch = Stopwatch.createStarted(); + List> rules = FilteringLogic.getRules(); for (int i = rules.size() - 1; i >= 0; i--) { - innerStopwatch.reset().start(); FilteringRule rule = rules.get(i); - cache.put(rule, rule.prepareCache(true)); - context.handleResult((FilteringResultImpl) ((FilteringRule) rule).processFilteredStacks(context, - () -> new FilteringResultImpl(new ArrayList<>(), new ArrayList<>()), - cache.get(rule), true)); - InternalLogger.getInstance().debug("Refiltered rule [%s] in %s.", rule.getType().toString(), innerStopwatch.stop().toString()); + if (!filteringData.containsKey(rule)) filteringData.put(rule, new DataPair()); + DataPair longPair = filteringData.get(rule); + LongSet hidden = longPair.hidden(); + LongSet shown = longPair.shown(); + boolean refilter = refilterRules == null || refilterRules.contains(rule); + if (refilter) { + if (!hidden.isEmpty()) { + hidden.removeAll(hashes); + mod++; + } + if (!shown.isEmpty()) { + shown.removeAll(hashes); + mod++; + } + Map> map = FilteringLogic.hidden(List.of(rule), log, true, stacks); + Set hiddenWrappers = map.get(FilteringContextType.HIDDEN); + Set shownWrappers = map.get(FilteringContextType.SHOWN); + for (HashedEntryStackWrapper stack : hiddenWrappers) { + hidden.add(stack.hashExact()); + cached.put(stack.hashExact(), false); + } + for (HashedEntryStackWrapper stack : shownWrappers) { + shown.add(stack.hashExact()); + cached.put(stack.hashExact(), true); + } + if (!hiddenWrappers.isEmpty() || !shownWrappers.isEmpty()) mod++; + } else { + hashIterator = hashes.iterator(); + while (hashIterator.hasNext()) { + long hash = hashIterator.nextLong(); + if (hidden.contains(hash)) { + cached.put(hash, false); + mod++; + } else if (shown.contains(hash)) { + cached.put(hash, true); + mod++; + } + } + } + } + + queueSearchUpdate(); + } + + private void removeFilteringFor(List> stacks, @Nullable LongList hashes) { + if (hashes == null) { + hashes = new LongArrayList(stacks.size()); + for (EntryStack stack : stacks) { + hashes.add(EntryStacks.hashExact(stack)); + } } - Set hiddenStacks = context.stacks.get(FilteringContextType.HIDDEN); - if (hiddenStacks.isEmpty()) { - preFilteredList = Lists.newCopyOnWriteArrayList(stacks); - } else { - preFilteredList = stacks.parallelStream() - .map(HashedEntryStackWrapper::new) - .filter(not(hiddenStacks::contains)) - .map(HashedEntryStackWrapper::unwrap) - .collect(Collectors.toCollection(Lists::newCopyOnWriteArrayList)); + removeFilteringFor(hashes); + } + + private void removeFilteringFor(LongList hashes) { + for (DataPair value : filteringData.values()) { + value.hidden().removeAll(hashes); + value.shown().removeAll(hashes); } - InternalLogger.getInstance().debug("Refiltered %d entries with %d rules in %s.", stacks.size() - preFilteredList.size(), rules.size(), stopwatch.stop().toString()); + LongListIterator hashIterator = hashes.iterator(); + while (hashIterator.hasNext()) { + long hash = hashIterator.nextLong(); + cached.remove(hash); + } + } + + @Override + public List> getList() { + return listView; } - public Collection> refilterNew(boolean warn, Collection> entries) { - if (lastRefilterWarning != null && warn) { - if (lastRefilterWarning.getValue() > 0 && System.currentTimeMillis() - lastRefilterWarning.getValue() > 5000) { - InternalLogger.getInstance().warn("Detected runtime EntryRegistry modification, this can be extremely dangerous, or be extremely inefficient!"); + private class InternalLi