From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Jake Potrebic Date: Sun, 13 Aug 2023 15:41:52 -0700 Subject: [PATCH] Improve performance of mass crafts When the server crafts all available items in CraftingMenu or InventoryMenu the game checks either 4 or 9 times for each individual craft for a matching recipe for that container. This check can be expensive if 64 total crafts are being performed with the recipe matching logic being run 64 * 9 + 64 times. A breakdown of those times is below. This patch caches the last matching recipe so that it is checked first and only if it doesn't match does the rest of the matching logic run. Shift-click crafts are processed one at a time, so shift clicking on an item in the result of a iron block craft where all the 9 inputs are full stacks of iron will run 64 iron block crafts. For each of those crafts, the 'remaining' blocks are calculated. This is due to recipes that have leftover items like buckets. This is done for each craft, and done once to get the full 9 leftover items which are usually air. Then 1 item is removed from each of the 9 inputs and each time that happens, logic is triggered to update the result itemstack. So for each craft, that logic is run 9 times (hence the 64 * 9). The + 64 is from the 64 checks for remaining items. After this patch, the full iteration over all recipes checking for a match should run once for a full craft to find the initial recipe match. Then that recipe will be checked first for all future recipe match checks. diff --git a/src/main/java/net/minecraft/world/inventory/CraftingContainer.java b/src/main/java/net/minecraft/world/inventory/CraftingContainer.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/world/inventory/CraftingContainer.java +++ b/src/main/java/net/minecraft/world/inventory/CraftingContainer.java @@ -0,0 +0,0 @@ public interface CraftingContainer extends Container, StackedContentsCompatible List getItems(); // CraftBukkit start - default RecipeHolder getCurrentRecipe() { + default RecipeHolder getCurrentRecipe() { // Paper - use correct generic return null; } - default void setCurrentRecipe(RecipeHolder recipe) { + default void setCurrentRecipe(RecipeHolder recipe) { // Paper - use correct generic } // CraftBukkit end diff --git a/src/main/java/net/minecraft/world/inventory/CraftingMenu.java b/src/main/java/net/minecraft/world/inventory/CraftingMenu.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/world/inventory/CraftingMenu.java +++ b/src/main/java/net/minecraft/world/inventory/CraftingMenu.java @@ -0,0 +0,0 @@ public class CraftingMenu extends RecipeBookMenu CraftingInput craftinginput = craftingInventory.asCraftInput(); ServerPlayer entityplayer = (ServerPlayer) player; ItemStack itemstack = ItemStack.EMPTY; + if (recipe == null) recipe = craftingInventory.getCurrentRecipe(); // Paper - Perf: Improve mass crafting; check last recipe used first Optional> optional = world.getServer().getRecipeManager().getRecipeFor(RecipeType.CRAFTING, craftinginput, world, recipe); craftingInventory.setCurrentRecipe(optional.orElse(null)); // CraftBukkit diff --git a/src/main/java/net/minecraft/world/inventory/ResultSlot.java b/src/main/java/net/minecraft/world/inventory/ResultSlot.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/world/inventory/ResultSlot.java +++ b/src/main/java/net/minecraft/world/inventory/ResultSlot.java @@ -0,0 +0,0 @@ public class ResultSlot extends Slot { CraftingInput craftingInput = positioned.input(); int i = positioned.left(); int j = positioned.top(); - NonNullList nonNullList = player.level().getRecipeManager().getRemainingItemsFor(RecipeType.CRAFTING, craftingInput, player.level()); + NonNullList nonNullList = player.level().getRecipeManager().getRemainingItemsFor(RecipeType.CRAFTING, craftingInput, player.level(), this.craftSlots.getCurrentRecipe()); // Paper - Perf: Improve mass crafting; check last recipe used first for (int k = 0; k < craftingInput.height(); k++) { for (int l = 0; l < craftingInput.width(); l++) { diff --git a/src/main/java/net/minecraft/world/inventory/TransientCraftingContainer.java b/src/main/java/net/minecraft/world/inventory/TransientCraftingContainer.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/world/inventory/TransientCraftingContainer.java +++ b/src/main/java/net/minecraft/world/inventory/TransientCraftingContainer.java @@ -0,0 +0,0 @@ public class TransientCraftingContainer implements CraftingContainer { // CraftBukkit start - add fields public List transaction = new java.util.ArrayList(); - private RecipeHolder currentRecipe; + private RecipeHolder currentRecipe; // Paper - use correct generic public Container resultInventory; private Player owner; private int maxStack = MAX_STACK; @@ -0,0 +0,0 @@ public class TransientCraftingContainer implements CraftingContainer { } @Override - public RecipeHolder getCurrentRecipe() { + public RecipeHolder getCurrentRecipe() { // Paper - use correct generic return this.currentRecipe; } @Override - public void setCurrentRecipe(RecipeHolder currentRecipe) { + public void setCurrentRecipe(RecipeHolder currentRecipe) { // Paper - use correct generic this.currentRecipe = currentRecipe; } diff --git a/src/main/java/net/minecraft/world/item/crafting/RecipeManager.java b/src/main/java/net/minecraft/world/item/crafting/RecipeManager.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/world/item/crafting/RecipeManager.java +++ b/src/main/java/net/minecraft/world/item/crafting/RecipeManager.java @@ -0,0 +0,0 @@ public class RecipeManager extends SimpleJsonResourceReloadListener { } public > Optional> getRecipeFor(RecipeType type, I input, Level world, @Nullable RecipeHolder recipe) { - // CraftBukkit start - List> list = this.byType(type).stream().filter((recipeholder1) -> { - return recipeholder1.value().matches(input, world); - }).toList(); - Optional> recipe1 = (list.isEmpty() || input.isEmpty()) ? Optional.empty() : (recipe != null && recipe.value().matches(input, world) ? Optional.of(recipe) : Optional.of(list.getLast())); // CraftBukkit - SPIGOT-4638: last recipe gets priority - return recipe1; - // CraftBukkit end + // Paper start - fix upstream's complete screw up of checking last used recipe + if (input.isEmpty()) { + return Optional.empty(); + } else if (recipe != null && recipe.value().matches(input, world)) { + return Optional.of(recipe); + } else { + // CraftBukkit start + List> list = this.byType(type).stream().filter((recipeholder1) -> { + return recipeholder1.value().matches(input, world); + }).toList(); + return list.isEmpty() ? Optional.empty() : Optional.of(list.getLast()); // CraftBukkit - SPIGOT-4638: last recipe gets priority + // CraftBukkit end + } + // Paper end } public > List> getAllRecipesFor(RecipeType type) { @@ -0,0 +0,0 @@ public class RecipeManager extends SimpleJsonResourceReloadListener { } public > NonNullList getRemainingItemsFor(RecipeType type, I input, Level world) { - Optional> optional = this.getRecipeFor(type, input, world); + // Paper start - Perf: improve performance of mass crafts + return this.getRemainingItemsFor(type, input, world, null); + } + public > NonNullList getRemainingItemsFor(RecipeType type, I input, Level world, @Nullable RecipeHolder previousRecipe) { + Optional> optional = this.getRecipeFor(type, input, world, previousRecipe); + // Paper end - Perf: improve performance of mass crafts if (optional.isPresent()) { return ((RecipeHolder) optional.get()).value().getRemainingItems(input);