Hopper optimisations

This commit is contained in:
Spottedleaf
2023-08-27 01:57:43 -07:00
parent e19f6a8796
commit 48043cd406
8 changed files with 323 additions and 24 deletions

View File

@@ -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;
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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 {

View File

@@ -15,7 +15,8 @@
},
"breaks": {
"notenoughcrashes": "*",
"starlight": "*"
"starlight": "*",
"lithium": "*"
},
"license": "GPL-3.0-only",
"icon": "assets/moonrise/icon.png",

View File

@@ -36,6 +36,7 @@
"fluid.FluidMixin",
"fluid.FluidStateMixin",
"hopper.HopperBlockEntityMixin",
"hopper.RandomizableContainerBlockEntityMixin",
"keep_alive_client.ServerGamePacketListenerImplMixin",
"poi_lookup.AcquirePoiMixin",
"poi_lookup.PoiManagerMixin",