diff --git a/.vscode/agb.code-workspace b/.vscode/agb.code-workspace index 7c029667..b0575aac 100644 --- a/.vscode/agb.code-workspace +++ b/.vscode/agb.code-workspace @@ -39,6 +39,9 @@ { "path": "../tools" }, + { + "path": "../examples/combo" + }, { "path": "../agb-hashmap" } diff --git a/CHANGELOG.md b/CHANGELOG.md index e8e37174..f65ee3aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,10 +8,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - You can now import aseprite files directly (in addition to the already supported png and bmp files) when importing background tiles. +- New additional unmanaged object API for interacting with a more straightforward manner with the underlying hardware. ### Changed - Importing background tiles has been improved. You no longer need to use `include_gfx!` with the toml file. Instead, use `include_background_gfx`. See the documentation for usage. - The hashmap implementation is now it its own crate, `agb-hashmap`. There is no change in API, but you can now use this for interop between non-agb code and agb code +- Moved the existing object API to be the OamManaged API. The old names persist with deprecated notices on them. ## [0.14.0] - 2023/04/11 diff --git a/agb-gbafix/Cargo.lock b/agb-gbafix/Cargo.lock index a600b538..ad76e499 100644 --- a/agb-gbafix/Cargo.lock +++ b/agb-gbafix/Cargo.lock @@ -88,18 +88,18 @@ checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" [[package]] name = "clap" -version = "4.2.2" +version = "4.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b802d85aaf3a1cdb02b224ba472ebdea62014fccfcb269b95a4d76443b5ee5a" +checksum = "956ac1f6381d8d82ab4684768f89c0ea3afe66925ceadb4eeb3fc452ffc55d62" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.2.2" +version = "4.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14a1a858f532119338887a4b8e1af9c60de8249cd7bafd68036a489e261e37b6" +checksum = "84080e799e54cff944f4b4a4b0e71630b0e0443b25b985175c7dddc1a859b749" dependencies = [ "anstream", "anstyle", @@ -187,21 +187,21 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.141" +version = "0.2.142" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5" +checksum = "6a987beff54b60ffa6d51982e1aa1146bc42f19bd26be28b0586f252fccf5317" [[package]] name = "linux-raw-sys" -version = "0.3.1" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d59d8c75012853d2e872fb56bc8a2e53718e2cafe1a4c823143141c6d90c322f" +checksum = "9b085a4f2cde5781fc4b1717f2e86c62f5cda49de7ba99a7c2eae02b61c9064c" [[package]] name = "rustix" -version = "0.37.11" +version = "0.37.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85597d61f83914ddeba6a47b3b8ffe7365107221c2e557ed94426489fefb5f77" +checksum = "f79bef90eb6d984c72722595b5b1348ab39275a5e5123faca6863bf07d75a4e0" dependencies = [ "bitflags", "errno", diff --git a/agb/examples/chicken.rs b/agb/examples/chicken.rs index 3615ee79..9d56ce68 100644 --- a/agb/examples/chicken.rs +++ b/agb/examples/chicken.rs @@ -4,7 +4,7 @@ use agb::{ display::tiled::{TileFormat, TileSet, TileSetting, TiledMap}, display::{ - object::{Object, ObjectController, Size, Sprite}, + object::{OamManaged, Object, Size, Sprite}, palette16::Palette16, tiled::RegularBackgroundSize, HEIGHT, WIDTH, @@ -74,7 +74,7 @@ fn main(mut gba: agb::Gba) -> ! { background.show(); background.commit(&mut vram); - let object = gba.display.object.get(); + let object = gba.display.object.get_managed(); let sprite = object.sprite(&CHICKEN_SPRITES[0]); let mut chicken = Character { @@ -143,9 +143,9 @@ fn main(mut gba: agb::Gba) -> ! { } } -fn update_chicken_object<'a>( - chicken: &'_ mut Character<'a>, - object: &'a ObjectController, +fn update_chicken_object( + chicken: &'_ mut Character<'_>, + gfx: &OamManaged, state: State, frame_count: u32, ) { @@ -157,20 +157,18 @@ fn update_chicken_object<'a>( match state { State::Ground => { if chicken.velocity.x.abs() > 1 << 4 { - chicken.object.set_sprite( - object.sprite(&CHICKEN_SPRITES[frame_ranger(frame_count, 1, 3, 10)]), - ); - } else { chicken .object - .set_sprite(object.sprite(&CHICKEN_SPRITES[0])); + .set_sprite(gfx.sprite(&CHICKEN_SPRITES[frame_ranger(frame_count, 1, 3, 10)])); + } else { + chicken.object.set_sprite(gfx.sprite(&CHICKEN_SPRITES[0])); } } State::Upwards => {} State::Flapping => { chicken .object - .set_sprite(object.sprite(&CHICKEN_SPRITES[frame_ranger(frame_count, 4, 5, 5)])); + .set_sprite(gfx.sprite(&CHICKEN_SPRITES[frame_ranger(frame_count, 4, 5, 5)])); } } diff --git a/agb/examples/sprites.rs b/agb/examples/sprites.rs index c5ea5ba8..161e4ed8 100644 --- a/agb/examples/sprites.rs +++ b/agb/examples/sprites.rs @@ -3,7 +3,12 @@ extern crate alloc; -use agb::display::object::{Graphics, ObjectController, Sprite, TagMap}; +use agb::display::{ + affine::AffineMatrix, + object::{self, Graphics, OamManaged, Sprite, TagMap}, +}; +use agb::fixnum::num; +use agb_fixnum::Num; use alloc::vec::Vec; const GRAPHICS: &Graphics = agb::include_aseprite!( @@ -15,14 +20,20 @@ const GRAPHICS: &Graphics = agb::include_aseprite!( const SPRITES: &[Sprite] = GRAPHICS.sprites(); const TAG_MAP: &TagMap = GRAPHICS.tags(); -fn all_sprites(gfx: &ObjectController) { +fn all_sprites(gfx: &OamManaged, rotation_speed: Num) { let mut input = agb::input::ButtonController::new(); let mut objs = Vec::new(); + let mut rotation: Num = num!(0.); + + let rotation_matrix = AffineMatrix::from_rotation(rotation); + let matrix = object::AffineMatrixInstance::new(rotation_matrix.to_object_wrapping()); + for y in 0..9 { for x in 0..14 { - let mut obj = gfx.object(gfx.sprite(&SPRITES[0])); - obj.show(); + let mut obj = gfx.object_sprite(&SPRITES[0]); + obj.set_affine_matrix(matrix.clone()); + obj.show_affine(object::AffineMode::Affine); obj.set_position((x * 16 + 8, y * 16 + 8).into()); objs.push(obj); } @@ -41,6 +52,15 @@ fn all_sprites(gfx: &ObjectController) { break; } + rotation += rotation_speed; + let rotation_matrix = AffineMatrix::from_rotation(rotation); + + let matrix = object::AffineMatrixInstance::new(rotation_matrix.to_object_wrapping()); + + for obj in objs.iter_mut() { + obj.set_affine_matrix(matrix.clone()); + } + count += 1; if count % 5 == 0 { @@ -50,12 +70,12 @@ fn all_sprites(gfx: &ObjectController) { let this_image = (image + i) % SPRITES.len(); obj.set_sprite(gfx.sprite(&SPRITES[this_image])); } - gfx.commit(); } + gfx.commit(); } } -fn all_tags(gfx: &ObjectController) { +fn all_tags(gfx: &OamManaged) { let mut input = agb::input::ButtonController::new(); let mut objs = Vec::new(); @@ -65,7 +85,7 @@ fn all_tags(gfx: &ObjectController) { let sprite = v.sprite(0); let (size_x, size_y) = sprite.size().to_width_height(); let (size_x, size_y) = (size_x as i32, size_y as i32); - let mut obj = gfx.object(gfx.sprite(sprite)); + let mut obj = gfx.object_sprite(sprite); obj.show(); obj.set_position((x * 32 + 16 - size_x / 2, y * 32 + 16 - size_y / 2).into()); objs.push((obj, v)); @@ -99,12 +119,11 @@ fn all_tags(gfx: &ObjectController) { #[agb::entry] fn main(mut gba: agb::Gba) -> ! { - let gfx = gba.display.object.get(); + let gfx = gba.display.object.get_managed(); loop { all_tags(&gfx); - gfx.commit(); - all_sprites(&gfx); - gfx.commit(); + all_sprites(&gfx, num!(0.)); + all_sprites(&gfx, num!(0.01)); } } diff --git a/agb/src/arena.rs b/agb/src/arena.rs new file mode 100644 index 00000000..976f44bb --- /dev/null +++ b/agb/src/arena.rs @@ -0,0 +1,77 @@ +use core::{alloc::Allocator, mem::ManuallyDrop}; + +use alloc::{alloc::Global, vec::Vec}; + +union ArenaItem { + free: Option, + occupied: ManuallyDrop, +} + +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] +pub struct ArenaKey(usize); + +pub struct Arena { + tip: Option, + data: Vec, A>, + inserted: usize, +} + +impl Arena { + pub const fn new() -> Self { + Self::new_in(Global) + } +} + +impl Arena { + pub const fn new_in(alloc: A) -> Self { + Self { + tip: None, + data: Vec::new_in(alloc), + inserted: 0, + } + } + + pub unsafe fn insert(&mut self, value: T) -> ArenaKey { + self.inserted += 1; + match self.tip { + Some(tip) => { + self.tip = self.data[tip.0].free; + self.data[tip.0].occupied = ManuallyDrop::new(value); + tip + } + None => { + self.data.push(ArenaItem { + occupied: ManuallyDrop::new(value), + }); + ArenaKey(self.data.len() - 1) + } + } + } + + pub unsafe fn remove(&mut self, key: ArenaKey) { + self.inserted = self + .inserted + .checked_sub(1) + .expect("removed more items than exist in here!"); + + unsafe { + core::mem::ManuallyDrop::::drop(&mut self.data[key.0].occupied); + } + + self.data[key.0].free = self.tip; + self.tip = Some(key); + } + + pub unsafe fn get(&self, key: ArenaKey) -> &T { + &self.data[key.0].occupied + } +} + +impl Drop for Arena { + fn drop(&mut self) { + assert_eq!( + self.inserted, 0, + "must remove all elements from arena before dropping it!" + ); + } +} diff --git a/agb/src/display/affine.rs b/agb/src/display/affine.rs index 38412619..fa1b269c 100644 --- a/agb/src/display/affine.rs +++ b/agb/src/display/affine.rs @@ -356,6 +356,15 @@ impl AffineMatrixObject { y: 0.into(), } } + + pub(crate) fn components(self) -> [u16; 4] { + [ + self.a.to_raw() as u16, + self.b.to_raw() as u16, + self.c.to_raw() as u16, + self.d.to_raw() as u16, + ] + } } impl From for AffineMatrix { diff --git a/agb/src/display/mod.rs b/agb/src/display/mod.rs index 939c22d3..e91ac9d6 100644 --- a/agb/src/display/mod.rs +++ b/agb/src/display/mod.rs @@ -4,7 +4,11 @@ use bitflags::bitflags; use modular_bitfield::BitfieldSpecifier; use video::Video; -use self::{blend::Blend, object::ObjectController, window::Windows}; +use self::{ + blend::Blend, + object::{initilise_oam, OamManaged, OamUnmanaged, SpriteLoader}, + window::Windows, +}; /// Graphics mode 3. Bitmap mode that provides a 16-bit colour framebuffer. pub mod bitmap3; @@ -12,7 +16,6 @@ pub mod bitmap3; pub mod bitmap4; /// Test logo of agb. pub mod example_logo; -/// Implements sprites. pub mod object; /// Palette type. pub mod palette16; @@ -80,8 +83,21 @@ pub struct Display { pub struct ObjectDistribution; impl ObjectDistribution { - pub fn get(&mut self) -> ObjectController<'_> { - ObjectController::new() + pub fn get_unmanaged(&mut self) -> (OamUnmanaged<'_>, SpriteLoader) { + unsafe { initilise_oam() }; + (OamUnmanaged::new(), SpriteLoader::new()) + } + + pub fn get_managed(&mut self) -> OamManaged<'_> { + unsafe { initilise_oam() }; + OamManaged::new() + } + + /// The old name for [`get_managed`][ObjectDistribution::get_managed] kept around for easier migration. + /// This will be removed in a future release. + #[deprecated = "use get_managed to get the managed oam instead"] + pub fn get(&mut self) -> OamManaged<'_> { + self.get_managed() } } @@ -143,7 +159,7 @@ pub fn busy_wait_for_vblank() { while VCOUNT.get() < 160 {} } -#[derive(BitfieldSpecifier, Clone, Copy)] +#[derive(BitfieldSpecifier, Clone, Copy, Debug)] pub enum Priority { P0 = 0, P1 = 1, diff --git a/agb/src/display/object.rs b/agb/src/display/object.rs index df1eb842..64c13b76 100644 --- a/agb/src/display/object.rs +++ b/agb/src/display/object.rs @@ -1,1331 +1,43 @@ -#![deny(missing_docs)] -use alloc::rc::{Rc, Weak}; -use alloc::vec::Vec; -use core::alloc::Layout; +#![warn(missing_docs)] +//! # Sprites and objects +//! +//! There are two implementations of objects depending on how you want to make +//! your game. There is the *Managed* and *Unmanaged* systems, given by +//! [OamManaged] and [OamUnmanaged] respectively. The managed Oam is easier to +//! use and has built in support for setting the `z` coordinate. The unmanaged +//! Oam is simpler and more efficient with the tradeoff that it is slightly +//! harder to integrate into your games depending on how they are architectured. -use core::cell::UnsafeCell; -use core::marker::PhantomData; -use core::mem::MaybeUninit; -use core::ops::DerefMut; -use core::ptr::NonNull; -use core::slice; -use modular_bitfield::prelude::{B10, B2, B3, B4, B5, B8, B9}; -use modular_bitfield::{bitfield, BitfieldSpecifier}; +mod affine; +mod managed; +mod sprites; +mod unmanaged; -const BYTES_PER_TILE_4BPP: usize = 32; - -use super::palette16::Palette16; -use super::{Priority, DISPLAY_CONTROL}; -use crate::agb_alloc::block_allocator::BlockAllocator; -use crate::agb_alloc::bump_allocator::StartEnd; -use crate::dma; -use crate::fixnum::Vector2D; -use crate::hash_map::HashMap; - -use attributes::*; - -/// Include this type if you call `get_object_controller` in impl block. This -/// helps you use the right lifetimes and doesn't impl Sync (using from two -/// "threads" without synchronisation is not safe), but sending to another -/// "thread" is safe. -#[derive(Clone, Copy)] -struct ObjectControllerReference<'a> { - #[cfg(debug_assertions)] - reference: &'a core::cell::RefCell, - - _ref: PhantomData<&'a UnsafeCell<()>>, -} - -#[cfg(debug_assertions)] -static mut OBJECT_CONTROLLER: MaybeUninit> = - MaybeUninit::uninit(); -#[cfg(not(debug_assertions))] -static mut OBJECT_CONTROLLER: MaybeUninit = MaybeUninit::uninit(); - -impl<'a> ObjectControllerReference<'a> { - unsafe fn init() -> Self { - #[cfg(debug_assertions)] - OBJECT_CONTROLLER.write(core::cell::RefCell::new(ObjectControllerStatic::new())); - #[cfg(not(debug_assertions))] - OBJECT_CONTROLLER.write(ObjectControllerStatic::new()); - Self { - #[cfg(debug_assertions)] - reference: unsafe { OBJECT_CONTROLLER.assume_init_ref() }, - _ref: PhantomData, - } - } - - unsafe fn uninit() { - OBJECT_CONTROLLER.assume_init_drop(); - } - - #[track_caller] - #[cfg(debug_assertions)] - fn borrow_cell_ref(self) -> core::cell::RefMut<'a, ObjectControllerStatic> { - self.reference.borrow_mut() - } - #[track_caller] - #[cfg(not(debug_assertions))] - unsafe fn borrow_direct(self) -> &'a mut ObjectControllerStatic { - unsafe { OBJECT_CONTROLLER.assume_init_mut() } - } - - #[track_caller] - unsafe fn borrow_mut(self) -> impl DerefMut + 'a { - #[cfg(debug_assertions)] - { - self.reference.borrow_mut() - } - #[cfg(not(debug_assertions))] - unsafe { - OBJECT_CONTROLLER.assume_init_mut() - } - } -} - -static SPRITE_ALLOCATOR: BlockAllocator = unsafe { - BlockAllocator::new(StartEnd { - start: || TILE_SPRITE, - end: || TILE_SPRITE + 1024 * 8 * 4, - }) +pub use sprites::{ + include_aseprite, DynamicSprite, Graphics, PaletteVram, Size, Sprite, SpriteLoader, SpriteVram, + Tag, TagMap, }; -static PALETTE_ALLOCATOR: BlockAllocator = unsafe { - BlockAllocator::new(StartEnd { - start: || PALETTE_SPRITE, - end: || PALETTE_SPRITE + 0x200, - }) -}; +pub use affine::AffineMatrixInstance; +pub use managed::{OamManaged, Object}; +pub use unmanaged::{AffineMode, OamIterator, OamSlot, OamUnmanaged, ObjectUnmanaged}; -const PALETTE_SPRITE: usize = 0x0500_0200; -const TILE_SPRITE: usize = 0x06010000; -const OBJECT_ATTRIBUTE_MEMORY: usize = 0x0700_0000; +use super::DISPLAY_CONTROL; -/// Sprite data. Refers to the palette, pixel data, and the size of the sprite. -pub struct Sprite { - palette: &'static Palette16, - data: &'static [u8], - size: Size, -} - -/// Sprite data that can be used to create sprites in vram. -pub struct DynamicSprite<'a> { - data: &'a [u8], - size: Size, -} - -impl DynamicSprite<'_> { - #[must_use] - /// Creates a new dynamic sprite from underlying bytes. Note that despite - /// being an array of u8, this must be aligned to at least a 2 byte - /// boundary. - 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 } - } - - #[must_use] - /// Tries to copy the sprite to vram to be used to set object sprites. - /// Returns None if there is no room in sprite vram. - pub fn try_vram(&self, palette: PaletteVram) -> Option { - Some(SpriteBorrow { - sprite: unsafe { SpriteVram::new(self.data, self.size, palette)? }, - }) - } - - #[must_use] - /// Tries to copy the sprite to vram to be used to set object sprites. - /// Panics if there is no room in sprite vram. - pub fn to_vram(&self, palette: PaletteVram) -> SpriteBorrow { - self.try_vram(palette) - .expect("No slot for sprite available") - } -} - -/// The sizes of sprite supported by the GBA. -#[derive(Clone, Copy, PartialEq, Eq)] -#[allow(missing_docs)] -pub enum Size { - // stored as attr0 attr1 - S8x8 = 0b00_00, - S16x16 = 0b00_01, - S32x32 = 0b00_10, - S64x64 = 0b00_11, - - S16x8 = 0b01_00, - S32x8 = 0b01_01, - S32x16 = 0b01_10, - S64x32 = 0b01_11, - - S8x16 = 0b10_00, - S8x32 = 0b10_01, - S16x32 = 0b10_10, - S32x64 = 0b10_11, -} - -#[doc(hidden)] -#[repr(C)] // guarantee 'bytes' comes after '_align' -pub struct AlignedAs { - pub _align: [Align; 0], - pub bytes: Bytes, -} - -#[doc(hidden)] -#[macro_export] -macro_rules! align_bytes { - ($align_ty:ty, $data:literal) => {{ - use $crate::display::object::AlignedAs; - - const ALIGNED: &AlignedAs<$align_ty, [u8]> = &AlignedAs { - _align: [], - bytes: *$data, - }; - - &ALIGNED.bytes - }}; -} - -/// Includes sprites found in the referenced aseprite files. Can include -/// multiple at once and optimises palettes of all included in the single call -/// together. See [Size] for supported sizes. Returns a reference to [Graphics]. -/// -/// ```rust,no_run -/// # #![no_std] -/// # #![no_main] -/// # use agb::{display::object::Graphics, include_aseprite}; -/// const GRAPHICS: &Graphics = include_aseprite!( -/// "examples/gfx/boss.aseprite", -/// "examples/gfx/objects.aseprite" -/// ); -/// ``` -/// The tags from the aseprite file are included so you can refer to sprites by -/// name in code. You should ensure tags are unique as this is not enforced by -/// aseprite. -/// -#[macro_export] -macro_rules! include_aseprite { - ($($aseprite_path: expr),*) => {{ - use $crate::display::object::{Size, Sprite, Tag, TagMap, Graphics}; - use $crate::display::palette16::Palette16; - use $crate::align_bytes; - - $crate::include_aseprite_inner!($($aseprite_path),*); - - &Graphics::new(SPRITES, TAGS) - }}; -} - -/// Stores sprite and tag data returned by [include_aseprite]. -pub struct Graphics { - sprites: &'static [Sprite], - tag_map: &'static TagMap, -} - -impl Graphics { - #[doc(hidden)] - /// Creates graphics data from sprite data and a tag_map. This is used - /// internally by [include_aseprite] and would be otherwise difficult to - /// use. - #[must_use] - pub const fn new(sprites: &'static [Sprite], tag_map: &'static TagMap) -> Self { - Self { sprites, tag_map } - } - #[must_use] - /// Gets the tag map from the aseprite files. This allows reference to - /// sprite sequences by name. - pub const fn tags(&self) -> &TagMap { - self.tag_map - } - /// Gets a big list of the sprites themselves. Using tags is often easier. - #[must_use] - pub const fn sprites(&self) -> &[Sprite] { - self.sprites - } -} - -/// Stores aseprite tags. Can be used to refer to animation sequences by name. -/// ```rust,no_run -/// # #![no_std] -/// # #![no_main] -/// # use agb::{display::object::{Graphics, Tag}, include_aseprite}; -/// const GRAPHICS: &Graphics = include_aseprite!( -/// "examples/gfx/boss.aseprite", -/// "examples/gfx/objects.aseprite" -/// ); -/// -/// const EMU_WALK: &Tag = GRAPHICS.tags().get("emu-walk"); -/// ``` -/// This being the whole animation associated with the walk sequence of the emu. -/// See [Tag] for details on how to use this. -pub struct TagMap { - tags: &'static [(&'static str, Tag)], -} - -const fn const_byte_compare(a: &[u8], b: &[u8]) -> bool { - if a.len() != b.len() { - return false; - } - - let mut i = 0; - while i < a.len() { - if a[i] != b[i] { - return false; - } - i += 1; - } - true -} - -impl TagMap { - #[doc(hidden)] - /// Creates a new tag map from (name, Tag) pairs. Used internally by - /// [include_aseprite] and should not really be used outside of it. - #[must_use] - pub const fn new(tags: &'static [(&'static str, Tag)]) -> TagMap { - Self { tags } - } - - #[doc(hidden)] - /// Attempts to get a tag. Generally should not be used. - #[must_use] - pub const fn try_get(&'static self, tag: &str) -> Option<&'static Tag> { - let mut i = 0; - while i < self.tags.len() { - let s = self.tags[i].0; - if const_byte_compare(s.as_bytes(), tag.as_bytes()) { - return Some(&self.tags[i].1); - } - - i += 1; - } - - None - } - - /// Gets a tag associated with the name. A tag in aseprite refers to a - /// sequence of sprites with some metadata for how to animate it. You should - /// call this in a constant context so it is evaluated at compile time. It - /// is inefficient to call this elsewhere. - /// ```rust,no_run - /// # #![no_std] - /// # #![no_main] - /// # use agb::{display::object::{Graphics, Tag}, include_aseprite}; - /// const GRAPHICS: &Graphics = include_aseprite!( - /// "examples/gfx/boss.aseprite", - /// "examples/gfx/objects.aseprite" - /// ); - /// - /// const EMU_WALK: &Tag = GRAPHICS.tags().get("emu-walk"); - /// ``` - /// - /// See [Tag] for more details. - #[must_use] - pub const fn get(&'static self, tag: &str) -> &'static Tag { - let t = self.try_get(tag); - match t { - Some(t) => t, - None => panic!("The requested tag does not exist"), - } - } - - /// Takes an iterator over all the tags in the map. Not generally useful. - pub fn values(&self) -> impl Iterator { - self.tags.iter().map(|x| &x.1) - } -} - -#[derive(Clone, Copy)] -enum Direction { - Forward, - Backward, - PingPong, -} - -impl Direction { - const fn from_usize(a: usize) -> Self { - match a { - 0 => Direction::Forward, - 1 => Direction::Backward, - 2 => Direction::PingPong, - _ => panic!("Invalid direction, this is a bug in image converter or agb"), - } - } -} - -/// A sequence of sprites from aseprite. -pub struct Tag { - sprites: *const Sprite, - len: usize, - direction: Direction, -} - -impl Tag { - /// The individual sprites that make up the animation themselves. - #[must_use] - pub fn sprites(&self) -> &'static [Sprite] { - unsafe { slice::from_raw_parts(self.sprites, self.len) } - } - - /// A single sprite referred to by index in the animation sequence. - #[must_use] - pub const fn sprite(&self, idx: usize) -> &'static Sprite { - if idx >= self.len { - panic!("out of bounds access to sprite"); - } - unsafe { &*self.sprites.add(idx) } - } - - /// A sprite that follows the animation sequence. For instance, in aseprite - /// tags can be specified to animate: - /// * Forward - /// * Backward - /// * Ping pong - /// - /// This takes the animation type in account and returns the correct sprite - /// following these requirements. - #[inline] - #[must_use] - pub fn animation_sprite(&self, idx: usize) -> &'static Sprite { - let len_sub_1 = self.len - 1; - match self.direction { - Direction::Forward => self.sprite(idx % self.len), - Direction::Backward => self.sprite(len_sub_1 - (idx % self.len)), - Direction::PingPong => self.sprite( - (((idx + len_sub_1) % (len_sub_1 * 2)) as isize - len_sub_1 as isize) - .unsigned_abs(), - ), - } - } - - #[doc(hidden)] - /// Creates a new sprite from it's constituent parts. Used internally by - /// [include_aseprite] and should generally not be used elsewhere. - #[must_use] - pub const fn new(sprites: &'static [Sprite], from: usize, to: usize, direction: usize) -> Self { - assert!(from <= to); - assert!(to < sprites.len()); - Self { - sprites: &sprites[from] as *const Sprite, - len: to - from + 1, - direction: Direction::from_usize(direction), - } - } -} - -impl Size { - const fn number_of_tiles(self) -> usize { - match self { - Size::S8x8 => 1, - Size::S16x16 => 4, - Size::S32x32 => 16, - Size::S64x64 => 64, - Size::S16x8 => 2, - Size::S32x8 => 4, - Size::S32x16 => 8, - Size::S64x32 => 32, - Size::S8x16 => 2, - Size::S8x32 => 4, - Size::S16x32 => 8, - Size::S32x64 => 32, - } - } - const fn shape_size(self) -> (u8, u8) { - (self as u8 >> 2, self as u8 & 0b11) - } - - fn layout(self) -> Layout { - Layout::from_size_align(self.number_of_tiles() * BYTES_PER_TILE_4BPP, 8).unwrap() - } - - #[must_use] - /// Creates a size from width and height in pixels, panics if the width and - /// height is not representable by GBA sprites. - pub const fn from_width_height(width: usize, height: usize) -> Self { - match (width, height) { - (8, 8) => Size::S8x8, - (16, 16) => Size::S16x16, - (32, 32) => Size::S32x32, - (64, 64) => Size::S64x64, - (16, 8) => Size::S16x8, - (32, 8) => Size::S32x8, - (32, 16) => Size::S32x16, - (64, 32) => Size::S64x32, - (8, 16) => Size::S8x16, - (8, 32) => Size::S8x32, - (16, 32) => Size::S16x32, - (32, 64) => Size::S32x64, - (_, _) => panic!("Bad width and height!"), - } - } - - #[must_use] - /// Returns the width and height of the size in pixels. - pub const fn to_width_height(self) -> (usize, usize) { - match self { - Size::S8x8 => (8, 8), - Size::S16x16 => (16, 16), - Size::S32x32 => (32, 32), - Size::S64x64 => (64, 64), - Size::S16x8 => (16, 8), - Size::S32x8 => (32, 8), - Size::S32x16 => (32, 16), - Size::S64x32 => (64, 32), - Size::S8x16 => (8, 16), - Size::S8x32 => (8, 32), - Size::S16x32 => (16, 32), - Size::S32x64 => (32, 64), - } - } -} - -/// A reference to a sprite that is currently copied in vram. This is reference -/// counted and can be cloned to keep it in vram or otherwise manipulated. If -/// objects no longer refer to this sprite, then it's vram slot is freed for the -/// next sprite. This is obtained from the [ObjectController]. -#[derive(Clone)] -pub struct SpriteBorrow { - sprite: SpriteVram, -} - -#[derive(PartialEq, Eq)] -struct Attributes { - a0: ObjectAttribute0, - a1s: ObjectAttribute1Standard, - a1a: ObjectAttribute1Affine, - a2: ObjectAttribute2, -} - -impl Attributes { - fn new() -> Self { - Self { - a0: ObjectAttribute0::new(), - a1s: ObjectAttribute1Standard::new(), - a1a: ObjectAttribute1Affine::new(), - a2: ObjectAttribute2::new(), - } - } - - fn commit(&self, location: usize) { - let mode = self.a0.object_mode(); - let attrs: [[u8; 2]; 3] = match mode { - ObjectMode::Normal => [ - self.a0.into_bytes(), - self.a1s.into_bytes(), - self.a2.into_bytes(), - ], - _ => [ - self.a0.into_bytes(), - self.a1a.into_bytes(), - self.a2.into_bytes(), - ], - }; - - unsafe { - let attrs: [u16; 3] = core::mem::transmute(attrs); - let ptr = (OBJECT_ATTRIBUTE_MEMORY as *mut u16).add(location * 4); - - ptr.add(0).write_volatile(attrs[0]); - ptr.add(1).write_volatile(attrs[1]); - ptr.add(2).write_volatile(attrs[2]); - }; - } -} - -/// An object that may be displayed on screen. The object can be modified using -/// the available methods. This is obtained from the [ObjectController]. -pub struct Object<'a> { - loan: Loan<'a>, -} - -#[derive(Clone, Copy)] -struct Location(usize); - -impl Location { - fn from_sprite_ptr(d: NonNull) -> Self { - Self(((d.as_ptr() as usize) - TILE_SPRITE) / BYTES_PER_TILE_4BPP) - } - fn from_palette_ptr(d: NonNull) -> 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(Clone)] -/// The palette data in Vram, this is reference counted and the palette data is -/// removed and can be reused from vram when no strong references remain. -pub struct PaletteVram(Rc); - -#[derive(Clone)] -struct SpriteVram(Rc); - -struct PaletteData { - location: Location, -} - -impl PaletteVram { - /// Creates a palette in vram from the given palette. Can be used to create - /// sprites in vram in the [DynamicSprite] functions. - #[must_use] - pub fn new(palette: &Palette16) -> Option { - let dest = unsafe { PALETTE_ALLOCATOR.alloc(Palette16::layout())? }; - - unsafe { - dma::dma_copy16( - palette.colours.as_ptr().cast(), - dest.as_ptr().cast(), - palette.colours.len(), - ); - } - - Some(PaletteVram(Rc::new(PaletteData { - location: Location::from_palette_ptr(dest), - }))) - } -} - -impl SpriteVram { - /// # Safety - /// data should be aligned to a 2 byte boundary - unsafe fn new(data: &[u8], size: Size, palette: PaletteVram) -> Option { - let dest = unsafe { SPRITE_ALLOCATOR.alloc(size.layout())? }; - - unsafe { - dma::dma_copy16(data.as_ptr().cast(), dest.as_ptr().cast(), data.len() / 2); - } - - Some(SpriteVram(Rc::new(SpriteArena { - location: Location::from_sprite_ptr(dest), - size, - palette, - }))) - } -} - -impl Drop for PaletteData { - fn drop(&mut self) { - unsafe { PALETTE_ALLOCATOR.dealloc(self.location.as_palette_ptr(), Palette16::layout()) }; - } -} - -struct SpriteArena { - location: Location, - size: Size, - palette: PaletteVram, -} - -impl Drop for SpriteArena { - fn drop(&mut self) { - unsafe { SPRITE_ALLOCATOR.dealloc(self.location.as_sprite_ptr(), self.size.layout()) } - } -} - -#[derive(Default)] -struct SpriteControllerInner { - static_palette_map: HashMap>, - static_sprite_map: HashMap>, -} - -struct Loan<'a> { - index: u8, - controller: ObjectControllerReference<'a>, -} - -impl Drop for Loan<'_> { - fn drop(&mut self) { - let mut s = unsafe { self.controller.borrow_mut() }; - - unsafe { - s.shadow_oam[self.index as usize] - .as_mut() - .unwrap_unchecked() - .destroy = true; - }; - } -} - -struct ObjectInner { - attrs: Attributes, - sprite: SpriteBorrow, - previous_sprite: SpriteBorrow, - destroy: bool, - z: i32, -} - -struct ObjectControllerStatic { - _free_affine_matrices: Vec, - free_object: Vec, - shadow_oam: Vec>, - z_order: Vec, - sprite_controller: SpriteControllerInner, -} - -impl ObjectControllerStatic { - unsafe fn new() -> Self { - Self { - shadow_oam: (0..128).map(|_| None).collect(), - z_order: (0..128).collect(), - free_object: (0..128).collect(), - _free_affine_matrices: (0..32).collect(), - sprite_controller: SpriteControllerInner::new(), - } - } - - fn update_z_ordering(&mut self) { - let shadow_oam = &self.shadow_oam; - self.z_order - .sort_by_key(|&a| shadow_oam[a as usize].as_ref().map_or(i32::MAX, |s| s.z)); - } -} - -/// A controller that distributes objects and sprites. This controls sprites and -/// objects being copied to vram when it needs to be. -pub struct ObjectController<'gba> { - phantom: PhantomData<&'gba ()>, - inner: ObjectControllerReference<'static>, -} - -impl<'gba> Drop for ObjectController<'gba> { - fn drop(&mut self) { - unsafe { - ObjectControllerReference::uninit(); - } - } -} - -const HIDDEN_VALUE: u16 = 0b10 << 8; - -impl<'gba> ObjectController<'gba> { - /// Commits the objects to vram and delete sprites where possible. This - /// should be called shortly after having waited for the next vblank to - /// ensure what is displayed on screen doesn't change part way through. - pub fn commit(&self) { - let mut s = unsafe { self.inner.borrow_mut() }; - - let s = &mut *s; - - for (i, &z) in s.z_order.iter().enumerate() { - if let Some(o) = &mut s.shadow_oam[z as usize] { - if o.destroy { - s.free_object.push(z); - - unsafe { - (OBJECT_ATTRIBUTE_MEMORY as *mut u16) - .add(i * 4) - .write_volatile(HIDDEN_VALUE); - } - - let _ = unsafe { s.shadow_oam[z as usize].take().unwrap_unchecked() }; - } else { - o.attrs.commit(i); - o.previous_sprite = o.sprite.clone(); - } - } else { - unsafe { - (OBJECT_ATTRIBUTE_MEMORY as *mut u16) - .add(i * 4) - .write_volatile(HIDDEN_VALUE); - } - } - } - - s.sprite_controller.gc(); - } - - pub(crate) fn new() -> Self { - DISPLAY_CONTROL.set_bits(1, 1, 0x6); - DISPLAY_CONTROL.set_bits(1, 1, 0xC); - DISPLAY_CONTROL.set_bits(0, 1, 0x7); - - for i in 0..128 { - unsafe { - (OBJECT_ATTRIBUTE_MEMORY as *mut u16) - .add(i * 4) - .write_volatile(HIDDEN_VALUE); - } - } - - Self { - phantom: PhantomData, - inner: unsafe { ObjectControllerReference::init() }, - } - } - - #[must_use] - /// Creates an object with it's initial sprite being the sprite reference. - /// Panics if there is no space for the sprite or if there are no free - /// objects. This will reuse an existing copy of the sprite in vram if - /// possible. - /// ```rust,no_run - /// # #![no_std] - /// # #![no_main] - /// # use agb::{display::object::{Graphics, Tag}, include_aseprite}; - /// const GRAPHICS: &Graphics = include_aseprite!( - /// "examples/gfx/boss.aseprite", - /// "examples/gfx/objects.aseprite" - /// ); - /// - /// const EMU_WALK: &Tag = GRAPHICS.tags().get("emu-walk"); - /// - /// # fn foo(gba: &mut agb::Gba) { - /// # let object_controller = gba.display.object.get(); - /// let emu = object_controller.object_sprite(EMU_WALK.animation_sprite(0)); - /// # } - /// ``` - pub fn object_sprite<'a>(&'a self, sprite: &'static Sprite) -> Object<'a> { - let sprite = self.sprite(sprite); - self.object(sprite) - } - - #[must_use] - /// Creates an object with it's initial sprite being the sprite reference. - /// Returns [None] if the sprite or object could not be allocated. This will - /// reuse an existing copy of the sprite in vram if possible. - /// ```rust,no_run - /// # #![no_std] - /// # #![no_main] - /// # use agb::{display::object::{Graphics, Tag}, include_aseprite}; - /// const GRAPHICS: &Graphics = include_aseprite!( - /// "examples/gfx/boss.aseprite", - /// "examples/gfx/objects.aseprite" - /// ); - /// - /// const EMU_WALK: &Tag = GRAPHICS.tags().get("emu-walk"); - /// - /// # fn foo(gba: &mut agb::Gba) { - /// # let object_controller = gba.display.object.get(); - /// let emu = object_controller.try_get_object_sprite( - /// EMU_WALK.animation_sprite(0) - /// ).expect("the sprite or object could be allocated"); - /// # } - /// ``` - pub fn try_get_object_sprite<'a>(&'a self, sprite: &'static Sprite) -> Option> { - let sprite = self.try_get_sprite(sprite)?; - self.try_get_object(sprite) - } - - /// Creates an object with it's initial sprite being what is in the - /// [SpriteBorrow]. Panics if there are no objects left. A [SpriteBorrow] is - /// created using the [ObjectController::sprite] function. - /// ```rust,no_run - /// # #![no_std] - /// # #![no_main] - /// # use agb::{display::object::{Graphics, Tag}, include_aseprite}; - /// const GRAPHICS: &Graphics = include_aseprite!( - /// "examples/gfx/boss.aseprite", - /// "examples/gfx/objects.aseprite" - /// ); - /// - /// const EMU_WALK: &Tag = GRAPHICS.tags().get("emu-walk"); - /// - /// # fn foo(gba: &mut agb::Gba) { - /// # let object_controller = gba.display.object.get(); - /// let emu = object_controller.object(object_controller.sprite(EMU_WALK.animation_sprite(0))); - /// # } - /// ``` - #[must_use] - pub fn object(&self, sprite: SpriteBorrow) -> Object { - self.try_get_object(sprite).expect("No object available") - } - - /// Creates an object with it's initial sprite being what is in the - /// [SpriteBorrow]. A [SpriteBorrow] is created using the - /// [ObjectController::try_get_sprite] function. - /// ```rust,no_run - /// # #![no_std] - /// # #![no_main] - /// # use agb::{display::object::{Graphics, Tag}, include_aseprite}; - /// const GRAPHICS: &Graphics = include_aseprite!( - /// "examples/gfx/boss.aseprite", - /// "examples/gfx/objects.aseprite" - /// ); - /// - /// const EMU_WALK: &Tag = GRAPHICS.tags().get("emu-walk"); - /// - /// # fn foo(gba: &mut agb::Gba) { - /// # let object_controller = gba.display.object.get(); - /// let emu = object_controller.try_get_object( - /// object_controller.sprite(EMU_WALK.animation_sprite(0)) - /// ).expect("the object should be allocatable"); - /// # } - /// ``` - #[must_use] - pub fn try_get_object(&self, sprite: SpriteBorrow) -> Option { - let mut s = unsafe { self.inner.borrow_mut() }; - - let mut attrs = Attributes::new(); - - attrs.a2.set_tile_index(sprite.sprite.0.location.0 as u16); - let shape_size = sprite.sprite.0.size.shape_size(); - attrs - .a2 - .set_palette_bank((sprite.sprite.0.palette.0.location.0) as u8); - attrs.a0.set_shape(shape_size.0); - attrs.a1a.set_size(shape_size.1); - attrs.a1s.set_size(shape_size.1); - - let index = s.free_object.pop()?; - - s.shadow_oam[index as usize] = Some(ObjectInner { - attrs, - z: 0, - previous_sprite: sprite.clone(), - destroy: false, - sprite, - }); - - let loan = Loan { - index, - controller: self.inner, - }; - - s.update_z_ordering(); - - Some(Object { loan }) - } - - /// Creates a [SpriteBorrow] from the given sprite, panics if the sprite - /// could not be allocated. This will reuse an existing copy of the sprite - /// in vram if possible. - /// ```rust,no_run - /// # #![no_std] - /// # #![no_main] - /// # use agb::{display::object::{Graphics, Tag}, include_aseprite}; - /// const GRAPHICS: &Graphics = include_aseprite!( - /// "examples/gfx/boss.aseprite", - /// "examples/gfx/objects.aseprite" - /// ); - /// - /// const EMU_WALK: &Tag = GRAPHICS.tags().get("emu-walk"); - /// - /// # fn foo(gba: &mut agb::Gba) { - /// # let object_controller = gba.display.object.get(); - /// let sprite = object_controller.sprite(EMU_WALK.animation_sprite(0)); - /// # } - /// ``` - #[must_use] - pub fn sprite(&self, sprite: &'static Sprite) -> SpriteBorrow { - self.try_get_sprite(sprite) - .expect("No slot for sprite available") - } - - /// Creates a [SpriteBorrow] from the given sprite. This will reuse an - /// existing copy of the sprite in vram if possible. - /// ```rust,no_run - /// # #![no_std] - /// # #![no_main] - /// # use agb::{display::object::{Graphics, Tag}, include_aseprite}; - /// const GRAPHICS: &Graphics = include_aseprite!( - /// "examples/gfx/boss.aseprite", - /// "examples/gfx/objects.aseprite" - /// ); - /// - /// const EMU_WALK: &Tag = GRAPHICS.tags().get("emu-walk"); - /// - /// # fn foo(gba: &mut agb::Gba) { - /// # let object_controller = gba.display.object.get(); - /// let sprite = object_controller.try_get_sprite( - /// EMU_WALK.animation_sprite(0) - /// ).expect("the sprite should be allocatable"); - /// # } - /// ``` - #[must_use] - pub fn try_get_sprite(&self, sprite: &'static Sprite) -> Option { - unsafe { self.inner.borrow_mut() } - .sprite_controller - .try_get_sprite(sprite) - } -} - -impl<'a> Object<'a> { - #[inline(always)] - unsafe fn object_inner(&self) -> impl DerefMut + 'a { - #[cfg(debug_assertions)] - { - core::cell::RefMut::map(self.loan.controller.borrow_cell_ref(), |s| { - s.shadow_oam[self.loan.index as usize] - .as_mut() - .unwrap_unchecked() - }) - } - #[cfg(not(debug_assertions))] - { - self.loan.controller.borrow_direct().shadow_oam[self.loan.index as usize] - .as_mut() - .unwrap_unchecked() - } - } - - /// Swaps out the current sprite. This handles changing of size, palette, - /// etc. No change will be seen until [ObjectController::commit] is called. - pub fn set_sprite(&'_ mut self, sprite: SpriteBorrow) { - let mut object_inner = unsafe { self.object_inner() }; - object_inner - .attrs - .a2 - .set_tile_index(sprite.sprite.0.location.0 as u16); - let shape_size = sprite.sprite.0.size.shape_size(); - object_inner - .attrs - .a2 - .set_palette_bank(sprite.sprite.0.palette.0.location.0 as u8); - object_inner.attrs.a0.set_shape(shape_size.0); - object_inner.attrs.a1a.set_size(shape_size.1); - object_inner.attrs.a1s.set_size(shape_size.1); - - object_inner.sprite = sprite; - } - - /// Shows the sprite. No change will be seen until - /// [ObjectController::commit] is called. - pub fn show(&mut self) -> &mut Self { - { - let mut object_inner = unsafe { self.object_inner() }; - object_inner.attrs.a0.set_object_mode(ObjectMode::Normal); - } - - self - } - - /// Controls whether the sprite is flipped horizontally, for example useful - /// for reusing the same sprite for the left and right walking directions. - /// No change will be seen until [ObjectController::commit] is called. - pub fn set_hflip(&mut self, flip: bool) -> &mut Self { - { - let mut object_inner = unsafe { self.object_inner() }; - object_inner.attrs.a1s.set_horizontal_flip(flip); - } - self - } - - /// Controls whether the sprite is flipped vertically, for example useful - /// for reusing the same sprite for the up and down walking directions. No - /// change will be seen until [ObjectController::commit] is called. - pub fn set_vflip(&mut self, flip: bool) -> &mut Self { - { - let mut object_inner = unsafe { self.object_inner() }; - object_inner.attrs.a1s.set_vertical_flip(flip); - } - self - } - - /// Sets the x position of the object. The coordinate refers to the top-left - /// corner of the sprite. No change will be seen until - /// [ObjectController::commit] is called. - pub fn set_x(&mut self, x: u16) -> &mut Self { - { - let mut object_inner = unsafe { self.object_inner() }; - object_inner.attrs.a1a.set_x(x.rem_euclid(1 << 9)); - object_inner.attrs.a1s.set_x(x.rem_euclid(1 << 9)); - } - self - } - - /// Sets the z priority of the sprite. Higher priority will be displayed - /// above background layers with lower priorities. No change will be seen - /// until [ObjectController::commit] is called. - pub fn set_priority(&mut self, priority: Priority) -> &mut Self { - { - let mut object_inner = unsafe { self.object_inner() }; - object_inner.attrs.a2.set_priority(priority); - } - self - } - - /// Hides the object. No change will be seen until - /// [ObjectController::commit] is called. - pub fn hide(&mut self) -> &mut Self { - { - let mut object_inner = unsafe { self.object_inner() }; - object_inner.attrs.a0.set_object_mode(ObjectMode::Disabled); - } - self - } - - /// Sets the y position of the sprite. The coordinate refers to the top-left - /// corner of the sprite. No change will be seen until - /// [ObjectController::commit] is called. - pub fn set_y(&mut self, y: u16) -> &mut Self { - { - let mut object_inner = unsafe { self.object_inner() }; - object_inner.attrs.a0.set_y(y as u8); - } - - self - } - - /// Sets the z position of the sprite, this controls which sprites are above - /// each other. No change will be seen until [ObjectController::commit] is - /// called. - pub fn set_z(&mut self, z: i32) -> &mut Self { - { - let mut object_inner = unsafe { self.object_inner() }; - object_inner.z = z; - } - unsafe { self.loan.controller.borrow_mut().update_z_ordering() }; - - self - } - - /// Sets the position of the sprite using a [Vector2D]. The coordinate - /// refers to the top-left corner of the sprite. No change will be seen - /// until [ObjectController::commit] is called. - pub fn set_position(&mut self, position: Vector2D) -> &mut Self { - { - let mut object_inner = unsafe { self.object_inner() }; - object_inner.attrs.a0.set_y(position.y as u8); - object_inner - .attrs - .a1a - .set_x(position.x.rem_euclid(1 << 9) as u16); - object_inner - .attrs - .a1s - .set_x(position.x.rem_euclid(1 << 9) as u16); - } - self - } -} - -/// The Sprite Id is a thin wrapper around the pointer to the sprite in -/// rom and is therefore a unique identifier to a sprite -#[derive(Clone, Copy, PartialEq, Eq, Hash)] -struct SpriteId(usize); - -/// The palette id is a thin wrapper around the pointer to the palette in rom -/// and is therefore a unique reference to a palette -#[derive(Copy, Clone, PartialEq, Eq, Hash)] -struct PaletteId(usize); - -impl Palette16 { - fn id(&'static self) -> PaletteId { - PaletteId(self as *const _ as usize) - } - const fn layout() -> Layout { - Layout::new::() - } -} - -impl Sprite { - fn id(&'static self) -> SpriteId { - SpriteId(self as *const _ as usize) - } - fn layout(&self) -> Layout { - Layout::from_size_align(self.size.number_of_tiles() * BYTES_PER_TILE_4BPP, 8).unwrap() - } - #[doc(hidden)] - /// Creates a sprite from it's constituent data, used internally by - /// [include_aseprite] and should generally not be used outside it. - /// - /// # Safety - /// The data should be aligned to a 2 byte boundary - #[must_use] - pub const unsafe fn new(palette: &'static Palette16, data: &'static [u8], size: Size) -> Self { - Self { - palette, - data, - size, - } - } - #[must_use] - /// The size of the sprite in it's form that is displayable on the GBA. - pub const fn size(&self) -> Size { - self.size - } -} - -impl SpriteControllerInner { - fn try_get_sprite(&mut self, sprite: &'static Sprite) -> Option { - let id = sprite.id(); - if let Some(storage) = self.static_sprite_map.get_mut(&id) { - if let Some(strong) = storage.upgrade() { - return Some(SpriteBorrow { - sprite: SpriteVram(strong), - }); - } - } - - // layout is non zero sized, so this is safe to call - - let palette_location = self.palette(sprite.palette); - let palette_location = match palette_location { - Some(a) => a, - None => { - return None; - } - }; - - let dest = unsafe { SPRITE_ALLOCATOR.alloc(sprite.layout())? }; - - unsafe { - dma::dma_copy16( - sprite.data.as_ptr().cast(), - dest.as_ptr().cast(), - sprite.data.len() / 2, - ); - } - - let sprite = SpriteVram(Rc::new(SpriteArena { - location: Location::from_sprite_ptr(dest), - size: sprite.size(), - palette: palette_location, - })); - - self.static_sprite_map.insert(id, Rc::downgrade(&sprite.0)); - - Some(SpriteBorrow { sprite }) - } - - /// Cleans up weak references to sprites and palettes no longer in vram - fn gc(&mut self) { - self.static_palette_map.retain(|_, v| v.strong_count() != 0); - self.static_sprite_map.retain(|_, v| v.strong_count() != 0); - } - - fn new() -> Self { - Default::default() - } - fn palette(&mut self, palette: &'static Palette16) -> Option { - let id = palette.id(); - if let Some(storage) = self.static_palette_map.get(&id) { - if let Some(up) = storage.upgrade() { - return Some(PaletteVram(up)); - } - } - - let palette_vram = PaletteVram::new(palette)?; - - self.static_palette_map - .insert(id, Rc::downgrade(&palette_vram.0)); - - Some(palette_vram) - } -} - -#[derive(BitfieldSpecifier, Clone, Copy)] -enum ObjectMode { - Normal, - Affine, - Disabled, - AffineDouble, -} - -#[derive(BitfieldSpecifier, Clone, Copy)] -#[bits = 2] -enum GraphicsMode { - Normal, - AlphaBlending, - Window, -} - -#[derive(BitfieldSpecifier, Clone, Copy)] -enum ColourMode { - Four, - Eight, -} - -// this mod is not public, so the internal parts don't need documenting. -#[allow(dead_code)] -mod attributes { - use super::*; - #[bitfield] - #[derive(Clone, Copy, PartialEq, Eq)] - pub(super) struct ObjectAttribute0 { - pub y: B8, - pub object_mode: ObjectMode, - pub graphics_mode: GraphicsMode, - pub mosaic: bool, - pub colour_mode: ColourMode, - pub shape: B2, - } - - #[bitfield] - #[derive(Clone, Copy, PartialEq, Eq)] - pub(super) struct ObjectAttribute1Standard { - pub x: B9, - #[skip] - __: B3, - pub horizontal_flip: bool, - pub vertical_flip: bool, - pub size: B2, - } - - #[bitfield] - #[derive(Clone, Copy, PartialEq, Eq)] - pub(super) struct ObjectAttribute1Affine { - pub x: B9, - pub affine_index: B5, - pub size: B2, - } - - #[bitfield] - #[derive(Clone, Copy, PartialEq, Eq)] - pub(super) struct ObjectAttribute2 { - pub tile_index: B10, - pub priority: Priority, - pub palette_bank: B4, - } -} - -#[cfg(test)] -mod tests { - use super::*; - use core::mem::size_of; - - #[test_case] - fn size_of_ObjectControllerReference(_: &mut crate::Gba) { - if !cfg!(debug_assertions) { - assert_eq!(size_of::(), 0); - } - } - - #[test_case] - fn object_usage(gba: &mut crate::Gba) { - const GRAPHICS: &Graphics = include_aseprite!( - "../examples/the-purple-night/gfx/objects.aseprite", - "../examples/the-purple-night/gfx/boss.aseprite" - ); - - const BOSS: &Tag = GRAPHICS.tags().get("Boss"); - const EMU: &Tag = GRAPHICS.tags().get("emu - idle"); - - let object = gba.display.object.get(); - - { - let mut objects: Vec<_> = alloc::vec![ - object.object(object.sprite(BOSS.sprite(0))), - object.object(object.sprite(EMU.sprite(0))), - ] - .into_iter() - .map(Some) - .collect(); - - object.commit(); - - let x = objects[0].as_mut().unwrap(); - x.set_hflip(true); - x.set_vflip(true); - x.set_position((1, 1).into()); - x.set_z(100); - x.set_sprite(object.sprite(BOSS.sprite(2))); - - object.commit(); - } - - object.commit(); - } +const OBJECT_ATTRIBUTE_MEMORY: *mut u16 = 0x0700_0000 as *mut u16; + +#[deprecated = "use OamManaged directly instead"] +/// The old name for [`OamManaged`] kept around for easier migration. +/// This will be removed in a future release. +pub type ObjectController<'a> = OamManaged<'a>; + +pub(super) unsafe fn initilise_oam() { + for i in 0..128 { + let ptr = (OBJECT_ATTRIBUTE_MEMORY).add(i * 4); + ptr.write_volatile(0b10 << 8); + } + + DISPLAY_CONTROL.set_bits(1, 1, 0x6); + DISPLAY_CONTROL.set_bits(1, 1, 0xC); + DISPLAY_CONTROL.set_bits(0, 1, 0x7); } diff --git a/agb/src/display/object/affine.rs b/agb/src/display/object/affine.rs new file mode 100644 index 00000000..f10b9007 --- /dev/null +++ b/agb/src/display/object/affine.rs @@ -0,0 +1,87 @@ +use core::cell::Cell; + +use alloc::rc::Rc; + +use crate::display::affine::AffineMatrixObject; + +#[derive(Debug)] +struct AffineMatrixData { + frame_count: Cell, + location: Cell, + matrix: AffineMatrixObject, +} + +#[derive(Debug, Clone)] +pub(crate) struct AffineMatrixVram(Rc); + +/// An affine matrix that can be used on objects. It is just in time copied to +/// vram, so you can have as many as you like of these but you can only use up +/// to 16 in one frame. They are reference counted (Cloning is cheap) and +/// immutable, if you want to change a matrix you must make a new one and set it +/// on all your objects. +#[derive(Debug, Clone)] +pub struct AffineMatrixInstance { + location: AffineMatrixVram, +} + +impl AffineMatrixInstance { + #[must_use] + /// Creates an instance of an affine matrix from its object form. Check out + /// the docs for [AffineMatrix][crate::display::affine::AffineMatrix] to see + /// how you can use them to create effects. + pub fn new(affine_matrix: AffineMatrixObject) -> AffineMatrixInstance { + AffineMatrixInstance { + location: AffineMatrixVram(Rc::new(AffineMatrixData { + frame_count: Cell::new(u32::MAX), + location: Cell::new(u32::MAX), + matrix: affine_matrix, + })), + } + } + + pub(crate) fn vram(self) -> AffineMatrixVram { + self.location + } +} + +impl AffineMatrixVram { + pub fn frame_count(&self) -> u32 { + self.0.frame_count.get() + } + + pub fn set_frame_count(&self, frame: u32) { + self.0.frame_count.set(frame); + } + + pub fn location(&self) -> u32 { + self.0.location.get() + } + + pub fn set_location(&self, location: u32) { + self.0.location.set(location); + } + + pub fn write_to_location(&self, oam: *mut u16) { + let components = self.0.matrix.components(); + let location = self.0.location.get() as usize; + for (idx, component) in components.iter().enumerate() { + unsafe { + oam.add(location * 16 + idx * 4 + 3) + .write_volatile(*component); + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test_case] + fn niche_optimisation(_gba: &mut crate::Gba) { + assert_eq!( + core::mem::size_of::(), + core::mem::size_of::>() + ); + } +} diff --git a/agb/src/display/object/managed.rs b/agb/src/display/object/managed.rs new file mode 100644 index 00000000..b55c1806 --- /dev/null +++ b/agb/src/display/object/managed.rs @@ -0,0 +1,511 @@ +use core::cell::{Cell, UnsafeCell}; + +use agb_fixnum::Vector2D; + +use crate::{ + arena::{Arena, ArenaKey}, + display::Priority, +}; + +use super::{ + AffineMatrixInstance, AffineMode, OamUnmanaged, ObjectUnmanaged, Sprite, SpriteLoader, + SpriteVram, +}; + +type ObjectKey = ArenaKey; + +#[derive(Clone, Copy)] +struct Ordering { + next: Option, + previous: Option, +} + +struct ObjectItem { + object: UnsafeCell, + z_order: Cell, + z_index: Cell, +} + +struct Store { + store: UnsafeCell>, + first_z: Cell>, +} + +struct StoreIterator<'store> { + store: &'store Arena, + current: Option, +} + +impl<'store> Iterator for StoreIterator<'store> { + type Item = &'store ObjectItem; + + fn next(&mut self) -> Option { + let to_output = unsafe { self.store.get(self.current?) }; + self.current = to_output.z_order.get().next; + Some(to_output) + } +} + +impl Store { + /// SAFETY: while this exists, no other store related operations should be + /// performed. Notably this means you shouldn't drop the ObjectItem as this + /// implementation will touch this. + unsafe fn iter(&self) -> StoreIterator { + StoreIterator { + store: unsafe { &*self.store.get() }, + current: self.first_z.get(), + } + } + + #[cfg(test)] + fn is_all_ordered_right(&self) -> bool { + let mut previous_z = i32::MIN; + let mut current_index = self.first_z.get(); + + while let Some(ci) = current_index { + let obj = self.get_object(ci); + let this_z = obj.z_index.get(); + if this_z < previous_z { + return false; + } + previous_z = this_z; + current_index = obj.z_order.get().next; + } + + true + } + + fn insert_object(&self, object: ObjectUnmanaged) -> Object { + let object_item = ObjectItem { + object: UnsafeCell::new(object), + z_order: Cell::new(Ordering { + next: None, + previous: None, + }), + z_index: Cell::new(0), + }; + let idx = { + let data = unsafe { &mut *self.store.get() }; + unsafe { data.insert(object_item) } + }; + + if let Some(first) = self.first_z.get() { + let mut this_index = first; + while self.get_object(this_index).z_index.get() < 0 { + if let Some(idx) = self.get_object(this_index).z_order.get().next { + this_index = idx; + } else { + break; + } + } + if self.get_object(this_index).z_index.get() < 0 { + add_after_element(self, idx, this_index); + } else { + add_before_element(self, idx, this_index); + } + } else { + self.first_z.set(Some(idx)); + } + + Object { + me: idx, + store: self, + } + } + + fn remove_object(&self, object: ObjectKey) { + remove_from_linked_list(self, object); + + let data = unsafe { &mut *self.store.get() }; + unsafe { data.remove(object) }; + } + + fn get_object(&self, key: ObjectKey) -> &ObjectItem { + unsafe { (*self.store.get()).get(key) } + } +} + +/// OAM that manages z ordering and commit all visible objects in one call. This +/// is simpler to use than the [`OamUnmanaged`], but is less performant +/// depending on how objects are stored. +/// +/// Use this if: +/// * You don't want to handle z ordering. +/// * You don't want to deal with the complexity of committing all objects during vblank. +/// +/// Otherwise I'd recommend using [`OamUnmanaged`]. +pub struct OamManaged<'gba> { + object_store: Store, + sprite_loader: UnsafeCell, + unmanaged: UnsafeCell>, +} + +impl OamManaged<'_> { + pub(crate) fn new() -> Self { + Self { + object_store: Store { + store: UnsafeCell::new(Arena::new()), + first_z: Cell::new(None), + }, + sprite_loader: UnsafeCell::new(SpriteLoader::new()), + unmanaged: UnsafeCell::new(OamUnmanaged::new()), + } + } + + /// SAFETY: + /// Do not reenter or recurse or otherwise use sprite loader cell during this. + unsafe fn do_work_with_sprite_loader(&self, c: C) -> T + where + C: Fn(&mut SpriteLoader) -> T, + { + let sprite_loader = unsafe { &mut *self.sprite_loader.get() }; + + c(sprite_loader) + } + + /// Commits all the visible objects. Call during vblank to make changes made + /// to objects visible. + pub fn commit(&self) { + // safety: commit is not reentrant + let unmanaged = unsafe { &mut *self.unmanaged.get() }; + + for (object, slot) in unsafe { self.object_store.iter() } + .map(|item| unsafe { &*item.object.get() }) + .filter(|object| object.is_visible()) + .zip(unmanaged.iter()) + { + slot.set(object); + } + + // safety: not reentrant + unsafe { + self.do_work_with_sprite_loader(SpriteLoader::garbage_collect); + } + } + + /// Creates an object from the sprite in vram. + pub fn object(&self, sprite: SpriteVram) -> Object<'_> { + self.object_store + .insert_object(ObjectUnmanaged::new(sprite)) + } + + /// Creates a sprite in vram from a static sprite from [`include_aseprite`][crate::include_aseprite]. + pub fn sprite(&self, sprite: &'static Sprite) -> SpriteVram { + // safety: not reentrant + unsafe { + self.do_work_with_sprite_loader(|sprite_loader| sprite_loader.get_vram_sprite(sprite)) + } + } + + /// Creates a sprite in vram and uses it to make an object from a static sprite from [`include_aseprite`][crate::include_aseprite]. + pub fn object_sprite(&self, sprite: &'static Sprite) -> Object<'_> { + self.object(self.sprite(sprite)) + } +} + +/// A managed object used with the [`OamManaged`] interface. +pub struct Object<'controller> { + me: ObjectKey, + store: &'controller Store, +} + +impl Drop for Object<'_> { + fn drop(&mut self) { + self.store.remove_object(self.me); + } +} + +fn remove_from_linked_list(store: &Store, to_remove: ObjectKey) { + let my_current_neighbours = store.get_object(to_remove).z_order.get(); + + if let Some(previous) = my_current_neighbours.previous { + let stored_part = &store.get_object(previous).z_order; + let mut neighbour_left = stored_part.get(); + neighbour_left.next = my_current_neighbours.next; + stored_part.set(neighbour_left); + } else { + store.first_z.set(my_current_neighbours.next); + } + + if let Some(next) = my_current_neighbours.next { + let stored_part = &store.get_object(next).z_order; + let mut neighbour_right = stored_part.get(); + neighbour_right.previous = my_current_neighbours.previous; + stored_part.set(neighbour_right); + } + + store.get_object(to_remove).z_order.set(Ordering { + next: None, + previous: None, + }); +} + +fn add_before_element(store: &Store, elem: ObjectKey, before_this: ObjectKey) { + assert_ne!(elem, before_this); + + let this_element_store = &store.get_object(elem).z_order; + let mut this_element = this_element_store.get(); + + let before_store = &store.get_object(before_this).z_order; + let mut before = before_store.get(); + + if let Some(previous) = before.previous { + let neighbour_left_store = &store.get_object(previous).z_order; + let mut neighbour_left = neighbour_left_store.get(); + neighbour_left.next = Some(elem); + neighbour_left_store.set(neighbour_left); + } else { + store.first_z.set(Some(elem)); + } + this_element.next = Some(before_this); + this_element.previous = before.previous; + + before.previous = Some(elem); + + this_element_store.set(this_element); + before_store.set(before); +} + +fn add_after_element(store: &Store, elem: ObjectKey, after_this: ObjectKey) { + assert_ne!(elem, after_this); + + let this_element_store = &store.get_object(elem).z_order; + let mut this_element = this_element_store.get(); + + let after_store = &store.get_object(after_this).z_order; + let mut after = after_store.get(); + + if let Some(next) = after.next { + let neighbour_left_store = &store.get_object(next).z_order; + let mut neighbour_right = neighbour_left_store.get(); + neighbour_right.previous = Some(elem); + neighbour_left_store.set(neighbour_right); + } + + this_element.previous = Some(after_this); + this_element.next = after.next; + + after.next = Some(elem); + + this_element_store.set(this_element); + after_store.set(after); +} + +fn move_before(store: &Store, source: ObjectKey, before_this: ObjectKey) { + assert_ne!(source, before_this); + + remove_from_linked_list(store, source); + add_before_element(store, source, before_this); +} + +fn move_after(store: &Store, source: ObjectKey, after_this: ObjectKey) { + assert_ne!(source, after_this); + + remove_from_linked_list(store, source); + add_after_element(store, source, after_this); +} + +impl Object<'_> { + /// Sets the z position of an object. This is not a GBA concept. It causes + /// the order of rendering to be different, thus changing whether objects + /// are rendered above eachother. + /// + /// Negative z is more towards the outside and positive z is further into + /// the screen => an object with a more *negative* z is drawn on top of an + /// object with a more *positive* z. + pub fn set_z(&mut self, z_index: i32) -> &mut Self { + let my_object = &self.store.get_object(self.me); + + let order = z_index.cmp(&my_object.z_index.get()); + + match order { + core::cmp::Ordering::Equal => {} + core::cmp::Ordering::Less => { + let mut previous_index = self.me; + let mut current_index = self.me; + while self.store.get_object(current_index).z_index.get() > z_index { + previous_index = current_index; + let previous = self.store.get_object(current_index).z_order.get().previous; + if let Some(previous) = previous { + current_index = previous; + } else { + break; + } + } + if previous_index != self.me { + move_before(self.store, self.me, previous_index); + } + } + core::cmp::Ordering::Greater => { + let mut previous_index = self.me; + let mut current_index = self.me; + while self.store.get_object(current_index).z_index.get() < z_index { + previous_index = current_index; + let next = self.store.get_object(current_index).z_order.get().next; + if let Some(next) = next { + current_index = next; + } else { + break; + } + } + if previous_index != self.me { + move_after(self.store, self.me, previous_index); + } + } + } + + my_object.z_index.set(z_index); + + self + } + + /// Safety: + /// Only have *ONE* of these at a time, do not call any functions that modify the slot map while having this. + unsafe fn object(&mut self) -> &mut ObjectUnmanaged { + unsafe { &mut *self.store.get_object(self.me).object.get() } + } + + /// Safety: + /// Don't have a mutable one of these while having one of these, do not call any functions that modify the slot map while having this. + unsafe fn object_shared(&self) -> &ObjectUnmanaged { + unsafe { &*self.store.get_object(self.me).object.get() } + } + + #[must_use] + /// Checks whether the object is not marked as hidden. Note that it could be + /// off screen or completely transparent and still claimed to be visible. + pub fn is_visible(&self) -> bool { + // safety: only have one of these, doesn't modify slotmap + unsafe { self.object_shared() }.is_visible() + } + + /// Display the sprite in Normal mode. + pub fn show(&mut self) -> &mut Self { + // safety: only have one of these, doesn't modify slotmap + unsafe { self.object().show() }; + + self + } + + /// Display the sprite in Affine mode. + pub fn show_affine(&mut self, affine_mode: AffineMode) -> &mut Self { + // safety: only have one of these, doesn't modify slotmap + unsafe { self.object().show_affine(affine_mode) }; + + self + } + + /// Sets the horizontal flip, note that this only has a visible affect in Normal mode. + pub fn set_hflip(&mut self, flip: bool) -> &mut Self { + // safety: only have one of these, doesn't modify slotmap + unsafe { self.object().set_hflip(flip) }; + + self + } + + /// Sets the vertical flip, note that this only has a visible affect in Normal mode. + pub fn set_vflip(&mut self, flip: bool) -> &mut Self { + // safety: only have one of these, doesn't modify slotmap + unsafe { self.object().set_vflip(flip) }; + + self + } + + /// Sets the priority of the object relative to the backgrounds priority. + pub fn set_priority(&mut self, priority: Priority) -> &mut Self { + // safety: only have one of these, doesn't modify slotmap + unsafe { self.object().set_priority(priority) }; + + self + } + + /// Changes the sprite mode to be hidden, can be changed to Normal or Affine + /// modes using [`show`][Object::show] and + /// [`show_affine`][Object::show_affine] respectively. + pub fn hide(&mut self) -> &mut Self { + // safety: only have one of these, doesn't modify slotmap + unsafe { self.object().hide() }; + + self + } + + /// Sets the x position of the object. + pub fn set_x(&mut self, x: u16) -> &mut Self { + // safety: only have one of these, doesn't modify slotmap + unsafe { self.object().set_x(x) }; + + self + } + + /// Sets the y position of the object. + pub fn set_y(&mut self, y: u16) -> &mut Self { + // safety: only have one of these, doesn't modify slotmap + unsafe { self.object().set_y(y) }; + + self + } + + /// Sets the position of the object. + pub fn set_position(&mut self, position: Vector2D) -> &mut Self { + // safety: only have one of these, doesn't modify slotmap + unsafe { self.object().set_position(position) }; + + self + } + + /// Sets the affine matrix. This only has an affect in Affine mode. + pub fn set_affine_matrix(&mut self, affine_matrix: AffineMatrixInstance) -> &mut Self { + // safety: only have one of these, doesn't modify slotmap + unsafe { self.object().set_affine_matrix(affine_matrix) }; + + self + } + + /// Sets the current sprite for the object. + pub fn set_sprite(&mut self, sprite: SpriteVram) -> &mut Self { + // safety: only have one of these, doesn't modify slotmap + unsafe { self.object().set_sprite(sprite) }; + + self + } +} + +#[cfg(test)] +mod tests { + use alloc::vec::Vec; + + use crate::{display::object::Graphics, include_aseprite}; + + use super::*; + + const TEST_SPRITES: &Graphics = include_aseprite!("examples/gfx/tall.aseprite"); + + const TEST_SPRITE: &Sprite = &TEST_SPRITES.sprites()[0]; + + #[test_case] + fn test_always_ordered(gba: &mut crate::Gba) { + let managed = gba.display.object.get_managed(); + + let sprite = managed.sprite(TEST_SPRITE); + + let mut objects = Vec::new(); + for _ in 0..200 { + let obj = managed.object(sprite.clone()); + objects.push(obj); + } + + for modification_number in 0..10_000 { + let index_to_modify = (crate::rng::gen() as usize) % objects.len(); + let modify_to = crate::rng::gen(); + objects[index_to_modify].set_z(modify_to); + + assert!( + managed.object_store.is_all_ordered_right(), + "objects are unordered after {} modifications. Modified {} to {}.", + modification_number + 1, + index_to_modify, + modify_to + ); + } + } +} diff --git a/agb/src/display/object/sprites.rs b/agb/src/display/object/sprites.rs new file mode 100644 index 00000000..d9b3d9cf --- /dev/null +++ b/agb/src/display/object/sprites.rs @@ -0,0 +1,7 @@ +mod sprite; +mod sprite_allocator; + +const BYTES_PER_TILE_4BPP: usize = 32; + +pub use sprite::{include_aseprite, Graphics, Size, Sprite, Tag, TagMap}; +pub use sprite_allocator::{DynamicSprite, PaletteVram, SpriteLoader, SpriteVram}; diff --git a/agb/src/display/object/sprites/sprite.rs b/agb/src/display/object/sprites/sprite.rs new file mode 100644 index 00000000..6751c47d --- /dev/null +++ b/agb/src/display/object/sprites/sprite.rs @@ -0,0 +1,370 @@ +use core::{alloc::Layout, slice}; + +use crate::display::palette16::Palette16; + +use super::BYTES_PER_TILE_4BPP; + +/// Sprite data. Refers to the palette, pixel data, and the size of the sprite. +pub struct Sprite { + pub(crate) palette: &'static Palette16, + pub(crate) data: &'static [u8], + pub(crate) size: Size, +} + +impl Sprite { + #[doc(hidden)] + /// Creates a sprite from it's constituent data, used internally by + /// [include_aseprite] and should generally not be used outside it. + /// + /// # Safety + /// The data should be aligned to a 2 byte boundary + #[must_use] + pub const unsafe fn new(palette: &'static Palette16, data: &'static [u8], size: Size) -> Self { + Self { + palette, + data, + size, + } + } + + #[must_use] + /// Gives the size of the sprite + pub fn size(&self) -> Size { + self.size + } +} + +/// The sizes of sprite supported by the GBA. +#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)] +#[allow(missing_docs)] +pub enum Size { + // stored as attr0 attr1 + S8x8 = 0b00_00, + S16x16 = 0b00_01, + S32x32 = 0b00_10, + S64x64 = 0b00_11, + + S16x8 = 0b01_00, + S32x8 = 0b01_01, + S32x16 = 0b01_10, + S64x32 = 0b01_11, + + S8x16 = 0b10_00, + S8x32 = 0b10_01, + S16x32 = 0b10_10, + S32x64 = 0b10_11, +} + +#[doc(hidden)] +#[macro_export] +macro_rules! align_bytes { + ($align_ty:ty, $data:literal) => {{ + #[repr(C)] // guarantee 'bytes' comes after '_align' + struct AlignedAs { + pub _align: [Align; 0], + pub bytes: Bytes, + } + + const ALIGNED: &AlignedAs<$align_ty, [u8]> = &AlignedAs { + _align: [], + bytes: *$data, + }; + + &ALIGNED.bytes + }}; +} + +/// Includes sprites found in the referenced aseprite files. Can include +/// multiple at once and optimises palettes of all included in the single call +/// together. See [Size] for supported sizes. Returns a reference to [Graphics]. +/// +/// ```rust,no_run +/// # #![no_std] +/// # #![no_main] +/// # use agb::{display::object::Graphics, include_aseprite}; +/// const GRAPHICS: &Graphics = include_aseprite!( +/// "examples/gfx/boss.aseprite", +/// "examples/gfx/objects.aseprite" +/// ); +/// ``` +/// The tags from the aseprite file are included so you can refer to sprites by +/// name in code. You should ensure tags are unique as this is not enforced by +/// aseprite. +/// +#[macro_export] +macro_rules! include_aseprite { + ($($aseprite_path: expr),*) => {{ + use $crate::display::object::{Size, Sprite, Tag, TagMap, Graphics}; + use $crate::display::palette16::Palette16; + use $crate::align_bytes; + + $crate::include_aseprite_inner!($($aseprite_path),*); + + &Graphics::new(SPRITES, TAGS) + }}; +} + +pub use include_aseprite; + +/// Stores sprite and tag data returned by [include_aseprite]. +pub struct Graphics { + sprites: &'static [Sprite], + tag_map: &'static TagMap, +} + +impl Graphics { + #[doc(hidden)] + /// Creates graphics data from sprite data and a tag_map. This is used + /// internally by [include_aseprite] and would be otherwise difficult to + /// use. + #[must_use] + pub const fn new(sprites: &'static [Sprite], tag_map: &'static TagMap) -> Self { + Self { sprites, tag_map } + } + #[must_use] + /// Gets the tag map from the aseprite files. This allows reference to + /// sprite sequences by name. + pub const fn tags(&self) -> &TagMap { + self.tag_map + } + /// Gets a big list of the sprites themselves. Using tags is often easier. + #[must_use] + pub const fn sprites(&self) -> &[Sprite] { + self.sprites + } +} + +/// Stores aseprite tags. Can be used to refer to animation sequences by name. +/// ```rust,no_run +/// # #![no_std] +/// # #![no_main] +/// # use agb::{display::object::{Graphics, Tag}, include_aseprite}; +/// const GRAPHICS: &Graphics = include_aseprite!( +/// "examples/gfx/boss.aseprite", +/// "examples/gfx/objects.aseprite" +/// ); +/// +/// const EMU_WALK: &Tag = GRAPHICS.tags().get("emu-walk"); +/// ``` +/// This being the whole animation associated with the walk sequence of the emu. +/// See [Tag] for details on how to use this. +pub struct TagMap { + tags: &'static [(&'static str, Tag)], +} + +const fn const_byte_compare(a: &[u8], b: &[u8]) -> bool { + if a.len() != b.len() { + return false; + } + + let mut i = 0; + while i < a.len() { + if a[i] != b[i] { + return false; + } + i += 1; + } + true +} + +impl TagMap { + #[doc(hidden)] + /// Creates a new tag map from (name, Tag) pairs. Used internally by + /// [include_aseprite] and should not really be used outside of it. + #[must_use] + pub const fn new(tags: &'static [(&'static str, Tag)]) -> TagMap { + Self { tags } + } + + #[doc(hidden)] + /// Attempts to get a tag. Generally should not be used. + #[must_use] + pub const fn try_get(&'static self, tag: &str) -> Option<&'static Tag> { + let mut i = 0; + while i < self.tags.len() { + let s = self.tags[i].0; + if const_byte_compare(s.as_bytes(), tag.as_bytes()) { + return Some(&self.tags[i].1); + } + + i += 1; + } + + None + } + + /// Gets a tag associated with the name. A tag in aseprite refers to a + /// sequence of sprites with some metadata for how to animate it. You should + /// call this in a constant context so it is evaluated at compile time. It + /// is inefficient to call this elsewhere. + /// ```rust,no_run + /// # #![no_std] + /// # #![no_main] + /// # use agb::{display::object::{Graphics, Tag}, include_aseprite}; + /// const GRAPHICS: &Graphics = include_aseprite!( + /// "examples/gfx/boss.aseprite", + /// "examples/gfx/objects.aseprite" + /// ); + /// + /// const EMU_WALK: &Tag = GRAPHICS.tags().get("emu-walk"); + /// ``` + /// + /// See [Tag] for more details. + #[must_use] + pub const fn get(&'static self, tag: &str) -> &'static Tag { + let t = self.try_get(tag); + match t { + Some(t) => t, + None => panic!("The requested tag does not exist"), + } + } + + /// Takes an iterator over all the tags in the map. Not generally useful. + pub fn values(&self) -> impl Iterator { + self.tags.iter().map(|x| &x.1) + } +} + +#[derive(Clone, Copy)] +enum Direction { + Forward, + Backward, + PingPong, +} + +impl Direction { + const fn from_usize(a: usize) -> Self { + match a { + 0 => Direction::Forward, + 1 => Direction::Backward, + 2 => Direction::PingPong, + _ => panic!("Invalid direction, this is a bug in image converter or agb"), + } + } +} + +/// A sequence of sprites from aseprite. +pub struct Tag { + sprites: *const Sprite, + len: usize, + direction: Direction, +} + +impl Tag { + /// The individual sprites that make up the animation themselves. + #[must_use] + pub fn sprites(&self) -> &'static [Sprite] { + unsafe { slice::from_raw_parts(self.sprites, self.len) } + } + + /// A single sprite referred to by index in the animation sequence. + #[must_use] + pub const fn sprite(&self, idx: usize) -> &'static Sprite { + if idx >= self.len { + panic!("out of bounds access to sprite"); + } + unsafe { &*self.sprites.add(idx) } + } + + /// A sprite that follows the animation sequence. For instance, in aseprite + /// tags can be specified to animate: + /// * Forward + /// * Backward + /// * Ping pong + /// + /// This takes the animation type in account and returns the correct sprite + /// following these requirements. + #[inline] + #[must_use] + pub fn animation_sprite(&self, idx: usize) -> &'static Sprite { + let len_sub_1 = self.len - 1; + match self.direction { + Direction::Forward => self.sprite(idx % self.len), + Direction::Backward => self.sprite(len_sub_1 - (idx % self.len)), + Direction::PingPong => self.sprite( + (((idx + len_sub_1) % (len_sub_1 * 2)) as isize - len_sub_1 as isize) + .unsigned_abs(), + ), + } + } + + #[doc(hidden)] + /// Creates a new sprite from it's constituent parts. Used internally by + /// [include_aseprite] and should generally not be used elsewhere. + #[must_use] + pub const fn new(sprites: &'static [Sprite], from: usize, to: usize, direction: usize) -> Self { + assert!(from <= to); + assert!(to < sprites.len()); + Self { + sprites: &sprites[from] as *const Sprite, + len: to - from + 1, + direction: Direction::from_usize(direction), + } + } +} + +impl Size { + pub(crate) const fn number_of_tiles(self) -> usize { + match self { + Size::S8x8 => 1, + Size::S16x16 => 4, + Size::S32x32 => 16, + Size::S64x64 => 64, + Size::S16x8 => 2, + Size::S32x8 => 4, + Size::S32x16 => 8, + Size::S64x32 => 32, + Size::S8x16 => 2, + Size::S8x32 => 4, + Size::S16x32 => 8, + Size::S32x64 => 32, + } + } + pub(crate) const fn shape_size(self) -> (u16, u16) { + (self as u16 >> 2, self as u16 & 0b11) + } + + pub(crate) fn layout(self) -> Layout { + Layout::from_size_align(self.number_of_tiles() * BYTES_PER_TILE_4BPP, 8).unwrap() + } + + #[must_use] + /// Creates a size from width and height in pixels, panics if the width and + /// height is not representable by GBA sprites. + pub const fn from_width_height(width: usize, height: usize) -> Self { + match (width, height) { + (8, 8) => Size::S8x8, + (16, 16) => Size::S16x16, + (32, 32) => Size::S32x32, + (64, 64) => Size::S64x64, + (16, 8) => Size::S16x8, + (32, 8) => Size::S32x8, + (32, 16) => Size::S32x16, + (64, 32) => Size::S64x32, + (8, 16) => Size::S8x16, + (8, 32) => Size::S8x32, + (16, 32) => Size::S16x32, + (32, 64) => Size::S32x64, + (_, _) => panic!("Bad width and height!"), + } + } + + #[must_use] + /// Returns the width and height of the size in pixels. + pub const fn to_width_height(self) -> (usize, usize) { + match self { + Size::S8x8 => (8, 8), + Size::S16x16 => (16, 16), + Size::S32x32 => (32, 32), + Size::S64x64 => (64, 64), + Size::S16x8 => (16, 8), + Size::S32x8 => (32, 8), + Size::S32x16 => (32, 16), + Size::S64x32 => (64, 32), + Size::S8x16 => (8, 16), + Size::S8x32 => (8, 32), + Size::S16x32 => (16, 32), + Size::S32x64 => (32, 64), + } + } +} diff --git a/agb/src/display/object/sprites/sprite_allocator.rs b/agb/src/display/object/sprites/sprite_allocator.rs new file mode 100644 index 00000000..ae28f908 --- /dev/null +++ b/agb/src/display/object/sprites/sprite_allocator.rs @@ -0,0 +1,324 @@ +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, + }) +}; + +/// The Sprite Id is a thin wrapper around the pointer to the sprite in +/// rom and is therefore a unique identifier to a sprite +#[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) + } +} + +/// The palette id is a thin wrapper around the pointer to the palette in rom +/// and is therefore a unique reference to a palette +#[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) + } +} + +/// This holds loading of static sprites and palettes. +pub struct SpriteLoader { + static_palette_map: HashMap>, + static_sprite_map: HashMap>, +} + +#[derive(Clone, Copy, Debug)] +struct Location(usize); + +impl Location { + fn from_sprite_ptr(d: NonNull) -> Self { + Self(((d.as_ptr() as usize) - TILE_SPRITE) / BYTES_PER_TILE_4BPP) + } + fn from_palette_ptr(d: NonNull) -> 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()) } + } +} + +/// A palette in vram, this is reference counted so it is cheap to Clone. +#[derive(Debug, Clone)] +pub struct PaletteVram { + data: Rc, +} + +impl PaletteVram { + /// Attempts to allocate a new palette in sprite vram + pub fn new(palette: &Palette16) -> Result { + let allocated = unsafe { PALETTE_ALLOCATOR.alloc(Palette16::layout()) } + .ok_or(LoaderError::PaletteFull)?; + + unsafe { + allocated + .as_ptr() + .cast::() + .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, +} + +/// A sprite that is currently loaded into vram. +/// +/// This is referenced counted such that clones of this are cheap and can be +/// reused between objects. When nothing references the sprite it gets +/// deallocated from vram. +/// +/// You can create one of these either via the [DynamicSprite] interface, which +/// allows you to generate sprites at run time, or via a [SpriteLoader] (or +/// [OamManaged][super::super::OamManaged]). +#[derive(Clone, Debug)] +pub struct SpriteVram { + data: Rc, +} + +impl SpriteVram { + fn new(data: &[u8], size: Size, palette: PaletteVram) -> Result { + 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>, + sprite: &'static Sprite, + ) -> Result<(Weak, 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>, + palette: &'static Palette16, + ) -> Result { + 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 + } + }) + } + + /// Attempts to get a sprite + pub fn try_get_vram_sprite( + &mut self, + sprite: &'static Sprite, + ) -> Result { + // check if we already have the sprite in vram + + 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 + } + }) + } + + /// Attempts to allocate a static palette + pub fn try_get_vram_palette( + &mut self, + palette: &'static Palette16, + ) -> Result { + Self::try_get_vram_palette_asoc(&mut self.static_palette_map, palette) + } + + /// Allocates a sprite to vram, panics if it cannot fit. + pub fn get_vram_sprite(&mut self, sprite: &'static Sprite) -> SpriteVram { + self.try_get_vram_sprite(sprite) + .expect("cannot create sprite") + } + + /// Allocates a palette to vram, panics if it cannot fit. + 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(), + } + } + + /// Remove internal references to sprites that no longer exist in vram. If + /// you neglect calling this, memory will leak over time in relation to the + /// total number of different sprites used. It will not leak vram. + 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() + } +} + +/// Sprite data that can be used to create sprites in vram. +pub struct DynamicSprite<'a> { + data: &'a [u8], + size: Size, +} + +impl DynamicSprite<'_> { + #[must_use] + /// Creates a new dynamic sprite from underlying bytes. Note that despite + /// being an array of u8, this must be aligned to at least a 2 byte + /// boundary. + 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 } + } + + /// Tries to copy the sprite to vram to be used to set object sprites. + pub fn try_vram(&self, palette: PaletteVram) -> Result { + SpriteVram::new(self.data, self.size, palette) + } + + #[must_use] + /// Tries to copy the sprite to vram to be used to set object sprites. + /// Panics if it cannot be allocated. + pub fn to_vram(&self, palette: PaletteVram) -> SpriteVram { + self.try_vram(palette).expect("cannot create sprite") + } +} diff --git a/agb/src/display/object/unmanaged.rs b/agb/src/display/object/unmanaged.rs new file mode 100644 index 00000000..42f609fa --- /dev/null +++ b/agb/src/display/object/unmanaged.rs @@ -0,0 +1,5 @@ +mod attributes; +mod object; + +pub use attributes::AffineMode; +pub use object::{OamIterator, OamSlot, OamUnmanaged, ObjectUnmanaged}; diff --git a/agb/src/display/object/unmanaged/attributes.rs b/agb/src/display/object/unmanaged/attributes.rs new file mode 100644 index 00000000..2ae0d948 --- /dev/null +++ b/agb/src/display/object/unmanaged/attributes.rs @@ -0,0 +1,210 @@ +use modular_bitfield::BitfieldSpecifier; + +use crate::display::Priority; + +use self::attributes::{ + ObjectAttribute0, ObjectAttribute1Affine, ObjectAttribute1Standard, ObjectAttribute2, +}; + +#[derive(PartialEq, Eq, Debug, Clone, Copy)] +pub struct Attributes { + a0: ObjectAttribute0, + a1s: ObjectAttribute1Standard, + a1a: ObjectAttribute1Affine, + a2: ObjectAttribute2, +} + +impl Default for Attributes { + fn default() -> Self { + Self { + a0: ObjectAttribute0::from_bytes([0, 0b10]), + a1s: Default::default(), + a1a: Default::default(), + a2: Default::default(), + } + } +} + +#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] +/// The affine mode +pub enum AffineMode { + /// Normal affine, this is where the area of the affine is equal to the sprite size + Affine = 1, + /// Double affine, this is where the area of the affine is double that of the sprite + AffineDouble = 3, +} + +impl Attributes { + pub fn write(self, ptr: *mut u16) { + let mode = self.a0.object_mode(); + unsafe { + let attrs = core::mem::transmute::<_, [u16; 3]>(match mode { + ObjectMode::Normal => [ + self.a0.into_bytes(), + self.a1s.into_bytes(), + self.a2.into_bytes(), + ], + _ => [ + self.a0.into_bytes(), + self.a1a.into_bytes(), + self.a2.into_bytes(), + ], + }); + + ptr.add(0).write_volatile(attrs[0]); + ptr.add(1).write_volatile(attrs[1]); + ptr.add(2).write_volatile(attrs[2]); + } + } + + pub fn is_visible(self) -> bool { + self.a0.object_mode() != ObjectMode::Disabled + } + + pub fn show(&mut self) -> &mut Self { + self.a0.set_object_mode(ObjectMode::Normal); + + self + } + + pub fn show_affine(&mut self, affine_mode: AffineMode) -> &mut Self { + self.a0.set_object_mode(match affine_mode { + AffineMode::Affine => ObjectMode::Affine, + AffineMode::AffineDouble => ObjectMode::AffineDouble, + }); + + self + } + + pub fn set_hflip(&mut self, flip: bool) -> &mut Self { + self.a1s.set_horizontal_flip(flip); + + self + } + + pub fn set_vflip(&mut self, flip: bool) -> &mut Self { + self.a1s.set_vertical_flip(flip); + + self + } + + pub fn set_x(&mut self, x: u16) -> &mut Self { + self.a1a.set_x(x.rem_euclid(1 << 9)); + self.a1s.set_x(x.rem_euclid(1 << 9)); + + self + } + + pub fn set_priority(&mut self, priority: Priority) -> &mut Self { + self.a2.set_priority(priority); + + self + } + + pub fn hide(&mut self) -> &mut Self { + self.a0.set_object_mode(ObjectMode::Disabled); + + self + } + + pub fn set_y(&mut self, y: u16) -> &mut Self { + self.a0.set_y(y as u8); + + self + } + + pub fn set_palette(&mut self, palette_id: u16) -> &mut Self { + self.a2.set_palette_bank(palette_id as u8); + + self + } + + pub fn set_affine_matrix(&mut self, affine_matrix_id: u16) -> &mut Self { + self.a1a.set_affine_index(affine_matrix_id as u8); + + self + } + + pub fn set_sprite(&mut self, sprite_id: u16, shape: u16, size: u16) -> &mut Self { + self.a2.set_tile_index(sprite_id); + self.a1a.set_size(size as u8); + self.a1s.set_size(size as u8); + self.a0.set_shape(shape as u8); + + self + } +} + +#[derive(BitfieldSpecifier, Clone, Copy, Debug, PartialEq, Eq)] +enum ObjectMode { + Normal, + Affine, + Disabled, + AffineDouble, +} + +#[derive(BitfieldSpecifier, Clone, Copy, Debug, PartialEq, Eq)] +#[bits = 2] +enum GraphicsMode { + Normal, + AlphaBlending, + Window, +} + +#[derive(BitfieldSpecifier, Clone, Copy, Debug, PartialEq, Eq)] +enum ColourMode { + Four, + Eight, +} + +// this mod is not public, so the internal parts don't need documenting. +#[allow(dead_code)] +#[allow(clippy::all)] +#[allow(clippy::map_unwrap_or)] +mod attributes { + use modular_bitfield::{ + bitfield, + specifiers::{B10, B2, B3, B4, B5, B8, B9}, + }; + + use crate::display::Priority; + + use super::*; + #[bitfield] + #[derive(Clone, Copy, PartialEq, Eq, Debug, Default)] + pub(super) struct ObjectAttribute0 { + pub y: B8, + pub object_mode: ObjectMode, + pub graphics_mode: GraphicsMode, + pub mosaic: bool, + pub colour_mode: ColourMode, + pub shape: B2, + } + + #[bitfield] + #[derive(Clone, Copy, PartialEq, Eq, Debug, Default)] + pub(super) struct ObjectAttribute1Standard { + pub x: B9, + #[skip] + __: B3, + pub horizontal_flip: bool, + pub vertical_flip: bool, + pub size: B2, + } + + #[bitfield] + #[derive(Clone, Copy, PartialEq, Eq, Debug, Default)] + pub(super) struct ObjectAttribute1Affine { + pub x: B9, + pub affine_index: B5, + pub size: B2, + } + + #[bitfield] + #[derive(Clone, Copy, PartialEq, Eq, Debug, Default)] + pub(super) struct ObjectAttribute2 { + pub tile_index: B10, + pub priority: Priority, + pub palette_bank: B4, + } +} diff --git a/agb/src/display/object/unmanaged/object.rs b/agb/src/display/object/unmanaged/object.rs new file mode 100644 index 00000000..fb5a42ef --- /dev/null +++ b/agb/src/display/object/unmanaged/object.rs @@ -0,0 +1,375 @@ +use core::{cell::UnsafeCell, marker::PhantomData}; + +use agb_fixnum::Vector2D; +use alloc::vec::Vec; + +use crate::display::{ + object::{ + affine::AffineMatrixVram, sprites::SpriteVram, AffineMatrixInstance, + OBJECT_ATTRIBUTE_MEMORY, + }, + Priority, +}; + +use super::attributes::{AffineMode, Attributes}; + +#[derive(Debug)] +struct OamFrameModifyables { + this_frame_sprites: Vec, + frame: u32, + affine_matrix_count: u32, + previous_index: usize, +} + +/// This handles the unmanaged oam system which gives more control to the OAM slots. +/// This is utilised by calling the iter function and writing objects to those slots. +pub struct OamUnmanaged<'gba> { + phantom: PhantomData<&'gba ()>, + frame_data: UnsafeCell, + previous_frame_sprites: Vec, +} + +/// The iterator over the OAM slots. Dropping this will finalise the frame. To +/// use, iterate over and write to each slot. +/// +/// For example, it could look like this: +/// +/// ```no_run +/// # #![no_main] +/// # #![no_std] +/// use agb::display::object::{OamIterator, ObjectUnmanaged}; +/// +/// fn write_to_oam(oam_iterator: OamIterator, objects: &[ObjectUnmanaged]) { +/// for (object, slot) in objects.iter().zip(oam_iterator) { +/// slot.set(&object); +/// } +/// } +/// ``` +/// +/// # Pitfalls +/// You *must* use each OamSlot you obtain, this can be an issue if instead of +/// the above you write +/// +/// ```no_run +/// # #![no_main] +/// # #![no_std] +/// use agb::display::object::{OamIterator, ObjectUnmanaged}; +/// +/// fn write_to_oam(oam_iterator: OamIterator, objects: &[ObjectUnmanaged]) { +/// for (slot, object) in oam_iterator.zip(objects.iter()) { +/// slot.set(&object); +/// } +/// } +/// ``` +/// +/// This will panic if called because when you run out of objects the zip will +/// have already grabbed the next OamSlot before realising there are no more +/// objects. + +pub struct OamIterator<'oam> { + index: usize, + frame_data: &'oam UnsafeCell, +} + +/// A slot in Oam that you can write to. Note that you must call [OamSlot::set] +/// or else it is a bug and will panic when dropped. +/// +/// See [`OamIterator`] for potential pitfalls. +pub struct OamSlot<'oam> { + slot: usize, + frame_data: &'oam UnsafeCell, +} + +impl Drop for OamSlot<'_> { + #[track_caller] + fn drop(&mut self) { + panic!("Dropping an OamSlot is a bug in your code. Use the slot by calling set (this consumes the slot) or don't obtain one. See documentation for notes on potential pitfalls.") + } +} + +impl OamSlot<'_> { + /// Set the slot in OAM to contain the sprite given. + #[inline(always)] + pub fn set(self, object: &ObjectUnmanaged) { + self.set_inner(object); + + // don't call the drop implementation. + // okay as none of the fields we have have drop implementations. + core::mem::forget(self); + } + + /// By writing these as two separate functions, one inlined and one not, the + /// compiler doesn't have to copy around the slot structure while still + /// keeping move semantics. This is slightly faster in benchmarks. + #[inline(never)] + fn set_inner(&self, object: &ObjectUnmanaged) { + let mut attributes = object.attributes; + // SAFETY: This function is not reentrant and we currently hold a mutable borrow of the [UnmanagedOAM]. + let frame_data = unsafe { &mut *self.frame_data.get() }; + + if let Some(affine_matrix) = &object.affine_matrix { + Self::handle_affine(&mut attributes, frame_data, affine_matrix); + } + attributes.write(unsafe { (OBJECT_ATTRIBUTE_MEMORY as *mut u16).add(self.slot * 4) }); + + frame_data.this_frame_sprites.push(object.sprite.clone()); + } + + fn handle_affine( + attributes: &mut Attributes, + frame_data: &mut OamFrameModifyables, + affine_matrix: &AffineMatrixVram, + ) { + if affine_matrix.frame_count() != frame_data.frame { + affine_matrix.set_frame_count(frame_data.frame); + assert!( + frame_data.affine_matrix_count <= 32, + "too many affine matricies in one frame" + ); + affine_matrix.set_location(frame_data.affine_matrix_count); + frame_data.affine_matrix_count += 1; + affine_matrix.write_to_location(OBJECT_ATTRIBUTE_MEMORY); + } + + attributes.set_affine_matrix(affine_matrix.location() as u16); + } +} + +impl<'oam> Iterator for OamIterator<'oam> { + type Item = OamSlot<'oam>; + + #[inline(always)] + fn next(&mut self) -> Option { + let idx = self.index; + if idx == 128 { + None + } else { + self.index += 1; + Some(OamSlot { + slot: idx, + frame_data: self.frame_data, + }) + } + } +} + +impl Drop for OamIterator<'_> { + fn drop(&mut self) { + let number_writen = self.index; + let last_frame_written = unsafe { &mut (*self.frame_data.get()).previous_index }; + + for idx in number_writen..*last_frame_written { + unsafe { + let ptr = (OBJECT_ATTRIBUTE_MEMORY as *mut u16).add(idx * 4); + ptr.write_volatile(0b10 << 8); + } + } + *last_frame_written = number_writen; + } +} + +impl OamUnmanaged<'_> { + /// Returns the OamSlot iterator for this frame. + pub fn iter(&mut self) -> OamIterator<'_> { + let frame_data = self.frame_data.get_mut(); + frame_data.frame = frame_data.frame.wrapping_add(1); + frame_data.affine_matrix_count = 0; + + // We drain the previous frame sprites here to reuse the Vecs allocation and remove the now unused sprites. + // Any sprites currently being shown will now be put in the new Vec. + self.previous_frame_sprites.clear(); + core::mem::swap( + &mut frame_data.this_frame_sprites, + &mut self.previous_frame_sprites, + ); + + OamIterator { + index: 0, + frame_data: &self.frame_data, + } + } + + pub(crate) fn new() -> Self { + Self { + frame_data: UnsafeCell::new(OamFrameModifyables { + this_frame_sprites: Vec::new(), + frame: 0, + affine_matrix_count: 0, + previous_index: 0, + }), + phantom: PhantomData, + previous_frame_sprites: Default::default(), + } + } +} + +#[derive(Debug, Clone)] +/// An object to be used by the [`OamUnmanaged`] system. Changes made here are +/// reflected when set to an OamSlot using [`OamSlot::set`]. +pub struct ObjectUnmanaged { + attributes: Attributes, + sprite: SpriteVram, + affine_matrix: Option, +} + +impl ObjectUnmanaged { + #[must_use] + /// Creates an unmanaged object from a sprite in vram. + pub fn new(sprite: SpriteVram) -> Self { + let sprite_location = sprite.location(); + let palette_location = sprite.palette_location(); + let (shape, size) = sprite.size().shape_size(); + + let mut sprite = Self { + attributes: Attributes::default(), + sprite, + affine_matrix: None, + }; + + sprite.attributes.set_sprite(sprite_location, shape, size); + sprite.attributes.set_palette(palette_location); + + sprite + } + + #[must_use] + /// Checks whether the object is not marked as hidden. Note that it could be + /// off screen or completely transparent and still claimed to be visible. + pub fn is_visible(&self) -> bool { + self.attributes.is_visible() + } + + /// Display the sprite in Normal mode. + pub fn show(&mut self) -> &mut Self { + self.attributes.show(); + + self + } + + /// Display the sprite in Affine mode. + pub fn show_affine(&mut self, affine_mode: AffineMode) -> &mut Self { + assert!( + self.affine_matrix.is_some(), + "affine matrix must be set before enabling affine matrix!" + ); + + self.attributes.show_affine(affine_mode); + + self + } + + /// Sets the horizontal flip, note that this only has a visible affect in Normal mode. + pub fn set_hflip(&mut self, flip: bool) -> &mut Self { + self.attributes.set_hflip(flip); + + self + } + + /// Sets the vertical flip, note that this only has a visible affect in Normal mode. + pub fn set_vflip(&mut self, flip: bool) -> &mut Self { + self.attributes.set_vflip(flip); + + self + } + + /// Sets the priority of the object relative to the backgrounds priority. + pub fn set_priority(&mut self, priority: Priority) -> &mut Self { + self.attributes.set_priority(priority); + + self + } + + /// Changes the sprite mode to be hidden, can be changed to Normal or Affine + /// modes using [`show`][ObjectUnmanaged::show] and + /// [`show_affine`][ObjectUnmanaged::show_affine] respectively. + pub fn hide(&mut self) -> &mut Self { + self.attributes.hide(); + + self + } + + /// Sets the x position of the object. + pub fn set_x(&mut self, x: u16) -> &mut Self { + self.attributes.set_x(x); + + self + } + + /// Sets the y position of the object. + pub fn set_y(&mut self, y: u16) -> &mut Self { + self.attributes.set_y(y); + + self + } + + /// Sets the position of the object. + pub fn set_position(&mut self, position: Vector2D) -> &mut Self { + self.set_y(position.y.rem_euclid(1 << 9) as u16); + self.set_x(position.x.rem_euclid(1 << 9) as u16); + + self + } + + /// Sets the affine matrix. This only has an affect in Affine mode. + pub fn set_affine_matrix(&mut self, affine_matrix: AffineMatrixInstance) -> &mut Self { + let vram = affine_matrix.vram(); + self.affine_matrix = Some(vram); + + self + } + + fn set_sprite_attributes(&mut self, sprite: &SpriteVram) -> &mut Self { + let size = sprite.size(); + let (shape, size) = size.shape_size(); + + self.attributes.set_sprite(sprite.location(), shape, size); + self.attributes.set_palette(sprite.palette_location()); + + self + } + + /// Sets the current sprite for the object. + pub fn set_sprite(&mut self, sprite: SpriteVram) -> &mut Self { + self.set_sprite_attributes(&sprite); + + self.sprite = sprite; + + self + } +} + +#[cfg(test)] +mod tests { + use crate::{ + display::object::{Graphics, Tag}, + include_aseprite, + }; + + use super::*; + + #[test_case] + fn object_usage(gba: &mut crate::Gba) { + const GRAPHICS: &Graphics = include_aseprite!( + "../examples/the-purple-night/gfx/objects.aseprite", + "../examples/the-purple-night/gfx/boss.aseprite" + ); + + const BOSS: &Tag = GRAPHICS.tags().get("Boss"); + + let (mut gfx, mut loader) = gba.display.object.get_unmanaged(); + + { + let mut slotter = gfx.iter(); + + let slot_a = slotter.next().unwrap(); + let slot_b = slotter.next().unwrap(); + + let mut obj = ObjectUnmanaged::new(loader.get_vram_sprite(BOSS.sprite(2))); + + obj.show(); + + slot_b.set(&obj); + slot_a.set(&obj); + } + } +} diff --git a/agb/src/display/palette16.rs b/agb/src/display/palette16.rs index 6a4441ba..7296b5bd 100644 --- a/agb/src/display/palette16.rs +++ b/agb/src/display/palette16.rs @@ -1,3 +1,5 @@ +use core::alloc::Layout; + #[repr(C)] #[derive(Clone)] pub struct Palette16 { @@ -12,7 +14,6 @@ impl Palette16 { // Clippy bug: claims that index is only used in recursion. I can't reproduce in // other examples, even just copy pasting this struct and impl into a blank project :/ - #[allow(clippy::only_used_in_recursion)] pub fn update_colour(&mut self, index: usize, colour: u16) { self.colours[index] = colour; } @@ -21,4 +22,8 @@ impl Palette16 { pub fn colour(&self, index: usize) -> u16 { self.colours[index] } + + pub(crate) const fn layout() -> Layout { + Layout::new::() + } } diff --git a/agb/src/lib.rs b/agb/src/lib.rs index effd589f..cda67dae 100644 --- a/agb/src/lib.rs +++ b/agb/src/lib.rs @@ -165,6 +165,8 @@ pub mod syscall; /// Interactions with the internal timers pub mod timer; +pub(crate) mod arena; + pub use {agb_alloc::ExternalAllocator, agb_alloc::InternalAllocator}; #[cfg(not(any(test, feature = "testing")))] diff --git a/book/games/pong/Cargo.lock b/book/games/pong/Cargo.lock index cff08e4b..36dbf3d2 100644 --- a/book/games/pong/Cargo.lock +++ b/book/games/pong/Cargo.lock @@ -76,11 +76,11 @@ dependencies = [ [[package]] name = "ahash" -version = "0.7.6" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" dependencies = [ - "getrandom", + "cfg-if", "once_cell", "version_check", ] @@ -178,30 +178,19 @@ dependencies = [ [[package]] name = "fontdue" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a62391ecb864cf12ed06b2af4eda2e609b97657950d6a8f06841b17726ab253" +checksum = "0793f5137567643cf65ea42043a538804ff0fbf288649e2141442b602d81f9bc" dependencies = [ "hashbrown", "ttf-parser", ] -[[package]] -name = "getrandom" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" -dependencies = [ - "cfg-if", - "libc", - "wasi", -] - [[package]] name = "hashbrown" -version = "0.11.2" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" dependencies = [ "ahash", ] @@ -227,12 +216,6 @@ dependencies = [ "png", ] -[[package]] -name = "libc" -version = "0.2.141" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5" - [[package]] name = "log" version = "0.4.17" @@ -422,9 +405,3 @@ name = "version_check" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" - -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" diff --git a/book/games/pong/src/main.rs b/book/games/pong/src/main.rs index 73916baa..8e7182f2 100644 --- a/book/games/pong/src/main.rs +++ b/book/games/pong/src/main.rs @@ -32,7 +32,7 @@ const BALL: &Tag = GRAPHICS.tags().get("Ball"); #[agb::entry] fn main(mut gba: agb::Gba) -> ! { // Get the OAM manager - let object = gba.display.object.get(); + let object = gba.display.object.get_managed(); // Create an object with the ball sprite let mut ball = object.object_sprite(BALL.sprite(0)); diff --git a/examples/amplitude/.cargo/config.toml b/examples/amplitude/.cargo/config.toml new file mode 100644 index 00000000..b3276236 --- /dev/null +++ b/examples/amplitude/.cargo/config.toml @@ -0,0 +1,14 @@ +[unstable] +build-std = ["core", "alloc"] +build-std-features = ["compiler-builtins-mem"] + +[build] +target = "thumbv4t-none-eabi" + +[target.thumbv4t-none-eabi] +rustflags = ["-Clink-arg=-Tgba.ld", "-Ctarget-cpu=arm7tdmi"] +runner = "mgba-qt" + +[target.armv4t-none-eabi] +rustflags = ["-Clink-arg=-Tgba.ld", "-Ctarget-cpu=arm7tdmi"] +runner = "mgba-qt" diff --git a/examples/amplitude/Cargo.lock b/examples/amplitude/Cargo.lock new file mode 100644 index 00000000..dd6fd268 --- /dev/null +++ b/examples/amplitude/Cargo.lock @@ -0,0 +1,407 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "adler32" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" + +[[package]] +name = "agb" +version = "0.14.0" +dependencies = [ + "agb_fixnum", + "agb_hashmap", + "agb_image_converter", + "agb_macros", + "agb_sound_converter", + "bare-metal", + "bitflags 2.1.0", + "modular-bitfield", + "rustc-hash", +] + +[[package]] +name = "agb_fixnum" +version = "0.14.0" +dependencies = [ + "agb_macros", +] + +[[package]] +name = "agb_hashmap" +version = "0.14.0" +dependencies = [ + "rustc-hash", +] + +[[package]] +name = "agb_image_converter" +version = "0.14.0" +dependencies = [ + "asefile", + "fontdue", + "image", + "proc-macro2", + "quote", + "syn 2.0.15", +] + +[[package]] +name = "agb_macros" +version = "0.14.0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.15", +] + +[[package]] +name = "agb_sound_converter" +version = "0.14.0" +dependencies = [ + "hound", + "proc-macro2", + "quote", + "syn 2.0.15", +] + +[[package]] +name = "ahash" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", +] + +[[package]] +name = "amplitude" +version = "0.1.0" +dependencies = [ + "agb", +] + +[[package]] +name = "asefile" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a71de7aecd2d0a76ec90fde2c443d12667c737d92de76bd187f101eca37891" +dependencies = [ + "bitflags 1.3.2", + "byteorder", + "flate2", + "image", + "log", + "nohash", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bare-metal" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fe8f5a8a398345e52358e18ff07cc17a568fbca5c6f73873d3a62056309603" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c70beb79cbb5ce9c4f8e20849978f34225931f665bb49efa6982875a4d5facb3" + +[[package]] +name = "bytemuck" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17febce684fd15d89027105661fec94afb475cb995fbc59d2865198446ba2eea" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "deflate" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73770f8e1fe7d64df17ca66ad28994a0a623ea497fa69486e14984e715c5d174" +dependencies = [ + "adler32", + "byteorder", +] + +[[package]] +name = "flate2" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841" +dependencies = [ + "crc32fast", + "miniz_oxide 0.6.2", +] + +[[package]] +name = "fontdue" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0793f5137567643cf65ea42043a538804ff0fbf288649e2141442b602d81f9bc" +dependencies = [ + "hashbrown", + "ttf-parser", +] + +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +dependencies = [ + "ahash", +] + +[[package]] +name = "hound" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d13cdbd5dbb29f9c88095bbdc2590c9cba0d0a1269b983fef6b2cdd7e9f4db1" + +[[package]] +name = "image" +version = "0.23.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24ffcb7e7244a9bf19d35bf2883b9c080c4ced3c07a9895572178cdb8f13f6a1" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "num-iter", + "num-rational", + "num-traits", + "png", +] + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "miniz_oxide" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435" +dependencies = [ + "adler32", +] + +[[package]] +name = "miniz_oxide" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" +dependencies = [ + "adler", +] + +[[package]] +name = "modular-bitfield" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a53d79ba8304ac1c4f9eb3b9d281f21f7be9d4626f72ce7df4ad8fbde4f38a74" +dependencies = [ + "modular-bitfield-impl", + "static_assertions", +] + +[[package]] +name = "modular-bitfield-impl" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a7d5f7076603ebc68de2dc6a650ec331a062a13abaa346975be747bbfa4b789" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "nohash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0f889fb66f7acdf83442c35775764b51fed3c606ab9cee51500dbde2cf528ca" + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" + +[[package]] +name = "png" +version = "0.16.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c3287920cb847dee3de33d301c463fba14dda99db24214ddf93f83d3021f4c6" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "deflate", + "miniz_oxide 0.3.7", +] + +[[package]] +name = "proc-macro2" +version = "1.0.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "ttf-parser" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b3e06c9b9d80ed6b745c7159c40b311ad2916abb34a49e9be2653b90db0d8dd" + +[[package]] +name = "unicode-ident" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" diff --git a/examples/amplitude/Cargo.toml b/examples/amplitude/Cargo.toml new file mode 100644 index 00000000..470d0c45 --- /dev/null +++ b/examples/amplitude/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "amplitude" +version = "0.1.0" +authors = [""] +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +agb = { version = "0.14.0", path = "../../agb" } + +[profile.dev] +opt-level = 2 +debug = true + +[profile.release] +panic = "abort" +lto = true +debug = true +codegen-units = 1 diff --git a/examples/amplitude/README.md b/examples/amplitude/README.md new file mode 100644 index 00000000..2d488275 --- /dev/null +++ b/examples/amplitude/README.md @@ -0,0 +1,72 @@ +# AGBRS template + +## A basic template example for agb projects + +This makes getting started with a new project for the Game Boy Advance in rust really simple, by providing +all the boiler plate files for you. + +## Building + +### Prerequisites + +You will need the following installed in order to build and run this project: + +* A recent version of `rustup`. See the [rust website](https://www.rust-lang.org/tools/install) for instructions for your operating system +* `arm-none-eabi-binutils` for assembling and linking + * Windows: [GNU Arm Embedded Toolchain](https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-rm/downloads). + Make sure you select "Add path to environment variable" during the install + * Debian and derivatives (e.g. Ubuntu, raspberry pi OS, linux mint): `sudo apt install binutils-arm-none-eabi` + * Arch linux and derivatives: `sudo pacman -S arm-none-eabi-binutils` + +You will also want to install an emulator. The best support in agb is with [mgba](https://mgba.io), with +`println!` support via `agb::println!` but any emulator should work. You'll get the best experience if +`mgba-qt` is in your `PATH`. + +If you want to run your game on real hardware, you will also need to install `agb-gbafix` which you can do after installing +rust with the following: `cargo install agb-gbafix`. This is not required if you are only running your game in an emulator. + +### Running in an emulator + +Once you have the prerequisites installed, you should be able to build using + +```sh +cargo build +``` + +or in release mode (recommended for the final version to ship to players) + +```sh +cargo build --release +``` + +The resulting file will be in `target/thumbv4t-none-eabi/debug/` or `target/thumbv4t-none-eabi/release/` depending on +whether you did a release or debug build. + +If you have `mgba-qt` in your path, you will be able to run your game with + +```sh +cargo run +``` + +or in release mode + +```sh +cargo run --release +``` + +## Starting development + +You can find the documentation for agb [here](https://docs.rs/agb/latest/agb/). + +You may also want to change the package name and version in `Cargo.toml` before you start. + +## Shipping a .gba file for real hardware + +To make a game run on real hardware, you will need to convert the built file into a file suitable for +running on the real thing. + +First build the binary in release mode using the instructions above, then do the following: + +```sh +agb-gbafix target/thumbv4t-none-eabi/release/ -o .gba +``` \ No newline at end of file diff --git a/examples/amplitude/gba.ld b/examples/amplitude/gba.ld new file mode 100644 index 00000000..4190e45b --- /dev/null +++ b/examples/amplitude/gba.ld @@ -0,0 +1,117 @@ +OUTPUT_FORMAT("elf32-littlearm", "elf32-bigarm", "elf32-littlearm") +OUTPUT_ARCH(arm) + +ENTRY(__start) +EXTERN(__RUST_INTERRUPT_HANDLER) + +EXTERN(__agbabi_memset) +EXTERN(__agbabi_memcpy) + +MEMORY { + ewram (w!x) : ORIGIN = 0x02000000, LENGTH = 256K + iwram (w!x) : ORIGIN = 0x03000000, LENGTH = 32K + rom (rx) : ORIGIN = 0x08000000, LENGTH = 32M +} + +__text_start = ORIGIN(rom); + +INPUT (agb.a) + +SECTIONS { + . = __text_start; + + + .text : { + KEEP(*(.crt0)); + *(.crt0 .crt0*); + *(.text .text*); + . = ALIGN(4); + } > rom + __text_end = .; + + .rodata : { + *(.rodata .rodata.*); + . = ALIGN(4); + } > rom + + __iwram_rom_start = .; + .iwram : { + __iwram_data_start = ABSOLUTE(.); + + *(.iwram .iwram.*); + . = ALIGN(4); + + *(.text_iwram .text_iwram.*); + . = ALIGN(4); + + __iwram_data_end = ABSOLUTE(.); + } > iwram AT>rom + + . = __iwram_rom_start + (__iwram_data_end - __iwram_data_start); + + __ewram_rom_start = .; + .ewram : { + __ewram_data_start = ABSOLUTE(.); + + *(.ewram .ewram.*); + . = ALIGN(4); + + *(.data .data.*); + . = ALIGN(4); + + __ewram_data_end = ABSOLUTE(.); + } > ewram AT>rom + + .bss : { + *(.bss .bss.*); + . = ALIGN(4); + __iwram_end = ABSOLUTE(.); + } > iwram + + __iwram_rom_length_bytes = __iwram_data_end - __iwram_data_start; + __iwram_rom_length_halfwords = (__iwram_rom_length_bytes + 1) / 2; + + __ewram_rom_length_bytes = __ewram_data_end - __ewram_data_start; + __ewram_rom_length_halfwords = (__ewram_rom_length_bytes + 1) / 2; + + .shstrtab : { + *(.shstrtab) + } + + /* debugging sections */ + /* Stabs */ + .stab 0 : { *(.stab) } + .stabstr 0 : { *(.stabstr) } + .stab.excl 0 : { *(.stab.excl) } + .stab.exclstr 0 : { *(.stab.exclstr) } + .stab.index 0 : { *(.stab.index) } + .stab.indexstr 0 : { *(.stab.indexstr) } + .comment 0 : { *(.comment) } + /* DWARF 1 */ + .debug 0 : { *(.debug) } + .line 0 : { *(.line) } + /* GNU DWARF 1 extensions */ + .debug_srcinfo 0 : { *(.debug_srcinfo) } + .debug_sfnames 0 : { *(.debug_sfnames) } + /* DWARF 1.1 and DWARF 2 */ + .debug_aranges 0 : { *(.debug_aranges) } + .debug_pubnames 0 : { *(.debug_pubnames) } + /* DWARF 2 */ + .debug_info 0 : { *(.debug_info) } + .debug_abbrev 0 : { *(.debug_abbrev) } + .debug_line 0 : { *(.debug_line) } + .debug_frame 0 : { *(.debug_frame) } + .debug_str 0 : { *(.debug_str) } + .debug_loc 0 : { *(.debug_loc) } + .debug_macinfo 0 : { *(.debug_macinfo) } + /* SGI/MIPS DWARF 2 extensions */ + .debug_weaknames 0 : { *(.debug_weaknames) } + .debug_funcnames 0 : { *(.debug_funcnames) } + .debug_typenames 0 : { *(.debug_typenames) } + .debug_varnames 0 : { *(.debug_varnames) } + + .debug_ranges 0 : { *(.debug_ranges) } + + /* discard anything not already mentioned */ + /DISCARD/ : { *(*) } +} \ No newline at end of file diff --git a/examples/amplitude/gba_mb.ld b/examples/amplitude/gba_mb.ld new file mode 100644 index 00000000..0f3ef446 --- /dev/null +++ b/examples/amplitude/gba_mb.ld @@ -0,0 +1,115 @@ +OUTPUT_FORMAT("elf32-littlearm", "elf32-bigarm", "elf32-littlearm") +OUTPUT_ARCH(arm) + +ENTRY(__start) +EXTERN(__RUST_INTERRUPT_HANDLER) + +EXTERN(__agbabi_memset) +EXTERN(__agbabi_memcpy) + +MEMORY { + ewram (w!x) : ORIGIN = 0x02000000, LENGTH = 256K + iwram (w!x) : ORIGIN = 0x03000000, LENGTH = 32K +} + +__text_start = ORIGIN(ewram); + +INPUT (agb.a) + +SECTIONS { + . = __text_start; + + .text : { + KEEP(*(.crt0)); + *(.crt0 .crt0*); + *(.text .text*); + . = ALIGN(4); + } > rom + __text_end = .; + + .rodata : { + *(.rodata .rodata.*); + . = ALIGN(4); + } > ewram + + __iwram_rom_start = .; + .iwram : { + __iwram_data_start = ABSOLUTE(.); + + *(.iwram .iwram.*); + . = ALIGN(4); + + *(.text_iwram .text_iwram.*); + . = ALIGN(4); + + __iwram_data_end = ABSOLUTE(.); + } > iwram AT>ewram + + . = __iwram_rom_start + (__iwram_data_end - __iwram_data_start); + + __ewram_rom_start = .; + .ewram : { + __ewram_data_start = ABSOLUTE(.); + + *(.ewram .ewram.*); + . = ALIGN(4); + + *(.data .data.*); + . = ALIGN(4); + + __ewram_data_end = ABSOLUTE(.); + } > ewram AT>ewram + + .bss : { + *(.bss .bss.*); + . = ALIGN(4); + __iwram_end = ABSOLUTE(.); + } > iwram + + __iwram_rom_length_bytes = __iwram_data_end - __iwram_data_start; + __iwram_rom_length_halfwords = (__iwram_rom_length_bytes + 1) / 2; + + __ewram_rom_length_bytes = __ewram_data_end - __ewram_data_start; + __ewram_rom_length_halfwords = (__ewram_rom_length_bytes + 1) / 2; + + .shstrtab : { + *(.shstrtab) + } + + /* debugging sections */ + /* Stabs */ + .stab 0 : { *(.stab) } + .stabstr 0 : { *(.stabstr) } + .stab.excl 0 : { *(.stab.excl) } + .stab.exclstr 0 : { *(.stab.exclstr) } + .stab.index 0 : { *(.stab.index) } + .stab.indexstr 0 : { *(.stab.indexstr) } + .comment 0 : { *(.comment) } + /* DWARF 1 */ + .debug 0 : { *(.debug) } + .line 0 : { *(.line) } + /* GNU DWARF 1 extensions */ + .debug_srcinfo 0 : { *(.debug_srcinfo) } + .debug_sfnames 0 : { *(.debug_sfnames) } + /* DWARF 1.1 and DWARF 2 */ + .debug_aranges 0 : { *(.debug_aranges) } + .debug_pubnames 0 : { *(.debug_pubnames) } + /* DWARF 2 */ + .debug_info 0 : { *(.debug_info) } + .debug_abbrev 0 : { *(.debug_abbrev) } + .debug_line 0 : { *(.debug_line) } + .debug_frame 0 : { *(.debug_frame) } + .debug_str 0 : { *(.debug_str) } + .debug_loc 0 : { *(.debug_loc) } + .debug_macinfo 0 : { *(.debug_macinfo) } + /* SGI/MIPS DWARF 2 extensions */ + .debug_weaknames 0 : { *(.debug_weaknames) } + .debug_funcnames 0 : { *(.debug_funcnames) } + .debug_typenames 0 : { *(.debug_typenames) } + .debug_varnames 0 : { *(.debug_varnames) } + + .debug_ranges 0 : { *(.debug_ranges) } + + /* discard anything not already mentioned */ + /DISCARD/ : { *(*) } +} \ No newline at end of file diff --git a/examples/amplitude/gfx/circles.aseprite b/examples/amplitude/gfx/circles.aseprite new file mode 100644 index 00000000..3282aaca Binary files /dev/null and b/examples/amplitude/gfx/circles.aseprite differ diff --git a/examples/amplitude/gfx/numbers.aseprite b/examples/amplitude/gfx/numbers.aseprite new file mode 100644 index 00000000..d839e9de Binary files /dev/null and b/examples/amplitude/gfx/numbers.aseprite differ diff --git a/examples/amplitude/gfx/saw.aseprite b/examples/amplitude/gfx/saw.aseprite new file mode 100644 index 00000000..a92d5c26 Binary files /dev/null and b/examples/amplitude/gfx/saw.aseprite differ diff --git a/examples/amplitude/rust-toolchain.toml b/examples/amplitude/rust-toolchain.toml new file mode 100644 index 00000000..6e1f5327 --- /dev/null +++ b/examples/amplitude/rust-toolchain.toml @@ -0,0 +1,3 @@ +[toolchain] +channel = "nightly" +components = ["rust-src", "clippy", "rustfmt"] diff --git a/examples/amplitude/src/lib.rs b/examples/amplitude/src/lib.rs new file mode 100644 index 00000000..8c145abb --- /dev/null +++ b/examples/amplitude/src/lib.rs @@ -0,0 +1,370 @@ +#![no_std] +#![no_main] +#![cfg_attr(test, feature(custom_test_frameworks))] +#![cfg_attr(test, reexport_test_harness_main = "test_main")] +#![cfg_attr(test, test_runner(agb::test_runner::test_runner))] +#![deny(clippy::all)] + +extern crate alloc; + +use agb::{ + display::{ + self, + affine::AffineMatrix, + object::{ + AffineMatrixInstance, AffineMode, Graphics, OamIterator, ObjectUnmanaged, Sprite, + SpriteLoader, SpriteVram, Tag, + }, + palette16::Palette16, + }, + fixnum::{num, Num, Vector2D}, + include_aseprite, + input::{Button, ButtonController}, + rng, +}; +use alloc::{boxed::Box, collections::VecDeque, vec::Vec}; + +type Number = Num; + +struct Saw { + object: ObjectUnmanaged, + position: Vector2D, + angle: Number, + rotation_speed: Number, +} + +enum Colour { + Red, + Blue, +} + +struct Circle { + colour: Colour, + position: Vector2D, +} + +#[derive(Clone)] +struct SpriteCache { + saw: SpriteVram, + blue: SpriteVram, + red: SpriteVram, + numbers: Box<[SpriteVram]>, +} + +#[derive(Clone, Copy, PartialEq, Eq)] +enum DrawDirection { + Left, + Right, +} + +fn draw_number( + mut number: u32, + position: Vector2D, + oam: &mut OamIterator, + direction: DrawDirection, + sprite_cache: &SpriteCache, +) -> Option<()> { + let mut digits = Vec::new(); + if number == 0 { + digits.push(0); + } + + while number != 0 { + digits.push(number % 10); + number /= 10; + } + + let mut current_position = if direction == DrawDirection::Right { + position + (4 * (digits.len() - 1) as i32, 0).into() + } else { + position + }; + + for digit in digits { + let mut obj = ObjectUnmanaged::new(sprite_cache.numbers[digit as usize].clone()); + obj.show().set_position(current_position); + + oam.next()?.set(&obj); + + current_position -= (4, 0).into(); + } + + Some(()) +} + +impl SpriteCache { + fn new(loader: &mut SpriteLoader) -> Self { + const SPRITES: &Graphics = include_aseprite!( + "gfx/circles.aseprite", + "gfx/saw.aseprite", + "gfx/numbers.aseprite" + ); + + const NUMBERS: &Tag = SPRITES.tags().get("numbers"); + const BLUE_CIRCLE: &Sprite = SPRITES.tags().get("Blue").sprite(0); + const RED_CIRCLE: &Sprite = SPRITES.tags().get("Red").sprite(0); + const SAW: &Sprite = SPRITES.tags().get("Saw").sprite(0); + + Self { + saw: loader.get_vram_sprite(SAW), + blue: loader.get_vram_sprite(BLUE_CIRCLE), + red: loader.get_vram_sprite(RED_CIRCLE), + numbers: (0..10) + .map(|x| NUMBERS.sprite(x)) + .map(|x| loader.get_vram_sprite(x)) + .collect::>() + .into_boxed_slice(), + } + } +} + +struct Game { + settings: FinalisedSettings, + circles: VecDeque, + saws: VecDeque, + head_position: Vector2D, + phase_time: Number, + input: ButtonController, + frame_since_last_saw: i32, + alive_frames: u32, +} + +enum GameState { + Continue, + Loss(u32), +} + +impl Game { + fn from_settings(settings: Settings) -> Self { + let finalised = settings.to_finalised_settings(); + + let mut circles = VecDeque::with_capacity(finalised.number_of_circles); + for idx in 0..finalised.number_of_circles { + circles.push_back(Circle { + colour: Colour::Red, + position: Vector2D::new( + finalised.speed * idx as i32 - 4, + settings.head_start_position.y, + ), + }) + } + + Game { + input: agb::input::ButtonController::new(), + settings: finalised, + circles, + saws: VecDeque::new(), + head_position: settings.head_start_position, + phase_time: 0.into(), + frame_since_last_saw: 0, + alive_frames: 0, + } + } + + fn frame(&mut self, sprite_cache: &SpriteCache) -> GameState { + self.input.update(); + + let (height, colour) = if self.input.is_pressed(Button::A) { + (self.settings.wave_height_ability, Colour::Blue) + } else { + (self.settings.wave_height_normal, Colour::Red) + }; + + let next_phase_time = self.phase_time + self.settings.phase_speed; + + let this_frame_y_delta = next_phase_time.cos() - self.phase_time.cos(); + self.phase_time = next_phase_time % num!(1.); + let this_frame_y_delta = this_frame_y_delta * height; + self.head_position.y += this_frame_y_delta; + + // update circles + for circle in self.circles.iter_mut() { + circle.position.x -= self.settings.speed; + } + + self.circles.pop_front(); + + // generate circle + let circle = Circle { + colour, + position: self.head_position, + }; + + self.circles.push_back(circle); + + // update saws + check for death + let mut saw_has_hit_head = false; + let mut number_of_saws_to_pop = 0; + for (idx, saw) in self.saws.iter_mut().enumerate() { + saw.position.x -= self.settings.speed; + if saw.position.x < (-32).into() { + number_of_saws_to_pop = idx + 1; + } + saw.angle += saw.rotation_speed; + + let angle_affine_matrix = AffineMatrix::from_rotation(saw.angle); + + saw.object.set_affine_matrix(AffineMatrixInstance::new( + angle_affine_matrix.to_object_wrapping(), + )); + saw.object.show_affine(AffineMode::Affine); + + saw.object + .set_position(saw.position.floor() - (16, 16).into()); + + if (saw.position - self.head_position).magnitude_squared() + < ((16 + 4) * (16 + 4)).into() + { + saw_has_hit_head = true; + } + } + + // destroy saws + for _ in 0..number_of_saws_to_pop { + self.saws.pop_front(); + } + + // create saw + self.frame_since_last_saw -= 1; + if self.frame_since_last_saw <= 0 { + self.frame_since_last_saw = self.settings.frames_between_saws; + let mut rotation_direction = rng::gen().signum(); + if rotation_direction == 0 { + rotation_direction = 1; + } + + let rotation_magnitude = + Number::from_raw(rng::gen().abs() % (1 << 8)) % num!(0.02) + num!(0.005); + + let rotation_speed = rotation_magnitude * rotation_direction; + let saw = Saw { + object: ObjectUnmanaged::new(sprite_cache.saw.clone()), + position: (300, rng::gen().rem_euclid(display::HEIGHT)).into(), + angle: 0.into(), + rotation_speed, + }; + + self.saws.push_back(saw); + } + + self.alive_frames += 1; + + let out_of_bounds_death = self.head_position.y.floor() < -4 + || (self.head_position.y + 1).floor() > display::HEIGHT + 4; + + if saw_has_hit_head || out_of_bounds_death { + GameState::Loss(self.alive_frames) + } else { + GameState::Continue + } + } + + fn render(&self, oam: &mut OamIterator, sprite_cache: &SpriteCache) -> Option<()> { + for saw in self.saws.iter() { + oam.next()?.set(&saw.object); + } + + for circle in self.circles.iter() { + let mut object = ObjectUnmanaged::new(match circle.colour { + Colour::Red => sprite_cache.red.clone(), + Colour::Blue => sprite_cache.blue.clone(), + }); + + object + .show() + .set_position(circle.position.floor() - (4, 4).into()); + + oam.next()?.set(&object); + } + + Some(()) + } +} + +struct Settings { + phase_speed: Number, + frames_between_saws: i32, + speed: Number, + head_start_position: Vector2D, + wave_height_normal: Number, + wave_height_ability: Number, +} + +impl Settings { + fn to_finalised_settings(&self) -> FinalisedSettings { + FinalisedSettings { + number_of_circles: ((self.head_start_position.x + 4) / self.speed + 1) + .floor() + .try_into() + .expect("number should be positive"), + speed: self.speed, + phase_speed: self.phase_speed, + frames_between_saws: self.frames_between_saws, + wave_height_ability: self.wave_height_ability, + wave_height_normal: self.wave_height_normal, + } + } +} + +struct FinalisedSettings { + wave_height_normal: Number, + wave_height_ability: Number, + phase_speed: Number, + frames_between_saws: i32, + speed: Number, + number_of_circles: usize, +} + +pub fn main(mut gba: agb::Gba) -> ! { + let (mut unmanaged, mut sprites) = gba.display.object.get_unmanaged(); + let sprite_cache = SpriteCache::new(&mut sprites); + + let (_background, mut vram) = gba.display.video.tiled0(); + + vram.set_background_palettes(&[Palette16::new([u16::MAX; 16])]); + + let vblank = agb::interrupt::VBlank::get(); + + let mut max_score = 0; + + loop { + let mut game = Game::from_settings(Settings { + phase_speed: num!(0.02), + frames_between_saws: 60, + speed: num!(1.), + head_start_position: (40, 100).into(), + wave_height_normal: 20.into(), + wave_height_ability: 5.into(), + }); + loop { + let state = game.frame(&sprite_cache); + if game.alive_frames > max_score { + max_score = game.alive_frames; + } + vblank.wait_for_vblank(); + let oam_frame = &mut unmanaged.iter(); + draw_number( + max_score, + (display::WIDTH - 4, 1).into(), + oam_frame, + DrawDirection::Left, + &sprite_cache, + ); + draw_number( + game.alive_frames, + (1, 1).into(), + oam_frame, + DrawDirection::Right, + &sprite_cache, + ); + game.render(oam_frame, &sprite_cache); + + if let GameState::Loss(_) = state { + for _ in 0..30 { + vblank.wait_for_vblank(); + } + break; + } + } + } +} diff --git a/examples/amplitude/src/main.rs b/examples/amplitude/src/main.rs new file mode 100644 index 00000000..2bd74c7c --- /dev/null +++ b/examples/amplitude/src/main.rs @@ -0,0 +1,10 @@ +#![no_std] +#![no_main] +#![cfg_attr(test, feature(custom_test_frameworks))] +#![cfg_attr(test, reexport_test_harness_main = "test_main")] +#![cfg_attr(test, test_runner(agb::test_runner::test_runner))] + +#[agb::entry] +fn main(mut gba: agb::Gba) -> ! { + amplitude::main(gba) +} diff --git a/examples/combo/Cargo.lock b/examples/combo/Cargo.lock index 84f65f39..b39df590 100644 --- a/examples/combo/Cargo.lock +++ b/examples/combo/Cargo.lock @@ -76,15 +76,22 @@ dependencies = [ [[package]] name = "ahash" -version = "0.7.6" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" dependencies = [ - "getrandom", + "cfg-if 1.0.0", "once_cell", "version_check", ] +[[package]] +name = "amplitude" +version = "0.1.0" +dependencies = [ + "agb", +] + [[package]] name = "asefile" version = "0.3.5" @@ -167,6 +174,7 @@ name = "combo" version = "0.1.0" dependencies = [ "agb", + "amplitude", "hyperspace-roll", "the-hat-chooses-the-wizard", "the-purple-night", @@ -203,9 +211,9 @@ dependencies = [ [[package]] name = "fontdue" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a62391ecb864cf12ed06b2af4eda2e609b97657950d6a8f06841b17726ab253" +checksum = "0793f5137567643cf65ea42043a538804ff0fbf288649e2141442b602d81f9bc" dependencies = [ "hashbrown", "ttf-parser", @@ -220,22 +228,11 @@ dependencies = [ "cfg-if 0.1.10", ] -[[package]] -name = "getrandom" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" -dependencies = [ - "cfg-if 1.0.0", - "libc", - "wasi", -] - [[package]] name = "hashbrown" -version = "0.11.2" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" dependencies = [ "ahash", ] @@ -274,12 +271,6 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" -[[package]] -name = "libc" -version = "0.2.141" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5" - [[package]] name = "libflate" version = "0.1.27" @@ -554,12 +545,6 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - [[package]] name = "xml-rs" version = "0.8.4" diff --git a/examples/combo/Cargo.toml b/examples/combo/Cargo.toml index 91d4772b..aed8cc82 100644 --- a/examples/combo/Cargo.toml +++ b/examples/combo/Cargo.toml @@ -10,6 +10,7 @@ agb = { version = "0.14.0", path = "../../agb" } the-purple-night = { path = "../the-purple-night" } the-hat-chooses-the-wizard = { path = "../the-hat-chooses-the-wizard" } hyperspace-roll = { path = "../hyperspace-roll" } +amplitude = { path = "../amplitude" } [profile.dev] opt-level = 3 diff --git a/examples/combo/gfx/amplitude.png b/examples/combo/gfx/amplitude.png new file mode 100644 index 00000000..2086f1cb Binary files /dev/null and b/examples/combo/gfx/amplitude.png differ diff --git a/examples/combo/gfx/games.aseprite b/examples/combo/gfx/games.aseprite deleted file mode 100644 index 5f2832bd..00000000 Binary files a/examples/combo/gfx/games.aseprite and /dev/null differ diff --git a/examples/combo/gfx/hyperspace.png b/examples/combo/gfx/hyperspace.png index 59aae544..b73df24b 100644 Binary files a/examples/combo/gfx/hyperspace.png and b/examples/combo/gfx/hyperspace.png differ diff --git a/examples/combo/src/lib.rs b/examples/combo/src/lib.rs index 0092078a..42c9180c 100644 --- a/examples/combo/src/lib.rs +++ b/examples/combo/src/lib.rs @@ -21,6 +21,7 @@ pub enum Game { TheHatChoosesTheWizard, ThePurpleNight, HyperspaceRoll, + Amplitude, } impl Game { @@ -29,6 +30,7 @@ impl Game { Game::TheHatChoosesTheWizard => the_hat_chooses_the_wizard::main(gba), Game::ThePurpleNight => the_purple_night::main(gba), Game::HyperspaceRoll => hyperspace_roll::main(gba), + Game::Amplitude => amplitude::main(gba), } } @@ -37,6 +39,7 @@ impl Game { 0 => Game::TheHatChoosesTheWizard, 1 => Game::ThePurpleNight, 2 => Game::HyperspaceRoll, + 3 => Game::Amplitude, _ => unreachable!("game out of index in an unreachable manner"), } } @@ -46,7 +49,8 @@ include_background_gfx!( games, "121105", hat => "gfx/hat.png", purple => "gfx/purple.png", - hyperspace => "gfx/hyperspace.png" + hyperspace => "gfx/hyperspace.png", + amplitude => "gfx/amplitude.png" ); fn get_game(gba: &mut agb::Gba) -> Game { @@ -58,13 +62,15 @@ fn get_game(gba: &mut agb::Gba) -> Game { let hat = TileSet::new(games::hat.tiles, TileFormat::FourBpp); let purple = TileSet::new(games::purple.tiles, TileFormat::FourBpp); let hyperspace = TileSet::new(games::hyperspace.tiles, TileFormat::FourBpp); + let amplitude = TileSet::new(games::amplitude.tiles, TileFormat::FourBpp); - let tiles = [hat, purple, hyperspace]; + let tiles = [hat, purple, hyperspace, amplitude]; let palette_assignments = &[ games::hat.palette_assignments, games::purple.palette_assignments, games::hyperspace.palette_assignments, + games::amplitude.palette_assignments, ]; vram.set_background_palettes(games::PALETTES); @@ -79,7 +85,7 @@ fn get_game(gba: &mut agb::Gba) -> Game { let y = pos.y.rem_euclid(20); let x = pos.x.rem_euclid(30); - let game = (pos.x).rem_euclid(90) as usize / 30; + let game = (pos.x).rem_euclid(tiles.len() as i32 * 30) as usize / 30; let tile_id = (y * 30 + x) as usize; ( &tiles[game], diff --git a/examples/hyperspace-roll/Cargo.lock b/examples/hyperspace-roll/Cargo.lock index 651a3d7a..aebc74e3 100644 --- a/examples/hyperspace-roll/Cargo.lock +++ b/examples/hyperspace-roll/Cargo.lock @@ -76,11 +76,11 @@ dependencies = [ [[package]] name = "ahash" -version = "0.7.6" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" dependencies = [ - "getrandom", + "cfg-if", "once_cell", "version_check", ] @@ -178,30 +178,19 @@ dependencies = [ [[package]] name = "fontdue" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a62391ecb864cf12ed06b2af4eda2e609b97657950d6a8f06841b17726ab253" +checksum = "0793f5137567643cf65ea42043a538804ff0fbf288649e2141442b602d81f9bc" dependencies = [ "hashbrown", "ttf-parser", ] -[[package]] -name = "getrandom" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" -dependencies = [ - "cfg-if", - "libc", - "wasi", -] - [[package]] name = "hashbrown" -version = "0.11.2" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" dependencies = [ "ahash", ] @@ -234,12 +223,6 @@ dependencies = [ "png", ] -[[package]] -name = "libc" -version = "0.2.141" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5" - [[package]] name = "log" version = "0.4.17" @@ -422,9 +405,3 @@ name = "version_check" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" - -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" diff --git a/examples/hyperspace-roll/src/battle.rs b/examples/hyperspace-roll/src/battle.rs index 04f76fdd..bbde2c45 100644 --- a/examples/hyperspace-roll/src/battle.rs +++ b/examples/hyperspace-roll/src/battle.rs @@ -494,7 +494,7 @@ pub(crate) fn battle_screen( let obj = &agb.obj; - let mut select_box_obj = agb.obj.object(agb.obj.sprite(SELECT_BOX.sprite(0))); + let mut select_box_obj = agb.obj.object_sprite(SELECT_BOX.sprite(0)); select_box_obj.show(); let num_dice = player_dice.dice.len(); diff --git a/examples/hyperspace-roll/src/battle/display.rs b/examples/hyperspace-roll/src/battle/display.rs index 1b33009f..fbc1cd88 100644 --- a/examples/hyperspace-roll/src/battle/display.rs +++ b/examples/hyperspace-roll/src/battle/display.rs @@ -1,4 +1,4 @@ -use agb::display::object::{Object, ObjectController}; +use agb::display::object::{OamManaged, Object}; use agb::rng; use alloc::vec; use alloc::vec::Vec; @@ -39,7 +39,7 @@ pub struct BattleScreenDisplay<'a> { const HEALTH_BAR_WIDTH: usize = 48; impl<'a> BattleScreenDisplay<'a> { - pub fn new(obj: &'a ObjectController, current_battle_state: &CurrentBattleState) -> Self { + pub fn new(obj: &'a OamManaged, current_battle_state: &CurrentBattleState) -> Self { let mut misc_sprites = vec![]; let player_x = 12; let player_y = 8; @@ -52,8 +52,8 @@ impl<'a> BattleScreenDisplay<'a> { Ship::PilotedShip }); - let mut player_obj = obj.object(obj.sprite(player_sprite)); - let mut enemy_obj = obj.object(obj.sprite(enemy_sprite)); + let mut player_obj = obj.object_sprite(player_sprite); + let mut enemy_obj = obj.object_sprite(enemy_sprite); player_obj.set_x(player_x).set_y(player_y).set_z(1).show(); enemy_obj.set_x(enemy_x).set_y(player_y).set_z(1).show(); @@ -66,7 +66,7 @@ impl<'a> BattleScreenDisplay<'a> { .faces_to_render() .enumerate() .map(|(i, (face, _))| { - let mut die_obj = obj.object(obj.sprite(FACE_SPRITES.sprite_for_face(face))); + let mut die_obj = obj.object_sprite(FACE_SPRITES.sprite_for_face(face)); die_obj.set_y(120).set_x(i as u16 * 40 + 28).show(); @@ -89,7 +89,7 @@ impl<'a> BattleScreenDisplay<'a> { let player_shield: Vec<_> = (0..5) .map(|i| { - let mut shield_obj = obj.object(obj.sprite(shield_sprite)); + let mut shield_obj = obj.object_sprite(shield_sprite); shield_obj .set_x(player_x + 18 + 11 * i) .set_y(player_y) @@ -101,7 +101,7 @@ impl<'a> BattleScreenDisplay<'a> { let enemy_shield: Vec<_> = (0..5) .map(|i| { - let mut shield_obj = obj.object(obj.sprite(shield_sprite)); + let mut shield_obj = obj.object_sprite(shield_sprite); shield_obj .set_x(enemy_x - 16 - 11 * i) .set_y(player_y) @@ -146,9 +146,8 @@ impl<'a> BattleScreenDisplay<'a> { let enemy_attack_display = (0..2) .map(|i| { - let mut attack_obj = obj.object( - obj.sprite(ENEMY_ATTACK_SPRITES.sprite_for_attack(EnemyAttackType::Attack)), - ); + let mut attack_obj = obj + .object_sprite(ENEMY_ATTACK_SPRITES.sprite_for_attack(EnemyAttackType::Attack)); let attack_obj_position = (120, 56 + 32 * i).into(); attack_obj.set_position(attack_obj_position).hide(); @@ -189,7 +188,7 @@ impl<'a> BattleScreenDisplay<'a> { pub fn update( &mut self, - obj: &'a ObjectController, + obj: &'a OamManaged, current_battle_state: &CurrentBattleState, ) -> Vec { for (i, player_shield) in self.objs.player_shield.iter_mut().enumerate() { @@ -279,7 +278,7 @@ impl<'a> BattleScreenDisplay<'a> { actions_to_apply } - pub fn add_action(&mut self, action: Action, obj: &'a ObjectController, sfx: &mut Sfx) { + pub fn add_action(&mut self, action: Action, obj: &'a OamManaged, sfx: &mut Sfx) { play_sound_for_action_start(&action, sfx); self.animations @@ -309,7 +308,7 @@ impl<'a> EnemyAttackDisplay<'a> { } } - pub fn update(&mut self, attack: &Option, obj: &'a ObjectController) { + pub fn update(&mut self, attack: &Option, obj: &'a OamManaged) { if let Some(attack) = attack { self.face.show().set_sprite( obj.sprite(ENEMY_ATTACK_SPRITES.sprite_for_attack(attack.attack_type())), @@ -350,27 +349,27 @@ enum AnimationUpdateState { } impl<'a> AnimationStateHolder<'a> { - fn for_action(a: Action, obj: &'a ObjectController) -> Self { + fn for_action(a: Action, obj: &'a OamManaged) -> Self { let state = match a { Action::PlayerActivateShield { amount, .. } => { AnimationState::PlayerActivateShield { amount, frame: 0 } } Action::PlayerShoot { .. } => AnimationState::PlayerShoot { - bullet: obj.object(obj.sprite(BULLET_SPRITE)), + bullet: obj.object_sprite(BULLET_SPRITE), x: 64, }, Action::PlayerDisrupt { .. } => AnimationState::PlayerDisrupt { - bullet: obj.object(obj.sprite(DISRUPT_BULLET)), + bullet: obj.object_sprite(DISRUPT_BULLET), x: 64, }, Action::PlayerHeal { .. } => AnimationState::PlayerHeal {}, Action::PlayerBurstShield { .. } => AnimationState::PlayerBurstShield { frame: 0 }, Action::PlayerSendBurstShield { .. } => AnimationState::PlayerSendBurstShield { - bullet: obj.object(obj.sprite(BURST_BULLET)), + bullet: obj.object_sprite(BURST_BULLET), x: 64, }, Action::EnemyShoot { .. } => AnimationState::EnemyShoot { - bullet: obj.object(obj.sprite(BULLET_SPRITE)), + bullet: obj.object_sprite(BULLET_SPRITE), x: 175, }, Action::EnemyShield { amount, .. } => AnimationState::EnemyShield { amount, frame: 0 }, @@ -383,7 +382,7 @@ impl<'a> AnimationStateHolder<'a> { fn update( &mut self, objs: &mut BattleScreenDisplayObjects<'a>, - obj: &'a ObjectController, + obj: &'a OamManaged, current_battle_state: &CurrentBattleState, ) -> AnimationUpdateState { match &mut self.state { diff --git a/examples/hyperspace-roll/src/customise.rs b/examples/hyperspace-roll/src/customise.rs index 407797af..4c6a5aec 100644 --- a/examples/hyperspace-roll/src/customise.rs +++ b/examples/hyperspace-roll/src/customise.rs @@ -1,6 +1,6 @@ use agb::{ display::{ - object::{Object, ObjectController}, + object::{OamManaged, Object}, tiled::{RegularMap, TiledMap}, HEIGHT, WIDTH, }, @@ -91,10 +91,10 @@ fn move_net_position_ud(idx: usize, direction: Tri) -> usize { } } -fn create_dice_display<'a>(gfx: &'a ObjectController, dice: &'_ PlayerDice) -> Vec> { +fn create_dice_display<'a>(gfx: &'a OamManaged, dice: &'_ PlayerDice) -> Vec> { let mut objects = Vec::new(); for (idx, dice) in dice.dice.iter().enumerate() { - let mut obj = gfx.object(gfx.sprite(FACE_SPRITES.sprite_for_face(dice.faces[1]))); + let mut obj = gfx.object_sprite(FACE_SPRITES.sprite_for_face(dice.faces[1])); obj.set_x((idx as i32 * 32 - 24 / 2 + 20) as u16); obj.set_y(16 - 24 / 2); @@ -105,10 +105,10 @@ fn create_dice_display<'a>(gfx: &'a ObjectController, dice: &'_ PlayerDice) -> V objects } -fn create_net<'a>(gfx: &'a ObjectController, die: &'_ Die, modified: &[usize]) -> Vec> { +fn create_net<'a>(gfx: &'a OamManaged, die: &'_ Die, modified: &[usize]) -> Vec> { let mut objects = Vec::new(); for (idx, &face) in die.faces.iter().enumerate() { - let mut obj = gfx.object(gfx.sprite(FACE_SPRITES.sprite_for_face(face))); + let mut obj = gfx.object_sprite(FACE_SPRITES.sprite_for_face(face)); let (x, y) = screen_position_for_index(idx); obj.set_x((x - 24 / 2) as u16); obj.set_y((y - 24 / 2) as u16); @@ -119,7 +119,7 @@ fn create_net<'a>(gfx: &'a ObjectController, die: &'_ Die, modified: &[usize]) - } for &m in modified.iter().chain(core::iter::once(&3)) { - let mut obj = gfx.object(gfx.sprite(MODIFIED_BOX)); + let mut obj = gfx.object_sprite(MODIFIED_BOX); let (x, y) = screen_position_for_index(m); obj.set_x((x - 32 / 2) as u16); obj.set_y((y - 32 / 2) as u16); @@ -139,10 +139,10 @@ fn upgrade_position(idx: usize) -> (u32, u32) { ) } -fn create_upgrade_objects<'a>(gfx: &'a ObjectController, upgrades: &[Face]) -> Vec> { +fn create_upgrade_objects<'a>(gfx: &'a OamManaged, upgrades: &[Face]) -> Vec> { let mut objects = Vec::new(); for (idx, &upgrade) in upgrades.iter().enumerate() { - let mut obj = gfx.object(gfx.sprite(FACE_SPRITES.sprite_for_face(upgrade))); + let mut obj = gfx.object_sprite(FACE_SPRITES.sprite_for_face(upgrade)); let (x, y) = upgrade_position(idx); obj.set_x((x - 24 / 2) as u16); obj.set_y((y - 24 / 2) as u16); @@ -180,13 +180,13 @@ pub(crate) fn customise_screen( let mut input = agb::input::ButtonController::new(); - let mut select_box = agb.obj.object(agb.obj.sprite(SELECT_BOX.sprite(0))); + let mut select_box = agb.obj.object_sprite(SELECT_BOX.sprite(0)); select_box.show(); - let mut selected_dice = agb.obj.object(agb.obj.sprite(SELECTED_BOX)); + let mut selected_dice = agb.obj.object_sprite(SELECTED_BOX); selected_dice.hide(); - let mut selected_face = agb.obj.object(agb.obj.sprite(SELECTED_BOX)); + let mut selected_face = agb.obj.object_sprite(SELECTED_BOX); selected_face.hide(); agb.sfx.frame(); diff --git a/examples/hyperspace-roll/src/graphics.rs b/examples/hyperspace-roll/src/graphics.rs index 8a3f54ad..4f05521b 100644 --- a/examples/hyperspace-roll/src/graphics.rs +++ b/examples/hyperspace-roll/src/graphics.rs @@ -1,5 +1,5 @@ use agb::{ - display::object::{Object, ObjectController, Sprite, Tag}, + display::object::{OamManaged, Object, Sprite, Tag}, fixnum::Vector2D, }; use alloc::vec::Vec; @@ -141,14 +141,12 @@ pub struct HealthBar<'a> { } impl<'a> HealthBar<'a> { - pub fn new(pos: Vector2D, max: usize, obj: &'a ObjectController) -> Self { + pub fn new(pos: Vector2D, max: usize, obj: &'a OamManaged) -> Self { assert_eq!(max % 8, 0); let sprites = (0..(max / 8)) .map(|i| { - let health_sprite = obj.sprite(SMALL_SPRITES.red_bar(0)); - - let mut health_object = obj.object(health_sprite); + let mut health_object = obj.object_sprite(SMALL_SPRITES.red_bar(0)); health_object .set_position(pos + (i as i32 * 8, 0).into()) .show(); @@ -159,7 +157,7 @@ impl<'a> HealthBar<'a> { Self { max, sprites } } - pub fn set_value(&mut self, new_value: usize, obj: &'a ObjectController) { + pub fn set_value(&mut self, new_value: usize, obj: &'a OamManaged) { assert!(new_value <= self.max); for (i, sprite) in self.sprites.iter_mut().enumerate() { @@ -195,22 +193,22 @@ pub struct FractionDisplay<'a> { } impl<'a> FractionDisplay<'a> { - pub fn new(pos: Vector2D, digits: usize, obj: &'a ObjectController) -> Self { + pub fn new(pos: Vector2D, digits: usize, obj: &'a OamManaged) -> Self { let mut sprites = Vec::with_capacity(digits * 2 + 1); for i in 0..digits { - let mut left_digit = obj.object(obj.sprite(SMALL_SPRITES.number(0))); + let mut left_digit = obj.object_sprite(SMALL_SPRITES.number(0)); left_digit.set_position(pos + (i as i32 * 4, 0).into()); sprites.push(left_digit); - let mut right_digit = obj.object(obj.sprite(SMALL_SPRITES.number(0))); + let mut right_digit = obj.object_sprite(SMALL_SPRITES.number(0)); right_digit.set_position(pos + (i as i32 * 4 + digits as i32 * 4 + 7, 0).into()); sprites.push(right_digit); } - let mut slash = obj.object(obj.sprite(SMALL_SPRITES.slash())); + let mut slash = obj.object_sprite(SMALL_SPRITES.slash()); slash.set_position(pos + (digits as i32 * 4 + 1, 0).into()); sprites.push(slash); @@ -222,7 +220,7 @@ impl<'a> FractionDisplay<'a> { } } - pub fn set_value(&mut self, current: usize, max: usize, obj: &'a ObjectController) { + pub fn set_value(&mut self, current: usize, max: usize, obj: &'a OamManaged) { if self.current_current == current && self.current_max == max { return; } @@ -260,7 +258,7 @@ impl<'a> NumberDisplay<'a> { } } - pub fn set_value(&mut self, new_value: Option, obj: &'a ObjectController) { + pub fn set_value(&mut self, new_value: Option, obj: &'a OamManaged) { if self.value == new_value { return; } @@ -271,7 +269,7 @@ impl<'a> NumberDisplay<'a> { if let Some(mut new_value) = new_value { if new_value == 0 { - let mut zero_object = obj.object(obj.sprite(SMALL_SPRITES.number(0))); + let mut zero_object = obj.object_sprite(SMALL_SPRITES.number(0)); zero_object.show().set_position(self.position); self.objects.push(zero_object); @@ -284,7 +282,7 @@ impl<'a> NumberDisplay<'a> { new_value /= 10; let mut current_value_obj = - obj.object(obj.sprite(SMALL_SPRITES.number(current_value_digit))); + obj.object_sprite(SMALL_SPRITES.number(current_value_digit)); current_value_obj .show() diff --git a/examples/hyperspace-roll/src/lib.rs b/examples/hyperspace-roll/src/lib.rs index 793eed6c..055c1e04 100644 --- a/examples/hyperspace-roll/src/lib.rs +++ b/examples/hyperspace-roll/src/lib.rs @@ -12,7 +12,7 @@ #![cfg_attr(test, reexport_test_harness_main = "test_main")] #![cfg_attr(test, test_runner(agb::test_runner::test_runner))] -use agb::display::object::ObjectController; +use agb::display::object::OamManaged; use agb::display::tiled::{TileFormat, TiledMap, VRamManager}; use agb::display::Priority; use agb::interrupt::VBlank; @@ -90,7 +90,7 @@ pub struct PlayerDice { } struct Agb<'a> { - obj: ObjectController<'a>, + obj: OamManaged<'a>, vblank: VBlank, star_background: StarBackground<'a>, vram: VRamManager, @@ -104,7 +104,7 @@ pub fn main(mut gba: agb::Gba) -> ! { save::save_high_score(&mut gba.save, 0).expect("Could not reset high score"); } - let gfx = gba.display.object.get(); + let gfx = gba.display.object.get_managed(); let vblank = agb::interrupt::VBlank::get(); let (tiled, mut vram) = gba.display.video.tiled0(); diff --git a/examples/the-hat-chooses-the-wizard/Cargo.lock b/examples/the-hat-chooses-the-wizard/Cargo.lock index cecf9caf..725bbc69 100644 --- a/examples/the-hat-chooses-the-wizard/Cargo.lock +++ b/examples/the-hat-chooses-the-wizard/Cargo.lock @@ -76,11 +76,11 @@ dependencies = [ [[package]] name = "ahash" -version = "0.7.6" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" dependencies = [ - "getrandom", + "cfg-if", "once_cell", "version_check", ] @@ -178,30 +178,19 @@ dependencies = [ [[package]] name = "fontdue" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a62391ecb864cf12ed06b2af4eda2e609b97657950d6a8f06841b17726ab253" +checksum = "0793f5137567643cf65ea42043a538804ff0fbf288649e2141442b602d81f9bc" dependencies = [ "hashbrown", "ttf-parser", ] -[[package]] -name = "getrandom" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" -dependencies = [ - "cfg-if", - "libc", - "wasi", -] - [[package]] name = "hashbrown" -version = "0.11.2" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" dependencies = [ "ahash", ] @@ -233,12 +222,6 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" -[[package]] -name = "libc" -version = "0.2.141" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5" - [[package]] name = "log" version = "0.4.17" @@ -467,9 +450,3 @@ name = "version_check" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" - -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" diff --git a/examples/the-hat-chooses-the-wizard/src/enemies.rs b/examples/the-hat-chooses-the-wizard/src/enemies.rs index 223f067d..d7904ae5 100644 --- a/examples/the-hat-chooses-the-wizard/src/enemies.rs +++ b/examples/the-hat-chooses-the-wizard/src/enemies.rs @@ -2,7 +2,7 @@ use crate::TAG_MAP; use super::{sfx::SfxPlayer, Entity, FixedNumberType, HatState, Level}; use agb::{ - display::object::{ObjectController, Tag}, + display::object::{OamManaged, Tag}, fixnum::Vector2D, }; @@ -35,11 +35,11 @@ pub enum EnemyUpdateState { } impl<'a> Enemy<'a> { - pub fn new_slime(object: &'a ObjectController, start_pos: Vector2D) -> Self { + pub fn new_slime(object: &'a OamManaged, start_pos: Vector2D) -> Self { Enemy::Slime(Slime::new(object, start_pos + (0, 1).into())) } - pub fn new_snail(object: &'a ObjectController, start_pos: Vector2D) -> Self { + pub fn new_snail(object: &'a OamManaged, start_pos: Vector2D) -> Self { Enemy::Snail(Snail::new(object, start_pos)) } @@ -52,7 +52,7 @@ impl<'a> Enemy<'a> { pub fn update( &mut self, - controller: &'a ObjectController, + controller: &'a OamManaged, level: &Level, player_pos: Vector2D, hat_state: HatState, @@ -94,7 +94,7 @@ struct EnemyInfo<'a> { impl<'a> EnemyInfo<'a> { fn new( - object: &'a ObjectController, + object: &'a OamManaged, start_pos: Vector2D, collision: Vector2D, ) -> Self { @@ -135,7 +135,7 @@ pub struct Slime<'a> { } impl<'a> Slime<'a> { - fn new(object: &'a ObjectController, start_pos: Vector2D) -> Self { + fn new(object: &'a OamManaged, start_pos: Vector2D) -> Self { let slime = Slime { enemy_info: EnemyInfo::new(object, start_pos, (14u16, 14u16).into()), state: SlimeState::Idle, @@ -146,7 +146,7 @@ impl<'a> Slime<'a> { fn update( &mut self, - controller: &'a ObjectController, + controller: &'a OamManaged, level: &Level, player_pos: Vector2D, hat_state: HatState, @@ -257,7 +257,7 @@ pub struct Snail<'a> { } impl<'a> Snail<'a> { - fn new(object: &'a ObjectController, start_pos: Vector2D) -> Self { + fn new(object: &'a OamManaged, start_pos: Vector2D) -> Self { let snail = Snail { enemy_info: EnemyInfo::new(object, start_pos, (16u16, 16u16).into()), state: SnailState::Idle(0), @@ -272,7 +272,7 @@ impl<'a> Snail<'a> { fn update( &mut self, - controller: &'a ObjectController, + controller: &'a OamManaged, level: &Level, player_pos: Vector2D, hat_state: HatState, diff --git a/examples/the-hat-chooses-the-wizard/src/lib.rs b/examples/the-hat-chooses-the-wizard/src/lib.rs index 2161cae6..5a03e89c 100644 --- a/examples/the-hat-chooses-the-wizard/src/lib.rs +++ b/examples/the-hat-chooses-the-wizard/src/lib.rs @@ -8,7 +8,7 @@ extern crate alloc; use agb::{ display::{ - object::{Graphics, Object, ObjectController, Tag, TagMap}, + object::{Graphics, OamManaged, Object, Tag, TagMap}, tiled::{ InfiniteScrolledMap, PartialUpdateStatus, RegularBackgroundSize, TileFormat, TileSet, TileSetting, TiledMap, VRamManager, @@ -124,12 +124,11 @@ pub struct Entity<'a> { } impl<'a> Entity<'a> { - pub fn new(object: &'a ObjectController, collision_mask: Vector2D) -> Self { - let dummy_sprite = object.sprite(WALKING.sprite(0)); - let mut sprite = object.object(dummy_sprite); - sprite.set_priority(Priority::P1); + pub fn new(object: &'a OamManaged, collision_mask: Vector2D) -> Self { + let mut dummy_object = object.object_sprite(WALKING.sprite(0)); + dummy_object.set_priority(Priority::P1); Entity { - sprite, + sprite: dummy_object, collision_mask, position: (0, 0).into(), velocity: (0, 0).into(), @@ -348,7 +347,7 @@ fn ping_pong(i: i32, n: i32) -> i32 { } impl<'a> Player<'a> { - fn new(controller: &'a ObjectController, start_position: Vector2D) -> Self { + fn new(controller: &'a OamManaged, start_position: Vector2D) -> Self { let mut wizard = Entity::new(controller, (6_u16, 14_u16).into()); let mut hat = Entity::new(controller, (6_u16, 6_u16).into()); @@ -382,7 +381,7 @@ impl<'a> Player<'a> { fn update_frame( &mut self, input: &ButtonController, - controller: &'a ObjectController, + controller: &'a OamManaged, timer: i32, level: &Level, enemies: &[enemies::Enemy], @@ -616,7 +615,7 @@ enum UpdateState { impl<'a, 'b> PlayingLevel<'a, 'b> { fn open_level( level: &'a Level, - object_control: &'a ObjectController, + object_control: &'a OamManaged, background: &'a mut InfiniteScrolledMap<'b>, foreground: &'a mut InfiniteScrolledMap<'b>, input: ButtonController, @@ -677,7 +676,7 @@ impl<'a, 'b> PlayingLevel<'a, 'b> { self.player.wizard.sprite.set_priority(Priority::P0); } - fn dead_update(&mut self, controller: &'a ObjectController) -> bool { + fn dead_update(&mut self, controller: &'a OamManaged) -> bool { self.timer += 1; let frame = PLAYER_DEATH.animation_sprite(self.timer as usize / 8); @@ -696,7 +695,7 @@ impl<'a, 'b> PlayingLevel<'a, 'b> { &mut self, sfx_player: &mut SfxPlayer, vram: &mut VRamManager, - controller: &'a ObjectController, + controller: &'a OamManaged, ) -> UpdateState { self.timer += 1; self.input.update(); @@ -828,7 +827,7 @@ pub fn main(mut agb: agb::Gba) -> ! { vram.set_background_palettes(tile_sheet::PALETTES); - let object = agb.display.object.get(); + let object = agb.display.object.get_managed(); let vblank = agb::interrupt::VBlank::get(); let mut current_level = 0; diff --git a/examples/the-purple-night/Cargo.lock b/examples/the-purple-night/Cargo.lock index bd9ad872..b0ade7b4 100644 --- a/examples/the-purple-night/Cargo.lock +++ b/examples/the-purple-night/Cargo.lock @@ -76,11 +76,11 @@ dependencies = [ [[package]] name = "ahash" -version = "0.7.6" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" dependencies = [ - "getrandom", + "cfg-if 1.0.0", "once_cell", "version_check", ] @@ -193,9 +193,9 @@ dependencies = [ [[package]] name = "fontdue" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a62391ecb864cf12ed06b2af4eda2e609b97657950d6a8f06841b17726ab253" +checksum = "0793f5137567643cf65ea42043a538804ff0fbf288649e2141442b602d81f9bc" dependencies = [ "hashbrown", "ttf-parser", @@ -210,22 +210,11 @@ dependencies = [ "cfg-if 0.1.10", ] -[[package]] -name = "getrandom" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" -dependencies = [ - "cfg-if 1.0.0", - "libc", - "wasi", -] - [[package]] name = "hashbrown" -version = "0.11.2" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" dependencies = [ "ahash", ] @@ -251,12 +240,6 @@ dependencies = [ "png", ] -[[package]] -name = "libc" -version = "0.2.141" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5" - [[package]] name = "libflate" version = "0.1.27" @@ -485,12 +468,6 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - [[package]] name = "xml-rs" version = "0.8.4" diff --git a/examples/the-purple-night/src/lib.rs b/examples/the-purple-night/src/lib.rs index 2ad3ebd8..ea667825 100644 --- a/examples/the-purple-night/src/lib.rs +++ b/examples/the-purple-night/src/lib.rs @@ -14,7 +14,7 @@ use alloc::{boxed::Box, vec::Vec}; use agb::{ display::{ - object::{Graphics, Object, ObjectController, Sprite, Tag, TagMap}, + object::{Graphics, OamManaged, Object, Sprite, Tag, TagMap}, tiled::{ InfiniteScrolledMap, RegularBackgroundSize, TileFormat, TileSet, TileSetting, VRamManager, @@ -164,9 +164,8 @@ struct Entity<'a> { } impl<'a> Entity<'a> { - fn new(object_controller: &'a ObjectController, collision_mask: Rect) -> Self { - let s = object_controller.sprite(LONG_SWORD_IDLE.sprite(0)); - let mut sprite = object_controller.object(s); + fn new(object_controller: &'a OamManaged, collision_mask: Rect) -> Self { + let mut sprite = object_controller.object_sprite(LONG_SWORD_IDLE.sprite(0)); sprite.set_priority(Priority::P1); Entity { sprite, @@ -523,7 +522,7 @@ struct Player<'a> { } impl<'a> Player<'a> { - fn new(object_controller: &'a ObjectController<'a>) -> Player { + fn new(object_controller: &'a OamManaged<'_>) -> Player<'a> { let mut entity = Entity::new(object_controller, Rect::new((0, 1).into(), (5, 10).into())); let s = object_controller.sprite(LONG_SWORD_IDLE.sprite(0)); entity.sprite.set_sprite(s); @@ -546,7 +545,7 @@ impl<'a> Player<'a> { fn update( &mut self, - controller: &'a ObjectController, + controller: &'a OamManaged, buttons: &ButtonController, level: &Level, sfx: &mut sfx::Sfx, @@ -800,7 +799,7 @@ impl BatData { fn update<'a>( &mut self, - controller: &'a ObjectController, + controller: &'a OamManaged, entity: &mut Entity<'a>, player: &Player, level: &Level, @@ -933,7 +932,7 @@ impl SlimeData { fn update<'a>( &mut self, - controller: &'a ObjectController, + controller: &'a OamManaged, entity: &mut Entity<'a>, player: &Player, level: &Level, @@ -1062,7 +1061,7 @@ impl MiniFlameData { fn update<'a>( &mut self, - controller: &'a ObjectController, + controller: &'a OamManaged, entity: &mut Entity<'a>, player: &Player, _level: &Level, @@ -1191,7 +1190,7 @@ impl EmuData { fn update<'a>( &mut self, - controller: &'a ObjectController, + controller: &'a OamManaged, entity: &mut Entity<'a>, player: &Player, level: &Level, @@ -1364,7 +1363,7 @@ impl EnemyData { fn update<'a>( &mut self, - controller: &'a ObjectController, + controller: &'a OamManaged, entity: &mut Entity<'a>, player: &Player, level: &Level, @@ -1385,7 +1384,7 @@ struct Enemy<'a> { } impl<'a> Enemy<'a> { - fn new(object_controller: &'a ObjectController, enemy_data: EnemyData) -> Self { + fn new(object_controller: &'a OamManaged, enemy_data: EnemyData) -> Self { let mut entity = Entity::new(object_controller, enemy_data.collision_mask()); let sprite = enemy_data.sprite(); @@ -1399,7 +1398,7 @@ impl<'a> Enemy<'a> { fn update( &mut self, - controller: &'a ObjectController, + controller: &'a OamManaged, player: &Player, level: &Level, sfx: &mut sfx::Sfx, @@ -1430,7 +1429,7 @@ impl ParticleData { fn update<'a>( &mut self, - controller: &'a ObjectController, + controller: &'a OamManaged, entity: &mut Entity<'a>, player: &Player, _level: &Level, @@ -1518,7 +1517,7 @@ struct Particle<'a> { impl<'a> Particle<'a> { fn new( - object_controller: &'a ObjectController, + object_controller: &'a OamManaged, particle_data: ParticleData, position: Vector2D, ) -> Self { @@ -1534,7 +1533,7 @@ impl<'a> Particle<'a> { fn update( &mut self, - controller: &'a ObjectController, + controller: &'a OamManaged, player: &Player, level: &Level, ) -> UpdateInstruction { @@ -1561,7 +1560,7 @@ impl<'a> BossState<'a> { fn update( &mut self, enemies: &mut Arena>, - object_controller: &'a ObjectController, + object_controller: &'a OamManaged, player: &Player, sfx: &mut sfx::Sfx, ) -> BossInstruction { @@ -1596,7 +1595,7 @@ struct FollowingBoss<'a> { } impl<'a> FollowingBoss<'a> { - fn new(object_controller: &'a ObjectController, position: Vector2D) -> Self { + fn new(object_controller: &'a OamManaged, position: Vector2D) -> Self { let mut entity = Entity::new(object_controller, Rect::new((0, 0).into(), (0, 0).into())); entity.position = position; @@ -1608,7 +1607,7 @@ impl<'a> FollowingBoss<'a> { gone: false, } } - fn update(&mut self, controller: &'a ObjectController, player: &Player) { + fn update(&mut self, controller: &'a OamManaged, player: &Player) { let difference = player.entity.position - self.entity.position; self.timer += 1; @@ -1677,7 +1676,7 @@ enum BossInstruction { } impl<'a> Boss<'a> { - fn new(object_controller: &'a ObjectController, screen_coords: Vector2D) -> Self { + fn new(object_controller: &'a OamManaged, screen_coords: Vector2D) -> Self { let mut entity = Entity::new(object_controller, Rect::new((0, 0).into(), (28, 28).into())); entity.position = screen_coords + (144, 136).into(); Self { @@ -1693,7 +1692,7 @@ impl<'a> Boss<'a> { fn update( &mut self, enemies: &mut Arena>, - object_controller: &'a ObjectController, + object_controller: &'a OamManaged, player: &Player, sfx: &mut sfx::Sfx, ) -> BossInstruction { @@ -1797,7 +1796,7 @@ impl<'a> Boss<'a> { self.entity .commit_with_size(offset + shake, (32, 32).into()); } - fn explode(&self, enemies: &mut Arena>, object_controller: &'a ObjectController) { + fn explode(&self, enemies: &mut Arena>, object_controller: &'a OamManaged) { for _ in 0..(6 - self.health) { let x_offset: Number = Number::from_raw(rng::gen()).rem_euclid(2.into()) - 1; let y_offset: Number = Number::from_raw(rng::gen()).rem_euclid(2.into()) - 1; @@ -1871,7 +1870,7 @@ impl<'a> Game<'a> { fn advance_frame( &mut self, - object_controller: &'a ObjectController, + object_controller: &'a OamManaged, vram: &mut VRamManager, sfx: &mut sfx::Sfx, ) -> GameStatus { @@ -2083,7 +2082,7 @@ impl<'a> Game<'a> { } } - fn load_enemies(&mut self, object_controller: &'a ObjectController) { + fn load_enemies(&mut self, object_controller: &'a OamManaged) { if self.slime_load < self.level.slime_spawns.len() { for (idx, slime_spawn) in self .level @@ -2153,7 +2152,7 @@ impl<'a> Game<'a> { vram.set_background_palettes(&modified_palettes); } - fn new(object: &'a ObjectController, level: Level<'a>, start_at_boss: bool) -> Self { + fn new(object: &'a OamManaged<'a>, level: Level<'a>, start_at_boss: bool) -> Self { let mut player = Player::new(object); let mut offset = (8, 8).into(); if start_at_boss { @@ -2196,7 +2195,7 @@ fn game_with_level(gba: &mut agb::Gba) { let (background, mut vram) = gba.display.video.tiled0(); vram.set_background_palettes(background::PALETTES); let tileset = TileSet::new(background::background.tiles, TileFormat::FourBpp); - let object = gba.display.object.get(); + let object = gba.display.object.get_managed(); loop { let backdrop = InfiniteScrolledMap::new( diff --git a/mgba-test-runner/Cargo.lock b/mgba-test-runner/Cargo.lock index 09bfcca0..29ce0ceb 100644 --- a/mgba-test-runner/Cargo.lock +++ b/mgba-test-runner/Cargo.lock @@ -10,9 +10,9 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "aho-corasick" -version = "0.7.20" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" +checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04" dependencies = [ "memchr", ] @@ -121,9 +121,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.141" +version = "0.2.142" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5" +checksum = "6a987beff54b60ffa6d51982e1aa1146bc42f19bd26be28b0586f252fccf5317" [[package]] name = "memchr" @@ -205,9 +205,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.7.3" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b1f693b24f6ac912f4893ef08244d70b6067480d2f1a46e950c9691e6749d1d" +checksum = "af83e617f331cc6ae2da5443c602dfa5af81e517212d9d611a5b3ba1777b5370" dependencies = [ "aho-corasick", "memchr", @@ -216,9 +216,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.29" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" +checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c" [[package]] name = "simd-adler32" diff --git a/tools/Cargo.lock b/tools/Cargo.lock index ed67cdfc..82407993 100644 --- a/tools/Cargo.lock +++ b/tools/Cargo.lock @@ -74,9 +74,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bumpalo" -version = "3.12.0" +version = "3.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" +checksum = "9b1ce199063694f33ffb7dd4e0ee620741495c32833cde5aa08f02a0bf96f0c8" [[package]] name = "cc" @@ -107,18 +107,18 @@ dependencies = [ [[package]] name = "clap" -version = "4.2.2" +version = "4.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b802d85aaf3a1cdb02b224ba472ebdea62014fccfcb269b95a4d76443b5ee5a" +checksum = "956ac1f6381d8d82ab4684768f89c0ea3afe66925ceadb4eeb3fc452ffc55d62" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.2.2" +version = "4.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14a1a858f532119338887a4b8e1af9c60de8249cd7bafd68036a489e261e37b6" +checksum = "84080e799e54cff944f4b4a4b0e71630b0e0443b25b985175c7dddc1a859b749" dependencies = [ "anstream", "anstyle", @@ -321,9 +321,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.141" +version = "0.2.142" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5" +checksum = "6a987beff54b60ffa6d51982e1aa1146bc42f19bd26be28b0586f252fccf5317" [[package]] name = "link-cplusplus" @@ -336,9 +336,9 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.3.1" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d59d8c75012853d2e872fb56bc8a2e53718e2cafe1a4c823143141c6d90c322f" +checksum = "9b085a4f2cde5781fc4b1717f2e86c62f5cda49de7ba99a7c2eae02b61c9064c" [[package]] name = "log" @@ -410,9 +410,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.37.11" +version = "0.37.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85597d61f83914ddeba6a47b3b8ffe7365107221c2e557ed94426489fefb5f77" +checksum = "f79bef90eb6d984c72722595b5b1348ab39275a5e5123faca6863bf07d75a4e0" dependencies = [ "bitflags", "errno",