use std::borrow::Cow;
use std::fmt::Debug;
use std::mem::ManuallyDrop;
use std::ops::Range;
use super::{Item, UniversalRead};
use crate::common::ext::aligned_vec::ACow;
use crate::common::generic_consts::AccessPattern;
use crate::common::universal_io::{UioResult, UserData};
pub trait ReadPipeline<'file, U>: Sized
where
U: UserData,
{
type File: 'file;
fn new() -> UioResult<Self>;
fn can_schedule(&mut self) -> bool;
fn schedule<P: AccessPattern>(
&mut self,
user_data: U,
file: &'file Self::File,
range: Range<u64>,
align: usize,
) -> UioResult<()>;
fn schedule_whole(&mut self, user_data: U, file: &'file Self::File, from: u64)
-> UioResult<()>;
fn wait(&mut self) -> UioResult<Option<(U, ACow<'file>)>>;
#[inline]
fn wait_bytemuck<T: Item>(&mut self) -> UioResult<Option<(U, Cow<'file, [T]>)>> {
let Some((user_data, bytes)) = self.wait()? else {
return Ok(None);
};
let items = bytes
.try_cast_bytemuck()
.expect("data has compatible layout");
Ok(Some((user_data, items)))
}
}
pub struct OwnedPipeline<R, U>
where
R: UniversalRead + 'static,
U: UserData,
{
pipeline: ManuallyDrop<R::ReadPipeline<'static, U>>,
file: ManuallyDrop<Box<R>>,
}
impl<R, U> Debug for OwnedPipeline<R, U>
where
R: UniversalRead,
U: UserData,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("OwnedPipeline")
.field("file", &self.file)
.finish_non_exhaustive()
}
}
impl<R, U> OwnedPipeline<R, U>
where
R: UniversalRead + 'static,
U: UserData,
{
pub fn new(file: R) -> UioResult<Self> {
let pipeline = R::ReadPipeline::new()?;
let pipeline = Self {
pipeline: ManuallyDrop::new(pipeline),
file: ManuallyDrop::new(Box::new(file)),
};
Ok(pipeline)
}
#[inline]
pub fn can_schedule(&mut self) -> bool {
self.pipeline.can_schedule()
}
pub fn schedule<P: AccessPattern>(
&mut self,
user_data: U,
range: Range<u64>,
align: usize,
) -> UioResult<()> {
let file: &R = &self.file;
let file: &'static R = unsafe { (file as *const R).as_ref_unchecked() };
self.pipeline.schedule::<P>(user_data, file, range, align)
}
pub fn schedule_whole(&mut self, user_data: U, from: u64) -> UioResult<()> {
let file: &R = &self.file;
let file: &'static R = unsafe { (file as *const R).as_ref_unchecked() };
self.pipeline.schedule_whole(user_data, file, from)
}
#[inline]
pub fn wait(&mut self) -> UioResult<Option<(U, ACow<'_>)>> {
self.pipeline.wait()
}
#[inline]
pub fn wait_bytemuck<T: Item>(&mut self) -> UioResult<Option<(U, Cow<'_, [T]>)>> {
let Some((user_data, bytes)) = self.wait()? else {
return Ok(None);
};
let items = bytes
.try_cast_bytemuck()
.expect("data has compatible layout");
Ok(Some((user_data, items)))
}
#[expect(
clippy::let_and_return,
reason = "better readability around unsafe code"
)]
pub fn into_inner(self) -> R {
let mut this = ManuallyDrop::new(self);
let file = unsafe { this.destructure() };
file
}
}
impl<R, U> OwnedPipeline<R, U>
where
R: UniversalRead + 'static,
U: UserData,
{
unsafe fn destructure(&mut self) -> R {
let Self { pipeline, file } = self;
let file = unsafe { ManuallyDrop::take(file) };
let pipeline = unsafe { ManuallyDrop::take(pipeline) };
drop(pipeline);
*file
}
}
impl<R, U> Drop for OwnedPipeline<R, U>
where
R: UniversalRead + 'static,
U: UserData,
{
fn drop(&mut self) {
let file = unsafe { self.destructure() };
drop(file);
}
}