|
| 1 | +#![cfg(feature = "std")] |
| 2 | + |
| 3 | +use core::{any::type_name, fmt, ptr}; |
| 4 | + |
| 5 | +use crate::{Drain, WeakFixupFn}; |
| 6 | + |
| 7 | +/// A splicing iterator for a `HeaderVec`. |
| 8 | +/// |
| 9 | +/// This struct is created by [`Vec::splice()`]. |
| 10 | +/// See its documentation for more. |
| 11 | +/// |
| 12 | +/// # Example |
| 13 | +/// |
| 14 | +/// ``` |
| 15 | +/// # use header_vec::HeaderVec; |
| 16 | +/// let mut hv: HeaderVec<(), _> = HeaderVec::from([0, 1, 2]); |
| 17 | +/// let new = [7, 8]; |
| 18 | +/// let iter = hv.splice(1.., new); |
| 19 | +/// ``` |
| 20 | +pub struct Splice<'a, H, I: Iterator + 'a> { |
| 21 | + pub(super) drain: Drain<'a, H, I::Item>, |
| 22 | + pub(super) replace_with: I, |
| 23 | + pub(super) weak_fixup: Option<WeakFixupFn<'a>>, |
| 24 | +} |
| 25 | + |
| 26 | +impl<H, I: Iterator> Iterator for Splice<'_, H, I> { |
| 27 | + type Item = I::Item; |
| 28 | + |
| 29 | + fn next(&mut self) -> Option<Self::Item> { |
| 30 | + self.drain.next() |
| 31 | + } |
| 32 | + |
| 33 | + fn size_hint(&self) -> (usize, Option<usize>) { |
| 34 | + self.drain.size_hint() |
| 35 | + } |
| 36 | +} |
| 37 | + |
| 38 | +impl<H, I: Iterator> Splice<'_, H, I> { |
| 39 | + /// Not a standard function, might be useful nevertheless, we use it in tests. |
| 40 | + pub fn drained_slice(&self) -> &[I::Item] { |
| 41 | + self.drain.as_slice() |
| 42 | + } |
| 43 | +} |
| 44 | + |
| 45 | +impl<H, I: Iterator> DoubleEndedIterator for Splice<'_, H, I> { |
| 46 | + fn next_back(&mut self) -> Option<Self::Item> { |
| 47 | + self.drain.next_back() |
| 48 | + } |
| 49 | +} |
| 50 | + |
| 51 | +impl<H, I: Iterator> ExactSizeIterator for Splice<'_, H, I> {} |
| 52 | + |
| 53 | +impl<H, I> fmt::Debug for Splice<'_, H, I> |
| 54 | +where |
| 55 | + I: Iterator + fmt::Debug, |
| 56 | + I::Item: fmt::Debug, |
| 57 | +{ |
| 58 | + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 59 | + f.debug_struct(&format!( |
| 60 | + "Splice<{}, {}>", |
| 61 | + type_name::<H>(), |
| 62 | + type_name::<I>() |
| 63 | + )) |
| 64 | + .field("drain", &self.drain.as_slice()) |
| 65 | + .field("replace_with", &self.replace_with) |
| 66 | + .field("weak_fixup", &self.weak_fixup.is_some()) |
| 67 | + .finish() |
| 68 | + } |
| 69 | +} |
| 70 | + |
| 71 | +impl<H, I: Iterator> Drop for Splice<'_, H, I> { |
| 72 | + #[track_caller] |
| 73 | + fn drop(&mut self) { |
| 74 | + self.drain.by_ref().for_each(drop); |
| 75 | + // At this point draining is done and the only remaining tasks are splicing |
| 76 | + // and moving things into the final place. |
| 77 | + // Which means we can replace the slice::Iter with pointers that won't point to deallocated |
| 78 | + // memory, so that Drain::drop is still allowed to call iter.len(), otherwise it would break |
| 79 | + // the ptr.sub_ptr contract. |
| 80 | + self.drain.iter = [].iter(); |
| 81 | + |
| 82 | + // We will use the replace_with iterator to append elements in place on self.drain.vec. |
| 83 | + // When this hits the tail then elements are moved from the tail to tmp_tail. |
| 84 | + // When the tail is or becomes empty by that, then the remaining elements can be extended to the vec. |
| 85 | + // |
| 86 | + // Finally: |
| 87 | + // Then have continuous elements in the vec: |head|replace_with|(old_tail|)spare_capacity|. |
| 88 | + // The old tail needs to be moved to its final destination. |
| 89 | + // Perhaps making space for the elements in the tmp_tail. |
| 90 | + let mut tmp_tail = Vec::new(); |
| 91 | + |
| 92 | + unsafe { |
| 93 | + let vec = self.drain.vec.as_mut(); |
| 94 | + loop { |
| 95 | + if self.drain.tail_len() == 0 { |
| 96 | + // If the tail is empty, we can just extend the vector with the remaining elements. |
| 97 | + // but we may have stashed some tmp_tail away and should reserve for that. |
| 98 | + // PLANNED: should become 'extend_reserve()' |
| 99 | + vec.reserve_intern( |
| 100 | + self.replace_with.size_hint().0 + tmp_tail.len(), |
| 101 | + false, |
| 102 | + &mut self.weak_fixup, |
| 103 | + ); |
| 104 | + vec.extend(self.replace_with.by_ref()); |
| 105 | + // in case the size_hint was not exact (or returned 0) we need to reserve for the tmp_tail |
| 106 | + // in most cases this will not allocate. later we expect that we have this space reserved. |
| 107 | + vec.reserve_intern(tmp_tail.len(), false, &mut self.weak_fixup); |
| 108 | + break; |
| 109 | + } else if let Some(next) = self.replace_with.next() { |
| 110 | + if vec.len_exact() >= self.drain.tail_start && self.drain.tail_len() > 0 { |
| 111 | + // move one element from the tail to the tmp_tail |
| 112 | + // We reserve for as much elements are hinted by replace_with or the remaining tail, |
| 113 | + // whatever is smaller. |
| 114 | + tmp_tail |
| 115 | + .reserve(self.replace_with.size_hint().0.min(self.drain.tail_len())); |
| 116 | + tmp_tail.push(ptr::read(vec.as_ptr().add(self.drain.tail_start))); |
| 117 | + self.drain.tail_start += 1; |
| 118 | + } |
| 119 | + |
| 120 | + // since we overwrite the old tail here this will never reallocate. |
| 121 | + // PLANNED: vec.push_within_capacity().unwrap_unchecked() |
| 122 | + vec.push(next); |
| 123 | + } else { |
| 124 | + // replace_with is depleted |
| 125 | + break; |
| 126 | + } |
| 127 | + } |
| 128 | + |
| 129 | + let tail_len = self.drain.tail_len(); |
| 130 | + if tail_len > 0 { |
| 131 | + // In case we need to shift the tail farther back we need to reserve space for that. |
| 132 | + // Reserve needs to preserve the tail we have, thus we temporarily set the length to the |
| 133 | + // tail_end and then restore it after the reserve. |
| 134 | + let old_len = vec.len_exact(); |
| 135 | + vec.set_len(self.drain.tail_end); |
| 136 | + vec.reserve_intern(tmp_tail.len(), false, &mut self.weak_fixup); |
| 137 | + vec.set_len(old_len); |
| 138 | + |
| 139 | + // now we can move the tail around |
| 140 | + ptr::copy( |
| 141 | + vec.as_ptr().add(self.drain.tail_start), |
| 142 | + vec.as_mut_ptr().add(vec.len_exact() + tmp_tail.len()), |
| 143 | + tail_len, |
| 144 | + ); |
| 145 | + |
| 146 | + // all elements are moved from the tail, ensure that Drain drop does nothing. |
| 147 | + // PLANNED: eventually we may not need use Drain here |
| 148 | + self.drain.tail_start = self.drain.tail_end; |
| 149 | + } |
| 150 | + |
| 151 | + let tmp_tail_len = tmp_tail.len(); |
| 152 | + if !tmp_tail.is_empty() { |
| 153 | + // When we stashed tail elements to tmp_tail, then fill the gap |
| 154 | + tmp_tail.set_len(0); |
| 155 | + ptr::copy_nonoverlapping( |
| 156 | + tmp_tail.as_ptr(), |
| 157 | + vec.as_mut_ptr().add(vec.len_exact()), |
| 158 | + tmp_tail_len, |
| 159 | + ); |
| 160 | + } |
| 161 | + |
| 162 | + // finally fix the vec length |
| 163 | + let new_len = vec.len_exact() + tmp_tail_len + tail_len; |
| 164 | + vec.set_len(new_len); |
| 165 | + } |
| 166 | + |
| 167 | + // IDEA: implement and benchmark batched copying. This leaves a gap in front of the tails which |
| 168 | + // needs to be filled before resizing. |
| 169 | + // Batch size: |
| 170 | + // Moving one element per iteration to the tmp_tail is not efficient to make space for |
| 171 | + // a element from the replace_with. Thus we determine a number of elements that we |
| 172 | + // transfer in a batch to the tmp_tail. We compute the batch size to be roughly 4kb |
| 173 | + // (Common page size on many systems) (or I::Item, whatever is larger) or the size of |
| 174 | + // the tail when it is smaller. The later ensure that we do a single reserve with the |
| 175 | + // minimum space needed when the tail is smaller than a batch would be . |
| 176 | + // let batch_size = (4096 / std::mem::size_of::<I::Item>()) |
| 177 | + // .max(1) |
| 178 | + // .min(self.drain.tail_len); |
| 179 | + } |
| 180 | +} |
0 commit comments