From faceba091c562a0b104d6d99600f1ffaee7ccd3d Mon Sep 17 00:00:00 2001 From: kralverde Date: Sat, 31 Aug 2024 15:06:41 -0400 Subject: [PATCH 1/4] 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 000000000..f7f60ff41 --- /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 f56a4c182..b5389aa49 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 dea1255ad..e8d7e4f12 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] = [ From 14e59dfb8ff5ceab946fb9ea531d21f895b7f80e Mon Sep 17 00:00:00 2001 From: we sell insurance Date: Sat, 31 Aug 2024 17:24:43 -0500 Subject: [PATCH 2/4] Create Identifer to help keep track of plugin resources. --- Cargo.lock | 9 +++++---- pumpkin-plugin/Cargo.toml | 3 ++- pumpkin-plugin/src/api/identifer.rs | 15 +++++++++++++++ pumpkin-plugin/src/api/mod.rs | 1 + pumpkin-plugin/src/lib.rs | 2 ++ 5 files changed, 25 insertions(+), 5 deletions(-) create mode 100644 pumpkin-plugin/src/api/identifer.rs create mode 100644 pumpkin-plugin/src/api/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 990b46cf0..daf2d714a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1972,6 +1972,7 @@ version = "0.1.0" dependencies = [ "extism", "log", + "serde", ] [[package]] @@ -2404,9 +2405,9 @@ checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] name = "serde" -version = "1.0.205" +version = "1.0.209" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e33aedb1a7135da52b7c21791455563facbbcc43d0f0f66165b42c21b3dfb150" +checksum = "99fce0ffe7310761ca6bf9faf5115afbc19688edd00171d81b1bb1b116c63e09" dependencies = [ "serde_derive", ] @@ -2422,9 +2423,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.205" +version = "1.0.209" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "692d6f5ac90220161d6774db30c662202721e64aed9058d2c394f451261420c1" +checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170" dependencies = [ "proc-macro2", "quote", diff --git a/pumpkin-plugin/Cargo.toml b/pumpkin-plugin/Cargo.toml index a3d6bea27..e757c6539 100644 --- a/pumpkin-plugin/Cargo.toml +++ b/pumpkin-plugin/Cargo.toml @@ -5,4 +5,5 @@ edition.workspace = true [dependencies] extism = "1.5.0" -log.workspace = true \ No newline at end of file +log.workspace = true +serde ={ version = "1.0.209", features = ["derive"] } \ No newline at end of file diff --git a/pumpkin-plugin/src/api/identifer.rs b/pumpkin-plugin/src/api/identifer.rs new file mode 100644 index 000000000..cdfce0e47 --- /dev/null +++ b/pumpkin-plugin/src/api/identifer.rs @@ -0,0 +1,15 @@ +use extism::{convert::Msgpack, host_fn, FromBytes, ToBytes}; +use serde::{Deserialize, Serialize}; + +/// Used to keep track of things created by plugins. +/// This can include things like events, commands, etc. +#[derive(Hash, PartialEq, Eq, ToBytes, FromBytes, Serialize, Deserialize)] +#[encoding(Msgpack)] +struct Identifier { + namespace: String, + path: String, +} + +host_fn!(new(namespace: String, path: String) -> Result { + Ok(Identifier { namespace, path }) +}); diff --git a/pumpkin-plugin/src/api/mod.rs b/pumpkin-plugin/src/api/mod.rs new file mode 100644 index 000000000..e9cb03788 --- /dev/null +++ b/pumpkin-plugin/src/api/mod.rs @@ -0,0 +1 @@ +mod identifer; diff --git a/pumpkin-plugin/src/lib.rs b/pumpkin-plugin/src/lib.rs index 3ac56bf6f..66865e2c0 100644 --- a/pumpkin-plugin/src/lib.rs +++ b/pumpkin-plugin/src/lib.rs @@ -1,3 +1,5 @@ +mod api; + use std::path::Path; use extism::{Manifest, Plugin, Wasm}; From 5ab877a40a0003cead2753ea3c9354e09dc675c7 Mon Sep 17 00:00:00 2001 From: we sell insurance Date: Sun, 1 Sep 2024 08:16:43 -0500 Subject: [PATCH 3/4] Add TODO comment for encoding --- pumpkin-plugin/src/api/identifer.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pumpkin-plugin/src/api/identifer.rs b/pumpkin-plugin/src/api/identifer.rs index cdfce0e47..886282bad 100644 --- a/pumpkin-plugin/src/api/identifer.rs +++ b/pumpkin-plugin/src/api/identifer.rs @@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize}; /// Used to keep track of things created by plugins. /// This can include things like events, commands, etc. #[derive(Hash, PartialEq, Eq, ToBytes, FromBytes, Serialize, Deserialize)] -#[encoding(Msgpack)] +#[encoding(Msgpack)] // TODO: Switch to protocal buffers for smaller size struct Identifier { namespace: String, path: String, From 0a02c28b26d7de11b125c4d1af24c7d290f903ef Mon Sep 17 00:00:00 2001 From: we sell insurance Date: Sun, 1 Sep 2024 12:26:48 -0500 Subject: [PATCH 4/4] Move serde into workspace --- Cargo.toml | 1 + pumpkin-core/Cargo.toml | 2 +- pumpkin-plugin/Cargo.toml | 2 +- pumpkin-protocol/Cargo.toml | 2 +- pumpkin-registry/Cargo.toml | 2 +- pumpkin-world/Cargo.toml | 2 +- pumpkin/Cargo.toml | 2 +- 7 files changed, 7 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3fbea597f..e52fe3294 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,3 +17,4 @@ tokio = { version = "1.39.2", features = ["net", "macros", "rt-multi-thread", "f rayon = "1.10.0" uuid = { version = "1.10.0", features = ["serde", "v3"] } derive_more = { version = "1.0.0", features = ["full"] } +serde = { version = "1.0", features = ["derive"] } diff --git a/pumpkin-core/Cargo.toml b/pumpkin-core/Cargo.toml index 6bd39248b..f2a86dcd1 100644 --- a/pumpkin-core/Cargo.toml +++ b/pumpkin-core/Cargo.toml @@ -4,7 +4,7 @@ version.workspace = true edition.workspace = true [dependencies] -serde = { version = "1.0", features = ["derive"] } +serde.workspace = true fastnbt = { git = "https://github.com/owengage/fastnbt.git" } uuid.workspace = true colored = "2" diff --git a/pumpkin-plugin/Cargo.toml b/pumpkin-plugin/Cargo.toml index e757c6539..558ce3b4c 100644 --- a/pumpkin-plugin/Cargo.toml +++ b/pumpkin-plugin/Cargo.toml @@ -6,4 +6,4 @@ edition.workspace = true [dependencies] extism = "1.5.0" log.workspace = true -serde ={ version = "1.0.209", features = ["derive"] } \ No newline at end of file +serde.workspace = true diff --git a/pumpkin-protocol/Cargo.toml b/pumpkin-protocol/Cargo.toml index e87e51557..b9d59e16f 100644 --- a/pumpkin-protocol/Cargo.toml +++ b/pumpkin-protocol/Cargo.toml @@ -12,7 +12,7 @@ bytes = "1.7" uuid.workspace = true -serde = { version = "1.0", features = ["derive"] } +serde.workspace = true # to parse strings to json responses serde_json = "1.0" diff --git a/pumpkin-registry/Cargo.toml b/pumpkin-registry/Cargo.toml index f1e53cba3..8ba0b9457 100644 --- a/pumpkin-registry/Cargo.toml +++ b/pumpkin-registry/Cargo.toml @@ -11,4 +11,4 @@ pumpkin-core = { path = "../pumpkin-core"} fastnbt = { git = "https://github.com/owengage/fastnbt.git" } fastsnbt = "0.2" -serde = { version = "1.0", features = ["derive"] } +serde.workspace = true diff --git a/pumpkin-world/Cargo.toml b/pumpkin-world/Cargo.toml index 4008eaa84..687c60b58 100644 --- a/pumpkin-world/Cargo.toml +++ b/pumpkin-world/Cargo.toml @@ -14,7 +14,7 @@ itertools = "0.13.0" thiserror = "1.0.63" futures = "0.3.30" flate2 = "1.0.33" -serde = { version = "1.0", features = ["derive"] } +serde.workspace = true lazy_static = "1.5.0" serde_json = "1.0" static_assertions = "1.1.0" diff --git a/pumpkin/Cargo.toml b/pumpkin/Cargo.toml index 0494fc462..ef2d4371f 100644 --- a/pumpkin/Cargo.toml +++ b/pumpkin/Cargo.toml @@ -18,7 +18,7 @@ pumpkin-protocol = { path = "../pumpkin-protocol"} pumpkin-registry = { path = "../pumpkin-registry"} # config -serde = { version = "1.0", features = ["derive"] } +serde.workspace = true serde_json = "1.0" toml = "0.8.19"