mirror of
https://github.com/GTNewHorizons/NotEnoughItems.git
synced 2026-11-14 01:14:07 +00:00
Item Craftables Panel Optimization (#1036)
This commit is contained in:
1 parent
ab512d3fd8
commit
9e87f2b158
2 files changed
+232
-31
No files matched your search
@@ -6,11 +6,14 @@ import java.util.Comparator;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.IntStream;
|
import java.util.stream.IntStream;
|
||||||
|
|
||||||
import net.minecraft.client.gui.inventory.GuiContainer;
|
import net.minecraft.client.gui.inventory.GuiContainer;
|
||||||
|
import net.minecraft.item.Item;
|
||||||
import net.minecraft.item.ItemStack;
|
import net.minecraft.item.ItemStack;
|
||||||
|
import net.minecraftforge.fluids.FluidContainerRegistry;
|
||||||
|
|
||||||
import codechicken.lib.vec.Rectangle4i;
|
import codechicken.lib.vec.Rectangle4i;
|
||||||
import codechicken.nei.ItemsGrid.ItemsGridSlot;
|
import codechicken.nei.ItemsGrid.ItemsGridSlot;
|
||||||
@@ -21,7 +24,6 @@ import codechicken.nei.recipe.GuiCraftingRecipe;
|
|||||||
import codechicken.nei.recipe.GuiRecipe;
|
import codechicken.nei.recipe.GuiRecipe;
|
||||||
import codechicken.nei.recipe.ICraftingHandler;
|
import codechicken.nei.recipe.ICraftingHandler;
|
||||||
import codechicken.nei.recipe.IRecipeHandler;
|
import codechicken.nei.recipe.IRecipeHandler;
|
||||||
import codechicken.nei.recipe.Recipe;
|
|
||||||
import codechicken.nei.recipe.Recipe.RecipeId;
|
import codechicken.nei.recipe.Recipe.RecipeId;
|
||||||
import codechicken.nei.recipe.RecipeInfo;
|
import codechicken.nei.recipe.RecipeInfo;
|
||||||
import codechicken.nei.recipe.StackInfo;
|
import codechicken.nei.recipe.StackInfo;
|
||||||
@@ -82,6 +84,42 @@ public class ItemCraftablesPanel
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static class CraftablesSnapshot {
|
||||||
|
|
||||||
|
final GuiContainer guiContainer;
|
||||||
|
final ItemStackAmount inventory;
|
||||||
|
final boolean showOnlyFavorites;
|
||||||
|
final int maxCandidates;
|
||||||
|
|
||||||
|
CraftablesSnapshot(GuiContainer guiContainer, ItemStackAmount inventory, boolean showOnlyFavorites,
|
||||||
|
int maxCandidates) {
|
||||||
|
this.guiContainer = guiContainer;
|
||||||
|
this.inventory = inventory;
|
||||||
|
this.showOnlyFavorites = showOnlyFavorites;
|
||||||
|
this.maxCandidates = maxCandidates;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class RecipeCandidate {
|
||||||
|
|
||||||
|
final IRecipeHandler handler;
|
||||||
|
final int recipeIndex;
|
||||||
|
final List<PositionedStack> ingredientStacks;
|
||||||
|
final ItemStack sortKeyItem;
|
||||||
|
|
||||||
|
RecipeCandidate(IRecipeHandler handler, int recipeIndex, List<PositionedStack> ingredientStacks,
|
||||||
|
ItemStack sortKeyItem) {
|
||||||
|
this.handler = handler;
|
||||||
|
this.recipeIndex = recipeIndex;
|
||||||
|
this.ingredientStacks = ingredientStacks;
|
||||||
|
this.sortKeyItem = sortKeyItem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final Comparator<ItemStack> ITEM_SORT_COMPARATOR = Comparator
|
||||||
|
.comparing(FavoriteRecipes::containsManual).thenComparing(FavoriteRecipes::contains).reversed()
|
||||||
|
.thenComparing(ItemSorter.instance);
|
||||||
|
|
||||||
public static List<String> guiBlacklist = new ArrayList<>();
|
public static List<String> guiBlacklist = new ArrayList<>();
|
||||||
private final int MIN_DELAY = 600;
|
private final int MIN_DELAY = 600;
|
||||||
|
|
||||||
@@ -93,6 +131,16 @@ public class ItemCraftablesPanel
|
|||||||
private long lastPlayerInventorySync = 0;
|
private long lastPlayerInventorySync = 0;
|
||||||
private int lastFavoritesCount = 0;
|
private int lastFavoritesCount = 0;
|
||||||
|
|
||||||
|
private volatile CraftablesSnapshot pendingSnapshot;
|
||||||
|
private final AtomicReference<Map<ItemStack, RecipeId>> pendingResult = new AtomicReference<>();
|
||||||
|
private final RestartableTask task = new RestartableTask("NEI Craftables Panel") {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute() {
|
||||||
|
pendingResult.set(generateCraftables(ItemCraftablesPanel.this.pendingSnapshot));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
public ItemCraftablesPanel() {
|
public ItemCraftablesPanel() {
|
||||||
this.grid = new ItemsGrid<>() {
|
this.grid = new ItemsGrid<>() {
|
||||||
|
|
||||||
@@ -114,12 +162,7 @@ public class ItemCraftablesPanel
|
|||||||
|
|
||||||
if (!ItemCraftablesPanel.this.availableRecipes.isEmpty()) {
|
if (!ItemCraftablesPanel.this.availableRecipes.isEmpty()) {
|
||||||
final List<Map.Entry<ItemStack, RecipeId>> entries = ItemCraftablesPanel.this.availableRecipes
|
final List<Map.Entry<ItemStack, RecipeId>> entries = ItemCraftablesPanel.this.availableRecipes
|
||||||
.entrySet().stream()
|
.entrySet().stream().sorted(Map.Entry.comparingByKey(ITEM_SORT_COMPARATOR))
|
||||||
.sorted(
|
|
||||||
Map.Entry.comparingByKey(
|
|
||||||
Comparator.comparing(FavoriteRecipes::containsManual)
|
|
||||||
.thenComparing(FavoriteRecipes::contains).reversed()
|
|
||||||
.thenComparing(ItemSorter.instance)))
|
|
||||||
.limit(maxSlotIndex).collect(Collectors.toList());
|
.limit(maxSlotIndex).collect(Collectors.toList());
|
||||||
int slotIndex = 0;
|
int slotIndex = 0;
|
||||||
int itemIndex = 0;
|
int itemIndex = 0;
|
||||||
@@ -195,10 +238,20 @@ public class ItemCraftablesPanel
|
|||||||
|
|
||||||
public void update() {
|
public void update() {
|
||||||
this.splittingLineColor = NEIClientConfig.getSetting("inventory.craftables.color").getHexValue();
|
this.splittingLineColor = NEIClientConfig.getSetting("inventory.craftables.color").getHexValue();
|
||||||
|
applyPendingResult();
|
||||||
updateCraftables();
|
updateCraftables();
|
||||||
super.update();
|
super.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void applyPendingResult() {
|
||||||
|
final Map<ItemStack, RecipeId> result = this.pendingResult.getAndSet(null);
|
||||||
|
|
||||||
|
if (result != null && NEIClientConfig.showCraftablesPanelWidget()) {
|
||||||
|
this.availableRecipes = result;
|
||||||
|
this.grid.onGridChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void updateCraftables() {
|
private void updateCraftables() {
|
||||||
|
|
||||||
if (!NEIClientConfig.showCraftablesPanelWidget()) {
|
if (!NEIClientConfig.showCraftablesPanelWidget()) {
|
||||||
@@ -219,8 +272,14 @@ public class ItemCraftablesPanel
|
|||||||
this.lastPlayerInventory = inv;
|
this.lastPlayerInventory = inv;
|
||||||
this.lastGuiContainer = firstGui;
|
this.lastGuiContainer = firstGui;
|
||||||
this.lastFavoritesCount = FavoriteRecipes.size();
|
this.lastFavoritesCount = FavoriteRecipes.size();
|
||||||
this.availableRecipes = generateCraftables(firstGui);
|
final int maxRows = NEIClientConfig.getIntSetting("inventory.craftables.useRows") + 2;
|
||||||
this.grid.onGridChanged();
|
final int maxCandidates = Math.max(this.grid.columns, 1) * maxRows;
|
||||||
|
this.pendingSnapshot = new CraftablesSnapshot(
|
||||||
|
firstGui,
|
||||||
|
inv,
|
||||||
|
showOnlyFavorites,
|
||||||
|
maxCandidates);
|
||||||
|
this.task.restart();
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
@@ -244,17 +303,28 @@ public class ItemCraftablesPanel
|
|||||||
return (firstGui instanceof GuiRecipe<?>gui) ? gui.firstGui : firstGui;
|
return (firstGui instanceof GuiRecipe<?>gui) ? gui.firstGui : firstGui;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<ItemStack, RecipeId> generateCraftables(GuiContainer firstGui) {
|
private Map<ItemStack, RecipeId> generateCraftables(CraftablesSnapshot snapshot) {
|
||||||
final List<IRecipeHandler> availableHandlers = getAvailableHandlers(firstGui);
|
final List<IRecipeHandler> availableHandlers = getAvailableHandlers(snapshot.guiContainer);
|
||||||
final Map<ItemStack, RecipeId> availableRecipes = new HashMap<>();
|
final Map<ItemStack, RecipeId> availableRecipes = new HashMap<>();
|
||||||
|
|
||||||
if (!availableHandlers.isEmpty()) {
|
if (!availableHandlers.isEmpty()) {
|
||||||
final List<ItemStack> invStacks = ItemCraftablesPanel.this.lastPlayerInventory.values();
|
final Map<Item, List<ItemStack>> invByItem = indexInventoryByItem(snapshot.inventory.values());
|
||||||
|
|
||||||
for (IRecipeHandler handler : availableHandlers) {
|
if (snapshot.showOnlyFavorites) {
|
||||||
availableRecipes.putAll(filterHandlerRecipes(handler, firstGui, invStacks));
|
for (IRecipeHandler handler : availableHandlers) {
|
||||||
|
availableRecipes.putAll(filterHandlerRecipes(handler, invByItem, true));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
final List<RecipeCandidate> candidates = Collections.synchronizedList(new ArrayList<>());
|
||||||
|
|
||||||
|
for (IRecipeHandler handler : availableHandlers) {
|
||||||
|
collectHandlerCandidates(handler, invByItem, candidates);
|
||||||
|
}
|
||||||
|
|
||||||
|
candidates.sort(Comparator.comparing(c -> c.sortKeyItem, ITEM_SORT_COMPARATOR));
|
||||||
|
candidates.subList(0, Math.min(candidates.size(), snapshot.maxCandidates)).parallelStream()
|
||||||
|
.forEach(candidate -> buildCandidate(candidate, availableRecipes));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return availableRecipes;
|
return availableRecipes;
|
||||||
@@ -270,19 +340,126 @@ public class ItemCraftablesPanel
|
|||||||
return availableHandlers;
|
return availableHandlers;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<ItemStack, RecipeId> filterHandlerRecipes(IRecipeHandler handler, GuiContainer firstGui,
|
private static Map<Item, List<ItemStack>> indexInventoryByItem(List<ItemStack> invStacks) {
|
||||||
List<ItemStack> invStacks) {
|
final Map<Item, List<ItemStack>> index = new HashMap<>();
|
||||||
|
|
||||||
|
for (ItemStack stack : invStacks) {
|
||||||
|
index.computeIfAbsent(stack.getItem(), i -> new ArrayList<>()).add(stack);
|
||||||
|
}
|
||||||
|
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void collectHandlerCandidates(IRecipeHandler handler, Map<Item, List<ItemStack>> invByItem,
|
||||||
|
List<RecipeCandidate> candidates) {
|
||||||
|
IntStream.range(0, handler.numRecipes()).parallel().forEach(recipeIndex -> {
|
||||||
|
final List<PositionedStack> ingredientStacks = handler.getIngredientStacks(recipeIndex);
|
||||||
|
|
||||||
|
if (existsIngredients(ingredientStacks, invByItem)) {
|
||||||
|
final ItemStack sortKeyItem = getCheapResultItem(handler, recipeIndex);
|
||||||
|
|
||||||
|
if (sortKeyItem != null) {
|
||||||
|
candidates.add(new RecipeCandidate(handler, recipeIndex, ingredientStacks, sortKeyItem));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void buildCandidate(RecipeCandidate candidate, Map<ItemStack, RecipeId> availableRecipes) {
|
||||||
|
final ItemStack result = getCheapResult(candidate.handler, candidate.recipeIndex);
|
||||||
|
|
||||||
|
if (result != null) {
|
||||||
|
final RecipeId recipeId = RecipeId.of(candidate.handler, candidate.recipeIndex, candidate.ingredientStacks);
|
||||||
|
|
||||||
|
synchronized (availableRecipes) {
|
||||||
|
availableRecipes.put(result, recipeId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ItemStack getCheapResultItem(IRecipeHandler handler, int recipeIndex) {
|
||||||
|
final PositionedStack resultStack = handler.getResultStack(recipeIndex);
|
||||||
|
|
||||||
|
if (resultStack != null) {
|
||||||
|
return resultStack.item;
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<PositionedStack> otherStacks = handler.getOtherStacks(recipeIndex);
|
||||||
|
|
||||||
|
for (PositionedStack otherStack : otherStacks) {
|
||||||
|
if (!FluidContainerRegistry.isContainer(otherStack.items[0])
|
||||||
|
|| StackInfo.getFluid(otherStack.items[0]) != null) {
|
||||||
|
return otherStack.item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return otherStacks.isEmpty() ? null : otherStacks.get(0).item;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ItemStack getCheapResult(IRecipeHandler handler, int recipeIndex) {
|
||||||
|
final PositionedStack resultStack = handler.getResultStack(recipeIndex);
|
||||||
|
final List<PositionedStack> results = resultStack != null ? Collections.singletonList(resultStack)
|
||||||
|
: handler.getOtherStacks(recipeIndex);
|
||||||
|
|
||||||
|
if (results.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
ItemStack stack = null;
|
||||||
|
|
||||||
|
if (results.size() > 1) {
|
||||||
|
for (PositionedStack result : results) {
|
||||||
|
if (!FluidContainerRegistry.isContainer(result.item) || StackInfo.getFluid(result.item) != null) {
|
||||||
|
stack = result.item;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stack == null) {
|
||||||
|
stack = results.get(0).item;
|
||||||
|
}
|
||||||
|
|
||||||
|
int stackSize = 0;
|
||||||
|
|
||||||
|
for (PositionedStack result : results) {
|
||||||
|
if (containsSameType(result, stack)) {
|
||||||
|
stackSize += StackInfo.getAmount(result.item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return StackInfo.withAmount(stack, stackSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean containsSameType(PositionedStack pStack, ItemStack stack) {
|
||||||
|
final ItemStack normalized = StackInfo.withAmount(stack, 0);
|
||||||
|
|
||||||
|
for (ItemStack permutation : pStack.items) {
|
||||||
|
if (NEIServerUtils.areStacksSameTypeWithNBT(StackInfo.withAmount(permutation, 0), normalized)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<ItemStack, RecipeId> filterHandlerRecipes(IRecipeHandler handler, Map<Item, List<ItemStack>> invByItem,
|
||||||
|
boolean showOnlyFavorites) {
|
||||||
final Map<ItemStack, RecipeId> availableRecipes = new HashMap<>();
|
final Map<ItemStack, RecipeId> availableRecipes = new HashMap<>();
|
||||||
final boolean showOnlyFavorites = NEIClientConfig.getBooleanSetting("inventory.craftables.favoritesOnly");
|
|
||||||
|
|
||||||
IntStream.range(0, handler.numRecipes()).parallel().forEach(recipeIndex -> {
|
IntStream.range(0, handler.numRecipes()).parallel().forEach(recipeIndex -> {
|
||||||
if (existsIngredients(firstGui, handler.getIngredientStacks(recipeIndex), invStacks)) {
|
final List<PositionedStack> ingredientStacks = handler.getIngredientStacks(recipeIndex);
|
||||||
final Recipe recipe = Recipe.of(handler, recipeIndex);
|
|
||||||
|
|
||||||
if (!recipe.getResults().isEmpty()
|
if (existsIngredients(ingredientStacks, invByItem)) {
|
||||||
&& (!showOnlyFavorites || FavoriteRecipes.getFavorite(recipe.getRecipeId()) != null)) {
|
final ItemStack result = getCheapResult(handler, recipeIndex);
|
||||||
synchronized (availableRecipes) {
|
|
||||||
availableRecipes.put(recipe.getResult(), recipe.getRecipeId());
|
if (result != null) {
|
||||||
|
final RecipeId recipeId = RecipeId.of(handler, recipeIndex, ingredientStacks);
|
||||||
|
|
||||||
|
if (!showOnlyFavorites || FavoriteRecipes.getFavorite(recipeId) != null) {
|
||||||
|
synchronized (availableRecipes) {
|
||||||
|
availableRecipes.put(result, recipeId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -291,14 +468,27 @@ public class ItemCraftablesPanel
|
|||||||
return availableRecipes;
|
return availableRecipes;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean existsIngredients(GuiContainer firstGui, List<PositionedStack> ingredients,
|
private boolean existsIngredients(List<PositionedStack> ingredients, Map<Item, List<ItemStack>> invByItem) {
|
||||||
List<ItemStack> invStacks) {
|
|
||||||
final Map<ItemStack, Integer> usedItems = new HashMap<>();
|
final Map<ItemStack, Integer> usedItems = new HashMap<>();
|
||||||
|
|
||||||
for (PositionedStack pStack : ingredients) {
|
for (PositionedStack pStack : ingredients) {
|
||||||
final ItemStack used = invStacks.stream().filter(
|
ItemStack used = null;
|
||||||
is -> usedItems.getOrDefault(is, is.stackSize) >= pStack.item.stackSize && pStack.contains(is))
|
|
||||||
.findAny().orElse(null);
|
for (ItemStack permutation : pStack.items) {
|
||||||
|
final List<ItemStack> candidates = invByItem.get(permutation.getItem());
|
||||||
|
|
||||||
|
if (candidates == null) continue;
|
||||||
|
|
||||||
|
for (ItemStack is : candidates) {
|
||||||
|
if (usedItems.getOrDefault(is, is.stackSize) >= pStack.item.stackSize
|
||||||
|
&& NEIServerUtils.areStacksSameTypeCrafting(permutation, is)) {
|
||||||
|
used = is;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (used != null) break;
|
||||||
|
}
|
||||||
|
|
||||||
if (used != null) {
|
if (used != null) {
|
||||||
usedItems.put(used, usedItems.getOrDefault(used, used.stackSize) - pStack.item.stackSize);
|
usedItems.put(used, usedItems.getOrDefault(used, used.stackSize) - pStack.item.stackSize);
|
||||||
|
|||||||
@@ -4,7 +4,9 @@ import java.util.ArrayList;
|
|||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
|
import java.util.IdentityHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import net.minecraft.item.Item;
|
import net.minecraft.item.Item;
|
||||||
@@ -43,7 +45,13 @@ public class Recipe {
|
|||||||
|
|
||||||
if (!isShapedRecipe()) {
|
if (!isShapedRecipe()) {
|
||||||
// sort to ignore ingredients position in shapeless recipes
|
// sort to ignore ingredients position in shapeless recipes
|
||||||
this.ingredients.sort(Comparator.comparing(StackInfo::getItemStackGUID));
|
final Map<ItemStack, String> guids = new IdentityHashMap<>();
|
||||||
|
|
||||||
|
for (ItemStack ingredient : this.ingredients) {
|
||||||
|
guids.put(ingredient, StackInfo.getItemStackGUID(ingredient));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.ingredients.sort(Comparator.comparing(guids::get));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,7 +60,10 @@ public class Recipe {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static RecipeId of(IRecipeHandler handler, int recipeIndex) {
|
public static RecipeId of(IRecipeHandler handler, int recipeIndex) {
|
||||||
final List<PositionedStack> ingredients = handler.getIngredientStacks(recipeIndex);
|
return of(handler, recipeIndex, handler.getIngredientStacks(recipeIndex));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RecipeId of(IRecipeHandler handler, int recipeIndex, List<PositionedStack> ingredients) {
|
||||||
final String handlerName = GuiRecipeTab.getHandlerInfo(handler).getHandlerName();
|
final String handlerName = GuiRecipeTab.getHandlerInfo(handler).getHandlerName();
|
||||||
PositionedStack pStackResult = handler.getResultStack(recipeIndex);
|
PositionedStack pStackResult = handler.getResultStack(recipeIndex);
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user