Skip to main content

base_scheduler/
io_buffers.rs

1// Released under the MIT License.
2// Copyright, 2026, by Samuel Williams.
3
4use crate::IoBufSequence;
5use crate::buffer_sequence_owner::BufferSequenceOwner;
6use std::io::IoSlice;
7
8/// An owned scatter/gather package with I/O vectors prepared once at construction. The vector allocation and source owners are reused between operations; payload bytes are never copied.
9///
10/// ```
11/// use base_scheduler::{IoBuffers, IoBufSequence};
12/// let mut buffers = IoBuffers::new([b"header".to_vec(), b"body".to_vec()]);
13/// // After an operation returns its owner and reports a short transfer:
14/// buffers.advance(7);
15/// assert_eq!(buffers.buffer(0), b"ody");
16/// let [header, body] = buffers.into_inner();
17/// assert_eq!(header, b"header");
18/// assert_eq!(body, b"body");
19/// ```
20///
21/// Borrowed I/O vectors cannot outlive the package, even when copied:
22///
23/// ```compile_fail
24/// use base_scheduler::{IoBuffers, IoBufSequence};
25/// let vector = {
26///     let buffers = IoBuffers::new([vec![1, 2]]);
27///     buffers.io_slices().unwrap()[0]
28/// };
29/// assert_eq!(&*vector, &[1, 2]);
30/// ```
31pub struct IoBuffers<Buffers> {
32    // Private lifetime erasure retains native layout without borrowing a sibling field. No 'static view escapes: all access is tied to a borrow of this package. Drop the vectors before releasing the owners.
33    vectors: Vec<IoSlice<'static>>,
34    buffers: BufferSequenceOwner<Buffers>,
35    start: usize,
36}
37
38impl<Buffers: IoBufSequence> IoBuffers<Buffers> {
39    /// Prepare one vector per source range, retaining the complete sequence owner. This allocates vector storage once; subsequent moves, borrows, and advancement do not allocate.
40    ///
41    /// Panics on Windows if an individual byte range exceeds the `IoSlice`/`IoSliceMut` 4 GiB limit.
42    pub fn new(buffers: Buffers) -> Self {
43        let buffers = BufferSequenceOwner::new(buffers);
44        let vectors = (0..buffers.get().buffer_count())
45            .map(|index| {
46                // SAFETY: IoBufSequence guarantees stable storage across owner moves. The source owner remains private and retained until vectors are destroyed; public views shorten this lifetime to a borrow of the package.
47                unsafe {
48                    std::mem::transmute::<IoSlice<'_>, IoSlice<'static>>(IoSlice::new(
49                        buffers.get().buffer(index),
50                    ))
51                }
52            })
53            .collect();
54        Self {
55            vectors,
56            buffers,
57            start: 0,
58        }
59    }
60
61    /// Skip completed bytes after an operation has returned the package. Fully consumed vectors are skipped; only the first partially consumed vector is adjusted. Source owners and their original ranges are preserved.
62    ///
63    /// Panics without changing the package if `bytes` exceeds the remaining length. Advancing by zero also skips leading empty vectors. Schedulers never advance the package automatically.
64    pub fn advance(&mut self, bytes: usize) {
65        let (count, rest) =
66            crate::io_buffers::advance_position(&self.vectors[self.start..], bytes, |vector| {
67                vector.len()
68            });
69        if rest > 0 {
70            self.vectors[self.start + count].advance(rest);
71        }
72        self.start += count;
73    }
74
75    /// Recover the original sequence, including fully consumed buffers and their original ranges.
76    pub fn into_inner(self) -> Buffers {
77        drop(self.vectors);
78        self.buffers.into_inner()
79    }
80}
81
82// Compute the cursor before modifying anything, so over-advancement leaves the package intact. Visit only consumed vectors and the first remaining vector.
83pub(crate) fn advance_position<T>(
84    vectors: &[T],
85    mut bytes: usize,
86    len: impl Fn(&T) -> usize,
87) -> (usize, usize) {
88    let mut count = 0;
89    for vector in vectors {
90        let length = len(vector);
91        if bytes < length {
92            break;
93        }
94        bytes -= length;
95        count += 1;
96    }
97    assert!(
98        count < vectors.len() || bytes == 0,
99        "advance beyond remaining buffers"
100    );
101    (count, bytes)
102}
103
104// SAFETY: The private sequence retains all byte storage, and only exclusive, caller-controlled advancement changes exposed ranges. Cached vectors remain valid across moves and are borrowed only for the lifetime of the package.
105unsafe impl<Buffers: IoBufSequence> IoBufSequence for IoBuffers<Buffers> {
106    fn buffer_count(&self) -> usize {
107        self.vectors.len() - self.start
108    }
109
110    fn buffer(&self, index: usize) -> &[u8] {
111        &self.vectors[self.start..][index]
112    }
113
114    fn io_slices(&self) -> Option<&[IoSlice<'_>]> {
115        Some(&self.vectors[self.start..])
116    }
117}