aboutsummaryrefslogtreecommitdiffhomepage
path: root/patches/server/1027-Improve-performance-of-mass-crafts.patch
diff options
context:
space:
mode:
Diffstat (limited to 'patches/server/1027-Improve-performance-of-mass-crafts.patch')
-rw-r--r--patches/server/1027-Improve-performance-of-mass-crafts.patch83
1 files changed, 83 insertions, 0 deletions
diff --git a/patches/server/1027-Improve-performance-of-mass-crafts.patch b/patches/server/1027-Improve-performance-of-mass-crafts.patch
new file mode 100644
index 0000000000..640d0204e2
--- /dev/null
+++ b/patches/server/1027-Improve-performance-of-mass-crafts.patch
@@ -0,0 +1,83 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <[email protected]>
+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 7a0c1a55a211035bbca7b97293e94b04ae308bae..c3800bdd5096cb06e085e28f6bf0f65586ecf11e 100644
+--- a/src/main/java/net/minecraft/world/inventory/CraftingMenu.java
++++ b/src/main/java/net/minecraft/world/inventory/CraftingMenu.java
+@@ -76,7 +76,8 @@ public class CraftingMenu extends RecipeBookMenu<CraftingContainer> {
+ if (!world.isClientSide) {
+ ServerPlayer entityplayer = (ServerPlayer) player;
+ ItemStack itemstack = ItemStack.EMPTY;
+- Optional<RecipeHolder<CraftingRecipe>> 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<RecipeHolder<CraftingRecipe>> optional = currentRecipe == null ? world.getServer().getRecipeManager().getRecipeFor(RecipeType.CRAFTING, craftingInventory, world) : world.getServer().getRecipeManager().getRecipeFor(RecipeType.CRAFTING, craftingInventory, world, currentRecipe.id()).map(com.mojang.datafixers.util.Pair::getSecond); // Paper - Perf: Improve mass crafting; check last recipe used first
+
+ if (optional.isPresent()) {
+ RecipeHolder<CraftingRecipe> 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 7b2ac37e8bd305919f04ded043e50f13b3fe4253..245731757f2593c736916ac6ee59e2c91d179934 100644
+--- a/src/main/java/net/minecraft/world/inventory/ResultSlot.java
++++ b/src/main/java/net/minecraft/world/inventory/ResultSlot.java
+@@ -59,7 +59,7 @@ public class ResultSlot extends Slot {
+ @Override
+ public void onTake(Player player, ItemStack stack) {
+ this.checkTakeAchievements(stack);
+- NonNullList<ItemStack> nonNullList = player.level().getRecipeManager().getRemainingItemsFor(RecipeType.CRAFTING, this.craftSlots, player.level());
++ NonNullList<ItemStack> 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 a0ab3c55826af292d1cdac05648139b4d31f1376..d87124f5356180a37e581febc6141fdc5f1395a7 100644
+--- a/src/main/java/net/minecraft/world/item/crafting/RecipeManager.java
++++ b/src/main/java/net/minecraft/world/item/crafting/RecipeManager.java
+@@ -122,13 +122,16 @@ public class RecipeManager extends SimpleJsonResourceReloadListener {
+ RecipeHolder<T> recipeholder = (RecipeHolder) map.get(id);
+
+ if (recipeholder != null && recipeholder.value().matches(inventory, world)) {
++ inventory.setCurrentRecipe(recipeholder); // Paper - Perf: Improve mass crafting
+ return Optional.of(Pair.of(id, recipeholder));
+ }
+ }
+
++ inventory.setCurrentRecipe(null); // Paper - Perf: Improve mass crafting;; clear before it might be set again
+ return map.entrySet().stream().filter((entry) -> {
+ return ((RecipeHolder) entry.getValue()).value().matches(inventory, world);
+ }).findFirst().map((entry) -> {
++ inventory.setCurrentRecipe(entry.getValue()); // Paper - Perf: Improve mass crafting
+ return Pair.of((ResourceLocation) entry.getKey(), (RecipeHolder) entry.getValue());
+ });
+ }
+@@ -150,7 +153,12 @@ public class RecipeManager extends SimpleJsonResourceReloadListener {
+ }
+
+ public <C extends Container, T extends Recipe<C>> NonNullList<ItemStack> getRemainingItemsFor(RecipeType<T> type, C inventory, Level world) {
+- Optional<RecipeHolder<T>> 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 <C extends Container, T extends Recipe<C>> NonNullList<ItemStack> getRemainingItemsFor(RecipeType<T> type, C inventory, Level world, @Nullable ResourceLocation firstToCheck) {
++ Optional<RecipeHolder<T>> optional = firstToCheck == null ? this.getRecipeFor(type, inventory, world) : this.getRecipeFor(type, inventory, world, firstToCheck).map(Pair::getSecond);
++ // Paper end - Perf: Improve mass crafting
+
+ if (optional.isPresent()) {
+ return ((RecipeHolder) optional.get()).value().getRemainingItems(inventory);