mirror of
https://github.com/PaperMC/Paper.git
synced 2024-12-28 20:07:41 +01:00
update 'improve mass craft perf' patch
This commit is contained in:
parent
b6b5a57b8b
commit
35a678ddb3
138
patches/server/Improve-performance-of-mass-crafts.patch
Normal file
138
patches/server/Improve-performance-of-mass-crafts.patch
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
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/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<ItemStack> getItems();
|
||||||
|
|
||||||
|
// CraftBukkit start
|
||||||
|
- default RecipeHolder<?> getCurrentRecipe() {
|
||||||
|
+ default RecipeHolder<net.minecraft.world.item.crafting.CraftingRecipe> getCurrentRecipe() { // Paper - use correct generic
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
- default void setCurrentRecipe(RecipeHolder<?> recipe) {
|
||||||
|
+ default void setCurrentRecipe(RecipeHolder<net.minecraft.world.item.crafting.CraftingRecipe> 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, CraftingRecipe>
|
||||||
|
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<RecipeHolder<CraftingRecipe>> 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<ItemStack> nonNullList = player.level().getRecipeManager().getRemainingItemsFor(RecipeType.CRAFTING, craftingInput, player.level());
|
||||||
|
+ NonNullList<ItemStack> 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<HumanEntity> transaction = new java.util.ArrayList<HumanEntity>();
|
||||||
|
- private RecipeHolder<?> currentRecipe;
|
||||||
|
+ private RecipeHolder<net.minecraft.world.item.crafting.CraftingRecipe> 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<net.minecraft.world.item.crafting.CraftingRecipe> getCurrentRecipe() { // Paper - use correct generic
|
||||||
|
return this.currentRecipe;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
- public void setCurrentRecipe(RecipeHolder<?> currentRecipe) {
|
||||||
|
+ public void setCurrentRecipe(RecipeHolder<net.minecraft.world.item.crafting.CraftingRecipe> 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 <I extends RecipeInput, T extends Recipe<I>> Optional<RecipeHolder<T>> getRecipeFor(RecipeType<T> type, I input, Level world, @Nullable RecipeHolder<T> recipe) {
|
||||||
|
- // CraftBukkit start
|
||||||
|
- List<RecipeHolder<T>> list = this.byType(type).stream().filter((recipeholder1) -> {
|
||||||
|
- return recipeholder1.value().matches(input, world);
|
||||||
|
- }).toList();
|
||||||
|
- Optional<RecipeHolder<T>> 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<RecipeHolder<T>> 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 <I extends RecipeInput, T extends Recipe<I>> List<RecipeHolder<T>> getAllRecipesFor(RecipeType<T> type) {
|
||||||
|
@@ -0,0 +0,0 @@ public class RecipeManager extends SimpleJsonResourceReloadListener {
|
||||||
|
}
|
||||||
|
|
||||||
|
public <I extends RecipeInput, T extends Recipe<I>> NonNullList<ItemStack> getRemainingItemsFor(RecipeType<T> type, I input, Level world) {
|
||||||
|
- Optional<RecipeHolder<T>> optional = this.getRecipeFor(type, input, world);
|
||||||
|
+ // Paper start - Perf: improve performance of mass crafts
|
||||||
|
+ return this.getRemainingItemsFor(type, input, world, null);
|
||||||
|
+ }
|
||||||
|
+ public <I extends RecipeInput, T extends Recipe<I>> NonNullList<ItemStack> getRemainingItemsFor(RecipeType<T> type, I input, Level world, @Nullable RecipeHolder<T> previousRecipe) {
|
||||||
|
+ Optional<RecipeHolder<T>> 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);
|
@ -1,74 +0,0 @@
|
|||||||
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 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<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()); // 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 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<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 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<T> 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 <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);
|
|
||||||
+ // Paper end - Perf: Improve mass crafting
|
|
||||||
|
|
||||||
if (optional.isPresent()) {
|
|
||||||
return ((RecipeHolder) optional.get()).value().getRemainingItems(inventory);
|
|
Loading…
Reference in New Issue
Block a user