use crate::{
bamio::{BamDataSource, BamReader, OutputDataDest},
errors::{Error, ErrorKind},
jobqueue::IntervalJobs,
output::{write_multiple_outputs, OrderedPileupOutput},
params::{InputParams, PileupParams},
pileup_iterator::PileupIterator,
position_queue::{create_region_queue, intervals_from_header, GenomeInterval},
refseq::{RefSeq, RefSeqHandle},
threading::ThreadPool,
utils::OutputWriter,
};
use log::{info, warn};
use std::marker::PhantomData;
use std::sync::Arc;
pub const BUFWRITER_CAP: usize = 2 * 1024 * 1024;
pub const MIN_BAM_READ_THREADS: usize = 2;
pub const MIN_COORDS_PER_THREAD: i64 = 250_000;
struct PileupEngineQuery {
intervals: Vec<GenomeInterval>,
src: Vec<BamDataSource>,
}
impl TryFrom<InputParams> for PileupEngineQuery {
type Error = Error;
fn try_from(value: InputParams) -> Result<Self, Error> {
let mut src = Vec::with_capacity(value.file.len());
for file in value.file {
src.push(BamDataSource::from_string(&file)?);
}
let tempreader = BamReader::new(&src[0], 1)?;
let header = &tempreader.header;
let intervals = if let Some(region) = value.regions {
create_region_queue(®ion, header)?
} else {
intervals_from_header(header)?
};
Ok(Self { intervals, src })
}
}
pub struct PileupEngine<T: OrderedPileupOutput> {
plp_params: PileupParams,
_t: PhantomData<T>,
refseq: Option<RefSeq>,
}
impl<T: OrderedPileupOutput + 'static> PileupEngine<T> {
pub fn new(plp_params: PileupParams) -> Result<Self, Error> {
let refseq = if let Some(ref fasta) = plp_params.refseq {
if !std::fs::exists(std::path::Path::new(fasta))? {
return Err(Error::from(ErrorKind::IOError(std::io::Error::new(
std::io::ErrorKind::NotFound,
"Fasta file {fasta} doesn't exist!",
))));
}
Some(RefSeq::new(fasta.clone()))
} else {
None
};
Ok(Self {
plp_params,
_t: PhantomData,
refseq,
})
}
pub fn iter(&self, input: InputParams) -> Result<Vec<PileupIterator<T>>, Error> {
let query: PileupEngineQuery = input.try_into()?;
let mut ret = Vec::with_capacity(query.intervals.len());
for interval in query.intervals.iter() {
ret.push(PileupIterator::from_query(
&query.src,
self.get_refseq(&interval.name)?,
interval,
&self.plp_params,
)?);
}
Ok(ret)
}
pub fn write(&self, input: InputParams, output: &str, threads: usize) -> Result<(), Error> {
if threads == 0 {
return Err(Error::from(ErrorKind::IOError(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"thread count must be greater than zero",
))));
}
let query: PileupEngineQuery = input.try_into()?;
let dest = OutputDataDest::from_string(output);
if let OutputDataDest::File(ref f) = dest {
if std::fs::exists(f)? {
warn!("Output file {} already exists! Overwriting...", f);
if let Err(e) = std::fs::remove_file(f) {
warn!("Failed to remove file {f}; {e}. Output will be appended...");
};
}
}
let index_found = query.src.iter().all(|f| f.has_index().unwrap_or(false));
if index_found {
info!("Found index for for input files");
}
if threads == 1 {
self.run_all_1t(&query, &dest)
} else if !index_found {
warn!(
"User asked for more than {} threads but at least one input file is unindexed. Running in single-threaded mode",
threads
);
self.run_all_1t(&query, &dest)
} else {
info!("Running with {} threads...", threads);
self.run_all_par(&query, &dest, threads)
}
}
fn get_refseq(&self, ref_name: &str) -> Result<RefSeqHandle, Error> {
if let Some(ref refseq) = self.refseq {
refseq.yield_handle(ref_name)
} else {
Ok(Arc::new(None))
}
}
fn run_all_1t(&self, query: &PileupEngineQuery, dest: &OutputDataDest) -> Result<(), Error> {
for interval in query.intervals.iter() {
let mut main_writer = OutputWriter::new(dest, BUFWRITER_CAP, true, false)?;
let mut iterator = PileupIterator::<T>::from_query(
&query.src,
self.get_refseq(&interval.name)?,
interval,
&self.plp_params,
)?;
while iterator.advance()?.is_some() {
write_multiple_outputs(&iterator.ctx(), iterator.current(), main_writer.get())?;
}
main_writer.flush()?;
}
Ok(())
}
fn run_all_par(&self, query: &PileupEngineQuery, dest: &OutputDataDest, threads: usize) -> Result<(), Error> {
let sub_ref_split_strat = if self.plp_params.depth != 0 && !self.plp_params.lax_depth {
warn!("Depth was bound to {}, so one thread will be used per reference. For generally faster sub-reference threading, either set depth to 0 (disable) or enable lax_depth.", self.plp_params.depth);
None
} else {
Some(self.plp_params.coords_per_thread)
};
let mut jobs = IntervalJobs::new(&query.intervals, sub_ref_split_strat, threads as i64, dest.clone());
if jobs.queue.len() == 1 {
return self.run_all_1t(query, dest);
}
let mut pool = ThreadPool::new(threads);
let mut n_jobs = 0;
while !jobs.is_completed() {
jobs.merge_completed()?;
if let Some(worker) = pool.get_available() {
if let Some(job) = jobs.queue.pop_front() {
n_jobs += 1;
let refseq_handle = self.get_refseq(&job.interval.name)?;
worker.run::<T>(n_jobs, self.plp_params.clone(), job, query.src.clone(), refseq_handle);
}
}
}
jobs.conclude()?;
Ok(())
}
}