1use bumpalo::collections::Vec as BumpVec;
38use bumpalo::Bump;
39
40use crate::crypto::Hash;
41
42pub const DEFAULT_ARENA_CAPACITY: usize = 1024 * 1024;
44
45pub struct BatchArena {
50 bump: Bump,
51}
52
53impl BatchArena {
54 #[inline]
56 pub fn new() -> Self {
57 Self {
58 bump: Bump::with_capacity(DEFAULT_ARENA_CAPACITY),
59 }
60 }
61
62 #[inline]
64 pub fn with_capacity(capacity: usize) -> Self {
65 Self {
66 bump: Bump::with_capacity(capacity),
67 }
68 }
69
70 #[inline]
74 pub fn for_events(count: usize) -> Self {
75 let capacity = count * 1024;
76 Self::with_capacity(capacity.max(DEFAULT_ARENA_CAPACITY))
77 }
78
79 #[inline]
83 pub fn for_hashes(count: usize) -> Self {
84 let capacity = count * 64;
85 Self::with_capacity(capacity.max(65536))
86 }
87
88 #[inline]
90 pub fn alloc_bytes(&self, bytes: &[u8]) -> &[u8] {
91 self.bump.alloc_slice_copy(bytes)
92 }
93
94 #[inline]
96 pub fn alloc_str(&self, s: &str) -> &str {
97 self.bump.alloc_str(s)
98 }
99
100 #[inline]
102 pub fn alloc<T>(&self, value: T) -> &mut T {
103 self.bump.alloc(value)
104 }
105
106 #[inline]
108 pub fn alloc_slice<T: Copy>(&self, slice: &[T]) -> &[T] {
109 self.bump.alloc_slice_copy(slice)
110 }
111
112 #[inline]
114 pub fn alloc_vec<T>(&self) -> BumpVec<'_, T> {
115 BumpVec::new_in(&self.bump)
116 }
117
118 #[inline]
120 pub fn alloc_vec_with_capacity<T>(&self, capacity: usize) -> BumpVec<'_, T> {
121 BumpVec::with_capacity_in(capacity, &self.bump)
122 }
123
124 #[inline]
126 pub fn alloc_slice_fill_default<T: Default + Clone>(&self, count: usize) -> &mut [T] {
127 self.bump.alloc_slice_fill_default(count)
128 }
129
130 #[inline]
132 pub fn alloc_hash_array(&self, count: usize) -> &mut [Hash] {
133 self.bump.alloc_slice_fill_default(count)
134 }
135
136 #[inline]
138 pub fn allocated_bytes(&self) -> usize {
139 self.bump.allocated_bytes()
140 }
141
142 #[inline]
147 pub fn reset(&mut self) {
148 self.bump.reset();
149 }
150
151 #[inline]
156 pub fn as_bump(&self) -> &Bump {
157 &self.bump
158 }
159}
160
161impl Default for BatchArena {
162 fn default() -> Self {
163 Self::new()
164 }
165}
166
167impl std::fmt::Debug for BatchArena {
168 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
169 f.debug_struct("BatchArena")
170 .field("allocated_bytes", &self.allocated_bytes())
171 .finish()
172 }
173}
174
175pub struct CanonicalBytesArena<'a> {
180 arena: &'a BatchArena,
181 items: BumpVec<'a, &'a [u8]>,
182}
183
184impl<'a> CanonicalBytesArena<'a> {
185 pub fn new(arena: &'a BatchArena, capacity: usize) -> Self {
187 Self {
188 arena,
189 items: arena.alloc_vec_with_capacity(capacity),
190 }
191 }
192
193 pub fn push(&mut self, bytes: &[u8]) {
195 let allocated = self.arena.alloc_bytes(bytes);
196 self.items.push(allocated);
197 }
198
199 pub fn as_slices(&self) -> &[&'a [u8]] {
201 &self.items
202 }
203
204 pub fn len(&self) -> usize {
206 self.items.len()
207 }
208
209 pub fn is_empty(&self) -> bool {
211 self.items.is_empty()
212 }
213}
214
215#[cfg(test)]
216mod tests {
217 use super::*;
218 use crate::hash;
219
220 #[test]
221 fn test_arena_basic_allocation() {
222 let arena = BatchArena::new();
223
224 let bytes1 = arena.alloc_bytes(b"hello");
225 let bytes2 = arena.alloc_bytes(b"world");
226
227 assert_eq!(bytes1, b"hello");
228 assert_eq!(bytes2, b"world");
229 }
230
231 #[test]
232 fn test_arena_vector() {
233 let arena = BatchArena::new();
234
235 let mut vec = arena.alloc_vec::<u32>();
236 vec.push(1);
237 vec.push(2);
238 vec.push(3);
239
240 assert_eq!(vec.as_slice(), &[1, 2, 3]);
241 }
242
243 #[test]
244 fn test_arena_hash_array() {
245 let arena = BatchArena::new();
246
247 let hashes = arena.alloc_hash_array(4);
248 assert_eq!(hashes.len(), 4);
249
250 for h in hashes.iter() {
252 assert!(h.is_zero());
253 }
254
255 hashes[0] = hash(b"test");
257 assert!(!hashes[0].is_zero());
258 }
259
260 #[test]
261 fn test_arena_reset() {
262 let mut arena = BatchArena::new();
263
264 let first = arena.alloc_bytes(&[0u8; 10000]);
266 let first_ptr = first.as_ptr();
267 let before = arena.allocated_bytes();
268 assert!(before >= 10000);
269
270 arena.reset();
272
273 let second = arena.alloc_bytes(&[1u8; 10000]);
276 let second_ptr = second.as_ptr();
277
278 assert_eq!(second.len(), 10000);
281
282 let after = arena.allocated_bytes();
285 assert!(after <= before * 2, "Arena should reuse memory after reset");
288
289 let _ = (first_ptr, second_ptr);
292 }
293
294 #[test]
295 fn test_canonical_bytes_arena() {
296 let arena = BatchArena::new();
297 let mut collector = CanonicalBytesArena::new(&arena, 10);
298
299 collector.push(b"event 1 canonical bytes");
300 collector.push(b"event 2 canonical bytes");
301 collector.push(b"event 3 canonical bytes");
302
303 assert_eq!(collector.len(), 3);
304
305 let slices = collector.as_slices();
306 assert_eq!(slices[0], b"event 1 canonical bytes");
307 assert_eq!(slices[1], b"event 2 canonical bytes");
308 assert_eq!(slices[2], b"event 3 canonical bytes");
309 }
310
311 #[test]
312 fn test_arena_sizing() {
313 let arena = BatchArena::for_events(100);
314 assert!(arena.as_bump().chunk_capacity() >= 100 * 1024);
316
317 let arena = BatchArena::for_hashes(1000);
318 assert!(arena.as_bump().chunk_capacity() >= 64 * 1024);
320 }
321
322 #[test]
323 fn test_arena_many_small_allocations() {
324 let arena = BatchArena::new();
325
326 for i in 0..1000u32 {
328 let _ = arena.alloc(i);
329 }
330
331 assert!(arena.allocated_bytes() >= 4000);
333 }
334
335 #[test]
336 fn test_arena_slice_allocation() {
337 let arena = BatchArena::new();
338
339 let hashes: Vec<Hash> = (0..10u32).map(|i| hash(&i.to_le_bytes())).collect();
340
341 let allocated = arena.alloc_slice(&hashes);
342
343 assert_eq!(allocated.len(), 10);
344 for (i, h) in allocated.iter().enumerate() {
345 assert_eq!(*h, hash(&(i as u32).to_le_bytes()));
346 }
347 }
348}