use anyhow::{bail, Result};
use byteorder::{ByteOrder, LittleEndian};
use rustc_hash::FxHashMap;
use std::{path::Path, sync::OnceLock};
use rayon::prelude::*;
use crate::{append_suffix, safe_mmap_readonly};
#[derive(Debug, Clone, Copy)]
pub struct PrtEntry {
pub child: u32,
pub parent: u32,
}
#[derive(Debug)]
pub struct PrtMap {
pub entries: Vec<PrtEntry>,
index_cache: OnceLock<FxHashMap<u32, u32>>,
}
impl PrtMap {
pub fn new(entries: Vec<PrtEntry>) -> Self {
Self {
entries,
index_cache: OnceLock::new(),
}
}
pub fn index(&self) -> FxHashMap<u32, u32> {
self.entries.iter().map(|e| (e.child, e.parent)).collect()
}
pub fn index_cached(&self) -> &FxHashMap<u32, u32> {
self.index_cache.get_or_init(|| self.index())
}
pub fn get_parent(&self, child: u32) -> Option<u32> {
self.entries
.iter()
.find(|e| e.child == child)
.map(|e| e.parent)
}
#[inline]
fn resolve_root(&self, start: u32) -> (u32, bool) {
let n = self.entries.len() as u32;
let mut cur = start;
loop {
if cur >= n {
return (u32::MAX, true);
}
let p = self.entries[cur as usize].parent;
if p == cur {
return (cur, false);
}
if p >= n {
return (u32::MAX, true);
}
cur = p;
}
}
#[inline]
pub fn map_fids_to_roots(&self, fids: &Vec<u32>, threads: usize) -> Vec<u32> {
let should_parallel = threads > 1 && fids.len() > 256;
if should_parallel {
fids.par_iter()
.map(|&fid| {
if fid == u32::MAX {
u32::MAX
} else {
self.resolve_root(fid).0
}
})
.collect()
} else {
let mut out = Vec::with_capacity(fids.len());
for &fid in fids {
if fid == u32::MAX {
out.push(u32::MAX);
} else {
out.push(self.resolve_root(fid).0);
}
}
out
}
}
}
pub fn load_prt<P: AsRef<Path>>(gff_path: P) -> Result<PrtMap> {
let path = gff_path.as_ref();
let prt_path = append_suffix(path, ".prt");
let mmap = safe_mmap_readonly(&prt_path)?;
if mmap.len() % 4 != 0 {
bail!("Corrupted PRT: not aligned to u32");
}
let mut entries = Vec::with_capacity(mmap.len() / 4);
for (i, chunk) in mmap.chunks_exact(4).enumerate() {
let parent = LittleEndian::read_u32(chunk);
entries.push(PrtEntry {
child: i as u32,
parent,
});
}
Ok(PrtMap::new(entries))
}