From d460f8f824438785b404b89b97a94d9492f9c919 Mon Sep 17 00:00:00 2001 From: 2No2Name <2No2Name@web.de> Date: Thu, 14 May 2020 03:29:52 +0200 Subject: [PATCH 1/3] Keep track of oversized blocks in chunk sections, reduce number of blocks iterated over when possible --- .../movement/BlockCollisionSweeper.java | 8 +++ .../oversized_blocks/MixinChunkSection.java | 64 +++++++++++++++++++ src/main/resources/lithium.mixins.json | 1 + 3 files changed, 73 insertions(+) create mode 100644 src/main/java/me/jellysquid/mods/lithium/mixin/chunk/oversized_blocks/MixinChunkSection.java diff --git a/src/main/java/me/jellysquid/mods/lithium/common/entity/movement/BlockCollisionSweeper.java b/src/main/java/me/jellysquid/mods/lithium/common/entity/movement/BlockCollisionSweeper.java index 85c615e8f..c7440caf6 100644 --- a/src/main/java/me/jellysquid/mods/lithium/common/entity/movement/BlockCollisionSweeper.java +++ b/src/main/java/me/jellysquid/mods/lithium/common/entity/movement/BlockCollisionSweeper.java @@ -14,6 +14,8 @@ import net.minecraft.util.shape.VoxelShapes; import net.minecraft.world.BlockView; import net.minecraft.world.CollisionView; +import net.minecraft.world.World; +import net.minecraft.world.chunk.Chunk; import static me.jellysquid.mods.lithium.common.entity.LithiumEntityCollisions.EPSILON; @@ -73,6 +75,8 @@ public boolean step() { if (chunk == null) { return true; + } else if (World.isHeightInvalid(y) || edgesHit >= 1 && chunk instanceof Chunk && !((HasOversizedBlocks)(((Chunk) chunk).getSectionArray()[y >> 4])).hasOversizedBlocks()) { + return true; } final BlockPos.Mutable mpos = this.mpos; @@ -148,4 +152,8 @@ private static VoxelShape getCollidedShape(Box entityBox, VoxelShape entityShape return null; } + + public interface HasOversizedBlocks { + boolean hasOversizedBlocks(); + } } diff --git a/src/main/java/me/jellysquid/mods/lithium/mixin/chunk/oversized_blocks/MixinChunkSection.java b/src/main/java/me/jellysquid/mods/lithium/mixin/chunk/oversized_blocks/MixinChunkSection.java new file mode 100644 index 000000000..5ddffbd19 --- /dev/null +++ b/src/main/java/me/jellysquid/mods/lithium/mixin/chunk/oversized_blocks/MixinChunkSection.java @@ -0,0 +1,64 @@ +package me.jellysquid.mods.lithium.mixin.chunk.oversized_blocks; + +import me.jellysquid.mods.lithium.common.entity.movement.BlockCollisionSweeper; +import net.minecraft.block.BlockState; +import net.minecraft.fluid.FluidState; +import net.minecraft.world.chunk.ChunkSection; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +import org.spongepowered.asm.mixin.injection.callback.LocalCapture; + +/** + * Keep track of how many oversized blocks are in this chunk section. If none are there, collision code can skip a few blocks. + * Oversided blocks are fences, walls, extended piston heads and blocks with dynamic bounds (scaffolding, shulker box, moving blocks) + * @author 2No2Name + */ +@Mixin(ChunkSection.class) +public class MixinChunkSection implements BlockCollisionSweeper.HasOversizedBlocks { + private short oversizedBlockCount; + + //inject into lambda in calculateCounts + //method name can be figured out by looking at the bytecode of ChunkSection: + //INVOKEDYNAMIC accept(Lnet/minecraft/world/chunk/ChunkSection;)Lnet/minecraft/world/chunk/PalettedContainer$CountConsumer; [ + // // handle kind 0x6 : INVOKESTATIC + // java/lang/invoke/LambdaMetafactory.metafactory(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite; + // // arguments: + // (Ljava/lang/Object;I)V, !! HERE !! + // // handle kind 0x7 : INVOKESPECIAL VVVVVVVVVVVVV + // net/minecraft/world/chunk/ChunkSection.method_21731(Lnet/minecraft/block/BlockState;I)V, <-------------HERE + // (Lnet/minecraft/block/BlockState;I)V + // ] + @Inject(method = "method_21731", at = @At(value = "INVOKE", target = "Lnet/minecraft/block/AbstractBlock$AbstractBlockState;hasRandomTicks()Z", shift = At.Shift.BEFORE), locals = LocalCapture.CAPTURE_FAILHARD) + private void addToOversizedBlockCount(BlockState blockState, int i, CallbackInfo ci, FluidState fluidState) { + if (blockState.exceedsCube()) { + this.oversizedBlockCount = (short)(this.oversizedBlockCount + i); + } + } + + @Inject(method = "calculateCounts", at = @At("HEAD")) + private void resetOversizedBlockCount(CallbackInfo ci) { + this.oversizedBlockCount = 0; + } + + @Inject(method = "setBlockState(IIILnet/minecraft/block/BlockState;Z)Lnet/minecraft/block/BlockState;", at = @At(ordinal = 0, value = "INVOKE", target = "Lnet/minecraft/block/BlockState;hasRandomTicks()Z", shift = At.Shift.BEFORE), locals = LocalCapture.PRINT) + private void decrOversizedBlockCount(int x, int y, int z, BlockState state, boolean lock, CallbackInfoReturnable cir) { + if (state.exceedsCube()) { + this.oversizedBlockCount--; + } + } + + @Inject(method = "setBlockState(IIILnet/minecraft/block/BlockState;Z)Lnet/minecraft/block/BlockState;", at = @At(ordinal = 1, value = "INVOKE", target = "Lnet/minecraft/block/BlockState;hasRandomTicks()Z", shift = At.Shift.BEFORE), locals = LocalCapture.PRINT) + private void incrOversizedBlockCount(int x, int y, int z, BlockState state, boolean lock, CallbackInfoReturnable cir) { + if (state.exceedsCube()) { + this.oversizedBlockCount++; + } + } + + @Override + public boolean hasOversizedBlocks() { + return this.oversizedBlockCount > 0; + } +} diff --git a/src/main/resources/lithium.mixins.json b/src/main/resources/lithium.mixins.json index d8c5cc21c..d27ce7bd9 100644 --- a/src/main/resources/lithium.mixins.json +++ b/src/main/resources/lithium.mixins.json @@ -44,6 +44,7 @@ "block.piston_shapes.PistonHeadBlockMixin", "cached_hashcode.BlockNeighborGroupMixin", "chunk.no_locking.PalettedContainerMixin", + "chunk.oversized_blocks.MixinChunkSection", "chunk.palette.PalettedContainerMixin", "chunk.serialization.PackedIntegerArrayMixin", "chunk.serialization.PalettedContainerMixin", From 1825eb25ef9312d406bb68d11892f30d8371cf48 Mon Sep 17 00:00:00 2001 From: 2No2Name <2No2Name@web.de> Date: Thu, 21 May 2020 16:28:23 +0200 Subject: [PATCH 2/3] Add a chunk section aware BlockCollisionSweeper replacement --- .../entity/LithiumEntityCollisions.java | 27 +- .../movement/BlockCollisionSweeper.java | 8 - .../ChunkAwareBlockCollisionSweeper.java | 243 ++++++++++++++++++ .../oversized_blocks/MixinChunkSection.java | 43 ++-- 4 files changed, 271 insertions(+), 50 deletions(-) create mode 100644 src/main/java/me/jellysquid/mods/lithium/common/entity/movement/ChunkAwareBlockCollisionSweeper.java diff --git a/src/main/java/me/jellysquid/mods/lithium/common/entity/LithiumEntityCollisions.java b/src/main/java/me/jellysquid/mods/lithium/common/entity/LithiumEntityCollisions.java index 79611dee9..4400ae484 100644 --- a/src/main/java/me/jellysquid/mods/lithium/common/entity/LithiumEntityCollisions.java +++ b/src/main/java/me/jellysquid/mods/lithium/common/entity/LithiumEntityCollisions.java @@ -1,6 +1,6 @@ package me.jellysquid.mods.lithium.common.entity; -import me.jellysquid.mods.lithium.common.entity.movement.BlockCollisionSweeper; +import me.jellysquid.mods.lithium.common.entity.movement.ChunkAwareBlockCollisionSweeper; import me.jellysquid.mods.lithium.common.util.Producer; import net.minecraft.entity.Entity; import net.minecraft.util.math.Box; @@ -32,7 +32,7 @@ public static Stream getBlockCollisions(CollisionView world, Entity return Stream.empty(); } - final BlockCollisionSweeper sweeper = new BlockCollisionSweeper(world, entity, box); + final ChunkAwareBlockCollisionSweeper sweeper = new ChunkAwareBlockCollisionSweeper(world, entity, box); return StreamSupport.stream(new Spliterators.AbstractSpliterator(Long.MAX_VALUE, Spliterator.NONNULL | Spliterator.IMMUTABLE) { private boolean skipWorldBorderCheck = entity == null; @@ -49,14 +49,10 @@ public boolean tryAdvance(Consumer consumer) { } } - while (sweeper.step()) { - VoxelShape shape = sweeper.getCollidedShape(); - - if (shape != null) { - consumer.accept(shape); - - return true; - } + VoxelShape shape = sweeper.step(); + if (shape != null) { + consumer.accept(shape); + return true; } return false; @@ -74,15 +70,10 @@ public static boolean doesBoxCollideWithBlocks(CollisionView world, Entity entit return false; } - final BlockCollisionSweeper sweeper = new BlockCollisionSweeper(world, entity, box); - - while (sweeper.step()) { - if (sweeper.getCollidedShape() != null) { - return true; - } - } + final ChunkAwareBlockCollisionSweeper sweeper = new ChunkAwareBlockCollisionSweeper(world, entity, box); - return false; + VoxelShape shape = sweeper.step(); + return shape != null; } /** diff --git a/src/main/java/me/jellysquid/mods/lithium/common/entity/movement/BlockCollisionSweeper.java b/src/main/java/me/jellysquid/mods/lithium/common/entity/movement/BlockCollisionSweeper.java index c7440caf6..85c615e8f 100644 --- a/src/main/java/me/jellysquid/mods/lithium/common/entity/movement/BlockCollisionSweeper.java +++ b/src/main/java/me/jellysquid/mods/lithium/common/entity/movement/BlockCollisionSweeper.java @@ -14,8 +14,6 @@ import net.minecraft.util.shape.VoxelShapes; import net.minecraft.world.BlockView; import net.minecraft.world.CollisionView; -import net.minecraft.world.World; -import net.minecraft.world.chunk.Chunk; import static me.jellysquid.mods.lithium.common.entity.LithiumEntityCollisions.EPSILON; @@ -75,8 +73,6 @@ public boolean step() { if (chunk == null) { return true; - } else if (World.isHeightInvalid(y) || edgesHit >= 1 && chunk instanceof Chunk && !((HasOversizedBlocks)(((Chunk) chunk).getSectionArray()[y >> 4])).hasOversizedBlocks()) { - return true; } final BlockPos.Mutable mpos = this.mpos; @@ -152,8 +148,4 @@ private static VoxelShape getCollidedShape(Box entityBox, VoxelShape entityShape return null; } - - public interface HasOversizedBlocks { - boolean hasOversizedBlocks(); - } } diff --git a/src/main/java/me/jellysquid/mods/lithium/common/entity/movement/ChunkAwareBlockCollisionSweeper.java b/src/main/java/me/jellysquid/mods/lithium/common/entity/movement/ChunkAwareBlockCollisionSweeper.java new file mode 100644 index 000000000..ab1554724 --- /dev/null +++ b/src/main/java/me/jellysquid/mods/lithium/common/entity/movement/ChunkAwareBlockCollisionSweeper.java @@ -0,0 +1,243 @@ +package me.jellysquid.mods.lithium.common.entity.movement; + +import me.jellysquid.mods.lithium.common.shapes.VoxelShapeExtended; +import net.minecraft.block.BlockState; +import net.minecraft.block.Blocks; +import net.minecraft.block.ShapeContext; +import net.minecraft.entity.Entity; +import net.minecraft.util.function.BooleanBiFunction; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Box; +import net.minecraft.util.math.MathHelper; +import net.minecraft.util.shape.VoxelShape; +import net.minecraft.util.shape.VoxelShapes; +import net.minecraft.world.CollisionView; +import net.minecraft.world.chunk.Chunk; +import net.minecraft.world.chunk.ChunkSection; + +import static me.jellysquid.mods.lithium.common.entity.LithiumEntityCollisions.EPSILON; + +/** + * ChunkAwareBlockCollisionSweeper iterates over blocks in one chunk section at a time. Together with the chunk + * section keeping track of the amount of oversized blocks inside the number of iterations can often be reduced. + */ +public class ChunkAwareBlockCollisionSweeper { + private final BlockPos.Mutable pos = new BlockPos.Mutable(); + + /** + * The collision box being swept through the world. + */ + private final Box box; + + /** + * The VoxelShape of the collision box being swept through the world. + */ + private final VoxelShape shape; + + private final CollisionView view; + private final ShapeContext context; + + //limits of the area without extension for oversized blocks + private final int minX, minY, minZ, maxX, maxY, maxZ; + + //variables prefixed with c refer to the iteration of the currently cached chunk section + private int chunkX, chunkY, chunkZ; + private int cStartX, cStartZ; + private int cEndX, cEndZ; + private int cX, cY, cZ; + + private int cTotalSize; + private int cIterated; + + private boolean sectionOversizedBlocks; + private Chunk cachedChunk; + private ChunkSection cachedChunkSection; + + public ChunkAwareBlockCollisionSweeper(CollisionView view, Entity entity, Box box) { + this.box = box; + this.shape = VoxelShapes.cuboid(box); + this.context = entity == null ? ShapeContext.absent() : ShapeContext.of(entity); + this.view = view; + + this.minX = MathHelper.floor(box.x1 - EPSILON); + this.maxX = MathHelper.floor(box.x2 + EPSILON); + this.minY = MathHelper.clamp((int)(box.y1 - EPSILON),0,255); + this.maxY = MathHelper.clamp((int)(box.y2 + EPSILON),0,255); + this.minZ = MathHelper.floor(box.z1 - EPSILON); + this.maxZ = MathHelper.floor(box.z2 + EPSILON); + + this.chunkX = (this.minX - 1) >> 4; + this.chunkZ = (this.minZ - 1) >> 4; + + this.cIterated = 0; + this.cTotalSize = 0; + + //decrement as first nextSection call will increment it again + this.chunkX--; + } + + private boolean nextSection() { + do { + do { + //find the coordinates of the next section inside the area expanded by 1 block on all sides + //note: this.minX, maxX etc are not expanded, so there are lots of +1 and -1 around. + if (this.cachedChunk != null && this.chunkY < 15 && this.chunkY < ((this.maxY + 1) >> 4)) { + this.chunkY++; + this.cachedChunkSection = this.cachedChunk.getSectionArray()[this.chunkY]; + } else { + this.chunkY = MathHelper.clamp((this.minY - 1) >> 4, 0, 15); + + if ((this.chunkX < ((this.maxX + 1) >> 4))) { + //first initialization takes this branch + this.chunkX++; + } else { + this.chunkX = (this.minX - 1) >> 4; + + if (this.chunkZ < ((this.maxZ + 1) >> 4)) { + this.chunkZ++; + } else { + return false; //no more sections to iterate + } + } + //Casting to Chunk is not checked, together with other mods this could cause a ClassCastException + this.cachedChunk = (Chunk) this.view.getExistingChunk(this.chunkX, this.chunkZ); + if (this.cachedChunk != null) { + this.cachedChunkSection = this.cachedChunk.getSectionArray()[this.chunkY]; + } + } + //skip empty chunks and empty chunk sections + } while (this.cachedChunk == null || ChunkSection.isEmpty(this.cachedChunkSection)); + + this.sectionOversizedBlocks = hasChunkSectionOversizedBlocks(this.cachedChunk, this.chunkY); + + int sizeExtension = this.sectionOversizedBlocks ? 1 : 0; + + this.cEndX = Math.min(this.maxX + sizeExtension, 15 + (this.chunkX << 4)); + int cEndY = Math.min(this.maxY + sizeExtension, 15 + (this.chunkY << 4)); + this.cEndZ = Math.min(this.maxZ + sizeExtension, 15 + (this.chunkZ << 4)); + + this.cStartX = Math.max(this.minX - sizeExtension, this.chunkX << 4); + int cStartY = Math.max(this.minY - sizeExtension, this.chunkY << 4); + this.cStartZ = Math.max(this.minZ - sizeExtension, this.chunkZ << 4); + this.cX = this.cStartX; + this.cY = cStartY; + this.cZ = this.cStartZ; + + this.cTotalSize = (this.cEndX - this.cStartX + 1) * (cEndY - cStartY + 1) * (this.cEndZ - this.cStartZ + 1); + //skip completely empty section iterations + } while(this.cTotalSize == 0); + this.cIterated = 0; + + return true; + } + + + /** + * Advances the sweep forward until finding a block with a box-colliding VoxelShape + * + * @return null if no VoxelShape is left in the area, otherwise the next VoxelShape + */ + public VoxelShape step() { + while(true) { + if (this.cIterated >= this.cTotalSize) { + if (!this.nextSection()) { + return null; + } + } + this.cIterated++; + + + final int x = this.cX; + final int y = this.cY; + final int z = this.cZ; + + //The iteration order within a chunk section is chosen so that it causes a mostly linear array access in the storage. + //In net.minecraft.world.chunk.PalettedContainer.toIndex x gets the 4 least significant bits, z the 4 above, and y the 4 even higher ones. + //Linearly accessing arrays might be slightly faster than other access patterns. + //This code hasn't been benchmarked in comparison to another access order. + if (this.cX < this.cEndX) { + this.cX++; + } else if (this.cZ < this.cEndZ) { + this.cX = this.cStartX; + this.cZ++; + } else { + this.cX = this.cStartX; + this.cZ = this.cStartZ; + this.cY++; + //stop condition was already checked using this.cIterated at the start of the method + } + + final int edgesHit = this.sectionOversizedBlocks ? 0 : + //using < minX and > maxX instead of <= and >= in vanilla, because minX, maxX are the coordinates + //that were of box that wasn't extended for oversized blocks yet. + (x < this.minX || x > this.maxX ? 1 : 0) + + (y < this.minY || y > this.maxY ? 1 : 0) + + (z < this.minZ || z > this.maxZ ? 1 : 0); + + if (edgesHit == 3) { + continue; + } + + final BlockState state = this.cachedChunkSection.getBlockState(x & 15, y & 15, z & 15); + + if (canInteractWithBlock(state, edgesHit)) { + this.pos.set(x, y, z); + VoxelShape collisionShape = state.getCollisionShape(this.view, this.pos, this.context); + + if (collisionShape != VoxelShapes.empty()) { + VoxelShape collidedShape = getCollidedShape(this.box, this.shape, collisionShape, x, y, z); + if (collidedShape != null) { + return collidedShape; + } + } + } + } + } + + /** + * This is an artifact from vanilla which is used to avoid testing shapes in the extended portion of a volume + * unless they are a shape which exceeds their voxel. Pistons must be special-cased here. + * + * @return True if the shape can be interacted with at the given edge boundary + */ + private static boolean canInteractWithBlock(BlockState state, int edgesHit) { + return (edgesHit != 1 || state.exceedsCube()) && (edgesHit != 2 || state.getBlock() == Blocks.MOVING_PISTON); + } + + /** + * Checks if the {@param entityShape} or {@param entityBox} intersects the given {@param shape} which is translated + * to the given position. This is a very specialized implementation which tries to avoid going through VoxelShape + * for full-cube shapes. + * + * @return A {@link VoxelShape} which contains the shape representing that which was collided with, otherwise null + */ + private static VoxelShape getCollidedShape(Box entityBox, VoxelShape entityShape, VoxelShape shape, int x, int y, int z) { + if (shape instanceof VoxelShapeExtended) { + if (((VoxelShapeExtended) shape).intersects(entityBox, x, y, z)) { + return shape.offset(x, y, z); + } else { + return null; + } + } + + shape = shape.offset(x, y, z); + + if (VoxelShapes.matchesAnywhere(shape, entityShape, BooleanBiFunction.AND)) { + return shape; + } + + return null; + } + + /** + * Checks the cached information whether the {@param chunkY} section of the {@param chunk} has oversized blocks. + * @return Whether there are any oversized blocks in the chunk section. + */ + private static boolean hasChunkSectionOversizedBlocks(Chunk chunk, int chunkY) { + ChunkSection section = chunk.getSectionArray()[chunkY]; + return section != null && ((OversizedBlocksCounter)section).hasOversizedBlocks(); + } + public interface OversizedBlocksCounter { + boolean hasOversizedBlocks(); + } +} diff --git a/src/main/java/me/jellysquid/mods/lithium/mixin/chunk/oversized_blocks/MixinChunkSection.java b/src/main/java/me/jellysquid/mods/lithium/mixin/chunk/oversized_blocks/MixinChunkSection.java index 5ddffbd19..cad61d5c3 100644 --- a/src/main/java/me/jellysquid/mods/lithium/mixin/chunk/oversized_blocks/MixinChunkSection.java +++ b/src/main/java/me/jellysquid/mods/lithium/mixin/chunk/oversized_blocks/MixinChunkSection.java @@ -1,41 +1,36 @@ package me.jellysquid.mods.lithium.mixin.chunk.oversized_blocks; -import me.jellysquid.mods.lithium.common.entity.movement.BlockCollisionSweeper; +import me.jellysquid.mods.lithium.common.entity.movement.ChunkAwareBlockCollisionSweeper; import net.minecraft.block.BlockState; -import net.minecraft.fluid.FluidState; import net.minecraft.world.chunk.ChunkSection; +import net.minecraft.world.chunk.PalettedContainer; import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.Redirect; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; import org.spongepowered.asm.mixin.injection.callback.LocalCapture; /** * Keep track of how many oversized blocks are in this chunk section. If none are there, collision code can skip a few blocks. - * Oversided blocks are fences, walls, extended piston heads and blocks with dynamic bounds (scaffolding, shulker box, moving blocks) + * Oversized blocks are fences, walls, extended piston heads and blocks with dynamic bounds (scaffolding, shulker box, moving blocks) * @author 2No2Name */ @Mixin(ChunkSection.class) -public class MixinChunkSection implements BlockCollisionSweeper.HasOversizedBlocks { +public class MixinChunkSection implements ChunkAwareBlockCollisionSweeper.OversizedBlocksCounter { + @Unique private short oversizedBlockCount; - //inject into lambda in calculateCounts - //method name can be figured out by looking at the bytecode of ChunkSection: - //INVOKEDYNAMIC accept(Lnet/minecraft/world/chunk/ChunkSection;)Lnet/minecraft/world/chunk/PalettedContainer$CountConsumer; [ - // // handle kind 0x6 : INVOKESTATIC - // java/lang/invoke/LambdaMetafactory.metafactory(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite; - // // arguments: - // (Ljava/lang/Object;I)V, !! HERE !! - // // handle kind 0x7 : INVOKESPECIAL VVVVVVVVVVVVV - // net/minecraft/world/chunk/ChunkSection.method_21731(Lnet/minecraft/block/BlockState;I)V, <-------------HERE - // (Lnet/minecraft/block/BlockState;I)V - // ] - @Inject(method = "method_21731", at = @At(value = "INVOKE", target = "Lnet/minecraft/block/AbstractBlock$AbstractBlockState;hasRandomTicks()Z", shift = At.Shift.BEFORE), locals = LocalCapture.CAPTURE_FAILHARD) - private void addToOversizedBlockCount(BlockState blockState, int i, CallbackInfo ci, FluidState fluidState) { - if (blockState.exceedsCube()) { - this.oversizedBlockCount = (short)(this.oversizedBlockCount + i); - } + @Redirect(method = "calculateCounts", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/chunk/PalettedContainer;count(Lnet/minecraft/world/chunk/PalettedContainer$CountConsumer;)V")) + private void addToOversizedBlockCount(PalettedContainer palettedContainer, PalettedContainer.CountConsumer consumer) { + palettedContainer.count((state, count) -> { + consumer.accept(state, count); + if (state.exceedsCube()) { + this.oversizedBlockCount += count; + } + }); } @Inject(method = "calculateCounts", at = @At("HEAD")) @@ -43,17 +38,17 @@ private void resetOversizedBlockCount(CallbackInfo ci) { this.oversizedBlockCount = 0; } - @Inject(method = "setBlockState(IIILnet/minecraft/block/BlockState;Z)Lnet/minecraft/block/BlockState;", at = @At(ordinal = 0, value = "INVOKE", target = "Lnet/minecraft/block/BlockState;hasRandomTicks()Z", shift = At.Shift.BEFORE), locals = LocalCapture.PRINT) + @Inject(method = "setBlockState(IIILnet/minecraft/block/BlockState;Z)Lnet/minecraft/block/BlockState;", at = @At(ordinal = 0, value = "INVOKE", target = "Lnet/minecraft/block/BlockState;hasRandomTicks()Z", shift = At.Shift.BEFORE), locals = LocalCapture.CAPTURE_FAILHARD) private void decrOversizedBlockCount(int x, int y, int z, BlockState state, boolean lock, CallbackInfoReturnable cir) { if (state.exceedsCube()) { - this.oversizedBlockCount--; + --this.oversizedBlockCount; } } - @Inject(method = "setBlockState(IIILnet/minecraft/block/BlockState;Z)Lnet/minecraft/block/BlockState;", at = @At(ordinal = 1, value = "INVOKE", target = "Lnet/minecraft/block/BlockState;hasRandomTicks()Z", shift = At.Shift.BEFORE), locals = LocalCapture.PRINT) + @Inject(method = "setBlockState(IIILnet/minecraft/block/BlockState;Z)Lnet/minecraft/block/BlockState;", at = @At(ordinal = 1, value = "INVOKE", target = "Lnet/minecraft/block/BlockState;hasRandomTicks()Z", shift = At.Shift.BEFORE), locals = LocalCapture.CAPTURE_FAILHARD) private void incrOversizedBlockCount(int x, int y, int z, BlockState state, boolean lock, CallbackInfoReturnable cir) { if (state.exceedsCube()) { - this.oversizedBlockCount++; + ++this.oversizedBlockCount; } } From 00ebdefc757096dae64248a62cf77e0d065aa629 Mon Sep 17 00:00:00 2001 From: 2No2Name <2No2Name@web.de> Date: Sun, 5 Jul 2020 02:48:36 +0200 Subject: [PATCH 3/3] Allow oversized block counting to be disabled without breaking the chunk section aware block collision sweeper. Update variable names --- .../ChunkAwareBlockCollisionSweeper.java | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/src/main/java/me/jellysquid/mods/lithium/common/entity/movement/ChunkAwareBlockCollisionSweeper.java b/src/main/java/me/jellysquid/mods/lithium/common/entity/movement/ChunkAwareBlockCollisionSweeper.java index ab1554724..ea083a564 100644 --- a/src/main/java/me/jellysquid/mods/lithium/common/entity/movement/ChunkAwareBlockCollisionSweeper.java +++ b/src/main/java/me/jellysquid/mods/lithium/common/entity/movement/ChunkAwareBlockCollisionSweeper.java @@ -1,6 +1,6 @@ package me.jellysquid.mods.lithium.common.entity.movement; -import me.jellysquid.mods.lithium.common.shapes.VoxelShapeExtended; +import me.jellysquid.mods.lithium.common.shapes.VoxelShapeCaster; import net.minecraft.block.BlockState; import net.minecraft.block.Blocks; import net.minecraft.block.ShapeContext; @@ -22,6 +22,8 @@ * section keeping track of the amount of oversized blocks inside the number of iterations can often be reduced. */ public class ChunkAwareBlockCollisionSweeper { + private static final boolean OVERSIZED_BLOCK_COUNTING_ENABLED = OversizedBlocksCounter.class.isAssignableFrom(ChunkSection.class); + private final BlockPos.Mutable pos = new BlockPos.Mutable(); /** @@ -59,12 +61,12 @@ public ChunkAwareBlockCollisionSweeper(CollisionView view, Entity entity, Box bo this.context = entity == null ? ShapeContext.absent() : ShapeContext.of(entity); this.view = view; - this.minX = MathHelper.floor(box.x1 - EPSILON); - this.maxX = MathHelper.floor(box.x2 + EPSILON); - this.minY = MathHelper.clamp((int)(box.y1 - EPSILON),0,255); - this.maxY = MathHelper.clamp((int)(box.y2 + EPSILON),0,255); - this.minZ = MathHelper.floor(box.z1 - EPSILON); - this.maxZ = MathHelper.floor(box.z2 + EPSILON); + this.minX = MathHelper.floor(box.minX - EPSILON); + this.maxX = MathHelper.floor(box.maxX + EPSILON); + this.minY = MathHelper.clamp((int)(box.minY - EPSILON),0,255); + this.maxY = MathHelper.clamp((int)(box.maxY + EPSILON),0,255); + this.minZ = MathHelper.floor(box.minZ - EPSILON); + this.maxZ = MathHelper.floor(box.maxZ + EPSILON); this.chunkX = (this.minX - 1) >> 4; this.chunkZ = (this.minZ - 1) >> 4; @@ -212,8 +214,8 @@ private static boolean canInteractWithBlock(BlockState state, int edgesHit) { * @return A {@link VoxelShape} which contains the shape representing that which was collided with, otherwise null */ private static VoxelShape getCollidedShape(Box entityBox, VoxelShape entityShape, VoxelShape shape, int x, int y, int z) { - if (shape instanceof VoxelShapeExtended) { - if (((VoxelShapeExtended) shape).intersects(entityBox, x, y, z)) { + if (shape instanceof VoxelShapeCaster) { + if (((VoxelShapeCaster) shape).intersects(entityBox, x, y, z)) { return shape.offset(x, y, z); } else { return null; @@ -234,8 +236,11 @@ private static VoxelShape getCollidedShape(Box entityBox, VoxelShape entityShape * @return Whether there are any oversized blocks in the chunk section. */ private static boolean hasChunkSectionOversizedBlocks(Chunk chunk, int chunkY) { - ChunkSection section = chunk.getSectionArray()[chunkY]; - return section != null && ((OversizedBlocksCounter)section).hasOversizedBlocks(); + if (OVERSIZED_BLOCK_COUNTING_ENABLED) { + ChunkSection section = chunk.getSectionArray()[chunkY]; + return section != null && ((OversizedBlocksCounter)section).hasOversizedBlocks(); + } + return true; //like vanilla, assume that a chunk section has oversized blocks, when the section mixin isn't loaded } public interface OversizedBlocksCounter { boolean hasOversizedBlocks();