#[cfg(feature = "async")]
pub mod future;
mod loom;
mod standard;
mod state;
pub(crate) use crate::loom::*;
pub use standard::*;
pub use state::*;
use core::result::Result as CoreResult;
use std::error::Error;
pub type Result<T> = CoreResult<T, CatcherError>;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum TransformPosition {
Read(usize),
Finished,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum CatcherError {
ChunkSize,
}
impl std::fmt::Display for CatcherError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{:?}", self)
}
}
impl Error for CatcherError {}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Finaliser {
InPlace,
NewThread,
#[cfg(feature = "async-std-compat")]
AsyncStd,
#[cfg(feature = "tokio-compat")]
Tokio,
#[cfg(feature = "smol-compat")]
Smol,
}
impl Finaliser {
pub fn is_sync(&self) -> bool {
matches!(self, Finaliser::InPlace | Finaliser::NewThread)
}
pub fn run_elsewhere(&self) -> bool {
!matches!(self, Finaliser::InPlace)
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum GrowthStrategy {
Constant(usize),
Linear { start: usize, max: usize },
Geometric { start: usize, max: usize },
}
impl GrowthStrategy {
pub fn lower_bound(self) -> usize {
use GrowthStrategy::*;
match self {
Constant(a) => a,
Linear { start, .. } => start,
Geometric { start, .. } => start,
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Config {
pub chunk_size: GrowthStrategy,
pub spawn_finaliser: Finaliser,
pub use_backing: bool,
pub length_hint: Option<usize>,
pub read_burst_len: usize,
}
impl Config {
pub fn new() -> Self {
Self {
chunk_size: GrowthStrategy::Constant(4096),
spawn_finaliser: Finaliser::NewThread,
use_backing: true,
length_hint: None,
read_burst_len: 4096,
}
}
pub fn chunk_size(mut self, size: GrowthStrategy) -> Self {
self.chunk_size = size;
self
}
pub fn use_backing(mut self, val: bool) -> Self {
self.use_backing = val;
self
}
pub fn spawn_finaliser(mut self, finaliser: Finaliser) -> Self {
self.spawn_finaliser = finaliser;
self
}
pub fn length_hint(mut self, hint: Option<usize>) -> Self {
self.length_hint = hint;
self
}
pub fn read_burst_len(mut self, burst: usize) -> Self {
self.read_burst_len = burst;
self
}
pub fn build<T>(self, source: T) -> Result<Catcher<T>> {
Catcher::with_config(source, self)
}
pub fn build_tx<T, Tx: NeedsBytes>(self, source: T, transform: Tx) -> Result<TxCatcher<T, Tx>> {
TxCatcher::with_tx(source, transform, Some(self))
}
pub(crate) fn next_chunk_size(&self, last_chunk_size: usize, chunk_count: usize) -> usize {
let first_is_special = self.length_hint.is_some();
use GrowthStrategy::*;
match self.chunk_size {
Constant(a) => a,
Linear { start, max } =>
if first_is_special && chunk_count == 1 {
start
} else {
max.min(start + last_chunk_size)
},
Geometric { start, max } =>
if first_is_special && chunk_count == 1 {
start
} else {
last_chunk_size
.checked_shl(1)
.map(|v| max.min(v))
.unwrap_or(max)
},
}
}
}
impl Default for Config {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, Copy, Debug, Default)]
pub struct Identity {}
#[derive(Debug)]
struct BufferChunk {
data: Vec<u8>,
start_pos: usize,
end_pos: usize,
}
impl BufferChunk {
fn new(start_pos: usize, chunk_len: usize) -> Self {
BufferChunk {
data: Vec::with_capacity(chunk_len),
start_pos,
end_pos: start_pos,
}
}
}
trait RopeAndState {
const SHIFT_AMT: usize;
const HOLD_FLAGS: usize = !(0b11 << Self::SHIFT_AMT);
fn state(self) -> FinaliseState;
fn upgrade_state(self) -> Self;
fn holders(self) -> Holders<Self>
where
Self: Sized;
}
impl RopeAndState for usize {
const SHIFT_AMT: usize = (usize::MAX.count_ones() as usize) - 2;
fn state(self) -> FinaliseState {
FinaliseState::from(self >> Self::SHIFT_AMT)
}
fn upgrade_state(self) -> Self {
self + (1 << Self::SHIFT_AMT)
}
fn holders(self) -> Holders<Self> {
Holders(self & Self::HOLD_FLAGS)
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
struct Holders<T>(T);
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum CacheReadLocation {
Roped,
Backed,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum FinaliseState {
Live,
Finalising,
Finalised,
}
impl From<usize> for FinaliseState {
fn from(val: usize) -> Self {
use FinaliseState::*;
match val {
0 => Live,
1 => Finalising,
2 => Finalised,
_ => unreachable!(),
}
}
}
impl From<FinaliseState> for usize {
fn from(val: FinaliseState) -> Self {
use FinaliseState::*;
match val {
Live => 0,
Finalising => 1,
Finalised => 2,
}
}
}
impl FinaliseState {
fn is_source_live(self) -> bool {
matches!(self, FinaliseState::Live)
}
fn is_source_finished(self) -> bool {
!self.is_source_live()
}
fn is_backing_ready(self) -> bool {
matches!(self, FinaliseState::Finalised)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn state_upgrade() {
const INIT_USERS: usize =
0b0000_0000_0000_0000_0000_0000_0000_0000_0000_0010_0000_0100_0000_0000_0000_0011;
const UPGRADE_1: usize =
0b0100_0000_0000_0000_0000_0000_0000_0000_0000_0010_0000_0100_0000_0000_0000_0011;
const UPGRADE_2: usize =
0b1000_0000_0000_0000_0000_0000_0000_0000_0000_0010_0000_0100_0000_0000_0000_0011;
let u1 = INIT_USERS.upgrade_state();
let u2 = u1.upgrade_state();
assert_eq!(u1, UPGRADE_1);
assert_eq!(u2, UPGRADE_2);
assert_eq!(INIT_USERS.state(), FinaliseState::Live);
assert_eq!(u1.state(), FinaliseState::Finalising);
assert_eq!(u2.state(), FinaliseState::Finalised);
assert_eq!(INIT_USERS.holders().0, INIT_USERS);
assert_eq!(u1.holders().0, INIT_USERS);
assert_eq!(u2.holders().0, INIT_USERS);
}
#[test]
fn allocation_strategies() {
let linear_cfg = Config::new().chunk_size(GrowthStrategy::Linear {
start: 4096,
max: 16_384,
});
assert_eq!(linear_cfg.next_chunk_size(4096, 1), 8192);
assert_eq!(linear_cfg.next_chunk_size(8192, 2), 12_288);
assert_eq!(linear_cfg.next_chunk_size(12_288, 3), 16_384);
assert_eq!(linear_cfg.next_chunk_size(16_384, 4), 16_384);
let geom_cfg = Config::new().chunk_size(GrowthStrategy::Geometric {
start: 4096,
max: 32_768,
});
assert_eq!(geom_cfg.next_chunk_size(4096, 1), 8192);
assert_eq!(geom_cfg.next_chunk_size(8192, 2), 16_384);
assert_eq!(geom_cfg.next_chunk_size(16_384, 3), 32_768);
assert_eq!(geom_cfg.next_chunk_size(32_768, 4), 32_768);
}
}