1use alloc::vec::Vec;
8
9use concinnity_memory::{Arena, ArenaVec};
10
11#[derive(Clone, Copy)]
31pub struct FrameContext<'a> {
32 pub scratch: &'a Arena,
36}
37
38impl<'a> FrameContext<'a> {
39 pub fn new(scratch: &'a Arena) -> Self {
41 Self { scratch }
42 }
43
44 pub fn collect<T, I>(&self, items: I) -> FrameVec<'a, T>
55 where
56 T: Copy,
57 I: IntoIterator<Item = T>,
58 {
59 let items = items.into_iter();
60 let reservation = items
61 .size_hint()
62 .1
63 .and_then(|upper| self.scratch.vec::<T>(upper));
64 match reservation {
65 Some(mut out) => {
66 out.extend(items);
67 FrameVec::Scratch(out)
68 }
69 None => FrameVec::Heap(items.collect()),
70 }
71 }
72
73 pub fn filled<T: Copy>(&self, len: usize, value: T) -> FrameVec<'a, T> {
79 match self.scratch.vec::<T>(len) {
80 Some(mut out) => {
81 out.extend(core::iter::repeat_n(value, len));
82 FrameVec::Scratch(out)
83 }
84 None => FrameVec::Heap(alloc::vec![value; len]),
85 }
86 }
87
88 pub fn vec<T: Copy>(&self, capacity: usize) -> FrameVec<'a, T> {
96 match self.scratch.vec::<T>(capacity) {
97 Some(out) => FrameVec::Scratch(out),
98 None => FrameVec::Heap(Vec::new()),
99 }
100 }
101}
102
103pub enum FrameVec<'a, T: Copy> {
110 Scratch(ArenaVec<'a, T>),
112 Heap(Vec<T>),
114}
115
116impl<T: Copy> FrameVec<'_, T> {
117 pub fn push(&mut self, value: T) {
122 match self {
123 FrameVec::Scratch(v) => {
124 if !v.push(value) {
125 let mut heap = Vec::with_capacity(v.len() + 1);
126 heap.extend_from_slice(v);
127 heap.push(value);
128 *self = FrameVec::Heap(heap);
129 }
130 }
131 FrameVec::Heap(v) => v.push(value),
132 }
133 }
134}
135
136impl<T: Copy> core::ops::Deref for FrameVec<'_, T> {
137 type Target = [T];
138
139 fn deref(&self) -> &[T] {
140 match self {
141 FrameVec::Scratch(v) => v,
142 FrameVec::Heap(v) => v,
143 }
144 }
145}
146
147impl<T: Copy> core::ops::DerefMut for FrameVec<'_, T> {
148 fn deref_mut(&mut self) -> &mut [T] {
149 match self {
150 FrameVec::Scratch(v) => v,
151 FrameVec::Heap(v) => v,
152 }
153 }
154}
155
156impl<'v, T: Copy> IntoIterator for &'v FrameVec<'_, T> {
157 type Item = &'v T;
158 type IntoIter = core::slice::Iter<'v, T>;
159
160 fn into_iter(self) -> Self::IntoIter {
161 self.iter()
162 }
163}
164
165#[cfg(test)]
166mod tests {
167 use super::*;
168
169 #[test]
170 fn a_gather_that_fits_lands_in_scratch() {
171 let arena = Arena::with_capacity(4096);
172 let frame = FrameContext::new(&arena);
173
174 let out = frame.collect([1u32, 2, 3]);
175 assert!(matches!(out, FrameVec::Scratch(_)));
176 assert_eq!(&*out, &[1, 2, 3]);
177 assert_eq!(arena.overflows(), 0);
178 assert!(arena.used() > 0, "it came out of the reserve");
179 }
180
181 #[test]
184 fn a_gather_too_large_falls_back_to_the_heap_and_is_recorded() {
185 let arena = Arena::with_capacity(8);
186 let frame = FrameContext::new(&arena);
187
188 let out = frame.collect([1u64, 2, 3, 4]);
189 assert!(matches!(out, FrameVec::Heap(_)));
190 assert_eq!(&*out, &[1, 2, 3, 4], "the fallback holds the same values");
191 assert_eq!(arena.overflows(), 1);
192 }
193
194 #[test]
196 fn both_arms_read_the_same_way() {
197 let roomy = Arena::with_capacity(4096);
198 let tight = Arena::with_capacity(0);
199 let items = [7u16, 8, 9];
200
201 let from_scratch = FrameContext::new(&roomy).collect(items);
202 let from_heap = FrameContext::new(&tight).collect(items);
203
204 assert_eq!(&*from_scratch, &*from_heap);
205 assert_eq!(from_scratch.len(), 3);
206 assert_eq!(from_heap.iter().copied().sum::<u16>(), 24);
207 for (a, b) in (&from_scratch).into_iter().zip(&from_heap) {
208 assert_eq!(a, b);
209 }
210 }
211
212 #[test]
213 fn a_filled_frame_is_writable_in_place() {
214 let arena = Arena::with_capacity(4096);
215 let mut frame = FrameContext::new(&arena).filled(4, None::<u32>);
216 assert!(matches!(frame, FrameVec::Scratch(_)));
217 assert_eq!(&*frame, &[None, None, None, None]);
218
219 frame[2] = Some(9);
220 assert_eq!(&*frame, &[None, None, Some(9), None]);
221 }
222
223 #[test]
226 fn a_filled_frame_that_overflowed_is_still_writable() {
227 let arena = Arena::with_capacity(0);
228 let mut frame = FrameContext::new(&arena).filled(3, 0u32);
229 assert!(matches!(frame, FrameVec::Heap(_)));
230 frame[1] = 5;
231 assert_eq!(&*frame, &[0, 5, 0]);
232 assert_eq!(arena.overflows(), 1);
233 }
234
235 #[test]
236 fn a_reserved_frame_takes_pushes_in_scratch() {
237 let arena = Arena::with_capacity(4096);
238 let mut out = FrameContext::new(&arena).vec::<u32>(3);
239 out.push(1);
240 out.push(2);
241 assert!(matches!(out, FrameVec::Scratch(_)));
242 assert_eq!(&*out, &[1, 2]);
243 assert_eq!(arena.overflows(), 0);
244 }
245
246 #[test]
249 fn a_push_past_the_reservation_moves_to_the_heap() {
250 let arena = Arena::with_capacity(4096);
251 let mut out = FrameContext::new(&arena).vec::<u32>(2);
252 out.push(1);
253 out.push(2);
254 out.push(3);
255 assert!(matches!(out, FrameVec::Heap(_)));
256 assert_eq!(&*out, &[1, 2, 3]);
257 }
258
259 #[test]
260 fn a_reservation_the_reserve_cannot_hold_starts_on_the_heap() {
261 let arena = Arena::with_capacity(8);
262 let mut out = FrameContext::new(&arena).vec::<u64>(64);
263 assert!(matches!(out, FrameVec::Heap(_)));
264 assert_eq!(arena.overflows(), 1, "the decline is recorded");
265 out.push(7);
266 assert_eq!(&*out, &[7]);
267 }
268
269 #[test]
270 fn an_empty_gather_costs_nothing() {
271 let arena = Arena::with_capacity(4096);
272 let out = FrameContext::new(&arena).collect([0u8; 0]);
273 assert!(out.is_empty());
274 assert_eq!(arena.overflows(), 0);
275 }
276
277 #[test]
280 fn a_copied_context_shares_one_reserve() {
281 let arena = Arena::with_capacity(4096);
282 let frame = FrameContext::new(&arena);
283 let copy = frame;
284
285 let _a = frame.collect([1u32; 4]);
286 let used = arena.used();
287 let _b = copy.collect([2u32; 4]);
288 assert!(arena.used() > used, "the copy drew from the same reserve");
289 }
290}