From faceba091c562a0b104d6d99600f1ffaee7ccd3d Mon Sep 17 00:00:00 2001 From: kralverde Date: Sat, 31 Aug 2024 15:06:41 -0400 Subject: [PATCH] implement legacy_random and java string hash --- pumpkin-core/src/random/legacy_rand.rs | 326 ++++++++++++++++++++++++ pumpkin-core/src/random/mod.rs | 85 +++++- pumpkin-core/src/random/xoroshiro128.rs | 40 +-- 3 files changed, 411 insertions(+), 40 deletions(-) create mode 100644 pumpkin-core/src/random/legacy_rand.rs diff --git a/pumpkin-core/src/random/legacy_rand.rs b/pumpkin-core/src/random/legacy_rand.rs new file mode 100644 index 00000000..f7f60ff4 --- /dev/null +++ b/pumpkin-core/src/random/legacy_rand.rs @@ -0,0 +1,326 @@ +use super::{ + gaussian::GaussianGenerator, hash_block_pos, java_string_hash, Random, RandomSplitter, +}; + +struct LegacyRand { + seed: u64, + internal_next_gaussian: f64, + internal_has_next_gaussian: bool, +} + +impl LegacyRand { + fn next_random(&mut self) -> u64 { + let l = self.seed; + let m = l.wrapping_mul(0x5DEECE66D).wrapping_add(11) & 0xFFFFFFFFFFFF; + self.seed = m; + m + } +} + +impl GaussianGenerator for LegacyRand { + fn has_next_gaussian(&self) -> bool { + self.internal_has_next_gaussian + } + + fn stored_next_gaussian(&self) -> f64 { + self.internal_next_gaussian + } + + fn set_has_next_gaussian(&mut self, value: bool) { + self.internal_has_next_gaussian = value; + } + + fn set_stored_next_gaussian(&mut self, value: f64) { + self.internal_next_gaussian = value; + } +} + +impl Random for LegacyRand { + fn from_seed(seed: u64) -> Self { + LegacyRand { + seed: (seed ^ 0x5DEECE66D) & 0xFFFFFFFFFFFF, + internal_has_next_gaussian: false, + internal_next_gaussian: 0f64, + } + } + + fn next(&mut self, bits: u64) -> u64 { + self.next_random() >> (48 - bits) + } + + fn split(&mut self) -> Self { + LegacyRand::from_seed(self.next_i64() as u64) + } + + fn next_i32(&mut self) -> i32 { + self.next(32) as i32 + } + + fn next_i64(&mut self) -> i64 { + let i = self.next_i32(); + let j = self.next_i32(); + ((i as i64) << 32).wrapping_add(j as i64) + } + + fn next_f32(&mut self) -> f32 { + self.next(24) as f32 * 5.9604645E-8f32 + } + + fn next_f64(&mut self) -> f64 { + let i = self.next(26); + let j = self.next(27); + let l = (i << 27).wrapping_add(j); + l as f64 * 1.110223E-16f32 as f64 + } + + fn next_bool(&mut self) -> bool { + self.next(1) != 0 + } + + fn next_splitter(&mut self) -> impl RandomSplitter { + LegacySplitter::new(self.next_i64() as u64) + } + + fn next_gaussian(&mut self) -> f64 { + self.calculate_gaussian() + } + + fn next_bounded_i32(&mut self, bound: i32) -> i32 { + if bound & (bound - 1) == 0 { + (bound as u64).wrapping_mul(self.next(31) >> 31) as i32 + } else { + loop { + let i = self.next(31) as i32; + let j = i % bound; + if (i - j + (bound - 1)) > 0 { + return j; + } + } + } + } +} + +struct LegacySplitter { + seed: u64, +} + +impl LegacySplitter { + fn new(seed: u64) -> Self { + LegacySplitter { seed } + } +} + +impl RandomSplitter for LegacySplitter { + fn split_u64(&self, seed: u64) -> impl Random { + LegacyRand::from_seed(seed) + } + + fn split_string(&self, seed: &str) -> impl Random { + let string_hash = java_string_hash(seed); + LegacyRand::from_seed((string_hash as u64) ^ self.seed) + } + + fn split_pos(&self, x: i32, y: i32, z: i32) -> impl Random { + let pos_hash = hash_block_pos(x, y, z); + LegacyRand::from_seed((pos_hash as u64) ^ self.seed) + } +} + +#[cfg(test)] +mod test { + use crate::random::{Random, RandomSplitter}; + + use super::LegacyRand; + + #[test] + fn test_next_i32() { + let mut rand = LegacyRand::from_seed(0); + + let values = [ + -1155484576, + -723955400, + 1033096058, + -1690734402, + -1557280266, + 1327362106, + -1930858313, + 502539523, + -1728529858, + -938301587, + ]; + + for value in values { + assert_eq!(rand.next_i32(), value); + } + } + + #[test] + fn test_next_bounded_i32() { + let mut rand = LegacyRand::from_seed(0); + + let values = [0, 13, 4, 2, 5, 8, 11, 6, 9, 14]; + + for value in values { + assert_eq!(rand.next_bounded_i32(0xf), value); + } + } + + #[test] + fn test_next_inbetween_i32() { + let mut rand = LegacyRand::from_seed(0); + + let values = [1, 5, 2, 12, 12, 6, 12, 10, 4, 3]; + + for value in values { + assert_eq!(rand.next_inbetween_i32(1, 12), value); + } + } + + #[test] + fn test_next_inbetween_exclusive_i32() { + let mut rand = LegacyRand::from_seed(0); + + let values = [1, 7, 9, 6, 7, 3, 3, 7, 3, 1]; + + for value in values { + assert_eq!(rand.next_inbetween_i32_exclusive(1, 12), value); + } + } + + #[test] + fn test_next_f64() { + let mut rand = LegacyRand::from_seed(0); + + let values = [ + 0.730967787376657, + 0.24053641567148587, + 0.6374174253501083, + 0.5504370051176339, + 0.5975452777972018, + 0.3332183994766498, + 0.3851891847407185, + 0.984841540199809, + 0.8791825178724801, + 0.9412491794821144, + ]; + + for value in values { + assert_eq!(rand.next_f64(), value); + } + } + + #[test] + fn test_next_f32() { + let mut rand = LegacyRand::from_seed(0); + + let values: [f32; 10] = [ + 0.73096776, 0.831441, 0.24053639, 0.6063452, 0.6374174, 0.30905056, 0.550437, + 0.1170066, 0.59754527, 0.7815346, + ]; + + for value in values { + assert_eq!(rand.next_f32(), value); + } + } + + #[test] + fn test_next_i64() { + let mut rand = LegacyRand::from_seed(0); + + let values: [i64; 10] = [ + -4962768465676381896, + 4437113781045784766, + -6688467811848818630, + -8292973307042192125, + -7423979211207825555, + 6146794652083548235, + 7105486291024734541, + -279624296851435688, + -2228689144322150137, + -1083761183081836303, + ]; + + for value in values { + assert_eq!(rand.next_i64(), value); + } + } + + #[test] + fn test_next_bool() { + let mut rand = LegacyRand::from_seed(0); + + let values = [ + true, true, false, true, true, false, true, false, true, true, + ]; + + for value in values { + assert_eq!(rand.next_bool(), value); + } + } + + #[test] + fn test_next_gaussian() { + let mut rand = LegacyRand::from_seed(0); + + let values = [ + 0.8025330637390305, + -0.9015460884175122, + 2.080920790428163, + 0.7637707684364894, + 0.9845745328825128, + -1.6834122587673428, + -0.027290262907887285, + 0.11524570286202315, + -0.39016704137993774, + -0.643388813126449, + ]; + + for value in values { + assert_eq!(rand.next_gaussian(), value); + } + } + + #[test] + fn test_next_triangular() { + let mut rand = LegacyRand::from_seed(0); + + let values = [ + 124.52156858525856, + 104.34902101162372, + 113.2163439160276, + 70.01738222704547, + 96.89666691951828, + 107.30284075808541, + 106.16817675813144, + 79.11264482608078, + 73.96721613927062, + 81.72419521080646, + ]; + + for value in values { + assert_eq!(rand.next_triangular(100f64, 50f64), value); + } + } + + #[test] + fn test_split() { + let mut original_rand = LegacyRand::from_seed(0); + let mut new_rand = original_rand.split(); + + { + let splitter = new_rand.next_splitter(); + + let mut rand1 = splitter.split_string("TEST STRING"); + assert_eq!(rand1.next_i32(), -1170413697); + + let mut rand2 = splitter.split_u64(10); + assert_eq!(rand2.next_i32(), -1157793070); + + let mut rand3 = splitter.split_pos(1, 11, -111); + assert_eq!(rand3.next_i32(), -1213890343); + } + + assert_eq!(original_rand.next_i32(), 1033096058); + assert_eq!(new_rand.next_i32(), -888301832); + } +} diff --git a/pumpkin-core/src/random/mod.rs b/pumpkin-core/src/random/mod.rs index f56a4c18..b5389aa4 100644 --- a/pumpkin-core/src/random/mod.rs +++ b/pumpkin-core/src/random/mod.rs @@ -1,6 +1,6 @@ -pub mod xoroshiro128; - mod gaussian; +pub mod legacy_rand; +pub mod xoroshiro128; pub trait Random { fn from_seed(seed: u64) -> Self; @@ -35,7 +35,7 @@ pub trait Random { fn skip(&mut self, count: i32) { for _ in 0..count { - self.next_i32(); + self.next_i64(); } } @@ -51,3 +51,82 @@ pub trait RandomSplitter { fn split_pos(&self, x: i32, y: i32, z: i32) -> impl Random; } + +fn hash_block_pos(x: i32, y: i32, z: i32) -> i64 { + let l = (x.wrapping_mul(3129871) as i64) ^ ((z as i64).wrapping_mul(116129781i64)) ^ (y as i64); + let l = l + .wrapping_mul(l) + .wrapping_mul(42317861i64) + .wrapping_add(l.wrapping_mul(11i64)); + l >> 16 +} + +fn java_string_hash(string: &str) -> u32 { + // All byte values of latin1 align with + // the values of U+0000 - U+00FF making this code + // equivalent to both java hash implementations + + let mut result = 0u32; + + for char_encoding in string.encode_utf16() { + result = 31u32 + .wrapping_mul(result) + .wrapping_add(char_encoding as u32); + } + result +} + +#[cfg(test)] +mod tests { + + use crate::random::java_string_hash; + + use super::hash_block_pos; + + #[test] + fn block_position_hash() { + let values: [((i32, i32, i32), i64); 8] = [ + ((0, 0, 0), 0), + ((1, 1, 1), 60311958971344), + ((4, 4, 4), 120566413180880), + ((25, 25, 25), 111753446486209), + ((676, 676, 676), 75210837988243), + ((458329, 458329, 458329), -43764888250), + ((-387008604, -387008604, -387008604), 8437923733503), + ((176771161, 176771161, 176771161), 18421337580760), + ]; + + for ((x, y, z), value) in values { + assert_eq!(hash_block_pos(x, y, z), value); + } + } + + #[test] + fn test_java_string_hash() { + let values = [ + ("", 0), + ("1", 49), + ("TEST", 2571410), + ("TEST1", 79713759), + ("TEST0123456789", 506557463), + ( + " !\"#$%&'()*+,-./0123456789:\ + ;<=>?@ABCDEFGHIJKLMNOPQRST\ + UVWXYZ[\\]^_`abcdefghijklm\ + nopqrstuvwxyz{|}~¡¢£¤¥¦§¨©\ + ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄ\ + ÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞ\ + ßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþ", + (-1992287231i32) as u32, + ), + ("求同存异", 847053876), + // This might look wierd because hebrew is text is right to left + ("אבְּרֵאשִׁ֖ית בָּרָ֣א אֱלֹהִ֑ים אֵ֥ת הַשָּׁמַ֖יִם וְאֵ֥ת הָאָֽרֶץ:", 1372570871), + ("संस्कृत-", 1748614838), + ]; + + for (string, value) in values { + assert_eq!(java_string_hash(string), value); + } + } +} diff --git a/pumpkin-core/src/random/xoroshiro128.rs b/pumpkin-core/src/random/xoroshiro128.rs index dea1255a..e8d7e4f1 100644 --- a/pumpkin-core/src/random/xoroshiro128.rs +++ b/pumpkin-core/src/random/xoroshiro128.rs @@ -1,4 +1,4 @@ -use super::{gaussian::GaussianGenerator, Random, RandomSplitter}; +use super::{gaussian::GaussianGenerator, hash_block_pos, Random, RandomSplitter}; pub struct Xoroshiro { lo: u64, @@ -130,12 +130,6 @@ impl Random for Xoroshiro { fn next_gaussian(&mut self) -> f64 { self.calculate_gaussian() } - - fn skip(&mut self, count: i32) { - for _ in 0..count { - self.next_random(); - } - } } pub struct XoroshiroSplitter { @@ -143,19 +137,9 @@ pub struct XoroshiroSplitter { hi: u64, } -fn hash_pos(x: i32, y: i32, z: i32) -> i64 { - let l = - ((x.wrapping_mul(3129871)) as i64) ^ ((z as i64).wrapping_mul(116129781i64)) ^ (y as i64); - let l = l - .wrapping_mul(l) - .wrapping_mul(42317861i64) - .wrapping_add(l.wrapping_mul(11i64)); - l >> 16 -} - impl RandomSplitter for XoroshiroSplitter { fn split_pos(&self, x: i32, y: i32, z: i32) -> impl Random { - let l = hash_pos(x, y, z) as u64; + let l = hash_block_pos(x, y, z) as u64; let m = l ^ self.lo; Xoroshiro::new(m, self.hi) } @@ -177,28 +161,10 @@ impl RandomSplitter for XoroshiroSplitter { mod tests { use crate::random::{Random, RandomSplitter}; - use super::{hash_pos, mix_stafford_13, Xoroshiro}; + use super::{mix_stafford_13, Xoroshiro}; // Values checked against results from the equivalent Java source - #[test] - fn block_position_hash() { - let values: [((i32, i32, i32), i64); 8] = [ - ((0, 0, 0), 0), - ((1, 1, 1), 60311958971344), - ((4, 4, 4), 120566413180880), - ((25, 25, 25), 111753446486209), - ((676, 676, 676), 75210837988243), - ((458329, 458329, 458329), -43764888250), - ((-387008604, -387008604, -387008604), 8437923733503), - ((176771161, 176771161, 176771161), 18421337580760), - ]; - - for ((x, y, z), value) in values { - assert_eq!(hash_pos(x, y, z), value); - } - } - #[test] fn test_mix_stafford_13() { let values: [(u64, i64); 31] = [