Skip to content

Commit

Permalink
Fix Random bugs and optimize Simes
Browse files Browse the repository at this point in the history
- Fixed my random Xoroshift implementation being very off causing tremendous lag (due to like 5000 entities spawning :P)
- Fixed mobs not being very responsive
- Fixed a Random crash when locating structures
- Added an optimization to slime spawning that reduces Random operations by caching slime chunks in WorldChunk
  • Loading branch information
QPCrummer committed Jul 10, 2024
1 parent 6ed59b5 commit 2a3543a
Show file tree
Hide file tree
Showing 12 changed files with 205 additions and 134 deletions.
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ org.gradle.jvmargs=-Xmx1G
loader_version=0.15.11

# Mod Properties
mod_version = 0.0.1-dev.4
mod_version = 0.0.1-dev.5
maven_group = com.github.tatercertified
archives_base_name = potatoptimize

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package com.github.tatercertified.potatoptimize.mixin.random.generators;

import com.github.tatercertified.potatoptimize.Potatoptimize;
import com.github.tatercertified.potatoptimize.utils.random.ThreadLocalRandomImpl;
import com.github.tatercertified.potatoptimize.utils.random.XorShiftRandomImpl;
import com.moulberry.mixinconstraints.annotations.IfModAbsent;
import net.minecraft.util.math.random.ChunkRandom;
Expand All @@ -21,11 +19,6 @@ public class ChunkRandomMixin {

@Inject(method = "<init>", at = @At("TAIL"))
private void changeRandom(Random baseRandom, CallbackInfo ci) {
// Use TLR if the seed is not cared about as it is better for a multithreaded environment
if (!Potatoptimize.isUnsafeRandomEnabled) {
this.baseRandom = new ThreadLocalRandomImpl();
} else {
this.baseRandom = new XorShiftRandomImpl();
}
this.baseRandom = new XorShiftRandomImpl();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ static Random createLocal() {
return new ThreadLocalRandomImpl(ThreadLocalRandom.current().nextLong());
}


/**
* @author QPCrummer
* @reason Use my implementation of XorShift
Expand All @@ -37,5 +38,4 @@ static Random createLocal() {
static Random create(long seed) {
return new XorShiftRandomImpl((int) seed);
}

}
Original file line number Diff line number Diff line change
@@ -1,54 +1,80 @@
package com.github.tatercertified.potatoptimize.mixin.random.math;

import com.github.tatercertified.potatoptimize.utils.random.ThreadLocalRandomImpl;
import com.github.tatercertified.potatoptimize.utils.random.PotatoptimizedRandom;
import com.llamalad7.mixinextras.injector.ModifyReturnValue;
import com.llamalad7.mixinextras.sugar.Local;
import com.moulberry.mixinconstraints.annotations.IfModAbsent;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.random.Random;
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.CallbackInfoReturnable;

import java.util.UUID;

@IfModAbsent(value = "faster-random")
@Mixin(MathHelper.class)
public class RandomMathHelperMixin {
public abstract class RandomMathHelperMixin {

@Shadow @Final private static Random RANDOM;
@Shadow
public static float nextBetween(Random random, float min, float max) {
return 0;
}

@ModifyReturnValue(method = "nextGaussian", at = @At("RETURN"))
private static float optimizedGaussian(float original, @Local(ordinal = 0, argsOnly = true) float mean, @Local(ordinal = 0, argsOnly = true) float deviation, @Local(ordinal = 0, argsOnly = true) Random random) {
return (float) (mean + quickGaussian(random) * deviation);
}

@Inject(method = "nextGaussian", at = @At("HEAD"), cancellable = true)
private static void optimizedGaussian(Random random, float mean, float deviation, CallbackInfoReturnable<Float> cir) {
cir.setReturnValue(((ThreadLocalRandomImpl)RANDOM).nextGaussian(mean, deviation));
// Gaussian Code
private static double quickGaussian(Random random) {
long randomBits = random.nextLong();
long evenChunks = randomBits & EVEN_CHUNKS;
long oddChunks = (randomBits & ODD_CHUNKS) >>> 5;
long sum = chunkSum(evenChunks + oddChunks) - 186;
return sum / 31.0;
}

@Inject(method = "nextBetween(Lnet/minecraft/util/math/random/Random;II)I", at = @At("HEAD"), cancellable = true)
private static void optimizedNextBetween(Random random, int min, int max, CallbackInfoReturnable<Integer> cir) {
cir.setReturnValue(RANDOM.nextBetween(min, max));
private static long chunkSum(long bits) {
long sum = bits + (bits >>> 40);
sum += sum >>> 20;
sum += sum >>> 10;
sum &= (1<<10)-1;
return sum;
}

@Inject(method = "nextBetween(Lnet/minecraft/util/math/random/Random;FF)F", at = @At("HEAD"), cancellable = true)
private static void optimizedNextBetween(Random random, float min, float max, CallbackInfoReturnable<Float> cir) {
cir.setReturnValue(((ThreadLocalRandomImpl)RANDOM).nextFloat(min, max));
private static final long EVEN_CHUNKS = 0x7c1f07c1f07c1fL;
private static final long ODD_CHUNKS = EVEN_CHUNKS << 5;

@ModifyReturnValue(method = "nextFloat", at = @At("RETURN"))
private static float optimizedNextBetween(float original, @Local(ordinal = 0, argsOnly = true) Random random, @Local(ordinal = 0, argsOnly = true) float min, @Local(ordinal = 0, argsOnly = true) float max) {
if (random instanceof PotatoptimizedRandom potatoptimizedRandom) {
return potatoptimizedRandom.nextFloat(min, max);
} else {
return original;
}
}

@Inject(method = "randomUuid(Lnet/minecraft/util/math/random/Random;)Ljava/util/UUID;", at = @At("HEAD"), cancellable = true)
private static void optimizedRandomUUID(Random random, CallbackInfoReturnable<UUID> cir) {
cir.setReturnValue(((ThreadLocalRandomImpl)RANDOM).nextUUID());
@ModifyReturnValue(method = "randomUuid(Lnet/minecraft/util/math/random/Random;)Ljava/util/UUID;", at = @At("RETURN"))
private static UUID optimizedRandomUUID(UUID original, @Local(ordinal = 0, argsOnly = true) Random random) {
return new UUID(random.nextLong() & 0xFFFFFFFFFFFF0FFFL | 0x4000L, random.nextLong() & 0x3FFFFFFFFFFFFFFFL | Long.MIN_VALUE);
}

@Inject(method = "nextInt", at = @At("HEAD"), cancellable = true)
private static void optimizedNextInt(Random random, int min, int max, CallbackInfoReturnable<Integer> cir) {
cir.setReturnValue(RANDOM.nextBetween(min, max));
@ModifyReturnValue(method = "nextInt", at = @At("RETURN"))
private static int optimizedNextInt(int original, @Local(ordinal = 0, argsOnly = true) Random random, @Local(ordinal = 0, argsOnly = true) int min, @Local(ordinal = 0, argsOnly = true) int max) {
return random.nextBetween(min, max);
}

@Inject(method = "nextFloat", at = @At("HEAD"), cancellable = true)
private static void optimizedNextFloat(Random random, float min, float max, CallbackInfoReturnable<Float> cir) {
cir.setReturnValue(((ThreadLocalRandomImpl)RANDOM).nextFloat(min, max));
@ModifyReturnValue(method = "nextFloat", at = @At("RETURN"))
private static float optimizedNextFloat(float original, @Local(ordinal = 0, argsOnly = true) Random random, @Local(ordinal = 0, argsOnly = true) float min, @Local(ordinal = 0, argsOnly = true) float max) {
return nextBetween(random, min, max);
}

@Inject(method = "nextDouble", at = @At("HEAD"), cancellable = true)
private static void optimizedNextDouble(Random random, double min, double max, CallbackInfoReturnable<Double> cir) {
cir.setReturnValue(((ThreadLocalRandomImpl)RANDOM).nextDouble(min, max));
@ModifyReturnValue(method = "nextDouble", at = @At("RETURN"))
private static double optimizedNextDouble(double original, @Local(ordinal = 0, argsOnly = true) Random random, @Local(ordinal = 0, argsOnly = true) double min, @Local(ordinal = 0, argsOnly = true) double max) {
if (random instanceof PotatoptimizedRandom potatoptimizedRandom) {
return potatoptimizedRandom.nextDouble(min, max);
} else {
return original;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package com.github.tatercertified.potatoptimize.mixin.random.slime;

import com.github.tatercertified.potatoptimize.utils.interfaces.SlimeChunkInterface;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.SpawnReason;
import net.minecraft.entity.mob.MobEntity;
import net.minecraft.entity.mob.Monster;
import net.minecraft.entity.mob.SlimeEntity;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.random.Random;
import net.minecraft.world.StructureWorldAccess;
import net.minecraft.world.World;
import net.minecraft.world.WorldAccess;
import net.minecraft.world.chunk.WorldChunk;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;

@Mixin(SlimeEntity.class)
public abstract class RandomSlimeEntityMixin extends MobEntity implements Monster {
protected RandomSlimeEntityMixin(EntityType<? extends MobEntity> entityType, World world) {
super(entityType, world);
}

/**
* @author QPCrummer
* @reason This is just plain awful
*/
@Overwrite
public static boolean canSpawn(EntityType<SlimeEntity> type, WorldAccess world, SpawnReason spawnReason, BlockPos pos, Random random) {
if (!(world instanceof StructureWorldAccess)) {
return false;
}

if (SpawnReason.isAnySpawner(spawnReason)) {
return canMobSpawn(type, world, spawnReason, pos, random);
} else if (pos.getY() < 70 && pos.getY() > 50 ) {
float randomFloat = random.nextFloat(); // This will slightly change parity
if (randomFloat > 0.5f && randomFloat > world.getMoonSize() && world.getLightLevel(pos) <= random.nextInt(8)) {
return canMobSpawn(type, world, spawnReason, pos, random);
}
}

if (pos.getY() < 40 && random.nextInt(10) == 0) {
WorldChunk chunk = (WorldChunk) world.getChunk(pos);
if (((SlimeChunkInterface)chunk).isSlimeChunk()) {
return canMobSpawn(type, world, spawnReason, pos, random);
}
}

return false;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package com.github.tatercertified.potatoptimize.mixin.random.slime;

import com.github.tatercertified.potatoptimize.utils.interfaces.SlimeChunkInterface;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.math.ChunkPos;
import net.minecraft.util.math.random.ChunkRandom;
import net.minecraft.util.math.random.Random;
import net.minecraft.world.StructureWorldAccess;
import net.minecraft.world.World;
import net.minecraft.world.chunk.ChunkSection;
import net.minecraft.world.chunk.ProtoChunk;
import net.minecraft.world.chunk.UpgradeData;
import net.minecraft.world.chunk.WorldChunk;
import net.minecraft.world.gen.chunk.BlendingData;
import net.minecraft.world.tick.ChunkTickScheduler;
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.CallbackInfo;

@Mixin(WorldChunk.class)
public class SlimeChunkMixin implements SlimeChunkInterface {

private boolean slimeChunk;

@Inject(method = "<init>(Lnet/minecraft/world/World;Lnet/minecraft/util/math/ChunkPos;)V", at = @At("TAIL"))
private void assignSlimeChunk1(World world, ChunkPos pos, CallbackInfo ci) {
setSlimeChunk(world, pos);
}

@Inject(method = "<init>(Lnet/minecraft/server/world/ServerWorld;Lnet/minecraft/world/chunk/ProtoChunk;Lnet/minecraft/world/chunk/WorldChunk$EntityLoader;)V", at = @At("TAIL"))
private void assignSlimeChunk2(ServerWorld world, ProtoChunk protoChunk, WorldChunk.EntityLoader entityLoader, CallbackInfo ci) {
setSlimeChunk(world, protoChunk.getPos());
}

@Inject(method = "<init>(Lnet/minecraft/world/World;Lnet/minecraft/util/math/ChunkPos;Lnet/minecraft/world/chunk/UpgradeData;Lnet/minecraft/world/tick/ChunkTickScheduler;Lnet/minecraft/world/tick/ChunkTickScheduler;J[Lnet/minecraft/world/chunk/ChunkSection;Lnet/minecraft/world/chunk/WorldChunk$EntityLoader;Lnet/minecraft/world/gen/chunk/BlendingData;)V", at = @At("TAIL"))
private void assignSlimeChunk3(World world, ChunkPos pos, UpgradeData upgradeData, ChunkTickScheduler blockTickScheduler, ChunkTickScheduler fluidTickScheduler, long inhabitedTime, ChunkSection[] sectionArrayInitializer, WorldChunk.EntityLoader entityLoader, BlendingData blendingData, CallbackInfo ci) {
setSlimeChunk(world, pos);
}

@Override
public boolean isSlimeChunk() {
return slimeChunk;
}

private void setSlimeChunk(World world, ChunkPos pos) {
if (world instanceof StructureWorldAccess) {
Random random = ChunkRandom.getSlimeRandom(pos.x, pos.z, ((StructureWorldAccess)world).getSeed(), 987234911L);
slimeChunk = random.nextInt(10) == 0;
} else {
slimeChunk = false;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
@Mixin(WorldChunk.class)
public abstract class RandomWorldChunkMixin extends Chunk implements LightningInterface {

@Shadow @Final private World world;
@Shadow @Final World world;

public RandomWorldChunkMixin(ChunkPos pos, UpgradeData upgradeData, HeightLimitView heightLimitView, Registry<Biome> biomeRegistry, long inhabitedTime, @Nullable ChunkSection[] sectionArray, @Nullable BlendingData blendingData) {
super(pos, upgradeData, heightLimitView, biomeRegistry, inhabitedTime, sectionArray, blendingData);
Expand Down
Loading

0 comments on commit 2a3543a

Please sign in to comment.