use crate::{Error, Result, Tpx3Packet};
use rayon::prelude::*;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Tpx3ParserConfig {
pub hits_only: bool,
pub parallel: bool,
pub chunk_size: usize,
}
impl Default for Tpx3ParserConfig {
fn default() -> Self {
Self {
hits_only: true,
parallel: true,
chunk_size: 1024 * 1024, }
}
}
impl Tpx3ParserConfig {
pub fn new() -> Self {
Self::default()
}
pub fn with_hits_only(mut self, hits_only: bool) -> Self {
self.hits_only = hits_only;
self
}
pub fn with_parallel(mut self, parallel: bool) -> Self {
self.parallel = parallel;
self
}
pub fn with_chunk_size(mut self, size: usize) -> Self {
self.chunk_size = size;
self
}
}
#[derive(Debug, Clone, Default)]
pub struct Tpx3Parser {
config: Tpx3ParserConfig,
}
impl Tpx3Parser {
pub fn new() -> Self {
Self {
config: Tpx3ParserConfig::default(),
}
}
pub fn with_config(config: Tpx3ParserConfig) -> Self {
Self { config }
}
pub fn parse_bytes(&self, data: &[u8]) -> Result<Vec<Tpx3Packet>> {
if !data.len().is_multiple_of(8) {
return Err(Error::ParseError(format!(
"data length {} is not a multiple of 8",
data.len()
)));
}
let raw_packets: Vec<u64> = data
.chunks_exact(8)
.map(|chunk| u64::from_le_bytes(chunk.try_into().unwrap()))
.collect();
self.parse_raw(&raw_packets)
}
pub fn parse_raw(&self, raw_packets: &[u64]) -> Result<Vec<Tpx3Packet>> {
if self.config.parallel && raw_packets.len() > self.config.chunk_size {
self.parse_parallel(raw_packets)
} else {
self.parse_sequential(raw_packets)
}
}
fn parse_sequential(&self, raw_packets: &[u64]) -> Result<Vec<Tpx3Packet>> {
let mut packets = Vec::with_capacity(raw_packets.len());
for &raw in raw_packets {
match Tpx3Packet::parse(raw) {
Ok(packet) => {
if !self.config.hits_only || packet.is_hit() {
packets.push(packet);
}
}
Err(_) if self.config.hits_only => {
continue;
}
Err(e) => return Err(e),
}
}
Ok(packets)
}
fn parse_parallel(&self, raw_packets: &[u64]) -> Result<Vec<Tpx3Packet>> {
let results: Vec<Option<Tpx3Packet>> = raw_packets
.par_iter()
.map(|&raw| {
match Tpx3Packet::parse(raw) {
Ok(packet) => {
if !self.config.hits_only || packet.is_hit() {
Some(packet)
} else {
None
}
}
Err(_) if self.config.hits_only => None,
Err(_) => None, }
})
.collect();
Ok(results.into_iter().flatten().collect())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parser_config() {
let config = Tpx3ParserConfig::new()
.with_hits_only(false)
.with_parallel(false)
.with_chunk_size(1000);
assert!(!config.hits_only);
assert!(!config.parallel);
assert_eq!(config.chunk_size, 1000);
}
#[test]
fn test_parser_invalid_length() {
let parser = Tpx3Parser::new();
let data = [0u8; 7]; assert!(parser.parse_bytes(&data).is_err());
}
#[test]
fn test_parser_empty_data() {
let parser = Tpx3Parser::new();
let data: [u8; 0] = [];
let result = parser.parse_bytes(&data).unwrap();
assert!(result.is_empty());
}
}