Optimise BitSetDiscreteVoxelShape#forAllBoxes

This should speed up general shape joining, as VoxelShape#optimize()
uses this method when performing an uncached toAabbs()
This commit is contained in:
Spottedleaf
2023-09-04 16:14:33 -07:00
parent dc20d1a144
commit 13ce46a086
11 changed files with 278 additions and 20 deletions

View File

@@ -0,0 +1,109 @@
package ca.spottedleaf.moonrise.common.util;
import java.util.Objects;
public final class FlatBitsetUtil {
private static final int LOG2_LONG = 6;
private static final long ALL_SET = -1L;
private static final int BITS_PER_LONG = Long.SIZE;
// from inclusive
// to exclusive
public static int firstSet(final long[] bitset, final int from, final int to) {
if ((from | to | (to - from)) < 0) {
throw new IndexOutOfBoundsException();
}
int bitsetIdx = from >>> LOG2_LONG;
int bitIdx = from & ~(BITS_PER_LONG - 1);
long tmp = bitset[bitsetIdx] & (ALL_SET << from);
for (;;) {
if (tmp != 0L) {
final int ret = bitIdx | Long.numberOfTrailingZeros(tmp);
return ret >= to ? -1 : ret;
}
bitIdx += BITS_PER_LONG;
if (bitIdx >= to) {
return -1;
}
tmp = bitset[++bitsetIdx];
}
}
// from inclusive
// to exclusive
public static int firstClear(final long[] bitset, final int from, final int to) {
if ((from | to | (to - from)) < 0) {
throw new IndexOutOfBoundsException();
}
// like firstSet, but invert the bitset
int bitsetIdx = from >>> LOG2_LONG;
int bitIdx = from & ~(BITS_PER_LONG - 1);
long tmp = (~bitset[bitsetIdx]) & (ALL_SET << from);
for (;;) {
if (tmp != 0L) {
final int ret = bitIdx | Long.numberOfTrailingZeros(tmp);
return ret >= to ? -1 : ret;
}
bitIdx += BITS_PER_LONG;
if (bitIdx >= to) {
return -1;
}
tmp = ~bitset[++bitsetIdx];
}
}
// from inclusive
// to exclusive
public static void clearRange(final long[] bitset, final int from, int to) {
if ((from | to | (to - from)) < 0) {
throw new IndexOutOfBoundsException();
}
if (from == to) {
return;
}
--to;
final int fromBitsetIdx = from >>> LOG2_LONG;
final int toBitsetIdx = to >>> LOG2_LONG;
final long keepFirst = ALL_SET >>> ((BITS_PER_LONG - 1) ^ from);
final long keepLast = ALL_SET << to;
Objects.checkFromToIndex(fromBitsetIdx, toBitsetIdx, bitset.length);
if (fromBitsetIdx == toBitsetIdx) {
// special case: need to keep both first and last
bitset[fromBitsetIdx] &= (keepFirst | keepLast);
} else {
bitset[fromBitsetIdx] &= keepFirst;
for (int i = fromBitsetIdx + 1; i < toBitsetIdx; ++i) {
bitset[toBitsetIdx] = 0L;
}
bitset[toBitsetIdx] &= keepLast;
}
}
// from inclusive
// to exclusive
public static boolean isRangeSet(final long[] bitset, final int from, final int to) {
return firstClear(bitset, from, to) == -1;
}
private FlatBitsetUtil() {}
}

View File

@@ -0,0 +1,10 @@
package ca.spottedleaf.moonrise.common.util;
public final class MixinWorkarounds {
// mixins tries to find the owner of the clone() method, which doesn't exist and NPEs
public static long[] clone(final long[] values) {
return values.clone();
}
}

View File

@@ -0,0 +1,122 @@
package ca.spottedleaf.moonrise.mixin.collisions;
import ca.spottedleaf.moonrise.common.util.FlatBitsetUtil;
import ca.spottedleaf.moonrise.common.util.MixinWorkarounds;
import ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData;
import ca.spottedleaf.moonrise.patches.collisions.shape.CollisionDiscreteVoxelShape;
import net.minecraft.world.phys.shapes.BitSetDiscreteVoxelShape;
import net.minecraft.world.phys.shapes.DiscreteVoxelShape;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;
@Mixin(BitSetDiscreteVoxelShape.class)
public abstract class BitSetDiscreteVoxelShapeMixin extends DiscreteVoxelShape {
protected BitSetDiscreteVoxelShapeMixin(int i, int j, int k) {
super(i, j, k);
}
/**
* @reason avoid creating temporary shape, interact directly with the bitset
* @author Spottedleaf
*/
@Overwrite
public static void forAllBoxes(final DiscreteVoxelShape shape, final DiscreteVoxelShape.IntLineConsumer consumer, final boolean mergeAdjacent) {
// called with the shape of a VoxelShape, so we can expect the cache to exist
final CachedShapeData cache = ((CollisionDiscreteVoxelShape)shape).getOrCreateCachedShapeData();
final int sizeX = cache.sizeX();
final int sizeY = cache.sizeY();
final int sizeZ = cache.sizeZ();
int indexX;
int indexY = 0;
int indexZ;
int incY = sizeZ;
int incX = sizeZ*sizeY;
long[] bitset = cache.voxelSet();
// index = z + y*size_z + x*(size_z*size_y)
if (!mergeAdjacent) {
// due to the odd selection of loop order (which does affect behavior, unfortunately) we can't simply
// increment an index in the Z loop, and have to perform this trash (keeping track of 3 counters) to avoid
// the multiplication
for (int y = 0; y < sizeY; ++y, indexY += incY) {
indexX = indexY;
for (int x = 0; x < sizeX; ++x, indexX += incX) {
indexZ = indexX;
for (int z = 0; z < sizeZ; ++z, ++indexZ) {
if ((bitset[indexZ >>> 6] & (1L << indexZ)) != 0L) {
consumer.consume(x, y, z, x + 1, y + 1, z + 1);
}
}
}
}
} else {
// same notes about loop order as the above
// this branch is actually important to optimise, as it affects uncached toAabbs() (which affects optimize())
// only clone when we may write to it
bitset = MixinWorkarounds.clone(bitset);
for (int y = 0; y < sizeY; ++y, indexY += incY) {
indexX = indexY;
for (int x = 0; x < sizeX; ++x, indexX += incX) {
final int startIndex = indexX;
final int endIndex = indexX + sizeZ;
final int firstSetZ = FlatBitsetUtil.firstSet(bitset, startIndex, endIndex);
if (firstSetZ == -1) {
continue;
}
int lastSetZ = FlatBitsetUtil.firstClear(bitset, firstSetZ, endIndex);
if (lastSetZ == -1) {
lastSetZ = endIndex;
}
FlatBitsetUtil.clearRange(bitset, firstSetZ, lastSetZ);
// try to merge neighbouring on the X axis
int endX = x + 1; // exclusive
for (int neighbourIdxStart = firstSetZ + incX, neighbourIdxEnd = lastSetZ + incX;
endX < sizeX && FlatBitsetUtil.isRangeSet(bitset, neighbourIdxStart, neighbourIdxEnd);
neighbourIdxStart += incX, neighbourIdxEnd += incX) {
++endX;
FlatBitsetUtil.clearRange(bitset, neighbourIdxStart, neighbourIdxEnd);
}
// try to merge neighbouring on the Y axis
int endY; // exclusive
int firstSetZY, lastSetZY;
y_merge:
for (endY = y + 1, firstSetZY = firstSetZ + incY, lastSetZY = lastSetZ + incY; endY < sizeY;
++endY, firstSetZY += incY, lastSetZY += incY) {
// test the whole XZ range
for (int testX = x, start = firstSetZY, end = lastSetZY; testX < endX;
++testX, start += incX, end += incX) {
if (!FlatBitsetUtil.isRangeSet(bitset, start, end)) {
break y_merge;
}
}
// passed, so we can clear it
for (int testX = x, start = firstSetZY, end = lastSetZY; testX < endX;
++testX, start += incX, end += incX) {
FlatBitsetUtil.clearRange(bitset, start, end);
}
}
consumer.consume(x, y, firstSetZ - startIndex, endX, endY, lastSetZ - startIndex);
}
}
}
}
}

View File

@@ -10,7 +10,7 @@ import org.spongepowered.asm.mixin.Overwrite;
public abstract class BlockMixin {
/**
* @reason Replace with an implementation that does not use join AND one that caches the result
* @reason Replace with an implementation that does not use join AND one that caches the result per VoxelShape instance
* @author Spottedleaf
*/
@Overwrite

View File

@@ -3,9 +3,11 @@ package ca.spottedleaf.moonrise.mixin.collisions;
import ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData;
import ca.spottedleaf.moonrise.patches.collisions.shape.CollisionDiscreteVoxelShape;
import net.minecraft.core.Direction;
import net.minecraft.world.phys.shapes.BitSetDiscreteVoxelShape;
import net.minecraft.world.phys.shapes.DiscreteVoxelShape;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import java.util.Arrays;
@Mixin(DiscreteVoxelShape.class)
public abstract class DiscreteVoxelShapeMixin implements CollisionDiscreteVoxelShape {
@@ -29,19 +31,29 @@ public abstract class DiscreteVoxelShapeMixin implements CollisionDiscreteVoxelS
final int maxIndex = sizeX * sizeY * sizeZ; // exclusive
final int longsRequired = (maxIndex + (Long.SIZE - 1)) >>> 6;
final long[] voxelSet = new long[longsRequired];
long[] voxelSet;
final boolean isEmpty = discreteVoxelShape.isEmpty();
if (!isEmpty) {
final int mulX = sizeZ * sizeY;
for (int x = 0; x < sizeX; ++x) {
for (int y = 0; y < sizeY; ++y) {
for (int z = 0; z < sizeZ; ++z) {
if (discreteVoxelShape.isFull(x, y, z)) {
// index = z + y*size_z + x*(size_z*size_y)
final int index = z + y*sizeZ + x*mulX;
voxelSet[index >>> 6] |= 1L << index;
if (discreteVoxelShape instanceof BitSetDiscreteVoxelShape bitsetShape) {
voxelSet = bitsetShape.storage.toLongArray();
if (voxelSet.length < longsRequired) {
// happens when the later long values are 0L, so we need to resize
voxelSet = Arrays.copyOf(voxelSet, longsRequired);
}
} else {
voxelSet = new long[longsRequired];
if (!isEmpty) {
final int mulX = sizeZ * sizeY;
for (int x = 0; x < sizeX; ++x) {
for (int y = 0; y < sizeY; ++y) {
for (int z = 0; z < sizeZ; ++z) {
if (discreteVoxelShape.isFull(x, y, z)) {
// index = z + y*size_z + x*(size_z*size_y)
final int index = z + y * sizeZ + x * mulX;
voxelSet[index >>> 6] |= 1L << index;
}
}
}
}

View File

@@ -421,7 +421,7 @@ public abstract class LevelMixin implements CollisionLevel, CollisionEntityGette
}
/**
* @reason Use optimised merge strategy, avoid streams
* @reason Use optimised OR operator join strategy, avoid streams
* @author Spottedleaf
*/
@Override
@@ -464,7 +464,8 @@ public abstract class LevelMixin implements CollisionLevel, CollisionEntityGette
final VoxelShape joined = Shapes.or(first, rest);
// find free space
final VoxelShape freeSpace = Shapes.join(boundsShape, joined, BooleanOp.ONLY_FIRST);
// don't need optimized join, as closestPointTo uses toAabbs()
final VoxelShape freeSpace = Shapes.joinUnoptimized(boundsShape, joined, BooleanOp.ONLY_FIRST);
return freeSpace.closestPointTo(fromPosition);
}

View File

@@ -168,7 +168,7 @@ public abstract class ShapesMixin {
final VoxelShape first = tmp[i];
final VoxelShape second = tmp[next];
tmp[newSize++] = Shapes.join(first, second, BooleanOp.OR);
tmp[newSize++] = Shapes.or(first, second);
}
}
size = newSize;
@@ -269,10 +269,11 @@ public abstract class ShapesMixin {
final AABB bounds2 = shape2.bounds();
final double minX = Math.min(bounds1.minX, bounds2.minX);
final double maxX = Math.max(bounds1.maxX, bounds2.maxX);
final double minY = Math.min(bounds1.minY, bounds2.minY);
final double maxY = Math.max(bounds1.maxY, bounds2.maxY);
final double minZ = Math.min(bounds1.minZ, bounds2.minZ);
final double maxX = Math.max(bounds1.maxX, bounds2.maxX);
final double maxY = Math.max(bounds1.maxY, bounds2.maxY);
final double maxZ = Math.max(bounds1.maxZ, bounds2.maxZ);
return (minX <= CollisionUtil.COLLISION_EPSILON && maxX >= (1 - CollisionUtil.COLLISION_EPSILON)) &&

View File

@@ -657,6 +657,7 @@ public abstract class VoxelShapeMixin implements CollisionVoxelShape {
}
if (this.singleAABBRepresentation != null) {
// note: the isFullBlock() is fuzzy, and Shapes.create() is also fuzzy which would return block()
return this.isFullBlock() ? Shapes.block() : (VoxelShape)(Object)this;
}

View File

@@ -283,7 +283,7 @@ public abstract class HopperBlockEntityMixin extends RandomizableContainerBlockE
}
if (changed) {
((HopperBlockEntityMixin)(Object)hopperBlockEntity).setCooldown(8);
((HopperBlockEntityMixin)(Object)hopperBlockEntity).setCooldown(HopperBlockEntity.MOVE_ITEM_SPEED);
setChanged(level, blockPos, blockState);
return true;
}

View File

@@ -175,7 +175,7 @@ public final class VisibilityGraph {
ret.setAll(true);
return ret;
}
if (this.opaqueEdgeCount >= (TOTAL_BLOCKS_FACES - 1)) {
if (this.opaqueEdgeCount == TOTAL_BLOCKS_FACES) {
// impossible for there to be any visibility from one edge to another
ret.setAll(false);
return ret;
@@ -218,12 +218,13 @@ public final class VisibilityGraph {
final int directionOrdinal = Integer.numberOfTrailingZeros(negatedBitset);
final int add = INDEX_ADD_BY_DIRECTION_ORDINAL[directionOrdinal];
negatedBitset ^= -negatedBitset & negatedBitset; // remove trailing bit
final int neighbourIndex = add + queuedIndex;
final long bitsetValue = opaque[neighbourIndex >>> LOG2_LONG];
final int directionBitsetForNeighbour = DIRECTIONS_BITSET_BY_INDEX[neighbourIndex];
negatedBitset ^= -negatedBitset & negatedBitset; // remove trailing bit
final long neighbourMask = 1L << neighbourIndex;
final long newBitsetValue = bitsetValue | neighbourMask;

View File

@@ -10,6 +10,7 @@
"chunk_getblock.LevelChunkMixin",
"collisions.ArmorStandMixin",
"collisions.ArrayVoxelShapeMixin",
"collisions.BitSetDiscreteVoxelShapeMixin",
"collisions.BlockMixin",
"collisions.BlockStateBaseMixin",
"collisions.CollisionGetterMixin",