From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: MartijnMuijsers Date: Wed, 23 Nov 2022 22:11:06 +0100 Subject: [PATCH] Improve container checking with a bitset License: GPL-3.0 (https://www.gnu.org/licenses/gpl-3.0.html) This patch is based on the following patch: "Improve container checking with a bitset" By: Paul Sauve As part of: Airplane (https://github.com/TECHNOVE/Airplane) Licensed under: GPL-3.0 (https://www.gnu.org/licenses/gpl-3.0.html) * Airplane copyright * Airplane Copyright (C) 2020 Technove LLC This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . 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..193a58189e7173131e2c23780043adf9d4726fa3 --- /dev/null +++ b/src/main/java/gg/airplane/structs/ItemListWithBitset.java @@ -0,0 +1,116 @@ +// Gale - Airplane - improve container checking with a bitset + +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..d245210d0b01449894018717bc984fa577fc62ec 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; 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; } + // Gale start - Airplane - improve container checking with a bitset + @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); + } + // Gale end - Airplane - improve container checking with a bitset + @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 540bc9500c35c0db719b00aa26f6fb3a1b08ed9f..1e7dba1ca2fd270a7c362e3113f1a0060d835ee0 100644 --- a/src/main/java/net/minecraft/world/Container.java +++ b/src/main/java/net/minecraft/world/Container.java @@ -2,6 +2,8 @@ package net.minecraft.world; import java.util.Set; import java.util.function.Predicate; + +import net.minecraft.core.Direction; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; @@ -10,6 +12,63 @@ import org.bukkit.craftbukkit.entity.CraftHumanEntity; // CraftBukkit end public interface Container extends Clearable { + // Gale start - Airplane - improve container checking with a bitset - 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; + } + // Gale end - Airplane - improve container checking with a bitset - allow the inventory to override and optimize these frequent calls int LARGE_MAX_STACK_SIZE = 64; 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 b8fb7b5a347298ada16bc8b818edf1863e3f6040..b2c1e7f9d2132ee830d3e44941291bc7863cc1c4 100644 --- a/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecartContainer.java +++ b/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecartContainer.java @@ -27,7 +27,10 @@ import org.bukkit.inventory.InventoryHolder; public abstract class AbstractMinecartContainer extends AbstractMinecart implements ContainerEntity { + // Gale start - Airplane - improve container checking with a bitset private NonNullList itemStacks; + private gg.airplane.structs.ItemListWithBitset itemStacksOptimized; + // Gale end - Airplane - improve container checking with a bitset @Nullable public ResourceLocation lootTable; public long lootTableSeed; @@ -89,12 +92,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 + // Gale start - Airplane - improve container checking with a bitset + this.itemStacksOptimized = new gg.airplane.structs.ItemListWithBitset(this.getContainerSize()); // CraftBukkit - SPIGOT-3513 + this.itemStacks = this.itemStacksOptimized.nonNullList; + // Gale end - Airplane - improve container checking with a bitset } 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 + // Gale start - Airplane - improve container checking with a bitset + this.itemStacksOptimized = new gg.airplane.structs.ItemListWithBitset(this.getContainerSize()); // CraftBukkit - SPIGOT-3513 + this.itemStacks = this.itemStacksOptimized.nonNullList; + // Gale end - Airplane - improve container checking with a bitset } @Override @@ -156,6 +165,10 @@ public abstract class AbstractMinecartContainer extends AbstractMinecart impleme protected void readAdditionalSaveData(CompoundTag nbt) { super.readAdditionalSaveData(nbt); this.lootableData.loadNbt(nbt); // Paper + // Gale start - Airplane - improve container checking with a bitset + this.itemStacksOptimized = new gg.airplane.structs.ItemListWithBitset(this.getContainerSize()); + this.itemStacks = this.itemStacksOptimized.nonNullList; + // Gale end - Airplane - improve container checking with a bitset 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 a71414397bd45ee7bcacfeef0041d80dfa25f114..045325103597f91e257799ea567cc26ae9b66bd5 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; + // Gale start - Airplane - improve container checking with a bitset private NonNullList items; + private gg.airplane.structs.ItemListWithBitset optimizedItems; + // Gale end - Airplane - improve container checking with a bitset 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); // Gale - Airplane - improve container checking with a bitset protected ChestBlockEntity(BlockEntityType type, BlockPos pos, BlockState state) { super(type, pos, state); - this.items = NonNullList.withSize(27, ItemStack.EMPTY); + // Gale start - Airplane - improve container checking with a bitset + this.optimizedItems = new gg.airplane.structs.ItemListWithBitset(27); + this.items = this.optimizedItems.nonNullList; + // Gale end - Airplane - improve container checking with a bitset 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(); } + // Gale start - Airplane - improve container checking with a bitset + @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); + } + // Gale end - Airplane - improve container checking with a bitset + 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); + // Gale start - Airplane - improve container checking with a bitset + this.optimizedItems = new gg.airplane.structs.ItemListWithBitset(this.getContainerSize()); + this.items = this.optimizedItems.nonNullList; + // Gale end - Airplane - improve container checking with a bitset 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; + // Gale start - Airplane - improve container checking with a bitset + this.optimizedItems = gg.airplane.structs.ItemListWithBitset.fromList(list); + this.items = this.optimizedItems.nonNullList; + // Gale end - Airplane - improve container checking with a bitset } @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 ccad692aba2ed77259f6814d88f01b91ed9d229b..28e864885be7af3eaefae709fa6c390b6ce483d8 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 @@ -43,7 +43,10 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen public static final int MOVE_ITEM_SPEED = 8; public static final int HOPPER_CONTAINER_SIZE = 5; + // Gale start - Airplane - improve container checking with a bitset private NonNullList items; + private gg.airplane.structs.ItemListWithBitset optimizedItems; + // Gale end - Airplane - improve container checking with a bitset private int cooldownTime; private long tickedGameTime; @@ -79,14 +82,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); + // Gale start - Airplane - improve container checking with a bitset + this.optimizedItems = new gg.airplane.structs.ItemListWithBitset(5); + this.items = this.optimizedItems.nonNullList; + // Gale end - Airplane - improve container checking with a bitset this.cooldownTime = -1; } + // Gale start - Airplane - improve container checking with a bitset + @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); + } + // Gale end - Airplane - improve container checking with a bitset + @Override public void load(CompoundTag nbt) { super.load(nbt); - this.items = NonNullList.withSize(this.getContainerSize(), ItemStack.EMPTY); + // Gale start - Airplane - improve container checking with a bitset + this.optimizedItems = new gg.airplane.structs.ItemListWithBitset(this.getContainerSize()); + this.items = this.optimizedItems.nonNullList; + // Gale end - Airplane - improve container checking with a bitset if (!this.tryLoadLootTable(nbt)) { ContainerHelper.loadAllItems(nbt, this.items); } @@ -158,7 +184,7 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen flag = HopperBlockEntity.ejectItems(world, pos, state, (Container) blockEntity, blockEntity); // CraftBukkit } - if (!blockEntity.inventoryFull()) { + if (!blockEntity.optimizedItems.hasFullStacks() || !blockEntity.inventoryFull()) { // Gale - Airplane - improve container checking with a bitset - use bitset first flag |= booleansupplier.getAsBoolean(); } @@ -197,7 +223,7 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen skipPushModeEventFire = skipHopperEvents; boolean foundItem = false; for (int i = 0; i < hopper.getContainerSize(); ++i) { - ItemStack item = hopper.getItem(i); + ItemStack item = hopper.getItem(i); // Gale - Airplane - improve container checking with a bitset if (!item.isEmpty()) { foundItem = true; ItemStack origItemStack = item; @@ -400,12 +426,18 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen } private static boolean isFullContainer(Container inventory, Direction direction) { - return allMatch(inventory, direction, STACK_SIZE_TEST); // Paper - no streams + // Gale start - Airplane - improve container checking with a bitset - use bitsets + //return allMatch(inventory, direction, STACK_SIZE_TEST); // Paper - no streams + return inventory.isCompletelyFull(direction); + // Gale end - Airplane - improve container checking with a bitset - use bitsets } private static boolean isEmptyContainer(Container inv, Direction facing) { // Paper start - return allMatch(inv, facing, IS_EMPTY_TEST); + // Gale start - Airplane - improve container checking with a bitset - use bitsets + //return allMatch(inv, facing, IS_EMPTY_TEST); + return inv.isCompletelyEmpty(facing); + // Gale start - Airplane - improve container checking with a bitset - use bitsets } private static boolean allMatch(Container iinventory, Direction enumdirection, java.util.function.BiPredicate test) { if (iinventory instanceof WorldlyContainer) { @@ -582,7 +614,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); // Gale - Airplane - improve container checking with a bitset if (itemstack1.isEmpty()) { // Spigot start - SPIGOT-6693, InventorySubcontainer#setItem @@ -730,7 +762,10 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen @Override protected void setItems(NonNullList list) { - this.items = list; + // Gale start - Airplane - improve container checking with a bitset + this.optimizedItems = gg.airplane.structs.ItemListWithBitset.fromList(list); + this.items = this.optimizedItems.nonNullList; + // Gale end - Airplane - improve container checking with a bitset } 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 d559f93a9a09bac414dd5d58afccad42c127f09b..a25eb07c1209c935e4d97f24c820dc78742aca9d 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 @@ -96,13 +96,8 @@ public abstract class RandomizableContainerBlockEntity extends BaseContainerBloc public boolean isEmpty() { this.unpackLootTable((Player)null); // Paper start - for (ItemStack itemStack : this.getItems()) { - if (!itemStack.isEmpty()) { - return false; - } - } + return this.isCompletelyEmpty(null); // Gale - Airplane - improve container checking with a bitset - use super // Paper end - return true; } @Override