use ringbuf::{HeapRb, traits::*};
use std::io;
pub struct CircularBuffer {
rb: HeapRb<u8>,
partial: Vec<u8>,
}
impl CircularBuffer {
pub fn new(capacity: usize) -> Self {
Self {
rb: HeapRb::new(capacity),
partial: Vec::new(),
}
}
pub fn write(&mut self, data: &[u8]) -> usize {
let (mut prod, cons) = self.rb.split_ref();
let occupied = cons.occupied_len();
let capacity: usize = prod.capacity().into();
if occupied > 0 {
let usage_percent = (occupied * 100) / capacity;
if usage_percent >= 90 {
tracing::warn!(
"Circular buffer at {}% capacity ({} bytes), old data may be overwritten",
usage_percent,
occupied
);
}
}
let written = prod.push_slice(data);
written
}
pub fn read_line(&mut self) -> io::Result<Option<Vec<u8>>> {
let (_, mut cons) = self.rb.split_ref();
let mut line = self.partial.drain(..).collect::<Vec<_>>();
let mut found_data = !line.is_empty();
loop {
match cons.try_pop() {
Some(byte) => {
found_data = true;
if byte == b'\n' {
return Ok(Some(line));
}
line.push(byte);
}
None => {
if found_data {
self.partial = line;
return Ok(None);
}
return Ok(None);
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_circular_buffer_basic() {
let mut cb = CircularBuffer::new(100);
assert_eq!(cb.write(b"hello\n"), 6);
let result = cb.read_line().unwrap();
assert_eq!(result, Some(b"hello".to_vec()));
}
#[test]
fn test_circular_buffer_partial_line() {
let mut cb = CircularBuffer::new(100);
assert_eq!(cb.write(b"hello"), 5);
assert_eq!(cb.read_line().unwrap(), None);
assert_eq!(cb.write(b"\n"), 1);
assert_eq!(cb.read_line().unwrap(), Some(b"hello".to_vec()));
}
#[test]
fn test_circular_buffer_partial_line_preserved() {
let mut cb = CircularBuffer::new(100);
assert_eq!(cb.write(b"hello"), 5);
assert_eq!(cb.read_line().unwrap(), None);
assert_eq!(cb.write(b" world\n"), 7);
assert_eq!(cb.read_line().unwrap(), Some(b"hello world".to_vec()));
}
#[test]
fn test_circular_buffer_empty() {
let mut cb = CircularBuffer::new(100);
assert_eq!(cb.read_line().unwrap(), None);
}
#[test]
fn test_circular_buffer_multi_line() {
let mut cb = CircularBuffer::new(100);
cb.write(b"line1\nline2\nline3\n");
assert_eq!(cb.read_line().unwrap(), Some(b"line1".to_vec()));
assert_eq!(cb.read_line().unwrap(), Some(b"line2".to_vec()));
assert_eq!(cb.read_line().unwrap(), Some(b"line3".to_vec()));
assert_eq!(cb.read_line().unwrap(), None);
}
#[test]
fn test_circular_buffer_small_buffer() {
let mut cb = CircularBuffer::new(10);
cb.write(b"0123456789");
cb.write(b"AB");
let result = cb.read_line().unwrap();
assert!(result.is_some() || result.is_none());
}
}