diff --git a/agb/src/display/object.rs b/agb/src/display/object.rs index 85df2a554..1e5ec688a 100644 --- a/agb/src/display/object.rs +++ b/agb/src/display/object.rs @@ -20,7 +20,7 @@ pub use sprites::{ }; pub use affine::AffineMatrixInstance; -pub use managed::{OamManaged, Object}; +pub use managed::{OamManaged, Object, OrderedStore, OrderedStoreIterator}; pub use unmanaged::{AffineMode, OamIterator, OamSlot, OamUnmanaged, ObjectUnmanaged}; pub use font::{ChangeColour, ObjectTextRender, TextAlignment}; diff --git a/agb/src/display/object/managed.rs b/agb/src/display/object/managed.rs index b55c1806c..b7304c8cb 100644 --- a/agb/src/display/object/managed.rs +++ b/agb/src/display/object/managed.rs @@ -135,18 +135,85 @@ impl Store { /// /// Otherwise I'd recommend using [`OamUnmanaged`]. pub struct OamManaged<'gba> { - object_store: Store, + object_store: OrderedStore, sprite_loader: UnsafeCell, unmanaged: UnsafeCell>, } -impl OamManaged<'_> { - pub(crate) fn new() -> Self { +/// Stores a bunch of objects and manages the z ordering for you. +/// +/// An alternate to consider is using an arena, storing keys in a vector, and +/// sorting that vector by key in the arena. +pub struct OrderedStore { + object_store: Store, +} + +/// An iterator over the visible objects in the object store. +pub struct OrderedStoreIterator<'store> { + iter: StoreIterator<'store>, +} + +impl<'store> Iterator for OrderedStoreIterator<'store> { + type Item = &'store ObjectUnmanaged; + + fn next(&mut self) -> Option { + for next in self.iter.by_ref() { + let item = unsafe { &*next.object.get() }; + if item.is_visible() { + return Some(item); + } + } + + None + } +} + +impl<'a> IntoIterator for &'a OrderedStore { + type Item = &'a ObjectUnmanaged; + + type IntoIter = OrderedStoreIterator<'a>; + + fn into_iter(self) -> Self::IntoIter { + OrderedStoreIterator { + iter: unsafe { self.object_store.iter() }, + } + } +} + +impl OrderedStore { + /// Creates a new empty ordered object store. + #[must_use] + pub fn new() -> Self { Self { object_store: Store { store: UnsafeCell::new(Arena::new()), first_z: Cell::new(None), }, + } + } + + /// Creates an object from the sprite in vram. + pub fn object(&self, sprite: SpriteVram) -> Object<'_> { + self.object_store + .insert_object(ObjectUnmanaged::new(sprite)) + } + + /// Iter over the ordered store in order + pub fn iter(&self) -> OrderedStoreIterator { + self.into_iter() + } +} + +impl Default for OrderedStore { + fn default() -> Self { + Self::new() + } +} + +impl OamManaged<'_> { + pub(crate) fn new() -> Self { + Self { + object_store: OrderedStore::new(), sprite_loader: UnsafeCell::new(SpriteLoader::new()), unmanaged: UnsafeCell::new(OamUnmanaged::new()), } @@ -169,13 +236,9 @@ impl OamManaged<'_> { // 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); - } + let mut unmanaged = unmanaged.iter(); + + unmanaged.set(&self.object_store); // safety: not reentrant unsafe { @@ -185,8 +248,7 @@ impl OamManaged<'_> { /// Creates an object from the sprite in vram. pub fn object(&self, sprite: SpriteVram) -> Object<'_> { - self.object_store - .insert_object(ObjectUnmanaged::new(sprite)) + self.object_store.object(sprite) } /// Creates a sprite in vram from a static sprite from [`include_aseprite`][crate::include_aseprite]. @@ -500,7 +562,7 @@ mod tests { objects[index_to_modify].set_z(modify_to); assert!( - managed.object_store.is_all_ordered_right(), + managed.object_store.object_store.is_all_ordered_right(), "objects are unordered after {} modifications. Modified {} to {}.", modification_number + 1, index_to_modify, diff --git a/agb/src/display/object/unmanaged/object.rs b/agb/src/display/object/unmanaged/object.rs index 98fc2a659..859f9cae5 100644 --- a/agb/src/display/object/unmanaged/object.rs +++ b/agb/src/display/object/unmanaged/object.rs @@ -71,6 +71,22 @@ pub struct OamIterator<'oam> { frame_data: &'oam UnsafeCell, } +impl OamIterator<'_> { + fn set_inner(&mut self, object: &ObjectUnmanaged) -> OamDisplayResult { + if let Some(slot) = self.next() { + slot.set(object); + OamDisplayResult::Written + } else { + OamDisplayResult::SomeNotWritten + } + } + + /// Writes objects in the Renderable to slots in OAM. + pub fn set(&mut self, renderable: R) { + renderable.set_in(self); + } +} + /// 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. /// @@ -337,6 +353,53 @@ impl ObjectUnmanaged { } } +pub trait OamDisplay { + /// Write it to oam slots, returns whether all the writes could succeed. + fn set_in(self, oam: &mut OamIterator) -> OamDisplayResult; +} + +impl OamDisplay for ObjectUnmanaged { + fn set_in(self, oam: &mut OamIterator) -> OamDisplayResult { + oam.set_inner(&self) + } +} + +impl OamDisplay for &ObjectUnmanaged { + fn set_in(self, oam: &mut OamIterator) -> OamDisplayResult { + oam.set_inner(self) + } +} + +impl OamDisplay for &mut ObjectUnmanaged { + fn set_in(self, oam: &mut OamIterator) -> OamDisplayResult { + oam.set_inner(self) + } +} + +impl OamDisplay for T +where + T: IntoIterator, + O: OamDisplay, +{ + fn set_in(self, oam: &mut OamIterator) -> OamDisplayResult { + for object in self.into_iter() { + if matches!(object.set_in(oam), OamDisplayResult::SomeNotWritten) { + return OamDisplayResult::SomeNotWritten; + } + } + + OamDisplayResult::Written + } +} + +/// The result of setting on the Oam +pub enum OamDisplayResult { + /// All objects were written successfully + Written, + /// Some objects were not written + SomeNotWritten, +} + #[cfg(test)] mod tests { use crate::{