u_nesting_core/
memory.rs

1//! Memory optimization utilities.
2//!
3//! This module provides memory-efficient data structures and allocation patterns
4//! for high-performance nesting and packing operations.
5//!
6//! ## Features
7//!
8//! - **Object Pool**: Reusable object allocation to reduce heap pressure
9//! - **Geometry Instancing**: Shared vertex data for repeated geometries
10//! - **Scratch Buffer**: Thread-local temporary storage
11
12use std::cell::RefCell;
13use std::collections::HashMap;
14use std::sync::Arc;
15
16/// A simple object pool for reusing allocations.
17///
18/// This pool helps reduce allocation overhead when repeatedly creating
19/// and destroying objects of the same type during optimization loops.
20///
21/// # Example
22///
23/// ```rust
24/// use u_nesting_core::memory::ObjectPool;
25///
26/// let pool: ObjectPool<Vec<f64>> = ObjectPool::new(|| Vec::with_capacity(100));
27///
28/// // Get an object from the pool (or create new if empty)
29/// let mut vec = pool.get();
30/// vec.push(1.0);
31/// vec.push(2.0);
32///
33/// // Return to pool for reuse (clears the vec)
34/// pool.put(vec);
35///
36/// // Next get() will reuse the allocation
37/// let vec2 = pool.get();
38/// assert_eq!(vec2.capacity(), 100); // Capacity preserved
39/// ```
40pub struct ObjectPool<T> {
41    pool: RefCell<Vec<T>>,
42    factory: Box<dyn Fn() -> T>,
43    max_size: usize,
44}
45
46impl<T> ObjectPool<T> {
47    /// Creates a new object pool with the given factory function.
48    pub fn new<F>(factory: F) -> Self
49    where
50        F: Fn() -> T + 'static,
51    {
52        Self {
53            pool: RefCell::new(Vec::new()),
54            factory: Box::new(factory),
55            max_size: 64,
56        }
57    }
58
59    /// Creates a pool with a custom maximum size.
60    pub fn with_max_size<F>(factory: F, max_size: usize) -> Self
61    where
62        F: Fn() -> T + 'static,
63    {
64        Self {
65            pool: RefCell::new(Vec::new()),
66            factory: Box::new(factory),
67            max_size,
68        }
69    }
70
71    /// Gets an object from the pool, or creates a new one if the pool is empty.
72    pub fn get(&self) -> T {
73        self.pool
74            .borrow_mut()
75            .pop()
76            .unwrap_or_else(|| (self.factory)())
77    }
78
79    /// Returns an object to the pool for reuse.
80    pub fn put(&self, item: T) {
81        let mut pool = self.pool.borrow_mut();
82        if pool.len() < self.max_size {
83            pool.push(item);
84        }
85        // If pool is full, item is dropped
86    }
87
88    /// Returns the current number of objects in the pool.
89    pub fn len(&self) -> usize {
90        self.pool.borrow().len()
91    }
92
93    /// Returns true if the pool is empty.
94    pub fn is_empty(&self) -> bool {
95        self.pool.borrow().is_empty()
96    }
97
98    /// Clears all objects from the pool.
99    pub fn clear(&self) {
100        self.pool.borrow_mut().clear();
101    }
102}
103
104/// A clearable trait for objects that can be reset for reuse.
105pub trait Clearable {
106    /// Clears the object's contents while preserving capacity.
107    fn clear_for_reuse(&mut self);
108}
109
110impl<T> Clearable for Vec<T> {
111    fn clear_for_reuse(&mut self) {
112        self.clear();
113    }
114}
115
116impl<K, V> Clearable for HashMap<K, V> {
117    fn clear_for_reuse(&mut self) {
118        self.clear();
119    }
120}
121
122/// Object pool that automatically clears returned objects.
123pub struct ClearingPool<T: Clearable> {
124    inner: ObjectPool<T>,
125}
126
127impl<T: Clearable> ClearingPool<T> {
128    /// Creates a new clearing pool.
129    pub fn new<F>(factory: F) -> Self
130    where
131        F: Fn() -> T + 'static,
132    {
133        Self {
134            inner: ObjectPool::new(factory),
135        }
136    }
137
138    /// Gets an object from the pool.
139    pub fn get(&self) -> T {
140        self.inner.get()
141    }
142
143    /// Returns an object to the pool, clearing it first.
144    pub fn put(&self, mut item: T) {
145        item.clear_for_reuse();
146        self.inner.put(item);
147    }
148}
149
150/// Shared geometry data for instancing.
151///
152/// When placing multiple copies of the same geometry, this structure
153/// allows sharing the vertex data to reduce memory usage.
154#[derive(Debug, Clone)]
155pub struct SharedGeometry<V> {
156    /// Unique identifier
157    pub id: String,
158    /// Shared vertex data
159    vertices: Arc<Vec<V>>,
160    /// Reference count (for debugging/monitoring)
161    ref_count: usize,
162}
163
164impl<V: Clone> SharedGeometry<V> {
165    /// Creates a new shared geometry.
166    pub fn new(id: impl Into<String>, vertices: Vec<V>) -> Self {
167        Self {
168            id: id.into(),
169            vertices: Arc::new(vertices),
170            ref_count: 1,
171        }
172    }
173
174    /// Gets a reference to the shared vertices.
175    pub fn vertices(&self) -> &[V] {
176        &self.vertices
177    }
178
179    /// Creates a clone that shares the vertex data.
180    pub fn share(&self) -> Self {
181        Self {
182            id: self.id.clone(),
183            vertices: Arc::clone(&self.vertices),
184            ref_count: self.ref_count + 1,
185        }
186    }
187
188    /// Returns the number of references to this geometry's data.
189    pub fn strong_count(&self) -> usize {
190        Arc::strong_count(&self.vertices)
191    }
192}
193
194/// A geometry instance cache for deduplication.
195///
196/// Caches shared geometry data to avoid duplicating vertex arrays
197/// when the same geometry appears multiple times.
198#[derive(Default)]
199pub struct GeometryCache<V> {
200    cache: HashMap<String, SharedGeometry<V>>,
201}
202
203impl<V: Clone> GeometryCache<V> {
204    /// Creates a new empty cache.
205    pub fn new() -> Self {
206        Self {
207            cache: HashMap::new(),
208        }
209    }
210
211    /// Gets or creates a shared geometry.
212    ///
213    /// If a geometry with the same ID exists, returns a shared reference.
214    /// Otherwise, creates a new entry with the provided vertices.
215    pub fn get_or_insert(&mut self, id: &str, vertices: Vec<V>) -> SharedGeometry<V> {
216        if let Some(existing) = self.cache.get(id) {
217            existing.share()
218        } else {
219            let shared = SharedGeometry::new(id, vertices);
220            self.cache.insert(id.to_string(), shared.share());
221            shared
222        }
223    }
224
225    /// Checks if a geometry is already cached.
226    pub fn contains(&self, id: &str) -> bool {
227        self.cache.contains_key(id)
228    }
229
230    /// Returns the number of cached geometries.
231    pub fn len(&self) -> usize {
232        self.cache.len()
233    }
234
235    /// Returns true if the cache is empty.
236    pub fn is_empty(&self) -> bool {
237        self.cache.is_empty()
238    }
239
240    /// Clears the cache.
241    pub fn clear(&mut self) {
242        self.cache.clear();
243    }
244
245    /// Returns total memory usage estimate (vertex count * size).
246    pub fn memory_usage(&self) -> usize {
247        self.cache
248            .values()
249            .map(|g| g.vertices.len() * std::mem::size_of::<V>())
250            .sum()
251    }
252}
253
254/// Thread-local scratch buffer for temporary allocations.
255///
256/// Provides a reusable buffer for algorithms that need temporary storage,
257/// avoiding repeated allocations.
258///
259/// # Example
260///
261/// ```rust
262/// use u_nesting_core::memory::ScratchBuffer;
263///
264/// let scratch = ScratchBuffer::<f64>::new(1024);
265///
266/// // Use the buffer
267/// scratch.with_buffer(|buf| {
268///     buf.push(1.0);
269///     buf.push(2.0);
270///     // Process data...
271///     buf.iter().sum::<f64>()
272/// });
273/// // Buffer is automatically cleared after use
274/// ```
275pub struct ScratchBuffer<T> {
276    buffer: RefCell<Vec<T>>,
277    initial_capacity: usize,
278}
279
280impl<T> ScratchBuffer<T> {
281    /// Creates a new scratch buffer with the given initial capacity.
282    pub fn new(capacity: usize) -> Self {
283        Self {
284            buffer: RefCell::new(Vec::with_capacity(capacity)),
285            initial_capacity: capacity,
286        }
287    }
288
289    /// Executes a function with access to the buffer, clearing it afterward.
290    pub fn with_buffer<F, R>(&self, f: F) -> R
291    where
292        F: FnOnce(&mut Vec<T>) -> R,
293    {
294        let mut buf = self.buffer.borrow_mut();
295        let result = f(&mut buf);
296        buf.clear();
297        result
298    }
299
300    /// Returns the current capacity of the buffer.
301    pub fn capacity(&self) -> usize {
302        self.buffer.borrow().capacity()
303    }
304
305    /// Shrinks the buffer back to initial capacity if it has grown too large.
306    pub fn shrink_if_needed(&self, max_capacity: usize) {
307        let mut buf = self.buffer.borrow_mut();
308        if buf.capacity() > max_capacity {
309            buf.shrink_to(self.initial_capacity);
310        }
311    }
312}
313
314/// Memory statistics for monitoring.
315#[derive(Debug, Clone, Default)]
316pub struct MemoryStats {
317    /// Number of pool allocations avoided
318    pub pool_hits: usize,
319    /// Number of new allocations made
320    pub pool_misses: usize,
321    /// Number of cached geometries
322    pub cached_geometries: usize,
323    /// Estimated memory saved by caching (bytes)
324    pub memory_saved_bytes: usize,
325}
326
327impl MemoryStats {
328    /// Creates new empty stats.
329    pub fn new() -> Self {
330        Self::default()
331    }
332
333    /// Records a pool hit (reused allocation).
334    pub fn record_pool_hit(&mut self) {
335        self.pool_hits += 1;
336    }
337
338    /// Records a pool miss (new allocation).
339    pub fn record_pool_miss(&mut self) {
340        self.pool_misses += 1;
341    }
342
343    /// Returns the pool hit rate (0.0 - 1.0).
344    pub fn hit_rate(&self) -> f64 {
345        let total = self.pool_hits + self.pool_misses;
346        if total == 0 {
347            0.0
348        } else {
349            self.pool_hits as f64 / total as f64
350        }
351    }
352}
353
354#[cfg(test)]
355mod tests {
356    use super::*;
357
358    #[test]
359    fn test_object_pool() {
360        let pool: ObjectPool<Vec<i32>> = ObjectPool::new(|| Vec::with_capacity(10));
361
362        assert!(pool.is_empty());
363
364        let mut v1 = pool.get();
365        v1.push(1);
366        v1.push(2);
367        pool.put(v1);
368
369        assert_eq!(pool.len(), 1);
370
371        let v2 = pool.get();
372        assert!(pool.is_empty());
373        assert!(v2.capacity() >= 10);
374    }
375
376    #[test]
377    fn test_clearing_pool() {
378        let pool: ClearingPool<Vec<i32>> = ClearingPool::new(|| Vec::with_capacity(10));
379
380        let mut v1 = pool.get();
381        v1.push(1);
382        v1.push(2);
383        v1.push(3);
384        pool.put(v1);
385
386        let v2 = pool.get();
387        assert!(v2.is_empty()); // Should be cleared
388        assert!(v2.capacity() >= 10); // But capacity preserved
389    }
390
391    #[test]
392    fn test_shared_geometry() {
393        let g1 = SharedGeometry::new("test", vec![(0.0, 0.0), (1.0, 0.0), (1.0, 1.0)]);
394        assert_eq!(g1.strong_count(), 1);
395
396        let g2 = g1.share();
397        assert_eq!(g1.strong_count(), 2);
398        assert_eq!(g2.strong_count(), 2);
399
400        assert_eq!(g1.vertices().len(), 3);
401        assert_eq!(g2.vertices().len(), 3);
402    }
403
404    #[test]
405    fn test_geometry_cache() {
406        let mut cache: GeometryCache<(f64, f64)> = GeometryCache::new();
407
408        let g1 = cache.get_or_insert(
409            "rect1",
410            vec![(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)],
411        );
412        let g2 = cache.get_or_insert("rect1", vec![]); // Should reuse existing
413
414        assert_eq!(g1.strong_count(), 3); // cache + g1 + g2
415        assert_eq!(g2.strong_count(), 3);
416        assert_eq!(cache.len(), 1);
417    }
418
419    #[test]
420    fn test_scratch_buffer() {
421        let scratch = ScratchBuffer::<i32>::new(100);
422
423        let sum = scratch.with_buffer(|buf| {
424            buf.push(1);
425            buf.push(2);
426            buf.push(3);
427            buf.iter().sum::<i32>()
428        });
429
430        assert_eq!(sum, 6);
431
432        // Buffer should be cleared
433        scratch.with_buffer(|buf| {
434            assert!(buf.is_empty());
435        });
436    }
437
438    #[test]
439    fn test_memory_stats() {
440        let mut stats = MemoryStats::new();
441
442        stats.record_pool_hit();
443        stats.record_pool_hit();
444        stats.record_pool_miss();
445
446        assert_eq!(stats.pool_hits, 2);
447        assert_eq!(stats.pool_misses, 1);
448        assert!((stats.hit_rate() - 0.666).abs() < 0.01);
449    }
450}