Add ZeroCollidingReferenceStateTable for StateHolder property operations

This patch optimises property updating optimisations for StateHolder
operations.

98ae59d859

Additionally, implement some basic optimisations to some BlockPos
operations.

9c4490bda4
This commit is contained in:
Spottedleaf
2023-09-09 05:55:43 -07:00
parent c4aa0cc0aa
commit a1e89e9515
11 changed files with 635 additions and 23 deletions

View File

@@ -86,10 +86,11 @@ public abstract class SimpleBitStorageMixin implements BitStorage {
final long[] dataArray = this.data;
final long data = dataArray[dataIndex];
final int valuesPerLong = this.valuesPerLong;
final long mask = (1L << bits) - 1; // avoid extra memory read
final int bitIndex = (index - (dataIndex * this.valuesPerLong)) * bits;
final int bitIndex = (index - (dataIndex * valuesPerLong)) * bits;
final int prev = (int)(data >> bitIndex & mask);
final long write = data & ~(mask << bitIndex) | ((long)value & mask) << bitIndex;
@@ -116,9 +117,10 @@ public abstract class SimpleBitStorageMixin implements BitStorage {
final long[] dataArray = this.data;
final long data = dataArray[dataIndex];
final int valuesPerLong = this.valuesPerLong;
final long mask = (1L << bits) - 1; // avoid extra memory read
final int bitIndex = (index - (dataIndex * this.valuesPerLong)) * bits;
final int bitIndex = (index - (dataIndex * valuesPerLong)) * bits;
final long write = data & ~(mask << bitIndex) | ((long)value & mask) << bitIndex;
LONG_ARRAY_HANDLE.setOpaque(dataArray, dataIndex, write);
@@ -140,8 +142,9 @@ public abstract class SimpleBitStorageMixin implements BitStorage {
final long mask = (1L << bits) - 1; // avoid extra memory read
final long data = (long)LONG_ARRAY_HANDLE.getOpaque(this.data, dataIndex);
final int valuesPerLong = this.valuesPerLong;
final int bitIndex = (index - (dataIndex * this.valuesPerLong)) * bits;
final int bitIndex = (index - (dataIndex * valuesPerLong)) * bits;
return (int)(data >> bitIndex & mask);
}

View File

@@ -0,0 +1,29 @@
package ca.spottedleaf.moonrise.mixin.blockstate_propertyaccess;
import ca.spottedleaf.moonrise.patches.blockstate_propertyaccess.PropertyAccess;
import net.minecraft.world.level.block.state.properties.BooleanProperty;
import net.minecraft.world.level.block.state.properties.Property;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;
@Mixin(BooleanProperty.class)
public abstract class BooleanPropertyMixin extends Property<Boolean> implements PropertyAccess<Boolean> {
protected BooleanPropertyMixin(String string, Class<Boolean> class_) {
super(string, class_);
}
/**
* @reason Properties are identity comparable
* @author Spottedleaf
*/
@Overwrite
@Override
public boolean equals(final Object obj) {
return this == obj;
}
@Override
public final int getIdFor(final Boolean value) {
return value.booleanValue() ? 1 : 0;
}
}

View File

@@ -0,0 +1,57 @@
package ca.spottedleaf.moonrise.mixin.blockstate_propertyaccess;
import ca.spottedleaf.moonrise.patches.blockstate_propertyaccess.PropertyAccess;
import net.minecraft.util.StringRepresentable;
import net.minecraft.world.level.block.state.properties.EnumProperty;
import net.minecraft.world.level.block.state.properties.Property;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;
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.callback.CallbackInfo;
@Mixin(EnumProperty.class)
public abstract class EnumPropertyMixin<T extends Enum<T> & StringRepresentable> extends Property<T> implements PropertyAccess<T> {
@Unique
private int[] idLookupTable;
protected EnumPropertyMixin(String string, Class<T> class_) {
super(string, class_);
}
/**
* @reason Hook into constructor to init fields
* @author Spottedleaf
*/
@Inject(
method = "<init>",
at = @At(
value = "RETURN"
)
)
private void init(final CallbackInfo ci) {
int id = 0;
this.idLookupTable = new int[getValueClass().getEnumConstants().length];
java.util.Arrays.fill(this.idLookupTable, -1);
for (final T value : this.getPossibleValues()) {
this.idLookupTable[value.ordinal()] = id++;
}
}
/**
* @reason Properties are identity comparable
* @author Spottedleaf
*/
@Overwrite
@Override
public boolean equals(final Object obj) {
return this == obj;
}
@Override
public final int getIdFor(final T value) {
return this.idLookupTable[value.ordinal()];
}
}

View File

@@ -0,0 +1,43 @@
package ca.spottedleaf.moonrise.mixin.blockstate_propertyaccess;
import ca.spottedleaf.moonrise.patches.blockstate_propertyaccess.PropertyAccess;
import net.minecraft.world.level.block.state.properties.IntegerProperty;
import net.minecraft.world.level.block.state.properties.Property;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;
import org.spongepowered.asm.mixin.Shadow;
@Mixin(IntegerProperty.class)
public abstract class IntegerPropertyMixin extends Property<Integer> implements PropertyAccess<Integer> {
@Shadow
@Final
private int min;
@Shadow
@Final
private int max;
protected IntegerPropertyMixin(String string, Class<Integer> class_) {
super(string, class_);
}
/**
* @reason Properties are identity comparable
* @author Spottedleaf
*/
@Overwrite
@Override
public boolean equals(final Object obj) {
return this == obj;
}
@Override
public final int getIdFor(final Integer value) {
final int val = value.intValue();
final int ret = val - this.min;
return ret | ((this.max - ret) >> 31);
}
}

View File

@@ -0,0 +1,54 @@
package ca.spottedleaf.moonrise.mixin.blockstate_propertyaccess;
import ca.spottedleaf.moonrise.patches.blockstate_propertyaccess.PropertyAccess;
import net.minecraft.world.level.block.state.properties.Property;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;
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.callback.CallbackInfo;
import java.util.concurrent.atomic.AtomicInteger;
@Mixin(Property.class)
public abstract class PropertyMixin<T extends Comparable<T>> implements PropertyAccess<T> {
@Unique
private static final AtomicInteger ID_GENERATOR = new AtomicInteger();
@Unique
private int id;
/**
* @reason Hook into constructor to init fields
* @author Spottedleaf
*/
@Inject(
method = "<init>",
at = @At(
value = "RETURN"
)
)
private void initId(final CallbackInfo ci) {
this.id = ID_GENERATOR.getAndIncrement();
}
@Override
public final int getId() {
return this.id;
}
/**
* @reason Properties are identity comparable
* @author Spottedleaf
*/
@Overwrite
@Override
public boolean equals(final Object obj) {
return this == obj;
}
// this is re-declared here so that calls to Property#getIdFor are a virtual method invoke, rather than an interface invoke
@Override
public abstract int getIdFor(final T value);
}

View File

@@ -0,0 +1,110 @@
package ca.spottedleaf.moonrise.mixin.blockstate_propertyaccess;
import ca.spottedleaf.moonrise.patches.blockstate_propertyaccess.util.ZeroCollidingReferenceStateTable;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Table;
import net.minecraft.world.level.block.state.StateHolder;
import net.minecraft.world.level.block.state.properties.Property;
import org.spongepowered.asm.mixin.Final;
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 org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.Map;
import java.util.Optional;
@Mixin(StateHolder.class)
public abstract class StateHolderMixin<O, S> {
@Shadow
@Final
private ImmutableMap<Property<?>, Comparable<?>> values;
@Shadow
private Table<Property<?>, Comparable<?>, S> neighbours;
@Shadow
@Final
protected O owner;
@Unique
protected ZeroCollidingReferenceStateTable optimisedTable;
/**
* @reason Hook into constructor to init fields
* @author Spottedleaf
*/
@Inject(
method = "<init>",
at = @At(
value = "RETURN"
)
)
private void init(final CallbackInfo ci) {
this.optimisedTable = new ZeroCollidingReferenceStateTable((StateHolder<O, S>)(Object)this, this.values); // Paper - optimise state lookup
}
/**
* @reason Init table for ZCST
* @author Spottedleaf
*/
@Inject(
method = "populateNeighbours",
at = @At(
value = "RETURN"
)
)
private void loadTable(final Map<Map<Property<?>, Comparable<?>>, S> map, final CallbackInfo ci) {
this.optimisedTable.loadInTable((Table<Property<?>, Comparable<?>, StateHolder<?,?>>)this.neighbours, this.values);
}
/**
* @reason Replace with optimisedTable
* @author Spottedleaf
*/
@Overwrite
public <T extends Comparable<T>, V extends T> S setValue(final Property<T> property, final V value) {
final S ret = (S)this.optimisedTable.get(property, value);
if (ret == null) {
throw new IllegalArgumentException("Cannot set property " + property + " to " + value + " on " + this.owner + ", it is not an allowed value");
}
return ret;
}
/**
* @reason Replace with optimisedTable
* @author Spottedleaf
*/
@Overwrite
public <T extends Comparable<T>> Optional<T> getOptionalValue(final Property<T> property) {
final Comparable<?> ret = this.optimisedTable.get(property);
return ret == null ? Optional.empty() : Optional.of((T)ret);
}
/**
* @reason Replace with optimisedTable
* @author Spottedleaf
*/
@Overwrite
public <T extends Comparable<T>> T getValue(final Property<T> property) {
final Comparable<?> ret = this.optimisedTable.get(property);
if (ret == null) {
throw new IllegalArgumentException("Cannot get property " + property + " as it does not exist in " + this.owner);
} else {
return (T)ret;
}
}
/**
* @reason Replace with optimisedTable
* @author Spottedleaf
*/
@Overwrite
public <T extends Comparable<T>> boolean hasProperty(final Property<T> property) {
return this.optimisedTable.get(property) != null; // Paper - optimise state lookup
}
}

View File

@@ -0,0 +1,134 @@
package ca.spottedleaf.moonrise.mixin.collisions;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Vec3i;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;
@Mixin(BlockPos.class)
public abstract class BlockPosMixin extends Vec3i {
public BlockPosMixin(int i, int j, int k) {
super(i, j, k);
}
/**
* @reason https://bugs.mojang.com/browse/MC-136025
* @author Spottedleaf
*/
@Overwrite
@Override
public BlockPos above() {
return new BlockPos(this.getX(), this.getY() + 1, this.getZ());
}
/**
* @reason https://bugs.mojang.com/browse/MC-136025
* @author Spottedleaf
*/
@Overwrite
@Override
public BlockPos above(final int distance) {
return distance == 0 ? (BlockPos)(Object)this : new BlockPos(this.getX(), this.getY() + distance, this.getZ());
}
/**
* @reason https://bugs.mojang.com/browse/MC-136025
* @author Spottedleaf
*/
@Overwrite
@Override
public BlockPos below() {
return new BlockPos(this.getX(), this.getY() - 1, this.getZ());
}
/**
* @reason https://bugs.mojang.com/browse/MC-136025
* @author Spottedleaf
*/
@Overwrite
@Override
public BlockPos below(final int distance) {
return distance == 0 ? (BlockPos)(Object)this : new BlockPos(this.getX(), this.getY() - distance, this.getZ());
}
/**
* @reason https://bugs.mojang.com/browse/MC-136025
* @author Spottedleaf
*/
@Overwrite
@Override
public BlockPos north() {
return new BlockPos(this.getX(), this.getY(), this.getZ() - 1);
}
/**
* @reason https://bugs.mojang.com/browse/MC-136025
* @author Spottedleaf
*/
@Overwrite
@Override
public BlockPos north(final int distance) {
return distance == 0 ? (BlockPos)(Object)this : new BlockPos(this.getX(), this.getY(), this.getZ() - distance);
}
/**
* @reason https://bugs.mojang.com/browse/MC-136025
* @author Spottedleaf
*/
@Overwrite
@Override
public BlockPos south() {
return new BlockPos(this.getX(), this.getY(), this.getZ() + 1);
}
/**
* @reason https://bugs.mojang.com/browse/MC-136025
* @author Spottedleaf
*/
@Overwrite
@Override
public BlockPos south(final int distance) {
return distance == 0 ? (BlockPos)(Object)this : new BlockPos(this.getX(), this.getY(), this.getZ() + distance);
}
/**
* @reason https://bugs.mojang.com/browse/MC-136025
* @author Spottedleaf
*/
@Overwrite
@Override
public BlockPos west() {
return new BlockPos(this.getX() - 1, this.getY(), this.getZ());
}
/**
* @reason https://bugs.mojang.com/browse/MC-136025
* @author Spottedleaf
*/
@Overwrite
@Override
public BlockPos west(final int distance) {
return distance == 0 ? (BlockPos)(Object)this : new BlockPos(this.getX() - distance, this.getY(), this.getZ());
}
/**
* @reason https://bugs.mojang.com/browse/MC-136025
* @author Spottedleaf
*/
@Overwrite
@Override
public BlockPos east() {
return new BlockPos(this.getX() + 1, this.getY(), this.getZ());
}
/**
* @reason https://bugs.mojang.com/browse/MC-136025
* @author Spottedleaf
*/
@Overwrite
@Override
public BlockPos east(final int distance) {
return distance == 0 ? (BlockPos)(Object)this : new BlockPos(this.getX() + distance, this.getY(), this.getZ());
}
}

View File

@@ -4,7 +4,6 @@ import ca.spottedleaf.moonrise.common.util.CoordinateUtils;
import ca.spottedleaf.moonrise.patches.chunk_getblock.GetBlockChunk;
import ca.spottedleaf.moonrise.patches.collisions.CollisionUtil;
import ca.spottedleaf.moonrise.patches.collisions.block.CollisionBlockState;
import ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape;
import ca.spottedleaf.moonrise.patches.explosions.ExplosionBlockCache;
import it.unimi.dsi.fastutil.doubles.DoubleArrayList;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
@@ -357,12 +356,18 @@ public abstract class ExplosionMixin {
this.chunkCache = new LevelChunk[CHUNK_CACHE_WIDTH * CHUNK_CACHE_WIDTH];
ExplosionBlockCache[] blockCache = new ExplosionBlockCache[BLOCK_EXPLOSION_CACHE_WIDTH * BLOCK_EXPLOSION_CACHE_WIDTH * BLOCK_EXPLOSION_CACHE_WIDTH];
// avoid checking for initial state in loop by always defaulting to a position that will fail the first cache check
ExplosionBlockCache initialCache = new ExplosionBlockCache(
BlockPos.containing(this.x, this.y, this.z).above(2).asLong(),
null, null, null, 0f, true
);
ExplosionBlockCache cachedBlock;
// use initial cache value that is most likely to be used: the source position
final ExplosionBlockCache initialCache;
{
final int blockX = Mth.floor(this.x);
final int blockY = Mth.floor(this.y);
final int blockZ = Mth.floor(this.z);
final long key = BlockPos.asLong(blockX, blockY, blockZ);
initialCache = this.getOrCacheExplosionBlock(blockX, blockY, blockZ, key, true);
}
// only ~1/3rd of the loop iterations in vanilla will result in a ray, as it is iterating the perimeter of
// a 16x16x16 cube
@@ -371,24 +376,24 @@ public abstract class ExplosionMixin {
// additional aggressive caching of block retrieval is very significant, as at low power (i.e tnt) most
// block retrievals are not unique
for (int ray = 0, len = CACHED_RAYS.length; ray < len;) {
cachedBlock = initialCache;
ExplosionBlockCache cachedBlock = initialCache;
double currX = this.x;
double currY = this.y;
double currZ = this.z;
double incX = CACHED_RAYS[ray];
double incY = CACHED_RAYS[ray + 1];
double incZ = CACHED_RAYS[ray + 2];
final double incX = CACHED_RAYS[ray];
final double incY = CACHED_RAYS[ray + 1];
final double incZ = CACHED_RAYS[ray + 2];
ray += 3;
float power = this.radius * (0.7F + this.level.random.nextFloat() * 0.6F);
do {
int blockX = Mth.floor(currX);
int blockY = Mth.floor(currY);
int blockZ = Mth.floor(currZ);
final int blockX = Mth.floor(currX);
final int blockY = Mth.floor(currY);
final int blockZ = Mth.floor(currZ);
final long key = BlockPos.asLong(blockX, blockY, blockZ);
@@ -432,13 +437,13 @@ public abstract class ExplosionMixin {
// use null predicate to avoid indirection on test(), but we need to move the spectator check into the loop itself
final List<Entity> entities = this.level.getEntities(this.source,
new AABB(
Mth.floor(this.x - (diameter + 1.0)),
Mth.floor(this.y - (diameter + 1.0)),
Mth.floor(this.z - (diameter + 1.0)),
(double)Mth.floor(this.x - (diameter + 1.0)),
(double)Mth.floor(this.y - (diameter + 1.0)),
(double)Mth.floor(this.z - (diameter + 1.0)),
Mth.floor(this.x + (diameter + 1.0)),
Mth.floor(this.y + (diameter + 1.0)),
Mth.floor(this.z + (diameter + 1.0))
(double)Mth.floor(this.x + (diameter + 1.0)),
(double)Mth.floor(this.y + (diameter + 1.0)),
(double)Mth.floor(this.z + (diameter + 1.0))
),
null
);

View File

@@ -0,0 +1,9 @@
package ca.spottedleaf.moonrise.patches.blockstate_propertyaccess;
public interface PropertyAccess<T> {
public int getId();
public int getIdFor(final T value);
}

View File

@@ -0,0 +1,162 @@
package ca.spottedleaf.moonrise.patches.blockstate_propertyaccess.util;
import ca.spottedleaf.moonrise.patches.blockstate_propertyaccess.PropertyAccess;
import com.google.common.collect.Table;
import net.minecraft.world.level.block.state.StateHolder;
import net.minecraft.world.level.block.state.properties.Property;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
public final class ZeroCollidingReferenceStateTable {
// upper 32 bits: starting index
// lower 32 bits: bitset for contained ids
protected final long[] this_index_table;
protected final Comparable<?>[] this_table;
protected final StateHolder<?, ?> this_state;
protected long[] index_table;
protected StateHolder<?, ?>[][] value_table;
private boolean inited;
public ZeroCollidingReferenceStateTable(final StateHolder<?, ?> state, final Map<Property<?>, Comparable<?>> this_map) {
this.this_state = state;
this.this_index_table = this.create_table(this_map.keySet());
int max_id = -1;
for (final Property<?> property : this_map.keySet()) {
final int id = lookup_vindex(property, this.this_index_table);
if (id > max_id) {
max_id = id;
}
}
this.this_table = new Comparable[max_id + 1];
for (final Map.Entry<Property<?>, Comparable<?>> entry : this_map.entrySet()) {
this.this_table[lookup_vindex(entry.getKey(), this.this_index_table)] = entry.getValue();
}
}
public void loadInTable(final Table<Property<?>, Comparable<?>, StateHolder<?, ?>> table,
final Map<Property<?>, Comparable<?>> this_map) {
if (this.inited) {
throw new IllegalStateException();
}
this.inited = true;
final Set<Property<?>> combined = new HashSet<>(table.rowKeySet());
combined.addAll(this_map.keySet());
this.index_table = this.create_table(combined);
int max_id = -1;
for (final Property<?> property : combined) {
final int id = lookup_vindex(property, this.index_table);
if (id > max_id) {
max_id = id;
}
}
this.value_table = new StateHolder[max_id + 1][];
final Map<Property<?>, Map<Comparable<?>, StateHolder<?, ?>>> map = table.rowMap();
for (final Property<?> property : map.keySet()) {
final Map<Comparable<?>, StateHolder<?, ?>> propertyMap = map.get(property);
final int id = lookup_vindex(property, this.index_table);
final StateHolder<?, ?>[] states = this.value_table[id] = new StateHolder[property.getPossibleValues().size()];
for (final Map.Entry<Comparable<?>, StateHolder<?, ?>> entry : propertyMap.entrySet()) {
if (entry.getValue() == null) {
continue;
}
states[((PropertyAccess)property).getIdFor(entry.getKey())] = entry.getValue();
}
}
for (final Map.Entry<Property<?>, Comparable<?>> entry : this_map.entrySet()) {
final Property<?> property = entry.getKey();
final int index = lookup_vindex(property, this.index_table);
if (this.value_table[index] == null) {
this.value_table[index] = new StateHolder[property.getPossibleValues().size()];
}
this.value_table[index][((PropertyAccess)property).getIdFor(entry.getValue())] = this.this_state;
}
}
protected long[] create_table(final Collection<Property<?>> collection) {
int max_id = -1;
for (final Property<?> property : collection) {
final int id = ((PropertyAccess)property).getId();
if (id > max_id) {
max_id = id;
}
}
final long[] ret = new long[((max_id + 1) + 31) >>> 5]; // ceil((max_id + 1) / 32)
for (final Property<?> property : collection) {
final int id = ((PropertyAccess)property).getId();
ret[id >>> 5] |= (1L << (id & 31));
}
int total = 0;
for (int i = 1, len = ret.length; i < len; ++i) {
ret[i] |= (long)(total += Long.bitCount(ret[i - 1] & 0xFFFFFFFFL)) << 32;
}
return ret;
}
public Comparable<?> get(final Property<?> state) {
final Comparable<?>[] table = this.this_table;
final int index = lookup_vindex(state, this.this_index_table);
if (index < 0 || index >= table.length) {
return null;
}
return table[index];
}
public StateHolder<?, ?> get(final Property<?> property, final Comparable<?> with) {
final int index = lookup_vindex(property, this.index_table);
final StateHolder<?, ?>[][] table = this.value_table;
if (index < 0 || index >= table.length) {
return null;
}
final StateHolder<?, ?>[] values = table[index];
final int withId = ((PropertyAccess)property).getIdFor(with);
if (withId < 0 || withId >= values.length) {
return null;
}
return values[withId];
}
protected static int lookup_vindex(final Property<?> property, final long[] index_table) {
final int id = ((PropertyAccess)property).getId();
final long bitset_mask = (1L << (id & 31));
final long lower_mask = bitset_mask - 1;
final int index = id >>> 5;
if (index >= index_table.length) {
return -1;
}
final long index_value = index_table[index];
final long contains_check = ((index_value & bitset_mask) - 1) >> (Long.SIZE - 1); // -1L if doesn't contain
// index = total bits set in lower table values (upper 32 bits of index_value) plus total bits set in lower indices below id
// contains_check is 0 if the bitset had id set, else it's -1: so index is unaffected if contains_check == 0,
// otherwise it comes out as -1.
return (int)(((index_value >>> 32) + Long.bitCount(index_value & lower_mask)) | contains_check);
}
}

View File

@@ -6,12 +6,18 @@
"mixins": [
"bitstorage.SimpleBitStorageMixin",
"bitstorage.ZeroBitStorageMixin",
"blockstate_propertyaccess.BooleanPropertyMixin",
"blockstate_propertyaccess.EnumPropertyMixin",
"blockstate_propertyaccess.IntegerPropertyMixin",
"blockstate_propertyaccess.PropertyMixin",
"blockstate_propertyaccess.StateHolderMixin",
"chunk_getblock.ChunkAccessMixin",
"chunk_getblock.LevelChunkMixin",
"collisions.ArmorStandMixin",
"collisions.ArrayVoxelShapeMixin",
"collisions.BitSetDiscreteVoxelShapeMixin",
"collisions.BlockMixin",
"collisions.BlockPosMixin",
"collisions.BlockStateBaseMixin",
"collisions.CubeVoxelShapeMixin",
"collisions.DirectionMixin",