Merge pull request #40 from corwinkuiper/object-allocation

Object allocation
This commit is contained in:
Corwin 2021-06-05 13:06:10 +01:00 committed by GitHub
commit 7595c938c9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 206 additions and 64 deletions

View file

@ -15,8 +15,8 @@ enum State {
Flapping,
}
struct Character {
object: ObjectStandard,
struct Character<'a> {
object: ObjectStandard<'a>,
position: Vector2D,
velocity: Vector2D,
}
@ -151,8 +151,8 @@ fn update_chicken_object(chicken: &mut Character, state: State, frame_count: u32
}
}
let x: u8 = (chicken.position.x >> 8).try_into().unwrap();
let y: u8 = (chicken.position.y >> 8).try_into().unwrap();
let x: u16 = (chicken.position.x >> 8).try_into().unwrap();
let y: u16 = (chicken.position.y >> 8).try_into().unwrap();
chicken.object.set_x(x - 4);
chicken.object.set_y(y - 4);

54
agb/src/bitarray.rs Normal file
View file

@ -0,0 +1,54 @@
pub struct Bitarray<const N: usize> {
a: [u32; N],
}
impl<const N: usize> Bitarray<N> {
pub fn new() -> Self {
Bitarray { a: [0; N] }
}
pub fn get(&self, index: usize) -> Option<bool> {
if index < N * 32 {
Some((self.a[index / 32] >> (index % 32) & 1) != 0)
} else {
None
}
}
pub fn set(&mut self, index: usize, value: bool) {
let value = value as u32;
let mask = 1 << (index % 32);
let value_mask = value << (index % 32);
self.a[index / 32] = self.a[index / 32] & !mask | value_mask
}
}
#[test_case]
fn write_and_read(_gba: &mut crate::Gba) {
let mut a: Bitarray<2> = Bitarray::new();
assert_eq!(a.get(55).unwrap(), false, "expect unset values to be false");
a.set(62, true);
assert_eq!(a.get(62).unwrap(), true, "expect set value to be true");
assert_eq!(a.get(120), None, "expect out of range to give None");
}
#[test_case]
fn test_everything(_gba: &mut crate::Gba) {
for i in 0..64 {
let mut a: Bitarray<2> = Bitarray::new();
a.set(i, true);
for j in 0..64 {
let expected = if i == j { true } else { false };
assert_eq!(
a.get(j).unwrap(),
expected,
"set index {} and read {}, expected {} but got {}. u32 of this is {:#b}",
i,
j,
expected,
a.get(j).unwrap(),
a.a[j / 32],
);
}
}
}

View file

@ -1,4 +1,7 @@
use core::cell::RefCell;
use super::DISPLAY_CONTROL;
use crate::bitarray::Bitarray;
use crate::memory_mapped::MemoryMapped1DArray;
const OBJECT_ATTRIBUTE_MEMORY: MemoryMapped1DArray<u16, 512> =
@ -6,29 +9,39 @@ const OBJECT_ATTRIBUTE_MEMORY: MemoryMapped1DArray<u16, 512> =
/// Handles distributing objects and matricies along with operations that effect all objects.
pub struct ObjectControl {
object_count: u8,
affine_count: u8,
objects: RefCell<Bitarray<4>>,
affines: RefCell<Bitarray<1>>,
}
struct ObjectLoan<'a> {
index: u8,
objects: &'a RefCell<Bitarray<4>>,
}
struct AffineLoan<'a> {
index: u8,
affines: &'a RefCell<Bitarray<1>>,
}
/// The standard object, without rotation.
pub struct ObjectStandard {
pub struct ObjectStandard<'a> {
attributes: ObjectAttribute,
id: u8,
loan: ObjectLoan<'a>,
}
/// The affine object, with potential for using a transformation matrix to alter
/// how the sprite is rendered to screen.
pub struct ObjectAffine {
pub struct ObjectAffine<'a> {
attributes: ObjectAttribute,
id: u8,
loan: ObjectLoan<'a>,
aff_id: Option<u8>,
}
/// Refers to an affine matrix in the OAM. Includes both an index and the
/// components of the affine matrix.
pub struct AffineMatrix {
pub struct AffineMatrix<'a> {
pub attributes: AffineMatrixAttributes,
id: u8,
loan: AffineLoan<'a>,
}
/// The components of the affine matrix. The components are fixed point 8:8.
@ -66,19 +79,19 @@ pub enum Size {
S32x64 = 0b10_11,
}
impl ObjectStandard {
impl ObjectStandard<'_> {
/// Commits the object to OAM such that the updated version is displayed on
/// screen. Recommend to do this during VBlank.
pub fn commit(&self) {
unsafe { self.attributes.commit(self.id) }
unsafe { self.attributes.commit(self.loan.index) }
}
/// Sets the x coordinate of the sprite on screen.
pub fn set_x(&mut self, x: u8) {
pub fn set_x(&mut self, x: u16) {
self.attributes.set_x(x)
}
/// Sets the y coordinate of the sprite on screen.
pub fn set_y(&mut self, y: u8) {
pub fn set_y(&mut self, y: u16) {
self.attributes.set_y(y)
}
/// Sets the index of the tile to use as the sprite. Potentially a temporary function.
@ -103,19 +116,19 @@ impl ObjectStandard {
}
}
impl ObjectAffine {
impl<'a> ObjectAffine<'a> {
/// Commits the object to OAM such that the updated version is displayed on
/// screen. Recommend to do this during VBlank.
pub fn commit(&self) {
unsafe { self.attributes.commit(self.id) }
unsafe { self.attributes.commit(self.loan.index) }
}
/// Sets the x coordinate of the sprite on screen.
pub fn set_x(&mut self, x: u8) {
pub fn set_x(&mut self, x: u16) {
self.attributes.set_x(x)
}
/// Sets the y coordinate of the sprite on screen.
pub fn set_y(&mut self, y: u8) {
pub fn set_y(&mut self, y: u16) {
self.attributes.set_y(y)
}
/// Sets the index of the tile to use as the sprite. Potentially a temporary function.
@ -140,9 +153,9 @@ impl ObjectAffine {
}
/// 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.id);
self.aff_id = Some(aff.id);
pub fn set_affine_mat(&mut self, aff: &'a AffineMatrix) {
self.attributes.set_affine(aff.loan.index);
self.aff_id = Some(aff.loan.index);
}
}
@ -151,6 +164,20 @@ fn set_bits(current: u16, value: u16, length: u16, shift: u16) -> u16 {
(current & !(mask << shift)) | ((value & mask) << shift)
}
impl Drop for ObjectLoan<'_> {
fn drop(&mut self) {
let mut objs = self.objects.borrow_mut();
objs.set(self.index as usize, false);
}
}
impl Drop for AffineLoan<'_> {
fn drop(&mut self) {
let mut affs = self.affines.borrow_mut();
affs.set(self.index as usize, false);
}
}
struct ObjectAttribute {
a0: u16,
a1: u16,
@ -169,19 +196,19 @@ impl ObjectAttribute {
}
fn set_size(&mut self, size: Size) {
let lower = size as u16 & 0b11;
let upper = (size as u16 >> 2) & 0b11;
let a1 = size as u16 & 0b11;
let a0 = (size as u16 >> 2) & 0b11;
self.a0 = set_bits(self.a0, lower, 2, 0xE);
self.a1 = set_bits(self.a1, upper, 2, 0xE);
self.a0 = set_bits(self.a0, a0, 2, 0xE);
self.a1 = set_bits(self.a1, a1, 2, 0xE);
}
fn set_x(&mut self, x: u8) {
self.a1 = set_bits(self.a1, x as u16, 8, 0);
fn set_x(&mut self, x: u16) {
self.a1 = set_bits(self.a1, x, 9, 0);
}
fn set_y(&mut self, y: u8) {
self.a0 = set_bits(self.a0, y as u16, 8, 0)
fn set_y(&mut self, y: u16) {
self.a0 = set_bits(self.a0, y, 8, 0)
}
fn set_tile_id(&mut self, id: u16) {
@ -197,11 +224,11 @@ impl ObjectAttribute {
}
}
impl AffineMatrix {
impl AffineMatrix<'_> {
#[allow(clippy::identity_op)]
/// Commits matrix to OAM, will cause any objects using this matrix to be updated.
pub fn commit(&self) {
let id = self.id as usize;
let id = self.loan.index as usize;
OBJECT_ATTRIBUTE_MEMORY.set((id + 0) * 4 + 3, self.attributes.p_a as u16);
OBJECT_ATTRIBUTE_MEMORY.set((id + 1) * 4 + 3, self.attributes.p_b as u16);
OBJECT_ATTRIBUTE_MEMORY.set((id + 2) * 4 + 3, self.attributes.p_c as u16);
@ -227,8 +254,8 @@ impl ObjectControl {
unsafe { o.commit(index) };
}
ObjectControl {
object_count: 0,
affine_count: 0,
objects: RefCell::new(Bitarray::new()),
affines: RefCell::new(Bitarray::new()),
}
}
@ -246,43 +273,59 @@ impl ObjectControl {
DISPLAY_CONTROL.set(disp);
}
/// Get an unused standard object. Currently dropping an unused object will
/// not free this. You should either keep around all objects you need
/// forever or drop and reobtain ObjectControl. Panics if more than 128
/// objects are obtained.
pub fn get_object_standard(&mut self) -> ObjectStandard {
let id = self.object_count;
self.object_count += 1;
assert!(id < 128, "object id must be less than 128");
fn get_unused_object_index(&self) -> u8 {
let mut objects = self.objects.borrow_mut();
for index in 0..128 {
if !objects.get(index).unwrap() {
objects.set(index, true);
return index as u8;
}
}
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 {
let id = self.get_unused_object_index();
ObjectStandard {
attributes: ObjectAttribute::new(),
id,
loan: ObjectLoan {
objects: &self.objects,
index: id,
},
}
}
/// Get an unused affine object. Currently dropping an unused object will
/// not free this. You should either keep around all objects you need
/// forever or drop and reobtain ObjectControl. Panics if more than 128
/// objects are obtained.
pub fn get_object_affine(&mut self) -> ObjectAffine {
let id = self.object_count;
self.object_count += 1;
assert!(id < 128, "object id must be less than 128");
/// Get an unused affine object. Panics if more than 128 objects are
/// obtained.
pub fn get_object_affine(&self) -> ObjectAffine {
let id = self.get_unused_object_index();
ObjectAffine {
attributes: ObjectAttribute::new(),
id,
loan: ObjectLoan {
objects: &self.objects,
index: id,
},
aff_id: None,
}
}
/// Get an unused affine matrix. Currently dropping an unused object will
/// not free this. You should either keep around all affine matricies you
/// need forever or drop and reobtain ObjectControl. Panics if more than 32
/// affine matricies are obtained.
pub fn get_affine(&mut self) -> AffineMatrix {
let id = self.affine_count;
self.affine_count += 1;
assert!(id < 32, "affine id must be less than 32");
/// 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,
@ -290,7 +333,51 @@ impl ObjectControl {
p_c: 0,
p_d: 0,
},
id,
loan: AffineLoan {
affines: &self.affines,
index: id,
},
}
}
}
#[cfg(test)]
mod tests {
#[test_case]
fn get_and_release_object(gba: &mut crate::Gba) {
let gfx = gba.display.video.tiled0();
let objs = gfx.object;
let _o1 = {
let o0 = objs.get_object_standard();
let o1 = objs.get_object_standard();
assert_eq!(o0.loan.index, 0);
assert_eq!(o1.loan.index, 1);
o1
};
let o0 = objs.get_object_standard();
assert_eq!(o0.loan.index, 0);
let o2 = objs.get_object_affine();
assert_eq!(o2.loan.index, 2);
}
#[test_case]
fn get_and_release_affine(gba: &mut crate::Gba) {
let gfx = gba.display.video.tiled0();
let objs = gfx.object;
let _a1 = {
let a0 = objs.get_affine();
let a1 = objs.get_affine();
assert_eq!(a0.loan.index, 0);
assert_eq!(a1.loan.index, 1);
a1
};
let a0 = objs.get_affine();
assert_eq!(a0.loan.index, 0);
let a2 = objs.get_affine();
assert_eq!(a2.loan.index, 2);
}
}

View file

@ -253,7 +253,7 @@ pub struct Tiled0 {
impl Tiled0 {
pub(crate) unsafe fn new() -> Self {
set_graphics_settings(GraphicsSettings::empty());
set_graphics_settings(GraphicsSettings::empty() | GraphicsSettings::SPRITE1_D);
set_graphics_mode(DisplayMode::Tiled0);
Tiled0 {
used_blocks: 0,

View file

@ -20,6 +20,7 @@ pub mod input;
/// Implements sound output.
pub mod sound;
mod bitarray;
mod interrupt;
mod memory_mapped;
/// Implements logging to the mgba emulator.

View file

@ -18,7 +18,7 @@ enum Status {
fn test_file(file_to_run: &str) -> Status {
let mut finished = Status::Running;
let debug_reader_mutex = Regex::new(r"^\[(.*)\] GBA Debug: (.*)$").unwrap();
let debug_reader_mutex = Regex::new(r"(?s)^\[(.*)\] GBA Debug: (.*)$").unwrap();
let mut mgba = runner::MGBA::new(file_to_run);
let video_buffer = mgba.get_video_buffer();