From 35a678ddb30571493eeb63e4d152a9bf2433d58c Mon Sep 17 00:00:00 2001 From: Jake Potrebic Date: Sun, 16 Jun 2024 08:08:06 -0700 Subject: [PATCH] update 'improve mass craft perf' patch --- .../Improve-performance-of-mass-crafts.patch | 138 ++++++++++++++++++ .../Improve-performance-of-mass-crafts.patch | 74 ---------- 2 files changed, 138 insertions(+), 74 deletions(-) create mode 100644 patches/server/Improve-performance-of-mass-crafts.patch delete mode 100644 patches/unapplied/server/Improve-performance-of-mass-crafts.patch diff --git a/patches/server/Improve-performance-of-mass-crafts.patch b/patches/server/Improve-performance-of-mass-crafts.patch new file mode 100644 index 0000000000..3d70ffd0f1 --- /dev/null +++ b/patches/server/Improve-performance-of-mass-crafts.patch @@ -0,0 +1,138 @@ +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); diff --git a/patches/unapplied/server/Improve-performance-of-mass-crafts.patch b/patches/unapplied/server/Improve-performance-of-mass-crafts.patch deleted file mode 100644 index 396d7fd70f..0000000000 --- a/patches/unapplied/server/Improve-performance-of-mass-crafts.patch +++ /dev/null @@ -1,74 +0,0 @@ -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/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 { - if (!world.isClientSide) { - ServerPlayer entityplayer = (ServerPlayer) player; - ItemStack itemstack = ItemStack.EMPTY; -- Optional> optional = world.getServer().getRecipeManager().getRecipeFor(RecipeType.CRAFTING, craftingInventory, world); -+ final RecipeHolder currentRecipe = craftingInventory.getCurrentRecipe(); // Paper - Perf: Improve mass crafting; check last recipe used first -+ Optional> optional = currentRecipe == null ? world.getServer().getRecipeManager().getRecipeFor(RecipeType.CRAFTING, craftingInventory, world) : world.getServer().getRecipeManager().getRecipeFor(RecipeType.CRAFTING, craftingInventory, world, currentRecipe.id()); // Paper - Perf: Improve mass crafting; check last recipe used first - - if (optional.isPresent()) { - RecipeHolder recipeholder = (RecipeHolder) optional.get(); -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 { - @Override - public void onTake(Player player, ItemStack stack) { - this.checkTakeAchievements(stack); -- NonNullList nonNullList = player.level().getRecipeManager().getRemainingItemsFor(RecipeType.CRAFTING, this.craftSlots, player.level()); -+ NonNullList nonNullList = player.level().getRecipeManager().getRemainingItemsFor(RecipeType.CRAFTING, this.craftSlots, player.level(), this.craftSlots.getCurrentRecipe() != null ? this.craftSlots.getCurrentRecipe().id() : null); // Paper - Perf: Improve mass crafting; check last recipe used first - - for (int i = 0; i < nonNullList.size(); i++) { - ItemStack itemStack = this.craftSlots.getItem(i); -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 { - RecipeHolder recipeholder = this.byKeyTyped(type, id); - - if (recipeholder != null && recipeholder.value().matches(inventory, world)) { -+ inventory.setCurrentRecipe(recipeholder); // Paper - Perf: Improve mass crafting - return Optional.of(recipeholder); - } - } -@@ -0,0 +0,0 @@ public class RecipeManager extends SimpleJsonResourceReloadListener { - } - - public > NonNullList getRemainingItemsFor(RecipeType type, C inventory, Level world) { -- Optional> optional = this.getRecipeFor(type, inventory, world); -+ // Paper start - Perf: Improve mass crafting;; check last recipe used first -+ return this.getRemainingItemsFor(type, inventory, world, null); -+ } -+ public > NonNullList getRemainingItemsFor(RecipeType type, C inventory, Level world, @Nullable ResourceLocation firstToCheck) { -+ Optional> optional = firstToCheck == null ? this.getRecipeFor(type, inventory, world) : this.getRecipeFor(type, inventory, world, firstToCheck); -+ // Paper end - Perf: Improve mass crafting - - if (optional.isPresent()) { - return ((RecipeHolder) optional.get()).value().getRemainingItems(inventory);