848 lines
33 KiB
Diff
848 lines
33 KiB
Diff
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
|
From: AlphaKR93 <dev@alpha93.kr>
|
|
Date: Sat, 26 Oct 2024 18:30:26 +0900
|
|
Subject: [PATCH] Configurable RandomSource factory/provider
|
|
|
|
Xorshift Random: https://gist.github.com/Xyene/4637619
|
|
|
|
Based on AnOpenSauceDev/FastRandom
|
|
|
|
Copyright (C) 2024 AnOpenSauceDev
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
|
|
diff --git a/src/main/java/com/destroystokyo/paper/loottable/PaperLootableInventoryData.java b/src/main/java/com/destroystokyo/paper/loottable/PaperLootableInventoryData.java
|
|
index 861bff267cb397e13e8e1c79bd0776b130c6e5da..266e56f2c2e6d8b760209b5d58c58333af5bd0c1 100644
|
|
--- a/src/main/java/com/destroystokyo/paper/loottable/PaperLootableInventoryData.java
|
|
+++ b/src/main/java/com/destroystokyo/paper/loottable/PaperLootableInventoryData.java
|
|
@@ -21,7 +21,7 @@ import org.checkerframework.framework.qual.DefaultQualifier;
|
|
@DefaultQualifier(NonNull.class)
|
|
public class PaperLootableInventoryData {
|
|
|
|
- private static final Random RANDOM = new Random();
|
|
+ private static final Random RANDOM = java.util.concurrent.ThreadLocalRandom.current(); // Plazma - reduce creating random instance
|
|
|
|
private long lastFill = -1;
|
|
private long nextRefill = -1;
|
|
diff --git a/src/main/java/com/github/anopensaucedev/fasterrandom/util/math/random/RandomGeneratorRandom.java b/src/main/java/com/github/anopensaucedev/fasterrandom/util/math/random/RandomGeneratorRandom.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..a060fdbdc4daaffd9aafc707dc567cb56067d1e2
|
|
--- /dev/null
|
|
+++ b/src/main/java/com/github/anopensaucedev/fasterrandom/util/math/random/RandomGeneratorRandom.java
|
|
@@ -0,0 +1,115 @@
|
|
+package com.github.anopensaucedev.fasterrandom.util.math.random;
|
|
+
|
|
+import com.google.common.annotations.VisibleForTesting;
|
|
+import net.minecraft.core.BlockPos;
|
|
+import net.minecraft.util.Mth;
|
|
+import net.minecraft.world.level.levelgen.BitRandomSource;
|
|
+import net.minecraft.world.level.levelgen.PositionalRandomFactory;
|
|
+import net.minecraft.util.RandomSource;
|
|
+import org.jspecify.annotations.NonNull;
|
|
+
|
|
+import java.util.random.RandomGenerator;
|
|
+import java.util.random.RandomGeneratorFactory;
|
|
+
|
|
+public class RandomGeneratorRandom implements BitRandomSource {
|
|
+ private static final @NonNull RandomGeneratorFactory<RandomGenerator.SplittableGenerator> FACTORY = RandomGeneratorFactory.of(org.plazmamc.plazma.Options.RANDOM_ALGOL);
|
|
+ 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 = FACTORY.create(seed);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public @NonNull RandomSource fork() {
|
|
+ return new RandomGeneratorRandom(this.nextLong());
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public @NonNull PositionalRandomFactory forkPositional() {
|
|
+ return new RandomGeneratorRandomFactory(this.nextLong());
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void setSeed(long seed) {
|
|
+ this.seed = seed;
|
|
+ this.randomGenerator = 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 RandomGeneratorRandomFactory(long seed) implements PositionalRandomFactory {
|
|
+ @SuppressWarnings("deprecation")
|
|
+ @Override
|
|
+ public @NonNull RandomSource at(int x, int y, int z) {
|
|
+ return new RandomGeneratorRandom(Mth.getSeed(x, y, z) ^ this.seed);
|
|
+ }
|
|
+
|
|
+ @SuppressWarnings("deprecation")
|
|
+ @Override
|
|
+ public @NonNull RandomSource at(final @NonNull BlockPos pos) {
|
|
+ return new RandomGeneratorRandom(Mth.getSeed(pos.getX(), pos.getY(), pos.getZ()) ^ this.seed);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public @NonNull RandomSource fromHashOf(@NonNull String seed) {
|
|
+ return new RandomGeneratorRandom((long) seed.hashCode() ^ this.seed);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public @NonNull RandomSource fromSeed(final long seed) {
|
|
+ return new RandomGeneratorRandom(seed);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ @VisibleForTesting
|
|
+ public void parityConfigString(@NonNull StringBuilder info) {
|
|
+ info.append("RandomGeneratorRandom$RandomGeneratorRandomFactory{").append(this.seed).append("}");
|
|
+ }
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/net/minecraft/util/RandomSource.java b/src/main/java/net/minecraft/util/RandomSource.java
|
|
index 252aef3ffe0fecd47ebea1ed7df48e14fa873eb9..f73f8faac05f90c646a23fadf3da565938a4f788 100644
|
|
--- a/src/main/java/net/minecraft/util/RandomSource.java
|
|
+++ b/src/main/java/net/minecraft/util/RandomSource.java
|
|
@@ -10,18 +10,39 @@ import net.minecraft.world.level.levelgen.ThreadSafeLegacyRandomSource;
|
|
public interface RandomSource {
|
|
@Deprecated
|
|
double GAUSSIAN_SPREAD_FACTOR = 2.297;
|
|
+ // Plazma start - Implement RandomGenerator support
|
|
+ java.util.function.LongFunction<RandomSource> FACTORY = switch (org.plazmamc.plazma.Options.RANDOM_FACTORY) {
|
|
+ case "legacy" -> LegacyRandomSource::new;
|
|
+ case "xoroshiro" -> net.minecraft.world.level.levelgen.XoroshiroRandomSource::new;
|
|
+ case "xorshift" -> org.plazmamc.plazma.util.random.XorshiftRandomSource::new;
|
|
+ case "secure" -> org.plazmamc.plazma.util.random.SecureRandomSource::new;
|
|
+ case "generator" -> {
|
|
+ try {
|
|
+ java.util.random.RandomGeneratorFactory.of(org.plazmamc.plazma.Options.RANDOM_ALGOL).create();
|
|
+ yield com.github.anopensaucedev.fasterrandom.util.math.random.RandomGeneratorRandom::new;
|
|
+ } catch (IllegalArgumentException ignored) {
|
|
+ net.minecraft.server.MinecraftServer.LOGGER.error("Your JDK does not support RandomGenerator! Update JDK or remove \"-DPlazma.randomSourceFactory\" from the startup flag");
|
|
+ net.minecraft.server.MinecraftServer.LOGGER.warn("Falling back to legacy random");
|
|
+ yield LegacyRandomSource::new;
|
|
+ }
|
|
+ }
|
|
+ default -> throw new IllegalArgumentException("Invalid RandomSource: " + org.plazmamc.plazma.Options.RANDOM_FACTORY + ". Valid RandomSources are: legacy, xoroshiro, xorshift, generator");
|
|
+ };
|
|
+ @org.jspecify.annotations.Nullable RandomSource SHARED = org.plazmamc.plazma.Options.SHARED_RANDOM ? create() : null;
|
|
+ // Plazma end - Implement RandomGenerator support
|
|
|
|
static RandomSource create() {
|
|
- return create(RandomSupport.generateUniqueSeed());
|
|
+ return SHARED == null ? create(RandomSupport.generateUniqueSeed()) : SHARED; // Plazma - Configurable RandomSource
|
|
}
|
|
|
|
@Deprecated
|
|
static RandomSource createThreadSafe() {
|
|
+ if (org.plazmamc.plazma.Options.NO_THREAD_SAFE) return create(); // Plazma - Configurable RandomSource
|
|
return new ThreadSafeLegacyRandomSource(RandomSupport.generateUniqueSeed());
|
|
}
|
|
|
|
static RandomSource create(long seed) {
|
|
- return new LegacyRandomSource(seed);
|
|
+ return org.plazmamc.plazma.Options.IGNORE_SEED ? create() : FACTORY.apply(seed); // Plazma - Configurable RandomSource
|
|
}
|
|
|
|
static RandomSource createNewThreadLocalInstance() {
|
|
diff --git a/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinAi.java b/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinAi.java
|
|
index 27a6de70530c2a1cbe2f77a7fb493038121710ea..bbe093f7d5e0dc8e875f237058aa74c739967dd9 100644
|
|
--- a/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinAi.java
|
|
+++ b/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinAi.java
|
|
@@ -395,7 +395,7 @@ public class PiglinAi {
|
|
}
|
|
|
|
private static boolean wantsToDance(LivingEntity piglin, LivingEntity target) {
|
|
- return target.getType() != EntityType.HOGLIN ? false : RandomSource.create(piglin.level().getGameTime()).nextFloat() < 0.1F;
|
|
+ return target.getType() != EntityType.HOGLIN ? false : (org.plazmamc.plazma.Options.SHARED_RANDOM ? piglin.random.nextFloat() : RandomSource.create(piglin.level().getGameTime()).nextFloat()) < 0.1F; // Plazma - reduce creating random instance
|
|
}
|
|
|
|
protected static boolean wantsToPickup(Piglin piglin, ItemStack stack) {
|
|
diff --git a/src/main/java/net/minecraft/world/level/block/entity/StructureBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/StructureBlockEntity.java
|
|
index 0e4c17c7246093d7fdb64a8f98536a84a8cfd978..0147a7be5fd147ffa9bd42a90e85a324bc317a81 100644
|
|
--- a/src/main/java/net/minecraft/world/level/block/entity/StructureBlockEntity.java
|
|
+++ b/src/main/java/net/minecraft/world/level/block/entity/StructureBlockEntity.java
|
|
@@ -343,7 +343,7 @@ public class StructureBlockEntity extends BlockEntity {
|
|
}
|
|
|
|
public static RandomSource createRandom(long seed) {
|
|
- return seed == 0L ? RandomSource.create(Util.getMillis()) : RandomSource.create(seed);
|
|
+ return seed == 0L ? RandomSource.create() : RandomSource.create(seed); // Plazma - reduce creating random instance
|
|
}
|
|
|
|
public boolean placeStructureIfSameSize(ServerLevel world) {
|
|
diff --git a/src/main/java/net/minecraft/world/level/levelgen/structure/templatesystem/StructurePlaceSettings.java b/src/main/java/net/minecraft/world/level/levelgen/structure/templatesystem/StructurePlaceSettings.java
|
|
index f1e0d3d7b1f458fcce83cf67762a16309123cdcb..6a8d5948f127ea87a0b4554ece4ad2c4c49a47c6 100644
|
|
--- a/src/main/java/net/minecraft/world/level/levelgen/structure/templatesystem/StructurePlaceSettings.java
|
|
+++ b/src/main/java/net/minecraft/world/level/levelgen/structure/templatesystem/StructurePlaceSettings.java
|
|
@@ -120,7 +120,7 @@ public class StructurePlaceSettings {
|
|
}
|
|
|
|
public RandomSource getRandom(@Nullable BlockPos pos) {
|
|
- return this.random != null ? this.random : (pos == null ? RandomSource.create(Util.getMillis()) : RandomSource.create(Mth.getSeed(pos)));
|
|
+ return this.random != null ? this.random : (pos == null ? RandomSource.create() : RandomSource.create(Mth.getSeed(pos))); // Plazma - reduce creating random instance
|
|
}
|
|
|
|
public boolean isIgnoreEntities() {
|
|
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
|
|
index ea1b4f0073f775fdd18eb080886d60c71a08ab2c..20782c3be1f3f98f10c884fd8d937a626b3b1920 100644
|
|
--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
|
|
+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
|
|
@@ -295,7 +295,7 @@ public class CraftWorld extends CraftRegionAccessor implements World {
|
|
}
|
|
// Paper end
|
|
|
|
- private static final Random rand = new Random();
|
|
+ private static final Random rand = java.util.concurrent.ThreadLocalRandom.current(); // Plazma - reduce creating random instance
|
|
|
|
public CraftWorld(ServerLevel world, ChunkGenerator gen, BiomeProvider biomeProvider, Environment env) {
|
|
this.world = world;
|
|
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftFirework.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftFirework.java
|
|
index 759b6e54db93792c9862b1f1625118ac6fa49d7a..e81fa9aefc76af906ed1b6903f416f4d123689f5 100644
|
|
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftFirework.java
|
|
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftFirework.java
|
|
@@ -14,7 +14,7 @@ import org.bukkit.inventory.meta.FireworkMeta;
|
|
|
|
public class CraftFirework extends CraftProjectile implements Firework {
|
|
|
|
- private final Random random = new Random();
|
|
+ private final net.minecraft.util.RandomSource random = net.minecraft.util.RandomSource.create(); // Plazma - Configurable RandomSource
|
|
//private CraftItemStack item; // Paper - Remove usage, not accurate representation of current item.
|
|
|
|
public CraftFirework(CraftServer server, FireworkRocketEntity entity) {
|
|
diff --git a/src/main/java/org/plazmamc/plazma/Options.java b/src/main/java/org/plazmamc/plazma/Options.java
|
|
index 4b3ae18ef8cd09a2d9c9eaee2bf402d0dd7ee1cd..016a1cd625091fe9b93b27a9782cdc13057a0c8a 100644
|
|
--- a/src/main/java/org/plazmamc/plazma/Options.java
|
|
+++ b/src/main/java/org/plazmamc/plazma/Options.java
|
|
@@ -12,5 +12,10 @@ public interface Options {
|
|
boolean VANILLAIZE = getBoolean("Plazma.vanillaize") && !AGGRESSIVE;
|
|
boolean USE_VANILLA = getBoolean("Plazma.useVanillaConfiguration") && !AGGRESSIVE && NO_OPTIMIZE;
|
|
boolean VANILLA_ICO = getBoolean("Plazma.useVanillaFavicon");
|
|
+ String RANDOM_FACTORY = getProperty("Plazma.randomSourceFactory", "legacy");
|
|
+ String RANDOM_ALGOL = getProperty("Plazma.randomSourceAlgorithm", "L64X128MixRandom");
|
|
+ boolean SHARED_RANDOM = getBoolean("Plazma.sharedRandomSource");
|
|
+ boolean IGNORE_SEED = getBoolean("Plazma.ignoreSeed") && SHARED_RANDOM;
|
|
+ boolean NO_THREAD_SAFE = getBoolean("Plazma.overrideThreadSafeRandom");
|
|
|
|
}
|
|
diff --git a/src/main/java/org/plazmamc/plazma/util/random/SecureRandomSource.java b/src/main/java/org/plazmamc/plazma/util/random/SecureRandomSource.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..5d08f040a05df5d24f4bf9747177c1720fe246fe
|
|
--- /dev/null
|
|
+++ b/src/main/java/org/plazmamc/plazma/util/random/SecureRandomSource.java
|
|
@@ -0,0 +1,100 @@
|
|
+package org.plazmamc.plazma.util.random;
|
|
+
|
|
+import com.google.common.annotations.VisibleForTesting;
|
|
+import net.minecraft.core.BlockPos;
|
|
+import net.minecraft.util.Mth;
|
|
+import net.minecraft.util.RandomSource;
|
|
+import net.minecraft.world.level.levelgen.PositionalRandomFactory;
|
|
+import org.jetbrains.annotations.NotNull;
|
|
+import org.jspecify.annotations.NonNull;
|
|
+import java.security.SecureRandom;
|
|
+
|
|
+public class SecureRandomSource implements RandomSource {
|
|
+
|
|
+ private final SecureRandom random = new SecureRandom();
|
|
+
|
|
+ public SecureRandomSource(long seed) {
|
|
+ this.setSeed(seed);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public @NotNull RandomSource fork() {
|
|
+ return new SecureRandomSource(this.nextLong());
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public @NotNull PositionalRandomFactory forkPositional() {
|
|
+ return new SecureRandomSourceFactory(this.nextLong());
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void setSeed(final long seed) {
|
|
+ this.random.setSeed(seed);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public int nextInt() {
|
|
+ return this.random.nextInt();
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public int nextInt(final int bound) {
|
|
+ return this.random.nextInt(bound);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public long nextLong() {
|
|
+ return this.random.nextLong();
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public boolean nextBoolean() {
|
|
+ return this.random.nextBoolean();
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public float nextFloat() {
|
|
+ return this.random.nextFloat();
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public double nextDouble() {
|
|
+ return this.random.nextDouble();
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public double nextGaussian() {
|
|
+ return this.random.nextGaussian();
|
|
+ }
|
|
+
|
|
+ private record SecureRandomSourceFactory(long seed) implements PositionalRandomFactory {
|
|
+ @SuppressWarnings("deprecation")
|
|
+ @Override
|
|
+ public @NonNull RandomSource at(int x, int y, int z) {
|
|
+ return new SecureRandomSource(Mth.getSeed(x, y, z) ^ this.seed);
|
|
+ }
|
|
+
|
|
+ @SuppressWarnings("deprecation")
|
|
+ @Override
|
|
+ public @NonNull RandomSource at(final @NonNull BlockPos pos) {
|
|
+ return new SecureRandomSource(Mth.getSeed(pos.getX(), pos.getY(), pos.getZ()) ^ this.seed);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public @NonNull RandomSource fromHashOf(@NonNull String seed) {
|
|
+ return new SecureRandomSource((long) seed.hashCode() ^ this.seed);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public @NonNull RandomSource fromSeed(final long seed) {
|
|
+ return new SecureRandomSource(seed);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ @VisibleForTesting
|
|
+ public void parityConfigString(@NonNull StringBuilder info) {
|
|
+ info.append("SecureRandomSource$SecureRandomSourceFactory{").append(this.seed).append("}");
|
|
+ }
|
|
+ }
|
|
+
|
|
+}
|
|
diff --git a/src/main/java/org/plazmamc/plazma/util/random/XorshiftRandom.java b/src/main/java/org/plazmamc/plazma/util/random/XorshiftRandom.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..d9f08546997adb7e4f9b70ce913a4e92eced3a95
|
|
--- /dev/null
|
|
+++ b/src/main/java/org/plazmamc/plazma/util/random/XorshiftRandom.java
|
|
@@ -0,0 +1,391 @@
|
|
+package org.plazmamc.plazma.util.random;
|
|
+
|
|
+import java.util.Random;
|
|
+
|
|
+/**
|
|
+ * A random number generator based on the simple and fast xor-shift pseudo
|
|
+ * random number generator (RNG) specified in:
|
|
+ * Marsaglia, George. (2003). <a href="https://web.archive.org/web/20070211044403/http://www.jstatsoft.org/v08/i14/xorshift.pdf">Xorshift RNGs.</a>
|
|
+ * Translated from: <a href="https://web.archive.org/web/20230919193645/https://www.codeproject.com/Articles/9187/A-fast-equivalent-for-System-Random">A fast equivalent for System.Random</a>.
|
|
+ * Original Code from: <a href="https://gist.github.com/Xyene/4637619">Xyene/Random.java</a>
|
|
+ */
|
|
+@SuppressWarnings("SuspiciousNameCombination")
|
|
+public class XorshiftRandom extends Random {
|
|
+ final double REAL_UNIT_INT = 1.0 / (0x7FFFFFFFL);
|
|
+ final double REAL_UNIT_UINT = 1.0 / (0xFFFFFFFFL);
|
|
+ final long Y = 842502087L, Z = 3579807591L, W = 273326509L;
|
|
+ long x, y, z, w;
|
|
+
|
|
+ public XorshiftRandom() {
|
|
+ seed((int) System.currentTimeMillis());
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void setSeed(long seed) {
|
|
+ seed((int) seed);
|
|
+ }
|
|
+
|
|
+ public void seed(int seed) {
|
|
+ // The only stipulation stated for the xorshift RNG is that at least one of
|
|
+ // the seeds x,y,z,w is non-zero. We fulfill that requirement by only allowing
|
|
+ // resetting of the x seed
|
|
+ x = seed;
|
|
+ y = Y;
|
|
+ z = Z;
|
|
+ w = W;
|
|
+ }
|
|
+
|
|
+ long boolBuffer;
|
|
+ int boolBufferBits = 0;
|
|
+
|
|
+ @Override
|
|
+ public boolean nextBoolean() {
|
|
+ if (boolBufferBits == 0) {
|
|
+ boolBuffer = nextUInt();
|
|
+ boolBufferBits = 32;
|
|
+ }
|
|
+ boolBuffer >>= 1;
|
|
+ boolean bit = (boolBuffer & 1) == 0;
|
|
+ --boolBufferBits;
|
|
+ return bit;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void nextBytes(byte[] buffer) {
|
|
+ // Fill up the bulk of the buffer in chunks of 4 bytes at a time.
|
|
+ long x = this.x, y = this.y, z = this.z, w = this.w;
|
|
+ int i = 0;
|
|
+ long t;
|
|
+ for (int bound = buffer.length - 3; i < bound; ) {
|
|
+ // Generate 4 bytes.
|
|
+ // Increased performance is achieved by generating 4 random bytes per loop.
|
|
+ // Also note that no mask needs to be applied to zero out the higher order bytes before
|
|
+ // casting because the cast ignores thos bytes. Thanks to Stefan Trosch黷z for pointing this out.
|
|
+ t = (x ^ (x << 11));
|
|
+ x = y;
|
|
+ y = z;
|
|
+ z = w;
|
|
+ w = (w ^ (w >> 19)) ^ (t ^ (t >> 8));
|
|
+
|
|
+ buffer[i++] = (byte) w;
|
|
+ buffer[i++] = (byte) (w >> 8);
|
|
+ buffer[i++] = (byte) (w >> 16);
|
|
+ buffer[i++] = (byte) (w >> 24);
|
|
+ }
|
|
+
|
|
+ // Fill up any remaining bytes in the buffer.
|
|
+ if (i < buffer.length) {
|
|
+ // Generate 4 bytes.
|
|
+ t = (x ^ (x << 11));
|
|
+ x = y;
|
|
+ y = z;
|
|
+ z = w;
|
|
+ w = (w ^ (w >> 19)) ^ (t ^ (t >> 8));
|
|
+
|
|
+ buffer[i++] = (byte) w;
|
|
+ if (i < buffer.length) {
|
|
+ buffer[i++] = (byte) (w >> 8);
|
|
+ if (i < buffer.length) {
|
|
+ buffer[i++] = (byte) (w >> 16);
|
|
+ if (i < buffer.length) {
|
|
+ buffer[i] = (byte) (w >> 24);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ this.x = x;
|
|
+ this.y = y;
|
|
+ this.z = z;
|
|
+ this.w = w;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public double nextDouble() {
|
|
+ long t = (x ^ (x << 11));
|
|
+ x = y;
|
|
+ y = z;
|
|
+ z = w;
|
|
+
|
|
+ // Here we can gain a 2x speed improvement by generating a value that can be cast to
|
|
+ // an int instead of the more easily available uint. If we then explicitly cast to an
|
|
+ // int the compiler will then cast the int to a double to perform the multiplication,
|
|
+ // this final cast is a lot faster than casting from a uint to a double. The extra cast
|
|
+ // to an int is very fast (the allocated bits remain the same) and so the overall effect
|
|
+ // of the extra cast is a significant performance improvement.
|
|
+ //
|
|
+ // Also note that the loss of one bit of precision is equivalent to what occurs within
|
|
+ // System.Random.
|
|
+ return (REAL_UNIT_INT * (int) (0x7FFFFFFF & (w = (w ^ (w >> 19)) ^ (t ^ (t >> 8)))));
|
|
+ }
|
|
+
|
|
+ public double random() {
|
|
+ return nextDouble();
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public float nextFloat() {
|
|
+ return (float) nextDouble();
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public int nextInt() {
|
|
+ long t = (x ^ (x << 11));
|
|
+ x = y;
|
|
+ y = z;
|
|
+ z = w;
|
|
+ return (int) (0x7FFFFFFF & (w = (w ^ (w >> 19)) ^ (t ^ (t >> 8))));
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public int nextInt(int upperBound) {
|
|
+ if (upperBound < 0)
|
|
+ throw new IllegalArgumentException("upperBound must be >=0");
|
|
+
|
|
+ long t = (x ^ (x << 11));
|
|
+ x = y;
|
|
+ y = z;
|
|
+ z = w;
|
|
+
|
|
+ return (int) ((REAL_UNIT_INT * (int) (0x7FFFFFFF & (w = (w ^ (w >> 19)) ^ (t ^ (t >> 8))))) * upperBound);
|
|
+ }
|
|
+
|
|
+ public int nextInt(int lowerBound, int upperBound) {
|
|
+ if (lowerBound > upperBound)
|
|
+ throw new IllegalArgumentException("upperBound must be >=lowerBound");
|
|
+
|
|
+ long t = (x ^ (x << 11));
|
|
+ x = y;
|
|
+ y = z;
|
|
+ z = w;
|
|
+
|
|
+ // The explicit int cast before the first multiplication gives better performance.
|
|
+ // See comments in NextDouble.
|
|
+ int range = upperBound - lowerBound;
|
|
+ if (range < 0) {
|
|
+ // If range is <0 then an overflow has occured and must resort to using long integer arithmetic instead (slower).
|
|
+ // We also must use all 32 bits of precision, instead of the normal 31, which again is slower.
|
|
+ return lowerBound + (int) ((REAL_UNIT_UINT * (double) (w = (w ^ (w >> 19)) ^ (t ^ (t >> 8)))) * (double) ((long) upperBound - (long) lowerBound));
|
|
+ }
|
|
+ // 31 bits of precision will suffice if range<=int.MaxValue. This allows us to cast to an int and gain
|
|
+ // a little more performance.
|
|
+ return lowerBound + (int) ((REAL_UNIT_INT * (double) (int) (0x7FFFFFFF & (w = (w ^ (w >> 19)) ^ (t ^ (t >> 8))))) * (double) range);
|
|
+ }
|
|
+
|
|
+ public long nextUInt() {
|
|
+ long t = (x ^ (x << 11));
|
|
+ x = y;
|
|
+ y = z;
|
|
+ z = w;
|
|
+ return (w = (w ^ (w >> 19)) ^ (t ^ (t >> 8))) & (0xFFFFFFFFL);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public long nextLong() {
|
|
+ return nextUInt() << 32 + nextUInt();
|
|
+ }
|
|
+
|
|
+ double gaussNext;
|
|
+ boolean hasGaussNext;
|
|
+ final double TWOPI = Math.PI * 2;
|
|
+
|
|
+ /**
|
|
+ * Get a random number in the range [min, max) or [min, max] depending on rounding.
|
|
+ *
|
|
+ * @param min Low bound
|
|
+ * @param max High bound
|
|
+ * @return A uniformly distributed double
|
|
+ */
|
|
+ public double uniform(double min, double max) {
|
|
+ return min + (max - min) * nextDouble();
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Triangular distribution.
|
|
+ * <p/>
|
|
+ * Continuous distribution bounded by given lower and upper limits,
|
|
+ * and having a given mode value in-between.
|
|
+ * See: <a href="http://en.wikipedia.org/wiki/Triangular_distribution">Triangular distribution</a>
|
|
+ *
|
|
+ * @param low Low bound
|
|
+ * @param high High bound
|
|
+ * @param mode Mode
|
|
+ * @return A number from the triangular distribution specified
|
|
+ */
|
|
+ public double triangular(int low, int high, int mode) {
|
|
+ double u = nextDouble();
|
|
+ double c = (double) (mode - low) / (high - low);
|
|
+ if (u > c) {
|
|
+ u = 1.0 - u;
|
|
+ c = 1.0 - c;
|
|
+ int k = low;
|
|
+ low = high;
|
|
+ high = k;
|
|
+ }
|
|
+ return low + (high - low) * Math.sqrt(u * c);
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Gaussian distribution, mean is 0 and standard deviation is 1.
|
|
+ * <p/>
|
|
+ * mu is the mean, and sigma is the standard deviation.
|
|
+ *
|
|
+ * @return A double in Gaussian distribution
|
|
+ */
|
|
+ public double gauss() {
|
|
+ return nextGaussian();
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Gaussian distribution, with user-specified mean and standard deviation.
|
|
+ * <p/>
|
|
+ * mu is the mean, and sigma is the standard deviation.
|
|
+ *
|
|
+ * @return A double in Gaussian distribution
|
|
+ */
|
|
+ public double gauss(double mu, double sigma) {
|
|
+ return mu + sigma * nextGaussian();
|
|
+ }
|
|
+
|
|
+ public double gaussUnsigned(double mu, double sigma) {
|
|
+ double out = gauss(mu, sigma);
|
|
+ return out > 1 ? out : 1;
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Log normal distribution.
|
|
+ * <p/>
|
|
+ * If you take the natural logarithm of this distribution, you'll get a
|
|
+ * normal distribution with mean mu and standard deviation sigma.
|
|
+ * mu can have any value, and sigma must be greater than zero.
|
|
+ *
|
|
+ * @param mu Mean
|
|
+ * @param sigma Standard deviation
|
|
+ * @return A number from the log normal distribution specified
|
|
+ */
|
|
+ public double logNormal(double mu, double sigma) {
|
|
+ return Math.exp(gauss(mu, sigma));
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Exponential distribution.
|
|
+ * <p/>
|
|
+ * lambda is 1.0 divided by the desired mean. It should be
|
|
+ * nonzero. Returned values range from 0 to positive infinity
|
|
+ * if lambda is positive, and from negative infinity to 0
|
|
+ * if lambda is negative.
|
|
+ *
|
|
+ * @param lambda A non-zero value
|
|
+ */
|
|
+ public double exponential(double lambda) {
|
|
+ return -Math.log(1.0 - random()) / lambda;
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * Circular data distribution.
|
|
+ * <p/>
|
|
+ * If kappa is equal to zero, this distribution reduces
|
|
+ * to a uniform random angle over the range 0 to 2*pi.
|
|
+ *
|
|
+ * @param mu the mean angle, expressed in radians between 0 and 2*pi.
|
|
+ * @param kappa the concentration parameter, which must be greater than or
|
|
+ * equal to zero.
|
|
+ * @return A number from the circular data distribution specified
|
|
+ */
|
|
+ public double circularData(double mu, double kappa) {
|
|
+ if (kappa <= 1e-6)
|
|
+ return TWOPI * nextDouble();
|
|
+
|
|
+ double a = 1.0 + Math.sqrt(1.0 + 4.0 * kappa * kappa);
|
|
+ double b = (a - Math.sqrt(2.0 * a)) / (2.0 * kappa);
|
|
+ double r = (1.0 + b * b) / (2.0 * b);
|
|
+ double u1, u2, u3, f, c, z, theta = 0;
|
|
+
|
|
+ while (true) {
|
|
+ u1 = nextDouble();
|
|
+
|
|
+ z = Math.cos(Math.PI * u1);
|
|
+ f = (1.0 + r * z) / (r + z);
|
|
+ c = kappa * (r - f);
|
|
+
|
|
+ u2 = nextDouble();
|
|
+
|
|
+ if (u2 < c * (2.0 - c) || u2 <= c * Math.exp(1.0 - c))
|
|
+ break;
|
|
+
|
|
+ u3 = nextDouble();
|
|
+ if (u3 > 0.5)
|
|
+ theta = (mu % TWOPI) + Math.acos(f);
|
|
+ else
|
|
+ theta = (mu % TWOPI) - Math.acos(f);
|
|
+ }
|
|
+ return theta;
|
|
+ }
|
|
+
|
|
+
|
|
+ final double LOG4 = Math.log(4);
|
|
+ final double SG_MAGICCONST = 1.0 + Math.log(4.5);
|
|
+
|
|
+ /**
|
|
+ * Gamma distribution. Not the gamma function!
|
|
+ * Conditions on the parameters are alpha > 0 and beta > 0.
|
|
+ * <p/>
|
|
+ * The probability distribution function is:
|
|
+ * pdf(x) = (x ** (alpha - 1) * math.exp(-x / beta)) / (math.gamma(alpha) * beta ** alpha)
|
|
+ *
|
|
+ * @param alpha Alpha
|
|
+ * @param beta Beta
|
|
+ * @return A number from the gamma distribution specified
|
|
+ */
|
|
+ public double gamma(double alpha, double beta) {
|
|
+ if (alpha <= 0.0 || beta <= 0.0)
|
|
+ throw new IllegalArgumentException("alpha and beta must be > 0.0");
|
|
+
|
|
+ if (alpha > 1.0) {
|
|
+ double ainv = Math.sqrt(2.0 * alpha - 1.0);
|
|
+ double bbb = alpha - LOG4;
|
|
+ double ccc = alpha + ainv;
|
|
+ double u1, u2, v, x, z, r;
|
|
+
|
|
+ while (true) {
|
|
+ u1 = random();
|
|
+ if (!(1e-7 < u1 && u1 < .9999999))
|
|
+ continue;
|
|
+ u2 = 1.0 - random();
|
|
+ v = Math.log(u1 / (1.0 - u1)) / ainv;
|
|
+ x = alpha * Math.exp(v);
|
|
+ z = u1 * u1 * u2;
|
|
+ r = bbb + ccc * v - x;
|
|
+ if (r + SG_MAGICCONST - 4.5 * z >= 0.0 || r >= Math.log(z))
|
|
+ return x * beta;
|
|
+ }
|
|
+ } else if (alpha == 1.0) {
|
|
+ // exponential(1)
|
|
+ double u;
|
|
+ u = random();
|
|
+ while (u <= 1e-7)
|
|
+ u = random();
|
|
+ return -Math.log(u) * beta;
|
|
+ } else {
|
|
+ // alpha is between 0 and 1 (exclusive)
|
|
+ // Uses ALGORITHM GS of Statistical Computing -Kennedy & Gentle
|
|
+
|
|
+ double u, b, p, x, u1;
|
|
+ while (true) {
|
|
+ u = random();
|
|
+ b = (Math.E + alpha) / Math.E;
|
|
+ p = b * u;
|
|
+ if (p <= 1.0)
|
|
+ x = Math.pow(p, (1.0 / alpha));
|
|
+ else
|
|
+ x = -Math.log((b - p) / alpha);
|
|
+ u1 = random();
|
|
+ if (p > 1.0) {
|
|
+ if (u1 <= Math.pow(x, (alpha - 1.0)))
|
|
+ break;
|
|
+ } else if (u1 <= Math.exp(-x))
|
|
+ break;
|
|
+ }
|
|
+ return x * beta;
|
|
+ }
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/org/plazmamc/plazma/util/random/XorshiftRandomSource.java b/src/main/java/org/plazmamc/plazma/util/random/XorshiftRandomSource.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..646e9689f17211112f8d253099fb075666d942e9
|
|
--- /dev/null
|
|
+++ b/src/main/java/org/plazmamc/plazma/util/random/XorshiftRandomSource.java
|
|
@@ -0,0 +1,56 @@
|
|
+package org.plazmamc.plazma.util.random;
|
|
+
|
|
+import com.google.common.annotations.VisibleForTesting;
|
|
+import net.minecraft.core.BlockPos;
|
|
+import net.minecraft.util.Mth;
|
|
+import net.minecraft.util.RandomSource;
|
|
+import net.minecraft.world.level.levelgen.PositionalRandomFactory;
|
|
+import org.jspecify.annotations.NonNull;
|
|
+
|
|
+public class XorshiftRandomSource extends XorshiftRandom implements RandomSource {
|
|
+
|
|
+ public XorshiftRandomSource(long seed) {
|
|
+ this.setSeed(seed);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public @NonNull RandomSource fork() {
|
|
+ return new XorshiftRandomSource(this.nextLong());
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public @NonNull PositionalRandomFactory forkPositional() {
|
|
+ return new XorshiftRandomSourceFactory(this.nextLong());
|
|
+ }
|
|
+
|
|
+ private record XorshiftRandomSourceFactory(long seed) implements PositionalRandomFactory {
|
|
+ @SuppressWarnings("deprecation")
|
|
+ @Override
|
|
+ public @NonNull RandomSource at(int x, int y, int z) {
|
|
+ return new XorshiftRandomSource(Mth.getSeed(x, y, z) ^ this.seed);
|
|
+ }
|
|
+
|
|
+ @SuppressWarnings("deprecation")
|
|
+ @Override
|
|
+ public @NonNull RandomSource at(final @NonNull BlockPos pos) {
|
|
+ return new XorshiftRandomSource(Mth.getSeed(pos.getX(), pos.getY(), pos.getZ()) ^ this.seed);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public @NonNull RandomSource fromHashOf(@NonNull String seed) {
|
|
+ return new XorshiftRandomSource((long) seed.hashCode() ^ this.seed);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public @NonNull RandomSource fromSeed(final long seed) {
|
|
+ return new XorshiftRandomSource(seed);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ @VisibleForTesting
|
|
+ public void parityConfigString(@NonNull StringBuilder info) {
|
|
+ info.append("XorshiftRandomSource$XorshiftRandomSourceFactory{").append(this.seed).append("}");
|
|
+ }
|
|
+ }
|
|
+
|
|
+}
|