diff --git a/src/main/java/ca/spottedleaf/moonrise/mixin/collisions/CollisionGetterMixin.java b/src/main/java/ca/spottedleaf/moonrise/mixin/collisions/CollisionGetterMixin.java index fc91daf..094eca3 100644 --- a/src/main/java/ca/spottedleaf/moonrise/mixin/collisions/CollisionGetterMixin.java +++ b/src/main/java/ca/spottedleaf/moonrise/mixin/collisions/CollisionGetterMixin.java @@ -1,6 +1,7 @@ package ca.spottedleaf.moonrise.mixin.collisions; import ca.spottedleaf.moonrise.patches.collisions.CollisionUtil; +import ca.spottedleaf.moonrise.patches.collisions.block.CollisionBlockState; import ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape; import net.minecraft.core.BlockPos; import net.minecraft.util.Mth; @@ -48,11 +49,25 @@ public interface CollisionGetterMixin extends BlockGetter { final Vec3 entityPos = entity.position(); + BlockGetter lastChunk = null; + int lastChunkX = Integer.MIN_VALUE; + int lastChunkZ = Integer.MIN_VALUE; + for (int currZ = minBlockZ; currZ <= maxBlockZ; ++currZ) { + pos.setZ(currZ); for (int currX = minBlockX; currX <= maxBlockX; ++currX) { - pos.set(currX, 0, currZ); - final BlockGetter chunk = this.getChunkForCollisions(currX >> 4, currZ >> 4); - if (chunk == null) { + pos.setX(currX); + + final int newChunkX = currX >> 4; + final int newChunkZ = currZ >> 4; + + final int chunkDiff = ((newChunkX ^ lastChunkX) | (newChunkZ ^ lastChunkZ)); + + if (chunkDiff != 0) { + lastChunk = this.getChunkForCollisions(newChunkX, newChunkZ); + } + + if (lastChunk == null) { continue; } for (int currY = minBlockY; currY <= maxBlockY; ++currY) { @@ -70,8 +85,8 @@ public interface CollisionGetterMixin extends BlockGetter { continue; } - final BlockState state = chunk.getBlockState(pos); - if (state.isAir()) { + final BlockState state = lastChunk.getBlockState(pos); + if (((CollisionBlockState)state).emptyCollisionShape()) { continue; } @@ -79,7 +94,7 @@ public interface CollisionGetterMixin extends BlockGetter { if (collisionContext == null) { collisionContext = new CollisionUtil.LazyEntityCollisionContext(entity); } - final VoxelShape blockCollision = state.getCollisionShape(chunk, pos, collisionContext); + final VoxelShape blockCollision = state.getCollisionShape(lastChunk, pos, collisionContext); if (blockCollision.isEmpty()) { continue; } diff --git a/src/main/java/ca/spottedleaf/moonrise/mixin/hopper/HopperBlockEntityMixin.java b/src/main/java/ca/spottedleaf/moonrise/mixin/hopper/HopperBlockEntityMixin.java index a7d2a77..d63bea8 100644 --- a/src/main/java/ca/spottedleaf/moonrise/mixin/hopper/HopperBlockEntityMixin.java +++ b/src/main/java/ca/spottedleaf/moonrise/mixin/hopper/HopperBlockEntityMixin.java @@ -1,23 +1,70 @@ package ca.spottedleaf.moonrise.mixin.hopper; +import ca.spottedleaf.moonrise.patches.chunk_getblock.GetBlockChunk; import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.core.NonNullList; +import net.minecraft.util.Mth; +import net.minecraft.world.Container; +import net.minecraft.world.WorldlyContainer; +import net.minecraft.world.WorldlyContainerHolder; import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EntitySelector; import net.minecraft.world.entity.item.ItemEntity; +import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.ChestBlock; +import net.minecraft.world.level.block.HopperBlock; +import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.BlockEntityType; +import net.minecraft.world.level.block.entity.ChestBlockEntity; import net.minecraft.world.level.block.entity.Hopper; import net.minecraft.world.level.block.entity.HopperBlockEntity; import net.minecraft.world.level.block.entity.RandomizableContainerBlockEntity; import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.chunk.LevelChunk; import net.minecraft.world.phys.AABB; import net.minecraft.world.phys.shapes.VoxelShape; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Overwrite; +import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.Unique; import java.util.List; +import java.util.function.BooleanSupplier; @Mixin(HopperBlockEntity.class) public abstract class HopperBlockEntityMixin extends RandomizableContainerBlockEntity implements Hopper { + + @Shadow + private NonNullList items; + + @Shadow + protected abstract boolean isOnCooldown(); + + @Shadow + protected abstract void setCooldown(int i); + + @Shadow + private static Container getSourceContainer(Level level, Hopper hopper) { + return null; + } + + @Shadow + public static boolean addItem(Container container, ItemEntity itemEntity) { + return false; + } + + @Shadow + private static boolean tryTakeInItemFromSlot(Hopper hopper, Container container, int i, Direction direction) { + return false; + } + + @Shadow + private static boolean ejectItems(Level level, BlockPos blockPos, BlockState blockState, Container container) { + return false; + } + protected HopperBlockEntityMixin(BlockEntityType blockEntityType, BlockPos blockPos, BlockState blockState) { super(blockEntityType, blockPos, blockState); } @@ -82,4 +129,190 @@ public abstract class HopperBlockEntityMixin extends RandomizableContainerBlockE return false; }); } + + /** + * @reason Avoid checking for empty container and remove streams / indirection + * @author Spottedleaf + */ + @Overwrite + public static boolean suckInItems(final Level level, final Hopper hopper) { + final Container aboveContainer = getSourceContainer(level, hopper); + if (aboveContainer != null) { + final Direction down = Direction.DOWN; + + // don't bother checking for empty, because the same logic can be done by trying to move items - as + // that checks for an empty item. + + if (aboveContainer instanceof WorldlyContainer worldlyContainer) { + for (final int slot : worldlyContainer.getSlotsForFace(down)) { + if (tryTakeInItemFromSlot(hopper, aboveContainer, slot, down)) { + return true; + } + } + return false; + } else { + for (int slot = 0, max = aboveContainer.getContainerSize(); slot < max; ++slot) { + if (tryTakeInItemFromSlot(hopper, aboveContainer, slot, down)) { + return true; + } + } + return false; + } + + } else { + final List items = getItemsAtAndAbove(level, hopper); + + for (int i = 0, len = items.size(); i < len; ++i) { + if (addItem(hopper, items.get(i))) { + return true; + } + } + + return false; + } + } + + /** + * @reason Cache chunk for block read + TE read, and use getEntitiesOfClass to avoid looking at non-container entities + * @author Spottedleaf + */ + @Overwrite + private static Container getContainerAt(final Level level, final double x, final double y, final double z) { + final int blockX = Mth.floor(x); + final int blockY = Mth.floor(y); + final int blockZ = Mth.floor(z); + + final LevelChunk chunk = level.getChunk(blockX >> 4, blockZ >> 4); + + final BlockState state = ((GetBlockChunk)chunk).getBlock(blockX, blockY, blockZ); + final Block block = state.getBlock(); + + Container blockContainer = null; + if (block instanceof WorldlyContainerHolder worldlyContainerHolder) { + blockContainer = worldlyContainerHolder.getContainer(state, level, new BlockPos(blockX, blockY, blockZ)); + } else if (state.hasBlockEntity() && !level.isOutsideBuildHeight(blockY)) { + final BlockPos pos = new BlockPos(blockX, blockY, blockZ); + final BlockEntity blockEntity = chunk.getBlockEntity(pos, LevelChunk.EntityCreationType.IMMEDIATE); + if (blockEntity instanceof Container) { + blockContainer = (Container)blockEntity; + if (block instanceof ChestBlock chestBlock && blockContainer instanceof ChestBlockEntity) { + blockContainer = ChestBlock.getContainer(chestBlock, state, level, pos, true); + } + } + } + + if (blockContainer != null) { + return blockContainer; + } + + final List containers = level.getEntitiesOfClass((Class)Container.class, new AABB( + x - 0.5, y - 0.5, z - 0.5, + x + 0.5, y + 0.5, z + 0.5 + ), EntitySelector.CONTAINER_ENTITY_SELECTOR); + if (!containers.isEmpty()) { + return (Container)containers.get(level.random.nextInt(containers.size())); + } + + return null; + } + + @Unique + private static final int HOPPER_EMPTY = 0; + @Unique + private static final int HOPPER_HAS_ITEMS = 1; + @Unique + private static final int HOPPER_IS_FULL = 2; + + @Unique + private static int getFullState(final HopperBlockEntity tileEntity) { + tileEntity.unpackLootTable(null); + + final List hopperItems = ((HopperBlockEntityMixin)(Object)tileEntity).items; + + boolean empty = true; + boolean full = true; + + for (int i = 0, len = hopperItems.size(); i < len; ++i) { + final ItemStack stack = hopperItems.get(i); + if (stack.isEmpty()) { + full = false; + continue; + } + + if (!full) { + // can't be full + return HOPPER_HAS_ITEMS; + } + + empty = false; + + if (stack.getCount() != stack.getMaxStackSize()) { + // can't be full or empty + return HOPPER_HAS_ITEMS; + } + } + + return empty ? HOPPER_EMPTY : (full ? HOPPER_IS_FULL : HOPPER_HAS_ITEMS); + } + + + /** + * @reason Combine empty / full into one check + * @author Spottedleaf + */ + @Overwrite + private static boolean tryMoveItems(Level level, BlockPos blockPos, BlockState blockState, HopperBlockEntity hopperBlockEntity, BooleanSupplier booleanSupplier) { + if (level.isClientSide) { + return false; + } + + if (((HopperBlockEntityMixin)(Object)hopperBlockEntity).isOnCooldown() || !blockState.getValue(HopperBlock.ENABLED).booleanValue()) { + return false; + } + + final int fullState = getFullState(hopperBlockEntity); + + boolean changed = false; + if (fullState != HOPPER_EMPTY) { + changed = ejectItems(level, blockPos, blockState, hopperBlockEntity); + } + + // need to check changed here, as fullState will be outdated if it changed + if (fullState != HOPPER_IS_FULL || changed) { + changed |= booleanSupplier.getAsBoolean(); + } + + if (changed) { + ((HopperBlockEntityMixin)(Object)hopperBlockEntity).setCooldown(8); + setChanged(level, blockPos, blockState); + return true; + } + + return false; + } + + /** + * @reason Remove streams + * @author Spottedleaf + */ + @Overwrite + private static boolean isFullContainer(final Container container, final Direction direction) { + if (container instanceof WorldlyContainer worldlyContainer) { + for (final int slot : worldlyContainer.getSlotsForFace(direction)) { + final ItemStack stack = container.getItem(slot); + if (stack.getCount() < stack.getMaxStackSize()) { + return false; + } + } + return true; + } else { + for (int slot = 0, max = container.getContainerSize(); slot < max; ++slot) { + final ItemStack stack = container.getItem(slot); + if (stack.getCount() < stack.getMaxStackSize()) { + return false; + } + } + return true; + } + } } diff --git a/src/main/java/ca/spottedleaf/moonrise/mixin/hopper/RandomizableContainerBlockEntityMixin.java b/src/main/java/ca/spottedleaf/moonrise/mixin/hopper/RandomizableContainerBlockEntityMixin.java new file mode 100644 index 0000000..e39e4c1 --- /dev/null +++ b/src/main/java/ca/spottedleaf/moonrise/mixin/hopper/RandomizableContainerBlockEntityMixin.java @@ -0,0 +1,49 @@ +package ca.spottedleaf.moonrise.mixin.hopper; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.NonNullList; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.block.entity.BaseContainerBlockEntity; +import net.minecraft.world.level.block.entity.BlockEntityType; +import net.minecraft.world.level.block.entity.RandomizableContainerBlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import org.jetbrains.annotations.Nullable; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Overwrite; +import org.spongepowered.asm.mixin.Shadow; +import java.util.List; + +@Mixin(RandomizableContainerBlockEntity.class) +public abstract class RandomizableContainerBlockEntityMixin extends BaseContainerBlockEntity { + + @Shadow + public abstract void unpackLootTable(@Nullable Player player); + + @Shadow + protected abstract NonNullList getItems(); + + protected RandomizableContainerBlockEntityMixin(BlockEntityType blockEntityType, BlockPos blockPos, BlockState blockState) { + super(blockEntityType, blockPos, blockState); + } + + /** + * @reason Remove streams + * @author Spottedleaf + */ + @Overwrite + @Override + public boolean isEmpty() { + this.unpackLootTable(null); + + final List items = this.getItems(); + + for (int i = 0, len = items.size(); i < len; ++i) { + if (!items.get(i).isEmpty()) { + return false; + } + } + + return true; + } +} diff --git a/src/main/java/ca/spottedleaf/moonrise/mixin/util_thread_counts/UtilMixin.java b/src/main/java/ca/spottedleaf/moonrise/mixin/util_thread_counts/UtilMixin.java index b41a6f6..b641b9b 100644 --- a/src/main/java/ca/spottedleaf/moonrise/mixin/util_thread_counts/UtilMixin.java +++ b/src/main/java/ca/spottedleaf/moonrise/mixin/util_thread_counts/UtilMixin.java @@ -10,7 +10,9 @@ import org.spongepowered.asm.mixin.Overwrite; import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.Unique; import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.ForkJoinWorkerThread; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; @Mixin(Util.class) @@ -62,20 +64,20 @@ public abstract class UtilMixin { final AtomicInteger workerCount = new AtomicInteger(); - return Executors.newFixedThreadPool(threads, (final Runnable run) -> { - final Thread ret = new Thread(run); - ret.setName("Worker-" + name + "-" + workerCount.getAndIncrement()); + return new ForkJoinPool(threads, (forkJoinPool) -> { + ForkJoinWorkerThread forkJoinWorkerThread = new ForkJoinWorkerThread(forkJoinPool) { + protected void onTermination(final Throwable throwable) { + if (throwable != null) { + LOGGER.error(this.getName() + " died", throwable); + } else { + LOGGER.debug(this.getName() + " shutdown"); + } - ret.setUncaughtExceptionHandler((final Thread thread, final Throwable thr) -> { - LOGGER.error(thread.getName() + "died", thr); - - onThreadException(thread, thr); - }); - - ret.setDaemon(true); // forkjoin workers are daemon - ret.setPriority(Thread.NORM_PRIORITY - 1); // de-prioritise over main threads (render/server) - - return ret; - }); + super.onTermination(throwable); + } + }; + forkJoinWorkerThread.setName("Worker-" + name + "-" + workerCount.getAndIncrement()); + return forkJoinWorkerThread; + }, UtilMixin::onThreadException, true, 0, Integer.MAX_VALUE, 1, null, 365, TimeUnit.DAYS); } } diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/CollisionVoxelShape.java b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/CollisionVoxelShape.java index 5071305..34efc45 100644 --- a/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/CollisionVoxelShape.java +++ b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/CollisionVoxelShape.java @@ -1,9 +1,7 @@ package ca.spottedleaf.moonrise.patches.collisions.shape; -import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.world.phys.AABB; -import net.minecraft.world.phys.Vec3; import net.minecraft.world.phys.shapes.VoxelShape; public interface CollisionVoxelShape { diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/hopper/README.md b/src/main/java/ca/spottedleaf/moonrise/patches/hopper/README.md deleted file mode 100644 index e69de29..0000000 diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index a702929..71de15f 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -15,7 +15,8 @@ }, "breaks": { "notenoughcrashes": "*", - "starlight": "*" + "starlight": "*", + "lithium": "*" }, "license": "GPL-3.0-only", "icon": "assets/moonrise/icon.png", diff --git a/src/main/resources/moonrise.mixins.json b/src/main/resources/moonrise.mixins.json index 16de736..7bc318b 100644 --- a/src/main/resources/moonrise.mixins.json +++ b/src/main/resources/moonrise.mixins.json @@ -36,6 +36,7 @@ "fluid.FluidMixin", "fluid.FluidStateMixin", "hopper.HopperBlockEntityMixin", + "hopper.RandomizableContainerBlockEntityMixin", "keep_alive_client.ServerGamePacketListenerImplMixin", "poi_lookup.AcquirePoiMixin", "poi_lookup.PoiManagerMixin",