use core::{alloc::Allocator, cell::Cell, hint::assert_unchecked, ptr::NonNull};
use crate::{
ExternalAllocator,
agb_alloc::single_allocator::create_allocator_arena,
display::{
object::{PaletteMulti, sprites::sprite::Palette},
palette16::Palette16,
},
refcount::{RefCount, RefCountInner},
};
use super::{LoaderError, SPRITE_LOADER};
pub const PALETTE_SPRITE: usize = 0x0500_0200;
create_allocator_arena!(
PaletteArena,
ExternalAllocator,
RefCountInner<PaletteAllocation>
);
struct PaletteAllocator {
allocation: Cell<u16>,
}
#[derive(Debug)]
struct MultiPaletteAllocation(u16);
#[derive(Debug)]
struct SinglePaletteAllocation(u8);
impl Drop for SinglePaletteAllocation {
fn drop(&mut self) {
PALETTE_ALLOCATOR.deallocate_single(self);
}
}
impl Drop for MultiPaletteAllocation {
fn drop(&mut self) {
PALETTE_ALLOCATOR.deallocate_multi(self);
}
}
const PALETTE_VRAM: *mut [Palette16; 16] = PALETTE_SPRITE as *mut _;
impl PaletteAllocator {
const fn new() -> Self {
Self {
allocation: Cell::new(0),
}
}
fn allocate_multiple(&self, palette: &PaletteMulti) -> Option<MultiPaletteAllocation> {
unsafe {
assert_unchecked(palette.palettes().len() <= 16);
assert_unchecked(!palette.palettes().is_empty());
assert_unchecked(16 - palette.palettes().len() >= palette.first_index() as usize);
}
let claim = (1u32 << palette.palettes().len()) - 1;
let claim = claim << palette.first_index();
unsafe {
assert_unchecked(claim <= u16::MAX as u32);
}
let claim = claim as u16;
let currently_allocated = self.allocation.get();
if currently_allocated & claim != 0 {
return None;
}
self.allocation.set(currently_allocated | claim);
unsafe {
let p = (&mut (*PALETTE_VRAM)[palette.first_index() as usize]) as *mut Palette16;
p.copy_from_nonoverlapping(palette.palettes().as_ptr(), palette.palettes().len());
}
Some(MultiPaletteAllocation(claim))
}
fn allocate_single(&self, palette: &Palette16) -> Option<SinglePaletteAllocation> {
let currently_allocated = self.allocation.get();
for idx in 0..16 {
let claim = 1u16 << idx;
if currently_allocated & claim == 0 {
self.allocation.set(currently_allocated | claim);
unsafe {
let palette_to_write_to = &mut (*PALETTE_VRAM)[idx] as *mut Palette16;
palette_to_write_to.copy_from_nonoverlapping(palette, 1);
}
return Some(SinglePaletteAllocation(idx as u8));
}
}
None
}
fn deallocate_single(&self, claim: &SinglePaletteAllocation) {
assert!(claim.0 < 16);
let allocation = self.allocation.get();
self.allocation.set(allocation & !(1 << claim.0));
}
fn deallocate_multi(&self, claim: &MultiPaletteAllocation) {
let allocation = self.allocation.get();
self.allocation.set(allocation & !(claim.0));
}
}
unsafe impl Sync for PaletteAllocator {}
static PALETTE_ALLOCATOR: PaletteAllocator = PaletteAllocator::new();
#[derive(Debug)]
#[repr(align(4))]
#[expect(dead_code, reason = "the drop implementation is used and is important")]
enum PaletteAllocation {
Single(SinglePaletteAllocation),
Multi(MultiPaletteAllocation),
}
type RefCountedAllocation = RefCount<PaletteAllocation, PaletteArena>;
#[derive(Clone, Debug)]
pub struct PaletteVramSingle(RefCountedAllocation);
impl PaletteVramSingle {
#[must_use]
pub fn palette(self) -> PaletteVram {
PaletteVram(self.0)
}
pub fn try_allocate_new(palette: &Palette16) -> Result<Self, LoaderError> {
let allocation = PALETTE_ALLOCATOR
.allocate_single(palette)
.ok_or(LoaderError::PaletteFull)?;
let allocation = PaletteAllocation::Single(allocation);
Ok(Self(RefCount::new_in(allocation, PaletteArena)))
}
#[must_use]
pub fn new(palette: &'static Palette16) -> Self {
unsafe {
SPRITE_LOADER
.palette(Palette::Single(palette))
.expect("palette unallocatable")
.single()
.unwrap_unchecked()
}
}
}
#[derive(Clone, Debug)]
pub struct PaletteVramMulti(RefCountedAllocation);
impl PaletteVramMulti {
#[must_use]
pub fn palette(self) -> PaletteVram {
PaletteVram(self.0)
}
pub fn try_allocate_new(palette: &PaletteMulti) -> Result<Self, LoaderError> {
let allocation = PALETTE_ALLOCATOR
.allocate_multiple(palette)
.ok_or(LoaderError::PaletteFull)?;
let allocation = PaletteAllocation::Multi(allocation);
Ok(Self(RefCount::new_in(allocation, PaletteArena)))
}
#[must_use]
pub fn new(palette: &'static PaletteMulti) -> Self {
unsafe {
SPRITE_LOADER
.palette(Palette::Multi(palette))
.expect("palette unallocatable")
.multi()
.unwrap_unchecked()
}
}
}
#[derive(Clone, Debug)]
pub struct PaletteVram(RefCountedAllocation);
impl PaletteVram {
pub fn new_single(palette: &Palette16) -> Result<Self, LoaderError> {
PaletteVramSingle::try_allocate_new(palette).map(PaletteVramSingle::palette)
}
pub fn new_multi(palette: &PaletteMulti) -> Result<Self, LoaderError> {
PaletteVramMulti::try_allocate_new(palette).map(PaletteVramMulti::palette)
}
pub fn single(self) -> Result<PaletteVramSingle, Self> {
match &*self.0 {
PaletteAllocation::Single(_) => Ok(PaletteVramSingle(self.0)),
PaletteAllocation::Multi(_) => Err(self),
}
}
pub fn multi(self) -> Result<PaletteVramMulti, Self> {
match &*self.0 {
PaletteAllocation::Single(_) => Err(self),
PaletteAllocation::Multi(_) => Ok(PaletteVramMulti(self.0)),
}
}
#[must_use]
pub(crate) fn strong_count(&self) -> usize {
RefCount::count(&self.0)
}
#[must_use]
pub(crate) fn is_multi(&self) -> bool {
match &*self.0 {
PaletteAllocation::Single(_) => false,
PaletteAllocation::Multi(_) => true,
}
}
#[must_use]
pub(crate) fn single_palette_index(&self) -> Option<u8> {
match &*self.0 {
PaletteAllocation::Single(p) => Some(p.0),
PaletteAllocation::Multi(_) => None,
}
}
}