diff --git a/agb/src/arena.rs b/agb/src/arena.rs new file mode 100644 index 00000000..f8a685ed --- /dev/null +++ b/agb/src/arena.rs @@ -0,0 +1,140 @@ +use core::{cell::RefCell, num::NonZeroU8}; + +type Index = u8; + +pub struct Loan<'a, const S: usize> { + pub my_index: Index, + arena: &'a RefCell>, +} + +/// Next should be an option, however this raises this struct to 3 bytes where +/// it is now only 2 bytes This saves a byte per entry and therefore it is +/// probably worth it. +#[derive(Clone, Copy, Debug)] +enum Element { + Contains(NonZeroU8), + Next(Index), +} + +#[derive(Debug)] +struct ArenaInner { + arena: [Element; S], + first: Index, +} + +pub struct Arena { + arena_inner: RefCell>, +} + +impl Arena { + pub fn new() -> Arena { + // we use the special value u8::MAX as a None + assert!(S < u8::MAX as usize - 1); + + let mut arena: [Element; S] = [Element::Next(u8::MAX); S]; + arena + .iter_mut() + .enumerate() + .for_each(|(index, e)| *e = Element::Next(index as Index + 1)); + arena.last_mut().map(|a| *a = Element::Next(u8::MAX)); + Arena { + arena_inner: RefCell::new(ArenaInner { arena, first: 0 }), + } + } + + pub fn get_next_free(&self) -> Option> { + let mut arena = self.arena_inner.borrow_mut(); + let i = arena.first; + if i == u8::MAX { + return None; + } + if let Element::Next(n) = arena.arena[i as usize] { + arena.first = n; + } else { + unreachable!("invalid state, next points to already occupied state"); + } + arena.arena[i as usize] = Element::Contains(NonZeroU8::new(1).unwrap()); + Some(Loan { + my_index: i, + arena: &self.arena_inner, + }) + } +} + +impl Drop for Loan<'_, S> { + fn drop(&mut self) { + let mut arena = self.arena.borrow_mut(); + let me = &mut arena.arena[self.my_index as usize]; + match me { + Element::Contains(n) => { + let mut a = n.get(); + a -= 1; + if a == 0 { + arena.arena[self.my_index as usize] = Element::Next(arena.first); + arena.first = self.my_index; + } else { + *n = NonZeroU8::new(a).unwrap(); + } + } + _ => unreachable!("if a loan exists the correspoinding arena entry should be filled"), + } + } +} + +impl Clone for Loan<'_, S> { + fn clone(&self) -> Self { + match &mut self.arena.borrow_mut().arena[self.my_index as usize] { + Element::Contains(n) => { + let a = n.get(); + *n = NonZeroU8::new(a + 1).unwrap(); + } + _ => unreachable!("if a loan exists the correspoinding arena entry should be filled"), + } + Loan { + my_index: self.my_index, + arena: self.arena, + } + } +} + +#[cfg(test)] +mod tests { + + use super::*; + use alloc; + + #[test_case] + fn size_of_element(_gba: &mut crate::Gba) { + let s = core::mem::size_of::(); + assert_eq!(s, 2, "elements should be of a minimum size"); + } + + #[test_case] + fn get_everything(_gba: &mut crate::Gba) { + let s: Arena<4> = Arena::new(); + { + let mut c = alloc::vec::Vec::new(); + c.push(s.get_next_free()); + c.push(s.get_next_free()); + c.iter().for_each(|a| assert!(a.is_some())); + } + { + let mut c = alloc::vec::Vec::new(); + c.push(s.get_next_free()); + c.push(s.get_next_free()); + c.push(s.get_next_free()); + c.push(s.get_next_free()); + c.iter().for_each(|a| assert!(a.is_some())); + assert!(s.get_next_free().is_none()); + } + { + let mut c = alloc::vec::Vec::new(); + c.push(s.get_next_free()); + c.push(s.get_next_free()); + c.push(s.get_next_free()); + c.push(s.get_next_free()); + c.iter().for_each(|a| assert!(a.is_some())); + assert!(s.get_next_free().is_none()); + } + } +} diff --git a/agb/src/display/object.rs b/agb/src/display/object.rs index 5d1ecd77..e7ce3707 100644 --- a/agb/src/display/object.rs +++ b/agb/src/display/object.rs @@ -5,6 +5,9 @@ use crate::bitarray::Bitarray; use crate::memory_mapped::MemoryMapped1DArray; use crate::number::Vector2D; +type AffineLoan<'a> = crate::arena::Loan<'a, 32>; +type AffineArena = crate::arena::Arena<32>; + const OBJECT_ATTRIBUTE_MEMORY: MemoryMapped1DArray = unsafe { MemoryMapped1DArray::new(0x0700_0000) }; const PALETTE_SPRITE: MemoryMapped1DArray = @@ -15,7 +18,7 @@ const TILE_SPRITE: MemoryMapped1DArray = /// Handles distributing objects and matricies along with operations that effect all objects. pub struct ObjectControl { objects: RefCell>, - affines: RefCell>, + affines: AffineArena, } struct ObjectLoan<'a> { @@ -23,11 +26,6 @@ struct ObjectLoan<'a> { objects: &'a RefCell>, } -struct AffineLoan<'a> { - index: u8, - affines: &'a RefCell>, -} - /// The standard object, without rotation. pub struct ObjectStandard<'a> { attributes: ObjectAttribute, @@ -39,7 +37,7 @@ pub struct ObjectStandard<'a> { pub struct ObjectAffine<'a> { attributes: ObjectAttribute, loan: ObjectLoan<'a>, - aff_id: Option, + aff_loan: Option>, } /// Refers to an affine matrix in the OAM. Includes both an index and the @@ -161,7 +159,7 @@ impl<'a> ObjectAffine<'a> { /// Show the object on screen. Panics if affine matrix has not been set. pub fn show(&mut self) { - if self.aff_id.is_none() { + if self.aff_loan.is_none() { panic!("affine matrix should be set") } self.attributes.set_mode(Mode::Affine) @@ -170,11 +168,12 @@ impl<'a> ObjectAffine<'a> { pub fn hide(&mut self) { self.attributes.set_mode(Mode::Hidden) } + /// Sets the affine matrix to use. Changing the affine matrix will change /// how the sprite is rendered. - pub fn set_affine_mat(&mut self, aff: &AffineMatrix) { - self.attributes.set_affine(aff.loan.index); - self.aff_id = Some(aff.loan.index); + pub fn set_affine_mat(&mut self, aff: &AffineMatrix<'a>) { + self.attributes.set_affine(aff.loan.my_index); + self.aff_loan = Some(aff.loan.clone()); } /// Sets the x and y position of the object, performing casts as nessesary @@ -207,22 +206,6 @@ impl Drop for ObjectLoan<'_> { } } -impl Drop for AffineLoan<'_> { - fn drop(&mut self) { - let attributes = AffineMatrixAttributes { - p_a: 0, - p_b: 0, - p_c: 0, - p_d: 0, - }; - unsafe { - attributes.commit(self.index); - } - let mut affs = self.affines.borrow_mut(); - affs.set(self.index as usize, false); - } -} - struct ObjectAttribute { a0: u16, a1: u16, @@ -276,7 +259,7 @@ impl ObjectAttribute { impl AffineMatrix<'_> { /// Commits matrix to OAM, will cause any objects using this matrix to be updated. pub fn commit(&self) { - unsafe { self.attributes.commit(self.loan.index) }; + unsafe { self.attributes.commit(self.loan.my_index) }; } } @@ -311,7 +294,7 @@ impl ObjectControl { } ObjectControl { objects: RefCell::new(Bitarray::new()), - affines: RefCell::new(Bitarray::new()), + affines: AffineArena::new(), } } @@ -373,17 +356,6 @@ impl ObjectControl { panic!("object id must be less than 128"); } - fn get_unused_affine_index(&self) -> u8 { - let mut affines = self.affines.borrow_mut(); - for index in 0..32 { - if !affines.get(index).unwrap() { - affines.set(index, true); - return index as u8; - } - } - panic!("affine id must be less than 32"); - } - /// Get an unused standard object. Panics if more than 128 objects are /// obtained. pub fn get_object_standard(&self) -> ObjectStandard { @@ -407,14 +379,13 @@ impl ObjectControl { objects: &self.objects, index: id, }, - aff_id: None, + aff_loan: None, } } /// Get an unused affine matrix. Panics if more than 32 affine matricies are /// obtained. pub fn get_affine(&self) -> AffineMatrix { - let id = self.get_unused_affine_index(); AffineMatrix { attributes: AffineMatrixAttributes { p_a: 0, @@ -422,10 +393,10 @@ impl ObjectControl { p_c: 0, p_d: 0, }, - loan: AffineLoan { - affines: &self.affines, - index: id, - }, + loan: self + .affines + .get_next_free() + .expect("there are no affines avaliable"), } } } @@ -457,14 +428,14 @@ mod tests { let _a1 = { let a0 = objs.get_affine(); let a1 = objs.get_affine(); - assert_eq!(a0.loan.index, 0); - assert_eq!(a1.loan.index, 1); + assert_eq!(a0.loan.my_index, 0); + assert_eq!(a1.loan.my_index, 1); a1 }; let a0 = objs.get_affine(); - assert_eq!(a0.loan.index, 0); + assert_eq!(a0.loan.my_index, 0); let a2 = objs.get_affine(); - assert_eq!(a2.loan.index, 2); + assert_eq!(a2.loan.my_index, 2); } } diff --git a/agb/src/lib.rs b/agb/src/lib.rs index a16ae0fd..fe09a26c 100644 --- a/agb/src/lib.rs +++ b/agb/src/lib.rs @@ -13,31 +13,29 @@ //! internal workings of the Game Boy Advance whilst still being high //! performance and memory efficient. +pub use agb_image_converter::include_gfx; +pub use agb_macros::entry; +pub use agb_sound_converter::include_wav; + #[cfg(feature = "alloc")] extern crate alloc; #[cfg(feature = "alloc")] mod agb_alloc; +mod arena; +mod bitarray; /// Implements everything relating to things that are displayed on screen. pub mod display; /// Button inputs to the system. pub mod input; -/// Implements sound output. -pub mod sound; - -pub use agb_image_converter::include_gfx; -pub use agb_sound_converter::include_wav; - -pub use agb_macros::entry; - -mod bitarray; pub mod interrupt; mod memory_mapped; /// Implements logging to the mgba emulator. pub mod mgba; pub mod number; mod single; - +/// Implements sound output. +pub mod sound; /// System BIOS calls / syscalls. pub mod syscall;