diff options
Diffstat (limited to 'patches/server/0983-Optimize-Hoppers.patch')
-rw-r--r-- | patches/server/0983-Optimize-Hoppers.patch | 664 |
1 files changed, 664 insertions, 0 deletions
diff --git a/patches/server/0983-Optimize-Hoppers.patch b/patches/server/0983-Optimize-Hoppers.patch new file mode 100644 index 0000000000..0d9bca0049 --- /dev/null +++ b/patches/server/0983-Optimize-Hoppers.patch @@ -0,0 +1,664 @@ +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/src/main/java/io/papermc/paper/event/inventory/PaperInventoryMoveItemEvent.java b/src/main/java/io/papermc/paper/event/inventory/PaperInventoryMoveItemEvent.java +new file mode 100644 +index 0000000000000000000000000000000000000000..5c42823726e70ce6c9d0121d074315488e8b3f60 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/event/inventory/PaperInventoryMoveItemEvent.java +@@ -0,0 +1,31 @@ ++package io.papermc.paper.event.inventory; ++ ++import org.bukkit.event.inventory.InventoryMoveItemEvent; ++import org.bukkit.inventory.Inventory; ++import org.bukkit.inventory.ItemStack; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.framework.qual.DefaultQualifier; ++import org.jetbrains.annotations.NotNull; ++ ++@DefaultQualifier(NonNull.class) ++public class PaperInventoryMoveItemEvent extends InventoryMoveItemEvent { ++ ++ public boolean calledSetItem; ++ public boolean calledGetItem; ++ ++ public PaperInventoryMoveItemEvent(final @NotNull Inventory sourceInventory, final @NotNull ItemStack itemStack, final @NotNull 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/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 510087723ce7497dbd9ecd42385acb31e36cf458..b37e49bd25ed327e03250415799c0777f95bd57b 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -1724,6 +1724,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa + ServerLevel worldserver = (ServerLevel) iterator.next(); + worldserver.hasPhysicsEvent = org.bukkit.event.block.BlockPhysicsEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - BlockPhysicsEvent + worldserver.hasEntityMoveEvent = io.papermc.paper.event.entity.EntityMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - Add EntityMoveEvent ++ net.minecraft.world.level.block.entity.HopperBlockEntity.skipHopperEvents = worldserver.paperConfig().hopper.disableMoveEvent || org.bukkit.event.inventory.InventoryMoveItemEvent.getHandlerList().getRegisteredListeners().length == 0; // Paper - Perf: Optimize Hoppers + + gameprofilerfiller.push(() -> { + String s = String.valueOf(worldserver); +diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java +index 947e2a3620d73569552c5185664b7564e908007e..33e7d2884195677c4d6340d8b84c1dd85c636ec1 100644 +--- a/src/main/java/net/minecraft/world/item/ItemStack.java ++++ b/src/main/java/net/minecraft/world/item/ItemStack.java +@@ -881,10 +881,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(boolean originalItem) { ++ if (!originalItem && this.isEmpty()) { ++ // Paper end - Perf: Optimize Hoppers + return ItemStack.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/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java +index b4aff394694417cff1930cf8fbd6696b9f9c9d01..fb00e5a02bb8c64e27d6d009068ba041098951d6 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java +@@ -38,6 +38,7 @@ import co.aikar.timings.MinecraftTimings; // Paper + import co.aikar.timings.Timing; // Paper + + public abstract class BlockEntity { ++ static boolean ignoreTileUpdates; // Paper - Perf: Optimize Hoppers + + public Timing tickTimer = MinecraftTimings.getTileEntityTimings(this); // Paper + // CraftBukkit start - data containers +@@ -230,6 +231,7 @@ public abstract class BlockEntity { + + public void setChanged() { + if (this.level != null) { ++ if (ignoreTileUpdates) return; // Paper - Perf: Optimize Hoppers + BlockEntity.setChanged(this.level, this.worldPosition, this.blockState); + } + +diff --git a/src/main/java/net/minecraft/world/level/block/entity/HopperBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/HopperBlockEntity.java +index b35b44f0776aeb95ef0eda666ba0b652c5e90274..5ebbdb94d9b91c442ff60eb6872f740ebd790fa0 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/HopperBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/HopperBlockEntity.java +@@ -156,6 +156,43 @@ 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 tileEntity) { ++ tileEntity.unpackLootTable(null); ++ ++ final List<ItemStack> hopperItems = tileEntity.getItems(); ++ ++ 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 world, BlockPos pos, BlockState state, HopperBlockEntity blockEntity, BooleanSupplier booleansupplier) { + if (world.isClientSide) { + return false; +@@ -163,11 +200,12 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + if (!blockEntity.isOnCooldown() && (Boolean) 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 = HopperBlockEntity.ejectItems(world, pos, blockEntity); + } + +- if (!blockEntity.inventoryFull()) { ++ if (fullState != HOPPER_IS_FULL || flag) { // Paper - Perf: Optimize Hoppers + flag |= booleansupplier.getAsBoolean(); + } + +@@ -198,6 +236,202 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + return false; + } + ++ // Paper start - Perf: Optimize Hoppers ++ private static boolean skipPullModeEventFire; ++ private static boolean skipPushModeEventFire; ++ public static boolean skipHopperEvents; ++ ++ 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 of 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); ++ } ++ ++ ignoreTileUpdates = true; ++ container.setItem(i, origItemStack); ++ ignoreTileUpdates = false; ++ container.setChanged(); ++ return true; ++ } ++ origItemStack.setCount(originalItemCount); ++ ++ if (level.paperConfig().hopper.cooldownWhenFull) { ++ cooldownHopper(hopper); ++ } ++ ++ return false; ++ } ++ ++ @Nullable ++ private static ItemStack callPushMoveEvent(Container iinventory, ItemStack itemstack, HopperBlockEntity hopper) { ++ final Inventory destinationInventory = getInventory(iinventory); ++ final io.papermc.paper.event.inventory.PaperInventoryMoveItemEvent event = new io.papermc.paper.event.inventory.PaperInventoryMoveItemEvent(hopper.getOwner(false).getInventory(), ++ CraftItemStack.asCraftMirror(itemstack), destinationInventory, true); ++ final boolean result = event.callEvent(); ++ if (!event.calledGetItem && !event.calledSetItem) { ++ skipPushModeEventFire = true; ++ } ++ if (!result) { ++ cooldownHopper(hopper); ++ return null; ++ } ++ ++ if (event.calledSetItem) { ++ return CraftItemStack.asNMSCopy(event.getItem()); ++ } else { ++ return itemstack; ++ } ++ } ++ ++ @Nullable ++ private static ItemStack callPullMoveEvent(final Hopper hopper, final Container container, final ItemStack itemstack) { ++ final Inventory sourceInventory = getInventory(container); ++ final 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, CraftItemStack.asCraftMirror(itemstack), destination, false); ++ final boolean result = event.callEvent(); ++ if (!event.calledGetItem && !event.calledSetItem) { ++ skipPullModeEventFire = true; ++ } ++ if (!result) { ++ cooldownHopper(hopper); ++ return null; ++ } ++ ++ if (event.calledSetItem) { ++ return CraftItemStack.asNMSCopy(event.getItem()); ++ } else { ++ return itemstack; ++ } ++ } ++ ++ private static Inventory getInventory(final Container container) { ++ final Inventory sourceInventory; ++ if (container instanceof 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 CraftInventory(container); ++ } ++ return sourceInventory; ++ } ++ ++ private static void cooldownHopper(final Hopper hopper) { ++ if (hopper instanceof HopperBlockEntity blockEntity && blockEntity.getLevel() != null) { ++ blockEntity.setCooldown(blockEntity.getLevel().spigotConfig.hopperTransfer); ++ } ++ } ++ ++ private static boolean allMatch(Container iinventory, Direction enumdirection, java.util.function.BiPredicate<ItemStack, Integer> test) { ++ if (iinventory instanceof WorldlyContainer) { ++ for (int i : ((WorldlyContainer) iinventory).getSlotsForFace(enumdirection)) { ++ if (!test.test(iinventory.getItem(i), i)) { ++ return false; ++ } ++ } ++ } else { ++ int size = iinventory.getContainerSize(); ++ for (int i = 0; i < size; i++) { ++ if (!test.test(iinventory.getItem(i), i)) { ++ return false; ++ } ++ } ++ } ++ return true; ++ } ++ ++ private static boolean anyMatch(Container iinventory, Direction enumdirection, java.util.function.BiPredicate<ItemStack, Integer> test) { ++ if (iinventory instanceof WorldlyContainer) { ++ for (int i : ((WorldlyContainer) iinventory).getSlotsForFace(enumdirection)) { ++ if (test.test(iinventory.getItem(i), i)) { ++ return true; ++ } ++ } ++ } else { ++ int size = iinventory.getContainerSize(); ++ for (int i = 0; i < size; i++) { ++ if (test.test(iinventory.getItem(i), i)) { ++ 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 world, BlockPos pos, HopperBlockEntity blockEntity) { + Container iinventory = HopperBlockEntity.getAttachedContainer(world, pos, blockEntity); + +@@ -209,52 +443,55 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + if (HopperBlockEntity.isFullContainer(iinventory, enumdirection)) { + return false; + } else { +- for (int i = 0; i < blockEntity.getContainerSize(); ++i) { +- ItemStack itemstack = blockEntity.getItem(i); +- +- if (!itemstack.isEmpty()) { +- int j = itemstack.getCount(); +- // CraftBukkit start - Call event when pushing items into other inventories +- ItemStack original = itemstack.copy(); +- CraftItemStack oitemstack = CraftItemStack.asCraftMirror(blockEntity.removeItem(i, world.spigotConfig.hopperAmount)); // Spigot +- +- Inventory destinationInventory; +- // Have to special case large chests as they work oddly +- if (iinventory instanceof CompoundContainer) { +- destinationInventory = new org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest((CompoundContainer) iinventory); +- } else if (iinventory.getOwner() != null) { +- destinationInventory = iinventory.getOwner().getInventory(); +- } else { +- destinationInventory = new CraftInventory(iinventory); +- } +- +- InventoryMoveItemEvent event = new InventoryMoveItemEvent(blockEntity.getOwner().getInventory(), oitemstack, destinationInventory, true); +- world.getCraftServer().getPluginManager().callEvent(event); +- if (event.isCancelled()) { +- blockEntity.setItem(i, original); +- blockEntity.setCooldown(world.spigotConfig.hopperTransfer); // Delay hopper checks // Spigot +- return false; +- } +- int origCount = event.getItem().getAmount(); // Spigot +- ItemStack itemstack1 = HopperBlockEntity.addItem(blockEntity, iinventory, CraftItemStack.asNMSCopy(event.getItem()), enumdirection); +- // CraftBukkit end +- +- if (itemstack1.isEmpty()) { +- iinventory.setChanged(); +- return true; +- } +- +- itemstack.setCount(j); +- // Spigot start +- itemstack.shrink(origCount - itemstack1.getCount()); +- if (j <= world.spigotConfig.hopperAmount) { +- // Spigot end +- blockEntity.setItem(i, itemstack); +- } +- } +- } +- +- return false; ++ // Paper start - Perf: Optimize Hoppers ++ return hopperPush(world, iinventory, enumdirection, blockEntity); ++ //for (int i = 0; i < blockEntity.getContainerSize(); ++i) { ++ // ItemStack itemstack = blockEntity.getItem(i); ++ ++ // if (!itemstack.isEmpty()) { ++ // int j = itemstack.getCount(); ++ // // CraftBukkit start - Call event when pushing items into other inventories ++ // ItemStack original = itemstack.copy(); ++ // CraftItemStack oitemstack = CraftItemStack.asCraftMirror(blockEntity.removeItem(i, world.spigotConfig.hopperAmount)); // Spigot ++ ++ // Inventory destinationInventory; ++ // // Have to special case large chests as they work oddly ++ // if (iinventory instanceof CompoundContainer) { ++ // destinationInventory = new org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest((CompoundContainer) iinventory); ++ // } else if (iinventory.getOwner() != null) { ++ // destinationInventory = iinventory.getOwner().getInventory(); ++ // } else { ++ // destinationInventory = new CraftInventory(iinventory); ++ // } ++ ++ // InventoryMoveItemEvent event = new InventoryMoveItemEvent(tileentityhopper.getOwner().getInventory(), oitemstack, destinationInventory, true); ++ // world.getCraftServer().getPluginManager().callEvent(event); ++ // if (event.isCancelled()) { ++ // blockEntity.setItem(i, original); ++ // blockEntity.setCooldown(world.spigotConfig.hopperTransfer); // Delay hopper checks // Spigot ++ // return false; ++ // } ++ // int origCount = event.getItem().getAmount(); // Spigot ++ // ItemStack itemstack1 = HopperBlockEntity.addItem(blockEntity, iinventory, CraftItemStack.asNMSCopy(event.getItem()), enumdirection); ++ // // CraftBukkit end ++ ++ // if (itemstack1.isEmpty()) { ++ // iinventory.setChanged(); ++ // return true; ++ // } ++ ++ // itemstack.setCount(j); ++ // // Spigot start ++ // itemstack.shrink(origCount - itemstack1.getCount()); ++ // if (j <= world.spigotConfig.hopperAmount) { ++ // // Spigot end ++ // blockEntity.setItem(i, itemstack); ++ // } ++ // } ++ //} ++ ++ // return false; ++ // Paper end - Perf: Optimize Hoppers + } + } + } +@@ -305,7 +542,6 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + return false; + } + } +- + return true; + } + +@@ -316,6 +552,7 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + + if (iinventory != null) { + Direction enumdirection = Direction.DOWN; ++ skipPullModeEventFire = skipHopperEvents; // Paper - Perf: Optimize Hoppers + int[] aint = HopperBlockEntity.getSlots(iinventory, enumdirection); + int i = aint.length; + +@@ -351,49 +588,52 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + ItemStack itemstack = iinventory.getItem(i); + + if (!itemstack.isEmpty() && HopperBlockEntity.canTakeItemFromContainer(ihopper, iinventory, itemstack, i, enumdirection)) { +- int j = itemstack.getCount(); +- // CraftBukkit start - Call event on collection of items from inventories into the hopper +- ItemStack original = itemstack.copy(); +- CraftItemStack oitemstack = CraftItemStack.asCraftMirror(iinventory.removeItem(i, world.spigotConfig.hopperAmount)); // Spigot +- +- Inventory sourceInventory; +- // Have to special case large chests as they work oddly +- if (iinventory instanceof CompoundContainer) { +- sourceInventory = new org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest((CompoundContainer) iinventory); +- } else if (iinventory.getOwner() != null) { +- sourceInventory = iinventory.getOwner().getInventory(); +- } else { +- sourceInventory = new CraftInventory(iinventory); +- } +- +- InventoryMoveItemEvent event = new InventoryMoveItemEvent(sourceInventory, oitemstack, ihopper.getOwner().getInventory(), false); +- +- Bukkit.getServer().getPluginManager().callEvent(event); +- if (event.isCancelled()) { +- iinventory.setItem(i, original); +- +- if (ihopper instanceof HopperBlockEntity) { +- ((HopperBlockEntity) ihopper).setCooldown(world.spigotConfig.hopperTransfer); // Spigot +- } +- +- return false; +- } +- int origCount = event.getItem().getAmount(); // Spigot +- ItemStack itemstack1 = HopperBlockEntity.addItem(iinventory, ihopper, CraftItemStack.asNMSCopy(event.getItem()), null); +- // CraftBukkit end +- +- if (itemstack1.isEmpty()) { +- iinventory.setChanged(); +- return true; +- } +- +- itemstack.setCount(j); +- // Spigot start +- itemstack.shrink(origCount - itemstack1.getCount()); +- if (j <= world.spigotConfig.hopperAmount) { +- // Spigot end +- iinventory.setItem(i, itemstack); +- } ++ // Paper start - Perf: Optimize Hoppers ++ return hopperPull(world, ihopper, iinventory, itemstack, i); ++ // int j = itemstack.getCount(); ++ // // CraftBukkit start - Call event on collection of items from inventories into the hopper ++ // ItemStack original = itemstack.copy(); ++ // CraftItemStack oitemstack = CraftItemStack.asCraftMirror(iinventory.removeItem(i, world.spigotConfig.hopperAmount)); // Spigot ++ ++ // Inventory sourceInventory; ++ // // Have to special case large chests as they work oddly ++ // if (iinventory instanceof CompoundContainer) { ++ // sourceInventory = new org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest((CompoundContainer) iinventory); ++ // } else if (iinventory.getOwner() != null) { ++ // sourceInventory = iinventory.getOwner().getInventory(); ++ // } else { ++ // sourceInventory = new CraftInventory(iinventory); ++ // } ++ ++ // InventoryMoveItemEvent event = new InventoryMoveItemEvent(sourceInventory, oitemstack, ihopper.getOwner().getInventory(), false); ++ ++ // Bukkit.getServer().getPluginManager().callEvent(event); ++ // if (event.isCancelled()) { ++ // iinventory.setItem(i, original); ++ ++ // if (ihopper instanceof HopperBlockEntity) { ++ // ((HopperBlockEntity) ihopper).setCooldown(world.spigotConfig.hopperTransfer); // Spigot ++ // } ++ ++ // return false; ++ // } ++ // int origCount = event.getItem().getAmount(); // Spigot ++ // ItemStack itemstack1 = HopperBlockEntity.addItem(iinventory, ihopper, CraftItemStack.asNMSCopy(event.getItem()), null); ++ // // CraftBukkit end ++ ++ // if (itemstack1.isEmpty()) { ++ // iinventory.setChanged(); ++ // return true; ++ // } ++ ++ // itemstack.setCount(j); ++ // // Spigot start ++ // itemstack.shrink(origCount - itemstack1.getCount()); ++ // if (j <= world.spigotConfig.hopperAmount) { ++ // // Spigot end ++ // iinventory.setItem(i, itemstack); ++ // } ++ // Paper end - Perf: Optimize Hoppers + } + + return false; +@@ -402,12 +642,14 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + public static boolean addItem(Container inventory, ItemEntity itemEntity) { + boolean flag = false; + // CraftBukkit start +- InventoryPickupItemEvent event = new InventoryPickupItemEvent(inventory.getOwner().getInventory(), (org.bukkit.entity.Item) itemEntity.getBukkitEntity()); ++ if (InventoryPickupItemEvent.getHandlerList().getRegisteredListeners().length > 0) { // Paper - optimize hoppers ++ InventoryPickupItemEvent event = new InventoryPickupItemEvent(getInventory(inventory), (org.bukkit.entity.Item) itemEntity.getBukkitEntity()); // Paper - Perf: Optimize Hoppers; use getInventory() to avoid snapshot creation + itemEntity.level().getCraftServer().getPluginManager().callEvent(event); + if (event.isCancelled()) { + return false; + } + // CraftBukkit end ++ } // Paper - Perf: Optimize Hoppers + ItemStack itemstack = itemEntity.getItem().copy(); + ItemStack itemstack1 = HopperBlockEntity.addItem((Container) null, inventory, itemstack, (Direction) null); + +@@ -501,7 +743,9 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + stack = stack.split(to.getMaxStackSize()); + } + // Spigot end ++ ignoreTileUpdates = true; // Paper - Perf: Optimize Hoppers + to.setItem(slot, stack); ++ ignoreTileUpdates = false; // Paper - Perf: Optimize Hoppers + stack = leftover; // Paper - Make hoppers respect inventory max stack size + flag = true; + } else if (HopperBlockEntity.canMergeItems(itemstack1, stack)) { +@@ -581,14 +825,20 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + + @Nullable + public static Container getContainerAt(Level world, BlockPos pos) { +- return HopperBlockEntity.getContainerAt(world, pos, world.getBlockState(pos), (double) pos.getX() + 0.5D, (double) pos.getY() + 0.5D, (double) pos.getZ() + 0.5D); ++ return HopperBlockEntity.getContainerAt(world, pos, world.getBlockState(pos), (double) pos.getX() + 0.5D, (double) pos.getY() + 0.5D, (double) pos.getZ() + 0.5D, true); + } + + @Nullable + private static Container getContainerAt(Level world, BlockPos pos, BlockState state, double x, double y, double z) { ++ // Paper start - Perf: Optimize Hoppers ++ return HopperBlockEntity.getContainerAt(world, pos, state, x, y, z, false); ++ } ++ @Nullable ++ private static Container getContainerAt(Level world, BlockPos pos, BlockState state, double x, double y, double z, boolean optimizeEntities) { ++ // Paper end - Perf: Optimize Hoppers + Container iinventory = HopperBlockEntity.getBlockContainer(world, pos, state); + +- if (iinventory == null) { ++ if (iinventory == null && (!optimizeEntities || !world.paperConfig().hopper.ignoreOccludingBlocks || !state.getBukkitMaterial().isOccluding())) { // Paper - Perf: Optimize Hoppers + iinventory = HopperBlockEntity.getEntityContainer(world, x, y, z); + } + +@@ -623,13 +873,13 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen + + @Nullable + private static Container getEntityContainer(Level world, double x, double y, double z) { +- List<Entity> list = world.getEntities((Entity) null, new AABB(x - 0.5D, y - 0.5D, z - 0.5D, x + 0.5D, y + 0.5D, z + 0.5D), EntitySelector.CONTAINER_ENTITY_SELECTOR); ++ List<Entity> list = world.getEntitiesOfClass((Class) Container.class, new AABB(x - 0.5D, y - 0.5D, z - 0.5D, x + 0.5D, y + 0.5D, z + 0.5D), EntitySelector.CONTAINER_ENTITY_SELECTOR); // Paper - Perf: Optimize hoppers + + return !list.isEmpty() ? (Container) list.get(world.random.nextInt(list.size())) : null; + } + + private static boolean canMergeItems(ItemStack first, ItemStack second) { +- return first.getCount() <= first.getMaxStackSize() && ItemStack.isSameItemSameComponents(first, second); ++ return first.getCount() < first.getMaxStackSize() && ItemStack.isSameItemSameComponents(first, second); // Paper - Perf: Optimize Hoppers; used to return true for full itemstacks?! + } + + @Override +diff --git a/src/main/java/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java +index fc657b6052d4310ad9c28988042c2cf37cf5d213..a459163bd7148a27c60e030479df4da91e957049 100644 +--- a/src/main/java/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java ++++ b/src/main/java/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java +@@ -53,7 +53,7 @@ public abstract class RandomizableContainerBlockEntity extends BaseContainerBloc + + @Override + public ItemStack getItem(int slot) { +- this.unpackLootTable(null); ++ if (slot == 0) this.unpackLootTable(null); // Paper - Perf: Optimize Hoppers + return super.getItem(slot); + } + |