From d28e2d857ca58270109da25a5c310cbf43bd34f5 Mon Sep 17 00:00:00 2001 From: DBotThePony Date: Mon, 10 Mar 2025 20:16:30 +0700 Subject: [PATCH] Replace RandomSequence --- .../ru/dbotthepony/mc/prng/BetterRandom.java | 2 + .../mc/prng/BetterRandomSequence.java | 41 +++++++ .../mc/prng/BetterRandomSequences.java | 109 ++++++++++++++++++ .../mc/prng/GJRAND64RandomSource.java | 57 ++++++++- .../mc/prng/mixin/ServerLevelMixin.java | 45 ++++++++ .../resources/META-INF/accesstransformer.cfg | 7 ++ src/main/resources/better_random.mixins.json | 3 +- 7 files changed, 259 insertions(+), 5 deletions(-) create mode 100644 src/main/java/ru/dbotthepony/mc/prng/BetterRandomSequence.java create mode 100644 src/main/java/ru/dbotthepony/mc/prng/BetterRandomSequences.java create mode 100644 src/main/java/ru/dbotthepony/mc/prng/mixin/ServerLevelMixin.java create mode 100644 src/main/resources/META-INF/accesstransformer.cfg diff --git a/src/main/java/ru/dbotthepony/mc/prng/BetterRandom.java b/src/main/java/ru/dbotthepony/mc/prng/BetterRandom.java index 00d8aee..7b8eeeb 100644 --- a/src/main/java/ru/dbotthepony/mc/prng/BetterRandom.java +++ b/src/main/java/ru/dbotthepony/mc/prng/BetterRandom.java @@ -1,8 +1,10 @@ package ru.dbotthepony.mc.prng; +import net.minecraft.resources.ResourceLocation; import net.neoforged.fml.common.Mod; @Mod(BetterRandom.MOD_ID) public final class BetterRandom { public static final String MOD_ID = "better_random"; + public static final String SAVEDATA_LOCATION = "better_random_sequences"; } diff --git a/src/main/java/ru/dbotthepony/mc/prng/BetterRandomSequence.java b/src/main/java/ru/dbotthepony/mc/prng/BetterRandomSequence.java new file mode 100644 index 0000000..9865fdf --- /dev/null +++ b/src/main/java/ru/dbotthepony/mc/prng/BetterRandomSequence.java @@ -0,0 +1,41 @@ +package ru.dbotthepony.mc.prng; + +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.RandomSource; +import net.minecraft.world.RandomSequence; +import net.minecraft.world.level.levelgen.RandomSupport; +import net.minecraft.world.level.levelgen.XoroshiroRandomSource; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Optional; + +public final class BetterRandomSequence extends RandomSequence { + private static final XoroshiroRandomSource DUMMY = new XoroshiroRandomSource(0, 0); + + public final GJRAND64RandomSource actualRandom; + + public BetterRandomSequence(GJRAND64RandomSource random) { + super(DUMMY); + this.actualRandom = random; + } + + public BetterRandomSequence(long seed, @Nullable ResourceLocation location) { + this(seed, Optional.ofNullable(location)); + } + + public BetterRandomSequence(long seed, Optional location) { + super(DUMMY); + RandomSupport.Seed128bit mixSeed = RandomSupport.upgradeSeedTo128bitUnmixed(seed); + + if (location.isPresent()) + mixSeed = mixSeed.xor(seedForKey(location.get())); + + actualRandom = new GJRAND64RandomSource(mixSeed.mixed()); + } + + @Override + public @NotNull RandomSource random() { + return actualRandom; + } +} diff --git a/src/main/java/ru/dbotthepony/mc/prng/BetterRandomSequences.java b/src/main/java/ru/dbotthepony/mc/prng/BetterRandomSequences.java new file mode 100644 index 0000000..be6cb85 --- /dev/null +++ b/src/main/java/ru/dbotthepony/mc/prng/BetterRandomSequences.java @@ -0,0 +1,109 @@ +package ru.dbotthepony.mc.prng; + +import com.mojang.datafixers.util.Pair; +import net.minecraft.core.HolderLookup; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.NbtOps; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.RandomSource; +import net.minecraft.world.RandomSequence; +import net.minecraft.world.RandomSequences; +import net.minecraft.world.level.saveddata.SavedData; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.jetbrains.annotations.NotNull; + +import java.util.HashMap; +import java.util.Optional; +import java.util.function.BiConsumer; + +public final class BetterRandomSequences extends RandomSequences { + private static final Logger LOGGER = LogManager.getLogger(); + + public BetterRandomSequences(long seed) { + super(seed); + } + + public BetterRandomSequences(long seed, CompoundTag tag, HolderLookup.Provider provider) { + super(seed); + + includeWorldSeed = tag.getBoolean("includeWorldSeed"); + includeSequenceId = tag.getBoolean("includeSequenceId"); + salt = tag.getInt("salt"); + + var compound = tag.getCompound("sequences"); + + for (var key : compound.getAllKeys()) { + try { + var location = ResourceLocation.parse(key); + var rng = GJRAND64RandomSource.CODEC.decode(NbtOps.INSTANCE, compound.get(key)).map(Pair::getFirst).getOrThrow(); + betterSequences.put(location, new BetterRandomSequence(rng)); + } catch (RuntimeException err) { + LOGGER.error("Unable to deserialize random sequence at {}", key, err); + } + } + } + + private final HashMap betterSequences = new HashMap<>(); + + @Override + public @NotNull CompoundTag save(@NotNull CompoundTag tag, HolderLookup.@NotNull Provider registries) { + // super.save(tag, registries); + tag.putBoolean("includeWorldSeed", includeWorldSeed); + tag.putBoolean("includeSequenceId", includeSequenceId); + tag.putInt("salt", salt); + + var compound = new CompoundTag(); + tag.put("sequences", compound); + + for (var entry : betterSequences.entrySet()) { + compound.put(entry.getKey().toString(), GJRAND64RandomSource.CODEC.encodeStart(NbtOps.INSTANCE, entry.getValue().actualRandom).getOrThrow()); + } + + return tag; + } + + @Override + public @NotNull RandomSource get(@NotNull ResourceLocation location) { + return new DirtyMarkingRandomSource(betterSequences.computeIfAbsent(location, this::createSequence).actualRandom); + } + + private BetterRandomSequence createSequence(ResourceLocation location) { + return this.createSequence(location, this.salt, this.includeWorldSeed, this.includeSequenceId); + } + + private BetterRandomSequence createSequence(ResourceLocation location, int salt, boolean includeWorldSeed, boolean includeSequenceId) { + long i = (includeWorldSeed ? this.worldSeed : 0L) ^ (long)salt; + return new BetterRandomSequence(i, includeSequenceId ? Optional.of(location) : Optional.empty()); + } + + @Override + public void forAllSequences(@NotNull BiConsumer action) { + betterSequences.forEach(action); + } + + @Override + public int clear() { + int i = betterSequences.size(); + betterSequences.clear(); + return i; + } + + @Override + public void reset(@NotNull ResourceLocation sequence) { + betterSequences.put(sequence, createSequence(sequence)); + } + + @Override + public void reset(@NotNull ResourceLocation sequence, int seed, boolean includeWorldSeed, boolean includeSequenceId) { + betterSequences.put(sequence, createSequence(sequence, seed, includeWorldSeed, includeSequenceId)); + } + + public static SavedData.Factory betterFactory(final long seed) { + return new SavedData.Factory<>( + () -> new BetterRandomSequences(seed), + (tag, provider) -> new BetterRandomSequences(seed, tag, provider), + null + ); + } +} diff --git a/src/main/java/ru/dbotthepony/mc/prng/GJRAND64RandomSource.java b/src/main/java/ru/dbotthepony/mc/prng/GJRAND64RandomSource.java index 60669ca..93e3792 100644 --- a/src/main/java/ru/dbotthepony/mc/prng/GJRAND64RandomSource.java +++ b/src/main/java/ru/dbotthepony/mc/prng/GJRAND64RandomSource.java @@ -1,9 +1,10 @@ package ru.dbotthepony.mc.prng; +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; import it.unimi.dsi.fastutil.HashCommon; import net.minecraft.util.Mth; import net.minecraft.util.RandomSource; -import net.minecraft.world.level.levelgen.MarsagliaPolarGaussian; import net.minecraft.world.level.levelgen.PositionalRandomFactory; import net.minecraft.world.level.levelgen.RandomSupport; import org.jetbrains.annotations.NotNull; @@ -11,12 +12,39 @@ import org.jetbrains.annotations.NotNull; import java.util.random.RandomGenerator; public final class GJRAND64RandomSource implements RandomGenerator, RandomSource { + public static final Codec CODEC = RecordCodecBuilder.create(it -> { + return it.group( + Codec.LONG.fieldOf("s0").forGetter(o -> o.s0), + Codec.LONG.fieldOf("s1").forGetter(o -> o.s1), + Codec.LONG.fieldOf("s2").forGetter(o -> o.s2), + Codec.LONG.fieldOf("s3").forGetter(o -> o.s3), + Codec.DOUBLE.fieldOf("nextNextGaussian").forGetter(o -> o.nextNextGaussian), + Codec.BOOL.fieldOf("haveNextNextGaussian").forGetter(o -> o.haveNextNextGaussian) + ).apply(it, GJRAND64RandomSource::new); + }); + private long s0; private long s1; private long s2; private long s3; + private double nextNextGaussian; + private boolean haveNextNextGaussian; - private final MarsagliaPolarGaussian gaussian = new MarsagliaPolarGaussian(this); + private GJRAND64RandomSource( + long s0, + long s1, + long s2, + long s3, + double nextNextGaussian, + boolean haveNextNextGaussian + ) { + this.s0 = s0; + this.s1 = s1; + this.s2 = s2; + this.s3 = s3; + this.nextNextGaussian = nextNextGaussian; + this.haveNextNextGaussian = haveNextNextGaussian; + } public GJRAND64RandomSource() { reinitialize(RandomSupport.generateUniqueSeed(), RandomSupport.generateUniqueSeed()); @@ -30,6 +58,10 @@ public final class GJRAND64RandomSource implements RandomGenerator, RandomSource reinitialize(seed0, seed1); } + public GJRAND64RandomSource(RandomSupport.Seed128bit seed) { + this(seed.seedHi(), seed.seedLo()); + } + private void reinitialize(long seed) { reinitialize(seed, 0L); } @@ -54,7 +86,7 @@ public final class GJRAND64RandomSource implements RandomGenerator, RandomSource @Override public void setSeed(long l) { reinitialize(l); - gaussian.reset(); + haveNextNextGaussian = false; } @Override @@ -89,7 +121,24 @@ public final class GJRAND64RandomSource implements RandomGenerator, RandomSource @Override public double nextGaussian() { - return gaussian.nextGaussian(); + if (this.haveNextNextGaussian) { + this.haveNextNextGaussian = false; + return this.nextNextGaussian; + } else { + double d0; + double d1; + double d2; + do { + d0 = 2.0 * nextDouble() - 1.0; + d1 = 2.0 * nextDouble() - 1.0; + d2 = Mth.square(d0) + Mth.square(d1); + } while (d2 >= 1.0 || d2 == 0.0); + + double d3 = Math.sqrt(-2.0 * Math.log(d2) / d2); + this.nextNextGaussian = d1 * d3; + this.haveNextNextGaussian = true; + return d0 * d3; + } } @Override diff --git a/src/main/java/ru/dbotthepony/mc/prng/mixin/ServerLevelMixin.java b/src/main/java/ru/dbotthepony/mc/prng/mixin/ServerLevelMixin.java new file mode 100644 index 0000000..b79fdef --- /dev/null +++ b/src/main/java/ru/dbotthepony/mc/prng/mixin/ServerLevelMixin.java @@ -0,0 +1,45 @@ +package ru.dbotthepony.mc.prng.mixin; + +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.util.RandomSource; +import net.minecraft.world.RandomSequences; +import net.minecraft.world.level.Level; +import org.jetbrains.annotations.Nullable; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Overwrite; +import ru.dbotthepony.mc.prng.BetterRandom; +import ru.dbotthepony.mc.prng.BetterRandomSequences; + +import java.util.Objects; + +@Mixin(ServerLevel.class) +public abstract class ServerLevelMixin { + @Nullable + private BetterRandomSequences betterRandomSequences; + + private ServerLevel self$brng() { + return (ServerLevel) (Object) this; + } + + @Overwrite(remap = false) + public RandomSource getRandomSequence(ResourceLocation location) { + return getRandomSequences().get(location); + } + + @Overwrite(remap = false) + public RandomSequences getRandomSequences() { + if (this.betterRandomSequences == null) { + ServerLevel overworld = Objects.requireNonNull( + self$brng() + .getServer() + .getLevel(Level.OVERWORLD), "MinecraftServer lacks an Overworld Level, cannot continue. (exception thrown by BetterRandom)"); + + this.betterRandomSequences = overworld + .getDataStorage() + .computeIfAbsent(BetterRandomSequences.betterFactory(overworld.getSeed()), BetterRandom.SAVEDATA_LOCATION); + } + + return this.betterRandomSequences; + } +} diff --git a/src/main/resources/META-INF/accesstransformer.cfg b/src/main/resources/META-INF/accesstransformer.cfg new file mode 100644 index 0000000..931da8e --- /dev/null +++ b/src/main/resources/META-INF/accesstransformer.cfg @@ -0,0 +1,7 @@ + +protected net.minecraft.world.RandomSequences worldSeed +protected net.minecraft.world.RandomSequences salt +protected net.minecraft.world.RandomSequences includeWorldSeed +protected net.minecraft.world.RandomSequences includeSequenceId +public net.minecraft.world.RandomSequences$DirtyMarkingRandomSource +public net.minecraft.world.RandomSequences$DirtyMarkingRandomSource (Lnet/minecraft/world/RandomSequences;Lnet/minecraft/util/RandomSource;)V diff --git a/src/main/resources/better_random.mixins.json b/src/main/resources/better_random.mixins.json index af5f6a9..fc76402 100644 --- a/src/main/resources/better_random.mixins.json +++ b/src/main/resources/better_random.mixins.json @@ -6,7 +6,8 @@ "minVersion": "0.8", "mixins": [ "RandomSourceMixin", - "LevelMixin" + "LevelMixin", + "ServerLevelMixin" ], "client": [] }