Hopper optimisations
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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<ItemStack> 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<ItemEntity> 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<Entity> 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<ItemStack> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<ItemStack> 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<ItemStack> items = this.getItems();
|
||||
|
||||
for (int i = 0, len = items.size(); i < len; ++i) {
|
||||
if (!items.get(i).isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -15,7 +15,8 @@
|
||||
},
|
||||
"breaks": {
|
||||
"notenoughcrashes": "*",
|
||||
"starlight": "*"
|
||||
"starlight": "*",
|
||||
"lithium": "*"
|
||||
},
|
||||
"license": "GPL-3.0-only",
|
||||
"icon": "assets/moonrise/icon.png",
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
"fluid.FluidMixin",
|
||||
"fluid.FluidStateMixin",
|
||||
"hopper.HopperBlockEntityMixin",
|
||||
"hopper.RandomizableContainerBlockEntityMixin",
|
||||
"keep_alive_client.ServerGamePacketListenerImplMixin",
|
||||
"poi_lookup.AcquirePoiMixin",
|
||||
"poi_lookup.PoiManagerMixin",
|
||||
|
||||
Reference in New Issue
Block a user