Fix incorrect specialCollidingBlocks calculation clientside

Need to add a hook to the section read function that only
the client calls to actually count, as the client does not
invoke recalcBlockCounts on its own.

Can't be bothered to document the other changes in this commit.
This commit is contained in:
Spottedleaf
2023-08-20 15:56:24 -07:00
parent d4248d54bb
commit 716342c922
19 changed files with 615 additions and 159 deletions

View File

@@ -11,6 +11,8 @@ 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.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
@Mixin(SimpleBitStorage.class)
public abstract class SimpleBitStorageMixin implements BitStorage {
@@ -27,6 +29,8 @@ public abstract class SimpleBitStorageMixin implements BitStorage {
@Final
private int valuesPerLong;
@Unique
private static final VarHandle LONG_ARRAY_HANDLE = MethodHandles.arrayElementVarHandle(long[].class);
/*
@@ -73,6 +77,7 @@ public abstract class SimpleBitStorageMixin implements BitStorage {
@Override
public int getAndSet(final int index, final int value) {
// assume index/value in range
// note: enforce atomic writes
final long magic = this.magic;
final int bits = this.bits;
final long mul = magic >>> 32;
@@ -86,8 +91,9 @@ public abstract class SimpleBitStorageMixin implements BitStorage {
final int bitIndex = (index - (dataIndex * this.valuesPerLong)) * bits;
final int prev = (int)(data >> bitIndex & mask);
final long write = data & ~(mask << bitIndex) | ((long)value & mask) << bitIndex;
dataArray[dataIndex] = data & ~(mask << bitIndex) | ((long)value & mask) << bitIndex;
LONG_ARRAY_HANDLE.setOpaque(dataArray, dataIndex, write);
return prev;
}
@@ -100,6 +106,8 @@ public abstract class SimpleBitStorageMixin implements BitStorage {
@Override
public void set(final int index, final int value) {
// assume index/value in range
// note: enforce atomic writes
final long magic = this.magic;
final int bits = this.bits;
final long mul = magic >>> 32;
@@ -111,8 +119,9 @@ public abstract class SimpleBitStorageMixin implements BitStorage {
final long mask = (1L << bits) - 1; // avoid extra memory read
final int bitIndex = (index - (dataIndex * this.valuesPerLong)) * bits;
final long write = data & ~(mask << bitIndex) | ((long)value & mask) << bitIndex;
dataArray[dataIndex] = data & ~(mask << bitIndex) | ((long)value & mask) << bitIndex;
LONG_ARRAY_HANDLE.setOpaque(dataArray, dataIndex, write);
}
/**
@@ -123,13 +132,14 @@ public abstract class SimpleBitStorageMixin implements BitStorage {
@Override
public int get(final int index) {
// assume index in range
// note: enforce atomic reads
final long magic = this.magic;
final int bits = this.bits;
final long mul = magic >>> 32;
final int dataIndex = (int)(((long)index * mul) >>> magic);
final long mask = (1L << bits) - 1; // avoid extra memory read
final long data = this.data[dataIndex];
final long data = (long)LONG_ARRAY_HANDLE.getOpaque(this.data, dataIndex);
final int bitIndex = (index - (dataIndex * this.valuesPerLong)) * bits;

View File

@@ -0,0 +1,73 @@
package ca.spottedleaf.moonrise.mixin.chunk_getblock;
import ca.spottedleaf.moonrise.common.util.WorldUtil;
import net.minecraft.core.Holder;
import net.minecraft.core.Registry;
import net.minecraft.util.Mth;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.LevelHeightAccessor;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.biome.BiomeManager;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.LevelChunkSection;
import net.minecraft.world.level.chunk.LightChunk;
import net.minecraft.world.level.chunk.StructureAccess;
import net.minecraft.world.level.chunk.UpgradeData;
import net.minecraft.world.level.levelgen.blending.BlendingData;
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;
@Mixin(ChunkAccess.class)
public abstract class ChunkAccessMixin implements BlockGetter, BiomeManager.NoiseBiomeSource, LightChunk, StructureAccess {
@Shadow
@Final
protected LevelChunkSection[] sections;
@Unique
private int minSection;
@Unique
private int maxSection;
/**
* Initialises the min/max section
*/
@Inject(
method = "<init>",
at = @At("TAIL")
)
public void onConstruct(ChunkPos chunkPos, UpgradeData upgradeData, LevelHeightAccessor levelHeightAccessor, Registry registry, long l, LevelChunkSection[] levelChunkSections, BlendingData blendingData, CallbackInfo ci) {
this.minSection = WorldUtil.getMinSection(levelHeightAccessor);
this.maxSection = WorldUtil.getMaxSection(levelHeightAccessor);
}
/**
* @reason Optimise implementation
* @author Spottedleaf
*/
@Override
@Overwrite
public Holder<Biome> getNoiseBiome(final int biomeX, final int biomeY, final int biomeZ) {
int sectionY = (biomeY >> 2) - this.minSection;
int rel = biomeY & 3;
if (sectionY < 0) {
sectionY = 0;
rel = 0;
} else if (sectionY >= this.sections.length) {
sectionY = this.sections.length - 1;
rel = 3;
}
return this.sections[sectionY].getNoiseBiome(biomeX & 3, rel, biomeZ & 3);
}
}

View File

@@ -1,5 +1,6 @@
package ca.spottedleaf.moonrise.mixin.collisions;
import ca.spottedleaf.moonrise.patches.collisions.CollisionUtil;
import ca.spottedleaf.moonrise.patches.collisions.block.CollisionBlockState;
import ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape;
import com.google.common.collect.ImmutableMap;
@@ -35,7 +36,7 @@ public abstract class BlockStateBaseMixin extends StateHolder<Block, BlockState>
private static final Direction[] DIRECTIONS_CACHED = Direction.values();
@Unique
private static final AtomicInteger ID_GENERATOR = new AtomicInteger(RANDOM_OFFSET);
private static final AtomicInteger ID_GENERATOR = new AtomicInteger();
@Unique
private int id1, id2;
@@ -83,8 +84,8 @@ public abstract class BlockStateBaseMixin extends StateHolder<Block, BlockState>
)
private void init(final CallbackInfo ci) {
// note: murmurHash3 has an inverse, so the field is still unique
this.id1 = HashCommon.murmurHash3(ID_GENERATOR.getAndIncrement());
this.id2 = HashCommon.murmurHash3(ID_GENERATOR.getAndIncrement());
this.id1 = HashCommon.murmurHash3(HashCommon.murmurHash3(ID_GENERATOR.getAndIncrement()) + RANDOM_OFFSET);
this.id2 = HashCommon.murmurHash3(HashCommon.murmurHash3(ID_GENERATOR.getAndIncrement()) + RANDOM_OFFSET);
}
/**

View File

@@ -44,7 +44,7 @@ public abstract class DirectionMixin implements CollisionDirection {
private Quaternionf rotation;
@Unique
private int id = HashCommon.murmurHash3(((Enum)(Object)this).ordinal() + 1);
private int id;
@Unique
private int stepX;
@@ -69,7 +69,7 @@ public abstract class DirectionMixin implements CollisionDirection {
for (final Direction direction : VALUES) {
((DirectionMixin)(Object)direction).opposite = from3DDataValue(((DirectionMixin)(Object)direction).oppositeIndex);
((DirectionMixin)(Object)direction).rotation = ((DirectionMixin)(Object)direction).getRotationUncached();
((DirectionMixin)(Object)direction).id = HashCommon.murmurHash3(direction.ordinal() + RANDOM_OFFSET);
((DirectionMixin)(Object)direction).id = HashCommon.murmurHash3(HashCommon.murmurHash3(direction.ordinal()) + RANDOM_OFFSET);
((DirectionMixin)(Object)direction).stepX = ((DirectionMixin)(Object)direction).normal.getX();
((DirectionMixin)(Object)direction).stepY = ((DirectionMixin)(Object)direction).normal.getY();
((DirectionMixin)(Object)direction).stepZ = ((DirectionMixin)(Object)direction).normal.getZ();

View File

@@ -5,11 +5,13 @@ import ca.spottedleaf.moonrise.patches.collisions.shape.CollisionDiscreteVoxelSh
import net.minecraft.core.Direction;
import net.minecraft.world.phys.shapes.DiscreteVoxelShape;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
@Mixin(DiscreteVoxelShape.class)
public abstract class DiscreteVoxelShapeMixin implements CollisionDiscreteVoxelShape {
// ignore race conditions here: the shape is static, so it doesn't matter
@Unique
private CachedShapeData cachedShapeData;
@Override
@@ -31,12 +33,13 @@ public abstract class DiscreteVoxelShapeMixin implements CollisionDiscreteVoxelS
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 * sizeZ * sizeY;
final int index = z + y*sizeZ + x*mulX;
voxelSet[index >>> 6] |= 1L << index;
}

View File

@@ -1,11 +1,12 @@
package ca.spottedleaf.moonrise.mixin.collisions;
import ca.spottedleaf.moonrise.patches.collisions.CollisionUtil;
import ca.spottedleaf.moonrise.patches.collisions.world.BlockCounter;
import ca.spottedleaf.moonrise.patches.collisions.world.CollisionLevelChunkSection;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.LevelChunkSection;
import net.minecraft.world.level.chunk.Palette;
import net.minecraft.world.level.chunk.PalettedContainer;
import net.minecraft.world.level.material.FluidState;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;
@@ -13,7 +14,9 @@ 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 org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.function.Predicate;
@Mixin(LevelChunkSection.class)
public abstract class LevelChunkSectionMixin implements CollisionLevelChunkSection {
@@ -31,6 +34,9 @@ public abstract class LevelChunkSectionMixin implements CollisionLevelChunkSecti
@Shadow
private short tickingFluidCount;
@Shadow
public abstract boolean maybeHas(Predicate<BlockState> predicate);
@Unique
private int specialCollidingBlocks;
@@ -59,14 +65,56 @@ public abstract class LevelChunkSectionMixin implements CollisionLevelChunkSecti
*/
@Overwrite
public void recalcBlockCounts() {
final BlockCounter counter = new BlockCounter();
// reset, then recalculate
this.nonEmptyBlockCount = (short)0;
this.tickingBlockCount = (short)0;
this.tickingFluidCount = (short)0;
this.specialCollidingBlocks = (short)0;
this.states.count(counter);
if (this.maybeHas((final BlockState state) -> !state.isAir())) {
final PalettedContainer.Data<BlockState> data = this.states.data;
final Palette<BlockState> palette = data.palette;
this.nonEmptyBlockCount = (short)counter.nonEmptyBlockCount;
this.tickingBlockCount = (short)counter.tickingBlockCount;
this.tickingFluidCount = (short)counter.tickingFluidCount;
this.specialCollidingBlocks = (short)counter.specialCollidingBlocks;
data.storage.getAll((final int paletteIdx) -> {
final BlockState state = palette.valueFor(paletteIdx);
if (state.isAir()) {
return;
}
if (CollisionUtil.isSpecialCollidingBlock(state)) {
++this.specialCollidingBlocks;
}
this.nonEmptyBlockCount += 1;
if (state.isRandomlyTicking()) {
this.tickingBlockCount += 1;
}
final FluidState fluid = state.getFluidState();
if (!fluid.isEmpty()) {
//this.nonEmptyBlockCount += count; // fix vanilla bug: make non empty block count correct
if (fluid.isRandomlyTicking()) {
this.tickingFluidCount += 1;
}
}
});
}
}
/**
* @reason recalcBlockCounts is not called on the client, so we need to insert the call somewhere for our collision
* state caches
* @author Spottedleaf
*/
@Inject(
method = "read",
at = @At(
value = "RETURN"
)
)
private void callRecalcBlocksClient(final CallbackInfo ci) {
this.recalcBlockCounts();
}
@Override

View File

@@ -549,37 +549,6 @@ public abstract class VoxelShapeMixin implements CollisionVoxelShape {
return AABB.clip(((VoxelShape)(Object)this).toAabbs(), from, to, offset);
}
@Unique
private boolean multiAABBClips(final double fromX, final double fromY, final double fromZ,
final double directionInvX, final double directionInvY, final double directionInvZ,
final double tMax) {
final List<AABB> aabbs = this.toAabbs();
for (int i = 0, len = aabbs.size(); i < len; ++i) {
final AABB box = aabbs.get(i);
if (CollisionUtil.clips(box, fromX, fromY, fromZ, directionInvX, directionInvY, directionInvZ, tMax)) {
return true;
}
}
return false;
}
@Override
public boolean doesClip(final double fromX, final double fromY, final double fromZ,
final double directionInvX, final double directionInvY, final double directionInvZ,
final double tMax) {
if (this.isEmpty) {
return false;
}
final AABB singleAABB = this.singleAABBRepresentation;
if (singleAABB != null) {
return CollisionUtil.clips(singleAABB, fromX, fromY, fromZ, directionInvX, directionInvY, directionInvZ, tMax);
}
return this.multiAABBClips(fromX, fromY, fromZ, directionInvX, directionInvY, directionInvZ, tMax);
}
/**
* @reason Cache bounds
* @author Spottedleaf

View File

@@ -70,7 +70,7 @@ public abstract class FlowingFluidMixin extends Fluid {
@Shadow
public static short getCacheKey(BlockPos blockPos, BlockPos blockPos2) {
private static short getCacheKey(BlockPos blockPos, BlockPos blockPos2) {
return (short)0;
}
@@ -453,6 +453,11 @@ public abstract class FlowingFluidMixin extends Fluid {
directionsWithout.remove(direction);
except[direction.ordinal()] = directionsWithout.toArray(new Direction[0]);
}
for (int i = 0; i < except.length; ++i) {
if (except[i] == null) {
except[i] = HORIZONTAL_ARRAY;
}
}
HORIZONTAL_EXCEPT = except;
}

View File

@@ -47,6 +47,9 @@ public abstract class FluidStateMixin extends StateHolder<Fluid, FluidState> imp
@Unique
private float ownHeight;
@Unique
private boolean isRandomlyTicking;
@Unique
private BlockState legacyBlock;
@@ -68,6 +71,7 @@ public abstract class FluidStateMixin extends StateHolder<Fluid, FluidState> imp
this.isEmpty = this.getType().isEmpty();
this.isSource = this.getType().isSource((FluidState)(Object)this);
this.ownHeight = this.getType().getOwnHeight((FluidState)(Object)this);
this.isRandomlyTicking = this.getType().isRandomlyTicking();
if (this.getType() instanceof EmptyFluid) {
this.classification = FluidClassification.EMPTY;
@@ -140,6 +144,15 @@ public abstract class FluidStateMixin extends StateHolder<Fluid, FluidState> imp
return this.legacyBlock = this.getType().createLegacyBlock((FluidState)(Object)this);
}
/**
* @reason Use cached result, avoiding indirection
* @author Spottedleaf
*/
@Overwrite
public boolean isRandomlyTicking() {
return this.isRandomlyTicking;
}
@Override
public final FluidClassification getClassification() {
return this.classification;

View File

@@ -1,27 +1,40 @@
package ca.spottedleaf.moonrise.mixin.util_thread_counts;
import com.google.common.util.concurrent.MoreExecutors;
import net.minecraft.Util;
import net.minecraft.util.Mth;
import org.slf4j.Logger;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.Overwrite;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
@Mixin(Util.class)
public abstract class UtilMixin {
/**
* @reason Don't over-allocate executor threads, they may choke the rest of the game
* @author Spottedleaf
*/
@Redirect(
method = "makeExecutor",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/util/Mth;clamp(III)I"
)
)
private static int correctThreadCounts(int value, final int min, final int max) {
@Shadow
@Final
static Logger LOGGER;
@Shadow
private static int getMaxThreads() {
return 0;
}
@Shadow
private static void onThreadException(Thread thread, Throwable throwable) {
}
@Unique
private static int getThreadCounts(final int min, final int max) {
final int cpus = Runtime.getRuntime().availableProcessors() / 2;
final int value;
if (cpus <= 4) {
value = cpus <= 2 ? 1 : 2;
} else if (cpus <= 8) {
@@ -33,4 +46,36 @@ public abstract class UtilMixin {
return Mth.clamp(value, min, max);
}
/**
* @reason Don't over-allocate executor threads, they may choke the rest of the game
* Additionally, use a thread pool with a fixed core pool count. This is so that thread-locals are
* not lost due to some timeout, as G1GC has some issues cleaning up those.
* @author Spottedleaf
*/
@Overwrite
private static ExecutorService makeExecutor(final String name) {
final int threads = getThreadCounts(1, getMaxThreads());
if (threads <= 0) {
return MoreExecutors.newDirectExecutorService();
}
final AtomicInteger workerCount = new AtomicInteger();
return Executors.newFixedThreadPool(threads, (final Runnable run) -> {
final Thread ret = new Thread(run);
ret.setName("Worker-" + name + "-" + workerCount.getAndIncrement());
ret.setUncaughtExceptionHandler((final Thread thread, final Throwable thr) -> {
LOGGER.error(thread.getName() + "died", thr);
onThreadException(thread, thr);
});
ret.setDaemon(true); // forkjoin workers are daemon
ret.setPriority(Thread.NORM_PRIORITY - 1); // de-prioritise over main threads (render/server)
return ret;
});
}
}

View File

@@ -0,0 +1,94 @@
package ca.spottedleaf.moonrise.mixin.util_threading_detector;
import ca.spottedleaf.concurrentutil.util.ConcurrentUtil;
import net.minecraft.util.ThreadingDetector;
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.Redirect;
import java.lang.invoke.VarHandle;
import java.util.concurrent.Semaphore;
import java.util.concurrent.locks.ReentrantLock;
@Mixin(ThreadingDetector.class)
public abstract class ThreadingDetectorMixin {
@Shadow
@Final
private String name;
@Unique
private static final ReentrantLock CANT_USE_NULL_IN_NEW_REDIRECT_MIXIN_WHAT_THE_FUCK_REENTRANTLOCK = new ReentrantLock();
@Unique
private static final Semaphore CANT_USE_NULL_IN_NEW_REDIRECT_MIXIN_WHAT_THE_FUCK_SEMAPHORE = new Semaphore(1);
/**
* @reason The lock is unused after changes in this mixin
* @author Spottedleaf
*/
@Redirect(
method = "<init>",
at = @At(
value = "NEW",
target = "()Ljava/util/concurrent/locks/ReentrantLock;"
)
)
private static ReentrantLock nullLock() {
return CANT_USE_NULL_IN_NEW_REDIRECT_MIXIN_WHAT_THE_FUCK_REENTRANTLOCK;
}
/**
* @reason The semaphore is unused after changes in this mixin
* @author Spottedleaf
*/
@Redirect(
method = "<init>",
at = @At(
value = "NEW",
target = "(I)Ljava/util/concurrent/Semaphore;"
)
)
private static Semaphore nullSemaphore(final int p1) {
return CANT_USE_NULL_IN_NEW_REDIRECT_MIXIN_WHAT_THE_FUCK_SEMAPHORE;
}
// why bother with all of that locking crap when a single CAS works?
// no unique to prevent renames
private volatile Thread moonriseCurrThread;
@Unique
private static final VarHandle CURR_THREAD_HANDLE = ConcurrentUtil.getVarHandle(ThreadingDetector.class, "moonriseCurrThread", Thread.class);
/**
* @reason Replace with optimised version
* @author Spottedleaf
*/
@Overwrite
public void checkAndLock() {
final Thread prev = (Thread)CURR_THREAD_HANDLE.compareAndExchange((ThreadingDetector)(Object)this, (Thread)null, (Thread)Thread.currentThread());
if (prev != null) {
throw ThreadingDetector.makeThreadingException(this.name, prev);
}
}
/**
* @reason Replace with optimised version
* @author Spottedleaf
*/
@Overwrite
public void checkAndUnlock() {
final Thread expect = Thread.currentThread();
final Thread prev = (Thread)CURR_THREAD_HANDLE.compareAndExchange((ThreadingDetector)(Object)this, expect, (Thread)null);
if (prev != expect) {
throw ThreadingDetector.makeThreadingException(this.name, prev);
}
}
}

View File

@@ -1,6 +1,5 @@
package ca.spottedleaf.moonrise.patches.collisions;
import ca.spottedleaf.moonrise.common.util.WorldUtil;
import ca.spottedleaf.moonrise.patches.collisions.block.CollisionBlockState;
import ca.spottedleaf.moonrise.patches.collisions.entity.CollisionEntity;
import ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData;
@@ -13,9 +12,6 @@ import it.unimi.dsi.fastutil.doubles.DoubleArrayList;
import it.unimi.dsi.fastutil.doubles.DoubleList;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.server.level.ServerChunkCache;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.WorldGenRegion;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.item.Item;
@@ -26,12 +22,12 @@ import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.border.WorldBorder;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.ChunkSource;
import net.minecraft.world.level.chunk.ChunkStatus;
import net.minecraft.world.level.chunk.LevelChunkSection;
import net.minecraft.world.level.chunk.PalettedContainer;
import net.minecraft.world.level.material.FluidState;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.Vec3;
import net.minecraft.world.phys.shapes.ArrayVoxelShape;
import net.minecraft.world.phys.shapes.BitSetDiscreteVoxelShape;
@@ -1643,47 +1639,6 @@ public final class CollisionUtil {
return x > y ? x : y;
}
/**
* @param box Bounding volume to check
* @param fromX Starting x-coordinate of ray
* @param fromY Starting y-coordinate of ray
* @param fromZ Starting z-coordinate of ray
* @param directionInvX 1.0 / ray x-direction
* @param directionInvY 1.0 / ray y-direction
* @param directionInvZ 1.0 / ray z-direction
* @param tMax Maximum distance along ray direction that the ray can clip
*/
public static boolean clips(final AABB box,
final double fromX, final double fromY, final double fromZ,
final double directionInvX, final double directionInvY, final double directionInvZ,
double tMax) {
/* https://tavianator.com/2022/ray_box_boundary.html */
double tMin = 0.0;
double t1, t2;
t1 = (box.minX - fromX) * directionInvX;
t2 = (box.maxX - fromX) * directionInvX;
tMin = min(max(t1, tMin), max(t2, tMin));
tMax = max(min(t1, tMax), min(t2, tMax));
t1 = (box.minY - fromY) * directionInvY;
t2 = (box.maxY - fromY) * directionInvY;
tMin = min(max(t1, tMin), max(t2, tMin));
tMax = max(min(t1, tMax), min(t2, tMax));
t1 = (box.minZ - fromZ) * directionInvZ;
t2 = (box.maxZ - fromZ) * directionInvZ;
tMin = min(max(t1, tMin), max(t2, tMin));
tMax = max(min(t1, tMax), min(t2, tMax));
return tMin <= tMax;
}
public static final int COLLISION_FLAG_LOAD_CHUNKS = 1 << 0;
public static final int COLLISION_FLAG_COLLIDE_WITH_UNLOADED_CHUNKS = 1 << 1;
public static final int COLLISION_FLAG_CHECK_BORDER = 1 << 2;
@@ -1700,7 +1655,6 @@ public final class CollisionUtil {
if (checkOnly) {
return true;
} else {
// the collision shape is basically always voxel, don't check it
final VoxelShape borderShape = world.getWorldBorder().getCollisionShape();
intoVoxel.add(borderShape);
ret = true;
@@ -1738,10 +1692,11 @@ public final class CollisionUtil {
final int maxChunkZ = maxBlockZ >> 4;
final boolean loadChunks = (collisionFlags & COLLISION_FLAG_LOAD_CHUNKS) != 0;
final ChunkSource chunkSource = world.getChunkSource();
for (int currChunkZ = minChunkZ; currChunkZ <= maxChunkZ; ++currChunkZ) {
for (int currChunkX = minChunkX; currChunkX <= maxChunkX; ++currChunkX) {
final ChunkAccess chunk = (ChunkAccess)world.getChunk(currChunkX, currChunkZ, ChunkStatus.FULL, loadChunks);
final ChunkAccess chunk = chunkSource.getChunk(currChunkX, currChunkZ, ChunkStatus.FULL, loadChunks);
if (chunk == null) {
if ((collisionFlags & COLLISION_FLAG_COLLIDE_WITH_UNLOADED_CHUNKS) != 0) {
@@ -1768,11 +1723,12 @@ public final class CollisionUtil {
// empty
continue;
}
final PalettedContainer<BlockState> blocks = section.states;
final boolean hasSpecial = ((CollisionLevelChunkSection)section).getSpecialCollidingBlocks() != 0;
final int sectionAdjust = !hasSpecial ? 1 : 0;
final PalettedContainer<BlockState> blocks = section.states;
final int minXIterate = currChunkX == minChunkX ? (minBlockX & 15) + sectionAdjust : 0;
final int maxXIterate = currChunkX == maxChunkX ? (maxBlockX & 15) - sectionAdjust : 15;
final int minZIterate = currChunkZ == minChunkZ ? (minBlockZ & 15) + sectionAdjust : 0;
@@ -1809,10 +1765,6 @@ public final class CollisionUtil {
blockCollision = blockData.getCollisionShape(world, mutablePos, collisionShape);
}
if (blockCollision.isEmpty()) {
continue;
}
AABB singleAABB = ((CollisionVoxelShape)blockCollision).getSingleAABBRepresentation();
if (singleAABB != null) {
singleAABB = singleAABB.move((double)blockX, (double)blockY, (double)blockZ);
@@ -1836,6 +1788,10 @@ public final class CollisionUtil {
}
}
if (blockCollision.isEmpty()) {
continue;
}
final VoxelShape blockCollisionOffset = blockCollision.move((double)blockX, (double)blockY, (double)blockZ);
if (!voxelShapeIntersectNoEmpty(blockCollisionOffset, aabb)) {

View File

@@ -26,5 +26,4 @@ public interface CollisionBlockState {
public VoxelShape getConstantCollisionShape();
public AABB getConstantCollisionAABB();
}

View File

@@ -33,10 +33,6 @@ public interface CollisionVoxelShape {
public boolean isFullBlock();
public boolean doesClip(final double fromX, final double fromY, final double fromZ,
final double directionInvX, final double directionInvY, final double directionInvZ,
final double tMax);
public boolean occludesFullBlock();
public boolean occludesFullBlockIfCached();

View File

@@ -1,38 +0,0 @@
package ca.spottedleaf.moonrise.patches.collisions.world;
import ca.spottedleaf.moonrise.patches.collisions.CollisionUtil;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.PalettedContainer;
import net.minecraft.world.level.material.FluidState;
public final class BlockCounter implements PalettedContainer.CountConsumer<BlockState> {
public int nonEmptyBlockCount;
public int tickingBlockCount;
public int tickingFluidCount;
public int specialCollidingBlocks;
@Override
public void accept(final BlockState state, final int count) {
// our logic
if (CollisionUtil.isSpecialCollidingBlock(state)) {
this.specialCollidingBlocks += count;
}
// Vanilla logic
if (!state.isAir()) {
this.nonEmptyBlockCount += count;
if (state.isRandomlyTicking()) {
this.tickingBlockCount += count;
}
}
final FluidState fluid = state.getFluidState();
if (!fluid.isEmpty()) {
//this.nonEmptyBlockCount += i; // fix vanilla bug: make non empty block count correct
if (fluid.isRandomlyTicking()) {
this.tickingFluidCount += count;
}
}
}
}

View File

@@ -1,5 +1,7 @@
package ca.spottedleaf.moonrise.patches.collisions.world;
import net.minecraft.world.level.block.state.BlockState;
public interface CollisionLevelChunkSection {
public int getSpecialCollidingBlocks();

View File

@@ -0,0 +1,265 @@
package ca.spottedleaf.moonrise.patches.render;
import net.minecraft.client.renderer.chunk.VisibilitySet;
import net.minecraft.core.Direction;
import java.util.Arrays;
public final class VisibilityGraph {
private static final int SECTION_WIDTH = 16;
private static final int LOG2_SECTION_WIDTH = 4;
private static final long LOG2_LONG = 6;
private static final long[] X_SET_FACES = new long[(SECTION_WIDTH*SECTION_WIDTH*SECTION_WIDTH + (Long.SIZE - 1)) >>> LOG2_LONG];
private static final int[] DIRECTIONS_BITSET_BY_INDEX = new int[SECTION_WIDTH*SECTION_WIDTH*SECTION_WIDTH];
private static final int ALL_DIRECTIONS_BITSET = 0b111_111;
private static final int TOTAL_BLOCKS_FACES;
static {
int set = 0;
for (int y = 0; y < SECTION_WIDTH; ++y) {
for (int z = 0; z < SECTION_WIDTH; ++z) {
for (int x = 0; x < SECTION_WIDTH; ++x) {
if (x != 0 && x != (SECTION_WIDTH - 1) && y != 0 && y != (SECTION_WIDTH - 1) && z != 0 && z != (SECTION_WIDTH - 1)) {
continue;
}
final int idx = x | (z << LOG2_SECTION_WIDTH) | (y << (LOG2_SECTION_WIDTH+LOG2_SECTION_WIDTH));
final int bitsetIndex = idx >>> LOG2_LONG;
X_SET_FACES[bitsetIndex] |= (1L << idx);
++set;
int bitset = 0;
if (x == 0) {
bitset |= (1 << Direction.WEST.ordinal());
}
if (x == 15) {
bitset |= (1 << Direction.EAST.ordinal());
}
if (y == 0) {
bitset |= (1 << Direction.DOWN.ordinal());
}
if (y == 15) {
bitset |= (1 << Direction.UP.ordinal());
}
if (z == 0) {
bitset |= (1 << Direction.NORTH.ordinal());
}
if (z == 15) {
bitset |= (1 << Direction.SOUTH.ordinal());
}
DIRECTIONS_BITSET_BY_INDEX[idx] = bitset;
}
}
}
TOTAL_BLOCKS_FACES = set;
}
private static final int[] INDEX_ADD_BY_DIRECTION_ORDINAL = new int[Direction.values().length];
private static final int[] OPPOSITE_BITSET_BY_ORIDINAL = new int[Direction.values().length];
static {
for (final Direction direction : Direction.values()) {
final int x = Math.abs(direction.getStepX());
final int y = Math.abs(direction.getStepY());
final int z = Math.abs(direction.getStepZ());
int value = x | (z << LOG2_SECTION_WIDTH) | (y << (LOG2_SECTION_WIDTH+LOG2_SECTION_WIDTH));
if (direction.getAxisDirection() == Direction.AxisDirection.NEGATIVE) {
value = -value;
}
INDEX_ADD_BY_DIRECTION_ORDINAL[direction.ordinal()] = value;
OPPOSITE_BITSET_BY_ORIDINAL[direction.ordinal()] = 1 << direction.getOpposite().ordinal();
}
}
// idx = (axis1 & 15) | ((axis2 & 15) << 4) | ((axis3 & 15) << (4+4))
private static final long BLOCKS_PER_LONG = Long.SIZE / SECTION_WIDTH;
private static final int BITSET_SIZE = (SECTION_WIDTH*SECTION_WIDTH*SECTION_WIDTH + (Long.SIZE - 1)) >>> LOG2_LONG;
// x z y
private final long[] opaque = new long[BITSET_SIZE];
private static final int TOTAL_DIRECTIONS = 6;
private int opaqueEdgeCount = 0;
// lower 12 bits: block index
// next 6 bits: propagation direction bitset
private int[] bfsQueue = new int[BITSET_SIZE * 4];
// assume (x,y,z) in [0,SECTION_WIDTH-1],[0,SECTION_WIDTH-1],[0,SECTION_WIDTH-1]
// assume that setOpaque for (x,y,z) has not been called since last reset()
public void setOpaque(final int x, final int y, final int z) {
final int idx = x | (z << LOG2_SECTION_WIDTH) | (y << (LOG2_SECTION_WIDTH+LOG2_SECTION_WIDTH));;
final int bitsetIdx = idx >>> LOG2_LONG;
final long bitsetMask = 1L << idx;
this.opaque[bitsetIdx] |= bitsetMask;
this.opaqueEdgeCount += Long.bitCount(X_SET_FACES[bitsetIdx] & bitsetMask);
}
public void reset() {
if (this.opaqueEdgeCount != 0L) {
Arrays.fill(this.opaque, 0L);
this.opaqueEdgeCount = 0;
}
}
private static final long EXCLUDING_EDGES = -1L ^
(
(1L << (0L * 16L)) |
(1L << (1L * 16L)) |
(1L << (2L * 16L)) |
(1L << (3L * 16L)) |
(1L << (15L + 0L * 16L)) |
(1L << (15L + 1L * 16L)) |
(1L << (15L + 2L * 16L)) |
(1L << (15L + 3L * 16L))
);
private static final long EXCLUDING_LEFT_EDGES = -1L ^
(
(1L << (0L * 16L)) |
(1L << (1L * 16L)) |
(1L << (2L * 16L)) |
(1L << (3L * 16L))
);
private static final long EXCLUDING_RIGHT_EDGES = -1L ^
(
(1L << (15L + 0L * 16L)) |
(1L << (15L + 1L * 16L)) |
(1L << (15L + 2L * 16L)) |
(1L << (15L + 3L * 16L))
);
private static long maskMoveNeighbours(long from, long neighbour) {
from = ~from;
neighbour = ~neighbour;
// from/neighbour is a bitset where 1 is transparent
final long leftMask = (EXCLUDING_LEFT_EDGES << 1) & neighbour;
final long rightMask = (EXCLUDING_RIGHT_EDGES >>> 1) & neighbour;
long ret = from & neighbour;
for (int i = 0; i < (16 - 1); ++i) {
final long left = (ret << 1) & leftMask;
final long right = (ret >>> 1) & rightMask;
ret = left | right;
}
return ~ret;
}
private final int[] resizeBFSQueue() {
return this.bfsQueue = Arrays.copyOf(this.bfsQueue, this.bfsQueue.length * 2);
}
public VisibilitySet findFaces() {
final VisibilitySet ret = new VisibilitySet();
if (this.opaqueEdgeCount < (SECTION_WIDTH*SECTION_WIDTH)) {
// impossible for one face to be fully opaque
ret.setAll(true);
return ret;
}
if (this.opaqueEdgeCount >= (TOTAL_BLOCKS_FACES - 1)) {
// impossible for there to be any visibility from one edge to another
ret.setAll(false);
return ret;
}
final long[] opaque = this.opaque;
int[] queue = this.bfsQueue;
for (int faceBitsetIndex = 0; faceBitsetIndex < 64; ++faceBitsetIndex) {
final long faceMask = X_SET_FACES[faceBitsetIndex];
for (;;) {
final long value = opaque[faceBitsetIndex];
final long valueMask = ~value & faceMask; // bitset of transparent blocks on the edge
if (valueMask == 0L) {
break;
}
int setIndex = Long.numberOfTrailingZeros(valueMask);
// mark visited
opaque[faceBitsetIndex] = value | (1L << setIndex);
setIndex |= (faceBitsetIndex << 6);
int directionBitset = DIRECTIONS_BITSET_BY_INDEX[setIndex];
int bfsIndex = 0;
int queued = 1;
queue[0] = setIndex | (directionBitset << (LOG2_SECTION_WIDTH+LOG2_SECTION_WIDTH+LOG2_SECTION_WIDTH));
while (bfsIndex < queued) {
final int queuedValue = queue[bfsIndex++];
// retrieve bits NOT set in the bitset, as this is where we want to propagate (AND it prevents us from propagating out of bounds)
int negatedBitset = (~queuedValue & (ALL_DIRECTIONS_BITSET << (LOG2_SECTION_WIDTH+LOG2_SECTION_WIDTH+LOG2_SECTION_WIDTH))) >>> (LOG2_SECTION_WIDTH+LOG2_SECTION_WIDTH+LOG2_SECTION_WIDTH);
final int queuedIndex = queuedValue & (SECTION_WIDTH*SECTION_WIDTH*SECTION_WIDTH - 1);
// propagate to neighbours
// improve branch prediction by using bitCount compared to while (bitset != 0)
for (int i = 0, len = Integer.bitCount(negatedBitset); i < len; ++i) {
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];
final long neighbourMask = 1L << neighbourIndex;
final long newBitsetValue = bitsetValue | neighbourMask;
if (newBitsetValue == bitsetValue) {
continue;
}
final int opposite = OPPOSITE_BITSET_BY_ORIDINAL[directionOrdinal];
directionBitset |= directionBitsetForNeighbour;
opaque[neighbourIndex >>> LOG2_LONG] = newBitsetValue;
if (queued >= queue.length) {
queue = this.resizeBFSQueue();
}
queue[queued++] = neighbourIndex | ((directionBitsetForNeighbour | opposite) << (LOG2_SECTION_WIDTH+LOG2_SECTION_WIDTH+LOG2_SECTION_WIDTH));
}
}
int iter1 = directionBitset;
for (int i = 0, ilen = Integer.bitCount(directionBitset); i < ilen; ++i) {
final int first = Integer.numberOfTrailingZeros(iter1);
iter1 ^= (-iter1 & iter1);
int iter2 = directionBitset;
for (int k = 0, klen = Integer.bitCount(directionBitset); k < klen; ++k) {
final int second = Integer.numberOfTrailingZeros(iter2);
iter2 ^= (-iter2 & iter2);
ret.data.set(first + (second * VisibilitySet.FACINGS));
ret.data.set(second + (first * VisibilitySet.FACINGS));
}
}
}
}
return ret;
}
}

View File

@@ -12,6 +12,13 @@ accessible field net/minecraft/world/level/chunk/LevelChunkSection states Lnet/m
# PalettedContainer
accessible method net/minecraft/world/level/chunk/PalettedContainer get (I)Ljava/lang/Object;
accessible field net/minecraft/world/level/chunk/PalettedContainer data Lnet/minecraft/world/level/chunk/PalettedContainer$Data;
# PalettedContainer.Data
accessible class net/minecraft/world/level/chunk/PalettedContainer$Data
accessible field net/minecraft/world/level/chunk/PalettedContainer$Data storage Lnet/minecraft/util/BitStorage;
accessible field net/minecraft/world/level/chunk/PalettedContainer$Data palette Lnet/minecraft/world/level/chunk/Palette;
# ChunkMap
@@ -107,4 +114,10 @@ accessible field net/minecraft/world/level/ClipContext fluid Lnet/minecraft/worl
# Fluid
accessible method net/minecraft/world/level/material/Fluid isEmpty ()Z
accessible method net/minecraft/world/level/material/Fluid createLegacyBlock (Lnet/minecraft/world/level/material/FluidState;)Lnet/minecraft/world/level/block/state/BlockState;
accessible method net/minecraft/world/level/material/Fluid createLegacyBlock (Lnet/minecraft/world/level/material/FluidState;)Lnet/minecraft/world/level/block/state/BlockState;
accessible method net/minecraft/world/level/material/Fluid isRandomlyTicking ()Z
# VisibilitySet
accessible field net/minecraft/client/renderer/chunk/VisibilitySet FACINGS I
accessible field net/minecraft/client/renderer/chunk/VisibilitySet data Ljava/util/BitSet;

View File

@@ -6,6 +6,7 @@
"mixins": [
"bitstorage.SimpleBitStorageMixin",
"bitstorage.ZeroBitStorageMixin",
"chunk_getblock.ChunkAccessMixin",
"chunk_getblock.LevelChunkMixin",
"collisions.ArmorStandMixin",
"collisions.ArrayVoxelShapeMixin",
@@ -51,6 +52,7 @@
"starlight.world.LevelMixin",
"starlight.world.ServerWorldMixin",
"util_thread_counts.UtilMixin",
"util_threading_detector.ThreadingDetectorMixin",
"util_time_source.UtilMixin"
],
"client": [