use core::ptr::NonNull;
use alloc::rc::{Rc, Weak};
use crate::{
agb_alloc::{block_allocator::BlockAllocator, bump_allocator::StartEnd},
display::palette16::Palette16,
hash_map::HashMap,
};
use super::{
sprite::{Size, Sprite},
BYTES_PER_TILE_4BPP,
};
const PALETTE_SPRITE: usize = 0x0500_0200;
const TILE_SPRITE: usize = 0x06010000;
static SPRITE_ALLOCATOR: BlockAllocator = unsafe {
BlockAllocator::new(StartEnd {
start: || TILE_SPRITE,
end: || TILE_SPRITE + 1024 * 8 * 4,
})
};
static PALETTE_ALLOCATOR: BlockAllocator = unsafe {
BlockAllocator::new(StartEnd {
start: || PALETTE_SPRITE,
end: || PALETTE_SPRITE + 0x200,
})
};
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
struct SpriteId(usize);
impl SpriteId {
fn from_static_sprite(sprite: &'static Sprite) -> SpriteId {
SpriteId(sprite as *const _ as usize)
}
}
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
struct PaletteId(usize);
impl PaletteId {
fn from_static_palette(palette: &'static Palette16) -> PaletteId {
PaletteId(palette as *const _ as usize)
}
}
pub struct SpriteLoader {
static_palette_map: HashMap<PaletteId, Weak<PaletteVramData>>,
static_sprite_map: HashMap<SpriteId, Weak<SpriteVramData>>,
}
#[derive(Clone, Copy, Debug)]
struct Location(usize);
impl Location {
fn from_sprite_ptr(d: NonNull<u8>) -> Self {
Self(((d.as_ptr() as usize) - TILE_SPRITE) / BYTES_PER_TILE_4BPP)
}
fn from_palette_ptr(d: NonNull<u8>) -> Self {
Self((d.as_ptr() as usize - PALETTE_SPRITE) / Palette16::layout().size())
}
fn as_palette_ptr(self) -> *mut u8 {
(self.0 * Palette16::layout().size() + PALETTE_SPRITE) as *mut u8
}
fn as_sprite_ptr(self) -> *mut u8 {
(self.0 * BYTES_PER_TILE_4BPP + TILE_SPRITE) as *mut u8
}
}
#[derive(Debug)]
struct PaletteVramData {
location: Location,
}
impl Drop for PaletteVramData {
fn drop(&mut self) {
unsafe { PALETTE_ALLOCATOR.dealloc(self.location.as_palette_ptr(), Palette16::layout()) }
}
}
#[derive(Debug, Clone)]
pub struct PaletteVram {
data: Rc<PaletteVramData>,
}
impl PaletteVram {
pub fn new(palette: &Palette16) -> Result<PaletteVram, LoaderError> {
let allocated = unsafe { PALETTE_ALLOCATOR.alloc(Palette16::layout()) }
.ok_or(LoaderError::PaletteFull)?;
unsafe {
allocated
.as_ptr()
.cast::<u16>()
.copy_from_nonoverlapping(palette.colours.as_ptr(), palette.colours.len());
}
Ok(PaletteVram {
data: Rc::new(PaletteVramData {
location: Location::from_palette_ptr(allocated),
}),
})
}
}
#[derive(Debug)]
struct SpriteVramData {
location: Location,
size: Size,
palette: PaletteVram,
}
impl Drop for SpriteVramData {
fn drop(&mut self) {
unsafe { SPRITE_ALLOCATOR.dealloc(self.location.as_sprite_ptr(), self.size.layout()) }
}
}
#[non_exhaustive]
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum LoaderError {
SpriteFull,
PaletteFull,
}
#[derive(Clone, Debug)]
pub struct SpriteVram {
data: Rc<SpriteVramData>,
}
impl SpriteVram {
fn new(data: &[u8], size: Size, palette: PaletteVram) -> Result<SpriteVram, LoaderError> {
let allocated =
unsafe { SPRITE_ALLOCATOR.alloc(size.layout()) }.ok_or(LoaderError::SpriteFull)?;
unsafe {
allocated
.as_ptr()
.copy_from_nonoverlapping(data.as_ptr(), data.len());
}
Ok(SpriteVram {
data: Rc::new(SpriteVramData {
location: Location::from_sprite_ptr(allocated),
size,
palette,
}),
})
}
pub(crate) fn location(&self) -> u16 {
self.data.location.0 as u16
}
pub(crate) fn size(&self) -> Size {
self.data.size
}
pub(crate) fn palette_location(&self) -> u16 {
self.data.palette.data.location.0 as u16
}
}
impl SpriteLoader {
fn create_sprite_no_insert(
palette_map: &mut HashMap<PaletteId, Weak<PaletteVramData>>,
sprite: &'static Sprite,
) -> Result<(Weak<SpriteVramData>, SpriteVram), LoaderError> {
let palette = Self::try_get_vram_palette_asoc(palette_map, sprite.palette)?;
let sprite = SpriteVram::new(sprite.data, sprite.size, palette)?;
Ok((Rc::downgrade(&sprite.data), sprite))
}
fn try_get_vram_palette_asoc(
palette_map: &mut HashMap<PaletteId, Weak<PaletteVramData>>,
palette: &'static Palette16,
) -> Result<PaletteVram, LoaderError> {
let id = PaletteId::from_static_palette(palette);
Ok(match palette_map.entry(id) {
crate::hash_map::Entry::Occupied(mut entry) => match entry.get().upgrade() {
Some(data) => PaletteVram { data },
None => {
let pv = PaletteVram::new(palette)?;
entry.insert(Rc::downgrade(&pv.data));
pv
}
},
crate::hash_map::Entry::Vacant(entry) => {
let pv = PaletteVram::new(palette)?;
entry.insert(Rc::downgrade(&pv.data));
pv
}
})
}
pub fn try_get_vram_sprite(
&mut self,
sprite: &'static Sprite,
) -> Result<SpriteVram, LoaderError> {
let id = SpriteId::from_static_sprite(sprite);
Ok(match self.static_sprite_map.entry(id) {
crate::hash_map::Entry::Occupied(mut entry) => match entry.get().upgrade() {
Some(data) => SpriteVram { data },
None => {
let (weak, vram) =
Self::create_sprite_no_insert(&mut self.static_palette_map, sprite)?;
entry.insert(weak);
vram
}
},
crate::hash_map::Entry::Vacant(entry) => {
let (weak, vram) =
Self::create_sprite_no_insert(&mut self.static_palette_map, sprite)?;
entry.insert(weak);
vram
}
})
}
pub fn try_get_vram_palette(
&mut self,
palette: &'static Palette16,
) -> Result<PaletteVram, LoaderError> {
Self::try_get_vram_palette_asoc(&mut self.static_palette_map, palette)
}
pub fn get_vram_sprite(&mut self, sprite: &'static Sprite) -> SpriteVram {
self.try_get_vram_sprite(sprite)
.expect("cannot create sprite")
}
pub fn get_vram_palette(&mut self, palette: &'static Palette16) -> PaletteVram {
self.try_get_vram_palette(palette)
.expect("cannot create sprite")
}
pub(crate) fn new() -> Self {
Self {
static_palette_map: HashMap::new(),
static_sprite_map: HashMap::new(),
}
}
pub fn garbage_collect(&mut self) {
self.static_sprite_map
.retain(|_, v| Weak::strong_count(v) != 0);
self.static_palette_map
.retain(|_, v| Weak::strong_count(v) != 0);
}
}
impl Default for SpriteLoader {
fn default() -> Self {
Self::new()
}
}
pub struct DynamicSprite<'a> {
data: &'a [u8],
size: Size,
}
impl DynamicSprite<'_> {
#[must_use]
pub fn new(data: &[u8], size: Size) -> DynamicSprite {
let ptr = &data[0] as *const _ as usize;
if ptr % 2 != 0 {
panic!("data is not aligned to a 2 byte boundary");
}
if data.len() != size.number_of_tiles() * BYTES_PER_TILE_4BPP {
panic!(
"data is not of expected length, got {} expected {}",
data.len(),
size.number_of_tiles() * BYTES_PER_TILE_4BPP
);
}
DynamicSprite { data, size }
}
pub fn try_vram(&self, palette: PaletteVram) -> Result<SpriteVram, LoaderError> {
SpriteVram::new(self.data, self.size, palette)
}
#[must_use]
pub fn to_vram(&self, palette: PaletteVram) -> SpriteVram {
self.try_vram(palette).expect("cannot create sprite")
}
}