1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
// Released under the MIT License.
// Copyright, 2026, by Samuel Williams.
use crate::IoBufSequence;
use crate::buffer_sequence_owner::BufferSequenceOwner;
use std::io::IoSlice;
/// 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.
///
/// ```
/// use base_scheduler::{IoBuffers, IoBufSequence};
/// let mut buffers = IoBuffers::new([b"header".to_vec(), b"body".to_vec()]);
/// // After an operation returns its owner and reports a short transfer:
/// buffers.advance(7);
/// assert_eq!(buffers.buffer(0), b"ody");
/// let [header, body] = buffers.into_inner();
/// assert_eq!(header, b"header");
/// assert_eq!(body, b"body");
/// ```
///
/// Borrowed I/O vectors cannot outlive the package, even when copied:
///
/// ```compile_fail
/// use base_scheduler::{IoBuffers, IoBufSequence};
/// let vector = {
/// let buffers = IoBuffers::new([vec![1, 2]]);
/// buffers.io_slices().unwrap()[0]
/// };
/// assert_eq!(&*vector, &[1, 2]);
/// ```
pub struct IoBuffers<Buffers> {
// 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.
vectors: Vec<IoSlice<'static>>,
buffers: BufferSequenceOwner<Buffers>,
start: usize,
}
impl<Buffers: IoBufSequence> IoBuffers<Buffers> {
/// Prepare one vector per source range, retaining the complete sequence owner. This allocates vector storage once; subsequent moves, borrows, and advancement do not allocate.
///
/// Panics on Windows if an individual byte range exceeds the `IoSlice`/`IoSliceMut` 4 GiB limit.
pub fn new(buffers: Buffers) -> Self {
let buffers = BufferSequenceOwner::new(buffers);
let vectors = (0..buffers.get().buffer_count())
.map(|index| {
// 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.
unsafe {
std::mem::transmute::<IoSlice<'_>, IoSlice<'static>>(IoSlice::new(
buffers.get().buffer(index),
))
}
})
.collect();
Self {
vectors,
buffers,
start: 0,
}
}
/// 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.
///
/// 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.
pub fn advance(&mut self, bytes: usize) {
let (count, rest) =
crate::io_buffers::advance_position(&self.vectors[self.start..], bytes, |vector| {
vector.len()
});
if rest > 0 {
self.vectors[self.start + count].advance(rest);
}
self.start += count;
}
/// Recover the original sequence, including fully consumed buffers and their original ranges.
pub fn into_inner(self) -> Buffers {
drop(self.vectors);
self.buffers.into_inner()
}
}
// Compute the cursor before modifying anything, so over-advancement leaves the package intact. Visit only consumed vectors and the first remaining vector.
pub(crate) fn advance_position<T>(
vectors: &[T],
mut bytes: usize,
len: impl Fn(&T) -> usize,
) -> (usize, usize) {
let mut count = 0;
for vector in vectors {
let length = len(vector);
if bytes < length {
break;
}
bytes -= length;
count += 1;
}
assert!(
count < vectors.len() || bytes == 0,
"advance beyond remaining buffers"
);
(count, bytes)
}
// 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.
unsafe impl<Buffers: IoBufSequence> IoBufSequence for IoBuffers<Buffers> {
fn buffer_count(&self) -> usize {
self.vectors.len() - self.start
}
fn buffer(&self, index: usize) -> &[u8] {
&self.vectors[self.start..][index]
}
fn io_slices(&self) -> Option<&[IoSlice<'_>]> {
Some(&self.vectors[self.start..])
}
}