use crate::wire::encode_frame;
#[cfg(test)]
use kevy_resp::Argv;
use kevy_resp::ArgvView;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub struct Frame {
pub offset: u64,
pub bytes: Vec<u8>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum FromOffset {
TooOld,
Future,
}
#[derive(Debug)]
pub struct ReplicationSource {
next_offset: u64,
bytes_in_buf: usize,
max_bytes: usize,
buf: std::collections::VecDeque<Frame>,
}
impl ReplicationSource {
pub fn new(max_bytes: usize) -> Self {
assert!(max_bytes > 0, "ReplicationSource max_bytes must be > 0");
Self { next_offset: 0, bytes_in_buf: 0, max_bytes, buf: std::collections::VecDeque::new() }
}
pub fn set_next_offset(&mut self, next: u64) {
assert!(self.buf.is_empty(), "set_next_offset on non-empty backlog");
self.next_offset = next;
}
pub fn max_bytes(&self) -> usize {
self.max_bytes
}
pub fn next_offset(&self) -> u64 {
self.next_offset
}
pub fn oldest_offset(&self) -> Option<u64> {
self.buf.front().map(|f| f.offset)
}
pub fn newest_offset(&self) -> Option<u64> {
self.buf.back().map(|f| f.offset)
}
pub fn buffered_bytes(&self) -> usize {
self.bytes_in_buf
}
pub fn len(&self) -> usize {
self.buf.len()
}
pub fn is_empty(&self) -> bool {
self.buf.is_empty()
}
#[allow(clippy::missing_panics_doc)]
pub fn push_mutation<A: ArgvView + ?Sized>(&mut self, argv: &A) -> u64 {
let offset = self.next_offset;
let bytes = encode_frame(offset, argv);
let frame_len = bytes.len();
while self.bytes_in_buf + frame_len > self.max_bytes && !self.buf.is_empty() {
let dropped = self.buf.pop_front().expect("non-empty checked");
self.bytes_in_buf -= dropped.bytes.len();
}
self.bytes_in_buf += frame_len;
self.buf.push_back(Frame { offset, bytes });
self.next_offset = self
.next_offset
.checked_add(1)
.expect("replication offset wrap — i64::MAX guard tripped");
offset
}
#[allow(clippy::missing_panics_doc)]
pub fn drop_up_to(&mut self, watermark: u64) {
while let Some(front) = self.buf.front() {
if front.offset >= watermark {
break;
}
let dropped = self.buf.pop_front().expect("front-of-loop");
self.bytes_in_buf -= dropped.bytes.len();
}
}
pub fn frames_from(&self, from: u64) -> Result<FramesIter<'_>, FromOffset> {
if from > self.next_offset {
return Err(FromOffset::Future);
}
if from == self.next_offset {
return Ok(FramesIter { buf: &self.buf, cursor: self.buf.len() });
}
match self.oldest_offset() {
Some(oldest) if from < oldest => return Err(FromOffset::TooOld),
None => return Err(FromOffset::TooOld),
_ => {}
}
let (a, b) = self.buf.as_slices();
let start = match a.partition_point(|f| f.offset < from) {
i if i < a.len() => i,
_ => a.len() + b.partition_point(|f| f.offset < from),
};
Ok(FramesIter { buf: &self.buf, cursor: start })
}
}
#[derive(Debug)]
pub struct FramesIter<'a> {
buf: &'a std::collections::VecDeque<Frame>,
cursor: usize,
}
impl<'a> Iterator for FramesIter<'a> {
type Item = &'a Frame;
fn next(&mut self) -> Option<&'a Frame> {
let item = self.buf.get(self.cursor)?;
self.cursor += 1;
Some(item)
}
}
#[cfg(test)]
#[path = "source_tests.rs"]
mod tests;