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:
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)) {
|
||||
|
||||
@@ -26,5 +26,4 @@ public interface CollisionBlockState {
|
||||
public VoxelShape getConstantCollisionShape();
|
||||
|
||||
public AABB getConstantCollisionAABB();
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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": [
|
||||
|
||||
Reference in New Issue
Block a user