use crate::Span;
use crate::memory::{Execution, MemoryError, OwnedVec, Work};
use crate::source::Snapshot;
use core::future::{Future, ready};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Stamp<D, G> {
pub snapshot: Snapshot<D, G>,
pub length: u64,
}
#[derive(Debug)]
pub enum ReadError<E> {
Resource(MemoryError),
Cause(E),
}
pub trait RangeSource {
type Document: Copy + Eq;
type Generation: Copy + Eq;
type Error;
fn stamp(&self) -> Stamp<Self::Document, Self::Generation>;
fn read_at<'q, W: Work + 'q>(
&'q self,
offset: u64,
output: &'q mut [u8],
work: &'q W,
) -> impl Future<Output = Result<usize, ReadError<Self::Error>>> + 'q;
}
pub struct Slice<'a, D, G> {
bytes: &'a [u8],
stamp: Stamp<D, G>,
}
impl<'a, D, G> Slice<'a, D, G> {
#[must_use]
pub fn new(bytes: &'a [u8], snapshot: Snapshot<D, G>) -> Self {
Self {
bytes,
stamp: Stamp {
snapshot,
length: bytes.len() as u64,
},
}
}
}
impl<D: Copy + Eq, G: Copy + Eq> RangeSource for Slice<'_, D, G> {
type Document = D;
type Generation = G;
type Error = core::convert::Infallible;
fn stamp(&self) -> Stamp<D, G> {
self.stamp
}
fn read_at<'q, W: Work + 'q>(
&'q self,
offset: u64,
output: &'q mut [u8],
_work: &'q W,
) -> impl Future<Output = Result<usize, ReadError<Self::Error>>> + 'q {
let count = if let Ok(offset) = usize::try_from(offset)
&& let Some(input) = self.bytes.get(offset..)
{
let count = input.len().min(output.len());
output[..count].copy_from_slice(&input[..count]);
count
} else {
0
};
ready(Ok(count))
}
}
#[derive(Debug)]
pub enum InputError<E> {
Resource {
range: Span,
cause: MemoryError,
},
Changed {
range: Span,
},
Contract {
range: Span,
},
Cause {
range: Span,
cause: E,
},
}
const WINDOW: usize = 4096;
const ZERO_WINDOW: [u8; WINDOW] = [0; WINDOW];
pub struct Window<'q, S: RangeSource> {
source: &'q S,
stamp: Stamp<S::Document, S::Generation>,
bounds: Span,
work: Execution<'q>,
bytes: OwnedVec<u8, Execution<'q>>,
start: u64,
filled: usize,
aligned: bool,
quantum: usize,
}
impl<'q, S: RangeSource> Window<'q, S> {
pub fn new(
source: &'q S,
stamp: Stamp<S::Document, S::Generation>,
bounds: Span,
work: Execution<'q>,
) -> Result<Self, InputError<S::Error>> {
let Some(end) = bounds.start.checked_add(bounds.length) else {
return Err(InputError::Contract { range: bounds });
};
if end > stamp.length {
return Err(InputError::Contract { range: bounds });
}
Self::guard(source, stamp, &work, bounds)?;
let mut bytes = work.vector();
let size = usize::try_from(bounds.length.min(WINDOW as u64))
.expect("bounded window length fits usize");
bytes
.try_extend_from_slice(&ZERO_WINDOW[..size])
.map_err(|cause| InputError::Resource {
range: bounds,
cause,
})?;
Ok(Self {
source,
stamp,
bounds,
work,
bytes,
start: 0,
filled: 0,
aligned: true,
quantum: WINDOW,
})
}
#[must_use]
pub fn execution(&self) -> Execution<'q> {
self.work
}
#[must_use]
pub fn stamp(&self) -> Stamp<S::Document, S::Generation> {
self.stamp
}
#[must_use]
pub fn bounds(&self) -> Span {
self.bounds
}
pub fn fork(&self) -> Result<Self, InputError<S::Error>> {
let mut fork = Self::new(self.source, self.stamp, self.bounds, self.work)?;
fork.aligned = self.aligned;
fork.quantum = self.quantum;
Ok(fork)
}
pub fn check(&self, range: Span) -> Result<(), InputError<S::Error>> {
Self::guard(self.source, self.stamp, &self.work, range)
}
fn guard(
source: &S,
stamp: Stamp<S::Document, S::Generation>,
work: &Execution<'_>,
range: Span,
) -> Result<(), InputError<S::Error>> {
work.charge_work(1)
.map_err(|cause| InputError::Resource { range, cause })?;
if source.stamp() != stamp {
return Err(InputError::Changed { range });
}
Ok(())
}
async fn refill(
&mut self,
start: u64,
length: usize,
complete: bool,
) -> Result<(), InputError<S::Error>> {
self.filled = 0;
let mut used = 0;
loop {
let wanted = (length - used).min(self.quantum);
let range = Span {
start: start + used as u64,
length: wanted as u64,
};
self.check(range)?;
self.work
.charge_work(wanted as u64)
.map_err(|cause| InputError::Resource { range, cause })?;
let count = self
.source
.read_at(
range.start,
&mut self.bytes[used..used + wanted],
&self.work,
)
.await
.map_err(|error| match error {
ReadError::Resource(cause) => InputError::Resource { range, cause },
ReadError::Cause(cause) => InputError::Cause { range, cause },
})?;
self.check(range)?;
if count == 0 || count > wanted {
return Err(InputError::Contract { range });
}
if count < wanted {
self.quantum = count;
self.aligned = false;
}
used += count;
if !complete || used == length {
self.start = start;
self.filled = used;
return Ok(());
}
}
}
pub async fn byte_at(&mut self, offset: u64) -> Result<Option<u8>, InputError<S::Error>> {
let point = Span {
start: offset,
length: 0,
};
self.check(point)?;
if offset < self.bounds.start {
return Err(InputError::Contract { range: point });
}
let end = self.bounds.end();
if offset >= end {
return Ok(None);
}
if offset >= self.start && offset - self.start < self.filled as u64 {
let index = usize::try_from(offset - self.start).expect("cached window index");
return Ok(Some(self.bytes[index]));
}
loop {
let mut start = if self.aligned {
offset / WINDOW as u64 * WINDOW as u64
} else {
offset
};
if start < self.bounds.start {
start = self.bounds.start;
}
let wanted = usize::try_from((end - start).min(self.bytes.len() as u64))
.expect("bounded requested length");
self.refill(start, wanted, false).await?;
let index = usize::try_from(offset - start).expect("query is inside window");
if index < self.filled {
return Ok(Some(self.bytes[index]));
}
debug_assert!(!self.aligned);
}
}
pub async fn chunk_at(&mut self, offset: u64, end: u64) -> Result<&[u8], InputError<S::Error>> {
let range = Span {
start: offset,
length: 0,
};
if end > self.bounds.end() || offset < self.bounds.start || offset > end {
return Err(InputError::Contract { range });
}
if offset == end {
self.check(range)?;
return Ok(&[]);
}
self.byte_at(offset).await?;
let start = usize::try_from(offset - self.start).expect("cached offset");
let count = usize::try_from((end - offset).min((self.filled - start) as u64))
.expect("bounded chunk");
Ok(&self.bytes[start..start + count])
}
pub async fn chunk_before(
&mut self,
end: u64,
minimum: u64,
) -> Result<&[u8], InputError<S::Error>> {
let point = Span {
start: end,
length: 0,
};
if minimum < self.bounds.start || end > self.bounds.end() || minimum > end {
return Err(InputError::Contract { range: point });
}
if end == minimum {
self.check(point)?;
return Ok(&[]);
}
self.check(point)?;
let last = end - 1;
if last < self.start || last - self.start >= self.filled as u64 {
let start = minimum.max(end - end.min(self.bytes.len() as u64));
let length = usize::try_from(end - start).expect("bounded reverse page");
self.refill(start, length, true).await?;
}
let start =
usize::try_from(minimum.max(self.start) - self.start).expect("resident reverse start");
let stop = usize::try_from(end - self.start).expect("resident reverse end");
Ok(&self.bytes[start..stop])
}
}
#[cfg(test)]
mod tests;