Skip to content

Commit

Permalink
Fix #1611
Browse files Browse the repository at this point in the history
  • Loading branch information
shedaniel committed Apr 25, 2024
1 parent 330b694 commit e4130e8
Show file tree
Hide file tree
Showing 7 changed files with 320 additions and 161 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,7 @@ private TransferHandler _getTransferHandler() {
Map<CategoryIdentifier<?>, Boolean> filteringQuickCraftCategories = ConfigObject.getInstance().getFilteringQuickCraftCategories();
boolean shouldFilterDisplays = ConfigObject.getInstance().shouldFilterDisplays();

for (Display display : displaysHolder.getAllDisplaysByOutputs(getEntries())) {
for (Display display : displaysHolder.cache().getAllDisplaysByOutputs(getEntries())) {
CategoryIdentifier<?> categoryIdentifier = display.getCategoryIdentifier();
Optional<? extends CategoryRegistry.CategoryConfiguration<?>> configuration;
if ((configuration = categoryRegistry.tryGet(categoryIdentifier)).isEmpty()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
* This file is licensed under the MIT License, part of Roughly Enough Items.
* Copyright (c) 2018, 2019, 2020, 2021, 2022, 2023 shedaniel
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/

package me.shedaniel.rei.impl.client.registry.display;

import com.google.common.collect.Iterables;
import me.shedaniel.rei.api.common.display.Display;
import me.shedaniel.rei.api.common.entry.EntryStack;
import me.shedaniel.rei.api.common.util.CollectionUtils;
import me.shedaniel.rei.impl.client.view.ViewsImpl;

import java.util.Collection;
import java.util.List;
import java.util.Set;

public interface DisplayCache {
boolean doesCache();

boolean isCached(Display display);

void add(Display display);

boolean remove(Display display);

void endReload();

Set<Display> getDisplaysNotCached();

Set<Display> getDisplaysByInput(EntryStack<?> stack);

Set<Display> getDisplaysByOutput(EntryStack<?> stack);

default Iterable<Display> getAllDisplaysByInputs(List<EntryStack<?>> stacks) {
if (stacks.isEmpty()) return List.of();
Iterable<Display> inputCached = null;
if (doesCache()) {
for (EntryStack<?> stack : stacks) {
Set<Display> set = getDisplaysByInput(stack);
inputCached = inputCached == null ? set : Iterables.concat(inputCached, set);
}
if (stacks.size() > 1) inputCached = CollectionUtils.distinctReferenceOf(inputCached);
}
Collection<Display> notCached = this.getDisplaysNotCached();
if (notCached.isEmpty()) return inputCached == null ? List.of() : inputCached;
Iterable<Display> filteredNotCached = Iterables.filter(notCached, display ->
ViewsImpl.isUsagesFor(null, stacks, display));
if (inputCached == null) return filteredNotCached;
return Iterables.concat(inputCached, filteredNotCached);
}

default Iterable<Display> getAllDisplaysByOutputs(List<EntryStack<?>> stacks) {
if (stacks.isEmpty()) return List.of();
Iterable<Display> outputCached = null;
if (doesCache()) {
for (EntryStack<?> stack : stacks) {
Set<Display> set = getDisplaysByOutput(stack);
outputCached = outputCached == null ? set : Iterables.concat(outputCached, set);
}
if (stacks.size() > 1) outputCached = CollectionUtils.distinctReferenceOf(outputCached);
}
Collection<Display> notCached = this.getDisplaysNotCached();
if (notCached.isEmpty()) return outputCached == null ? List.of() : outputCached;
Iterable<Display> filteredNotCached = Iterables.filter(notCached, display ->
ViewsImpl.isRecipesFor(null, stacks, display));
if (outputCached == null) return filteredNotCached;
return Iterables.concat(outputCached, filteredNotCached);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
/*
* This file is licensed under the MIT License, part of Roughly Enough Items.
* Copyright (c) 2018, 2019, 2020, 2021, 2022, 2023 shedaniel
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/

package me.shedaniel.rei.impl.client.registry.display;

import com.google.common.base.Stopwatch;
import com.google.common.collect.Multimaps;
import com.google.common.collect.SetMultimap;
import it.unimi.dsi.fastutil.Hash;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenCustomHashMap;
import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet;
import me.shedaniel.rei.api.client.config.ConfigObject;
import me.shedaniel.rei.api.common.display.Display;
import me.shedaniel.rei.api.common.entry.EntryIngredient;
import me.shedaniel.rei.api.common.entry.EntryStack;
import me.shedaniel.rei.api.common.util.EntryStacks;
import me.shedaniel.rei.impl.common.InternalLogger;

import java.util.Collections;
import java.util.Set;

public class DisplayCacheImpl implements DisplayCache {
private final boolean cache;
private Set<Display> displaysCached = new ReferenceOpenHashSet<>();
private Set<Display> displaysNotCached = Collections.synchronizedSet(new ReferenceOpenHashSet<>());
private SetMultimap<EntryStack<?>, Display> displaysByInput;
private SetMultimap<EntryStack<?>, Display> displaysByOutput;
private boolean preprocessed = false;

public DisplayCacheImpl(boolean init) {
this.cache = init && ConfigObject.getInstance().doesCacheDisplayLookup();
this.displaysByInput = createSetMultimap();
this.displaysByOutput = createSetMultimap();
}

@Override
public boolean doesCache() {
return this.cache;
}

@Override
public boolean isCached(Display display) {
return this.cache && this.displaysCached.contains(display);
}

@Override
public void add(Display display) {
if (this.cache) {
if (!preprocessed) {
this.displaysNotCached.add(display);
} else {
this.process(display);
this.displaysCached.add(display);
}
} else {
this.displaysNotCached.add(display);
}
}

@Override
public boolean remove(Display display) {
if (this.cache) {
if (!preprocessed) {
return this.displaysNotCached.remove(display);
} else {
boolean removed = this.displaysCached.remove(display);
if (removed) {
for (EntryIngredient input : display.getInputEntries()) {
for (EntryStack<?> stack : input) {
this.displaysByInput.remove(stack, display);
}
}
for (EntryIngredient output : display.getOutputEntries()) {
for (EntryStack<?> stack : output) {
this.displaysByOutput.remove(stack, display);
}
}
}
return removed;
}
} else {
return this.displaysNotCached.remove(display);
}
}

@Override
public void endReload() {
if (this.cache) {
if (preprocessed) {
InternalLogger.getInstance().error("DisplayCache#endReload called after preprocessed!");
}

InternalLogger.getInstance().debug("Processing %d displays for optimal lookup performance...", this.displaysNotCached.size());
Stopwatch stopwatch = Stopwatch.createStarted();
this.displaysCached = new ReferenceOpenHashSet<>(this.displaysNotCached.size());
this.displaysByInput = createSetMultimap();
this.displaysByOutput = createSetMultimap();
for (Display display : this.displaysNotCached) {
this.process(display);
}
this.displaysCached.addAll(this.displaysNotCached);
this.displaysNotCached = Set.of();
this.preprocessed = true;
InternalLogger.getInstance().debug("Processed displays for optimal lookup performance in %s.", stopwatch.stop());
}
}

@Override
public Set<Display> getDisplaysNotCached() {
return this.displaysNotCached;
}

@Override
public Set<Display> getDisplaysByInput(EntryStack<?> stack) {
return this.displaysByInput.get(stack);
}

@Override
public Set<Display> getDisplaysByOutput(EntryStack<?> stack) {
return this.displaysByOutput.get(stack);
}

private void process(Display display) {
for (EntryIngredient input : display.getInputEntries()) {
for (EntryStack<?> stack : input) {
this.displaysByInput.put(stack, display);
}
}
for (EntryIngredient output : display.getOutputEntries()) {
for (EntryStack<?> stack : output) {
this.displaysByOutput.put(stack, display);
}
}
}

private static SetMultimap<EntryStack<?>, Display> createSetMultimap() {
return Multimaps.newSetMultimap(
new Object2ObjectOpenCustomHashMap<>(5000, new Hash.Strategy<>() {
@Override
public int hashCode(EntryStack<?> stack) {
return Long.hashCode(EntryStacks.hashFuzzy(stack));
}

@Override
public boolean equals(EntryStack<?> o1, EntryStack<?> o2) {
return EntryStacks.equalsFuzzy(o1, o2);
}
}),
ReferenceOpenHashSet::new
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ public void add(Display display, @Nullable Object origin) {

@Override
public Map<CategoryIdentifier<?>, List<Display>> getAll() {
return this.displaysHolder.get();
return this.displaysHolder.getUnmodifiable();
}

@Override
Expand Down Expand Up @@ -201,8 +201,16 @@ public void endReload() {
}
}

List<Display> failedDisplays = new ArrayList<>();
for (List<Display> displays : getAll().values()) {
displays.removeIf(display -> !DisplayValidator.validate(display));
for (Display display : displays) {
if (!DisplayValidator.validate(display)) {
failedDisplays.add(display);
}
}
}
for (Display display : failedDisplays) {
this.displaysHolder.remove(display);
}

this.displaysHolder.endReload();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,76 +23,29 @@

package me.shedaniel.rei.impl.client.registry.display;

import com.google.common.collect.Iterables;
import me.shedaniel.rei.api.common.category.CategoryIdentifier;
import me.shedaniel.rei.api.common.display.Display;
import me.shedaniel.rei.api.common.entry.EntryStack;
import me.shedaniel.rei.api.common.util.CollectionUtils;
import me.shedaniel.rei.impl.client.view.ViewsImpl;
import org.jetbrains.annotations.Nullable;

import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;

public interface DisplaysHolder {
boolean doesCache();
DisplayCache cache();

void add(Display display, @Nullable Object origin);

boolean remove(Display display);

int size();

Map<CategoryIdentifier<?>, List<Display>> get();
Map<CategoryIdentifier<?>, List<Display>> getUnmodifiable();

@Nullable
Object getDisplayOrigin(Display display);

void endReload();

Set<Display> getDisplaysByKey(DisplayKey key);

boolean isCached(Display display);

Set<Display> getDisplaysNotCached();

Set<Display> getDisplaysByInput(EntryStack<?> stack);

Set<Display> getDisplaysByOutput(EntryStack<?> stack);

default Iterable<Display> getAllDisplaysByInputs(List<EntryStack<?>> stacks) {
if (stacks.isEmpty()) return List.of();
Iterable<Display> inputCached = null;
if (doesCache()) {
for (EntryStack<?> stack : stacks) {
Set<Display> set = getDisplaysByInput(stack);
inputCached = inputCached == null ? set : Iterables.concat(inputCached, set);
}
if (stacks.size() > 1) inputCached = CollectionUtils.distinctReferenceOf(inputCached);
}
Collection<Display> notCached = this.getDisplaysNotCached();
if (notCached.isEmpty()) return inputCached == null ? List.of() : inputCached;
Iterable<Display> filteredNotCached = Iterables.filter(notCached, display ->
ViewsImpl.isUsagesFor(null, stacks, display));
if (inputCached == null) return filteredNotCached;
return Iterables.concat(inputCached, filteredNotCached);
}

default Iterable<Display> getAllDisplaysByOutputs(List<EntryStack<?>> stacks) {
if (stacks.isEmpty()) return List.of();
Iterable<Display> outputCached = null;
if (doesCache()) {
for (EntryStack<?> stack : stacks) {
Set<Display> set = getDisplaysByOutput(stack);
outputCached = outputCached == null ? set : Iterables.concat(outputCached, set);
}
if (stacks.size() > 1) outputCached = CollectionUtils.distinctReferenceOf(outputCached);
}
Collection<Display> notCached = this.getDisplaysNotCached();
if (notCached.isEmpty()) return outputCached == null ? List.of() : outputCached;
Iterable<Display> filteredNotCached = Iterables.filter(notCached, display ->
ViewsImpl.isRecipesFor(null, stacks, display));
if (outputCached == null) return filteredNotCached;
return Iterables.concat(outputCached, filteredNotCached);
}
}
Loading

0 comments on commit e4130e8

Please sign in to comment.