From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: MrHua269 Date: Mon, 25 Mar 2024 13:48:33 +0000 Subject: [PATCH] Pufferfish Improve container checking with a bitset diff --git a/src/main/java/gg/airplane/structs/ItemListWithBitset.java b/src/main/java/gg/airplane/structs/ItemListWithBitset.java new file mode 100644 index 0000000000000000000000000000000000000000..1b7a4ee47f4445d7f2ac91d3a73ae113edbdddb2 --- /dev/null +++ b/src/main/java/gg/airplane/structs/ItemListWithBitset.java @@ -0,0 +1,114 @@ +package gg.airplane.structs; + +import net.minecraft.core.NonNullList; +import net.minecraft.world.item.ItemStack; +import org.apache.commons.lang.Validate; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.AbstractList; +import java.util.Arrays; +import java.util.List; + +public class ItemListWithBitset extends AbstractList { + public static ItemListWithBitset fromList(List list) { + if (list instanceof ItemListWithBitset ours) { + return ours; + } + return new ItemListWithBitset(list); + } + + private static ItemStack[] createArray(int size) { + ItemStack[] array = new ItemStack[size]; + Arrays.fill(array, ItemStack.EMPTY); + return array; + } + + private final ItemStack[] items; + + private long bitSet = 0; + private final long allBits; + + private static class OurNonNullList extends NonNullList { + protected OurNonNullList(List delegate) { + super(delegate, ItemStack.EMPTY); + } + } + + public final NonNullList nonNullList = new OurNonNullList(this); + + private ItemListWithBitset(List list) { + this(list.size()); + + for (int i = 0; i < list.size(); i++) { + this.set(i, list.get(i)); + } + } + + public ItemListWithBitset(int size) { + Validate.isTrue(size < Long.BYTES * 8, "size is too large"); + + this.items = createArray(size); + this.allBits = ((1L << size) - 1); + } + + public boolean isCompletelyEmpty() { + return this.bitSet == 0; + } + + public boolean hasFullStacks() { + return (this.bitSet & this.allBits) == allBits; + } + + @Override + public ItemStack set(int index, @NotNull ItemStack itemStack) { + ItemStack existing = this.items[index]; + + this.items[index] = itemStack; + + if (itemStack == ItemStack.EMPTY) { + this.bitSet &= ~(1L << index); + } else { + this.bitSet |= 1L << index; + } + + return existing; + } + + @NotNull + @Override + public ItemStack get(int var0) { + return this.items[var0]; + } + + @Override + public int size() { + return this.items.length; + } + + @Override + public void clear() { + Arrays.fill(this.items, ItemStack.EMPTY); + } + + // these are unsupported for block inventories which have a static size + @Override + public void add(int var0, ItemStack var1) { + throw new UnsupportedOperationException(); + } + + @Override + public ItemStack remove(int var0) { + throw new UnsupportedOperationException(); + } + + @Override + public String toString() { + return "ItemListWithBitset{" + + "items=" + Arrays.toString(items) + + ", bitSet=" + Long.toString(bitSet, 2) + + ", allBits=" + Long.toString(allBits, 2) + + ", size=" + this.items.length + + '}'; + } +} diff --git a/src/main/java/net/minecraft/world/CompoundContainer.java b/src/main/java/net/minecraft/world/CompoundContainer.java index 241fec02e6869c638d3a160819b32173a081467b..6a8f9e8f5bf108674c47018def28906e2d0a729c 100644 --- a/src/main/java/net/minecraft/world/CompoundContainer.java +++ b/src/main/java/net/minecraft/world/CompoundContainer.java @@ -1,5 +1,6 @@ package net.minecraft.world; +import net.minecraft.core.Direction; // Pufferfish import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; @@ -64,6 +65,23 @@ public class CompoundContainer implements Container { this.container2 = second; } + // Pufferfish start + @Override + public boolean hasEmptySlot(Direction enumdirection) { + return this.container1.hasEmptySlot(null) || this.container2.hasEmptySlot(null); + } + + @Override + public boolean isCompletelyFull(Direction enumdirection) { + return this.container1.isCompletelyFull(null) && this.container2.isCompletelyFull(null); + } + + @Override + public boolean isCompletelyEmpty(Direction enumdirection) { + return this.container1.isCompletelyEmpty(null) && this.container2.isCompletelyEmpty(null); + } + // Pufferfish end + @Override public int getContainerSize() { return this.container1.getContainerSize() + this.container2.getContainerSize(); diff --git a/src/main/java/net/minecraft/world/Container.java b/src/main/java/net/minecraft/world/Container.java index d6cbe98e67fdbf8db46338a88ab1356dd63b50a3..20dd3a63b2f955b05a75eb240e33ae4cf6aef28f 100644 --- a/src/main/java/net/minecraft/world/Container.java +++ b/src/main/java/net/minecraft/world/Container.java @@ -3,6 +3,8 @@ package net.minecraft.world; import java.util.Set; import java.util.function.Predicate; import net.minecraft.core.BlockPos; + +import net.minecraft.core.Direction; // Pufferfish import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; @@ -14,6 +16,63 @@ import org.bukkit.craftbukkit.entity.CraftHumanEntity; // CraftBukkit end public interface Container extends Clearable { + // Pufferfish start - allow the inventory to override and optimize these frequent calls + default boolean hasEmptySlot(@org.jetbrains.annotations.Nullable Direction enumdirection) { // there is a slot with 0 items in it + if (this instanceof WorldlyContainer worldlyContainer) { + for (int i : worldlyContainer.getSlotsForFace(enumdirection)) { + if (this.getItem(i).isEmpty()) { + return true; + } + } + } else { + int size = this.getContainerSize(); + for (int i = 0; i < size; i++) { + if (this.getItem(i).isEmpty()) { + return true; + } + } + } + return false; + } + + default boolean isCompletelyFull(@org.jetbrains.annotations.Nullable Direction enumdirection) { // every stack is maxed + if (this instanceof WorldlyContainer worldlyContainer) { + for (int i : worldlyContainer.getSlotsForFace(enumdirection)) { + ItemStack itemStack = this.getItem(i); + if (itemStack.getCount() < itemStack.getMaxStackSize()) { + return false; + } + } + } else { + int size = this.getContainerSize(); + for (int i = 0; i < size; i++) { + ItemStack itemStack = this.getItem(i); + if (itemStack.getCount() < itemStack.getMaxStackSize()) { + return false; + } + } + } + return true; + } + + default boolean isCompletelyEmpty(@org.jetbrains.annotations.Nullable Direction enumdirection) { + if (this instanceof WorldlyContainer worldlyContainer) { + for (int i : worldlyContainer.getSlotsForFace(enumdirection)) { + if (!this.getItem(i).isEmpty()) { + return false; + } + } + } else { + int size = this.getContainerSize(); + for (int i = 0; i < size; i++) { + if (!this.getItem(i).isEmpty()) { + return false; + } + } + } + return true; + } + // Pufferfish end int LARGE_MAX_STACK_SIZE = 64; int DEFAULT_DISTANCE_LIMIT = 8; diff --git a/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecartContainer.java b/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecartContainer.java index 756d0434472921992c9d84597d7c9c824e93614c..38c573d440946ca7ee6016ef92e9c1605031e611 100644 --- a/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecartContainer.java +++ b/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecartContainer.java @@ -28,7 +28,10 @@ import org.bukkit.inventory.InventoryHolder; public abstract class AbstractMinecartContainer extends AbstractMinecart implements ContainerEntity { + // Pufferfish start private NonNullList itemStacks; + private gg.airplane.structs.ItemListWithBitset itemStacksOptimized; + // Pufferfish end @Nullable public ResourceLocation lootTable; public long lootTableSeed; @@ -90,12 +93,18 @@ public abstract class AbstractMinecartContainer extends AbstractMinecart impleme protected AbstractMinecartContainer(EntityType type, Level world) { super(type, world); - this.itemStacks = NonNullList.withSize(this.getContainerSize(), ItemStack.EMPTY); // CraftBukkit - SPIGOT-3513 + // Pufferfish start + this.itemStacksOptimized = new gg.airplane.structs.ItemListWithBitset(this.getContainerSize()); // CraftBukkit - SPIGOT-3513 + this.itemStacks = this.itemStacksOptimized.nonNullList; + // Pufferfish end } protected AbstractMinecartContainer(EntityType type, double x, double y, double z, Level world) { super(type, world, x, y, z); - this.itemStacks = NonNullList.withSize(this.getContainerSize(), ItemStack.EMPTY); // CraftBukkit - SPIGOT-3513 + // Pufferfish start + this.itemStacksOptimized = new gg.airplane.structs.ItemListWithBitset(this.getContainerSize()); // CraftBukkit - SPIGOT-3513 + this.itemStacks = this.itemStacksOptimized.nonNullList; + // Pufferfish end } @Override @@ -164,6 +173,10 @@ public abstract class AbstractMinecartContainer extends AbstractMinecart impleme protected void readAdditionalSaveData(CompoundTag nbt) { super.readAdditionalSaveData(nbt); this.lootableData.loadNbt(nbt); // Paper + // Pufferfish start + this.itemStacksOptimized = new gg.airplane.structs.ItemListWithBitset(this.getContainerSize()); + this.itemStacks = this.itemStacksOptimized.nonNullList; + // Pufferfish end this.readChestVehicleSaveData(nbt); } diff --git a/src/main/java/net/minecraft/world/level/block/entity/ChestBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/ChestBlockEntity.java index 9b1243d96e0694c62fc9e82e9be540bce0d2b3ad..3514022d898a24052c917ebf55dcef3e757d6836 100644 --- a/src/main/java/net/minecraft/world/level/block/entity/ChestBlockEntity.java +++ b/src/main/java/net/minecraft/world/level/block/entity/ChestBlockEntity.java @@ -31,7 +31,10 @@ import org.bukkit.entity.HumanEntity; public class ChestBlockEntity extends RandomizableContainerBlockEntity implements LidBlockEntity { private static final int EVENT_SET_OPEN_COUNT = 1; + // Pufferfish start private NonNullList items; + private gg.airplane.structs.ItemListWithBitset optimizedItems; + // Pufferfish end public final ContainerOpenersCounter openersCounter; private final ChestLidController chestLidController; @@ -65,9 +68,13 @@ public class ChestBlockEntity extends RandomizableContainerBlockEntity implement } // CraftBukkit end + private final boolean isNative = getClass().equals(ChestBlockEntity.class); // Pufferfish protected ChestBlockEntity(BlockEntityType type, BlockPos pos, BlockState state) { super(type, pos, state); - this.items = NonNullList.withSize(27, ItemStack.EMPTY); + // Pufferfish start + this.optimizedItems = new gg.airplane.structs.ItemListWithBitset(27); + this.items = this.optimizedItems.nonNullList; + // Pufferfish end this.openersCounter = new ContainerOpenersCounter() { @Override protected void onOpen(Level world, BlockPos pos, BlockState state) { @@ -98,6 +105,23 @@ public class ChestBlockEntity extends RandomizableContainerBlockEntity implement this.chestLidController = new ChestLidController(); } + // Pufferfish start + @Override + public boolean hasEmptySlot(Direction enumdirection) { + return isNative ? !this.optimizedItems.hasFullStacks() : super.hasEmptySlot(enumdirection); + } + + @Override + public boolean isCompletelyFull(Direction enumdirection) { + return isNative ? this.optimizedItems.hasFullStacks() && super.isCompletelyFull(enumdirection) : super.isCompletelyFull(enumdirection); + } + + @Override + public boolean isCompletelyEmpty(Direction enumdirection) { + return isNative && this.optimizedItems.isCompletelyEmpty() || super.isCompletelyEmpty(enumdirection); + } + // Pufferfish end + public ChestBlockEntity(BlockPos pos, BlockState state) { this(BlockEntityType.CHEST, pos, state); } @@ -115,7 +139,10 @@ public class ChestBlockEntity extends RandomizableContainerBlockEntity implement @Override public void load(CompoundTag nbt) { super.load(nbt); - this.items = NonNullList.withSize(this.getContainerSize(), ItemStack.EMPTY); + // Pufferfish start + this.optimizedItems = new gg.airplane.structs.ItemListWithBitset(this.getContainerSize()); + this.items = this.optimizedItems.nonNullList; + // Pufferfish end if (!this.tryLoadLootTable(nbt)) { ContainerHelper.loadAllItems(nbt, this.items); } @@ -187,7 +214,10 @@ public class ChestBlockEntity extends RandomizableContainerBlockEntity implement @Override protected void setItems(NonNullList list) { - this.items = list; + // Pufferfish start + this.optimizedItems = gg.airplane.structs.ItemListWithBitset.fromList(list); + this.items = this.optimizedItems.nonNullList; + // Pufferfish end } @Override 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 82e7e76fecceb55522b5828a56f036e42ef55201..882e27de852c89c837d7943c5a9ae6fb3c2e896b 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 @@ -48,7 +48,10 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen public static final int MOVE_ITEM_SPEED = 8; public static final int HOPPER_CONTAINER_SIZE = 5; + // Pufferfish start private NonNullList items; + private gg.airplane.structs.ItemListWithBitset optimizedItems; // Pufferfish + // Pufferfish end public int cooldownTime; private long tickedGameTime = Long.MIN_VALUE; // Folia - region threading @@ -94,14 +97,37 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen public HopperBlockEntity(BlockPos pos, BlockState state) { super(BlockEntityType.HOPPER, pos, state); - this.items = NonNullList.withSize(5, ItemStack.EMPTY); + // Pufferfish start + this.optimizedItems = new gg.airplane.structs.ItemListWithBitset(5); + this.items = this.optimizedItems.nonNullList; + // Pufferfish end this.cooldownTime = -1; } + // Pufferfish start + @Override + public boolean hasEmptySlot(Direction enumdirection) { + return !this.optimizedItems.hasFullStacks(); + } + + @Override + public boolean isCompletelyFull(Direction enumdirection) { + return this.optimizedItems.hasFullStacks() && super.isCompletelyFull(enumdirection); + } + + @Override + public boolean isCompletelyEmpty(Direction enumdirection) { + return this.optimizedItems.isCompletelyEmpty() || super.isCompletelyEmpty(enumdirection); + } + // Pufferfish end + @Override public void load(CompoundTag nbt) { super.load(nbt); - this.items = NonNullList.withSize(this.getContainerSize(), ItemStack.EMPTY); + // Pufferfish start + this.optimizedItems = new gg.airplane.structs.ItemListWithBitset(this.getContainerSize()); + this.items = this.optimizedItems.nonNullList; + // Pufferfish end if (!this.tryLoadLootTable(nbt)) { ContainerHelper.loadAllItems(nbt, this.items); } @@ -504,6 +530,7 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen } private static boolean isFullContainer(Container inventory, Direction direction) { + if (true) return inventory.isCompletelyFull(direction); // Pufferfish - use bitsets // Paper start - Perf: Optimize Hoppers if (inventory instanceof WorldlyContainer worldlyContainer) { for (final int slot : worldlyContainer.getSlotsForFace(direction)) { @@ -526,7 +553,11 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen } private static boolean isEmptyContainer(Container inv, Direction facing) { - return allMatch(inv, facing, IS_EMPTY_TEST); // Paper - Perf: Optimize Hoppers + // Paper start + // Pufferfish start - use bitsets + //return allMatch(inv, facing, IS_EMPTY_TEST); + return inv.isCompletelyEmpty(facing); + // Pufferfish end } public static boolean suckInItems(Level world, Hopper hopper) { @@ -727,7 +758,7 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen if (HopperBlockEntity.canPlaceItemInContainer(to, stack, slot, side)) { boolean flag = false; - boolean flag1 = to.isEmpty(); + boolean flag1 = to.isCompletelyEmpty(side); // Pufferfish if (itemstack1.isEmpty()) { // Spigot start - SPIGOT-6693, InventorySubcontainer#setItem @@ -922,7 +953,10 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen @Override protected void setItems(NonNullList list) { - this.items = list; + // Pufferfish start + this.optimizedItems = gg.airplane.structs.ItemListWithBitset.fromList(list); + this.items = this.optimizedItems.nonNullList; + // Pufferfish end } public static void entityInside(Level world, BlockPos pos, BlockState state, Entity entity, HopperBlockEntity blockEntity) { 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 dfd1246b735fe64c5beae83567a013861eb00822..fa64bf5ad13c278438039b663ea3134e72108411 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 @@ -94,12 +94,7 @@ public abstract class RandomizableContainerBlockEntity extends BaseContainerBloc public boolean isEmpty() { this.unpackLootTable(null); // Paper start - Perf: Optimize Hoppers - for (final ItemStack itemStack : this.getItems()) { - if (!itemStack.isEmpty()) { - return false; - } - } - return true; + return this.isCompletelyEmpty(null); // Pufferfish - use super // Paper end - Perf: Optimize Hoppers }