Faster Filtering, Faster Voxel Shapes

This commit is contained in:
Taiyou06
2024-07-12 08:21:07 +03:00
parent a98e77aade
commit dabb5dd159
13 changed files with 541 additions and 43 deletions

View File

@@ -0,0 +1,34 @@
package net.gensokyoreimagined.nitori.access;
import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.level.ChunkHolder;
import net.minecraft.server.level.PlayerMap;
import net.minecraft.server.level.ServerChunkCache;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.util.thread.BlockableEventLoop;
import org.apache.commons.lang3.mutable.MutableObject;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
import org.spongepowered.asm.mixin.gen.Invoker;
@Mixin(ServerChunkCache.class)
public interface IThreadedAnvilChunkStorage {
@Invoker
ChunkHolder invokeGetCurrentChunkHolder(long pos);
@Invoker
ChunkHolder invokeGetChunkHolder(long pos);
@Accessor
ServerLevel getLevel();
@Accessor
PlayerMap getPlayerChunkWatchingManager();
@Accessor
BlockableEventLoop<Runnable> getMainThreadExecutor();
}

View File

@@ -0,0 +1,8 @@
package net.gensokyoreimagined.nitori.access;
public interface ITypeFilterableList {
Object[] getBackingArray();
}

View File

@@ -0,0 +1,33 @@
package net.gensokyoreimagined.nitori.mixin.shapes.precompute_shape_arrays;
import net.minecraft.world.phys.shapes.CubePointRange;
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.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(CubePointRange.class)
public class FractionalDoubleListMixin {
@Shadow
@Final
private int parts;
private double scale;
@Inject(method = "<init>(I)V", at = @At("RETURN"))
public void initScale(int sectionCount, CallbackInfo ci) {
this.scale = 1.0D / this.parts;
}
/**
* @author JellySquid
* @reason Replace division with multiplication
*/
@Overwrite
public double getDouble(int position) {
return position * this.scale;
}
}

View File

@@ -1,38 +1,58 @@
package net.gensokyoreimagined.nitori.mixin.shapes.precompute_shape_arrays;
//import it.unimi.dsi.fastutil.doubles.DoubleList;
//import net.minecraft.core.Direction;
//import net.minecraft.world.phys.shapes.CubePointRange;
//import net.minecraft.world.phys.shapes.CubeVoxelShape;
//import net.minecraft.world.phys.shapes.DiscreteVoxelShape;
//import org.spongepowered.asm.mixin.Mixin;
//import org.spongepowered.asm.mixin.Overwrite;
//import org.spongepowered.asm.mixin.injection.At;
//import org.spongepowered.asm.mixin.injection.Inject;
//import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
//
//@Mixin(CubeVoxelShape.class)
//public class SimpleVoxelShapeMixin {
// private static final Direction.Axis[] AXIS = Direction.Axis.values();
//
// private DoubleList[] parts;
//
// @Inject(method = "<init>", at = @At("RETURN"))
// private void CubeVoxelShape(DiscreteVoxelShape voxels, CallbackInfo ci) {
// this.parts = new DoubleList[AXIS.length];
//
// for (Direction.Axis axis : AXIS) {
// this.parts[axis.ordinal()] = new CubePointRange(voxels.getSize(axis));
// }
// }
//
// /**
// * @author JellySquid
// * @reason Use the cached array
// */
// @Overwrite
// public DoubleList getCoords(Direction.Axis axis) {
// return this.parts[axis.ordinal()];
// }
//
//}
import it.unimi.dsi.fastutil.doubles.DoubleList;
import net.minecraft.core.Direction;
import net.minecraft.world.phys.shapes.CubePointRange;
import net.minecraft.world.phys.shapes.CubeVoxelShape;
import net.minecraft.world.phys.shapes.DiscreteVoxelShape;
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.lang.reflect.Constructor;
@Mixin(CubeVoxelShape.class)
public class SimpleVoxelShapeMixin {
private static final Direction.Axis[] AXIS = Direction.Axis.values();
private DoubleList[] list;
@Unique
private static Constructor<CubePointRange> nitori$cubePointRangeConstructor;
@Unique
private CubePointRange nitori$cubePointRange(int sectionCount) {
try {
if (nitori$cubePointRangeConstructor == null) {
nitori$cubePointRangeConstructor = CubePointRange.class.getDeclaredConstructor(int.class);
nitori$cubePointRangeConstructor.setAccessible(true);
}
return nitori$cubePointRangeConstructor.newInstance(sectionCount);
} catch (Exception ex) {
throw new AssertionError("Failed to find CubePointRange constructor - " + ex.getMessage(), ex);
}
}
@Inject(method = "<init>", at = @At("RETURN"))
private void onConstructed(DiscreteVoxelShape voxels, CallbackInfo ci) {
this.list = new DoubleList[AXIS.length];
for (Direction.Axis axis : AXIS) {
this.list[axis.ordinal()] = nitori$cubePointRange(voxels.getSize(axis));
}
}
/**
* @author JellySquid
* @reason Use the cached array
*/
@Overwrite
public DoubleList getCoords(Direction.Axis axis) {
return this.list[axis.ordinal()];
}
}

View File

@@ -0,0 +1,151 @@
package net.gensokyoreimagined.nitori.mixin.shapes.specialized_shapes;
import it.unimi.dsi.fastutil.doubles.DoubleList;
import net.minecraft.core.AxisCycle;
import net.minecraft.world.phys.AABB;
import net.minecraft.core.Direction;
import net.minecraft.world.phys.shapes.DiscreteVoxelShape;
import net.minecraft.world.phys.shapes.VoxelShape;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;
import org.spongepowered.asm.mixin.Shadow;
/**
* Implement faster methods for determining penetration during collision resolution.
*/
@Mixin(VoxelShape.class)
public abstract class VoxelShapeMixin {
private static final double POSITIVE_EPSILON = +1.0E-7D;
private static final double NEGATIVE_EPSILON = -1.0E-7D;
@Shadow
@Final
public DiscreteVoxelShape shape;
@Shadow
public abstract boolean isEmpty();
@Shadow
protected abstract double get(Direction.Axis axis, int index);
@Shadow
public abstract DoubleList getCoords(Direction.Axis axis);
/**
* @reason Use optimized implementation which delays searching for coordinates as long as possible
* @author JellySquid
*/
@Overwrite
public double collideX(AxisCycle cycleDirection, AABB box, double maxDist) {
if (this.isEmpty()) {
return maxDist;
}
if (Math.abs(maxDist) < POSITIVE_EPSILON) {
return 0.0D;
}
AxisCycle cycle = cycleDirection.inverse();
Direction.Axis axisX = cycle.cycle(Direction.Axis.X);
Direction.Axis axisY = cycle.cycle(Direction.Axis.Y);
Direction.Axis axisZ = cycle.cycle(Direction.Axis.Z);
int minY = Integer.MIN_VALUE;
int maxY = Integer.MIN_VALUE;
int minZ = Integer.MIN_VALUE;
int maxZ = Integer.MIN_VALUE;
int x, y, z;
double dist;
if (maxDist > 0.0D) {
double max = box.max(axisX);
int maxIdx = this.findIndex(axisX, max - POSITIVE_EPSILON);
int maxX = this.shape.getSize(axisX);
for (x = maxIdx + 1; x < maxX; ++x) {
minY = minY == Integer.MIN_VALUE ? Math.max(0, this.findIndex(axisY, box.min(axisY) + POSITIVE_EPSILON)) : minY;
maxY = maxY == Integer.MIN_VALUE ? Math.min(this.shape.getSize(axisY), this.findIndex(axisY, box.max(axisY) - POSITIVE_EPSILON) + 1) : maxY;
for (y = minY; y < maxY; ++y) {
minZ = minZ == Integer.MIN_VALUE ? Math.max(0, this.findIndex(axisZ, box.min(axisZ) + POSITIVE_EPSILON)) : minZ;
maxZ = maxZ == Integer.MIN_VALUE ? Math.min(this.shape.getSize(axisZ), this.findIndex(axisZ, box.max(axisZ) - POSITIVE_EPSILON) + 1) : maxZ;
for (z = minZ; z < maxZ; ++z) {
if (this.shape.isFullWide(cycle, x, y, z)) {
dist = this.get(axisX, x) - max;
if (dist >= NEGATIVE_EPSILON) {
maxDist = Math.min(maxDist, dist);
}
return maxDist;
}
}
}
}
} else if (maxDist < 0.0D) {
double min = box.min(axisX);
int minIdx = this.findIndex(axisX, min + POSITIVE_EPSILON);
for (x = minIdx - 1; x >= 0; --x) {
minY = minY == Integer.MIN_VALUE ? Math.max(0, this.findIndex(axisY, box.min(axisY) + POSITIVE_EPSILON)) : minY;
maxY = maxY == Integer.MIN_VALUE ? Math.min(this.shape.getSize(axisY), this.findIndex(axisY, box.max(axisY) - POSITIVE_EPSILON) + 1) : maxY;
for (y = minY; y < maxY; ++y) {
minZ = minZ == Integer.MIN_VALUE ? Math.max(0, this.findIndex(axisZ, box.min(axisZ) + POSITIVE_EPSILON)) : minZ;
maxZ = maxZ == Integer.MIN_VALUE ? Math.min(this.shape.getSize(axisZ), this.findIndex(axisZ, box.max(axisZ) - POSITIVE_EPSILON) + 1) : maxZ;
for (z = minZ; z < maxZ; ++z) {
if (this.shape.isFullWide(cycle, x, y, z)) {
dist = this.get(axisX, x + 1) - min;
if (dist <= POSITIVE_EPSILON) {
maxDist = Math.max(maxDist, dist);
}
return maxDist;
}
}
}
}
}
return maxDist;
}
/**
* Inlines the lambda passed to MathHelper#binarySearch. Simplifies the implementation very slightly for additional
* speed.
*
* @reason Use faster implementation
* @author JellySquid
*/
@Overwrite
public int findIndex(Direction.Axis axis, double coord) {
DoubleList list = this.getCoords(axis);
int size = this.shape.getSize(axis);
int start = 0;
int end = size + 1 - start;
while (end > 0) {
int middle = end / 2;
int idx = start + middle;
if (idx >= 0 && (idx > size || coord < list.getDouble(idx))) {
end = middle;
} else {
start = idx + 1;
end -= middle + 1;
}
}
return start - 1;
}
}

View File

@@ -0,0 +1,113 @@
package net.gensokyoreimagined.nitori.mixin.shapes.specialized_shapes;
//TODO: Later
//import me.jellysquid.mods.lithium.common.shapes.VoxelShapeAlignedCuboid;
//import me.jellysquid.mods.lithium.common.shapes.VoxelShapeEmpty;
//import me.jellysquid.mods.lithium.common.shapes.VoxelShapeSimpleCube;
//import net.minecraft.world.phys.shapes.BitSetDiscreteVoxelShape;
//import net.minecraft.world.phys.shapes.DiscreteVoxelShape;
//import net.minecraft.world.phys.shapes.VoxelShape;
//import net.minecraft.world.phys.shapes.Shapes;
//import org.spongepowered.asm.mixin.*;
//
///**
// * Shape specialization allows us to optimize comparison logic by guaranteeing certain constraints about the
// * configuration of vertices in a given shape. For example, most block shapes consist of only one cuboid and by
// * nature, only one voxel. This fact can be taken advantage of to create an optimized implementation which avoids
// * scanning over voxels as there are only ever two given vertices in the shape, allowing simple math operations to be
// * used for determining intersection and penetration.
// * <p>
// * In most cases, comparison logic is rather simple as the game often only deals with empty shapes or simple cubes.
// * Specialization provides a significant speed-up to entity collision resolution and various other parts of the game
// * without needing invasive patches, as we can simply replace the types returned by this class. Modern processors
// * (along with the help of the potent JVM) make the cost of dynamic dispatch negligible when compared to the execution
// * times of shape comparison methods.
// */
//@Mixin(Shapes.class)
//public abstract class VoxelShapesMixin {
// @Mutable
// @Shadow
// @Final
// public static final VoxelShape INFINITY;
//
// @Mutable
// @Shadow
// @Final
// private static final VoxelShape BLOCK;
//
// @Mutable
// @Shadow
// @Final
// private static final VoxelShape EMPTY;
//
// private static final DiscreteVoxelShape FULL_CUBE_VOXELS;
//
// // Re-initialize the global cached shapes with our specialized ones. This will happen right after all the static
// // state has been initialized and before any external classes access it.
// static {
// // [VanillaCopy] The FULL_CUBE and UNBOUNDED shape is initialized with a single 1x1x1 voxel as neither will
// // contain multiple inner cuboids.
// FULL_CUBE_VOXELS = new BitSetDiscreteVoxelShape(1, 1, 1);
// FULL_CUBE_VOXELS.fill(0, 0, 0);
//
// // Used in some rare cases to indicate a shape which encompasses the entire world (such as a moving world border)
// INFINITY = new VoxelShapeSimpleCube(FULL_CUBE_VOXELS, Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY,
// Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY);
//
// // Represents a full-block cube shape, such as that for a dirt block.
// BLOCK = new VoxelShapeSimpleCube(FULL_CUBE_VOXELS, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0);
//
// // Represents an empty cube shape with no vertices that cannot be collided with.
// EMPTY = new VoxelShapeEmpty(new BitSetDiscreteVoxelShape(0, 0, 0));
// }
//
// /**
// * Vanilla implements some very complex logic in this function in order to allow entity boxes to be used in
// * collision resolution the same way as block shapes. The specialized simple cube shape however can trivially
// * represent these cases with nothing more than the two vertexes. This provides a modest speed up for entity
// * collision code by allowing them to also use our optimized shapes.
// * <p>
// * Vanilla uses different kinds of VoxelShapes depending on the size and position of the box.
// * A box that isn't aligned with 1/8th of a block will become a very simple ArrayVoxelShape, while others
// * will become a "SimpleVoxelShape" with a BitSetVoxelSet that possibly has a higher resolution (1-3 bits) per axis.
// * <p>
// * Shapes that have a high resolution (e.g. extended piston base has 2 bits on one axis) have collision
// * layers inside them. An upwards extended piston base has extra collision boxes at 0.25 and 0.5 height.
// * Slabs don't have extra collision boxes, because they are only as high as the smallest height that is possible
// * with their bit resolution (1, so half a block).
// *
// * @reason Use our optimized shape types
// * @author JellySquid, 2No2Name
// */
// @Overwrite
// public static VoxelShape cuboidUnchecked(double minX, double minY, double minZ, double maxX, double maxY, double maxZ) {
// if (maxX - minX < 1.0E-7D || maxY - minY < 1.0E-7D || maxZ - minZ < 1.0E-7D) {
// return EMPTY;
// }
//
// int xRes;
// int yRes;
// int zRes;
// //findRequiredBitResolution(...) looks unnecessarily slow, and it seems to unintentionally return -1 on inputs like -1e-8,
// //A faster implementation is not in the scope of this mixin.
//
// //Description of what vanilla does:
// //If the VoxelShape cannot be represented by a BitSet with 3 bit resolution on any axis (BitSetVoxelSet),
// //a shape without boxes inside will be used in vanilla (ArrayVoxelShape with only 2 PointPositions on each axis)
//
// if ((xRes = Shapes.findBits(minX, maxX)) < 0 ||
// (yRes = Shapes.findBits(minY, maxY)) < 0 ||
// (zRes = Shapes.findBits(minZ, maxZ)) < 0) {
// //vanilla uses ArrayVoxelShape here without any rounding of the coordinates
// return new VoxelShapeSimpleCube(FULL_CUBE_VOXELS, minX, minY, minZ, maxX, maxY, maxZ);
// } else {
// if (xRes == 0 && yRes == 0 && zRes == 0) {
// return BLOCK;
// }
// // vanilla would use a SimpleVoxelShape with a BitSetVoxelSet of resolution of xRes, yRes, zRes here, we match its behavior
// return new VoxelShapeAlignedCuboid(Math.round(minX * 8D) / 8D, Math.round(minY * 8D) / 8D, Math.round(minZ * 8D) / 8D,
// Math.round(maxX * 8D) / 8D, Math.round(maxY * 8D) / 8D, Math.round(maxZ * 8D) / 8D, xRes, yRes, zRes);
// }
// }
//}

View File

@@ -0,0 +1,34 @@
package net.gensokyoreimagined.nitori.mixin.vmp.entity.move_zero_velocity;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.MoverType;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
import org.spongepowered.asm.mixin.Mixin;
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;
@Mixin(Entity.class)
public class MixinEntity {
@Shadow private AABB bb;
@Unique
private boolean boundingBoxChanged = false;
@Inject(method = "move", at = @At("HEAD"), cancellable = true)
private void onMove(MoverType movementType, Vec3 movement, CallbackInfo ci) {
if (!boundingBoxChanged && movement.equals(Vec3.ZERO)) {
ci.cancel();
boundingBoxChanged = false;
}
}
@Inject(method = "setBoundingBox", at = @At("HEAD"))
private void onBoundingBoxChanged(AABB boundingBox, CallbackInfo ci) {
if (!this.bb.equals(boundingBox)) boundingBoxChanged = true;
}
}

View File

@@ -0,0 +1,76 @@
package net.gensokyoreimagined.nitori.mixin.vmp.general.collections;
import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import net.gensokyoreimagined.nitori.access.ITypeFilterableList;
import net.minecraft.util.ClassInstanceMultiMap;
import org.objectweb.asm.Opcodes;
import org.spongepowered.asm.mixin.*;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
import java.util.*;
@Mixin(value = ClassInstanceMultiMap.class, priority = 1005) // priority compatibility hack for lithium
public abstract class MixinTypeFilterableList<T> extends AbstractCollection<T> implements ITypeFilterableList {
@Mutable
@Shadow @Final private Map<Class<?>, List<T>> byClass;
@Mutable
@Shadow @Final private List<T> allInstances;
@Shadow @Final private Class<T> baseClass;
@Redirect(method = "<init>", at = @At(value = "FIELD", target = "Lnet/minecraft/util/ClassInstanceMultiMap;byClass:Ljava/util/Map;", opcode = Opcodes.PUTFIELD))
private void redirectSetElementsByType(ClassInstanceMultiMap<T> instance, Map<Class<?>, List<T>> value) {
this.byClass = new Object2ObjectLinkedOpenHashMap<>();
}
@Redirect(method = "<init>", at = @At(value = "FIELD", target = "Lnet/minecraft/util/ClassInstanceMultiMap;allInstances:Ljava/util/List;", opcode = Opcodes.PUTFIELD))
private void redirectSetAllElements(ClassInstanceMultiMap<T> instance, List<T> value) {
this.allInstances = new ObjectArrayList<>();
}
@Redirect(method = "<init>", at = @At(value = "INVOKE", target = "Lcom/google/common/collect/Maps;newHashMap()Ljava/util/HashMap;", remap = false))
private HashMap<?, ?> redirectNewHashMap() {
return null; // avoid unnecessary alloc
}
@Redirect(method = "<init>", at = @At(value = "INVOKE", target = "Lcom/google/common/collect/Lists;newArrayList()Ljava/util/ArrayList;", remap = false))
private ArrayList<?> redirectNewArrayList() {
return null;
}
@Override
public Object[] getBackingArray() {
return ((ObjectArrayList<T>) this.allInstances).elements();
}
/**
* @author ishland
* @reason use fastutil array list for faster iteration & use array for filtering iteration
*/
@Overwrite
public <S> Collection<S> find(Class<S> type) {
List<T> cached = this.byClass.get(type);
if (cached != null) return (Collection<S>) cached;
if (!this.baseClass.isAssignableFrom(type)) {
throw new IllegalArgumentException("Don't know how to search for " + type);
} else {
List<? extends T> list = this.byClass.computeIfAbsent(type,
typeClass -> {
ObjectArrayList<T> ts = new ObjectArrayList<>(this.allInstances.size());
for (Object _allElement : ((ObjectArrayList<T>) this.allInstances).elements()) {
if (typeClass.isInstance(_allElement)) {
ts.add((T) _allElement);
}
}
return ts;
}
);
return (Collection<S>) list;
}
}
}

View File

@@ -1,4 +1,4 @@
package net.gensokyoreimagined.nitori.mixin.playerwatching;
package net.gensokyoreimagined.nitori.mixin.vmp.playerwatching;
import net.minecraft.server.level.ChunkTrackingView;
import org.spongepowered.asm.mixin.Mixin;

View File

@@ -1,4 +1,4 @@
package net.gensokyoreimagined.nitori.mixin.playerwatching;
package net.gensokyoreimagined.nitori.mixin.vmp.playerwatching;
import net.gensokyoreimagined.nitori.common.chunkwatching.PlayerClientVDTracking;
import net.minecraft.server.level.ClientInformation;

View File

@@ -1,4 +1,4 @@
package net.gensokyoreimagined.nitori.mixin.playerwatching.optimize_nearby_player_lookups;
package net.gensokyoreimagined.nitori.mixin.vmp.playerwatching.optimize_nearby_player_lookups;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;

View File

@@ -0,0 +1,23 @@
package net.gensokyoreimagined.nitori.mixin.world.portal_checks;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.portal.PortalShape;
import net.minecraft.world.phys.Vec3;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.EntityDimensions;
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.CallbackInfoReturnable;
@Mixin({PortalShape.class})
public class DisablePortalChecksMixin {
@Inject(
at = {@At("HEAD")},
method = {"findCollisionFreePosition"},
cancellable = true
)
private static void init(Vec3 fallback, ServerLevel world, Entity entity, EntityDimensions dimensions, CallbackInfoReturnable<Vec3> cir) {
cir.setReturnValue(fallback);
}
}

View File

@@ -58,11 +58,16 @@
"math.vec.FastMathVec3DMixin",
"network.microopt.VarIntsMixin",
"network.microopt.StringEncodingMixin",
"playerwatching.MixinChunkFilter",
"playerwatching.MixinServerPlayerEntity",
"playerwatching.optimize_nearby_player_lookups.MixinMobEntity",
"vmp.playerwatching.MixinChunkFilter",
"vmp.playerwatching.MixinServerPlayerEntity",
"vmp.playerwatching.optimize_nearby_player_lookups.MixinMobEntity",
"vmp.general.collections.MixinTypeFilterableList",
"vmp.entity.move_zero_velocity.MixinEntity",
"shapes.blockstate_cache.BlockMixin",
"shapes.lazy_shape_context.EntityShapeContextMixin",
"shapes.precompute_shape_arrays.FractionalDoubleListMixin",
"shapes.precompute_shape_arrays.SimpleVoxelShapeMixin",
"shapes.specialized_shapes.VoxelShapeMixin",
"util.MixinLevelBlockEntityRetrieval",
"util.accessors.ClientEntityManagerAccessor",
"util.accessors.EntityTrackingSectionAccessor",
@@ -75,6 +80,7 @@
"world.block_entity_ticking.sleeping.furnace.AbstractFurnaceBlockEntityMixin",
"world.block_entity_ticking.support_cache.BlockEntityMixin",
"world.block_entity_ticking.support_cache.DirectBlockEntityTickInvokerMixin",
"world.block_entity_ticking.support_cache.WorldChunkMixin"
"world.block_entity_ticking.support_cache.WorldChunkMixin",
"world.portal_checks.DisablePortalChecksMixin"
]
}