use anyhow::{bail, Context, Result};
use byteorder::{ByteOrder, LittleEndian};
use rustc_hash::FxHashMap;
use std::{path::Path, sync::OnceLock};
use crate::{append_suffix, safe_mmap_readonly};
const MISSING: u64 = u64::MAX;
#[derive(Debug)]
pub struct GofEntry {
pub feature_id: u32,
pub seqid_num: u32,
pub start_offset: u64,
pub end_offset: u64,
}
#[derive(Debug)]
pub struct GofMap {
pub entries: Vec<GofEntry>,
index_cache: OnceLock<FxHashMap<u32, (u64, u64)>>,
pub seqid_index: FxHashMap<u32, Vec<usize>>,
}
impl GofMap {
pub fn index(&self) -> FxHashMap<u32, (u64, u64)> {
self.entries
.iter()
.map(|e| (e.feature_id, (e.start_offset, e.end_offset)))
.collect()
}
#[inline]
pub fn index_cached(&self) -> &FxHashMap<u32, (u64, u64)> {
self.index_cache.get_or_init(|| self.index())
}
#[inline]
pub fn get(&self, fid: u32) -> Option<&(u64, u64)> {
self.index_cached().get(&fid)
}
#[inline]
pub fn roots_to_offsets(
&self,
roots: &[u32],
threads: usize,
) -> Vec<(u32, u64, u64)> {
let idx = self.index_cached();
let should_parallel = threads > 1 && roots.len() > 2048;
if should_parallel {
use rayon::prelude::*;
roots
.par_iter()
.map(|&r| match idx.get(&r) {
Some(&(s, e)) => (r, s, e),
None => (r, MISSING, MISSING), })
.collect()
} else {
let mut out = Vec::with_capacity(roots.len());
for &r in roots {
if let Some(&(s, e)) = idx.get(&r) {
out.push((r, s, e));
} else {
out.push((r, MISSING, MISSING)); }
}
out
}
}
pub fn roots_for_seqid(&self, seqid_num: u32) -> Vec<&GofEntry> {
match self.seqid_index.get(&seqid_num) {
Some(indices) => indices.iter().map(|&i| &self.entries[i]).collect(),
None => Vec::new(),
}
}
}
pub fn load_gof<P: AsRef<Path>>(gff_path: P) -> Result<GofMap> {
let path = gff_path.as_ref();
let gof_path = append_suffix(path, ".gof");
let mmap = safe_mmap_readonly(&gof_path)
.with_context(|| format!("Failed to mmap {}", gof_path.display()))?;
let bytes = &mmap[..];
const REC_SIZE: usize = 4 + 4 + 8 + 8;
if bytes.len() % REC_SIZE != 0 {
bail!(
"Corrupted GOF ({}): length {} not multiple of {}",
gof_path.display(),
bytes.len(),
REC_SIZE
);
}
let mut entries = Vec::with_capacity(bytes.len() / REC_SIZE);
let mut seqid_index: FxHashMap<u32, Vec<usize>> = FxHashMap::default();
for (i, rec) in bytes.chunks_exact(REC_SIZE).enumerate() {
let fid = LittleEndian::read_u32(&rec[0..4]);
let seqid_num = LittleEndian::read_u32(&rec[4..8]);
let start = LittleEndian::read_u64(&rec[8..16]);
let end = LittleEndian::read_u64(&rec[16..24]);
entries.push(GofEntry { feature_id: fid, seqid_num: seqid_num, start_offset: start, end_offset: end });
seqid_index.entry(seqid_num).or_default().push(i);
}
Ok(GofMap {
entries,
index_cache: OnceLock::new(),
seqid_index,
})
}