use copa::{Parser, Perform};
use tracing::debug;
pub struct BatchedParser<const OSC_RAW_BUF_SIZE: usize = 1024> {
parser: Parser<OSC_RAW_BUF_SIZE>,
input_buffer: Vec<u8>,
batch_threshold: usize,
stats: BatchStats,
}
impl<const OSC_RAW_BUF_SIZE: usize> Default for BatchedParser<OSC_RAW_BUF_SIZE> {
fn default() -> Self {
Self::new()
}
}
impl<const OSC_RAW_BUF_SIZE: usize> BatchedParser<OSC_RAW_BUF_SIZE> {
pub fn new() -> Self {
Self {
parser: Parser::<OSC_RAW_BUF_SIZE>::default(),
input_buffer: Vec::with_capacity(4096),
batch_threshold: 1024, stats: BatchStats::default(),
}
}
pub fn advance<P: Perform>(&mut self, performer: &mut P, bytes: &[u8]) {
if bytes.len() < self.batch_threshold {
self.stats.record_immediate(bytes.len());
debug!("BatchedParser: immediate processing {} bytes", bytes.len());
self.parser.advance(performer, bytes);
return;
}
self.input_buffer.extend_from_slice(bytes);
debug!(
"BatchedParser: added {} bytes to buffer (total: {})",
bytes.len(),
self.input_buffer.len()
);
if self.input_buffer.len() >= self.batch_threshold {
let batch_size = self.input_buffer.len();
self.stats.record_batch(batch_size);
debug!("BatchedParser: flushing batch of {} bytes", batch_size);
self.flush_batch(performer);
}
}
pub fn flush<P: Perform>(&mut self, performer: &mut P) {
if !self.input_buffer.is_empty() {
self.flush_batch(performer);
}
}
fn flush_batch<P: Perform>(&mut self, performer: &mut P) {
if self.input_buffer.is_empty() {
return;
}
self.parser.advance(performer, &self.input_buffer);
self.input_buffer.clear();
if self.input_buffer.capacity() > 16384 {
self.input_buffer.shrink_to(4096);
}
}
pub fn inner(&self) -> &Parser<OSC_RAW_BUF_SIZE> {
&self.parser
}
pub fn inner_mut(&mut self) -> &mut Parser<OSC_RAW_BUF_SIZE> {
&mut self.parser
}
pub fn buffer_len(&self) -> usize {
self.input_buffer.len()
}
pub fn stats(&self) -> &BatchStats {
&self.stats
}
pub fn reset_stats(&mut self) {
self.stats = BatchStats::default();
}
pub fn batch_threshold(&self) -> usize {
self.batch_threshold
}
pub fn advance_until_terminated<P: Perform>(
&mut self,
performer: &mut P,
bytes: &[u8],
) -> usize {
if bytes.len() < self.batch_threshold {
self.stats.record_immediate(bytes.len());
return self.parser.advance_until_terminated(performer, bytes);
}
self.input_buffer.extend_from_slice(bytes);
let bytes_added = bytes.len();
if self.input_buffer.len() >= self.batch_threshold {
let batch_size = self.input_buffer.len();
self.stats.record_batch(batch_size);
self.flush_batch(performer);
}
bytes_added
}
}
#[derive(Debug, Default)]
pub struct BatchStats {
pub total_bytes: usize,
pub batch_count: usize,
pub immediate_count: usize,
pub avg_batch_size: f64,
}
impl BatchStats {
pub fn record_batch(&mut self, batch_size: usize) {
self.total_bytes += batch_size;
self.batch_count += 1;
self.update_average();
}
pub fn record_immediate(&mut self, size: usize) {
self.total_bytes += size;
self.immediate_count += 1;
}
fn update_average(&mut self) {
if self.batch_count > 0 {
self.avg_batch_size = self.total_bytes as f64 / self.batch_count as f64;
}
}
pub fn batch_efficiency(&self) -> f64 {
if self.total_bytes == 0 {
return 0.0;
}
let batched_bytes = self.batch_count as f64 * self.avg_batch_size;
(batched_bytes / self.total_bytes as f64) * 100.0
}
}
#[cfg(test)]
mod tests {
use super::*;
use copa::Perform;
struct MockPerformer {
chars_received: Vec<char>,
}
impl Perform for MockPerformer {
fn print(&mut self, c: char) {
self.chars_received.push(c);
}
fn execute(&mut self, _byte: u8) {}
fn hook(
&mut self,
_params: &copa::Params,
_intermediates: &[u8],
_ignore: bool,
_c: char,
) {
}
fn put(&mut self, _byte: u8) {}
fn unhook(&mut self) {}
fn osc_dispatch(&mut self, _params: &[&[u8]], _bell_terminated: bool) {}
fn csi_dispatch(
&mut self,
_params: &copa::Params,
_intermediates: &[u8],
_ignore: bool,
_c: char,
) {
}
fn esc_dispatch(&mut self, _intermediates: &[u8], _ignore: bool, _byte: u8) {}
}
#[test]
fn test_batched_parser_small_input() {
let mut parser = BatchedParser::<1024>::new();
let mut performer = MockPerformer {
chars_received: Vec::new(),
};
parser.advance(&mut performer, b"Hello");
assert_eq!(performer.chars_received, vec!['H', 'e', 'l', 'l', 'o']);
}
#[test]
fn test_batched_parser_large_input() {
let mut parser = BatchedParser::<1024>::new();
let mut performer = MockPerformer {
chars_received: Vec::new(),
};
let large_input = "A".repeat(1000);
parser.advance(&mut performer, large_input.as_bytes());
assert_eq!(performer.chars_received.len(), 1000);
assert!(performer.chars_received.iter().all(|&c| c == 'A'));
}
#[test]
fn test_batch_stats() {
let mut stats = BatchStats::default();
stats.record_batch(100);
stats.record_batch(200);
stats.record_immediate(50);
assert_eq!(stats.total_bytes, 350);
assert_eq!(stats.batch_count, 2);
assert_eq!(stats.immediate_count, 1);
assert_eq!(stats.avg_batch_size, 150.0);
}
}