jay-compositor 1.14.0

The Jay compositor
use {
    crate::utils::{free_list::FreeList, numcell::NumCell},
    std::{
        cell::{Cell, RefCell},
        collections::BTreeSet,
        rc::{Rc, Weak},
    },
};

#[cfg(test)]
mod tests;

linear_ids!(PageAllocIds, PageAllocId, u64);

pub const PAGE_ALLOC_PAGE_SIZE: u32 = 4096;
const SIZE_CLASSES: usize = PAGE_ALLOC_PAGE_SIZE.trailing_zeros() as usize + 1;

#[derive(Default)]
pub struct PageAllocCtx {
    ids: PageAllocIds,
}

pub struct PageAlloc {
    id: PageAllocId,
    unused_pages: FreeList<u32, 3>,
    size_classes: [SizeClass; SIZE_CLASSES],
    pages: Vec<Page>,
}

#[derive(Default)]
struct Page {
    unused_entries: FreeList<u32, 2>,
    size_class_idx: Cell<u16>,
    used_entries: NumCell<u16>,
}

#[derive(Default)]
struct SizeClass {
    free_pages: RefCell<BTreeSet<SizeClassPageInfo>>,
}

#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
struct SizeClassPageInfo {
    used_entries: u16,
    page_idx: u32,
}

pub struct PageAllocEntry {
    alloc: Weak<PageAlloc>,
    alloc_id: PageAllocId,
    offset: u32,
}

impl PageAllocCtx {
    pub fn create_alloc(&self, pages: usize) -> Rc<PageAlloc> {
        Rc::new(PageAlloc {
            id: self.ids.next(),
            unused_pages: Default::default(),
            size_classes: Default::default(),
            pages: (0..pages).map(|_| Default::default()).collect(),
        })
    }
}

impl PageAlloc {
    #[cfg_attr(not(test), expect(dead_code))]
    pub fn size(&self) -> usize {
        self.pages.len() * PAGE_ALLOC_PAGE_SIZE as usize
    }

    pub fn allocate(self: &Rc<Self>, size: usize) -> Option<Rc<PageAllocEntry>> {
        let size_class_idx = size.next_power_of_two().trailing_zeros() as usize;
        let size_class = &self.size_classes[size_class_idx];
        let free_pages = &mut *size_class.free_pages.borrow_mut();
        let (page_idx, page) = 'page: {
            if let Some(info) = free_pages.pop_last() {
                let page = &self.pages[info.page_idx as usize];
                break 'page (info.page_idx as u32, page);
            }
            let page_idx = self.unused_pages.acquire();
            let Some(p) = self.pages.get(page_idx as usize) else {
                self.unused_pages.release(page_idx);
                return None;
            };
            p.size_class_idx.set(size_class_idx as u16);
            (page_idx, p)
        };
        let page_offset = page.unused_entries.acquire() << size_class_idx;
        let offset = page_idx * PAGE_ALLOC_PAGE_SIZE + page_offset;
        let used_entries = page.used_entries.add_fetch(1);
        if ((used_entries as u32) << size_class_idx) < PAGE_ALLOC_PAGE_SIZE {
            free_pages.insert(SizeClassPageInfo {
                used_entries,
                page_idx,
            });
        }
        let res = PageAllocEntry {
            alloc: Rc::downgrade(self),
            alloc_id: self.id,
            offset,
        };
        Some(Rc::new(res))
    }
}

impl PageAllocEntry {
    pub fn is_in_alloc(&self, alloc: &PageAlloc) -> bool {
        self.alloc_id == alloc.id
    }

    pub fn is_not_in_alloc(&self, alloc: &PageAlloc) -> bool {
        !self.is_in_alloc(alloc)
    }

    pub fn offset(&self) -> u32 {
        self.offset
    }
}

impl Drop for PageAllocEntry {
    fn drop(&mut self) {
        let Some(heap) = self.alloc.upgrade() else {
            return;
        };
        let page_idx = self.offset / PAGE_ALLOC_PAGE_SIZE;
        let page_offset = self.offset % PAGE_ALLOC_PAGE_SIZE;
        let page = &heap.pages[page_idx as usize];
        let size_class_idx = page.size_class_idx.get();
        page.unused_entries.release(page_offset >> size_class_idx);
        let used_entries = page.used_entries.sub_fetch(1);
        let prev_used_entries = used_entries + 1;
        let size_class = &heap.size_classes[size_class_idx as usize];
        let free_pages = &mut *size_class.free_pages.borrow_mut();
        if ((prev_used_entries as u32) << size_class_idx) < PAGE_ALLOC_PAGE_SIZE {
            free_pages.remove(&SizeClassPageInfo {
                used_entries: prev_used_entries,
                page_idx,
            });
        }
        if used_entries == 0 {
            heap.unused_pages.release(page_idx);
        } else {
            free_pages.insert(SizeClassPageInfo {
                used_entries,
                page_idx,
            });
        }
    }
}