Make Randoms 100x faster and fix VarInts
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
package net.gensokyoreimagined.nitori.common.math.random;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.level.levelgen.BitRandomSource;
|
||||
import net.minecraft.world.level.levelgen.LegacyRandomSource;
|
||||
import net.minecraft.util.RandomSource;
|
||||
import net.minecraft.world.level.levelgen.PositionalRandomFactory;
|
||||
|
||||
import java.util.random.RandomGenerator;
|
||||
import java.util.random.RandomGeneratorFactory;
|
||||
|
||||
public class RandomGeneratorRandom implements BitRandomSource {
|
||||
private static final RandomGeneratorFactory<RandomGenerator.SplittableGenerator> RANDOM_GENERATOR_FACTORY = RandomGeneratorFactory.of("L64X128MixRandom");
|
||||
|
||||
private static final int INT_BITS = 48;
|
||||
private static final long SEED_MASK = 0xFFFFFFFFFFFFL;
|
||||
private static final long MULTIPLIER = 25214903917L;
|
||||
private static final long INCREMENT = 11L;
|
||||
|
||||
private long seed;
|
||||
private RandomGenerator.SplittableGenerator randomGenerator;
|
||||
|
||||
public RandomGeneratorRandom(long seed) {
|
||||
this.seed = seed;
|
||||
this.randomGenerator = RANDOM_GENERATOR_FACTORY.create(seed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RandomSource fork() {
|
||||
return new RandomGeneratorRandom(this.nextLong());
|
||||
}
|
||||
|
||||
@Override
|
||||
public PositionalRandomFactory forkPositional() {
|
||||
return new Splitter(this.nextLong());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSeed(long seed) {
|
||||
this.seed = seed;
|
||||
this.randomGenerator = RANDOM_GENERATOR_FACTORY.create(seed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int next(int bits) {
|
||||
// >>> instead of Mojang's >> fixes MC-239059
|
||||
return (int) ((seed * MULTIPLIER + INCREMENT & SEED_MASK) >>> INT_BITS - bits);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int nextInt() {
|
||||
return randomGenerator.nextInt();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int nextInt(int bound) {
|
||||
return randomGenerator.nextInt(bound);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long nextLong() {
|
||||
return randomGenerator.nextLong();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean nextBoolean() {
|
||||
return randomGenerator.nextBoolean();
|
||||
}
|
||||
|
||||
@Override
|
||||
public float nextFloat() {
|
||||
return randomGenerator.nextFloat();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double nextDouble() {
|
||||
return randomGenerator.nextDouble();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double nextGaussian() {
|
||||
return randomGenerator.nextGaussian();
|
||||
}
|
||||
|
||||
private record Splitter(long seed) implements PositionalRandomFactory {
|
||||
@Override
|
||||
public RandomSource at(int x, int y, int z) {
|
||||
return new RandomGeneratorRandom(Mth.getSeed(x, y, z) ^ this.seed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RandomSource fromHashOf(String seed) {
|
||||
return new RandomGeneratorRandom((long) seed.hashCode() ^ this.seed);
|
||||
}
|
||||
|
||||
/*
|
||||
@Override
|
||||
public Random split(long seed) {
|
||||
return new RandomGeneratorRandom(seed);
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
@Override
|
||||
@VisibleForTesting
|
||||
public void parityConfigString(StringBuilder info) {
|
||||
info.append("RandomGeneratorRandom$Splitter{").append(this.seed).append("}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package net.gensokyoreimagined.nitori.mixin.entity.collisions.unpushable_cramming;
|
||||
|
||||
import com.google.common.base.Predicates;
|
||||
import net.gensokyoreimagined.nitori.common.entity.pushable.EntityPushablePredicate;
|
||||
import net.gensokyoreimagined.nitori.common.world.WorldHelper;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.vehicle.Boat;
|
||||
import net.minecraft.world.phys.AABB;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.entity.EntitySectionStorage;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
@Mixin(Boat.class)
|
||||
public class BoatEntityMixin {
|
||||
@Redirect(
|
||||
method = "tick",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/world/level/Level;getEntities(Lnet/minecraft/world/entity/Entity;Lnet/minecraft/world/phys/AABB;Ljava/util/function/Predicate;)Ljava/util/List;"
|
||||
)
|
||||
)
|
||||
private List<Entity> getOtherPushableEntities(Level world, @Nullable Entity except, AABB box, Predicate<? super Entity> predicate) {
|
||||
//noinspection Guava
|
||||
if (predicate == Predicates.alwaysFalse()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
if (predicate instanceof EntityPushablePredicate<?> entityPushablePredicate) {
|
||||
EntitySectionStorage<Entity> cache = WorldHelper.getEntityCacheOrNull(world);
|
||||
if (cache != null) {
|
||||
//noinspection unchecked
|
||||
return WorldHelper.getPushableEntities(world, cache, except, box, (EntityPushablePredicate<? super Entity>) entityPushablePredicate);
|
||||
}
|
||||
}
|
||||
return world.getEntities(except, box, predicate);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package net.gensokyoreimagined.nitori.mixin.math.random;
|
||||
|
||||
import net.gensokyoreimagined.nitori.common.math.random.RandomGeneratorRandom;
|
||||
import io.netty.util.internal.ThreadLocalRandom;
|
||||
import net.minecraft.util.RandomSource;
|
||||
import net.minecraft.world.level.levelgen.RandomSupport;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
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(RandomSource.class)
|
||||
public interface RandomMixin {
|
||||
@Inject(method = "create(J)Lnet/minecraft/util/RandomSource;", at = @At(value = "HEAD"), cancellable = true)
|
||||
private static void fasterrandom$createInject(long seed, @NotNull CallbackInfoReturnable<RandomSource> cir) {
|
||||
cir.setReturnValue(new RandomGeneratorRandom(seed));
|
||||
}
|
||||
|
||||
@Inject(method = "createNewThreadLocalInstance", at = @At(value = "HEAD"), cancellable = true)
|
||||
private static void fasterrandom$createLocalInject(@NotNull CallbackInfoReturnable<RandomSource> cir) {
|
||||
cir.setReturnValue(new RandomGeneratorRandom(ThreadLocalRandom.current().nextLong()));
|
||||
}
|
||||
|
||||
@Inject(method = "createThreadSafe", at = @At(value = "HEAD"), cancellable = true)
|
||||
private static void fasterrandom$createThreadSafeInject(@NotNull CallbackInfoReturnable<RandomSource> cir) {
|
||||
cir.setReturnValue(new RandomGeneratorRandom(RandomSupport.generateUniqueSeed()));
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package net.gensokyoreimagined.nitori.mixin.needs_testing.brain;
|
||||
package net.gensokyoreimagined.nitori.mixin.needs_testing.collections.brain;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import it.unimi.dsi.fastutil.objects.Reference2ReferenceLinkedOpenHashMap;
|
||||
@@ -0,0 +1,40 @@
|
||||
package net.gensokyoreimagined.nitori.mixin.needs_testing.collections.mob_spawning;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import net.gensokyoreimagined.nitori.common.util.collections.HashedReferenceList;
|
||||
import net.minecraft.util.random.WeightedRandomList;
|
||||
import net.minecraft.util.random.WeightedEntry;
|
||||
import org.spongepowered.asm.mixin.*;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@Mixin(WeightedRandomList.class)
|
||||
public class PoolMixin<E extends WeightedEntry> {
|
||||
|
||||
@Mutable
|
||||
@Shadow
|
||||
@Final
|
||||
private ImmutableList<E> items;
|
||||
//Need a separate variable due to entries being type ImmutableList
|
||||
@Unique
|
||||
private List<E> entryHashList;
|
||||
|
||||
@Inject(method = "<init>", at = @At("RETURN"))
|
||||
private void init(List<? extends E> entries, CallbackInfo ci) {
|
||||
//We are using reference equality here, because all vanilla implementations of Weighted use reference equality
|
||||
this.entryHashList = this.items.size() > 4 ? Collections.unmodifiableList(new HashedReferenceList<>(this.items)) : this.items;
|
||||
}
|
||||
|
||||
/**
|
||||
* @author 2No2Name
|
||||
* @reason return a collection with a faster contains() call
|
||||
*/
|
||||
@Overwrite
|
||||
public List<E> unwrap() {
|
||||
return this.entryHashList;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
package net.gensokyoreimagined.nitori.mixin.network.microopt;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import net.gensokyoreimagined.nitori.mixin.util.network.VarIntUtil;
|
||||
import net.minecraft.network.VarInt;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Overwrite;
|
||||
@@ -13,8 +12,15 @@ public class VarIntsMixin {
|
||||
* @reason optimized version
|
||||
*/
|
||||
@Overwrite
|
||||
public static int getByteSize(int v) {
|
||||
return VarIntUtil.getVarIntLength(v);
|
||||
public static int getByteSize(int i) {
|
||||
return VARINT_EXACT_BYTE_LENGTHS[Integer.numberOfLeadingZeros(i)];
|
||||
}
|
||||
private static final int[] VARINT_EXACT_BYTE_LENGTHS = new int[33];
|
||||
static {
|
||||
for (int i = 0; i <= 32; ++i) {
|
||||
VARINT_EXACT_BYTE_LENGTHS[i] = (int) Math.ceil((31d - (i - 1)) / 7d);
|
||||
}
|
||||
VARINT_EXACT_BYTE_LENGTHS[32] = 1; // Special case for the number 0.
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
package net.gensokyoreimagined.nitori.mixin.util.block_tracking;
|
||||
|
||||
//import net.gensokyoreimagined.nitori.common.block.*;
|
||||
//import net.gensokyoreimagined.nitori.common.entity.block_tracking.ChunkSectionChangeCallback;
|
||||
//import net.gensokyoreimagined.nitori.common.entity.block_tracking.SectionedBlockChangeTracker;
|
||||
//import net.minecraft.world.level.block.state.BlockState;
|
||||
//import net.minecraft.network.FriendlyByteBuf;
|
||||
//import net.minecraft.core.SectionPos;
|
||||
//import net.minecraft.world.level.Level;
|
||||
//import net.minecraft.world.level.chunk.LevelChunkSection;
|
||||
//import net.minecraft.world.level.chunk.PalettedContainer;
|
||||
//import org.spongepowered.asm.mixin.Final;
|
||||
//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.Redirect;
|
||||
//import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
//import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
//import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
|
||||
//
|
||||
///**
|
||||
// * Keep track of how many blocks that meet certain criteria are in this chunk section.
|
||||
// * E.g. if no over-sized blocks are there, collision code can skip a few blocks.
|
||||
// *
|
||||
// * @author 2No2Name
|
||||
// */
|
||||
//@Mixin(LevelChunkSection.class)
|
||||
//public abstract class ChunkSectionMixin implements BlockCountingSection, BlockListeningSection {
|
||||
//
|
||||
// @Shadow
|
||||
// @Final
|
||||
// private PalettedContainer<BlockState> blockStateContainer;
|
||||
//
|
||||
// @Unique
|
||||
// private short[] countsByFlag = null;
|
||||
// @Unique
|
||||
// private ChunkSectionChangeCallback changeListener;
|
||||
// @Unique
|
||||
// private short listeningMask;
|
||||
//
|
||||
// @Unique
|
||||
// private static void addToFlagCount(short[] countsByFlag, BlockState state, short change) {
|
||||
// int flags = ((BlockStateFlagHolder) state).lithium$getAllFlags();
|
||||
// int i;
|
||||
// while ((i = Integer.numberOfTrailingZeros(flags)) < 32 && i < countsByFlag.length) {
|
||||
// //either count up by one (prevFlag not set) or down by one (prevFlag set)
|
||||
// countsByFlag[i] += change;
|
||||
// flags &= ~(1 << i);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public boolean lithium$mayContainAny(TrackedBlockStatePredicate trackedBlockStatePredicate) {
|
||||
// if (this.countsByFlag == null) {
|
||||
// fastInitClientCounts();
|
||||
// }
|
||||
// return this.countsByFlag[trackedBlockStatePredicate.getIndex()] != (short) 0;
|
||||
// }
|
||||
//
|
||||
// @Unique
|
||||
// private void fastInitClientCounts() {
|
||||
// this.countsByFlag = new short[BlockStateFlags.NUM_TRACKED_FLAGS];
|
||||
// for (TrackedBlockStatePredicate trackedBlockStatePredicate : BlockStateFlags.TRACKED_FLAGS) {
|
||||
// if (this.blockStateContainer.maybeHas(trackedBlockStatePredicate)) {
|
||||
// //We haven't counted, so we just set the count so high that it never incorrectly reaches 0.
|
||||
// //For most situations, this overestimation does not hurt client performance compared to correct counting,
|
||||
// this.countsByFlag[trackedBlockStatePredicate.getIndex()] = 16 * 16 * 16;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// @Redirect(
|
||||
// method = "recalcBlockCounts",
|
||||
// at = @At(
|
||||
// value = "INVOKE",
|
||||
// target = "Lnet/minecraft/world/level/chunk/PalettedContainer;forEachLocation(Lnet/minecraft/world/level/chunk/PalettedContainer$CountConsumer;)V"
|
||||
// )
|
||||
// )
|
||||
// private void initFlagCounters(PalettedContainer<BlockState> palettedContainer, PalettedContainer.Counter<BlockState> consumer) {
|
||||
// palettedContainer.count((state, count) -> {
|
||||
// consumer.accept(state, count);
|
||||
// addToFlagCount(this.countsByFlag, state, (short) count);
|
||||
// });
|
||||
// }
|
||||
//
|
||||
// @Inject(method = "recalcBlockCounts", at = @At("HEAD"))
|
||||
// private void createFlagCounters(CallbackInfo ci) {
|
||||
// this.countsByFlag = new short[BlockStateFlags.NUM_TRACKED_FLAGS];
|
||||
// }
|
||||
//
|
||||
// @Inject(
|
||||
// method = "read",
|
||||
// at = @At(value = "HEAD")
|
||||
// )
|
||||
// private void resetData(FriendlyByteBuf buf, CallbackInfo ci) {
|
||||
// this.countsByFlag = null;
|
||||
// }
|
||||
//
|
||||
// @Inject(
|
||||
// method = "setBlockState(IIILnet/minecraft/block/BlockState;Z)Lnet/minecraft/block/BlockState;",
|
||||
// at = @At(
|
||||
// value = "INVOKE",
|
||||
// target = "Lnet/minecraft/block/BlockState;getFluidState()Lnet/minecraft/fluid/FluidState;",
|
||||
// ordinal = 0,
|
||||
// shift = At.Shift.BEFORE
|
||||
// ),
|
||||
// locals = LocalCapture.CAPTURE_FAILHARD
|
||||
// )
|
||||
// private void updateFlagCounters(int x, int y, int z, BlockState newState, boolean lock, CallbackInfoReturnable<BlockState> cir, BlockState oldState) {
|
||||
// this.lithium$trackBlockStateChange(newState, oldState);
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public void lithium$trackBlockStateChange(BlockState newState, BlockState oldState) {
|
||||
// short[] countsByFlag = this.countsByFlag;
|
||||
// if (countsByFlag == null) {
|
||||
// return;
|
||||
// }
|
||||
// int prevFlags = ((BlockStateFlagHolder) oldState).lithium$getAllFlags();
|
||||
// int flags = ((BlockStateFlagHolder) newState).lithium$getAllFlags();
|
||||
//
|
||||
// int flagsXOR = prevFlags ^ flags;
|
||||
// //we need to iterate over indices that changed or are in the listeningMask
|
||||
// //Some Listening Flags are sensitive to both the previous and the new block. Others are only sensitive to
|
||||
// //blocks that are different according to the predicate (XOR). For XOR, the block counting needs to be updated
|
||||
// //as well.
|
||||
// int iterateFlags = (~BlockStateFlags.LISTENING_MASK_OR & flagsXOR) |
|
||||
// (BlockStateFlags.LISTENING_MASK_OR & this.listeningMask & (prevFlags | flags));
|
||||
// int flagIndex;
|
||||
//
|
||||
// while ((flagIndex = Integer.numberOfTrailingZeros(iterateFlags)) < 32 && flagIndex < countsByFlag.length) {
|
||||
// int flagBit = 1 << flagIndex;
|
||||
// //either count up by one (prevFlag not set) or down by one (prevFlag set)
|
||||
// if ((flagsXOR & flagBit) != 0) {
|
||||
// countsByFlag[flagIndex] += (short) (1 - (((prevFlags >>> flagIndex) & 1) << 1));
|
||||
// }
|
||||
// if ((this.listeningMask & flagBit) != 0) {
|
||||
// this.listeningMask = this.changeListener.onBlockChange(flagIndex, this);
|
||||
// }
|
||||
// iterateFlags &= ~flagBit;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public void lithium$addToCallback(ListeningBlockStatePredicate blockGroup, SectionedBlockChangeTracker tracker, long sectionPos, Level world) {
|
||||
// if (this.changeListener == null) {
|
||||
// if (sectionPos == Long.MIN_VALUE || world == null) {
|
||||
// throw new IllegalArgumentException("Expected world and section pos during intialization!");
|
||||
// }
|
||||
// this.changeListener = ChunkSectionChangeCallback.create(sectionPos, world);
|
||||
// }
|
||||
//
|
||||
// this.listeningMask = this.changeListener.addTracker(tracker, blockGroup);
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public void lithium$removeFromCallback(ListeningBlockStatePredicate blockGroup, SectionedBlockChangeTracker tracker) {
|
||||
// if (this.changeListener != null) {
|
||||
// this.listeningMask = this.changeListener.removeTracker(tracker, blockGroup);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// @Unique
|
||||
// public void lithium$invalidateListeningSection(SectionPos sectionPos) {
|
||||
// if (this.listeningMask != 0) {
|
||||
// this.changeListener.onChunkSectionInvalidated(sectionPos);
|
||||
// this.listeningMask = 0;
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
@@ -0,0 +1,16 @@
|
||||
package net.gensokyoreimagined.nitori.mixin.world.blending;
|
||||
|
||||
import net.gensokyoreimagined.nitori.common.math.FasterMathUtil;
|
||||
import net.minecraft.world.level.levelgen.blending.Blender;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
|
||||
// Credit Gale patch #0019
|
||||
@Mixin(Blender.class)
|
||||
public class BlendMixin {
|
||||
@Redirect(method = "heightToOffset", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/Mth;positiveModulo(DD)D"))
|
||||
private static double redirectBlendOffset(double dividend, double divisor) {
|
||||
return FasterMathUtil.positiveModuloForPositiveIntegerDivisor(dividend, divisor);
|
||||
}
|
||||
}
|
||||
@@ -44,6 +44,7 @@
|
||||
"entity.fast_retrieval.SectionedEntityCacheMixin",
|
||||
"entity.fall_damage.NoFallDamageMixin",
|
||||
"entity.sprinting_particles.EntityMixin",
|
||||
"entity.collisions.unpushable_cramming.BoatEntityMixin",
|
||||
"logic.fast_bits_blockpos.OptimizedBlockPosBitsMixin",
|
||||
"math.fast_blockops.BlockPosMixin",
|
||||
"math.fast_blockops.DirectionMixin",
|
||||
@@ -57,6 +58,7 @@
|
||||
"math.rounding.FastRoundingVoxShapeMixin",
|
||||
"math.sine_lut.MixinMth",
|
||||
"math.vec.FastMathVec3DMixin",
|
||||
"math.random.RandomMixin",
|
||||
"network.microopt.VarIntsMixin",
|
||||
"network.microopt.StringEncodingMixin",
|
||||
"vmp.playerwatching.MixinChunkFilter",
|
||||
@@ -82,6 +84,7 @@
|
||||
"world.block_entity_ticking.support_cache.BlockEntityMixin",
|
||||
"world.block_entity_ticking.support_cache.DirectBlockEntityTickInvokerMixin",
|
||||
"world.block_entity_ticking.support_cache.WorldChunkMixin",
|
||||
"world.portal_checks.DisablePortalChecksMixin"
|
||||
"world.portal_checks.DisablePortalChecksMixin",
|
||||
"world.blending.BlendMixin"
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user