use crate::libradicl::chunk::Chunk;
use crate::libradicl::codec::{CHUNK_CODEC_TAG, ChunkCodec, decompress_payload};
use crate::libradicl::header::RadPrelude;
use crate::libradicl::rad_types::{TagMap, TagValue};
use crate::libradicl::record::{MappedRecord, RecordContext};
use crate::libradicl::utils;
use anyhow::Context;
use crossbeam_queue::ArrayQueue;
use scroll::Pwrite;
use std::io::{BufRead, Cursor, Seek};
use std::sync::{
Arc,
atomic::{AtomicBool, Ordering},
};
pub const EMPTY_METACHUNK_CALLBACK: Option<Box<dyn FnMut(u64, u64)>> = None;
fn codec_from_tag_map(file_tag_map: &TagMap) -> anyhow::Result<ChunkCodec> {
match file_tag_map.get(CHUNK_CODEC_TAG) {
None => Ok(ChunkCodec::None),
Some(TagValue::U8(v)) => ChunkCodec::from_u8(*v),
Some(_) => anyhow::bail!("'{CHUNK_CODEC_TAG}' file tag must be a U8"),
}
}
pub struct MetaChunk<R: MappedRecord> {
pub first_chunk_index: usize,
pub num_sub_chunks: usize,
pub num_bytes: u32,
pub num_records: u32,
chunk_blob: Vec<u8>,
record_context: <R as MappedRecord>::ParsingContext,
}
pub struct MetaChunkIterator<'a, 'b, R: MappedRecord> {
curr_sub_chunk: usize,
num_sub_chunks: usize,
data: Cursor<&'a [u8]>,
record_context: &'b <R as MappedRecord>::ParsingContext,
}
impl<'a, 'b, R: MappedRecord> Iterator for MetaChunkIterator<'a, 'b, R> {
type Item = Chunk<R>;
fn next(&mut self) -> Option<Self::Item> {
if self.curr_sub_chunk < self.num_sub_chunks {
self.curr_sub_chunk += 1;
let c = Chunk::<R>::from_bytes(&mut self.data, self.record_context);
Some(c)
} else {
None
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
let rem = self.num_sub_chunks - self.curr_sub_chunk;
(rem, Some(rem))
}
}
impl<'a, 'b, R: MappedRecord> ExactSizeIterator for MetaChunkIterator<'a, 'b, R> {}
impl<R: MappedRecord> MetaChunk<R>
where
<R as MappedRecord>::ParsingContext: RecordContext,
{
pub fn new(
first_chunk_index: usize,
num_sub_chunks: usize,
num_bytes: u32,
num_records: u32,
record_context: <R as MappedRecord>::ParsingContext,
chunk_blob: Vec<u8>,
) -> Self {
Self {
first_chunk_index,
num_sub_chunks,
num_bytes,
num_records,
chunk_blob,
record_context,
}
}
pub fn iter(&self) -> MetaChunkIterator<'_, '_, R> {
MetaChunkIterator {
curr_sub_chunk: 0,
num_sub_chunks: self.num_sub_chunks,
data: Cursor::new(self.chunk_blob.as_slice()),
record_context: &self.record_context,
}
}
pub fn num_records(&self) -> u32 {
self.num_records
}
pub fn num_bytes(&self) -> u32 {
self.num_bytes
}
pub fn first_chunk_index(&self) -> usize {
self.first_chunk_index
}
}
fn fill_work_queue_filtered<
R: MappedRecord,
T: BufRead,
ChunkIt: Iterator<Item = usize> + BufReadProvider<T> + LastChunkSignaler,
FilterF,
F: FnMut(u64, u64),
>(
mut chunk_iter: ChunkIt,
filter_fn: FilterF,
mut callback: Option<F>,
prelude: &RadPrelude,
codec: ChunkCodec,
meta_chunk_queue: Arc<ArrayQueue<MetaChunk<R>>>,
done_var: Arc<AtomicBool>,
) -> anyhow::Result<()>
where
<R as MappedRecord>::ParsingContext: RecordContext,
<R as MappedRecord>::ParsingContext: Clone,
FilterF: Fn(&[u8], &<R as MappedRecord>::ParsingContext) -> bool,
{
const BUFSIZE: usize = 524208;
let mut buf = vec![0u8; BUFSIZE];
let mut scratch: Vec<u8> = Vec::new();
let mut cbytes = 0u32;
let mut crec = 0u32;
let mut chunks_in_meta_chunk = 0usize;
let mut first_chunk = 0usize;
let mut force_push = false;
let mut nbytes_chunk = 0u32;
let mut nrec_chunk = 0u32;
let record_context = prelude
.get_record_context::<<R as MappedRecord>::ParsingContext>()
.unwrap();
while let Some(chunk_num) = chunk_iter.next() {
if chunk_num > 0 {
let boffset = cbytes as usize;
let eff_nbytes = if codec == ChunkCodec::None {
if nbytes_chunk as usize > buf.len() {
force_push = true;
let chunk_resize = nbytes_chunk as usize + cbytes as usize;
buf.resize(chunk_resize, 0);
}
let br = chunk_iter.get_mut_buf_read();
buf.pwrite::<u32>(nbytes_chunk, boffset)?;
buf.pwrite::<u32>(nrec_chunk, boffset + 4)?;
br.read_exact(&mut buf[(boffset + 8)..(boffset + nbytes_chunk as usize)])
.context("failed to read from work queue.")?;
nbytes_chunk
} else {
let br = chunk_iter.get_mut_buf_read();
scratch.resize(nbytes_chunk as usize - 8, 0);
br.read_exact(&mut scratch)
.context("failed to read compressed chunk from work queue.")?;
let decoded = decompress_payload(codec, &scratch)?;
let eff = decoded.len() as u32 + 8;
if boffset + eff as usize > buf.len() {
force_push = true;
buf.resize(boffset + eff as usize, 0);
}
buf.pwrite::<u32>(eff, boffset)?;
buf.pwrite::<u32>(nrec_chunk, boffset + 4)?;
buf[(boffset + 8)..(boffset + eff as usize)].copy_from_slice(&decoded);
eff
};
if filter_fn(&buf[boffset..], &record_context) {
chunks_in_meta_chunk += 1;
cbytes += eff_nbytes;
crec += nrec_chunk;
} else {
force_push = false;
}
}
let last_chunk = chunk_iter.is_last_chunk();
if !last_chunk {
let (nc, nr) = Chunk::<R>::read_header(chunk_iter.get_mut_buf_read());
nbytes_chunk = nc;
nrec_chunk = nr;
}
if force_push || ((cbytes + nbytes_chunk) as usize > buf.len() && chunks_in_meta_chunk > 0)
|| last_chunk
{
let mut bclone = MetaChunk::<R>::new(
first_chunk,
chunks_in_meta_chunk,
cbytes,
crec,
record_context.clone(),
buf.clone(),
);
while let Err(t) = meta_chunk_queue.push(bclone) {
bclone = t;
while meta_chunk_queue.is_full() {}
}
callback
.iter_mut()
.for_each(|f| f(cbytes as u64, chunks_in_meta_chunk as u64));
first_chunk += chunks_in_meta_chunk;
chunks_in_meta_chunk = 0;
cbytes = 0;
crec = 0;
buf.resize(BUFSIZE, 0);
force_push = false;
}
}
done_var.store(true, Ordering::SeqCst);
Ok(())
}
fn fill_work_queue<
R: MappedRecord,
T: BufRead,
ChunkIt: Iterator<Item = usize> + BufReadProvider<T> + LastChunkSignaler,
F: FnMut(u64, u64),
>(
mut chunk_iter: ChunkIt,
mut callback: Option<F>,
prelude: &RadPrelude,
codec: ChunkCodec,
meta_chunk_queue: Arc<ArrayQueue<MetaChunk<R>>>,
done_var: Arc<AtomicBool>,
) -> anyhow::Result<()>
where
<R as MappedRecord>::ParsingContext: RecordContext,
<R as MappedRecord>::ParsingContext: Clone,
{
const BUFSIZE: usize = 524208;
let mut buf = vec![0u8; BUFSIZE];
let mut scratch: Vec<u8> = Vec::new();
let mut cbytes = 0u32;
let mut crec = 0u32;
let mut chunks_in_meta_chunk = 0usize;
let mut first_chunk = 0usize;
let mut force_push = false;
let mut nbytes_chunk = 0u32;
let mut nrec_chunk = 0u32;
let record_context = prelude
.get_record_context::<<R as MappedRecord>::ParsingContext>()
.unwrap();
while let Some(chunk_num) = chunk_iter.next() {
if chunk_num > 0 {
if codec == ChunkCodec::None {
if nbytes_chunk as usize > buf.len() {
force_push = true;
let chunk_resize = nbytes_chunk as usize + cbytes as usize;
buf.resize(chunk_resize, 0);
}
let br = chunk_iter.get_mut_buf_read();
let boffset = cbytes as usize;
buf.pwrite::<u32>(nbytes_chunk, boffset)?;
buf.pwrite::<u32>(nrec_chunk, boffset + 4)?;
br.read_exact(&mut buf[(boffset + 8)..(boffset + nbytes_chunk as usize)])
.context("failed to read from work queue.")?;
chunks_in_meta_chunk += 1;
cbytes += nbytes_chunk;
crec += nrec_chunk;
} else {
let br = chunk_iter.get_mut_buf_read();
scratch.resize(nbytes_chunk as usize - 8, 0);
br.read_exact(&mut scratch)
.context("failed to read compressed chunk from work queue.")?;
let decoded = decompress_payload(codec, &scratch)?;
let eff_nbytes = decoded.len() as u32 + 8;
let boffset = cbytes as usize;
if boffset + eff_nbytes as usize > buf.len() {
force_push = true;
buf.resize(boffset + eff_nbytes as usize, 0);
}
buf.pwrite::<u32>(eff_nbytes, boffset)?;
buf.pwrite::<u32>(nrec_chunk, boffset + 4)?;
buf[(boffset + 8)..(boffset + eff_nbytes as usize)].copy_from_slice(&decoded);
chunks_in_meta_chunk += 1;
cbytes += eff_nbytes;
crec += nrec_chunk;
}
}
let last_chunk = chunk_iter.is_last_chunk();
if !last_chunk {
let (nc, nr) = Chunk::<R>::read_header(chunk_iter.get_mut_buf_read());
nbytes_chunk = nc;
nrec_chunk = nr;
}
if force_push || ((cbytes + nbytes_chunk) as usize > buf.len() && chunks_in_meta_chunk > 0)
|| last_chunk
{
let mut bclone = MetaChunk::<R>::new(
first_chunk,
chunks_in_meta_chunk,
cbytes,
crec,
record_context.clone(),
buf.clone(),
);
while let Err(t) = meta_chunk_queue.push(bclone) {
bclone = t;
while meta_chunk_queue.is_full() {}
}
callback
.iter_mut()
.for_each(|f| f(cbytes as u64, chunks_in_meta_chunk as u64));
first_chunk += chunks_in_meta_chunk;
chunks_in_meta_chunk = 0;
cbytes = 0;
crec = 0;
buf.resize(BUFSIZE, 0);
force_push = false;
}
}
done_var.store(true, Ordering::SeqCst);
Ok(())
}
#[derive(Debug)]
pub struct ParallelRadReader<R: MappedRecord, T: BufRead + Seek> {
pub prelude: RadPrelude,
pub file_tag_map: TagMap,
reader: T,
pub meta_chunk_queue: Arc<ArrayQueue<MetaChunk<R>>>,
done_var: Arc<AtomicBool>,
}
impl<R: MappedRecord, T: BufRead + Seek> ParallelRadReader<R, T> {
pub fn new(mut reader: T, num_consumers: std::num::NonZeroUsize) -> Self {
let prelude = RadPrelude::from_bytes(&mut reader).unwrap();
let file_tag_map = prelude
.file_tags
.parse_tags_from_bytes(&mut reader)
.unwrap();
Self {
prelude,
file_tag_map,
reader,
meta_chunk_queue: Arc::new(ArrayQueue::<MetaChunk<R>>::new(num_consumers.get() * 4)),
done_var: Arc::new(AtomicBool::new(false)),
}
}
pub fn from_prelude(
mut reader: T,
prelude: RadPrelude,
num_consumers: std::num::NonZeroUsize,
) -> Self {
let file_tag_map = prelude
.file_tags
.parse_tags_from_bytes(&mut reader)
.unwrap();
Self {
prelude,
file_tag_map,
reader,
meta_chunk_queue: Arc::new(ArrayQueue::<MetaChunk<R>>::new(num_consumers.get() * 4)),
done_var: Arc::new(AtomicBool::new(false)),
}
}
pub fn from_prelude_and_file_tag_map(
reader: T,
prelude: RadPrelude,
file_tag_map: TagMap,
num_consumers: std::num::NonZeroUsize,
) -> Self {
Self {
prelude,
file_tag_map,
reader,
meta_chunk_queue: Arc::new(ArrayQueue::<MetaChunk<R>>::new(num_consumers.get() * 4)),
done_var: Arc::new(AtomicBool::new(false)),
}
}
pub fn get_queue(&self) -> Arc<ArrayQueue<MetaChunk<R>>> {
self.meta_chunk_queue.clone()
}
pub fn is_done(&self) -> Arc<AtomicBool> {
self.done_var.clone()
}
pub fn get_byte_offset(&mut self) -> u64 {
self.reader.stream_position().unwrap()
}
pub fn start_chunk_parsing<F: FnMut(u64, u64)>(
&mut self,
callback: Option<F>,
) -> anyhow::Result<()>
where
<R as MappedRecord>::ParsingContext: RecordContext,
<R as MappedRecord>::ParsingContext: Clone,
{
let mut pcr = ParallelChunkReader::<R> {
prelude: &self.prelude,
meta_chunk_queue: self.meta_chunk_queue.clone(),
done_var: self.done_var.clone(),
codec: codec_from_tag_map(&self.file_tag_map)?,
};
pcr.start(&mut self.reader, callback)
}
pub fn start_chunk_parsing_filtered<FilterFn, F: FnMut(u64, u64)>(
&mut self,
filter_fn: FilterFn,
callback: Option<F>,
) -> anyhow::Result<()>
where
<R as MappedRecord>::ParsingContext: RecordContext,
<R as MappedRecord>::ParsingContext: Clone,
FilterFn: Fn(&[u8], &<R as MappedRecord>::ParsingContext) -> bool,
{
let mut pcr = ParallelChunkReader::<R> {
prelude: &self.prelude,
meta_chunk_queue: self.meta_chunk_queue.clone(),
done_var: self.done_var.clone(),
codec: codec_from_tag_map(&self.file_tag_map)?,
};
pcr.start_filtered(&mut self.reader, filter_fn, callback)
}
}
trait LastChunkSignaler {
fn is_last_chunk(&mut self) -> bool;
}
trait BufReadProvider<T: BufRead> {
#[allow(dead_code)]
fn get_buf_read(&self) -> &T;
fn get_mut_buf_read(&mut self) -> &mut T;
}
struct ChunkCountIterator<T: BufRead> {
num_chunks: usize,
current_chunk: usize,
buf_reader: T,
}
impl<T: BufRead> Iterator for ChunkCountIterator<T> {
type Item = usize;
#[inline(always)]
fn next(&mut self) -> Option<Self::Item> {
let c = self.current_chunk;
self.current_chunk += 1;
if c <= self.num_chunks { Some(c) } else { None }
}
#[inline(always)]
fn size_hint(&self) -> (usize, Option<usize>) {
let rem = self.num_chunks + 1 - self.current_chunk;
(rem, Some(rem))
}
}
impl<T: BufRead> ExactSizeIterator for ChunkCountIterator<T> {}
impl<T: BufRead> LastChunkSignaler for ChunkCountIterator<T> {
fn is_last_chunk(&mut self) -> bool {
self.current_chunk > self.num_chunks
}
}
impl<T: BufRead> BufReadProvider<T> for ChunkCountIterator<T> {
fn get_buf_read(&self) -> &T {
&self.buf_reader
}
fn get_mut_buf_read(&mut self) -> &mut T {
&mut self.buf_reader
}
}
struct ReadUntilEOFIter<T: BufRead> {
current_chunk: usize,
buf_reader: T,
}
impl<T: BufRead> Iterator for ReadUntilEOFIter<T> {
type Item = usize;
#[inline(always)]
fn next(&mut self) -> Option<Self::Item> {
let c = self.current_chunk;
self.current_chunk += 1;
if utils::has_data_left(&mut self.buf_reader).expect("encountered error reading input file")
{
Some(c)
} else {
None
}
}
}
impl<T: BufRead> BufReadProvider<T> for ReadUntilEOFIter<T> {
fn get_buf_read(&self) -> &T {
&self.buf_reader
}
fn get_mut_buf_read(&mut self) -> &mut T {
&mut self.buf_reader
}
}
impl<T: BufRead> LastChunkSignaler for ReadUntilEOFIter<T> {
fn is_last_chunk(&mut self) -> bool {
!utils::has_data_left(&mut self.buf_reader).expect("encountered error reading input file")
}
}
#[derive(Debug)]
pub struct ParallelChunkReader<'a, R: MappedRecord> {
pub prelude: &'a RadPrelude,
pub meta_chunk_queue: Arc<ArrayQueue<MetaChunk<R>>>,
pub done_var: Arc<AtomicBool>,
codec: ChunkCodec,
}
impl<'a, R: MappedRecord> ParallelChunkReader<'a, R> {
pub fn new(prelude: &'a RadPrelude, num_consumers: std::num::NonZeroUsize) -> Self {
Self {
prelude,
meta_chunk_queue: Arc::new(ArrayQueue::<MetaChunk<R>>::new(num_consumers.get() * 4)),
done_var: Arc::new(AtomicBool::new(false)),
codec: ChunkCodec::None,
}
}
pub fn get_queue(&self) -> Arc<ArrayQueue<MetaChunk<R>>> {
self.meta_chunk_queue.clone()
}
pub fn is_done(&self) -> Arc<AtomicBool> {
self.done_var.clone()
}
}
impl<'a, R: MappedRecord> ParallelChunkReader<'a, R> {
pub fn start<T: BufRead, F: FnMut(u64, u64)>(
&mut self,
br: T,
callback: Option<F>,
) -> anyhow::Result<()>
where
<R as MappedRecord>::ParsingContext: RecordContext,
<R as MappedRecord>::ParsingContext: Clone,
{
if let Some(nchunks) = self.prelude.hdr.num_chunks() {
let num_chunks: usize = nchunks.into();
let chunk_iter = ChunkCountIterator::<T> {
num_chunks,
current_chunk: 0,
buf_reader: br,
};
fill_work_queue(
chunk_iter,
callback,
self.prelude,
self.codec,
self.meta_chunk_queue.clone(),
self.done_var.clone(),
)?;
} else {
let chunk_iter = ReadUntilEOFIter::<T> {
current_chunk: 0,
buf_reader: br,
};
fill_work_queue(
chunk_iter,
callback,
self.prelude,
self.codec,
self.meta_chunk_queue.clone(),
self.done_var.clone(),
)?;
}
Ok(())
}
pub fn start_filtered<T: BufRead, FilterF, F: FnMut(u64, u64)>(
&mut self,
br: T,
filter_fn: FilterF,
callback: Option<F>,
) -> anyhow::Result<()>
where
<R as MappedRecord>::ParsingContext: RecordContext,
<R as MappedRecord>::ParsingContext: Clone,
FilterF: Fn(&[u8], &<R as MappedRecord>::ParsingContext) -> bool,
{
if let Some(nchunks) = self.prelude.hdr.num_chunks() {
let num_chunks: usize = nchunks.into();
let chunk_iter = ChunkCountIterator::<T> {
num_chunks,
current_chunk: 0,
buf_reader: br,
};
fill_work_queue_filtered(
chunk_iter,
filter_fn,
callback,
self.prelude,
self.codec,
self.meta_chunk_queue.clone(),
self.done_var.clone(),
)?;
} else {
let chunk_iter = ReadUntilEOFIter::<T> {
current_chunk: 0,
buf_reader: br,
};
fill_work_queue_filtered(
chunk_iter,
filter_fn,
callback,
self.prelude,
self.codec,
self.meta_chunk_queue.clone(),
self.done_var.clone(),
)?;
}
Ok(())
}
}