lowlet 0.1.2

Low-latency IPC library using shared memory and lock-free structures
Documentation
use std::cell::UnsafeCell;
use std::mem::MaybeUninit;
use std::ptr::NonNull;
use std::sync::atomic::{AtomicUsize, Ordering};

use crate::err::{Error, Result};
use crate::sync::asm::fence::pause;

const CACHE_LINE: usize = 64;

#[repr(C)]
struct PoolSlot<T> {
    next: AtomicUsize,
    data: UnsafeCell<MaybeUninit<T>>,
}

#[repr(C, align(64))]
pub struct ObjectPool<T, const N: usize> {
    free_head: AtomicUsize,
    allocated: AtomicUsize,
    _pad: [u8; CACHE_LINE - 16],
    slots: [PoolSlot<T>; N],
}

unsafe impl<T: Send, const N: usize> Send for ObjectPool<T, N> {}
unsafe impl<T: Send, const N: usize> Sync for ObjectPool<T, N> {}

impl<T, const N: usize> ObjectPool<T, N> {
    const NULL: usize = usize::MAX;

    pub fn new() -> Self {
        let mut slots: [MaybeUninit<PoolSlot<T>>; N] =
            unsafe { MaybeUninit::uninit().assume_init() };

        for (i, slot) in slots.iter_mut().enumerate() {
            let next = if i + 1 < N { i + 1 } else { Self::NULL };
            slot.write(PoolSlot {
                next: AtomicUsize::new(next),
                data: UnsafeCell::new(MaybeUninit::uninit()),
            });
        }

        Self {
            free_head: AtomicUsize::new(0),
            allocated: AtomicUsize::new(0),
            _pad: [0; CACHE_LINE - 16],
            slots: unsafe { std::mem::transmute_copy(&slots) },
        }
    }

    pub fn acquire(&self) -> Result<PoolHandle<'_, T, N>> {
        loop {
            let head = self.free_head.load(Ordering::Acquire);
            if head == Self::NULL {
                return Err(Error::OutOfMemory);
            }

            let next = self.slots[head].next.load(Ordering::Relaxed);

            if self
                .free_head
                .compare_exchange_weak(head, next, Ordering::AcqRel, Ordering::Relaxed)
                .is_ok()
            {
                self.allocated.fetch_add(1, Ordering::Relaxed);
                return Ok(PoolHandle {
                    pool: self,
                    index: head,
                });
            }

            pause();
        }
    }

    pub fn try_acquire(&self) -> Option<PoolHandle<'_, T, N>> {
        let head = self.free_head.load(Ordering::Acquire);
        if head == Self::NULL {
            return None;
        }

        let next = self.slots[head].next.load(Ordering::Relaxed);

        if self
            .free_head
            .compare_exchange(head, next, Ordering::AcqRel, Ordering::Relaxed)
            .is_ok()
        {
            self.allocated.fetch_add(1, Ordering::Relaxed);
            Some(PoolHandle {
                pool: self,
                index: head,
            })
        } else {
            None
        }
    }

    fn release(&self, index: usize) {
        loop {
            let head = self.free_head.load(Ordering::Relaxed);
            self.slots[index].next.store(head, Ordering::Relaxed);

            if self
                .free_head
                .compare_exchange_weak(head, index, Ordering::Release, Ordering::Relaxed)
                .is_ok()
            {
                self.allocated.fetch_sub(1, Ordering::Relaxed);
                return;
            }

            pause();
        }
    }

    #[inline]
    pub fn allocated(&self) -> usize {
        self.allocated.load(Ordering::Relaxed)
    }

    #[inline]
    pub fn available(&self) -> usize {
        N - self.allocated()
    }

    #[inline]
    pub fn capacity(&self) -> usize {
        N
    }

    pub fn is_full(&self) -> bool {
        self.free_head.load(Ordering::Relaxed) == Self::NULL
    }
}

impl<T, const N: usize> Default for ObjectPool<T, N> {
    fn default() -> Self {
        Self::new()
    }
}

pub struct PoolHandle<'a, T, const N: usize> {
    pool: &'a ObjectPool<T, N>,
    index: usize,
}

impl<T, const N: usize> PoolHandle<'_, T, N> {
    pub fn write(&mut self, value: T) {
        unsafe {
            (*self.pool.slots[self.index].data.get()).write(value);
        }
    }

    pub fn as_ptr(&self) -> NonNull<T> {
        unsafe { NonNull::new_unchecked((*self.pool.slots[self.index].data.get()).as_mut_ptr()) }
    }

    pub fn get(&self) -> &T {
        unsafe { (*self.pool.slots[self.index].data.get()).assume_init_ref() }
    }

    pub fn get_mut(&mut self) -> &mut T {
        unsafe { (*self.pool.slots[self.index].data.get()).assume_init_mut() }
    }

    pub fn take(self) -> T {
        let pool = self.pool;
        let index = self.index;
        let value = unsafe { (*pool.slots[index].data.get()).assume_init_read() };
        std::mem::forget(self);
        pool.release(index);
        value
    }
}

impl<T, const N: usize> Drop for PoolHandle<'_, T, N> {
    fn drop(&mut self) {
        self.pool.release(self.index);
    }
}