From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: MrHua269 Date: Thu, 9 Jul 2026 12:00:19 +0800 Subject: [PATCH] Leaves Lithium Sleeping Block Entity As a part of leaves Origin patch link: https://github.com/LeavesMC/Leaves/blob/master/leaves-server/minecraft-patches/features/0136-Lithium-Sleeping-Block-Entity.patch Origin license: https://github.com/LeavesMC/Leaves/blob/master/LICENSE.md diff --git a/io/papermc/paper/threadedregions/RegionizedWorldData.java b/io/papermc/paper/threadedregions/RegionizedWorldData.java index c04dbcd615c97cb6dcb5f14fec88daf73e6e9b4a..53970fff0c119b190195e07823d5796a99de9c3b 100644 --- a/io/papermc/paper/threadedregions/RegionizedWorldData.java +++ b/io/papermc/paper/threadedregions/RegionizedWorldData.java @@ -65,6 +65,10 @@ import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; import java.util.function.Consumer; import java.util.function.Predicate; +// Shiroha start - imports for lithium sleeping block entity +import org.leavesmc.leaves.lithium.common.tracking.entity.ChunkSectionInventoryEntityTracker; +import org.leavesmc.leaves.lithium.common.tracking.entity.ChunkSectionItemEntityMovementTracker; +// Shiroha end public final class RegionizedWorldData { @@ -75,6 +79,32 @@ public final class RegionizedWorldData { public static final RegionizedData.RegioniserCallback REGION_CALLBACK = new RegionizedData.RegioniserCallback<>() { @Override public void merge(final RegionizedWorldData from, final RegionizedWorldData into, final long fromTickOffset) { + // Shiroha start - Region threading for lithium sleeping block entity + // lithium listeners + final long fromRedstoneTimeOffsetForLithiumTracker = into.redstoneTime - from.redstoneTime; + for (var entry : from.containerEntityMovementTrackerMap.entrySet()) { + var key = entry.getKey(); + var tracker = entry.getValue(); + + if (!tracker.hasUser()) { + continue; + } + + tracker.updateTicks(fromTickOffset, fromRedstoneTimeOffsetForLithiumTracker); + into.containerEntityMovementTrackerMap.put(key, tracker); + } + for (var entry : from.itemEntityMovementTrackerMap.entrySet()) { + var key = entry.getKey(); + var tracker = entry.getValue(); + + if (!tracker.hasUser()) { + continue; + } + + tracker.updateTicks(fromTickOffset, fromRedstoneTimeOffsetForLithiumTracker); + into.itemEntityMovementTrackerMap.put(key, tracker); + } + // Shiroha end // connections for (final Connection conn : from.connections) { into.connections.add(conn); @@ -111,16 +141,22 @@ public final class RegionizedWorldData { from.fluidLevelTicks.merge(into.fluidLevelTicks, fromRedstoneTimeOffset); // tile entity ticking - for (final TickingBlockEntity tileEntityWrapped : from.pendingBlockEntityTickers) { + for (TickingBlockEntity tileEntityWrapped : from.pendingBlockEntityTickers) { // Shiroha - Region threading for lithium sleeping block entity into.pendingBlockEntityTickers.add(tileEntityWrapped); - final BlockEntity tileEntity = tileEntityWrapped.getTileEntity(); + BlockEntity tileEntity = tileEntityWrapped.getTileEntity(); // Shiroha - Region threading for lithium sleeping block entity + // Shiroha start - Region threading for lithium sleeping block entity + tileEntityWrapped.updateTicksForLithium(fromRedstoneTimeOffset); + // Shiroha end - Region threading for lithium sleeping block entity if (tileEntity != null) { tileEntity.updateTicks(fromTickOffset, fromRedstoneTimeOffset); } } - for (final TickingBlockEntity tileEntityWrapped : from.blockEntityTickers) { + for (TickingBlockEntity tileEntityWrapped : from.blockEntityTickers) { // Shiroha - Region threading for lithium sleeping block entity into.blockEntityTickers.add(tileEntityWrapped); - final BlockEntity tileEntity = tileEntityWrapped.getTileEntity(); + BlockEntity tileEntity = tileEntityWrapped.getTileEntity(); // Shiroha - Region threading for lithium sleeping block entity + // Shiroha start - Region threading for lithium sleeping block entity + tileEntityWrapped.updateTicksForLithium(fromRedstoneTimeOffset); + // Shiroha end - Region threading for lithium sleeping block entity if (tileEntity != null) { tileEntity.updateTicks(fromTickOffset, fromRedstoneTimeOffset); } @@ -164,6 +200,37 @@ public final class RegionizedWorldData { public void split(final RegionizedWorldData from, final int chunkToRegionShift, final Long2ReferenceOpenHashMap regionToData, final ReferenceOpenHashSet dataSet) { + // Shiroha start - Region threading for lithium sleeping block entity + // lithium entity movement listeners + for (var entry : from.containerEntityMovementTrackerMap.entrySet()) { + final long key = entry.getKey(); + var tracker = entry.getValue(); + + var sectionPos = net.minecraft.core.SectionPos.of(key); + var pos = sectionPos.chunk(); + + // skip no user listeners + if (!tracker.hasUser()) { + continue; + } + + regionToData.get(CoordinateUtils.getChunkKey(pos.x() >> chunkToRegionShift, pos.z() >> chunkToRegionShift)).containerEntityMovementTrackerMap.put(key, tracker); + } + for (var entry : from.itemEntityMovementTrackerMap.entrySet()) { + final long key = entry.getKey(); + var tracker = entry.getValue(); + + var sectionPos = net.minecraft.core.SectionPos.of(key); + var pos = sectionPos.chunk(); + + // skip no user listeners + if (!tracker.hasUser()) { + continue; + } + + regionToData.get(CoordinateUtils.getChunkKey(pos.x() >> chunkToRegionShift, pos.z() >> chunkToRegionShift)).itemEntityMovementTrackerMap.put(key, tracker); + } + // Shiroha end // connections for (final Connection conn : from.connections) { final ServerPlayer player = conn.getPlayer(); @@ -237,8 +304,9 @@ public final class RegionizedWorldData { from.fluidLevelTicks.split(chunkToRegionShift, levelTicksFluidRegionData); // tile entity ticking - for (final TickingBlockEntity tileEntity : from.pendingBlockEntityTickers) { - final BlockPos pos = tileEntity.getPos(); + for (final TickingBlockEntity tileEntity : from.pendingBlockEntityTickers) { // Shiroha - Region threading for lithium sleeping block entity + BlockPos pos = tileEntity.getPos(); // Shiroha - Region threading for lithium sleeping block entity + if (pos == null) pos = tileEntity.getTileEntity().getBlockPos(); // Shiroha - Region threading for lithium sleeping block entity final int chunkX = pos.getX() >> 4; final int chunkZ = pos.getZ() >> 4; @@ -248,8 +316,9 @@ public final class RegionizedWorldData { } // else: when a chunk unloads, it does not actually _remove_ the tile entity from the list, it just gets // marked as removed. So if there is no section, it's probably removed! } - for (final TickingBlockEntity tileEntity : from.blockEntityTickers) { - final BlockPos pos = tileEntity.getPos(); + for (final TickingBlockEntity tileEntity : from.blockEntityTickers) { // Shiroha - Region threading for lithium sleeping block entity + BlockPos pos = tileEntity.getPos(); // Shiroha - Region threading for lithium sleeping block entity + if (pos == null) pos = tileEntity.getTileEntity().getBlockPos(); // Shiroha - Region threading for lithium sleeping block entity final int chunkX = pos.getX() >> 4; final int chunkZ = pos.getZ() >> 4; @@ -478,6 +547,11 @@ public final class RegionizedWorldData { public final io.papermc.paper.redstone.RedstoneWireTurbo turbo; // Environment attribute system public Reference2ObjectOpenHashMap, EnvironmentAttributeSystem.ValueSampler> attributeSamplers; + // Shiroha start - Region threading for lithium sleeping block entity + // Lithium + public final Map containerEntityMovementTrackerMap = new it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap<>(); + public final Map itemEntityMovementTrackerMap = new it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap<>(); + // Shiroha end public final TickRegions.TickRegionData regionData; diff --git a/net/minecraft/core/NonNullList.java b/net/minecraft/core/NonNullList.java index 8fd27a342f7302284c48b3db7d085bb692e08cdc..8992e13df499333235e1a51b725c0ad3ccd0284d 100644 --- a/net/minecraft/core/NonNullList.java +++ b/net/minecraft/core/NonNullList.java @@ -8,7 +8,7 @@ import java.util.Objects; import org.jspecify.annotations.Nullable; public class NonNullList extends AbstractList { - private final List list; + public final List list; // Leaves - private -> public private final @Nullable E defaultValue; public static NonNullList create() { diff --git a/net/minecraft/core/component/PatchedDataComponentMap.java b/net/minecraft/core/component/PatchedDataComponentMap.java index 08ea8107fc8cbec3742278ab5cf03ef9048720f9..e1397bb21b20a4be1223d12eb1f50677cd9f39d5 100644 --- a/net/minecraft/core/component/PatchedDataComponentMap.java +++ b/net/minecraft/core/component/PatchedDataComponentMap.java @@ -14,7 +14,7 @@ import java.util.Map.Entry; import java.util.stream.Collectors; import org.jspecify.annotations.Nullable; -public final class PatchedDataComponentMap implements DataComponentMap { +public final class PatchedDataComponentMap implements DataComponentMap, org.leavesmc.leaves.lithium.common.util.change_tracking.ChangePublisher { // Leaves - Lithium Sleeping Block Entity private final DataComponentMap prototype; private Reference2ObjectMap, Optional> patch; private boolean copyOnWrite; @@ -137,6 +137,7 @@ public final class PatchedDataComponentMap implements DataComponentMap { } private void ensureMapOwnership() { + if (io.nanachiyo0721.shiroha.config.modules.optimizations.LeavesSleepingBlockEntityConfig.enabled && this.copyOnWrite && this.subscriber != null) this.subscriber.lithium$notify((PatchedDataComponentMap) (Object) this, 0); // Leaves - Lithium Sleeping Block Entity (only notify when actually copying, matches Lithium mixin semantics) if (this.copyOnWrite) { this.patch = new Reference2ObjectArrayMap<>(this.patch); this.copyOnWrite = false; @@ -233,4 +234,22 @@ public final class PatchedDataComponentMap implements DataComponentMap { public String toString() { return "{" + this.stream().map(TypedDataComponent::toString).collect(Collectors.joining(", ")) + "}"; } + + // Leaves start - Lithium Sleeping Block Entity + private org.leavesmc.leaves.lithium.common.util.change_tracking.ChangeSubscriber subscriber; + + @Override + public void lithium$subscribe(org.leavesmc.leaves.lithium.common.util.change_tracking.ChangeSubscriber subscriber, int subscriberData) { + if (subscriberData != 0) { + throw new UnsupportedOperationException("ComponentMapImpl does not support subscriber data"); + } + this.subscriber = org.leavesmc.leaves.lithium.common.util.change_tracking.ChangeSubscriber.combine(this.subscriber, 0, subscriber, 0); + } + + @Override + public int lithium$unsubscribe(org.leavesmc.leaves.lithium.common.util.change_tracking.ChangeSubscriber subscriber) { + this.subscriber = org.leavesmc.leaves.lithium.common.util.change_tracking.ChangeSubscriber.without(this.subscriber, subscriber); + return 0; + } + // Leaves end - Lithium Sleeping Block Entity } diff --git a/net/minecraft/server/commands/data/EntityDataAccessor.java b/net/minecraft/server/commands/data/EntityDataAccessor.java index f2c5a6ce769d8a5fdb4b836eae6a61a4da91cb3f..7574c32c59a3e31973c58b54c5e352aaa161158f 100644 --- a/net/minecraft/server/commands/data/EntityDataAccessor.java +++ b/net/minecraft/server/commands/data/EntityDataAccessor.java @@ -57,6 +57,7 @@ public class EntityDataAccessor implements DataAccessor { try (ProblemReporter.ScopedCollector reporter = new ProblemReporter.ScopedCollector(this.entity.problemPath(), LOGGER)) { this.entity.load(TagValueInput.create(reporter, this.entity.registryAccess(), tag)); this.entity.setUUID(uuid); + if (io.nanachiyo0721.shiroha.config.modules.optimizations.LeavesSleepingBlockEntityConfig.enabled && this.entity instanceof net.minecraft.world.entity.item.ItemEntity itemEntity) itemEntity.levelCallback.onMove(); // Leaves - Lithium Sleeping Block Entity } } diff --git a/net/minecraft/world/Container.java b/net/minecraft/world/Container.java index 6e08d50794c4af4405f3e164a3fd46f376b3f78f..dd2d0940eece3e85078e574f66f71a4c497d75fd 100644 --- a/net/minecraft/world/Container.java +++ b/net/minecraft/world/Container.java @@ -16,7 +16,7 @@ import net.minecraft.world.level.Level; import net.minecraft.world.level.block.entity.BlockEntity; import org.jspecify.annotations.Nullable; -public interface Container extends Clearable, Iterable, SlotProvider { +public interface Container extends Clearable, Iterable, SlotProvider, org.leavesmc.leaves.lithium.api.inventory.LithiumCooldownReceivingInventory, org.leavesmc.leaves.lithium.api.inventory.LithiumTransferConditionInventory { // Leaves - Lithium Sleeping Block Entity float DEFAULT_DISTANCE_BUFFER = 4.0F; int getContainerSize(); diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java index 66593eb947035bd9489c4d31a0e9156c1c81b464..280d6ff1fcbfb74d194a8ade94c2af02fe2cd41b 100644 --- a/net/minecraft/world/entity/Entity.java +++ b/net/minecraft/world/entity/Entity.java @@ -324,7 +324,7 @@ public abstract class Entity private static final EntityDataAccessor DATA_NO_GRAVITY = SynchedEntityData.defineId(Entity.class, EntityDataSerializers.BOOLEAN); protected static final EntityDataAccessor DATA_POSE = SynchedEntityData.defineId(Entity.class, EntityDataSerializers.POSE); public static final EntityDataAccessor DATA_TICKS_FROZEN = SynchedEntityData.defineId(Entity.class, EntityDataSerializers.INT); - private EntityInLevelCallback levelCallback = EntityInLevelCallback.NULL; + public EntityInLevelCallback levelCallback = EntityInLevelCallback.NULL; // Leaves - private -> public private final VecDeltaCodec packetPositionCodec = new VecDeltaCodec(); public boolean needsSync; public boolean syncPosition; @@ -4469,11 +4469,13 @@ public abstract class Entity } protected Entity transformForAsyncTeleport(ServerLevel destination, Vec3 pos, Float yaw, Float pitch, Vec3 velocity) { + final boolean toSameWorld = this.level != destination; // Shiroha - Region threading for lithium sleeping block entity this.removeAfterChangingDimensions(); // remove before so that any CBEntity#getHandle call affects this entity before copying Entity copy = this.getType().create(destination, EntitySpawnReason.DIMENSION_TRAVEL); copy.restoreFrom(this); copy.transform(pos, yaw, pitch, velocity); + if (toSameWorld) copy.notifyLithiumTrackerIfNeeded(); // Shiroha - Region threading for lithium sleeping block entity if (copy instanceof net.minecraft.world.entity.boss.enderdragon.EnderDragon dragon) dragon.syncDragonPartsAfterTeleportTransform(); // Shiroha - Fix dragon part desync // vanilla code used to call remove _after_ copying, and some stuff is required to be after copy - so add hook here // for example, clearing of inventory after switching dimensions @@ -6040,8 +6042,29 @@ public abstract class Entity this.setBoundingBox(this.makeBoundingBox()); } // Paper end - Block invalid positions and bounding box + this.notifyLithiumTrackerIfNeeded(); // Shiroha - Leaves lithium sleeping block entity } + // Shiroha start - Region threading for lithium sleeping block entity + private void notifyLithiumTrackerIfNeeded() { + // Leaves start - Lithium Sleeping Block Entity + if (!io.nanachiyo0721.shiroha.config.modules.optimizations.LeavesSleepingBlockEntityConfig.enabled) return; + var currentWorldData = io.papermc.paper.threadedregions.TickRegionScheduler.getCurrentRegionizedWorldData(); + if (currentWorldData == null || currentWorldData.world != this.level) return; + if (this instanceof ItemEntity) { + long sectionKey = ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkSectionKey(this); + org.leavesmc.leaves.lithium.common.tracking.entity.ChunkSectionItemEntityMovementTracker tracker = currentWorldData.itemEntityMovementTrackerMap.get(sectionKey); + if (tracker != null) tracker.notifyAllListeners(currentWorldData.getRedstoneGameTime()); // Shiroha - Region threading for lithium sleeping block entity + } + else if (this instanceof net.minecraft.world.entity.vehicle.ContainerEntity) { + long sectionKey = ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkSectionKey(this); + org.leavesmc.leaves.lithium.common.tracking.entity.ChunkSectionInventoryEntityTracker tracker = currentWorldData.containerEntityMovementTrackerMap.get(sectionKey); + if (tracker != null) tracker.notifyAllListeners(currentWorldData.getRedstoneGameTime()); // Shiroha - Region threading for lithium sleeping block entity + } + // Leaves end - Lithium Sleeping Block Entity + } + // Shiroha end + public void checkDespawn() { } diff --git a/net/minecraft/world/entity/item/ItemEntity.java b/net/minecraft/world/entity/item/ItemEntity.java index 312c3974bdf27e18b79685a0d0c96cd5c3c134a1..d6abaaa951b12135b00e543d283d7714a575d797 100644 --- a/net/minecraft/world/entity/item/ItemEntity.java +++ b/net/minecraft/world/entity/item/ItemEntity.java @@ -35,8 +35,12 @@ import net.minecraft.world.level.storage.ValueInput; import net.minecraft.world.level.storage.ValueOutput; import net.minecraft.world.phys.Vec3; import org.jspecify.annotations.Nullable; +// Leaves start - Lithium Sleeping Block Entity +import org.leavesmc.leaves.lithium.common.util.change_tracking.ChangePublisher; +import org.leavesmc.leaves.lithium.common.util.change_tracking.ChangeSubscriber; +// Leaves end - Lithium Sleeping Block Entity -public class ItemEntity extends Entity implements TraceableEntity { +public class ItemEntity extends Entity implements TraceableEntity, ChangePublisher, ChangeSubscriber.CountChangeSubscriber { // Leaves - Lithium Sleeping Block Entity private static final EntityDataAccessor DATA_ITEM = SynchedEntityData.defineId(ItemEntity.class, EntityDataSerializers.ITEM_STACK); private static final float FLOAT_HEIGHT = 0.1F; public static final float EYE_HEIGHT = 0.2125F; @@ -532,6 +536,25 @@ public class ItemEntity extends Entity implements TraceableEntity { } public void setItem(final ItemStack itemStack) { + // Leaves start - Lithium Sleeping Block Entity + if (io.nanachiyo0721.shiroha.config.modules.optimizations.LeavesSleepingBlockEntityConfig.enabled && this.subscriber != null) { + ItemStack oldStack = this.getItem(); + if (oldStack != itemStack) { + if (!oldStack.isEmpty()) { + oldStack.lithium$unsubscribe(this); + } + + if (!itemStack.isEmpty()) { + itemStack.lithium$subscribe(this, this.subscriberData); + this.subscriber.lithium$notify((ItemEntity) (Object) this, this.subscriberData); + } else { + this.subscriber.lithium$forceUnsubscribe((ItemEntity) (Object) this, this.subscriberData); + this.subscriber = null; + this.subscriberData = 0; + } + } + } + // Leaves end - Lithium Sleeping Block Entity this.getEntityData().set(DATA_ITEM, itemStack); this.despawnRate = this.level().paperConfig().entities.spawning.altItemDespawnRate.enabled ? this.level().paperConfig().entities.spawning.altItemDespawnRate.items.getOrDefault(itemStack.getItem(), this.level().spigotConfig.itemDespawnRate) : this.level().spigotConfig.itemDespawnRate; // Paper - Alternative item-despawn-rate } @@ -599,4 +622,76 @@ public class ItemEntity extends Entity implements TraceableEntity { public @Nullable SlotAccess getSlot(final int slot) { return slot == 0 ? SlotAccess.of(this::getItem, this::setItem) : super.getSlot(slot); } + + // Leaves start - Lithium Sleeping Block Entity + private ChangeSubscriber subscriber; + //Stores the data of the subscriber, unless the subscriber is a Multi which stores the data in a list, in which case this variable stores 0 + private int subscriberData; + + private void startTrackingChanges() { + ItemStack stack = this.getItem(); + if (!stack.isEmpty()) { + stack.lithium$subscribe(this, 0); + } + } + + @Override + public void lithium$subscribe(ChangeSubscriber subscriber, int subscriberData) { + if (this.subscriber == null) { + this.startTrackingChanges(); + } + this.subscriber = ChangeSubscriber.combine(this.subscriber, this.subscriberData, subscriber, subscriberData); + if (this.subscriber instanceof ChangeSubscriber.Multi) { + this.subscriberData = 0; + } else { + this.subscriberData = subscriberData; + } + } + + @Override + public int lithium$unsubscribe(ChangeSubscriber subscriber) { + int retval = ChangeSubscriber.dataOf(this.subscriber, subscriber, this.subscriberData); + this.subscriberData = ChangeSubscriber.dataWithout(this.subscriber, subscriber, this.subscriberData); + this.subscriber = ChangeSubscriber.without(this.subscriber, subscriber); + + if (this.subscriber == null) { + ItemStack stack = this.getItem(); + if (!stack.isEmpty()) { + stack.lithium$unsubscribe(this); + } + } + return retval; + } + + @Override + public void lithium$notify(ItemStack publisher, int subscriberData) { + if (publisher != this.getItem()) { + throw new IllegalStateException("Received notification from an unexpected publisher"); + } + + if (this.subscriber != null) { + this.subscriber.lithium$notify(this, this.subscriberData); + } + } + + @Override + public void lithium$forceUnsubscribe(ItemStack publisher, int subscriberData) { + if (this.subscriber != null) { + this.subscriber.lithium$forceUnsubscribe(this, this.subscriberData); + this.subscriber = null; + this.subscriberData = 0; + } + } + + @Override + public void lithium$notifyCount(ItemStack publisher, int subscriberData, int newCount) { + if (publisher != this.getItem()) { + throw new IllegalStateException("Received notification from an unexpected publisher"); + } + + if (this.subscriber instanceof ChangeSubscriber.CountChangeSubscriber countChangeSubscriber) { + countChangeSubscriber.lithium$notifyCount(this, this.subscriberData, newCount); + } + } + // Leaves end - Lithium Sleeping Block Entity } diff --git a/net/minecraft/world/entity/vehicle/minecart/AbstractMinecartContainer.java b/net/minecraft/world/entity/vehicle/minecart/AbstractMinecartContainer.java index d3651110e15e8e1ad530df4bb4c05dbc73a79a63..d87dcb72fd5d2b22eafc1a444d4fc3807595b6a5 100644 --- a/net/minecraft/world/entity/vehicle/minecart/AbstractMinecartContainer.java +++ b/net/minecraft/world/entity/vehicle/minecart/AbstractMinecartContainer.java @@ -23,7 +23,7 @@ import net.minecraft.world.level.storage.loot.LootTable; import net.minecraft.world.phys.Vec3; import org.jspecify.annotations.Nullable; -public abstract class AbstractMinecartContainer extends AbstractMinecart implements ContainerEntity { +public abstract class AbstractMinecartContainer extends AbstractMinecart implements ContainerEntity, org.leavesmc.leaves.lithium.api.inventory.LithiumInventory { // Leaves - Lithium Sleeping Block Entity private NonNullList itemStacks = NonNullList.withSize(this.getContainerSize(), ItemStack.EMPTY); // CraftBukkit - SPIGOT-3513 private @Nullable ResourceKey lootTable; private long lootTableSeed; @@ -217,4 +217,15 @@ public abstract class AbstractMinecartContainer extends AbstractMinecart impleme return this.getBukkitEntity().getLocation(); } // CraftBukkit end + // Leaves start - Lithium Sleeping Block Entity + @Override + public net.minecraft.core.NonNullList getInventoryLithium() { + return itemStacks; + } + + @Override + public void setInventoryLithium(net.minecraft.core.NonNullList inventory) { + itemStacks = inventory; + } + // Leaves end - Lithium Sleeping Block Entity } diff --git a/net/minecraft/world/inventory/AbstractContainerMenu.java b/net/minecraft/world/inventory/AbstractContainerMenu.java index 136e018d83c5be90894358a97b23f5fcfa6c0d38..369d71f317f771fddbe9939e50a77562444209ec 100644 --- a/net/minecraft/world/inventory/AbstractContainerMenu.java +++ b/net/minecraft/world/inventory/AbstractContainerMenu.java @@ -893,6 +893,7 @@ public abstract class AbstractContainerMenu { float totalPercent = 0.0F; + if (io.nanachiyo0721.shiroha.config.modules.optimizations.LeavesSleepingBlockEntityConfig.enabled && container instanceof org.leavesmc.leaves.lithium.api.inventory.LithiumInventory optimizedInventory) return org.leavesmc.leaves.lithium.common.hopper.InventoryHelper.getLithiumStackList(optimizedInventory).getSignalStrength(container); // Leaves - Lithium Sleeping Block Entity for (int i = 0; i < container.getContainerSize(); i++) { ItemStack itemStack = container.getItem(i); if (!itemStack.isEmpty()) { diff --git a/net/minecraft/world/item/ItemStack.java b/net/minecraft/world/item/ItemStack.java index dc1e9acce122f3ebd7bf2b03150b4045db9493df..b507be6db077a0c9b77270f3359b0908c9298c71 100644 --- a/net/minecraft/world/item/ItemStack.java +++ b/net/minecraft/world/item/ItemStack.java @@ -98,8 +98,12 @@ import org.apache.commons.lang3.function.TriConsumer; import org.apache.commons.lang3.mutable.MutableBoolean; import org.jspecify.annotations.Nullable; import org.slf4j.Logger; +// Leaves start - Lithium Sleeping Block Entity +import org.leavesmc.leaves.lithium.common.util.change_tracking.ChangePublisher; +import org.leavesmc.leaves.lithium.common.util.change_tracking.ChangeSubscriber; +// Leaves end - Lithium Sleeping Block Entity -public final class ItemStack implements DataComponentHolder, ItemInstance { +public final class ItemStack implements DataComponentHolder, ItemInstance, ChangePublisher, ChangeSubscriber { // Leaves - Lithium Sleeping Block Entity private static final List OP_NBT_WARNING = List.of( Component.translatable("item.op_warning.line1").withStyle(ChatFormatting.RED, ChatFormatting.BOLD), Component.translatable("item.op_warning.line2").withStyle(ChatFormatting.RED), @@ -965,6 +969,7 @@ public final class ItemStack implements DataComponentHolder, ItemInstance { // CraftBukkit end public @Nullable T set(final DataComponentType type, final @Nullable T value) { + if (io.nanachiyo0721.shiroha.config.modules.optimizations.LeavesSleepingBlockEntityConfig.enabled && type == DataComponents.ENCHANTMENTS && this.subscriber instanceof ChangeSubscriber.EnchantmentSubscriber enchantmentSubscriber) enchantmentSubscriber.lithium$notifyAfterEnchantmentChange(this, this.subscriberData); // Leaves - Lithium Sleeping Block Entity return this.components.set(type, value); } @@ -1304,6 +1309,23 @@ public final class ItemStack implements DataComponentHolder, ItemInstance { } public void setCount(final int count) { + // Leaves start - Lithium Sleeping Block Entity + if (io.nanachiyo0721.shiroha.config.modules.optimizations.LeavesSleepingBlockEntityConfig.enabled && count != this.count) { + if (this.subscriber instanceof ChangeSubscriber.CountChangeSubscriber countChangeSubscriber) { + countChangeSubscriber.lithium$notifyCount(this, this.subscriberData, count); + } + + if (count == 0) { + this.components.lithium$unsubscribe(this); + + if (this.subscriber != null) { + this.subscriber.lithium$forceUnsubscribe(this, this.subscriberData); + this.subscriber = null; + this.subscriberData = 0; + } + } + } + // Leaves end - Lithium Sleeping Block Entity this.count = count; } @@ -1371,4 +1393,90 @@ public final class ItemStack implements DataComponentHolder, ItemInstance { .or(() -> Optional.ofNullable(this.getItem().getItemDamageSource(attacker))) .orElseGet(attacker::createDamageSource); } + + // Leaves start - Lithium Sleeping Block Entity + private ChangeSubscriber subscriber; + private int subscriberData; + + @Override + public void lithium$subscribe(ChangeSubscriber subscriber, int subscriberData) { + if (this.isEmpty()) { + throw new IllegalStateException("Cannot subscribe to an empty ItemStack!"); + } + + if (this.subscriber == null) { + this.startTrackingChanges(); + } + this.subscriber = ChangeSubscriber.combine(this.subscriber, this.subscriberData, subscriber, subscriberData); + if (this.subscriber instanceof ChangeSubscriber.Multi) { + this.subscriberData = 0; + } else { + this.subscriberData = subscriberData; + } + } + + @Override + public int lithium$unsubscribe(ChangeSubscriber subscriber) { + if (this.isEmpty()) { + throw new IllegalStateException("Cannot unsubscribe from an empty ItemStack!"); + } + + int retval = ChangeSubscriber.dataOf(this.subscriber, subscriber, this.subscriberData); + this.subscriberData = ChangeSubscriber.dataWithout(this.subscriber, subscriber, this.subscriberData); + this.subscriber = ChangeSubscriber.without(this.subscriber, subscriber); + + if (this.subscriber == null) { + this.components.lithium$unsubscribe(this); + } + return retval; + } + + @Override + public void lithium$unsubscribeWithData(ChangeSubscriber subscriber, int subscriberData) { + if (this.isEmpty()) { + throw new IllegalStateException("Cannot unsubscribe from an empty ItemStack!"); + } + + this.subscriberData = ChangeSubscriber.dataWithout(this.subscriber, subscriber, this.subscriberData, subscriberData, true); + this.subscriber = ChangeSubscriber.without(this.subscriber, subscriber, subscriberData, true); + + if (this.subscriber == null) { + this.components.lithium$unsubscribe(this); + } + } + + @Override + public boolean lithium$isSubscribedWithData(ChangeSubscriber subscriber, int subscriberData) { + if (this.isEmpty()) { + throw new IllegalStateException("Cannot be subscribed to an empty ItemStack!"); + } + + return ChangeSubscriber.containsSubscriber(this.subscriber, this.subscriberData, subscriber, subscriberData); + } + + @Override + public void lithium$forceUnsubscribe(PatchedDataComponentMap publisher, int subscriberData) { + if (publisher != this.components) { + throw new IllegalStateException("Invalid publisher, expected " + this.components + " but got " + publisher); + } + this.subscriber.lithium$forceUnsubscribe(this, this.subscriberData); + this.subscriber = null; + this.subscriberData = 0; + } + + private void startTrackingChanges() { + this.components.lithium$subscribe(this, 0); + } + + @Override + public void lithium$notify(PatchedDataComponentMap publisher, int subscriberData) { + if (publisher != this.components) { + throw new IllegalStateException("Invalid publisher, expected " + this.components + " but got " + publisher); + } + + if (this.subscriber != null) { + this.subscriber.lithium$notify(this, this.subscriberData); + } + } + // Leaves end - Lithium Sleeping Block Entity } diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java index 5878cd5ee450430276d0d97f2a33d49109d09ed5..fffaad68dd29198ae03994dcbd6c0c1e447135b5 100644 --- a/net/minecraft/world/level/Level.java +++ b/net/minecraft/world/level/Level.java @@ -1553,7 +1553,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl // Paper end - Fix MC-117075 use removeAll if (ticker.isRemoved()) { toRemove.add(ticker); // Paper - Fix MC-117075 use removeAll - } else if (tickBlockEntities && this.shouldTickBlocksAt(ticker.getPos())) { + } else if (tickBlockEntities && this.shouldTickBlockPosFilterNull(ticker.getPos())) { // Leaves - Lithium Sleeping Block Entity ticker.tick(); // Paper start - rewrite chunk system if ((++tickedEntities & 7) == 0) { @@ -1631,6 +1631,27 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl return this.shouldTickBlocksAt(ChunkPos.pack(pos)); } + // Leaves start - Lithium Sleeping Block Entity + public @org.jspecify.annotations.Nullable BlockEntity lithium$getLoadedExistingBlockEntity(BlockPos pos) { + if (!this.isOutsideBuildHeight(pos)) { + if (this.isClientSide || ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(this, pos)) { + ChunkAccess chunk = this.getChunk(SectionPos.blockToSectionCoord(pos.getX()), SectionPos.blockToSectionCoord(pos.getZ()), ChunkStatus.FULL, false); + if (chunk != null) { + return chunk.getBlockEntity(pos); + } + } + } + return null; + } + + private boolean shouldTickBlockPosFilterNull(BlockPos pos) { + if (pos == null) { + return false; + } + return shouldTickBlocksAt(pos); + } + // Leaves end - Lithium Sleeping Block Entity + public void explode( final @Nullable Entity source, final double x, final double y, final double z, final float r, final Level.ExplosionInteraction blockInteraction ) { diff --git a/net/minecraft/world/level/block/ComposterBlock.java b/net/minecraft/world/level/block/ComposterBlock.java index 69e3969242ad52168e947af209e6fa72f36178ae..ad0012256b6e7c490271f36b390db96c4bfe8f3d 100644 --- a/net/minecraft/world/level/block/ComposterBlock.java +++ b/net/minecraft/world/level/block/ComposterBlock.java @@ -426,7 +426,7 @@ public class ComposterBlock extends Block implements WorldlyContainerHolder { } } - public static class EmptyContainer extends SimpleContainer implements WorldlyContainer { + public static class EmptyContainer extends SimpleContainer implements WorldlyContainer, org.leavesmc.leaves.lithium.common.hopper.BlockStateOnlyInventory { // Leaves - Lithium Sleeping Block Entity public EmptyContainer(LevelAccessor levelAccessor, BlockPos blockPos) { // CraftBukkit super(0); this.bukkitOwner = new org.bukkit.craftbukkit.inventory.CraftBlockInventoryHolder(levelAccessor, blockPos, this); // CraftBukkit @@ -448,7 +448,7 @@ public class ComposterBlock extends Block implements WorldlyContainerHolder { } } - public static class InputContainer extends SimpleContainer implements WorldlyContainer { + public static class InputContainer extends SimpleContainer implements WorldlyContainer, org.leavesmc.leaves.lithium.common.hopper.BlockStateOnlyInventory { // Leaves - Lithium Sleeping Block Entity private final BlockState state; private final LevelAccessor level; private final BlockPos pos; @@ -494,12 +494,13 @@ public class ComposterBlock extends Block implements WorldlyContainerHolder { } // Paper end - Add CompostItemEvent and EntityCompostItemEvent this.level.levelEvent(LevelEvent.COMPOSTER_FILL, this.pos, newState != this.state ? 1 : 0); + if (io.nanachiyo0721.shiroha.config.modules.optimizations.LeavesSleepingBlockEntityConfig.enabled) this.changed = false; // Leaves - Lithium Sleeping Block Entity this.removeItemNoUpdate(0); } } } - public static class OutputContainer extends SimpleContainer implements WorldlyContainer { + public static class OutputContainer extends SimpleContainer implements WorldlyContainer, org.leavesmc.leaves.lithium.common.hopper.BlockStateOnlyInventory { // Leaves - Lithium Sleeping Block Entity private final BlockState state; private final LevelAccessor level; private final BlockPos pos; diff --git a/net/minecraft/world/level/block/DiodeBlock.java b/net/minecraft/world/level/block/DiodeBlock.java index 2339099e05af80b234e64778822b012cc6318157..a98a9ed2f992b7f269e6c9d893966dd1a687f43e 100644 --- a/net/minecraft/world/level/block/DiodeBlock.java +++ b/net/minecraft/world/level/block/DiodeBlock.java @@ -179,6 +179,7 @@ public abstract class DiodeBlock extends HorizontalDirectionalBlock { @Override protected void onPlace(final BlockState state, final Level level, final BlockPos pos, final BlockState oldState, final boolean movedByPiston) { this.updateNeighborsInFront(level, pos, state); + if (io.nanachiyo0721.shiroha.config.modules.optimizations.LeavesSleepingBlockEntityConfig.enabled && this instanceof ComparatorBlock && !oldState.is(Blocks.COMPARATOR)) org.leavesmc.leaves.lithium.common.block.entity.inventory_comparator_tracking.ComparatorTracking.notifyNearbyBlockEntitiesAboutNewComparator(level, pos); // Leaves - Lithium Sleeping Block Entity } @Override diff --git a/net/minecraft/world/level/block/HopperBlock.java b/net/minecraft/world/level/block/HopperBlock.java index 3932da81757ea0ab7a41617165f5b64dc44ea3eb..ef6aef74c8b031b1c05fc393e4a36c00037e017a 100644 --- a/net/minecraft/world/level/block/HopperBlock.java +++ b/net/minecraft/world/level/block/HopperBlock.java @@ -38,7 +38,7 @@ import net.minecraft.world.phys.shapes.Shapes; import net.minecraft.world.phys.shapes.VoxelShape; import org.jspecify.annotations.Nullable; -public class HopperBlock extends BaseEntityBlock { +public class HopperBlock extends BaseEntityBlock implements org.leavesmc.leaves.lithium.common.block.entity.ShapeUpdateHandlingBlockBehaviour { // Leaves - Lithium Sleeping Block Entity public static final MapCodec CODEC = simpleCodec(HopperBlock::new); public static final EnumProperty FACING = BlockStateProperties.FACING_HOPPER; public static final BooleanProperty ENABLED = BlockStateProperties.ENABLED; @@ -100,6 +100,17 @@ public class HopperBlock extends BaseEntityBlock { protected void onPlace(final BlockState state, final Level level, final BlockPos pos, final BlockState oldState, final boolean movedByPiston) { if (!oldState.is(state.getBlock())) { this.checkPoweredState(level, pos, state); + // Leaves start - Lithium Sleeping Block Entity + //invalidate caches of nearby hoppers when placing an update suppressed hopper + if (io.nanachiyo0721.shiroha.config.modules.optimizations.LeavesSleepingBlockEntityConfig.enabled && level.getBlockState(pos) != state) { + for (Direction direction : UPDATE_SHAPE_ORDER) { + BlockEntity hopper = level.lithium$getLoadedExistingBlockEntity(pos.relative(direction)); + if (hopper instanceof org.leavesmc.leaves.lithium.common.hopper.UpdateReceiver updateReceiver) { + updateReceiver.lithium$invalidateCacheOnNeighborUpdate(direction == Direction.DOWN); + } + } + } + // Leaves end - Lithium Sleeping Block Entity } } @@ -118,6 +129,7 @@ public class HopperBlock extends BaseEntityBlock { protected void neighborChanged( final BlockState state, final Level level, final BlockPos pos, final Block block, final @Nullable Orientation orientation, final boolean movedByPiston ) { + if (io.nanachiyo0721.shiroha.config.modules.optimizations.LeavesSleepingBlockEntityConfig.enabled && level.lithium$getLoadedExistingBlockEntity(pos) instanceof org.leavesmc.leaves.lithium.common.hopper.UpdateReceiver updateReceiver) updateReceiver.lithium$invalidateCacheOnUndirectedNeighborUpdate(); // Leaves - Lithium Sleeping Block Entity this.checkPoweredState(level, pos, state); } @@ -177,4 +189,25 @@ public class HopperBlock extends BaseEntityBlock { protected boolean isPathfindable(final BlockState state, final PathComputationType type) { return false; } + + // Leaves start - Lithium Sleeping Block Entity + @Override + public void lithium$handleShapeUpdate(net.minecraft.world.level.LevelReader levelReader, BlockState myBlockState, BlockPos myPos, BlockPos posFrom, BlockState newState) { + //invalidate cache when composters change state + if (newState.getBlock() instanceof net.minecraft.world.WorldlyContainerHolder) { + this.updateHopper(levelReader, myBlockState, myPos, posFrom); + } + } + + private void updateHopper(net.minecraft.world.level.LevelReader world, BlockState myBlockState, BlockPos myPos, BlockPos posFrom) { + Direction facing = myBlockState.getValue(HopperBlock.FACING); + boolean above = posFrom.getY() == myPos.getY() + 1; + if (above || posFrom.getX() == myPos.getX() + facing.getStepX() && posFrom.getY() == myPos.getY() + facing.getStepY() && posFrom.getZ() == myPos.getZ() + facing.getStepZ()) { + BlockEntity hopper = ((net.minecraft.world.level.Level) world).lithium$getLoadedExistingBlockEntity(myPos); + if (hopper instanceof org.leavesmc.leaves.lithium.common.hopper.UpdateReceiver updateReceiver) { + updateReceiver.lithium$invalidateCacheOnNeighborUpdate(above); + } + } + } + // Leaves end - Lithium Sleeping Block Entity } diff --git a/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java b/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java index 78dbdf43ac9095eb3cf0b0653bab2603d5527ff4..f1c5f122102e3164092342239e866ed672a1ec2e 100644 --- a/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java +++ b/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java @@ -39,7 +39,7 @@ import net.minecraft.world.level.storage.ValueOutput; import net.minecraft.world.phys.Vec3; import org.jspecify.annotations.Nullable; -public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntity implements WorldlyContainer, StackedContentsCompatible, RecipeCraftingHolder { +public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntity implements WorldlyContainer, StackedContentsCompatible, RecipeCraftingHolder, org.leavesmc.leaves.lithium.common.block.entity.inventory_change_tracking.InventoryChangeTracker, org.leavesmc.leaves.lithium.common.block.entity.SleepingBlockEntity, org.leavesmc.leaves.lithium.common.block.entity.SetChangedHandlingBlockEntity, org.leavesmc.leaves.lithium.api.inventory.LithiumInventory { // Leaves - Lithium Sleeping Block Entity protected static final int SLOT_INPUT = 0; protected static final int SLOT_FUEL = 1; protected static final int SLOT_RESULT = 2; @@ -162,6 +162,7 @@ public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntit this.recipesUsed.clear(); this.recipesUsed.putAll(input.read("RecipesUsed", RECIPES_USED_CODEC).orElse(Map.of())); this.cookSpeedMultiplier = input.getDoubleOr("Paper.CookSpeedMultiplier", 1); // Paper - cook speed multiplier API + if (io.nanachiyo0721.shiroha.config.modules.optimizations.LeavesSleepingBlockEntityConfig.enabled && this.isSleeping() && this.level != null) this.wakeUpNow(); // Leaves - Lithium Sleeping Block Entity } @Override @@ -269,6 +270,7 @@ public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntit if (changed) { setChanged(level, pos, state); } + if (io.nanachiyo0721.shiroha.config.modules.optimizations.LeavesSleepingBlockEntityConfig.enabled) entity.checkSleep(state); // Leaves - Lithium Sleeping Block Entity } private static void consumeFuel(final NonNullList items, final ItemStack fuel) { @@ -510,4 +512,53 @@ public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntit this.getRecipesToAwardAndPopExperience(serverLevel, Vec3.atCenterOf(pos)); } } + + // Leaves start - Lithium Sleeping Block Entity + private net.minecraft.world.level.chunk.LevelChunk.RebindableTickingBlockEntityWrapper tickWrapper = null; + private TickingBlockEntity sleepingTicker = null; + + @Override + public net.minecraft.world.level.chunk.LevelChunk.RebindableTickingBlockEntityWrapper lithium$getTickWrapper() { + return tickWrapper; + } + + @Override + public void lithium$setTickWrapper(net.minecraft.world.level.chunk.LevelChunk.RebindableTickingBlockEntityWrapper tickWrapper) { + this.tickWrapper = tickWrapper; + this.lithium$setSleepingTicker(null); + } + + @Override + public TickingBlockEntity lithium$getSleepingTicker() { + return sleepingTicker; + } + + @Override + public void lithium$setSleepingTicker(TickingBlockEntity sleepingTicker) { + this.sleepingTicker = sleepingTicker; + } + + private void checkSleep(BlockState state) { + if (this.litTimeRemaining <= 0 && this.cookingTimer == 0 && (state.is(net.minecraft.world.level.block.Blocks.FURNACE) || state.is(net.minecraft.world.level.block.Blocks.BLAST_FURNACE) || state.is(net.minecraft.world.level.block.Blocks.SMOKER)) && this.level != null) { // Leaves - Paper 26.1 fix + this.lithium$startSleeping(); + } + } + + @Override + public void lithium$handleSetChanged() { + if (this.isSleeping() && this.level != null && !this.level.isClientSide()) { + this.wakeUpNow(); + } + } + + @Override + public net.minecraft.core.NonNullList getInventoryLithium() { + return items; + } + + @Override + public void setInventoryLithium(net.minecraft.core.NonNullList inventory) { + items = inventory; + } + // Leaves end - Lithium Sleeping Block Entity } diff --git a/net/minecraft/world/level/block/entity/BarrelBlockEntity.java b/net/minecraft/world/level/block/entity/BarrelBlockEntity.java index 118af4e35eec652e7eb8a484a44652cfabc1bab5..35498b936f7d3f82a3379a2e0e3da2060d8d4bb2 100644 --- a/net/minecraft/world/level/block/entity/BarrelBlockEntity.java +++ b/net/minecraft/world/level/block/entity/BarrelBlockEntity.java @@ -23,7 +23,7 @@ import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.storage.ValueInput; import net.minecraft.world.level.storage.ValueOutput; -public class BarrelBlockEntity extends RandomizableContainerBlockEntity { +public class BarrelBlockEntity extends RandomizableContainerBlockEntity implements org.leavesmc.leaves.lithium.common.block.entity.inventory_change_tracking.InventoryChangeTracker, org.leavesmc.leaves.lithium.api.inventory.LithiumInventory { // Leaves - Lithium Sleeping Block Entity // CraftBukkit start - add fields and methods public java.util.List transaction = new java.util.ArrayList<>(); private int maxStack = MAX_STACK; @@ -129,6 +129,7 @@ public class BarrelBlockEntity extends RandomizableContainerBlockEntity { @Override protected void setItems(final NonNullList items) { this.items = items; + if (io.nanachiyo0721.shiroha.config.modules.optimizations.LeavesSleepingBlockEntityConfig.enabled) this.lithium$emitStackListReplaced(); // Leaves - Lithium Sleeping Block Entity } @Override @@ -180,4 +181,18 @@ public class BarrelBlockEntity extends RandomizableContainerBlockEntity { double z = this.worldPosition.getZ() + 0.5 + direction.getZ() / 2.0; this.level.playSound(null, x, y, z, event, SoundSource.BLOCKS, 0.5F, this.level.getRandom().nextFloat() * 0.1F + 0.9F); } + + // Leaves start - Lithium Sleeping Block Entity + + + @Override + public net.minecraft.core.NonNullList getInventoryLithium() { + return items; + } + + @Override + public void setInventoryLithium(net.minecraft.core.NonNullList inventory) { + items = inventory; + } + // Leaves end - Lithium Sleeping Block Entity } diff --git a/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java b/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java index 0e594121b60aa5a01b5bbe94b80350f2fc698b1a..1b6965f8ba4b9376fdb1c72ff8966006f9eb59cd 100644 --- a/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java +++ b/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java @@ -25,8 +25,17 @@ import net.minecraft.world.level.storage.ValueInput; import net.minecraft.world.level.storage.ValueOutput; import net.minecraft.world.phys.Vec3; import org.jspecify.annotations.Nullable; +// Leaves start - Lithium Sleeping Block Entity +import it.unimi.dsi.fastutil.objects.ReferenceArraySet; +import org.leavesmc.leaves.lithium.api.inventory.LithiumInventory; +import org.leavesmc.leaves.lithium.common.block.entity.inventory_change_tracking.InventoryChangeEmitter; +import org.leavesmc.leaves.lithium.common.block.entity.inventory_change_tracking.InventoryChangeListener; +import org.leavesmc.leaves.lithium.common.block.entity.inventory_change_tracking.InventoryChangeTracker; +import org.leavesmc.leaves.lithium.common.hopper.InventoryHelper; +import org.leavesmc.leaves.lithium.common.hopper.LithiumStackList; +// Leaves end - Lithium Sleeping Block Entity -public abstract class BaseContainerBlockEntity extends BlockEntity implements Container, MenuProvider, Nameable { +public abstract class BaseContainerBlockEntity extends BlockEntity implements Container, MenuProvider, Nameable, InventoryChangeEmitter { // Leaves - Lithium Sleeping Block Entity public LockCode lockKey = LockCode.NO_LOCK; public @Nullable Component name; @@ -39,6 +48,7 @@ public abstract class BaseContainerBlockEntity extends BlockEntity implements Co super.loadAdditional(input); this.lockKey = LockCode.fromTag(input); this.name = parseCustomNameSafe(input, "CustomName"); + if (io.nanachiyo0721.shiroha.config.modules.optimizations.LeavesSleepingBlockEntityConfig.enabled && this instanceof InventoryChangeTracker inventoryChangeTracker) inventoryChangeTracker.lithium$emitStackListReplaced(); // Leaves - Lithium Sleeping Block Entity } @Override @@ -183,4 +193,98 @@ public abstract class BaseContainerBlockEntity extends BlockEntity implements Co return org.bukkit.craftbukkit.util.CraftLocation.toBukkit(this.worldPosition, this.level); } // CraftBukkit end + + // Leaves start - Lithium Sleeping Block Entity + ReferenceArraySet inventoryChangeListeners = null; + ReferenceArraySet inventoryHandlingTypeListeners = null; + + @Override + public void lithium$emitContentModified() { + ReferenceArraySet inventoryChangeListeners = this.inventoryChangeListeners; + if (inventoryChangeListeners != null) { + for (InventoryChangeListener inventoryChangeListener : inventoryChangeListeners) { + inventoryChangeListener.lithium$handleInventoryContentModified(this); + } + inventoryChangeListeners.clear(); + } + } + + @Override + public void lithium$emitStackListReplaced() { + ReferenceArraySet listeners = this.inventoryHandlingTypeListeners; + if (listeners != null && !listeners.isEmpty()) { + for (InventoryChangeListener inventoryChangeListener : listeners) { + inventoryChangeListener.handleStackListReplaced(this); + } + listeners.clear(); + } + + if (this instanceof InventoryChangeListener listener) { + listener.handleStackListReplaced(this); + } + + this.invalidateChangeListening(); + } + + @Override + public void lithium$emitRemoved() { + ReferenceArraySet listeners = this.inventoryHandlingTypeListeners; + if (listeners != null && !listeners.isEmpty()) { + for (InventoryChangeListener listener : listeners) { + listener.lithium$handleInventoryRemoved(this); + } + listeners.clear(); + } + + if (this instanceof InventoryChangeListener listener) { + listener.lithium$handleInventoryRemoved(this); + } + + this.invalidateChangeListening(); + } + + private void invalidateChangeListening() { + if (this.inventoryChangeListeners != null) { + this.inventoryChangeListeners.clear(); + } + + LithiumStackList lithiumStackList = this instanceof LithiumInventory ? InventoryHelper.getLithiumStackListOrNull((LithiumInventory) this) : null; + if (lithiumStackList != null && this instanceof InventoryChangeTracker inventoryChangeTracker) { + lithiumStackList.removeInventoryModificationCallback(inventoryChangeTracker); + } + } + + @Override + public void lithium$emitFirstComparatorAdded() { + ReferenceArraySet inventoryChangeListeners = this.inventoryChangeListeners; + if (inventoryChangeListeners != null && !inventoryChangeListeners.isEmpty()) { + inventoryChangeListeners.removeIf(inventoryChangeListener -> inventoryChangeListener.lithium$handleComparatorAdded(this)); + } + } + + @Override + public void lithium$forwardContentChangeOnce(InventoryChangeListener inventoryChangeListener, LithiumStackList stackList, InventoryChangeTracker thisTracker) { + if (this.inventoryChangeListeners == null) { + this.inventoryChangeListeners = new ReferenceArraySet<>(1); + } + stackList.setInventoryModificationCallback(thisTracker); + this.inventoryChangeListeners.add(inventoryChangeListener); + + } + + @Override + public void lithium$forwardMajorInventoryChanges(InventoryChangeListener inventoryChangeListener) { + if (this.inventoryHandlingTypeListeners == null) { + this.inventoryHandlingTypeListeners = new ReferenceArraySet<>(1); + } + this.inventoryHandlingTypeListeners.add(inventoryChangeListener); + } + + @Override + public void lithium$stopForwardingMajorInventoryChanges(InventoryChangeListener inventoryChangeListener) { + if (this.inventoryHandlingTypeListeners != null) { + this.inventoryHandlingTypeListeners.remove(inventoryChangeListener); + } + } + // Leaves end - Lithium Sleeping Block Entity } diff --git a/net/minecraft/world/level/block/entity/BlockEntity.java b/net/minecraft/world/level/block/entity/BlockEntity.java index 2887cbaa954771d0fe357d8ec8df75073e9afa91..e3cbfcce259e3b6b5f8dae28c67be406c2650bdc 100644 --- a/net/minecraft/world/level/block/entity/BlockEntity.java +++ b/net/minecraft/world/level/block/entity/BlockEntity.java @@ -37,8 +37,16 @@ import net.minecraft.world.level.storage.ValueInput; import net.minecraft.world.level.storage.ValueOutput; import org.jspecify.annotations.Nullable; import org.slf4j.Logger; - -public abstract class BlockEntity implements DebugValueSource, TypedInstance> { +// Leaves start - Lithium Sleeping Block Entity +import net.minecraft.core.Direction; +import org.leavesmc.leaves.lithium.common.block.entity.inventory_comparator_tracking.ComparatorTracker; +import org.leavesmc.leaves.lithium.common.block.entity.inventory_comparator_tracking.ComparatorTracking; +import org.leavesmc.leaves.lithium.common.block.entity.inventory_change_tracking.InventoryChangeTracker; +import org.leavesmc.leaves.lithium.common.block.entity.SetBlockStateHandlingBlockEntity; +import org.leavesmc.leaves.lithium.common.block.entity.SetChangedHandlingBlockEntity; +// Leaves end - Lithium Sleeping Block Entity + +public abstract class BlockEntity implements DebugValueSource, TypedInstance>, ComparatorTracker, SetBlockStateHandlingBlockEntity, SetChangedHandlingBlockEntity { // Leaves - Lithium Sleeping Block Entity static final ThreadLocal IGNORE_TILE_UPDATES = ThreadLocal.withInitial(() -> Boolean.FALSE); // Paper - Perf: Optimize Hoppers // Folia - region threading // CraftBukkit start - data containers private static final org.bukkit.craftbukkit.persistence.CraftPersistentDataTypeRegistry DATA_TYPE_REGISTRY = new org.bukkit.craftbukkit.persistence.CraftPersistentDataTypeRegistry(); @@ -65,6 +73,7 @@ public abstract class BlockEntity implements DebugValueSource, TypedInstance= 1 && offset <= 2) { + this.hasComparators = COMPARATOR_PRESENT; + + if (this instanceof InventoryChangeTracker inventoryChangeTracker) { + inventoryChangeTracker.lithium$emitFirstComparatorAdded(); + } + } + } + + @Override + public boolean lithium$hasAnyComparatorNearby() { + if (this.hasComparators == UNKNOWN) { + this.hasComparators = ComparatorTracking.findNearbyComparators(this.level, this.worldPosition) ? COMPARATOR_PRESENT : COMPARATOR_ABSENT; + } + return this.hasComparators == COMPARATOR_PRESENT; + } + // Leaves end - Lithium Sleeping Block Entity } diff --git a/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java b/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java index 5d26efb1feb1fd7d0f3bcc0fd7678b303518312a..a2fa0fa4658290ac49c01d5b66cbfbdcdae8f50f 100644 --- a/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java +++ b/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java @@ -27,7 +27,7 @@ import net.minecraft.world.level.storage.ValueInput; import net.minecraft.world.level.storage.ValueOutput; import org.jspecify.annotations.Nullable; -public class BrewingStandBlockEntity extends BaseContainerBlockEntity implements WorldlyContainer { +public class BrewingStandBlockEntity extends BaseContainerBlockEntity implements WorldlyContainer, org.leavesmc.leaves.lithium.common.block.entity.inventory_change_tracking.InventoryChangeTracker, org.leavesmc.leaves.lithium.common.block.entity.SleepingBlockEntity, org.leavesmc.leaves.lithium.common.block.entity.SetChangedHandlingBlockEntity, org.leavesmc.leaves.lithium.api.inventory.LithiumInventory { // Leaves - Lithium Sleeping Block Entity private static final int INGREDIENT_SLOT = 3; private static final int FUEL_SLOT = 4; private static final int[] SLOTS_FOR_UP = new int[]{3}; @@ -139,6 +139,7 @@ public class BrewingStandBlockEntity extends BaseContainerBlockEntity implements } public static void serverTick(final Level level, final BlockPos pos, final BlockState selfState, final BrewingStandBlockEntity entity) { + if (io.nanachiyo0721.shiroha.config.modules.optimizations.LeavesSleepingBlockEntityConfig.enabled) entity.checkSleep(selfState); // Leaves - Lithium Sleeping Block Entity ItemStack fuel = entity.items.get(4); if (entity.fuel <= 0 && fuel.is(ItemTags.BREWING_FUEL)) { // CraftBukkit start @@ -156,6 +157,7 @@ public class BrewingStandBlockEntity extends BaseContainerBlockEntity implements fuel.shrink(1); } // CraftBukkit end + if (io.nanachiyo0721.shiroha.config.modules.optimizations.LeavesSleepingBlockEntityConfig.enabled) entity.wakeUpNow(); // Leaves - Lithium Sleeping Block Entity setChanged(level, pos, selfState); } @@ -170,7 +172,7 @@ public class BrewingStandBlockEntity extends BaseContainerBlockEntity implements } else if (!brewable || !ingredient.is(entity.ingredient)) { entity.brewTime = 0; } - + if (io.nanachiyo0721.shiroha.config.modules.optimizations.LeavesSleepingBlockEntityConfig.enabled) entity.wakeUpNow(); // Leaves - Lithium Sleeping Block Entity setChanged(level, pos, selfState); } else if (brewable && entity.fuel > 0) { entity.fuel--; @@ -183,6 +185,7 @@ public class BrewingStandBlockEntity extends BaseContainerBlockEntity implements entity.brewTime = event.getBrewingTime(); // 400 -> event.getTotalBrewTime() // Paper - use brewing time from event // CraftBukkit end entity.ingredient = ingredient.getItem(); + if (io.nanachiyo0721.shiroha.config.modules.optimizations.LeavesSleepingBlockEntityConfig.enabled) entity.wakeUpNow(); // Leaves - Lithium Sleeping Block Entity setChanged(level, pos, selfState); } @@ -291,6 +294,7 @@ public class BrewingStandBlockEntity extends BaseContainerBlockEntity implements } this.fuel = input.getByteOr("Fuel", (byte)0); + if (io.nanachiyo0721.shiroha.config.modules.optimizations.LeavesSleepingBlockEntityConfig.enabled && this.isSleeping() && this.level != null) this.wakeUpNow(); // Leaves - Lithium Sleeping Block Entity } @Override @@ -337,4 +341,53 @@ public class BrewingStandBlockEntity extends BaseContainerBlockEntity implements protected AbstractContainerMenu createMenu(final int containerId, final Inventory inventory) { return new BrewingStandMenu(containerId, inventory, this, this.dataAccess); } + + // Leaves start - Lithium Sleeping Block Entity + private net.minecraft.world.level.chunk.LevelChunk.RebindableTickingBlockEntityWrapper tickWrapper = null; + private TickingBlockEntity sleepingTicker = null; + + @Override + public net.minecraft.world.level.chunk.LevelChunk.RebindableTickingBlockEntityWrapper lithium$getTickWrapper() { + return tickWrapper; + } + + @Override + public void lithium$setTickWrapper(net.minecraft.world.level.chunk.LevelChunk.RebindableTickingBlockEntityWrapper tickWrapper) { + this.tickWrapper = tickWrapper; + this.lithium$setSleepingTicker(null); + } + + @Override + public TickingBlockEntity lithium$getSleepingTicker() { + return sleepingTicker; + } + + @Override + public void lithium$setSleepingTicker(TickingBlockEntity sleepingTicker) { + this.sleepingTicker = sleepingTicker; + } + + private void checkSleep(BlockState state) { + if (this.brewTime == 0 && state.is(net.minecraft.world.level.block.Blocks.BREWING_STAND) && this.level != null) { + this.lithium$startSleeping(); + } + } + + @Override + public void lithium$handleSetChanged() { + if (this.isSleeping() && this.level != null) { + this.wakeUpNow(); + } + } + + @Override + public net.minecraft.core.NonNullList getInventoryLithium() { + return items; + } + + @Override + public void setInventoryLithium(net.minecraft.core.NonNullList inventory) { + items = inventory; + } + // Leaves end - Lithium Sleeping Block Entity } diff --git a/net/minecraft/world/level/block/entity/CampfireBlockEntity.java b/net/minecraft/world/level/block/entity/CampfireBlockEntity.java index 64d0d60d9025de2b5eb16b31c6437372d4c20c5e..6d8841ffb610bb0d2c65ba51726cf9a5588128bb 100644 --- a/net/minecraft/world/level/block/entity/CampfireBlockEntity.java +++ b/net/minecraft/world/level/block/entity/CampfireBlockEntity.java @@ -39,7 +39,7 @@ import net.minecraft.world.level.storage.ValueOutput; import org.jspecify.annotations.Nullable; import org.slf4j.Logger; -public class CampfireBlockEntity extends BlockEntity implements Clearable { +public class CampfireBlockEntity extends BlockEntity implements Clearable, org.leavesmc.leaves.lithium.common.block.entity.SleepingBlockEntity { // Leaves - Lithium Sleeping Block Entity private static final Logger LOGGER = LogUtils.getLogger(); private static final int BURN_COOL_SPEED = 2; private static final int NUM_SLOTS = 4; @@ -111,7 +111,7 @@ public class CampfireBlockEntity extends BlockEntity implements Clearable { if (changed) { setChanged(level, pos, state); - } + } else if (io.nanachiyo0721.shiroha.config.modules.optimizations.LeavesSleepingBlockEntityConfig.enabled) entity.lithium$startSleeping(); // Leaves - Lithium Sleeping Block Entity } public static void cooldownTick(final Level level, final BlockPos pos, final BlockState state, final CampfireBlockEntity entity) { @@ -126,7 +126,7 @@ public class CampfireBlockEntity extends BlockEntity implements Clearable { if (changed) { setChanged(level, pos, state); - } + } else if (io.nanachiyo0721.shiroha.config.modules.optimizations.LeavesSleepingBlockEntityConfig.enabled) entity.lithium$startSleeping(); // Leaves - Lithium Sleeping Block Entity } public static void particleTick(final Level level, final BlockPos pos, final BlockState state, final CampfireBlockEntity entity) { @@ -183,6 +183,7 @@ public class CampfireBlockEntity extends BlockEntity implements Clearable { System.arraycopy(cookingState, 0, this.stopCooking, 0, Math.min(this.stopCooking.length, bytes.capacity())); }); // Paper end - Add more Campfire API + if (io.nanachiyo0721.shiroha.config.modules.optimizations.LeavesSleepingBlockEntityConfig.enabled) this.wakeUpNow(); // Leaves - Lithium Sleeping Block Entity } @Override @@ -234,6 +235,7 @@ public class CampfireBlockEntity extends BlockEntity implements Clearable { this.cookingTime[slot] = event.getTotalCookTime(); // recipe.get().value().cookingTime() -> event.getTotalCookTime() // CraftBukkit end this.cookingProgress[slot] = 0; + if (io.nanachiyo0721.shiroha.config.modules.optimizations.LeavesSleepingBlockEntityConfig.enabled) this.wakeUpNow(); // Leaves - Lithium Sleeping Block Entity this.items.set(slot, placeItem.consumeAndReturn(1, sourceEntity)); serverLevel.gameEvent(GameEvent.BLOCK_CHANGE, this.getBlockPos(), GameEvent.Context.of(sourceEntity, this.getBlockState())); this.markUpdated(); @@ -277,4 +279,30 @@ public class CampfireBlockEntity extends BlockEntity implements Clearable { public void removeComponentsFromTag(final ValueOutput output) { output.discard("Items"); } + + // Leaves start - Lithium Sleeping Block Entity + private net.minecraft.world.level.chunk.LevelChunk.RebindableTickingBlockEntityWrapper tickWrapper = null; + private TickingBlockEntity sleepingTicker = null; + + @Override + public net.minecraft.world.level.chunk.LevelChunk.RebindableTickingBlockEntityWrapper lithium$getTickWrapper() { + return tickWrapper; + } + + @Override + public void lithium$setTickWrapper(net.minecraft.world.level.chunk.LevelChunk.RebindableTickingBlockEntityWrapper tickWrapper) { + this.tickWrapper = tickWrapper; + this.lithium$setSleepingTicker(null); + } + + @Override + public TickingBlockEntity lithium$getSleepingTicker() { + return sleepingTicker; + } + + @Override + public void lithium$setSleepingTicker(TickingBlockEntity sleepingTicker) { + this.sleepingTicker = sleepingTicker; + } + // Leaves end - Lithium Sleeping Block Entity } diff --git a/net/minecraft/world/level/block/entity/ChestBlockEntity.java b/net/minecraft/world/level/block/entity/ChestBlockEntity.java index a9273aebb5c8efe78e88158f11e1ee2af0dff3f1..8dca1ddd7740e86367ce16db56b75b649fa80b07 100644 --- a/net/minecraft/world/level/block/entity/ChestBlockEntity.java +++ b/net/minecraft/world/level/block/entity/ChestBlockEntity.java @@ -25,7 +25,7 @@ import net.minecraft.world.level.block.state.properties.ChestType; import net.minecraft.world.level.storage.ValueInput; import net.minecraft.world.level.storage.ValueOutput; -public class ChestBlockEntity extends RandomizableContainerBlockEntity implements LidBlockEntity { +public class ChestBlockEntity extends RandomizableContainerBlockEntity implements LidBlockEntity, org.leavesmc.leaves.lithium.common.block.entity.inventory_change_tracking.InventoryChangeTracker, org.leavesmc.leaves.lithium.common.block.entity.inventory_change_tracking.InventoryChangeEmitter, org.leavesmc.leaves.lithium.common.block.entity.SetBlockStateHandlingBlockEntity, org.leavesmc.leaves.lithium.common.block.entity.SleepingBlockEntity, org.leavesmc.leaves.lithium.api.inventory.LithiumInventory { // Leaves - Lithium Sleeping Block Entity private static final int EVENT_SET_OPEN_COUNT = 1; public static final Component DEFAULT_NAME = Component.translatable("container.chest"); private NonNullList items = NonNullList.withSize(27, ItemStack.EMPTY); @@ -140,6 +140,7 @@ public class ChestBlockEntity extends RandomizableContainerBlockEntity implement public static void lidAnimateTick(final Level level, final BlockPos pos, final BlockState state, final ChestBlockEntity entity) { entity.chestLidController.tickLid(); + if (io.nanachiyo0721.shiroha.config.modules.optimizations.LeavesSleepingBlockEntityConfig.enabled) entity.checkSleep(); // Leaves - Lithium Sleeping Block Entity } public static void playSound(final Level level, final BlockPos worldPosition, final BlockState blockState, final SoundEvent event) { @@ -161,6 +162,7 @@ public class ChestBlockEntity extends RandomizableContainerBlockEntity implement @Override public boolean triggerEvent(final int b0, final int b1) { if (b0 == EVENT_SET_OPEN_COUNT) { + if (io.nanachiyo0721.shiroha.config.modules.optimizations.LeavesSleepingBlockEntityConfig.enabled && this.sleepingTicker != null) this.wakeUpNow(); // Leaves - Lithium Sleeping Block Entity this.chestLidController.shouldBeOpen(b1 > 0); return true; } else { @@ -198,6 +200,7 @@ public class ChestBlockEntity extends RandomizableContainerBlockEntity implement @Override protected void setItems(final NonNullList items) { this.items = items; + if (io.nanachiyo0721.shiroha.config.modules.optimizations.LeavesSleepingBlockEntityConfig.enabled) this.lithium$emitStackListReplaced(); // Leaves - Lithium Sleeping Block Entity } @Override @@ -233,4 +236,52 @@ public class ChestBlockEntity extends RandomizableContainerBlockEntity implement Block block = blockState.getBlock(); level.blockEvent(pos, block, EVENT_SET_OPEN_COUNT, current); } + + // Leaves start - Lithium Sleeping Block Entity + private net.minecraft.world.level.chunk.LevelChunk.RebindableTickingBlockEntityWrapper tickWrapper = null; + private TickingBlockEntity sleepingTicker = null; + + private void checkSleep() { + //If the animation is finished, it will stay unchanged until the next triggerEvent, which may change shouldBeOpen + if (this.getOpenNess(0.0F) == this.getOpenNess(1.0F)) { + this.lithium$startSleeping(); + } + } + + @Override + public net.minecraft.world.level.chunk.LevelChunk.RebindableTickingBlockEntityWrapper lithium$getTickWrapper() { + return this.tickWrapper; + } + + @Override + public void lithium$setTickWrapper(net.minecraft.world.level.chunk.LevelChunk.RebindableTickingBlockEntityWrapper tickWrapper) { + this.tickWrapper = tickWrapper; + } + + @Override + public TickingBlockEntity lithium$getSleepingTicker() { + return this.sleepingTicker; + } + + @Override + public void lithium$setSleepingTicker(TickingBlockEntity sleepingTicker) { + this.sleepingTicker = sleepingTicker; + } + + @Override + public void lithium$handleSetBlockState() { + //Handle switching double / single chest state + this.lithium$emitRemoved(); + } + + @Override + public net.minecraft.core.NonNullList getInventoryLithium() { + return items; + } + + @Override + public void setInventoryLithium(net.minecraft.core.NonNullList inventory) { + items = inventory; + } + // Leaves end - Lithium Sleeping Block Entity } diff --git a/net/minecraft/world/level/block/entity/ChiseledBookShelfBlockEntity.java b/net/minecraft/world/level/block/entity/ChiseledBookShelfBlockEntity.java index 8697f4d589766163a56b0de5be4fad999d09f809..19db4302ca6f1b3f2f86aef34f0d80229fb1ace8 100644 --- a/net/minecraft/world/level/block/entity/ChiseledBookShelfBlockEntity.java +++ b/net/minecraft/world/level/block/entity/ChiseledBookShelfBlockEntity.java @@ -22,7 +22,7 @@ import net.minecraft.world.level.storage.ValueInput; import net.minecraft.world.level.storage.ValueOutput; import org.slf4j.Logger; -public class ChiseledBookShelfBlockEntity extends BlockEntity implements ListBackedContainer { +public class ChiseledBookShelfBlockEntity extends BlockEntity implements ListBackedContainer, org.leavesmc.leaves.lithium.api.inventory.LithiumTransferConditionInventory { // Leaves - Lithium Sleeping Block Entity public static final int MAX_BOOKS_IN_STORAGE = 6; private static final Logger LOGGER = LogUtils.getLogger(); private static final int DEFAULT_LAST_INTERACTED_SLOT = -1; @@ -171,4 +171,6 @@ public class ChiseledBookShelfBlockEntity extends BlockEntity implements ListBac public void removeComponentsFromTag(final ValueOutput output) { output.discard("Items"); } + + @Override public boolean lithium$itemInsertionTestRequiresStackSize1() {return true;} // Leaves - Lithium Sleeping Block Entity } diff --git a/net/minecraft/world/level/block/entity/CrafterBlockEntity.java b/net/minecraft/world/level/block/entity/CrafterBlockEntity.java index 02d86d0675de22546a225dd6eaac5875eba0ce54..ae33a37a67f39a7c35228c7f95e3b78900685e12 100644 --- a/net/minecraft/world/level/block/entity/CrafterBlockEntity.java +++ b/net/minecraft/world/level/block/entity/CrafterBlockEntity.java @@ -23,7 +23,7 @@ import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.storage.ValueInput; import net.minecraft.world.level.storage.ValueOutput; -public class CrafterBlockEntity extends RandomizableContainerBlockEntity implements CraftingContainer { +public class CrafterBlockEntity extends RandomizableContainerBlockEntity implements CraftingContainer, org.leavesmc.leaves.lithium.common.block.entity.SleepingBlockEntity, org.leavesmc.leaves.lithium.common.block.entity.SetChangedHandlingBlockEntity { // Leaves - Lithium Sleeping Block Entity public static final int CONTAINER_WIDTH = 3; public static final int CONTAINER_HEIGHT = 3; public static final int CONTAINER_SIZE = 9; @@ -171,6 +171,7 @@ public class CrafterBlockEntity extends RandomizableContainerBlockEntity impleme } }); this.containerData.set(9, input.getIntOr("triggered", 0)); + if (io.nanachiyo0721.shiroha.config.modules.optimizations.LeavesSleepingBlockEntityConfig.enabled && this.isSleeping() && this.level != null) this.wakeUpNow(); // Leaves - Lithium Sleeping Block Entity } @Override @@ -280,10 +281,12 @@ public class CrafterBlockEntity extends RandomizableContainerBlockEntity impleme level.setBlock(blockPos, blockState.setValue(CrafterBlock.CRAFTING, false), Block.UPDATE_ALL); } } + if (io.nanachiyo0721.shiroha.config.modules.optimizations.LeavesSleepingBlockEntityConfig.enabled && craftingTicksRemaining < 0) entity.checkSleep(); // Leaves - Lithium Sleeping Block Entity } public void setCraftingTicksRemaining(final int maxCraftingTicks) { this.craftingTicksRemaining = maxCraftingTicks; + if (io.nanachiyo0721.shiroha.config.modules.optimizations.LeavesSleepingBlockEntityConfig.enabled && this.isSleeping() && this.level != null) this.wakeUpNow(); // Leaves - Lithium Sleeping Block Entity } public int getRedstoneSignal() { @@ -302,4 +305,43 @@ public class CrafterBlockEntity extends RandomizableContainerBlockEntity impleme private boolean slotCanBeDisabled(final int slotId) { return slotId > -1 && slotId < 9 && this.items.get(slotId).isEmpty(); } + + // Leaves start - Lithium Sleeping Block Entity + private net.minecraft.world.level.chunk.LevelChunk.RebindableTickingBlockEntityWrapper tickWrapper = null; + private TickingBlockEntity sleepingTicker = null; + + @Override + public net.minecraft.world.level.chunk.LevelChunk.RebindableTickingBlockEntityWrapper lithium$getTickWrapper() { + return this.tickWrapper; + } + + @Override + public void lithium$setTickWrapper(net.minecraft.world.level.chunk.LevelChunk.RebindableTickingBlockEntityWrapper tickWrapper) { + this.tickWrapper = tickWrapper; + this.lithium$setSleepingTicker(null); + } + + @Override + public TickingBlockEntity lithium$getSleepingTicker() { + return this.sleepingTicker; + } + + @Override + public void lithium$setSleepingTicker(TickingBlockEntity sleepingTicker) { + this.sleepingTicker = sleepingTicker; + } + + private void checkSleep() { + if (this.craftingTicksRemaining == 0) { + this.lithium$startSleeping(); + } + } + + @Override + public void lithium$handleSetChanged() { + if (this.isSleeping() && this.level != null && !this.level.isClientSide()) { + this.wakeUpNow(); + } + } + // Leaves end - Lithium Sleeping Block Entity } diff --git a/net/minecraft/world/level/block/entity/DispenserBlockEntity.java b/net/minecraft/world/level/block/entity/DispenserBlockEntity.java index 736f5fd76f4a86ab16740904b5c0823413d65cd2..b1a063c3e75cfcd99e936f1e336ce5464a8cc3b7 100644 --- a/net/minecraft/world/level/block/entity/DispenserBlockEntity.java +++ b/net/minecraft/world/level/block/entity/DispenserBlockEntity.java @@ -13,7 +13,7 @@ import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.storage.ValueInput; import net.minecraft.world.level.storage.ValueOutput; -public class DispenserBlockEntity extends RandomizableContainerBlockEntity { +public class DispenserBlockEntity extends RandomizableContainerBlockEntity implements org.leavesmc.leaves.lithium.common.block.entity.inventory_change_tracking.InventoryChangeTracker, org.leavesmc.leaves.lithium.api.inventory.LithiumInventory { // Leaves - Lithium Sleeping Block Entity public static final int CONTAINER_SIZE = 9; private static final Component DEFAULT_NAME = Component.translatable("container.dispenser"); private NonNullList items = NonNullList.withSize(9, ItemStack.EMPTY); @@ -135,10 +135,23 @@ public class DispenserBlockEntity extends RandomizableContainerBlockEntity { @Override protected void setItems(final NonNullList items) { this.items = items; + if (io.nanachiyo0721.shiroha.config.modules.optimizations.LeavesSleepingBlockEntityConfig.enabled) this.lithium$emitStackListReplaced(); // Leaves - Lithium Sleeping Block Entity } @Override protected AbstractContainerMenu createMenu(final int containerId, final Inventory inventory) { return new DispenserMenu(containerId, inventory, this); } + + // Leaves start - Lithium Sleeping Block Entity + @Override + public net.minecraft.core.NonNullList getInventoryLithium() { + return items; + } + + @Override + public void setInventoryLithium(net.minecraft.core.NonNullList inventory) { + items = inventory; + } + // Leaves end - Lithium Sleeping Block Entity } diff --git a/net/minecraft/world/level/block/entity/EnderChestBlockEntity.java b/net/minecraft/world/level/block/entity/EnderChestBlockEntity.java index 6109d8da984b398bb1eb6cd6ab9a55f0511a615c..d3d6b3aeb24da7f3bf92c327095a9dc9494c92da 100644 --- a/net/minecraft/world/level/block/entity/EnderChestBlockEntity.java +++ b/net/minecraft/world/level/block/entity/EnderChestBlockEntity.java @@ -11,7 +11,7 @@ import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.ChestBlock; import net.minecraft.world.level.block.state.BlockState; -public class EnderChestBlockEntity extends BlockEntity implements LidBlockEntity { +public class EnderChestBlockEntity extends BlockEntity implements LidBlockEntity, org.leavesmc.leaves.lithium.common.block.entity.SleepingBlockEntity { // Leaves - Lithium Sleeping Block Entity private final ChestLidController chestLidController = new ChestLidController(); public final ContainerOpenersCounter openersCounter = new ContainerOpenersCounter() { // Paper start - delay open/close callbacks @@ -66,11 +66,13 @@ public class EnderChestBlockEntity extends BlockEntity implements LidBlockEntity public static void lidAnimateTick(final Level level, final BlockPos pos, final BlockState state, final EnderChestBlockEntity entity) { entity.chestLidController.tickLid(); + if (io.nanachiyo0721.shiroha.config.modules.optimizations.LeavesSleepingBlockEntityConfig.enabled) entity.checkSleep(); // Leaves - Lithium Sleeping Block Entity } @Override public boolean triggerEvent(final int b0, final int b1) { if (b0 == ChestBlock.EVENT_SET_OPEN_COUNT) { + if (io.nanachiyo0721.shiroha.config.modules.optimizations.LeavesSleepingBlockEntityConfig.enabled && this.sleepingTicker != null) this.wakeUpNow(); // Leaves - Lithium Sleeping Block Entity this.chestLidController.shouldBeOpen(b1 > 0); return true; } else { @@ -107,4 +109,36 @@ public class EnderChestBlockEntity extends BlockEntity implements LidBlockEntity public float getOpenNess(final float a) { return this.chestLidController.getOpenness(a); } + + // Leaves start - Lithium Sleeping Block Entity + private net.minecraft.world.level.chunk.LevelChunk.RebindableTickingBlockEntityWrapper tickWrapper = null; + private TickingBlockEntity sleepingTicker = null; + + @Override + public net.minecraft.world.level.chunk.LevelChunk.RebindableTickingBlockEntityWrapper lithium$getTickWrapper() { + return this.tickWrapper; + } + + @Override + public void lithium$setTickWrapper(net.minecraft.world.level.chunk.LevelChunk.RebindableTickingBlockEntityWrapper tickWrapper) { + this.tickWrapper = tickWrapper; + } + + @Override + public TickingBlockEntity lithium$getSleepingTicker() { + return this.sleepingTicker; + } + + @Override + public void lithium$setSleepingTicker(TickingBlockEntity sleepingTicker) { + this.sleepingTicker = sleepingTicker; + } + + private void checkSleep() { + //If the animation is finished, it will stay unchanged until the next triggerEvent, which may change shouldBeOpen + if (this.getOpenNess(0.0F) == this.getOpenNess(1.0F)) { + this.lithium$startSleeping(); + } + } + // Leaves end - Lithium Sleeping Block Entity } diff --git a/net/minecraft/world/level/block/entity/HopperBlockEntity.java b/net/minecraft/world/level/block/entity/HopperBlockEntity.java index 95f0b3276137cb9d1fc296e97cc25a08a5200c95..26afe1f74b50b60d6c04e08967610a3d50183483 100644 --- a/net/minecraft/world/level/block/entity/HopperBlockEntity.java +++ b/net/minecraft/world/level/block/entity/HopperBlockEntity.java @@ -27,8 +27,29 @@ import net.minecraft.world.level.storage.ValueInput; import net.minecraft.world.level.storage.ValueOutput; import net.minecraft.world.phys.AABB; import org.jspecify.annotations.Nullable; - -public class HopperBlockEntity extends RandomizableContainerBlockEntity implements Hopper { +// Leaves start - Lithium Sleeping Block Entity +import java.util.Objects; +import net.minecraft.world.level.chunk.LevelChunk; +import net.minecraft.world.CompoundContainer; +import net.minecraft.server.level.ServerLevel; +import org.leavesmc.leaves.lithium.api.inventory.LithiumInventory; +import org.leavesmc.leaves.lithium.common.block.entity.SleepingBlockEntity; +import org.leavesmc.leaves.lithium.common.block.entity.inventory_change_tracking.InventoryChangeListener; +import org.leavesmc.leaves.lithium.common.block.entity.inventory_change_tracking.InventoryChangeTracker; +import org.leavesmc.leaves.lithium.common.block.entity.inventory_comparator_tracking.ComparatorTracker; +import org.leavesmc.leaves.lithium.common.hopper.BlockStateOnlyInventory; +import org.leavesmc.leaves.lithium.common.hopper.HopperCachingState; +import org.leavesmc.leaves.lithium.common.hopper.HopperHelper; +import org.leavesmc.leaves.lithium.common.hopper.InventoryHelper; +import org.leavesmc.leaves.lithium.common.hopper.LithiumStackList; +import org.leavesmc.leaves.lithium.common.hopper.UpdateReceiver; +import org.leavesmc.leaves.lithium.common.tracking.entity.ChunkSectionEntityMovementListener; +import org.leavesmc.leaves.lithium.common.tracking.entity.ChunkSectionEntityMovementTracker; +import org.leavesmc.leaves.lithium.common.tracking.entity.ChunkSectionInventoryEntityTracker; +import org.leavesmc.leaves.lithium.common.tracking.entity.ChunkSectionItemEntityMovementTracker; +// Leaves end - Lithium Sleeping Block Entity + +public class HopperBlockEntity extends RandomizableContainerBlockEntity implements Hopper, SleepingBlockEntity, ChunkSectionEntityMovementListener, LithiumInventory, InventoryChangeListener, UpdateReceiver, InventoryChangeTracker { // Leaves - Lithium Sleeping Block Entity public static final int MOVE_ITEM_SPEED = 8; public static final int HOPPER_CONTAINER_SIZE = 5; private static final int[][] CACHED_SLOTS = new int[54][]; @@ -77,9 +98,19 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen @Override public void updateTicks(final long fromTickOffset, final long fromRedstoneTimeOffset) { super.updateTicks(fromTickOffset, fromRedstoneTimeOffset); - if (this.tickedGameTime != Long.MIN_VALUE) { + if (this.tickedGameTime != Long.MIN_VALUE && this.tickedGameTime != Long.MAX_VALUE) { // Shiroha - Region threading for lithium sleeping block entity this.tickedGameTime += fromRedstoneTimeOffset; } + // Shiroha start - Region threading for lithium sleeping block entity + if (this.insertInventoryEntityFailedSearchTime != Long.MIN_VALUE) + this.insertInventoryEntityFailedSearchTime += fromRedstoneTimeOffset; + + if (this.extractInventoryEntityFailedSearchTime != Long.MIN_VALUE) + this.extractInventoryEntityFailedSearchTime += fromRedstoneTimeOffset; + + if (this.collectItemEntityAttemptTime != Long.MIN_VALUE) + this.collectItemEntityAttemptTime += fromRedstoneTimeOffset; + // Shiroha end } // Folia end - region threading @@ -129,6 +160,7 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen @Override public void setBlockState(final BlockState blockState) { + if (io.nanachiyo0721.shiroha.config.modules.optimizations.LeavesSleepingBlockEntityConfig.enabled && this.level != null && !this.level.isClientSide() && blockState.getValue(HopperBlock.FACING) != this.getBlockState().getValue(HopperBlock.FACING)) this.invalidateCachedData(); // Leaves - Lithium Sleeping Block Entity super.setBlockState(blockState); this.facing = blockState.getValue(HopperBlock.FACING); } @@ -145,6 +177,7 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen entity.setCooldown(0); // Spigot start boolean result = tryMoveItems(level, pos, state, entity, () -> suckInItems(level, entity)); + if (io.nanachiyo0721.shiroha.config.modules.optimizations.LeavesSleepingBlockEntityConfig.enabled) entity.checkSleepingConditions(); // Leaves - Lithium Sleeping Block Entity if (!result && entity.level.spigotConfig.hopperCheck > 1) { entity.setCooldown(entity.level.spigotConfig.hopperCheck); } @@ -210,6 +243,14 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen if (changed) { entity.setCooldown(level.spigotConfig.hopperTransfer); // Spigot setChanged(level, pos, state); + // Leaves start - Lithium Sleeping Block Entity + if (io.nanachiyo0721.shiroha.config.modules.optimizations.LeavesSleepingBlockEntityConfig.enabled + && !entity.isOnCooldown() + && !entity.isSleeping() + && !state.getValue(HopperBlock.ENABLED)) { + entity.lithium$startSleeping(); + } + // Leaves end - Lithium Sleeping Block Entity return true; } } @@ -418,6 +459,7 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen private static void applyCooldown(final Hopper hopper) { if (hopper instanceof HopperBlockEntity blockEntity && blockEntity.getLevel() != null) { blockEntity.setCooldown(blockEntity.getLevel().spigotConfig.hopperTransfer); + blockEntity.skipNextSleepCheckAfterCooldown = true; // Leaves - Lithium Sleeping Block Entity } } @@ -461,12 +503,20 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen // Paper end - Perf: Optimize Hoppers private static boolean ejectItems(final Level level, final BlockPos blockPos, final HopperBlockEntity self) { - Container container = getAttachedContainer(level, blockPos, self); + Container container = io.nanachiyo0721.shiroha.config.modules.optimizations.LeavesSleepingBlockEntityConfig.enabled ? self.getInsertInventory(level) : getAttachedContainer(level, blockPos, self); // Leaves - Lithium Sleeping Block Entity if (container == null) { return false; } Direction direction = self.facing.getOpposite(); + // Leaves start - Lithium Sleeping Block Entity + if (io.nanachiyo0721.shiroha.config.modules.optimizations.LeavesSleepingBlockEntityConfig.enabled) { + Boolean res = lithiumInsert(level, blockPos, self, container); + if (res != null) { + return res; + } + } + // Leaves end - Lithium Sleeping Block Entity if (isFullContainer(container, direction)) { return false; } @@ -522,13 +572,21 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen io.papermc.paper.threadedregions.RegionizedWorldData worldData = io.papermc.paper.threadedregions.TickRegionScheduler.getCurrentRegionizedWorldData(); // Folia - region threading BlockPos blockPos = BlockPos.containing(hopper.getLevelX(), hopper.getLevelY() + 1.0, hopper.getLevelZ()); BlockState blockState = level.getBlockState(blockPos); - Container container = getSourceContainer(level, hopper, blockPos, blockState); - if (container != null) { + Container sourceContainer = io.nanachiyo0721.shiroha.config.modules.optimizations.LeavesSleepingBlockEntityConfig.enabled ? getExtractInventory(level, hopper, blockPos, blockState) : getSourceContainer(level, hopper, blockPos, blockState); // Leaves - Lithium Sleeping Block Entity + if (sourceContainer != null) { Direction direction = Direction.DOWN; worldData.skipPullModeEventFire = worldData.skipHopperEvents; // Paper - Perf: Optimize Hoppers // Folia - region threading + // Leaves start - Lithium Sleeping Block Entity + if (io.nanachiyo0721.shiroha.config.modules.optimizations.LeavesSleepingBlockEntityConfig.enabled) { + Boolean res = lithiumExtract(level, hopper, sourceContainer); + if (res != null) { + return res; + } + } + // Leaves end - Lithium Sleeping Block Entity - for (int slot : getSlots(container, direction)) { - if (tryTakeInItemFromSlot(hopper, container, slot, direction, level)) { // Spigot + for (int slot : getSlots(sourceContainer, direction)) { + if (tryTakeInItemFromSlot(hopper, sourceContainer, slot, direction, level)) { // Spigot return true; } } @@ -539,7 +597,7 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen && blockState.isCollisionShapeFullBlock(level, blockPos) && !blockState.is(BlockTags.DOES_NOT_BLOCK_HOPPERS); if (!isBlocked) { - for (ItemEntity entity : getItemsAtAndAbove(level, hopper)) { + for (ItemEntity entity : io.nanachiyo0721.shiroha.config.modules.optimizations.LeavesSleepingBlockEntityConfig.enabled ? lithiumGetInputItemEntities(level, hopper) : getItemsAtAndAbove(level, hopper)) { // Leaves - Lithium Sleeping Block Entity if (addItem(hopper, entity)) { return true; } @@ -661,7 +719,7 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen // CraftBukkit start private static @Nullable Container runHopperInventorySearchEvent( - Container container, + @Nullable Container container, // Leaves - Lithium Sleeping Block Entity org.bukkit.craftbukkit.block.CraftBlock hopper, org.bukkit.craftbukkit.block.CraftBlock searchLocation, org.bukkit.event.inventory.HopperInventorySearchEvent.ContainerType containerType @@ -787,6 +845,19 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen } public void setCooldown(final int time) { + // Leaves start - Lithium Sleeping Block Entity + if (io.nanachiyo0721.shiroha.config.modules.optimizations.LeavesSleepingBlockEntityConfig.enabled) { + if (time == 7) { + if (this.tickedGameTime == Long.MAX_VALUE) { + this.sleepOnlyCurrentTick(); + } else { + this.wakeUpNow(); + } + } else if (time > 0 && this.sleepingTicker != null) { + this.wakeUpNow(); + } + } + // Leaves end - Lithium Sleeping Block Entity this.cooldownTime = time; } @@ -806,6 +877,7 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen @Override protected void setItems(final NonNullList items) { this.items = items; + if (io.nanachiyo0721.shiroha.config.modules.optimizations.LeavesSleepingBlockEntityConfig.enabled) this.lithium$emitStackListReplaced(); // Leaves - Lithium Sleeping Block Entity } public static void entityInside(final Level level, final BlockPos pos, final BlockState blockState, final Entity entity, final HopperBlockEntity hopper) { @@ -820,4 +892,758 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen protected AbstractContainerMenu createMenu(final int containerId, final Inventory inventory) { return new HopperMenu(containerId, inventory, this); } + + // Leaves start - Lithium Sleeping Block Entity + private LevelChunk.@org.jspecify.annotations.Nullable RebindableTickingBlockEntityWrapper tickWrapper = null; // Leaves - Paper 26.1 fix + @Nullable private TickingBlockEntity sleepingTicker = null; + private long myModCountAtLastInsert, myModCountAtLastExtract, myModCountAtLastItemCollect; + private boolean skipNextSleepCheckAfterCooldown = false; + + private HopperCachingState.BlockInventory insertionMode = HopperCachingState.BlockInventory.UNKNOWN; + private HopperCachingState.BlockInventory extractionMode = HopperCachingState.BlockInventory.UNKNOWN; + + //The currently used block inventories + @Nullable + private Container insertBlockInventory, extractBlockInventory; + + //The currently used inventories (optimized type, if not present, skip optimizations) + @Nullable + private LithiumInventory insertInventory, extractInventory; + @Nullable //Null iff corresp. LithiumInventory field is null + private LithiumStackList insertStackList, extractStackList; + //Mod count used to avoid transfer attempts that are known to fail (no change since last attempt) + private long insertStackListModCount, extractStackListModCount; + + @Nullable + private List collectItemEntityTracker; + private boolean collectItemEntityTrackerWasEmpty; + @Nullable + private AABB collectItemEntityBox; + private long collectItemEntityAttemptTime; + + @Nullable + private List extractInventoryEntityTracker; + @Nullable + private AABB extractInventoryEntityBox; + private long extractInventoryEntityFailedSearchTime; + + @Nullable + private List insertInventoryEntityTracker; + @Nullable + private AABB insertInventoryEntityBox; + private long insertInventoryEntityFailedSearchTime; + + private boolean shouldCheckSleep; + + private void checkSleepingConditions() { + if (this.cooldownTime > 0 || this.getLevel() == null || skipNextSleepCheckAfterCooldown) { + return; + } + if (isSleeping()) { + return; + } + if (!this.shouldCheckSleep) { + this.shouldCheckSleep = true; + return; + } + boolean listenToExtractTracker = false; + boolean listenToInsertTracker = false; + boolean listenToExtractEntities = false; + boolean listenToItemEntities = false; + boolean listenToInsertEntities = false; + + LithiumStackList thisStackList = InventoryHelper.getLithiumStackList(this); + + if (this.extractionMode != HopperCachingState.BlockInventory.BLOCK_STATE && thisStackList.getFullSlots() != thisStackList.size()) { + if (this.extractionMode == HopperCachingState.BlockInventory.REMOVAL_TRACKING_BLOCK_ENTITY) { + Container blockInventory = this.extractBlockInventory; + if (this.extractStackList != null && + blockInventory instanceof InventoryChangeTracker) { + if (!this.extractStackList.maybeSendsComparatorUpdatesOnFailedExtract() || (blockInventory instanceof ComparatorTracker comparatorTracker && !comparatorTracker.lithium$hasAnyComparatorNearby())) { + listenToExtractTracker = true; + } else { + return; + } + } else { + return; + } + } else if (this.extractionMode == HopperCachingState.BlockInventory.NO_BLOCK_INVENTORY) { + BlockState hopperState = this.getBlockState(); + listenToExtractEntities = true; + + BlockPos blockPos = this.getBlockPos().above(); + BlockState blockState = this.getLevel().getBlockState(blockPos); + if (!blockState.isCollisionShapeFullBlock(this.getLevel(), blockPos) || blockState.is(BlockTags.DOES_NOT_BLOCK_HOPPERS)) { + listenToItemEntities = true; + } + } else { + return; + } + } + if (this.insertionMode != HopperCachingState.BlockInventory.BLOCK_STATE && 0 < thisStackList.getOccupiedSlots()) { + if (this.insertionMode == HopperCachingState.BlockInventory.REMOVAL_TRACKING_BLOCK_ENTITY) { + Container blockInventory = this.insertBlockInventory; + if (this.insertStackList != null && blockInventory instanceof InventoryChangeTracker) { + listenToInsertTracker = true; + } else { + return; + } + } else if (this.insertionMode == HopperCachingState.BlockInventory.NO_BLOCK_INVENTORY) { + BlockState hopperState = this.getBlockState(); + listenToInsertEntities = true; + } else { + return; + } + } + + if (listenToExtractTracker) { + ((InventoryChangeTracker) this.extractBlockInventory).listenForContentChangesOnce(this.extractStackList, this); + } + if (listenToInsertTracker) { + ((InventoryChangeTracker) this.insertBlockInventory).listenForContentChangesOnce(this.insertStackList, this); + } + if (listenToInsertEntities) { + if (this.insertInventoryEntityTracker == null || this.insertInventoryEntityTracker.isEmpty()) { + return; + } + ChunkSectionEntityMovementTracker.listenToEntityMovementOnce(this, insertInventoryEntityTracker); + } + if (listenToExtractEntities) { + if (this.extractInventoryEntityTracker == null || this.extractInventoryEntityTracker.isEmpty()) { + return; + } + ChunkSectionEntityMovementTracker.listenToEntityMovementOnce(this, extractInventoryEntityTracker); + } + if (listenToItemEntities) { + if (this.collectItemEntityTracker == null || this.collectItemEntityTracker.isEmpty()) { + return; + } + ChunkSectionEntityMovementTracker.listenToEntityMovementOnce(this, collectItemEntityTracker); + } + + this.listenForContentChangesOnce(thisStackList, this); + lithium$startSleeping(); + } + + @Override + public void lithium$setSleepingTicker(@Nullable TickingBlockEntity sleepingTicker) { + this.sleepingTicker = sleepingTicker; + } + + @Override + public @Nullable TickingBlockEntity lithium$getSleepingTicker() { + return sleepingTicker; + } + + @Override + public void lithium$setTickWrapper(LevelChunk.RebindableTickingBlockEntityWrapper tickWrapper) { + this.tickWrapper = tickWrapper; + this.lithium$setSleepingTicker(null); + } + + @Override + public LevelChunk.@org.jspecify.annotations.Nullable RebindableTickingBlockEntityWrapper lithium$getTickWrapper() { // Leaves - Paper 26.1 fix + return tickWrapper; + } + + @Override + public boolean lithium$startSleeping() { + if (this.isSleeping()) { + return false; + } + + LevelChunk.RebindableTickingBlockEntityWrapper tickWrapper = this.lithium$getTickWrapper(); + if (tickWrapper != null) { + this.lithium$setSleepingTicker(tickWrapper.ticker); + tickWrapper.rebind(SleepingBlockEntity.SLEEPING_BLOCK_ENTITY_TICKER); + + // Set the last tick time to max value, so other hoppers transferring into this hopper will set it to 7gt + // cooldown. Then when waking up, we make sure to not tick this hopper in the same gametick. + // This makes the observable hopper cooldown not be different from vanilla. + this.tickedGameTime = Long.MAX_VALUE; + return true; + } + return false; + } + + @Override + public void handleEntityMovement() { + this.wakeUpNow(); + } + + @Override + public NonNullList getInventoryLithium() { + return items; + } + + @Override + public void setInventoryLithium(NonNullList inventory) { + this.items = inventory; + } + + @Override + public void lithium$handleInventoryContentModified(Container inventory) { + wakeUpNow(); + } + + @Override + public void lithium$handleInventoryRemoved(Container inventory) { + wakeUpNow(); + if (inventory == this.insertBlockInventory) { + this.invalidateBlockInsertionData(); + } + if (inventory == this.extractBlockInventory) { + this.invalidateBlockExtractionData(); + } + if (inventory == this) { + this.invalidateCachedData(); + } + } + + @Override + public boolean lithium$handleComparatorAdded(Container inventory) { + if (inventory == this.extractBlockInventory) { + wakeUpNow(); + return true; + } + return false; + } + + @Override + public void lithium$invalidateCacheOnNeighborUpdate(boolean fromAbove) { + //Clear the block inventory cache (composter inventories and no inventory present) on block update / observer update + if (fromAbove) { + if (this.extractionMode == HopperCachingState.BlockInventory.NO_BLOCK_INVENTORY || this.extractionMode == HopperCachingState.BlockInventory.BLOCK_STATE) { + this.invalidateBlockExtractionData(); + } + } else { + if (this.insertionMode == HopperCachingState.BlockInventory.NO_BLOCK_INVENTORY || this.insertionMode == HopperCachingState.BlockInventory.BLOCK_STATE) { + this.invalidateBlockInsertionData(); + } + } + } + + @Override + public void lithium$invalidateCacheOnUndirectedNeighborUpdate() { + if (this.extractionMode == HopperCachingState.BlockInventory.NO_BLOCK_INVENTORY || this.extractionMode == HopperCachingState.BlockInventory.BLOCK_STATE) { + this.invalidateBlockExtractionData(); + } + if (this.insertionMode == HopperCachingState.BlockInventory.NO_BLOCK_INVENTORY || this.insertionMode == HopperCachingState.BlockInventory.BLOCK_STATE) { + this.invalidateBlockInsertionData(); + } + } + + @Override + public void lithium$invalidateCacheOnNeighborUpdate(Direction fromDirection) { + boolean fromAbove = fromDirection == Direction.UP; + if (fromAbove || this.getBlockState().getValue(HopperBlock.FACING) == fromDirection) { + this.lithium$invalidateCacheOnNeighborUpdate(fromAbove); + } + } + + private void invalidateBlockInsertionData() { + this.insertionMode = HopperCachingState.BlockInventory.UNKNOWN; + this.insertBlockInventory = null; + this.insertInventory = null; + this.insertStackList = null; + this.insertStackListModCount = 0; + + wakeUpNow(); + } + + private void invalidateCachedData() { + this.shouldCheckSleep = false; + this.invalidateInsertionData(); + this.invalidateExtractionData(); + } + + private void invalidateInsertionData() { + if (this.level instanceof ServerLevel) { + if (this.insertInventoryEntityTracker != null) { + ChunkSectionEntityMovementTracker.unregister(this.insertInventoryEntityTracker); + this.insertInventoryEntityTracker = null; + this.insertInventoryEntityBox = null; + this.insertInventoryEntityFailedSearchTime = 0L; + } + } + + if (this.insertionMode == HopperCachingState.BlockInventory.REMOVAL_TRACKING_BLOCK_ENTITY) { + assert this.insertBlockInventory != null; + ((InventoryChangeTracker) this.insertBlockInventory).stopListenForMajorInventoryChanges(this); + } + this.invalidateBlockInsertionData(); + } + + private void invalidateExtractionData() { + if (this.level instanceof ServerLevel) { + if (this.extractInventoryEntityTracker != null) { + ChunkSectionEntityMovementTracker.unregister(this.extractInventoryEntityTracker); + this.extractInventoryEntityTracker = null; + this.extractInventoryEntityBox = null; + this.extractInventoryEntityFailedSearchTime = 0L; + } + if (this.collectItemEntityTracker != null) { + ChunkSectionEntityMovementTracker.unregister(this.collectItemEntityTracker); + this.collectItemEntityTracker = null; + this.collectItemEntityBox = null; + this.collectItemEntityTrackerWasEmpty = false; + } + } + if (this.extractionMode == HopperCachingState.BlockInventory.REMOVAL_TRACKING_BLOCK_ENTITY) { + assert this.extractBlockInventory != null; + ((InventoryChangeTracker) this.extractBlockInventory).stopListenForMajorInventoryChanges(this); + } + this.invalidateBlockExtractionData(); + } + + private void invalidateBlockExtractionData() { + this.extractionMode = HopperCachingState.BlockInventory.UNKNOWN; + this.extractBlockInventory = null; + this.extractInventory = null; + this.extractStackList = null; + this.extractStackListModCount = 0; + + this.wakeUpNow(); + } + + private static @Nullable Container getExtractInventory(Level world, Hopper hopper, BlockPos extractBlockPos, BlockState extractBlockState) { + if (!(hopper instanceof HopperBlockEntity hopperBlockEntity)) { + return getSourceContainer(world, hopper, extractBlockPos, extractBlockState); //Hopper Minecarts do not cache Inventories + } + + Container blockInventory = hopperBlockEntity.lithium$getExtractBlockInventory(world, extractBlockPos, extractBlockState); + if (blockInventory == null) { + blockInventory = hopperBlockEntity.lithium$getExtractEntityInventory(world); + } + return org.bukkit.event.inventory.HopperInventorySearchEvent.getHandlerList().getRegisteredListeners().length == 0 ? blockInventory : runHopperInventorySearchEvent( + blockInventory, + org.bukkit.craftbukkit.block.CraftBlock.at(world, hopperBlockEntity.getBlockPos()), + org.bukkit.craftbukkit.block.CraftBlock.at(world, extractBlockPos), + org.bukkit.event.inventory.HopperInventorySearchEvent.ContainerType.SOURCE + ); + } + + public @Nullable Container lithium$getExtractBlockInventory(Level world, BlockPos extractBlockPos, BlockState extractBlockState) { + Container blockInventory = this.extractBlockInventory; + if (this.extractionMode == HopperCachingState.BlockInventory.NO_BLOCK_INVENTORY) { + return null; + } else if (this.extractionMode == HopperCachingState.BlockInventory.BLOCK_STATE) { + return blockInventory; + } else if (this.extractionMode == HopperCachingState.BlockInventory.REMOVAL_TRACKING_BLOCK_ENTITY) { + return blockInventory; + } else if (this.extractionMode == HopperCachingState.BlockInventory.BLOCK_ENTITY) { + BlockEntity blockEntity = (BlockEntity) Objects.requireNonNull(blockInventory); + //Movable Block Entity compatibility - position comparison + BlockPos pos = blockEntity.getBlockPos(); + if (!(blockEntity).isRemoved() && pos.equals(extractBlockPos)) { + LithiumInventory optimizedInventory; + if ((optimizedInventory = this.extractInventory) != null) { + LithiumStackList insertInventoryStackList = InventoryHelper.getLithiumStackList(optimizedInventory); + //This check is necessary as sometimes the stacklist is silently replaced (e.g. command making furnace read inventory from nbt) + if (insertInventoryStackList == this.extractStackList) { + return optimizedInventory; + } else { + this.invalidateBlockExtractionData(); + } + } else { + return blockInventory; + } + } + } + + //No Cached Inventory: Get like vanilla and cache + blockInventory = getBlockContainer(world, extractBlockPos, extractBlockState); + blockInventory = HopperHelper.replaceDoubleInventory(blockInventory); + this.cacheExtractBlockInventory(blockInventory); + return blockInventory; + } + + public @Nullable Container lithium$getInsertBlockInventory(Level world) { + Container blockInventory = this.insertBlockInventory; + if (this.insertionMode == HopperCachingState.BlockInventory.NO_BLOCK_INVENTORY) { + return null; + } else if (this.insertionMode == HopperCachingState.BlockInventory.BLOCK_STATE) { + return blockInventory; + } else if (this.insertionMode == HopperCachingState.BlockInventory.REMOVAL_TRACKING_BLOCK_ENTITY) { + return blockInventory; + } else if (this.insertionMode == HopperCachingState.BlockInventory.BLOCK_ENTITY) { + BlockEntity blockEntity = (BlockEntity) Objects.requireNonNull(blockInventory); + //Movable Block Entity compatibility - position comparison + BlockPos pos = blockEntity.getBlockPos(); + Direction direction = this.facing; + BlockPos transferPos = this.getBlockPos().relative(direction); + if (!(blockEntity).isRemoved() && + pos.equals(transferPos)) { + LithiumInventory optimizedInventory; + if ((optimizedInventory = this.insertInventory) != null) { + LithiumStackList insertInventoryStackList = InventoryHelper.getLithiumStackList(optimizedInventory); + //This check is necessary as sometimes the stacklist is silently replaced (e.g. command making furnace read inventory from nbt) + if (insertInventoryStackList == this.insertStackList) { + return optimizedInventory; + } else { + this.invalidateBlockInsertionData(); + } + } else { + return blockInventory; + } + } + } + + //No Cached Inventory: Get like vanilla and cache + Direction direction = this.facing; + BlockPos insertBlockPos = this.getBlockPos().relative(direction); + BlockState blockState = world.getBlockState(insertBlockPos); + blockInventory = getBlockContainer(world, insertBlockPos, blockState); + blockInventory = HopperHelper.replaceDoubleInventory(blockInventory); + this.cacheInsertBlockInventory(blockInventory); + return blockInventory; + } + + public @Nullable Container getInsertInventory(Level world) { + Container blockInventory = getInsertInventory0(world); + return org.bukkit.event.inventory.HopperInventorySearchEvent.getHandlerList().getRegisteredListeners().length == 0 ? blockInventory : runHopperInventorySearchEvent( + blockInventory, + org.bukkit.craftbukkit.block.CraftBlock.at(world, this.getBlockPos()), + org.bukkit.craftbukkit.block.CraftBlock.at(world, this.getBlockPos().relative(this.facing)), + org.bukkit.event.inventory.HopperInventorySearchEvent.ContainerType.DESTINATION + ); + } + + public @Nullable Container getInsertInventory0(Level world) { + Container blockInventory = this.lithium$getInsertBlockInventory(world); + if (blockInventory != null) { + return blockInventory; + } + + if (this.insertInventoryEntityTracker == null) { + this.initInsertInventoryTracker(world); + } + if (ChunkSectionEntityMovementTracker.isUnchangedSince(this.insertInventoryEntityFailedSearchTime, this.insertInventoryEntityTracker)) { + this.insertInventoryEntityFailedSearchTime = this.tickedGameTime; + return null; + } + this.insertInventoryEntityFailedSearchTime = Long.MIN_VALUE; + this.shouldCheckSleep = false; + + List inventoryEntities = ChunkSectionInventoryEntityTracker.getEntities(world, this.insertInventoryEntityBox); + if (inventoryEntities.isEmpty()) { + this.insertInventoryEntityFailedSearchTime = this.tickedGameTime; + //Remember failed entity search timestamp. This allows shortcutting if no entity movement happens. + return null; + } + Container inventory = inventoryEntities.get(world.getRandom().nextInt(inventoryEntities.size())); // Leaves - Paper 26.1 fix + if (inventory instanceof LithiumInventory optimizedInventory) { + LithiumStackList insertInventoryStackList = InventoryHelper.getLithiumStackList(optimizedInventory); + if (inventory != this.insertInventory || this.insertStackList != insertInventoryStackList) { + this.cacheInsertLithiumInventory(optimizedInventory); + } + } + + return inventory; + } + + private void initCollectItemEntityTracker() { + assert this.level instanceof ServerLevel; + AABB inputBox = this.getSuckAabb().move(this.worldPosition.getX(), this.worldPosition.getY(), this.worldPosition.getZ()); + this.collectItemEntityBox = inputBox; + this.collectItemEntityTracker = + ChunkSectionItemEntityMovementTracker.registerAt( + (ServerLevel) this.level, + inputBox + ); + this.collectItemEntityAttemptTime = Long.MIN_VALUE; + } + + private void initExtractInventoryTracker(Level world) { + assert world instanceof ServerLevel; + BlockPos pos = this.worldPosition.relative(Direction.UP); + this.extractInventoryEntityBox = new AABB(pos.getX(), pos.getY(), pos.getZ(), pos.getX() + 1, pos.getY() + 1, pos.getZ() + 1); + this.extractInventoryEntityTracker = + ChunkSectionInventoryEntityTracker.registerAt( + (ServerLevel) this.level, + this.extractInventoryEntityBox + ); + this.extractInventoryEntityFailedSearchTime = Long.MIN_VALUE; + } + + private void initInsertInventoryTracker(Level world) { + assert world instanceof ServerLevel; + Direction direction = this.facing; + BlockPos pos = this.worldPosition.relative(direction); + this.insertInventoryEntityBox = new AABB(pos.getX(), pos.getY(), pos.getZ(), pos.getX() + 1, pos.getY() + 1, pos.getZ() + 1); + this.insertInventoryEntityTracker = + ChunkSectionInventoryEntityTracker.registerAt( + (ServerLevel) this.level, + this.insertInventoryEntityBox + ); + this.insertInventoryEntityFailedSearchTime = Long.MIN_VALUE; + } + + private @Nullable Container lithium$getExtractEntityInventory(Level world) { + if (this.extractInventoryEntityTracker == null) { + this.initExtractInventoryTracker(world); + } + if (ChunkSectionEntityMovementTracker.isUnchangedSince(this.extractInventoryEntityFailedSearchTime, this.extractInventoryEntityTracker)) { + this.extractInventoryEntityFailedSearchTime = this.tickedGameTime; + return null; + } + this.extractInventoryEntityFailedSearchTime = Long.MIN_VALUE; + this.shouldCheckSleep = false; + + List inventoryEntities = ChunkSectionInventoryEntityTracker.getEntities(world, this.extractInventoryEntityBox); + if (inventoryEntities.isEmpty()) { + this.extractInventoryEntityFailedSearchTime = this.tickedGameTime; + //only set unchanged when no entity present. this allows shortcutting this case + //shortcutting the entity present case requires checking its change counter + return null; + } + Container inventory = inventoryEntities.get(world.getRandom().nextInt(inventoryEntities.size())); // Leaves - Paper 26.1 fix + if (inventory instanceof LithiumInventory optimizedInventory) { + LithiumStackList extractInventoryStackList = InventoryHelper.getLithiumStackList(optimizedInventory); + if (inventory != this.extractInventory || this.extractStackList != extractInventoryStackList) { + //not caching the inventory (NO_BLOCK_INVENTORY prevents it) + //make change counting on the entity inventory possible, without caching it as block inventory + this.cacheExtractLithiumInventory(optimizedInventory); + } + } + return inventory; + } + + /** + * Makes this hopper remember the given inventory. + * + * @param insertInventory Block inventory / Blockentity inventory to be remembered + */ + private void cacheInsertBlockInventory(@Nullable Container insertInventory) { + assert !(insertInventory instanceof Entity); + if (insertInventory instanceof LithiumInventory optimizedInventory) { + this.cacheInsertLithiumInventory(optimizedInventory); + } else { + this.insertInventory = null; + this.insertStackList = null; + this.insertStackListModCount = 0; + } + + if (insertInventory instanceof BlockEntity || insertInventory instanceof CompoundContainer) { + this.insertBlockInventory = insertInventory; + if (insertInventory instanceof InventoryChangeTracker) { + this.insertionMode = HopperCachingState.BlockInventory.REMOVAL_TRACKING_BLOCK_ENTITY; + ((InventoryChangeTracker) insertInventory).listenForMajorInventoryChanges(this); + } else { + this.insertionMode = HopperCachingState.BlockInventory.BLOCK_ENTITY; + } + } else { + if (insertInventory == null) { + this.insertBlockInventory = null; + this.insertionMode = HopperCachingState.BlockInventory.NO_BLOCK_INVENTORY; + } else { + this.insertBlockInventory = insertInventory; + this.insertionMode = insertInventory instanceof BlockStateOnlyInventory ? HopperCachingState.BlockInventory.BLOCK_STATE : HopperCachingState.BlockInventory.UNKNOWN; + } + } + } + + private void cacheInsertLithiumInventory(LithiumInventory optimizedInventory) { + LithiumStackList insertInventoryStackList = InventoryHelper.getLithiumStackList(optimizedInventory); + this.insertInventory = optimizedInventory; + this.insertStackList = insertInventoryStackList; + this.insertStackListModCount = insertInventoryStackList.getModCount() - 1; + } + + private void cacheExtractLithiumInventory(LithiumInventory optimizedInventory) { + LithiumStackList extractInventoryStackList = InventoryHelper.getLithiumStackList(optimizedInventory); + this.extractInventory = optimizedInventory; + this.extractStackList = extractInventoryStackList; + this.extractStackListModCount = extractInventoryStackList.getModCount() - 1; + } + + /** + * Makes this hopper remember the given inventory. + * + * @param extractInventory Block inventory / Blockentity inventory to be remembered + */ + private void cacheExtractBlockInventory(@Nullable Container extractInventory) { + assert !(extractInventory instanceof Entity); + if (extractInventory instanceof LithiumInventory optimizedInventory) { + this.cacheExtractLithiumInventory(optimizedInventory); + } else { + this.extractInventory = null; + this.extractStackList = null; + this.extractStackListModCount = 0; + } + + if (extractInventory instanceof BlockEntity || extractInventory instanceof CompoundContainer) { + this.extractBlockInventory = extractInventory; + if (extractInventory instanceof InventoryChangeTracker) { + this.extractionMode = HopperCachingState.BlockInventory.REMOVAL_TRACKING_BLOCK_ENTITY; + ((InventoryChangeTracker) extractInventory).listenForMajorInventoryChanges(this); + } else { + this.extractionMode = HopperCachingState.BlockInventory.BLOCK_ENTITY; + } + } else { + if (extractInventory == null) { + this.extractBlockInventory = null; + this.extractionMode = HopperCachingState.BlockInventory.NO_BLOCK_INVENTORY; + } else { + this.extractBlockInventory = extractInventory; + this.extractionMode = extractInventory instanceof BlockStateOnlyInventory ? HopperCachingState.BlockInventory.BLOCK_STATE : HopperCachingState.BlockInventory.UNKNOWN; + } + } + } + + private static List lithiumGetInputItemEntities(Level world, Hopper hopper) { + if (!(hopper instanceof HopperBlockEntity hopperBlockEntity)) { + return getItemsAtAndAbove(world, hopper); //optimizations not implemented for hopper minecarts + } + + if (hopperBlockEntity.collectItemEntityTracker == null) { + hopperBlockEntity.initCollectItemEntityTracker(); + } + + long modCount = InventoryHelper.getLithiumStackList(hopperBlockEntity).getModCount(); + + if ((hopperBlockEntity.collectItemEntityTrackerWasEmpty || hopperBlockEntity.myModCountAtLastItemCollect == modCount) && + ChunkSectionEntityMovementTracker.isUnchangedSince(hopperBlockEntity.collectItemEntityAttemptTime, hopperBlockEntity.collectItemEntityTracker)) { + hopperBlockEntity.collectItemEntityAttemptTime = hopperBlockEntity.tickedGameTime; + return java.util.Collections.emptyList(); + } + + hopperBlockEntity.myModCountAtLastItemCollect = modCount; + hopperBlockEntity.shouldCheckSleep = false; + + List itemEntities = ChunkSectionItemEntityMovementTracker.getEntities(world, hopperBlockEntity.collectItemEntityBox); + hopperBlockEntity.collectItemEntityAttemptTime = hopperBlockEntity.tickedGameTime; + hopperBlockEntity.collectItemEntityTrackerWasEmpty = itemEntities.isEmpty(); + //set unchanged so that if this extract fails and there is no other change to hoppers or items, extracting + // items can be skipped. + return itemEntities; + } + + private static @Nullable Boolean lithiumInsert(Level world, BlockPos pos, HopperBlockEntity hopperBlockEntity, @Nullable Container insertInventory) { + if (insertInventory == null || hopperBlockEntity instanceof net.minecraft.world.WorldlyContainer) { + //call the vanilla code to allow other mods inject features + //e.g. carpet mod allows hoppers to insert items into wool blocks + return null; + } + + LithiumStackList hopperStackList = InventoryHelper.getLithiumStackList(hopperBlockEntity); + if (hopperBlockEntity.insertInventory == insertInventory && hopperStackList.getModCount() == hopperBlockEntity.myModCountAtLastInsert) { + if (hopperBlockEntity.insertStackList != null && hopperBlockEntity.insertStackList.getModCount() == hopperBlockEntity.insertStackListModCount) { +// ComparatorUpdatePattern.NO_UPDATE.apply(hopperBlockEntity, hopperStackList); //commented because it's a noop, Hoppers do not send useless comparator updates + return false; + } + } + + boolean insertInventoryWasEmptyHopperNotDisabled = insertInventory instanceof HopperBlockEntity hopperInv && + !hopperInv.isOnCustomCooldown() && hopperBlockEntity.insertStackList != null && + hopperBlockEntity.insertStackList.getOccupiedSlots() == 0; + + boolean insertInventoryHandlesModdedCooldown = + insertInventory.canReceiveTransferCooldown() && + hopperBlockEntity.insertStackList != null ? + hopperBlockEntity.insertStackList.getOccupiedSlots() == 0 : + insertInventory.isEmpty(); + + var worldData = world.getCurrentWorldData(); + worldData.skipPushModeEventFire = worldData.skipHopperEvents; + //noinspection ConstantConditions + if (!(hopperBlockEntity.insertInventory == insertInventory && hopperBlockEntity.insertStackList.getFullSlots() == hopperBlockEntity.insertStackList.size())) { + Direction fromDirection = hopperBlockEntity.facing.getOpposite(); + int size = hopperStackList.size(); + for (int i = 0; i < size; ++i) { + ItemStack transferStack = hopperStackList.get(i); + if (!transferStack.isEmpty()) { + if (!worldData.skipPushModeEventFire && canTakeItemFromContainer(insertInventory, hopperBlockEntity, transferStack, i, Direction.DOWN)) { + transferStack = callPushMoveEvent(insertInventory, transferStack, hopperBlockEntity); + if (transferStack == null) { // cancelled + break; + } + } + boolean transferSuccess = HopperHelper.tryMoveSingleItem(insertInventory, transferStack, fromDirection); + if (transferSuccess) { + if (insertInventoryWasEmptyHopperNotDisabled) { + HopperBlockEntity receivingHopper = (HopperBlockEntity) insertInventory; + int k = 8; + if (receivingHopper.tickedGameTime >= hopperBlockEntity.tickedGameTime) { + k = 7; + } + receivingHopper.setCooldown(k); + } + if (insertInventoryHandlesModdedCooldown) { + insertInventory.setTransferCooldown(hopperBlockEntity.tickedGameTime); + } + insertInventory.setChanged(); + return true; + } + } + } + } + hopperBlockEntity.myModCountAtLastInsert = hopperStackList.getModCount(); + if (hopperBlockEntity.insertStackList != null) { + hopperBlockEntity.insertStackListModCount = hopperBlockEntity.insertStackList.getModCount(); + } + return false; + } + + private static @Nullable Boolean lithiumExtract(Level world, Hopper to, Container from) { + if (!(to instanceof HopperBlockEntity hopperBlockEntity)) { + return null; //optimizations not implemented for hopper minecarts + } + + if (from != hopperBlockEntity.extractInventory || hopperBlockEntity.extractStackList == null) { + return null; //from inventory is not an optimized inventory, vanilla fallback + } + + LithiumStackList hopperStackList = InventoryHelper.getLithiumStackList(hopperBlockEntity); + LithiumStackList fromStackList = hopperBlockEntity.extractStackList; + + if (hopperStackList.getModCount() == hopperBlockEntity.myModCountAtLastExtract) { + if (fromStackList.getModCount() == hopperBlockEntity.extractStackListModCount) { + if (!(from instanceof ComparatorTracker comparatorTracker) || comparatorTracker.lithium$hasAnyComparatorNearby()) { + //noinspection CollectionAddedToSelf + fromStackList.runComparatorUpdatePatternOnFailedExtract(fromStackList, from); + } + return false; + } + } + + int[] availableSlots = from instanceof WorldlyContainer ? ((WorldlyContainer) from).getSlotsForFace(Direction.DOWN) : null; + int fromSize = availableSlots != null ? availableSlots.length : from.getContainerSize(); + for (int i = 0; i < fromSize; i++) { + int fromSlot = availableSlots != null ? availableSlots[i] : i; + ItemStack itemStack = fromStackList.get(fromSlot); + if (!itemStack.isEmpty() && canTakeItemFromContainer(to, from, itemStack, fromSlot, Direction.DOWN)) { + if (!world.getCurrentWorldData().skipPullModeEventFire) { + itemStack = callPullMoveEvent(to, from, itemStack); + if (itemStack == null) { // cancelled + return true; + } + } + //calling removeStack is necessary due to its side effects (markDirty in LootableContainerBlockEntity) + ItemStack takenItem = from.removeItem(fromSlot, 1); + assert !takenItem.isEmpty(); + boolean transferSuccess = HopperHelper.tryMoveSingleItem(to, takenItem, null); + if (transferSuccess) { + to.setChanged(); + from.setChanged(); + return true; + } + //put the item back similar to vanilla + ItemStack restoredStack = fromStackList.get(fromSlot); + if (restoredStack.isEmpty()) { + restoredStack = takenItem; + } else { + restoredStack.grow(1); + } + //calling setStack is necessary due to its side effects (markDirty in LootableContainerBlockEntity) + from.setItem(fromSlot, restoredStack); + } + } + hopperBlockEntity.myModCountAtLastExtract = hopperStackList.getModCount(); + if (fromStackList != null) { + hopperBlockEntity.extractStackListModCount = fromStackList.getModCount(); + } + return false; + } + // Leaves end - Lithium Sleeping Block Entity } diff --git a/net/minecraft/world/level/block/entity/ShulkerBoxBlockEntity.java b/net/minecraft/world/level/block/entity/ShulkerBoxBlockEntity.java index 174e39fb109bef3dd27ebc14aa13f6faabb10547..14138b6a4ab15e372f185171c7972bff353c2ae1 100644 --- a/net/minecraft/world/level/block/entity/ShulkerBoxBlockEntity.java +++ b/net/minecraft/world/level/block/entity/ShulkerBoxBlockEntity.java @@ -32,7 +32,7 @@ import net.minecraft.world.phys.AABB; import net.minecraft.world.phys.Vec3; import org.jspecify.annotations.Nullable; -public class ShulkerBoxBlockEntity extends RandomizableContainerBlockEntity implements WorldlyContainer { +public class ShulkerBoxBlockEntity extends RandomizableContainerBlockEntity implements WorldlyContainer, org.leavesmc.leaves.lithium.common.block.entity.inventory_change_tracking.InventoryChangeTracker, org.leavesmc.leaves.lithium.common.block.entity.SleepingBlockEntity, org.leavesmc.leaves.lithium.api.inventory.LithiumInventory { // Leaves - Lithium Sleeping Block Entity public static final int COLUMNS = 9; public static final int ROWS = 3; public static final int CONTAINER_SIZE = 27; @@ -134,6 +134,7 @@ public class ShulkerBoxBlockEntity extends RandomizableContainerBlockEntity impl doNeighborUpdates(level, pos, blockState); } } + if (io.nanachiyo0721.shiroha.config.modules.optimizations.LeavesSleepingBlockEntityConfig.enabled && this.animationStatus == ShulkerBoxBlockEntity.AnimationStatus.CLOSED && this.progressOld == 0.0f && this.progress == 0.0f) this.lithium$startSleeping(); // Leaves - Lithium Sleeping Block Entity } public ShulkerBoxBlockEntity.AnimationStatus getAnimationStatus() { @@ -174,6 +175,7 @@ public class ShulkerBoxBlockEntity extends RandomizableContainerBlockEntity impl @Override public boolean triggerEvent(final int b0, final int b1) { + if (io.nanachiyo0721.shiroha.config.modules.optimizations.LeavesSleepingBlockEntityConfig.enabled && this.sleepingTicker != null) this.wakeUpNow(); // Leaves - Lithium Sleeping Block Entity if (b0 == EVENT_SET_OPEN_COUNT) { this.openCount = b1; if (b1 == 0) { @@ -269,6 +271,7 @@ public class ShulkerBoxBlockEntity extends RandomizableContainerBlockEntity impl @Override protected void setItems(final NonNullList items) { this.itemStacks = items; + if (io.nanachiyo0721.shiroha.config.modules.optimizations.LeavesSleepingBlockEntityConfig.enabled) this.lithium$emitStackListReplaced(); // Leaves - Lithium Sleeping Block Entity } @Override @@ -309,4 +312,39 @@ public class ShulkerBoxBlockEntity extends RandomizableContainerBlockEntity impl OPENED, CLOSING; } + + // Leaves start - Lithium Sleeping Block Entity + private net.minecraft.world.level.chunk.LevelChunk.RebindableTickingBlockEntityWrapper tickWrapper = null; + private TickingBlockEntity sleepingTicker = null; + + @Override + public net.minecraft.world.level.chunk.LevelChunk.RebindableTickingBlockEntityWrapper lithium$getTickWrapper() { + return tickWrapper; + } + + @Override + public void lithium$setTickWrapper(net.minecraft.world.level.chunk.LevelChunk.RebindableTickingBlockEntityWrapper tickWrapper) { + this.tickWrapper = tickWrapper; + } + + @Override + public TickingBlockEntity lithium$getSleepingTicker() { + return sleepingTicker; + } + + @Override + public void lithium$setSleepingTicker(TickingBlockEntity sleepingTicker) { + this.sleepingTicker = sleepingTicker; + } + + @Override + public net.minecraft.core.NonNullList getInventoryLithium() { + return itemStacks; + } + + @Override + public void setInventoryLithium(net.minecraft.core.NonNullList inventory) { + itemStacks = inventory; + } + // Leaves end - Lithium Sleeping Block Entity } diff --git a/net/minecraft/world/level/block/entity/TickingBlockEntity.java b/net/minecraft/world/level/block/entity/TickingBlockEntity.java index c8facee29ee08e0975528083f89b64f0b593957f..e50a892ebc85a5d79e13ce9a4578cbd2e298d1fd 100644 --- a/net/minecraft/world/level/block/entity/TickingBlockEntity.java +++ b/net/minecraft/world/level/block/entity/TickingBlockEntity.java @@ -12,4 +12,8 @@ public interface TickingBlockEntity { String getType(); BlockEntity getTileEntity(); // Folia - region threading + + // Shiroha start - Region threading for lithium sleeping block entity + default void updateTicksForLithium(long redstoneGameTimeOffset) {} + // Shiroha end } diff --git a/net/minecraft/world/level/block/state/BlockBehaviour.java b/net/minecraft/world/level/block/state/BlockBehaviour.java index c78d77089e34c4bb559f173b449bfb7d91b05d5c..2cae0d5c815b5c273267e554c32ce117b608eebf 100644 --- a/net/minecraft/world/level/block/state/BlockBehaviour.java +++ b/net/minecraft/world/level/block/state/BlockBehaviour.java @@ -83,7 +83,7 @@ import net.minecraft.world.phys.shapes.Shapes; import net.minecraft.world.phys.shapes.VoxelShape; import org.jspecify.annotations.Nullable; -public abstract class BlockBehaviour implements FeatureElement { +public abstract class BlockBehaviour implements FeatureElement, org.leavesmc.leaves.lithium.common.block.entity.ShapeUpdateHandlingBlockBehaviour { // Leaves - Lithium Sleeping Block Entity protected static final Direction[] UPDATE_SHAPE_ORDER = new Direction[]{ Direction.WEST, Direction.EAST, Direction.NORTH, Direction.SOUTH, Direction.DOWN, Direction.UP }; @@ -155,6 +155,7 @@ public abstract class BlockBehaviour implements FeatureElement { final BlockState neighbourState, final RandomSource random ) { + if (io.nanachiyo0721.shiroha.config.modules.optimizations.LeavesSleepingBlockEntityConfig.enabled) this.lithium$handleShapeUpdate(level, state, pos, neighbourPos, neighbourState); // Leaves - Lithium Sleeping Block Entity /* Triggers when a shape update (= update that observers can detect) is sent */ return state; } diff --git a/net/minecraft/world/level/block/state/pattern/BlockPattern.java b/net/minecraft/world/level/block/state/pattern/BlockPattern.java index ed5a5fb38bfe8fb9216c7eab00b833485c4bbade..fc45703525901d700c98b5ad0c6eb10b65e775d8 100644 --- a/net/minecraft/world/level/block/state/pattern/BlockPattern.java +++ b/net/minecraft/world/level/block/state/pattern/BlockPattern.java @@ -57,7 +57,7 @@ public class BlockPattern { return this.matches(origin, forwards, up, cache); } - private BlockPattern.@Nullable BlockPatternMatch matches( + public BlockPattern.@Nullable BlockPatternMatch matches( // Leaves - private -> public final BlockPos origin, final Direction forwards, final Direction up, final LoadingCache cache ) { for (int x = 0; x < this.width; x++) { diff --git a/net/minecraft/world/level/chunk/LevelChunk.java b/net/minecraft/world/level/chunk/LevelChunk.java index 0e84b695a677e71f443a29c78fc5025eec7ae7f4..df6d65c258ea48e4e3b2f4ec02150ab06cd6682f 100644 --- a/net/minecraft/world/level/chunk/LevelChunk.java +++ b/net/minecraft/world/level/chunk/LevelChunk.java @@ -622,6 +622,7 @@ public class LevelChunk extends ChunkAccess implements DebugValueSource, ca.spot LevelChunk.RebindableTickingBlockEntityWrapper ticker = this.tickersInLevel.remove(pos); if (ticker != null) { ticker.rebind(NULL_TICKER); + ticker.createdBy = null; // Shiroha - Region threading for sleeping block entity } } @@ -883,6 +884,7 @@ public class LevelChunk extends ChunkAccess implements DebugValueSource, ca.spot this.blockEntities.values().forEach(BlockEntity::setRemoved); this.blockEntities.clear(); this.tickersInLevel.values().forEach(ticker -> ticker.rebind(NULL_TICKER)); + this.tickersInLevel.values().forEach(ticker -> ticker.createdBy = null); // Shiroha - Region threading for sleeping block entity this.tickersInLevel.clear(); } @@ -916,10 +918,14 @@ public class LevelChunk extends ChunkAccess implements DebugValueSource, ca.spot this.tickersInLevel.compute(blockEntity.getBlockPos(), (blockPos, existingTicker) -> { TickingBlockEntity actualTicker = this.createTicker(blockEntity, ticker); if (existingTicker != null) { + if (io.nanachiyo0721.shiroha.config.modules.optimizations.LeavesSleepingBlockEntityConfig.enabled && blockEntity instanceof org.leavesmc.leaves.lithium.common.block.entity.SleepingBlockEntity sleepingBlockEntity) sleepingBlockEntity.lithium$setTickWrapper(existingTicker); // Leaves - Lithium Sleeping Block Entity + if (io.nanachiyo0721.shiroha.config.modules.optimizations.LeavesSleepingBlockEntityConfig.enabled) existingTicker.createdBy = blockEntity; // Shiroha - Region threading for lithium sleeping block entity existingTicker.rebind(actualTicker); return (LevelChunk.RebindableTickingBlockEntityWrapper)existingTicker; } else if (this.isInLevel()) { LevelChunk.RebindableTickingBlockEntityWrapper result = new LevelChunk.RebindableTickingBlockEntityWrapper(actualTicker); + if (io.nanachiyo0721.shiroha.config.modules.optimizations.LeavesSleepingBlockEntityConfig.enabled && blockEntity instanceof org.leavesmc.leaves.lithium.common.block.entity.SleepingBlockEntity sleepingBlockEntity) sleepingBlockEntity.lithium$setTickWrapper(result); // Leaves - Lithium Sleeping Block Entity + if (io.nanachiyo0721.shiroha.config.modules.optimizations.LeavesSleepingBlockEntityConfig.enabled) result.createdBy = blockEntity; // Shiroha - Region threading for lithium sleeping block entity this.level.addBlockEntityTicker(result); return result; } else { @@ -1028,20 +1034,36 @@ public class LevelChunk extends ChunkAccess implements DebugValueSource, ca.spot void run(LevelChunk levelChunk); } - private static class RebindableTickingBlockEntityWrapper implements TickingBlockEntity { - private TickingBlockEntity ticker; + public static class RebindableTickingBlockEntityWrapper implements TickingBlockEntity { // Leaves - default -> public + public TickingBlockEntity ticker; // Leaves - private -> public + @org.jetbrains.annotations.Nullable private BlockEntity createdBy; // Shiroha - Region threading for lithium sleeping block entity private RebindableTickingBlockEntityWrapper(final TickingBlockEntity ticker) { this.ticker = ticker; } - private void rebind(final TickingBlockEntity ticker) { + public void rebind(final TickingBlockEntity ticker) { // Leaves - default -> public this.ticker = ticker; } // Folia start - region threading @Override public BlockEntity getTileEntity() { + // Shiroha start - Region threading for lithium sleeping block entity + if (true) { + if (this.ticker != null) { + var ret = this.ticker.getTileEntity(); + + if (ret == null) { + return this.createdBy; + } + + return ret; + } + + return null; + } + // Shiroha end return this.ticker == null ? null : this.ticker.getTileEntity(); } // Folia end - region threading @@ -1070,6 +1092,16 @@ public class LevelChunk extends ChunkAccess implements DebugValueSource, ca.spot public String toString() { return this.ticker + " "; } + + // Shiroha start - Region threading for lithium sleeping block entity + @Override + public void updateTicksForLithium(long redstoneGameTimeOffset) { + if (this.ticker != null) { + this.ticker.updateTicksForLithium(redstoneGameTimeOffset); + } + } + + // Shiroha end } @FunctionalInterface