From 473fc12cee0a8ad46e36ed66448f2bf13a1bdabd Mon Sep 17 00:00:00 2001 From: Corwin Date: Sun, 31 Jul 2022 13:42:41 +0100 Subject: [PATCH] document objects --- agb/examples/gfx/boss.aseprite | Bin 0 -> 5688 bytes agb/examples/gfx/objects.aseprite | Bin 0 -> 26972 bytes agb/examples/sprites.rs | 4 +- agb/src/display/object.rs | 264 ++++++++++++++++++++++++++++++ 4 files changed, 266 insertions(+), 2 deletions(-) create mode 100644 agb/examples/gfx/boss.aseprite create mode 100644 agb/examples/gfx/objects.aseprite diff --git a/agb/examples/gfx/boss.aseprite b/agb/examples/gfx/boss.aseprite new file mode 100644 index 0000000000000000000000000000000000000000..e27d4de3ca9fc472a088b4fb8036bf2388a285f9 GIT binary patch literal 5688 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 literal 0 HcmV?d00001 diff --git a/agb/examples/sprites.rs b/agb/examples/sprites.rs index 58fdcc8d..b0b5941b 100644 --- a/agb/examples/sprites.rs +++ b/agb/examples/sprites.rs @@ -7,8 +7,8 @@ use agb::display::object::{Graphics, ObjectController, Sprite, TagMap}; use alloc::vec::Vec; const GRAPHICS: &Graphics = agb::include_aseprite!( - "../examples/the-purple-night/gfx/objects.aseprite", - "../examples/the-purple-night/gfx/boss.aseprite" + "examples/gfx/objects.aseprite", + "examples/gfx/boss.aseprite" ); const SPRITES: &[Sprite] = GRAPHICS.sprites(); const TAG_MAP: &TagMap = GRAPHICS.tags(); diff --git a/agb/src/display/object.rs b/agb/src/display/object.rs index 04a33587..09ab9275 100644 --- a/agb/src/display/object.rs +++ b/agb/src/display/object.rs @@ -1,3 +1,4 @@ +#![deny(missing_docs)] use alloc::vec::Vec; use core::alloc::Layout; @@ -110,13 +111,16 @@ const PALETTE_SPRITE: usize = 0x0500_0200; const TILE_SPRITE: usize = 0x06010000; const OBJECT_ATTRIBUTE_MEMORY: usize = 0x0700_0000; +/// 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, } +/// 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, @@ -135,12 +139,14 @@ pub enum Size { 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) => {{ @@ -155,6 +161,25 @@ macro_rules! align_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. Currently limited to square sprites that match a supported +/// dimension of the GBA, 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),*) => {{ @@ -168,26 +193,48 @@ macro_rules! 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)], } @@ -208,10 +255,16 @@ const fn const_byte_compare(a: &[u8], b: &[u8]) -> bool { } 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; @@ -226,6 +279,24 @@ impl TagMap { 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 evalulated 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); @@ -234,6 +305,8 @@ impl TagMap { 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) } @@ -257,6 +330,7 @@ impl Direction { } } +/// A sequence of sprites from aseprite. pub struct Tag { sprites: *const Sprite, len: usize, @@ -264,11 +338,13 @@ pub struct Tag { } 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 refered to by index in the animation sequence. #[must_use] pub const fn sprite(&self, idx: usize) -> &'static Sprite { if idx >= self.len { @@ -277,6 +353,14 @@ impl Tag { 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 { @@ -292,6 +376,8 @@ impl Tag { } #[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); @@ -326,6 +412,8 @@ impl Size { } #[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, @@ -345,6 +433,7 @@ impl Size { } #[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), @@ -363,6 +452,10 @@ impl Size { } } +/// 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]. pub struct SpriteBorrow<'a> { id: SpriteId, sprite_location: u16, @@ -441,6 +534,8 @@ impl Attributes { } } +/// 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>, } @@ -502,6 +597,8 @@ impl ObjectControllerStatic { } } +/// A controller that distributes objects and sprites. This controls sprites and +/// objects being copied to vram when it needs to be. pub struct ObjectController { phantom: ObjectControllerReference<'static>, } @@ -517,6 +614,9 @@ impl Drop for ObjectController { const HIDDEN_VALUE: u16 = 0b10 << 8; impl ObjectController { + /// 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 { get_object_controller(self.phantom) }; @@ -572,11 +672,104 @@ impl ObjectController { } } + #[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<'a>(&'a self, sprite: SpriteBorrow<'a>) -> Object<'a> { 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<'a>(&'a self, sprite: SpriteBorrow<'a>) -> Option> { let mut s = unsafe { get_object_controller(self.phantom) }; @@ -612,12 +805,51 @@ impl ObjectController { 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 { let s = unsafe { get_object_controller(self.phantom) }; @@ -638,6 +870,8 @@ impl<'a> Object<'a> { .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<'a>) { let object_inner = unsafe { self.object_inner() }; object_inner.attrs.a2.set_tile_index(sprite.sprite_location); @@ -652,6 +886,8 @@ impl<'a> Object<'a> { object_inner.sprite = unsafe { core::mem::transmute(sprite) }; } + /// Shows the sprite. No change will be seen until + /// [ObjectController::commit] is called. pub fn show(&mut self) -> &mut Self { let object_inner = unsafe { self.object_inner() }; object_inner.attrs.a0.set_object_mode(ObjectMode::Normal); @@ -659,18 +895,27 @@ impl<'a> Object<'a> { 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 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 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 object_inner = unsafe { self.object_inner() }; object_inner.attrs.a1a.set_x(x.rem_euclid(1 << 9) as u16); @@ -678,18 +923,26 @@ impl<'a> Object<'a> { self } + /// Sets the z priority of the sprite. Higher priority will be dislayed + /// 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 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 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 object_inner = unsafe { self.object_inner() }; object_inner.attrs.a0.set_y(y as u8); @@ -697,6 +950,9 @@ impl<'a> Object<'a> { self } + /// Sets the z position of the sprite, this controls which sprites are above + /// eachother. No change will be seen until [ObjectController::commit] is + /// called. pub fn set_z(&mut self, z: i32) -> &mut Self { let object_inner = unsafe { self.object_inner() }; object_inner.z = z; @@ -707,6 +963,9 @@ impl<'a> Object<'a> { 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 object_inner = unsafe { self.object_inner() }; object_inner.attrs.a0.set_y(position.y as u8); @@ -757,6 +1016,9 @@ impl Sprite { 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. #[must_use] pub const fn new(palette: &'static Palette16, data: &'static [u8], size: Size) -> Self { Self { @@ -766,6 +1028,7 @@ impl Sprite { } } #[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 } @@ -932,6 +1195,7 @@ enum ColourMode { Eight, } +// this mod is not public, so the internal parts don't need documenting. #[allow(dead_code)] mod attributes { use super::*;