Faster Filtering, Faster Voxel Shapes
This commit is contained in:
@@ -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();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package net.gensokyoreimagined.nitori.access;
|
||||
|
||||
public interface ITypeFilterableList {
|
||||
|
||||
Object[] getBackingArray();
|
||||
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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()];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user