diff options
Diffstat (limited to 'paper-server/patches/features/0029-Optimize-Hoppers.patch')
-rw-r--r-- | paper-server/patches/features/0029-Optimize-Hoppers.patch | 683 |
1 files changed, 683 insertions, 0 deletions
diff --git a/paper-server/patches/features/0029-Optimize-Hoppers.patch b/paper-server/patches/features/0029-Optimize-Hoppers.patch new file mode 100644 index 0000000000..c81544a817 --- /dev/null +++ b/paper-server/patches/features/0029-Optimize-Hoppers.patch @@ -0,0 +1,683 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar <[email protected]> +Date: Wed, 27 Apr 2016 22:09:52 -0400 +Subject: [PATCH] Optimize Hoppers + +* Removes unnecessary extra calls to .update() that are very expensive +* Lots of itemstack cloning removed. Only clone if the item is actually moved +* Return true when a plugin cancels inventory move item event instead of false, as false causes pulls to cycle through all items. + However, pushes do not exhibit the same behavior, so this is not something plugins could of been relying on. +* Add option (Default on) to cooldown hoppers when they fail to move an item due to full inventory +* Skip subsequent InventoryMoveItemEvents if a plugin does not use the item after first event fire for an iteration by tracking changes to the event via an internal event implementation. +* Don't check for Entities with Inventories if the block above us is also occluding (not just Inventoried) +* Remove Streams from Item Suck In and restore restore 1.12 AABB checks which is simpler and no voxel allocations (was doing TWO Item Suck ins) + +diff --git a/io/papermc/paper/event/inventory/PaperInventoryMoveItemEvent.java b/io/papermc/paper/event/inventory/PaperInventoryMoveItemEvent.java +new file mode 100644 +index 0000000000000000000000000000000000000000..24a2090e068ad3c0d08705050944abdfe19136a2 +--- /dev/null ++++ b/io/papermc/paper/event/inventory/PaperInventoryMoveItemEvent.java +@@ -0,0 +1,29 @@ ++package io.papermc.paper.event.inventory; ++ ++import org.bukkit.event.inventory.InventoryMoveItemEvent; ++import org.bukkit.inventory.Inventory; ++import org.bukkit.inventory.ItemStack; ++import org.jspecify.annotations.NullMarked; ++ ++@NullMarked ++public class PaperInventoryMoveItemEvent extends InventoryMoveItemEvent { ++ ++ public boolean calledSetItem; ++ public boolean calledGetItem; ++ ++ public PaperInventoryMoveItemEvent(final Inventory sourceInventory, final ItemStack itemStack, final Inventory destinationInventory, final boolean didSourceInitiate) { ++ super(sourceInventory, itemStack, destinationInventory, didSourceInitiate); ++ } ++ ++ @Override ++ public ItemStack getItem() { ++ this.calledGetItem = true; ++ return super.getItem(); ++ } ++ ++ @Override ++ public void setItem(final ItemStack itemStack) { ++ super.setItem(itemStack); ++ this.calledSetItem = true; ++ } ++} +diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java +index 6dbae12bbfd47cd4e75bc3089561e8e226e9e604..9c859025302ddb2c20cf6457fa4e4eaf7fbafdd7 100644 +--- a/net/minecraft/server/MinecraftServer.java ++++ b/net/minecraft/server/MinecraftServer.java +@@ -1707,6 +1707,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa + serverLevel.hasPhysicsEvent = org.bukkit.event.block.BlockPhysicsEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - BlockPhysicsEvent + serverLevel.hasEntityMoveEvent = io.papermc.paper.event.entity.EntityMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - Add EntityMoveEvent + serverLevel.updateLagCompensationTick(); // Paper - lag compensation ++ net.minecraft.world.level.block.entity.HopperBlockEntity.skipHopperEvents = serverLevel.paperConfig().hopper.disableMoveEvent || org.bukkit.event.inventory.InventoryMoveItemEvent.getHandlerList().getRegisteredListeners().length == 0; // Paper - Perf: Optimize Hoppers + profilerFiller.push(() -> serverLevel + " " + serverLevel.dimension().location()); + /* Drop global time updates + if (this.tickCount % 20 == 0) { +diff --git a/net/minecraft/world/item/ItemStack.java b/net/minecraft/world/item/ItemStack.java +index c255e11cb0981bd7e0456d4fd401beb5257be597..d6361863d6a1e364de262d6199373cbd68d1c699 100644 +--- a/net/minecraft/world/item/ItemStack.java ++++ b/net/minecraft/world/item/ItemStack.java +@@ -808,10 +808,16 @@ public final class ItemStack implements DataComponentHolder { + } + + public ItemStack copy() { +- if (this.isEmpty()) { ++ // Paper start - Perf: Optimize Hoppers ++ return this.copy(false); ++ } ++ ++ public ItemStack copy(final boolean originalItem) { ++ if (!originalItem && this.isEmpty()) { ++ // Paper end - Perf: Optimize Hoppers + return EMPTY; + } else { +- ItemStack itemStack = new ItemStack(this.getItem(), this.count, this.components.copy()); ++ ItemStack itemStack = new ItemStack(originalItem ? this.item : this.getItem(), this.count, this.components.copy()); // Paper - Perf: Optimize Hoppers + itemStack.setPopTime(this.getPopTime()); + return itemStack; + } +diff --git a/net/minecraft/world/level/block/entity/BlockEntity.java b/net/minecraft/world/level/block/entity/BlockEntity.java +index 2ebdf1ad323bb53dfe9eed319e25856b35a1443c..77618757c0e678532dbab814aceed83f7f1cd892 100644 +--- a/net/minecraft/world/level/block/entity/BlockEntity.java ++++ b/net/minecraft/world/level/block/entity/BlockEntity.java +@@ -26,6 +26,7 @@ import net.minecraft.world.level.block.state.BlockState; + import org.slf4j.Logger; + + public abstract class BlockEntity { ++ static boolean ignoreBlockEntityUpdates; // Paper - Perf: Optimize Hoppers + // CraftBukkit start - data containers + private static final org.bukkit.craftbukkit.persistence.CraftPersistentDataTypeRegistry DATA_TYPE_REGISTRY = new org.bukkit.craftbukkit.persistence.CraftPersistentDataTypeRegistry(); + public org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer persistentDataContainer; +@@ -196,6 +197,7 @@ public abstract class BlockEntity { + + public void setChanged() { + if (this.level != null) { ++ if (ignoreBlockEntityUpdates) return; // Paper - Perf: Optimize Hoppers + setChanged(this.level, this.worldPosition, this.blockState); + } + } +diff --git a/net/minecraft/world/level/block/entity/HopperBlockEntity.java b/net/minecraft/world/level/block/entity/HopperBlockEntity.java +index e58a32593e8b42bfc534d13457240860293dd3f4..5cd1326ad5d046c88b2b3449d610a78fa880b4cd 100644 +--- a/net/minecraft/world/level/block/entity/HopperBlockEntity.java ++++ b/net/minecraft/world/level/block/entity/HopperBlockEntity.java +@@ -139,18 +139,56 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + } + } + ++ // Paper start - Perf: Optimize Hoppers ++ private static final int HOPPER_EMPTY = 0; ++ private static final int HOPPER_HAS_ITEMS = 1; ++ private static final int HOPPER_IS_FULL = 2; ++ ++ private static int getFullState(final HopperBlockEntity hopper) { ++ hopper.unpackLootTable(null); ++ ++ final List<ItemStack> hopperItems = hopper.items; ++ ++ boolean empty = true; ++ boolean full = true; ++ ++ for (int i = 0, len = hopperItems.size(); i < len; ++i) { ++ final ItemStack stack = hopperItems.get(i); ++ if (stack.isEmpty()) { ++ full = false; ++ continue; ++ } ++ ++ if (!full) { ++ // can't be full ++ return HOPPER_HAS_ITEMS; ++ } ++ ++ empty = false; ++ ++ if (stack.getCount() != stack.getMaxStackSize()) { ++ // can't be full or empty ++ return HOPPER_HAS_ITEMS; ++ } ++ } ++ ++ return empty ? HOPPER_EMPTY : (full ? HOPPER_IS_FULL : HOPPER_HAS_ITEMS); ++ } ++ // Paper end - Perf: Optimize Hoppers ++ + private static boolean tryMoveItems(Level level, BlockPos pos, BlockState state, HopperBlockEntity blockEntity, BooleanSupplier validator) { + if (level.isClientSide) { + return false; + } else { + if (!blockEntity.isOnCooldown() && state.getValue(HopperBlock.ENABLED)) { + boolean flag = false; +- if (!blockEntity.isEmpty()) { ++ final int fullState = getFullState(blockEntity); // Paper - Perf: Optimize Hoppers ++ if (fullState != HOPPER_EMPTY) { // Paper - Perf: Optimize Hoppers + flag = ejectItems(level, pos, blockEntity); + } + +- if (!blockEntity.inventoryFull()) { +- flag |= validator.getAsBoolean(); ++ if (fullState != HOPPER_IS_FULL || flag) { // Paper - Perf: Optimize Hoppers ++ flag |= validator.getAsBoolean(); // Paper - note: this is not a validator, it's what adds/sucks in items + } + + if (flag) { +@@ -174,6 +212,206 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + return true; + } + ++ // Paper start - Perf: Optimize Hoppers ++ public static boolean skipHopperEvents; ++ private static boolean skipPullModeEventFire; ++ private static boolean skipPushModeEventFire; ++ ++ private static boolean hopperPush(final Level level, final Container destination, final Direction direction, final HopperBlockEntity hopper) { ++ skipPushModeEventFire = skipHopperEvents; ++ boolean foundItem = false; ++ for (int i = 0; i < hopper.getContainerSize(); ++i) { ++ final ItemStack item = hopper.getItem(i); ++ if (!item.isEmpty()) { ++ foundItem = true; ++ ItemStack origItemStack = item; ++ ItemStack movedItem = origItemStack; ++ ++ final int originalItemCount = origItemStack.getCount(); ++ final int movedItemCount = Math.min(level.spigotConfig.hopperAmount, originalItemCount); ++ origItemStack.setCount(movedItemCount); ++ ++ // We only need to fire the event once to give protection plugins a chance to cancel this event ++ // Because nothing uses getItem, every event call should end up the same result. ++ if (!skipPushModeEventFire) { ++ movedItem = callPushMoveEvent(destination, movedItem, hopper); ++ if (movedItem == null) { // cancelled ++ origItemStack.setCount(originalItemCount); ++ return false; ++ } ++ } ++ ++ final ItemStack remainingItem = addItem(hopper, destination, movedItem, direction); ++ final int remainingItemCount = remainingItem.getCount(); ++ if (remainingItemCount != movedItemCount) { ++ origItemStack = origItemStack.copy(true); ++ origItemStack.setCount(originalItemCount); ++ if (!origItemStack.isEmpty()) { ++ origItemStack.setCount(originalItemCount - movedItemCount + remainingItemCount); ++ } ++ hopper.setItem(i, origItemStack); ++ destination.setChanged(); ++ return true; ++ } ++ origItemStack.setCount(originalItemCount); ++ } ++ } ++ if (foundItem && level.paperConfig().hopper.cooldownWhenFull) { // Inventory was full - cooldown ++ hopper.setCooldown(level.spigotConfig.hopperTransfer); ++ } ++ return false; ++ } ++ ++ private static boolean hopperPull(final Level level, final Hopper hopper, final Container container, ItemStack origItemStack, final int i) { ++ ItemStack movedItem = origItemStack; ++ final int originalItemCount = origItemStack.getCount(); ++ final int movedItemCount = Math.min(level.spigotConfig.hopperAmount, originalItemCount); ++ container.setChanged(); // original logic always marks source inv as changed even if no move happens. ++ movedItem.setCount(movedItemCount); ++ ++ if (!skipPullModeEventFire) { ++ movedItem = callPullMoveEvent(hopper, container, movedItem); ++ if (movedItem == null) { // cancelled ++ origItemStack.setCount(originalItemCount); ++ // Drastically improve performance by returning true. ++ // No plugin could have relied on the behavior of false as the other call ++ // site for IMIE did not exhibit the same behavior ++ return true; ++ } ++ } ++ ++ final ItemStack remainingItem = addItem(container, hopper, movedItem, null); ++ final int remainingItemCount = remainingItem.getCount(); ++ if (remainingItemCount != movedItemCount) { ++ origItemStack = origItemStack.copy(true); ++ origItemStack.setCount(originalItemCount); ++ if (!origItemStack.isEmpty()) { ++ origItemStack.setCount(originalItemCount - movedItemCount + remainingItemCount); ++ } ++ ++ ignoreBlockEntityUpdates = true; ++ container.setItem(i, origItemStack); ++ ignoreBlockEntityUpdates = false; ++ container.setChanged(); ++ return true; ++ } ++ origItemStack.setCount(originalItemCount); ++ ++ if (level.paperConfig().hopper.cooldownWhenFull) { ++ applyCooldown(hopper); ++ } ++ ++ return false; ++ } ++ ++ @Nullable ++ private static ItemStack callPushMoveEvent(Container destination, ItemStack itemStack, HopperBlockEntity hopper) { ++ final org.bukkit.inventory.Inventory destinationInventory = getInventory(destination); ++ final io.papermc.paper.event.inventory.PaperInventoryMoveItemEvent event = new io.papermc.paper.event.inventory.PaperInventoryMoveItemEvent( ++ hopper.getOwner(false).getInventory(), ++ org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemStack), ++ destinationInventory, ++ true ++ ); ++ final boolean result = event.callEvent(); ++ if (!event.calledGetItem && !event.calledSetItem) { ++ skipPushModeEventFire = true; ++ } ++ if (!result) { ++ applyCooldown(hopper); ++ return null; ++ } ++ ++ if (event.calledSetItem) { ++ return org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getItem()); ++ } else { ++ return itemStack; ++ } ++ } ++ ++ @Nullable ++ private static ItemStack callPullMoveEvent(final Hopper hopper, final Container container, final ItemStack itemstack) { ++ final org.bukkit.inventory.Inventory sourceInventory = getInventory(container); ++ final org.bukkit.inventory.Inventory destination = getInventory(hopper); ++ ++ // Mirror is safe as no plugins ever use this item ++ final io.papermc.paper.event.inventory.PaperInventoryMoveItemEvent event = new io.papermc.paper.event.inventory.PaperInventoryMoveItemEvent(sourceInventory, org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack), destination, false); ++ final boolean result = event.callEvent(); ++ if (!event.calledGetItem && !event.calledSetItem) { ++ skipPullModeEventFire = true; ++ } ++ if (!result) { ++ applyCooldown(hopper); ++ return null; ++ } ++ ++ if (event.calledSetItem) { ++ return org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getItem()); ++ } else { ++ return itemstack; ++ } ++ } ++ ++ private static org.bukkit.inventory.Inventory getInventory(final Container container) { ++ final org.bukkit.inventory.Inventory sourceInventory; ++ if (container instanceof net.minecraft.world.CompoundContainer compoundContainer) { ++ // Have to special-case large chests as they work oddly ++ sourceInventory = new org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest(compoundContainer); ++ } else if (container instanceof BlockEntity blockEntity) { ++ sourceInventory = blockEntity.getOwner(false).getInventory(); ++ } else if (container.getOwner() != null) { ++ sourceInventory = container.getOwner().getInventory(); ++ } else { ++ sourceInventory = new org.bukkit.craftbukkit.inventory.CraftInventory(container); ++ } ++ return sourceInventory; ++ } ++ ++ private static void applyCooldown(final Hopper hopper) { ++ if (hopper instanceof HopperBlockEntity blockEntity && blockEntity.getLevel() != null) { ++ blockEntity.setCooldown(blockEntity.getLevel().spigotConfig.hopperTransfer); ++ } ++ } ++ ++ private static boolean allMatch(Container container, Direction direction, java.util.function.BiPredicate<ItemStack, Integer> test) { ++ if (container instanceof WorldlyContainer) { ++ for (int slot : ((WorldlyContainer) container).getSlotsForFace(direction)) { ++ if (!test.test(container.getItem(slot), slot)) { ++ return false; ++ } ++ } ++ } else { ++ int size = container.getContainerSize(); ++ for (int slot = 0; slot < size; slot++) { ++ if (!test.test(container.getItem(slot), slot)) { ++ return false; ++ } ++ } ++ } ++ return true; ++ } ++ ++ private static boolean anyMatch(Container container, Direction direction, java.util.function.BiPredicate<ItemStack, Integer> test) { ++ if (container instanceof WorldlyContainer) { ++ for (int slot : ((WorldlyContainer) container).getSlotsForFace(direction)) { ++ if (test.test(container.getItem(slot), slot)) { ++ return true; ++ } ++ } ++ } else { ++ int size = container.getContainerSize(); ++ for (int slot = 0; slot < size; slot++) { ++ if (test.test(container.getItem(slot), slot)) { ++ return true; ++ } ++ } ++ } ++ return true; ++ } ++ private static final java.util.function.BiPredicate<ItemStack, Integer> STACK_SIZE_TEST = (itemStack, i) -> itemStack.getCount() >= itemStack.getMaxStackSize(); ++ private static final java.util.function.BiPredicate<ItemStack, Integer> IS_EMPTY_TEST = (itemStack, i) -> itemStack.isEmpty(); ++ // Paper end - Perf: Optimize Hoppers ++ + private static boolean ejectItems(Level level, BlockPos pos, HopperBlockEntity blockEntity) { + Container attachedContainer = getAttachedContainer(level, pos, blockEntity); + if (attachedContainer == null) { +@@ -183,57 +421,60 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + if (isFullContainer(attachedContainer, opposite)) { + return false; + } else { +- for (int i = 0; i < blockEntity.getContainerSize(); i++) { +- ItemStack item = blockEntity.getItem(i); +- if (!item.isEmpty()) { +- int count = item.getCount(); +- // CraftBukkit start - Call event when pushing items into other inventories +- ItemStack original = item.copy(); +- org.bukkit.craftbukkit.inventory.CraftItemStack oitemstack = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror( +- blockEntity.removeItem(i, level.spigotConfig.hopperAmount) +- ); // Spigot +- +- org.bukkit.inventory.Inventory destinationInventory; +- // Have to special case large chests as they work oddly +- if (attachedContainer instanceof final net.minecraft.world.CompoundContainer compoundContainer) { +- destinationInventory = new org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest(compoundContainer); +- } else if (attachedContainer.getOwner() != null) { +- destinationInventory = attachedContainer.getOwner().getInventory(); +- } else { +- destinationInventory = new org.bukkit.craftbukkit.inventory.CraftInventory(attachedContainer); +- } +- +- org.bukkit.event.inventory.InventoryMoveItemEvent event = new org.bukkit.event.inventory.InventoryMoveItemEvent( +- blockEntity.getOwner().getInventory(), +- oitemstack, +- destinationInventory, +- true +- ); +- if (!event.callEvent()) { +- blockEntity.setItem(i, original); +- blockEntity.setCooldown(level.spigotConfig.hopperTransfer); // Delay hopper checks // Spigot +- return false; +- } +- int origCount = event.getItem().getAmount(); // Spigot +- ItemStack itemStack = HopperBlockEntity.addItem(blockEntity, attachedContainer, org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getItem()), opposite); +- // CraftBukkit end +- +- if (itemStack.isEmpty()) { +- attachedContainer.setChanged(); +- return true; +- } +- +- item.setCount(count); +- // Spigot start +- item.shrink(origCount - itemStack.getCount()); +- if (count <= level.spigotConfig.hopperAmount) { +- // Spigot end +- blockEntity.setItem(i, item); +- } +- } +- } +- +- return false; ++ // Paper start - Perf: Optimize Hoppers ++ return hopperPush(level, attachedContainer, opposite, blockEntity); ++ //for (int i = 0; i < blockEntity.getContainerSize(); i++) { ++ // ItemStack item = blockEntity.getItem(i); ++ // if (!item.isEmpty()) { ++ // int count = item.getCount(); ++ // // CraftBukkit start - Call event when pushing items into other inventories ++ // ItemStack original = item.copy(); ++ // org.bukkit.craftbukkit.inventory.CraftItemStack oitemstack = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror( ++ // blockEntity.removeItem(i, level.spigotConfig.hopperAmount) ++ // ); // Spigot ++ ++ // org.bukkit.inventory.Inventory destinationInventory; ++ // // Have to special case large chests as they work oddly ++ // if (attachedContainer instanceof final net.minecraft.world.CompoundContainer compoundContainer) { ++ // destinationInventory = new org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest(compoundContainer); ++ // } else if (attachedContainer.getOwner() != null) { ++ // destinationInventory = attachedContainer.getOwner().getInventory(); ++ // } else { ++ // destinationInventory = new org.bukkit.craftbukkit.inventory.CraftInventory(attachedContainer); ++ // } ++ ++ // org.bukkit.event.inventory.InventoryMoveItemEvent event = new org.bukkit.event.inventory.InventoryMoveItemEvent( ++ // blockEntity.getOwner().getInventory(), ++ // oitemstack, ++ // destinationInventory, ++ // true ++ // ); ++ // if (!event.callEvent()) { ++ // blockEntity.setItem(i, original); ++ // blockEntity.setCooldown(level.spigotConfig.hopperTransfer); // Delay hopper checks // Spigot ++ // return false; ++ // } ++ // int origCount = event.getItem().getAmount(); // Spigot ++ // ItemStack itemStack = HopperBlockEntity.addItem(blockEntity, attachedContainer, org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getItem()), opposite); ++ // // CraftBukkit end ++ ++ // if (itemStack.isEmpty()) { ++ // attachedContainer.setChanged(); ++ // return true; ++ // } ++ ++ // item.setCount(count); ++ // // Spigot start ++ // item.shrink(origCount - itemStack.getCount()); ++ // if (count <= level.spigotConfig.hopperAmount) { ++ // // Spigot end ++ // blockEntity.setItem(i, item); ++ // } ++ // } ++ //} ++ ++ //return false; ++ // Paper end - Perf: Optimize Hoppers + } + } + } +@@ -288,6 +529,7 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + Container sourceContainer = getSourceContainer(level, hopper, blockPos, blockState); + if (sourceContainer != null) { + Direction direction = Direction.DOWN; ++ skipPullModeEventFire = skipHopperEvents; // Paper - Perf: Optimize Hoppers + + for (int i : getSlots(sourceContainer, direction)) { + if (tryTakeInItemFromSlot(hopper, sourceContainer, i, direction, level)) { // Spigot +@@ -313,55 +555,58 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + private static boolean tryTakeInItemFromSlot(Hopper hopper, Container container, int slot, Direction direction, Level level) { // Spigot + ItemStack item = container.getItem(slot); + if (!item.isEmpty() && canTakeItemFromContainer(hopper, container, item, slot, direction)) { +- int count = item.getCount(); +- // CraftBukkit start - Call event on collection of items from inventories into the hopper +- ItemStack original = item.copy(); +- org.bukkit.craftbukkit.inventory.CraftItemStack oitemstack = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror( +- container.removeItem(slot, level.spigotConfig.hopperAmount) // Spigot +- ); +- +- org.bukkit.inventory.Inventory sourceInventory; +- // Have to special case large chests as they work oddly +- if (container instanceof final net.minecraft.world.CompoundContainer compoundContainer) { +- sourceInventory = new org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest(compoundContainer); +- } else if (container.getOwner() != null) { +- sourceInventory = container.getOwner().getInventory(); +- } else { +- sourceInventory = new org.bukkit.craftbukkit.inventory.CraftInventory(container); +- } +- +- org.bukkit.event.inventory.InventoryMoveItemEvent event = new org.bukkit.event.inventory.InventoryMoveItemEvent( +- sourceInventory, +- oitemstack, +- hopper.getOwner().getInventory(), +- false +- ); +- +- if (!event.callEvent()) { +- container.setItem(slot, original); +- +- if (hopper instanceof final HopperBlockEntity hopperBlockEntity) { +- hopperBlockEntity.setCooldown(level.spigotConfig.hopperTransfer); // Spigot +- } +- +- return false; +- } +- int origCount = event.getItem().getAmount(); // Spigot +- ItemStack itemStack = HopperBlockEntity.addItem(container, hopper, org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getItem()), null); +- // CraftBukkit end +- +- if (itemStack.isEmpty()) { +- container.setChanged(); +- return true; +- } +- +- item.setCount(count); +- // Spigot start +- item.shrink(origCount - itemStack.getCount()); +- if (count <= level.spigotConfig.hopperAmount) { +- // Spigot end +- container.setItem(slot, item); +- } ++ // Paper start - Perf: Optimize Hoppers ++ return hopperPull(level, hopper, container, item, slot); ++ //int count = item.getCount(); ++ //// CraftBukkit start - Call event on collection of items from inventories into the hopper ++ //ItemStack original = item.copy(); ++ //org.bukkit.craftbukkit.inventory.CraftItemStack oitemstack = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror( ++ // container.removeItem(slot, level.spigotConfig.hopperAmount) // Spigot ++ //); ++ ++ //org.bukkit.inventory.Inventory sourceInventory; ++ //// Have to special case large chests as they work oddly ++ //if (container instanceof final net.minecraft.world.CompoundContainer compoundContainer) { ++ // sourceInventory = new org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest(compoundContainer); ++ //} else if (container.getOwner() != null) { ++ // sourceInventory = container.getOwner().getInventory(); ++ //} else { ++ // sourceInventory = new org.bukkit.craftbukkit.inventory.CraftInventory(container); ++ //} ++ ++ //org.bukkit.event.inventory.InventoryMoveItemEvent event = new org.bukkit.event.inventory.InventoryMoveItemEvent( ++ // sourceInventory, ++ // oitemstack, ++ // hopper.getOwner().getInventory(), ++ // false ++ //); ++ ++ //if (!event.callEvent()) { ++ // container.setItem(slot, original); ++ ++ // if (hopper instanceof final HopperBlockEntity hopperBlockEntity) { ++ // hopperBlockEntity.setCooldown(level.spigotConfig.hopperTransfer); // Spigot ++ // } ++ ++ // return false; ++ //} ++ //int origCount = event.getItem().getAmount(); // Spigot ++ //ItemStack itemStack = HopperBlockEntity.addItem(container, hopper, org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getItem()), null); ++ //// CraftBukkit end ++ ++ //if (itemStack.isEmpty()) { ++ // container.setChanged(); ++ // return true; ++ //} ++ ++ //item.setCount(count); ++ //// Spigot start ++ //item.shrink(origCount - itemStack.getCount()); ++ //if (count <= level.spigotConfig.hopperAmount) { ++ // // Spigot end ++ // container.setItem(slot, item); ++ //} ++ // Paper end - Perf: Optimize Hoppers + } + + return false; +@@ -370,13 +615,15 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + public static boolean addItem(Container container, ItemEntity item) { + boolean flag = false; + // CraftBukkit start ++ if (org.bukkit.event.inventory.InventoryPickupItemEvent.getHandlerList().getRegisteredListeners().length > 0) { // Paper - optimize hoppers + org.bukkit.event.inventory.InventoryPickupItemEvent event = new org.bukkit.event.inventory.InventoryPickupItemEvent( +- container.getOwner().getInventory(), (org.bukkit.entity.Item) item.getBukkitEntity() ++ getInventory(container), (org.bukkit.entity.Item) item.getBukkitEntity() // Paper - Perf: Optimize Hoppers; use getInventory() to avoid snapshot creation + ); + if (!event.callEvent()) { + return false; + } + // CraftBukkit end ++ } // Paper - Perf: Optimize Hoppers + ItemStack itemStack = item.getItem().copy(); + ItemStack itemStack1 = addItem(null, container, itemStack, null); + if (itemStack1.isEmpty()) { +@@ -431,7 +678,9 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + stack = stack.split(destination.getMaxStackSize()); + } + // Spigot end ++ ignoreBlockEntityUpdates = true; // Paper - Perf: Optimize Hoppers + destination.setItem(slot, stack); ++ ignoreBlockEntityUpdates = false; // Paper - Perf: Optimize Hoppers + stack = leftover; // Paper - Make hoppers respect inventory max stack size + flag = true; + } else if (canMergeItems(item, stack)) { +@@ -519,13 +768,19 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + + @Nullable + public static Container getContainerAt(Level level, BlockPos pos) { +- return getContainerAt(level, pos, level.getBlockState(pos), pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5); ++ return getContainerAt(level, pos, level.getBlockState(pos), pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5, true); // Paper - Optimize hoppers + } + + @Nullable + private static Container getContainerAt(Level level, BlockPos pos, BlockState state, double x, double y, double z) { ++ // Paper start - Perf: Optimize Hoppers ++ return HopperBlockEntity.getContainerAt(level, pos, state, x, y, z, false); ++ } ++ @Nullable ++ private static Container getContainerAt(Level level, BlockPos pos, BlockState state, double x, double y, double z, final boolean optimizeEntities) { ++ // Paper end - Perf: Optimize Hoppers + Container blockContainer = getBlockContainer(level, pos, state); +- if (blockContainer == null) { ++ if (blockContainer == null && (!optimizeEntities || !level.paperConfig().hopper.ignoreOccludingBlocks || !state.getBukkitMaterial().isOccluding())) { // Paper - Perf: Optimize Hoppers + blockContainer = getEntityContainer(level, x, y, z); + } + +@@ -551,14 +806,14 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + + @Nullable + private static Container getEntityContainer(Level level, double x, double y, double z) { +- List<Entity> entities = level.getEntities( +- (Entity)null, new AABB(x - 0.5, y - 0.5, z - 0.5, x + 0.5, y + 0.5, z + 0.5), EntitySelector.CONTAINER_ENTITY_SELECTOR ++ List<Entity> entities = level.getEntitiesOfClass( ++ (Class) Container.class, new AABB(x - 0.5, y - 0.5, z - 0.5, x + 0.5, y + 0.5, z + 0.5), EntitySelector.CONTAINER_ENTITY_SELECTOR // Paper - Perf: Optimize hoppers + ); + return !entities.isEmpty() ? (Container)entities.get(level.random.nextInt(entities.size())) : null; + } + + private static boolean canMergeItems(ItemStack stack1, ItemStack stack2) { +- return stack1.getCount() <= stack1.getMaxStackSize() && ItemStack.isSameItemSameComponents(stack1, stack2); ++ return stack1.getCount() < stack1.getMaxStackSize() && ItemStack.isSameItemSameComponents(stack1, stack2); // Paper - Perf: Optimize Hoppers; used to return true for full itemstacks?! + } + + @Override +diff --git a/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java b/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java +index 73b3ddb120d6b6f89e478960e78bed415baea205..f9c31da81d84033abfc1179fc643bceffe35da17 100644 +--- a/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java ++++ b/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java +@@ -53,7 +53,7 @@ public abstract class RandomizableContainerBlockEntity extends BaseContainerBloc + + @Override + public ItemStack getItem(int index) { +- this.unpackLootTable(null); ++ if (index == 0) this.unpackLootTable(null); // Paper - Perf: Optimize Hoppers + return super.getItem(index); + } + |