Speed up validation by precalculating some stuff and toing a fast return wherever possible
This commit is contained in:
		| @@ -1,12 +1,12 @@ | ||||
| use std::{fs::File, path::PathBuf, sync::LazyLock}; | ||||
| use std::{fs::File, path::PathBuf, sync::LazyLock, time::Instant}; | ||||
|  | ||||
| use az::{Az, WrappingAs}; | ||||
| use battlesnake::types::{Coord, Direction, wire::Request}; | ||||
| use bytemuck::cast_slice; | ||||
| use clap::Parser; | ||||
| use flamer::flame; | ||||
| //use flamer::flame; | ||||
| use hashbrown::HashMap; | ||||
| use log::{debug, error, info, trace, warn}; | ||||
| use log::{debug, error, info, trace}; | ||||
| use memmap2::{Mmap, MmapOptions}; | ||||
|  | ||||
| type DynError = Box<dyn std::error::Error>; | ||||
| @@ -23,7 +23,7 @@ struct Args { | ||||
|     file: PathBuf, | ||||
| } | ||||
|  | ||||
| #[flame] | ||||
| //#[flame] | ||||
| fn main() -> Result<(), DynError> { | ||||
|     env_logger::init(); | ||||
|  | ||||
| @@ -39,38 +39,58 @@ fn main() -> Result<(), DynError> { | ||||
|     let _ = *CHAIN; | ||||
|  | ||||
|     let mut rand = SeedRand::new(0); | ||||
|     let prepared = PreparedGame::prepare(&requests); | ||||
|  | ||||
|     if let Some(seed) = args.seed { | ||||
|         if validate(&mut rand, &requests, seed, true) { | ||||
|         info!("Validating using old method"); | ||||
|         let start = Instant::now(); | ||||
|         let valid = validate(&mut rand, &requests, seed, true); | ||||
|         let elapsed = start.elapsed(); | ||||
|         info!("Validation took {elapsed:?}"); | ||||
|         if valid { | ||||
|             println!("{seed} is the correct seed for this game"); | ||||
|         } else { | ||||
|             println!("{seed} is not the correct seed for this game"); | ||||
|         } | ||||
|         info!("Validating using prepared method"); | ||||
|         let start = Instant::now(); | ||||
|         let valid = prepared.validate(&mut rand, seed, true); | ||||
|         let elapsed = start.elapsed(); | ||||
|         info!("Validation took {elapsed:?}"); | ||||
|         if valid { | ||||
|             println!("{seed} is the correct seed for this game"); | ||||
|         } else { | ||||
|             println!("{seed} is not the correct seed for this game"); | ||||
|         } | ||||
|     } else { | ||||
|         info!("Trying every seed from 1 to {}", i32::MAX); | ||||
|         let start = Instant::now(); | ||||
|         for seed in 1..i32::MAX { | ||||
|             if seed == 100 { | ||||
|                 break; | ||||
|             } | ||||
|             if seed % 1_000_000 == 0 { | ||||
|                 debug!("Checking {seed:>10}"); | ||||
|             } | ||||
|             if validate(&mut rand, &requests, i64::from(seed), false) { | ||||
|             if prepared.validate(&mut rand, i64::from(seed), false) { | ||||
|                 let elapsed = start.elapsed(); | ||||
|                 info!( | ||||
|                     "Trying {seed} seeds took {elapsed:?} ({:?} / seed)", | ||||
|                     elapsed / seed.az() | ||||
|                 ); | ||||
|                 println!("The correct seed is {seed}"); | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     flame::dump_html(File::create("flamegraph.html")?)?; | ||||
|     //flame::dump_html(File::create("flamegraph.html")?)?; | ||||
|  | ||||
|     Ok(()) | ||||
| } | ||||
|  | ||||
| #[allow(clippy::too_many_lines)] | ||||
| #[flame] | ||||
| //#[flame] | ||||
| fn validate(input_rand: &mut SeedRand, requests: &[Request], seed: i64, log: bool) -> bool { | ||||
|     if log { | ||||
|         info!("Checking starting positions"); | ||||
|         debug!("Checking starting positions"); | ||||
|     } | ||||
|     let request = &requests[0]; | ||||
|     let rand = get_rand(input_rand, seed, 0); | ||||
| @@ -90,12 +110,8 @@ fn validate(input_rand: &mut SeedRand, requests: &[Request], seed: i64, log: boo | ||||
|         Coord { x: mx, y: md }, | ||||
|     ]; | ||||
|  | ||||
|     rand.shuffle(corner_points.len(), |i, j| { | ||||
|         corner_points.swap(i, j); | ||||
|     }); | ||||
|     rand.shuffle(cardinal_points.len(), |i, j| { | ||||
|         cardinal_points.swap(i, j); | ||||
|     }); | ||||
|     rand.shuffle(&mut corner_points); | ||||
|     rand.shuffle(&mut cardinal_points); | ||||
|  | ||||
|     let mut start_points = Vec::with_capacity(corner_points.len() + cardinal_points.len()); | ||||
|     if rand.int_n(2) == 0 { | ||||
| @@ -106,12 +122,12 @@ fn validate(input_rand: &mut SeedRand, requests: &[Request], seed: i64, log: boo | ||||
|         start_points.extend_from_slice(&corner_points); | ||||
|     } | ||||
|  | ||||
|     for i in 0..request.board.snakes.len() { | ||||
|         if start_points[i] != request.board.snakes[i].head { | ||||
|     for (snake, start_point) in request.board.snakes.iter().zip(start_points.iter()) { | ||||
|         if *start_point != snake.head { | ||||
|             if log { | ||||
|                 error!( | ||||
|                     "Starting position is not the same: {} != {}", | ||||
|                     start_points[i], request.board.snakes[i].head | ||||
|                     start_point, snake.head | ||||
|                 ); | ||||
|             } | ||||
|             return false; | ||||
| @@ -119,22 +135,16 @@ fn validate(input_rand: &mut SeedRand, requests: &[Request], seed: i64, log: boo | ||||
|     } | ||||
|  | ||||
|     if log { | ||||
|         info!("Checking food placement"); | ||||
|         debug!("Checking food placement"); | ||||
|     } | ||||
|     for i in 1..requests.len() { | ||||
|         let previous_request = &requests[i - 1]; | ||||
|         let request = &requests[i]; | ||||
|         if previous_request.turn + 1 != request.turn { | ||||
|             if log { | ||||
|                 warn!( | ||||
|                     "nonconsecutive requests: {} + 1 != {}", | ||||
|                     previous_request.turn, request.turn | ||||
|                 ); | ||||
|             } | ||||
|             continue; | ||||
|         } | ||||
|         if log { | ||||
|             info!("Checking turn {}", request.turn); | ||||
|             debug!("Checking turn {}", request.turn); | ||||
|         } | ||||
|         let rand = get_rand(input_rand, seed, i64::from(request.turn - 1)); | ||||
|         let mut forged_request = request.clone(); | ||||
| @@ -160,9 +170,6 @@ fn validate(input_rand: &mut SeedRand, requests: &[Request], seed: i64, log: boo | ||||
|                 &forged_request, | ||||
|             )); | ||||
|         } | ||||
|         if log { | ||||
|             debug!("snake head: {:?}", request.board.snakes[0].head); | ||||
|         } | ||||
|         if forged_request.board.food != request.board.food { | ||||
|             let forged: Vec<_> = forged_request | ||||
|                 .board | ||||
| @@ -190,14 +197,216 @@ fn validate(input_rand: &mut SeedRand, requests: &[Request], seed: i64, log: boo | ||||
|     true | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone)] | ||||
| struct PreparedGame { | ||||
|     corners: [Option<u8>; 4], | ||||
|     cardinals: [Option<u8>; 4], | ||||
|     corner_first: bool, | ||||
|     food_spawns: Vec<Option<FoodSpawn>>, | ||||
|     food_chance: u8, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, PartialEq, Eq, Clone, Copy)] | ||||
| enum FoodSpawn { | ||||
|     Forced { selected: u16, free_spots: u16 }, | ||||
|     Random { selected: u16, free_spots: u16 }, | ||||
| } | ||||
|  | ||||
| impl PreparedGame { | ||||
|     fn prepare(requests: &[Request]) -> Self { | ||||
|         debug!("Preparing initial board state"); | ||||
|         let request = &requests[0]; | ||||
|         assert_eq!(request.turn, 0); | ||||
|         let mn = 1; | ||||
|         let md = (request.board.width - 1) / 2; | ||||
|         let mx = request.board.width - 2; | ||||
|         let corners = [ | ||||
|             Coord { x: mn, y: mn }, | ||||
|             Coord { x: mn, y: mx }, | ||||
|             Coord { x: mx, y: mn }, | ||||
|             Coord { x: mx, y: mx }, | ||||
|         ]; | ||||
|         let cardinals = [ | ||||
|             Coord { x: mn, y: md }, | ||||
|             Coord { x: md, y: mn }, | ||||
|             Coord { x: md, y: mx }, | ||||
|             Coord { x: mx, y: md }, | ||||
|         ]; | ||||
|         let corner_first = corners.contains(&request.board.snakes[0].head); | ||||
|         let corners = corners.map(|start| { | ||||
|             request | ||||
|                 .board | ||||
|                 .snakes | ||||
|                 .iter() | ||||
|                 .position(|snake| snake.head == start) | ||||
|                 .map(|pos| (pos % 4).az()) | ||||
|         }); | ||||
|         let cardinals = cardinals.map(|start| { | ||||
|             request | ||||
|                 .board | ||||
|                 .snakes | ||||
|                 .iter() | ||||
|                 .position(|snake| snake.head == start) | ||||
|                 .map(|pos| (pos % 4).az()) | ||||
|         }); | ||||
|  | ||||
|         debug!("Preparing food placement"); | ||||
|         let food_chance = request.game.ruleset.settings.food_spawn_chance; | ||||
|         let food_min = request.game.ruleset.settings.minimum_food; | ||||
|  | ||||
|         let food_spawns = requests | ||||
|             .windows(2) | ||||
|             .filter_map(|window| { | ||||
|                 if let [previous, next] = window { | ||||
|                     if previous.turn + 1 == next.turn { | ||||
|                         Some((previous, next)) | ||||
|                     } else { | ||||
|                         None | ||||
|                     } | ||||
|                 } else { | ||||
|                     error!("Windows didn't return 2 requests: {requests:#?}"); | ||||
|                     None | ||||
|                 } | ||||
|             }) | ||||
|             .map(|(previous, next)| { | ||||
|                 // no new food, no food eaten | ||||
|                 if previous.board.food == next.board.food { | ||||
|                     return None; | ||||
|                 } | ||||
|  | ||||
|                 let diff: Vec<_> = next | ||||
|                     .board | ||||
|                     .food | ||||
|                     .iter() | ||||
|                     .filter(|next| !previous.board.food.contains(next)) | ||||
|                     .collect(); | ||||
|                 if diff.is_empty() { | ||||
|                     // We didn't spawn any food. It was only eaten | ||||
|                     return None; | ||||
|                 } | ||||
|  | ||||
|                 let mut tmp = next.clone(); | ||||
|                 tmp.board.food.clone_from(&previous.board.food); | ||||
|                 let heads: Vec<_> = tmp.board.snakes.iter().map(|snake| snake.head).collect(); | ||||
|                 tmp.board.food.retain(|coord| !heads.contains(coord)); | ||||
|                 let free_spots = get_unoccupied_points(&tmp); | ||||
|  | ||||
|                 let selected = free_spots | ||||
|                     .iter() | ||||
|                     .position(|spot| spot == diff[0]) | ||||
|                     .unwrap() | ||||
|                     .az(); | ||||
|                 let free_spots = free_spots.len().az(); | ||||
|  | ||||
|                 if next.board.food.len() == usize::from(food_min) { | ||||
|                     Some(FoodSpawn::Forced { | ||||
|                         selected, | ||||
|                         free_spots, | ||||
|                     }) | ||||
|                 } else { | ||||
|                     Some(FoodSpawn::Random { | ||||
|                         selected, | ||||
|                         free_spots, | ||||
|                     }) | ||||
|                 } | ||||
|             }) | ||||
|             .collect(); | ||||
|  | ||||
|         Self { | ||||
|             corners, | ||||
|             cardinals, | ||||
|             corner_first, | ||||
|             food_spawns, | ||||
|             food_chance, | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn validate(&self, input_rand: &mut SeedRand, seed: i64, log: bool) -> bool { | ||||
|         if log { | ||||
|             debug!("Checking Start positions"); | ||||
|         } | ||||
|         let mut corners = [None; 4]; | ||||
|         for (a, b) in self.corners.iter().zip(corners.iter_mut()) { | ||||
|             *b = a.map(usize::from); | ||||
|         } | ||||
|         let mut cardinals = [None; 4]; | ||||
|         for (a, b) in self.cardinals.iter().zip(cardinals.iter_mut()) { | ||||
|             *b = a.map(usize::from); | ||||
|         } | ||||
|         let rand = get_rand(input_rand, seed, 0); | ||||
|         if !rand.shuffle_check(&mut corners) { | ||||
|             if log { | ||||
|                 error!("Corners are shuffled wrong"); | ||||
|             } | ||||
|             return false; | ||||
|         } | ||||
|         if !rand.shuffle_check(&mut cardinals) { | ||||
|             if log { | ||||
|                 error!("Cardinals are shuffled wrong"); | ||||
|             } | ||||
|             return false; | ||||
|         } | ||||
|         if rand.int_n(2) == usize::from(self.corner_first) { | ||||
|             if log { | ||||
|                 error!("Corners and cardinals are flipped: {}", self.corner_first); | ||||
|             } | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         self.food_spawns.iter().enumerate().all(|(turn, spawn)| { | ||||
|             if log { | ||||
|                 debug!("Checking turn {turn}"); | ||||
|             } | ||||
|             let rand = get_rand(input_rand, seed, (turn).az()); | ||||
|  | ||||
|             match spawn { | ||||
|                 Some( | ||||
|                     spawn @ (FoodSpawn::Forced { | ||||
|                         selected, | ||||
|                         free_spots, | ||||
|                     } | ||||
|                     | FoodSpawn::Random { | ||||
|                         selected, | ||||
|                         free_spots, | ||||
|                     }), | ||||
|                 ) => { | ||||
|                     if matches!(spawn, FoodSpawn::Random { .. }) | ||||
|                         && 100 - rand.int_n(100) >= usize::from(self.food_chance) | ||||
|                     { | ||||
|                         if log { | ||||
|                             error!("Food not spawned, when some should have been spawned"); | ||||
|                         } | ||||
|                         return false; | ||||
|                     } | ||||
|  | ||||
|                     let mut foods = vec![None; usize::from(*free_spots)]; | ||||
|                     foods[usize::from(*selected)] = Some(0); | ||||
|                     let correct = rand.shuffle_check(&mut foods); | ||||
|                     if log && !correct { | ||||
|                         error!("Wrong food selected: {foods:?}"); | ||||
|                     } | ||||
|                     correct | ||||
|                 } | ||||
|                 None => { | ||||
|                     let correct = 100 - rand.int_n(100) >= usize::from(self.food_chance); | ||||
|                     if log && !correct { | ||||
|                         error!("Food spawned, when none should have been spawned"); | ||||
|                     } | ||||
|                     correct | ||||
|                 } | ||||
|             } | ||||
|         }) | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[must_use] | ||||
| #[flame] | ||||
| //#[flame] | ||||
| fn get_rand(rand: &mut SeedRand, seed: i64, turn: i64) -> &mut SeedRand { | ||||
|     rand.rand.seed(seed + turn); | ||||
|     rand | ||||
| } | ||||
|  | ||||
| #[flame] | ||||
| //#[flame] | ||||
| fn check_food_needing_placement(rand: &mut impl Rand, request: &Request) -> usize { | ||||
|     let min_food = request.game.ruleset.settings.minimum_food as usize; | ||||
|     let food_spawn_chance = request.game.ruleset.settings.food_spawn_chance as usize; | ||||
| @@ -213,13 +422,13 @@ fn check_food_needing_placement(rand: &mut impl Rand, request: &Request) -> usiz | ||||
|     0 | ||||
| } | ||||
|  | ||||
| #[flame] | ||||
| //#[flame] | ||||
| fn place_food_randomly(rand: &mut impl Rand, n: usize, request: &Request) -> Vec<Coord> { | ||||
|     let unoccupied_points = get_unoccupied_points(request); | ||||
|     place_food_randomly_at_positions(rand, n, unoccupied_points) | ||||
| } | ||||
|  | ||||
| #[flame] | ||||
| //#[flame] | ||||
| fn place_food_randomly_at_positions( | ||||
|     rand: &mut impl Rand, | ||||
|     n: usize, | ||||
| @@ -227,9 +436,7 @@ fn place_food_randomly_at_positions( | ||||
| ) -> Vec<Coord> { | ||||
|     let n = n.min(positions.len()); | ||||
|  | ||||
|     rand.shuffle(positions.len(), |i, j| { | ||||
|         positions.swap(i, j); | ||||
|     }); | ||||
|     rand.shuffle(&mut positions); | ||||
|  | ||||
|     let mut out = Vec::new(); | ||||
|     let mut i = 0; | ||||
| @@ -240,7 +447,7 @@ fn place_food_randomly_at_positions( | ||||
|     out | ||||
| } | ||||
|  | ||||
| #[flame] | ||||
| //#[flame] | ||||
| fn get_unoccupied_points(request: &Request) -> Vec<Coord> { | ||||
|     let possible_moves: Vec<_> = request | ||||
|         .board | ||||
| @@ -275,7 +482,10 @@ trait Rand { | ||||
|     fn range(&mut self, min: usize, max: usize) -> usize; | ||||
|     /// shuffle an array of n values using the swap function | ||||
|     #[allow(unused)] | ||||
|     fn shuffle(&mut self, n: usize, swap: impl FnMut(usize, usize)); | ||||
|     fn shuffle<T>(&mut self, data: &mut [T]); | ||||
|     /// checks shuffle an array of n values using the swap function | ||||
|     #[allow(unused)] | ||||
|     fn shuffle_check(&mut self, data: &mut [Option<usize>]) -> bool; | ||||
| } | ||||
|  | ||||
| #[derive(Debug, PartialEq, Eq, Clone)] | ||||
| @@ -284,7 +494,7 @@ struct SeedRand { | ||||
| } | ||||
|  | ||||
| impl SeedRand { | ||||
|     #[flame] | ||||
|     //#[flame] | ||||
|     fn new(seed: i64) -> Self { | ||||
|         Self { | ||||
|             rand: GoRand::new(GoSource::new(seed)), | ||||
| @@ -293,19 +503,23 @@ impl SeedRand { | ||||
| } | ||||
|  | ||||
| impl Rand for SeedRand { | ||||
|     #[flame] | ||||
|     //#[flame] | ||||
|     fn int_n(&mut self, n: usize) -> usize { | ||||
|         self.rand.int_n(n) | ||||
|     } | ||||
|  | ||||
|     #[flame] | ||||
|     //#[flame] | ||||
|     fn range(&mut self, min: usize, max: usize) -> usize { | ||||
|         self.rand.int_n(max - min + 1) + min | ||||
|     } | ||||
|  | ||||
|     #[flame] | ||||
|     fn shuffle(&mut self, n: usize, swap: impl FnMut(usize, usize)) { | ||||
|         self.rand.shuffle(n, swap); | ||||
|     //#[flame] | ||||
|     fn shuffle<T>(&mut self, data: &mut [T]) { | ||||
|         self.rand.shuffle(data); | ||||
|     } | ||||
|  | ||||
|     fn shuffle_check(&mut self, data: &mut [Option<usize>]) -> bool { | ||||
|         self.rand.shuffle_check(data) | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -315,26 +529,26 @@ struct GoRand { | ||||
| } | ||||
|  | ||||
| impl GoRand { | ||||
|     #[flame] | ||||
|     //#[flame] | ||||
|     const fn new(src: GoSource) -> Self { | ||||
|         Self { src } | ||||
|     } | ||||
|  | ||||
|     // Seed uses the provided seed value to initialize the generator to a deterministic state. | ||||
|     // Seed should not be called concurrently with any other [Rand] method. | ||||
|     #[flame] | ||||
|     //#[flame] | ||||
|     fn seed(&mut self, seed: i64) { | ||||
|         self.src.seed(seed); | ||||
|     } | ||||
|  | ||||
|     /// returns a non-negative pseudo-random 63-bit integer | ||||
|     #[flame] | ||||
|     //#[flame] | ||||
|     fn int63(&mut self) -> u64 { | ||||
|         self.src.int63() | ||||
|     } | ||||
|  | ||||
|     /// returns a non-negative pseudo-random 63-bit integer | ||||
|     #[flame] | ||||
|     //#[flame] | ||||
|     fn int31(&mut self) -> u32 { | ||||
|         (self.src.int63() >> 32) as u32 | ||||
|     } | ||||
| @@ -343,7 +557,7 @@ impl GoRand { | ||||
|     /// | ||||
|     /// # Panics | ||||
|     /// if n <= 0 | ||||
|     #[flame] | ||||
|     //#[flame] | ||||
|     fn int63_n(&mut self, n: u64) -> u64 { | ||||
|         assert!((n > 0), "invalid argument to int63_n"); | ||||
|         if n.is_power_of_two() { | ||||
| @@ -362,7 +576,7 @@ impl GoRand { | ||||
|     /// | ||||
|     /// # Panics | ||||
|     /// if n <= 0 | ||||
|     #[flame] | ||||
|     //#[flame] | ||||
|     fn int31_n(&mut self, n: u32) -> u32 { | ||||
|         assert!((n > 0), "invalid argument to in32_n"); | ||||
|         if n.is_power_of_two() { | ||||
| @@ -377,12 +591,12 @@ impl GoRand { | ||||
|         v % n | ||||
|     } | ||||
|  | ||||
|     #[flame] | ||||
|     //#[flame] | ||||
|     fn uint32(&mut self) -> u32 { | ||||
|         (self.int63() >> 31).az() | ||||
|     } | ||||
|  | ||||
|     #[flame] | ||||
|     //#[flame] | ||||
|     fn int31_n2(&mut self, n: u32) -> u32 { | ||||
|         let mut v = self.uint32(); | ||||
|         let mut prod = u64::from(v) * u64::from(n); | ||||
| @@ -402,7 +616,7 @@ impl GoRand { | ||||
|     /// | ||||
|     /// # Panics | ||||
|     /// if n <= 0 | ||||
|     #[flame] | ||||
|     //#[flame] | ||||
|     fn int_n(&mut self, n: usize) -> usize { | ||||
|         assert!((n > 0), "invalid argument to int_n"); | ||||
|         if n < (1 << 31) { | ||||
| @@ -413,34 +627,65 @@ impl GoRand { | ||||
|     } | ||||
|  | ||||
|     #[allow(unused)] | ||||
|     #[flame] | ||||
|     //#[flame] | ||||
|     fn int(&mut self) -> usize { | ||||
|         self.int63().az() | ||||
|     } | ||||
|  | ||||
|     // Shuffle pseudo-randomizes the order of elements. n is the number of elements. Shouffle | ||||
|     // panics if n < 0. swap swaps the elements with indices i and j. | ||||
|     #[flame] | ||||
|     fn shuffle(&mut self, n: usize, mut swap: impl FnMut(usize, usize)) { | ||||
|     //#[flame] | ||||
|     fn shuffle<T>(&mut self, data: &mut [T]) { | ||||
|         // Fisher-Yates shuffle: https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle | ||||
|         // Shuffle really ought not be called with n that doesn't fit in 32 bits. | ||||
|         // Not only will it take a very long time, but with 2³¹! possible permutations, | ||||
|         // there's no way that any PRNG can have a big enough internal state to | ||||
|         // generate even a minuscule percentage of the possible permutations. | ||||
|         // Nevertheless, the right API signature accepts an int n, so handle it as best we can. | ||||
|         let mut i = n.saturating_sub(1); | ||||
|         let mut i = data.len().saturating_sub(1); | ||||
|         while i > (1 << 31) - 1 - 1 { | ||||
|             let j = self.int63_n((i + 1) as u64).az(); | ||||
|             swap(i, j); | ||||
|             data.swap(i, j); | ||||
|             i -= 1; | ||||
|         } | ||||
|         let mut i: u32 = i.az(); | ||||
|         while i > 0 { | ||||
|             let j = self.int31_n2(i + 1); | ||||
|             swap(i.az(), j.az()); | ||||
|             data.swap(i.az(), j.az()); | ||||
|             i -= 1; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // Shuffle the array and check if it is deshuffled afterwards | ||||
|     fn shuffle_check(&mut self, data: &mut [Option<usize>]) -> bool { | ||||
|         let mut i = data.len().saturating_sub(1); | ||||
|         while i > (1 << 31) - 1 - 1 { | ||||
|             let j = self.int63_n((i + 1) as u64).az(); | ||||
|             let datum = data[j]; | ||||
|             if let Some(new_i) = datum { | ||||
|                 if new_i != i { | ||||
|                     return false; | ||||
|                 } | ||||
|             } | ||||
|             data.swap(i, j); | ||||
|             i -= 1; | ||||
|         } | ||||
|         let mut i: u32 = i.az(); | ||||
|         while i > 0 { | ||||
|             let j = self.int31_n2(i + 1); | ||||
|             let i_usize = i.az(); | ||||
|             let j_usize = j.az(); | ||||
|             let datum = data[j_usize]; | ||||
|             if let Some(i) = datum { | ||||
|                 if i != i_usize { | ||||
|                     return false; | ||||
|                 } | ||||
|             } | ||||
|             data.swap(i_usize, j_usize); | ||||
|             i -= 1; | ||||
|         } | ||||
|         true | ||||
|     } | ||||
| } | ||||
|  | ||||
| static CHAIN_RAW: LazyLock<Mmap> = LazyLock::new(|| unsafe { | ||||
| @@ -468,7 +713,7 @@ impl GoSource { | ||||
|     const MAX: u64 = 1 << 63; | ||||
|     const MASK: u64 = Self::MAX - 1; | ||||
|  | ||||
|     #[flame] | ||||
|     //#[flame] | ||||
|     fn new(seed: i64) -> Self { | ||||
|         let seed: i32 = (seed % i64::from(i32::MAX)).az(); | ||||
|         let seed = match seed { | ||||
| @@ -490,13 +735,13 @@ impl GoSource { | ||||
| } | ||||
|  | ||||
| impl GoSource { | ||||
|     #[flame] | ||||
|     //#[flame] | ||||
|     fn int63(&mut self) -> u64 { | ||||
|         self.uint64() & Self::MASK | ||||
|     } | ||||
|  | ||||
|     #[allow(unused)] | ||||
|     #[flame] | ||||
|     //#[flame] | ||||
|     fn seed(&mut self, seed: i64) { | ||||
|         let seed: i32 = (seed % i64::from(i32::MAX)).az(); | ||||
|         let seed = match seed { | ||||
| @@ -514,7 +759,7 @@ impl GoSource { | ||||
|         self.vec.clear(); | ||||
|     } | ||||
|  | ||||
|     #[flame] | ||||
|     //#[flame] | ||||
|     fn uint64(&mut self) -> u64 { | ||||
|         self.tap = self.tap.checked_sub(1).unwrap_or(Self::LEN - 1); | ||||
|         self.feed = self.feed.checked_sub(1).unwrap_or(Self::LEN - 1); | ||||
| @@ -536,7 +781,7 @@ impl GoSource { | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[flame] | ||||
| //#[flame] | ||||
| fn seed_one(base: usize, i: u16) -> i64 { | ||||
|     let i = usize::from(i); | ||||
|     let base = base + 20 + i * 3; | ||||
| @@ -554,7 +799,7 @@ fn seed_one(base: usize, i: u16) -> i64 { | ||||
|     u ^ GoSource::COOKED[i] | ||||
| } | ||||
|  | ||||
| #[flame] | ||||
| //#[flame] | ||||
| const fn seedrand(x: i32) -> i32 { | ||||
|     const A: i32 = 48_271; | ||||
|     const Q: i32 = 44_488; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user