memkit 0.2.0-beta.1

Deterministic, intent-driven memory allocation for systems requiring predictable performance
Documentation
//! Handle-based allocation for stable references.

use std::sync::RwLock;

/// A stable, generation-tracked handle to an allocation.
///
/// Handles are useful for references that need to survive arena resets
/// or memory relocation.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct MkHandle {
    pub(crate) index: u32,
    pub(crate) generation: u32,
}

/// An entry in the handle table.
struct HandleEntry {
    ptr: *mut u8,
    generation: u32,
}

// SAFETY: HandleEntry only contains raw pointers to memory that is managed
// by the allocator. Access is synchronized via RwLock in MkHandleAllocator.
// The pointers themselves are never dereferenced without proper lifetime checks.
unsafe impl Send for HandleEntry {}
unsafe impl Sync for HandleEntry {}

/// Manages a table of handles to allocations.
pub struct MkHandleAllocator {
    entries: RwLock<Vec<HandleEntry>>,
    free_indices: RwLock<Vec<u32>>,
}

impl MkHandleAllocator {
    /// Create a new handle allocator.
    pub fn new() -> Self {
        Self {
            entries: RwLock::new(Vec::with_capacity(1024)),
            free_indices: RwLock::new(Vec::with_capacity(128)),
        }
    }

    /// Associate a pointer with a new handle.
    pub fn allocate(&self, ptr: *mut u8) -> MkHandle {
        let mut free_indices = self.free_indices.write().unwrap();
        
        if let Some(index) = free_indices.pop() {
            let mut entries = self.entries.write().unwrap();
            let entry = &mut entries[index as usize];
            entry.ptr = ptr;
            entry.generation += 1;
            
            MkHandle {
                index,
                generation: entry.generation,
            }
        } else {
            let mut entries = self.entries.write().unwrap();
            let index = entries.len() as u32;
            let generation = 1;
            
            entries.push(HandleEntry {
                ptr,
                generation,
            });
            
            MkHandle {
                index,
                generation,
            }
        }
    }

    /// Update the pointer associated with a handle.
    ///
    /// Useful for memory relocation (e.g., during arena compaction).
    pub fn update(&self, handle: MkHandle, new_ptr: *mut u8) -> bool {
        let mut entries = self.entries.write().unwrap();
        if let Some(entry) = entries.get_mut(handle.index as usize) {
            if entry.generation == handle.generation {
                entry.ptr = new_ptr;
                return true;
            }
        }
        false
    }

    /// Resolve a handle to its current pointer.
    ///
    /// Returns `None` if the handle is invalid or has been deallocated.
    pub fn resolve(&self, handle: MkHandle) -> Option<*mut u8> {
        let entries = self.entries.read().unwrap();
        entries.get(handle.index as usize).and_then(|entry| {
            if entry.generation == handle.generation && !entry.ptr.is_null() {
                Some(entry.ptr)
            } else {
                None
            }
        })
    }

    /// Deallocate a handle, making its index reusable.
    pub fn deallocate(&self, handle: MkHandle) -> bool {
        let mut entries = self.entries.write().unwrap();
        if let Some(entry) = entries.get_mut(handle.index as usize) {
            if entry.generation == handle.generation {
                entry.ptr = std::ptr::null_mut();
                self.free_indices.write().unwrap().push(handle.index);
                return true;
            }
        }
        false
    }

    /// Get the number of active handles.
    pub fn len(&self) -> usize {
        let entries = self.entries.read().unwrap();
        let free_indices = self.free_indices.read().unwrap();
        entries.len() - free_indices.len()
    }

    /// Check if the handle allocator is empty.
    pub fn is_empty(&self) -> bool {
        self.len() == 0
    }
}

impl Default for MkHandleAllocator {
    fn default() -> Self {
        Self::new()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_handle_allocator_basic() {
        let table = MkHandleAllocator::new();
        let ptr = 0x1234 as *mut u8;
        
        let handle = table.allocate(ptr);
        assert_eq!(table.resolve(handle), Some(ptr));
        
        let new_ptr = 0x5678 as *mut u8;
        assert!(table.update(handle, new_ptr));
        assert_eq!(table.resolve(handle), Some(new_ptr));
        
        assert!(table.deallocate(handle));
        assert_eq!(table.resolve(handle), None);
    }

    #[test]
    fn test_handle_generation() {
        let table = MkHandleAllocator::new();
        let ptr1 = 0x1 as *mut u8;
        let handle1 = table.allocate(ptr1);
        
        table.deallocate(handle1);
        
        let ptr2 = 0x2 as *mut u8;
        let handle2 = table.allocate(ptr2);
        
        assert_eq!(handle1.index, handle2.index);
        assert_ne!(handle1.generation, handle2.generation);
        
        assert_eq!(table.resolve(handle1), None);
        assert_eq!(table.resolve(handle2), Some(ptr2));
    }
}