PaperMC/patches/server/1023-Improve-performance-of-mass-crafts.patch
Jake Potrebic bd38e0318a
Updated Upstream (Bukkit/CraftBukkit) (#10379)
Updated Upstream (Bukkit/CraftBukkit)

Upstream has released updates that appear to apply and compile correctly.
This update has not been tested by PaperMC and as with ANY update, please do your own testing

Bukkit Changes:
f02baa38 PR-988: Add World#getIntersectingChunks(BoundingBox)
9321d665 Move getItemInUse up to LivingEntity
819eef73 PR-959: Add access to current item's remaining ticks
c4fdadb0 SPIGOT-7601: Add AbstractArrow#getItem
be8261ca Add support for Java 22
26119676 PR-979: Add more translation keys
66753362 PR-985: Correct book maximum pages and characters per page documentation
c8be92fa PR-980: Improve getArmorContents() documentation
f1120ee2 PR-983: Expose riptide velocity to PlayerRiptideEvent

CraftBukkit Changes:
dfaa89bbe PR-1369: Add World#getIntersectingChunks(BoundingBox)
51bbab2b9 Move getItemInUse up to LivingEntity
668e09602 PR-1331: Add access to current item's remaining ticks
a639406d1 SPIGOT-7601: Add AbstractArrow#getItem
0398930fc SPIGOT-7602: Allow opening in-world horse and related inventories
ffd15611c SPIGOT-7608: Allow empty lists to morph to any PDT list
2188dcfa9 Add support for Java 22
45d6a609f SPIGOT-7604: Revert "SPIGOT-7365: DamageCause blocked by shield should trigger invulnerableTime"
06d915943 SPIGOT-7365: DamageCause blocked by shield should trigger invulnerableTime
ca3bc3707 PR-1361: Add more translation keys
366c3ca80 SPIGOT-7600: EntityChangeBlockEvent is not fired for frog eggs
06d0f9ba8 SPIGOT-7593: Fix sapling growth physics / client-side updates
45c2608e4 PR-1366: Expose riptide velocity to PlayerRiptideEvent
29b6bb79b SPIGOT-7587: Remove fixes for now-resolved MC-142590 and MC-109346
2024-04-06 12:53:39 -07:00

83 lines
6.5 KiB
Diff

From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Jake Potrebic <jake.m.potrebic@gmail.com>
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);