From 728ba0902262fb52545b28373d082a2c78a96e7b Mon Sep 17 00:00:00 2001 From: Taiyou06 Date: Mon, 15 Jul 2024 10:07:10 +0300 Subject: [PATCH] Make Randoms 100x faster and fix VarInts --- .../math/random/RandomGeneratorRandom.java | 111 +++++++++++ .../unpushable_cramming/BoatEntityMixin.java | 43 +++++ .../nitori/mixin/math/random/RandomMixin.java | 29 +++ .../{ => collections}/brain/BrainMixin.java | 2 +- .../collections/mob_spawning/PoolMixin.java | 40 ++++ .../mixin/network/microopt/VarIntsMixin.java | 12 +- .../block_tracking/ChunkSectionMixin.java | 173 ++++++++++++++++++ .../mixin/world/blending/BlendMixin.java | 16 ++ src/main/resources/mixins.core.json | 5 +- 9 files changed, 426 insertions(+), 5 deletions(-) create mode 100644 src/main/java/net/gensokyoreimagined/nitori/common/math/random/RandomGeneratorRandom.java create mode 100644 src/main/java/net/gensokyoreimagined/nitori/mixin/entity/collisions/unpushable_cramming/BoatEntityMixin.java create mode 100644 src/main/java/net/gensokyoreimagined/nitori/mixin/math/random/RandomMixin.java rename src/main/java/net/gensokyoreimagined/nitori/mixin/needs_testing/{ => collections}/brain/BrainMixin.java (94%) create mode 100644 src/main/java/net/gensokyoreimagined/nitori/mixin/needs_testing/collections/mob_spawning/PoolMixin.java create mode 100644 src/main/java/net/gensokyoreimagined/nitori/mixin/util/block_tracking/ChunkSectionMixin.java create mode 100644 src/main/java/net/gensokyoreimagined/nitori/mixin/world/blending/BlendMixin.java diff --git a/src/main/java/net/gensokyoreimagined/nitori/common/math/random/RandomGeneratorRandom.java b/src/main/java/net/gensokyoreimagined/nitori/common/math/random/RandomGeneratorRandom.java new file mode 100644 index 0000000..3123612 --- /dev/null +++ b/src/main/java/net/gensokyoreimagined/nitori/common/math/random/RandomGeneratorRandom.java @@ -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 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("}"); + } + } +} diff --git a/src/main/java/net/gensokyoreimagined/nitori/mixin/entity/collisions/unpushable_cramming/BoatEntityMixin.java b/src/main/java/net/gensokyoreimagined/nitori/mixin/entity/collisions/unpushable_cramming/BoatEntityMixin.java new file mode 100644 index 0000000..4c8988c --- /dev/null +++ b/src/main/java/net/gensokyoreimagined/nitori/mixin/entity/collisions/unpushable_cramming/BoatEntityMixin.java @@ -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 getOtherPushableEntities(Level world, @Nullable Entity except, AABB box, Predicate predicate) { + //noinspection Guava + if (predicate == Predicates.alwaysFalse()) { + return Collections.emptyList(); + } + if (predicate instanceof EntityPushablePredicate entityPushablePredicate) { + EntitySectionStorage cache = WorldHelper.getEntityCacheOrNull(world); + if (cache != null) { + //noinspection unchecked + return WorldHelper.getPushableEntities(world, cache, except, box, (EntityPushablePredicate) entityPushablePredicate); + } + } + return world.getEntities(except, box, predicate); + } +} diff --git a/src/main/java/net/gensokyoreimagined/nitori/mixin/math/random/RandomMixin.java b/src/main/java/net/gensokyoreimagined/nitori/mixin/math/random/RandomMixin.java new file mode 100644 index 0000000..f1570fa --- /dev/null +++ b/src/main/java/net/gensokyoreimagined/nitori/mixin/math/random/RandomMixin.java @@ -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 cir) { + cir.setReturnValue(new RandomGeneratorRandom(seed)); + } + + @Inject(method = "createNewThreadLocalInstance", at = @At(value = "HEAD"), cancellable = true) + private static void fasterrandom$createLocalInject(@NotNull CallbackInfoReturnable cir) { + cir.setReturnValue(new RandomGeneratorRandom(ThreadLocalRandom.current().nextLong())); + } + + @Inject(method = "createThreadSafe", at = @At(value = "HEAD"), cancellable = true) + private static void fasterrandom$createThreadSafeInject(@NotNull CallbackInfoReturnable cir) { + cir.setReturnValue(new RandomGeneratorRandom(RandomSupport.generateUniqueSeed())); + } +} \ No newline at end of file diff --git a/src/main/java/net/gensokyoreimagined/nitori/mixin/needs_testing/brain/BrainMixin.java b/src/main/java/net/gensokyoreimagined/nitori/mixin/needs_testing/collections/brain/BrainMixin.java similarity index 94% rename from src/main/java/net/gensokyoreimagined/nitori/mixin/needs_testing/brain/BrainMixin.java rename to src/main/java/net/gensokyoreimagined/nitori/mixin/needs_testing/collections/brain/BrainMixin.java index f650d18..3edc3e6 100644 --- a/src/main/java/net/gensokyoreimagined/nitori/mixin/needs_testing/brain/BrainMixin.java +++ b/src/main/java/net/gensokyoreimagined/nitori/mixin/needs_testing/collections/brain/BrainMixin.java @@ -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; diff --git a/src/main/java/net/gensokyoreimagined/nitori/mixin/needs_testing/collections/mob_spawning/PoolMixin.java b/src/main/java/net/gensokyoreimagined/nitori/mixin/needs_testing/collections/mob_spawning/PoolMixin.java new file mode 100644 index 0000000..6ca65db --- /dev/null +++ b/src/main/java/net/gensokyoreimagined/nitori/mixin/needs_testing/collections/mob_spawning/PoolMixin.java @@ -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 { + + @Mutable + @Shadow + @Final + private ImmutableList items; + //Need a separate variable due to entries being type ImmutableList + @Unique + private List entryHashList; + + @Inject(method = "", at = @At("RETURN")) + private void init(List 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 unwrap() { + return this.entryHashList; + } +} \ No newline at end of file diff --git a/src/main/java/net/gensokyoreimagined/nitori/mixin/network/microopt/VarIntsMixin.java b/src/main/java/net/gensokyoreimagined/nitori/mixin/network/microopt/VarIntsMixin.java index 632d41f..8779aaf 100644 --- a/src/main/java/net/gensokyoreimagined/nitori/mixin/network/microopt/VarIntsMixin.java +++ b/src/main/java/net/gensokyoreimagined/nitori/mixin/network/microopt/VarIntsMixin.java @@ -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. } /** diff --git a/src/main/java/net/gensokyoreimagined/nitori/mixin/util/block_tracking/ChunkSectionMixin.java b/src/main/java/net/gensokyoreimagined/nitori/mixin/util/block_tracking/ChunkSectionMixin.java new file mode 100644 index 0000000..3c38612 --- /dev/null +++ b/src/main/java/net/gensokyoreimagined/nitori/mixin/util/block_tracking/ChunkSectionMixin.java @@ -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 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 palettedContainer, PalettedContainer.Counter 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 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; +// } +// } +//} \ No newline at end of file diff --git a/src/main/java/net/gensokyoreimagined/nitori/mixin/world/blending/BlendMixin.java b/src/main/java/net/gensokyoreimagined/nitori/mixin/world/blending/BlendMixin.java new file mode 100644 index 0000000..db2fc72 --- /dev/null +++ b/src/main/java/net/gensokyoreimagined/nitori/mixin/world/blending/BlendMixin.java @@ -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); + } +} diff --git a/src/main/resources/mixins.core.json b/src/main/resources/mixins.core.json index 79ef6d3..599864c 100755 --- a/src/main/resources/mixins.core.json +++ b/src/main/resources/mixins.core.json @@ -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" ] }