use serde::Serialize;
use crate::Rng;
#[derive(Serialize)]
pub struct Analysis {
pub rng: Rng,
pub seeds: Vec<SeedInfo>,
pub branches: Vec<BranchInfo>,
pub loops: Vec<LoopInfo>,
}
#[derive(Clone, Copy, PartialEq, Eq, Serialize)]
pub enum SeedInfo {
Branch { id: u16 },
Loop { id: u16 },
}
#[derive(Serialize)]
pub struct BranchInfo {
pub seeds: Vec<u16>,
pub loop_id: u16,
}
#[derive(Serialize)]
pub struct LoopInfo {
pub seeds: Vec<u16>,
}
impl Rng {
pub fn analyze(&self) -> Analysis {
let mut seeds = [Option::<SeedInfo>::None; 0x10000];
let mut branches = Vec::new();
let mut loops = Vec::new();
for start in std::iter::once(self.seed).chain(0..=0xFFFFu16) {
if seeds[start as usize].is_some() {
continue;
}
let mut rng = self.with_seed(start);
let mut seeds_seen = Vec::new();
let new_branch = SeedInfo::Branch {
id: branches.len() as u16,
};
while seeds[rng.seed as usize].is_none() {
seeds[rng.seed as usize] = Some(new_branch);
seeds_seen.push(rng.seed);
rng.frame_advance();
}
match seeds[rng.seed as usize] {
None => unreachable!(),
Some(SeedInfo::Loop { id }) => {
branches.push(BranchInfo {
seeds: seeds_seen,
loop_id: id,
});
}
Some(info) if info == new_branch => {
let new_loop = SeedInfo::Loop {
id: loops.len() as u16,
};
let (branch_seeds, loop_seeds) = seeds_seen.split_at(
seeds_seen
.iter()
.enumerate()
.find(|(_, seed)| **seed == rng.seed)
.unwrap()
.0,
);
for &seed in branch_seeds {
seeds[seed as usize] = Some(new_branch);
}
for &seed in loop_seeds {
seeds[seed as usize] = Some(new_loop);
}
if !branch_seeds.is_empty() {
branches.push(BranchInfo {
seeds: branch_seeds.to_vec(),
loop_id: loops.len() as u16,
});
}
loops.push(LoopInfo {
seeds: loop_seeds.to_vec(),
})
}
suffix @ Some(SeedInfo::Branch { id }) => {
for &seed in &seeds_seen {
seeds[seed as usize] = suffix;
}
let branch = &mut branches[id as usize];
seeds_seen.append(&mut branch.seeds);
branches[id as usize].seeds = seeds_seen;
rng.reseed(start);
}
}
}
Analysis {
rng: self.clone(),
seeds: seeds.into_iter().map(Option::unwrap).collect(),
branches,
loops,
}
}
}
impl Analysis {
pub fn print(&self) {
println!("Loop analysis for {:#?}", self.rng);
println!();
for (id, l) in self.loops.iter().enumerate() {
let start = l.seeds[0];
let period = l.seeds.len();
if period > 100 {
println!("Loop {id} (period {period}) at {start:#06x}");
} else {
println!("Loop {id} (period {period}):");
const PER_LINE: usize = 10;
for (i, seed) in l.seeds.iter().enumerate() {
if i % PER_LINE == 0 {
print!(" ");
}
print!("{:#06x}", seed);
if i == period - 1 || (i + 1) % PER_LINE == 0 {
println!();
} else {
print!(", ");
}
}
}
}
println!();
println!("Branches: {}", self.branches.len());
for (i, branch) in self.branches.iter().enumerate() {
let pad = self.branches.len().ilog10() as usize + 1;
println!(
" {i:pad$}: length {:5} -> loop {}",
branch.seeds.len(),
branch.loop_id
);
}
}
}