From 1849571344b44e76fd0d337efb8a073c611893bd Mon Sep 17 00:00:00 2001 From: Corwin Date: Mon, 7 Feb 2022 23:01:52 +0000 Subject: [PATCH] sprite allocator --- agb/Cargo.lock | 56 +++ agb/Cargo.toml | 1 + agb/src/agb_alloc/block_allocator.rs | 2 +- agb/src/agb_alloc/bump_allocator.rs | 10 +- agb/src/agb_alloc/mod.rs | 10 +- agb/src/display/object.rs | 564 ++++----------------------- 6 files changed, 138 insertions(+), 505 deletions(-) diff --git a/agb/Cargo.lock b/agb/Cargo.lock index fa448ec0..cf0c71a0 100644 --- a/agb/Cargo.lock +++ b/agb/Cargo.lock @@ -24,6 +24,7 @@ dependencies = [ "agb_sound_converter", "bare-metal", "bitflags", + "hashbrown", ] [[package]] @@ -64,6 +65,17 @@ dependencies = [ "syn", ] +[[package]] +name = "ahash" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + [[package]] name = "autocfg" version = "1.1.0" @@ -124,6 +136,26 @@ dependencies = [ "adler32", ] +[[package]] +name = "getrandom" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d39cd93900197114fa1fcb7ae84ca742095eed9442088988ae74fa744e930e77" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "hashbrown" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c21d40587b92fa6a6c6e3c1bdbf87d75511db5672f9c93175574b3a00df1758" +dependencies = [ + "ahash", +] + [[package]] name = "hound" version = "3.4.0" @@ -145,6 +177,12 @@ dependencies = [ "png", ] +[[package]] +name = "libc" +version = "0.2.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4" + [[package]] name = "miniz_oxide" version = "0.5.1" @@ -195,6 +233,12 @@ dependencies = [ "autocfg", ] +[[package]] +name = "once_cell" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5" + [[package]] name = "png" version = "0.17.5" @@ -270,3 +314,15 @@ name = "unicode-xid" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wasi" +version = "0.10.2+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" diff --git a/agb/Cargo.toml b/agb/Cargo.toml index dbd7d298..0dcfbade 100644 --- a/agb/Cargo.toml +++ b/agb/Cargo.toml @@ -26,6 +26,7 @@ agb_sound_converter = { version = "0.1.0", path = "../agb-sound-converter" } agb_macros = { version = "0.1.0", path = "../agb-macros" } agb_fixnum = { version = "0.1.0", path = "../agb-fixnum" } bare-metal = "1.0" +hashbrown = "0.12.0" [package.metadata.docs.rs] default-target = "thumbv6m-none-eabi" diff --git a/agb/src/agb_alloc/block_allocator.rs b/agb/src/agb_alloc/block_allocator.rs index 2c60114a..82cb8443 100644 --- a/agb/src/agb_alloc/block_allocator.rs +++ b/agb/src/agb_alloc/block_allocator.rs @@ -49,7 +49,7 @@ pub(crate) struct BlockAllocator { } impl BlockAllocator { - pub(super) const unsafe fn new(start: StartEnd) -> Self { + pub const unsafe fn new(start: StartEnd) -> Self { Self { inner_allocator: BumpAllocator::new(start), state: Mutex::new(RefCell::new(BlockAllocatorState { diff --git a/agb/src/agb_alloc/bump_allocator.rs b/agb/src/agb_alloc/bump_allocator.rs index 0be21bdd..daafb3c1 100644 --- a/agb/src/agb_alloc/bump_allocator.rs +++ b/agb/src/agb_alloc/bump_allocator.rs @@ -6,11 +6,9 @@ use super::SendNonNull; use crate::interrupt::free; use bare_metal::{CriticalSection, Mutex}; -pub(crate) struct AddrFn(pub fn() -> usize); - pub(crate) struct StartEnd { - pub start: AddrFn, - pub end: AddrFn, + pub start: fn() -> usize, + pub end: fn() -> usize, } pub(crate) struct BumpAllocator { @@ -34,7 +32,7 @@ impl BumpAllocator { let ptr = if let Some(c) = *current_ptr { c.as_ptr() as usize } else { - self.start_end.borrow(*cs).start.0() + (self.start_end.borrow(*cs).start)() }; let alignment_bitmask = layout.align() - 1; @@ -45,7 +43,7 @@ impl BumpAllocator { let resulting_ptr = ptr + amount_to_add; let new_current_ptr = resulting_ptr + layout.size(); - if new_current_ptr as usize >= self.start_end.borrow(*cs).end.0() { + if new_current_ptr as usize >= (self.start_end.borrow(*cs).end)() { return None; } diff --git a/agb/src/agb_alloc/mod.rs b/agb/src/agb_alloc/mod.rs index 97fc1734..9a2c6a94 100644 --- a/agb/src/agb_alloc/mod.rs +++ b/agb/src/agb_alloc/mod.rs @@ -2,12 +2,12 @@ use core::alloc::Layout; use core::ops::{Deref, DerefMut}; use core::ptr::NonNull; -mod block_allocator; -mod bump_allocator; +pub(crate) mod block_allocator; +pub(crate) mod bump_allocator; use block_allocator::BlockAllocator; -use self::bump_allocator::{AddrFn, StartEnd}; +use self::bump_allocator::StartEnd; struct SendNonNull(NonNull); unsafe impl Send for SendNonNull {} @@ -37,8 +37,8 @@ const EWRAM_END: usize = 0x0204_0000; #[global_allocator] static GLOBAL_ALLOC: BlockAllocator = unsafe { BlockAllocator::new(StartEnd { - start: AddrFn(get_data_end), - end: AddrFn(|| EWRAM_END), + start: get_data_end, + end: || EWRAM_END, }) }; diff --git a/agb/src/display/object.rs b/agb/src/display/object.rs index 4dea6e6f..626acbba 100644 --- a/agb/src/display/object.rs +++ b/agb/src/display/object.rs @@ -1,12 +1,21 @@ use core::cell::RefCell; +use hashbrown::{hash_map::Entry, HashMap}; + +use super::palette16::Palette16; use super::{palette16, Priority, DISPLAY_CONTROL}; +use crate::agb_alloc::block_allocator::BlockAllocator; +use crate::agb_alloc::bump_allocator::StartEnd; use crate::bitarray::Bitarray; use crate::fixnum::Vector2D; use crate::memory_mapped::MemoryMapped1DArray; -type AffineLoan<'a> = crate::arena::Loan<'a, 32>; -type AffineArena = crate::arena::Arena<32>; +static SPRITE_ALLOCATOR: BlockAllocator = unsafe { + BlockAllocator::new(StartEnd { + start: || 0x06010000, + end: || 0x06010000 + 1024 * 8 * 4, + }) +}; const OBJECT_ATTRIBUTE_MEMORY: MemoryMapped1DArray = unsafe { MemoryMapped1DArray::new(0x0700_0000) }; @@ -15,523 +24,92 @@ const PALETTE_SPRITE: MemoryMapped1DArray = const TILE_SPRITE: MemoryMapped1DArray = unsafe { MemoryMapped1DArray::new(0x06010000) }; -/// Handles distributing objects and matrices along with operations that effect all objects. -/// You can create an instance of this using the Gba struct. -/// -/// This handles distribution of sprites, ensuring that object ids are not reused and are -/// returned to the pool once you're done handling them. -/// -/// # Examples -/// -/// ``` -/// # #![no_std] -/// # #![no_main] -/// # -/// # use agb::Gba; -/// # -/// # #[agb::entry] -/// # fn main() -> ! { -/// let mut gba = Gba::new(); -/// let mut object = gba.display.object.get(); -/// # -/// # loop {} -/// # } -/// ``` -pub struct ObjectControl { - objects: RefCell>, - affines: AffineArena, +pub struct Sprite { + palette: &'static Palette16, + data: &'static [u8], } -struct ObjectLoan<'a> { - index: u8, - objects: &'a RefCell>, +struct SpriteBorrow<'a> { + id: SpriteId, + controller: &'a RefCell, } -/// The standard object, without rotation. -/// -/// You should create this from an instance of ObjectControl created using the Gba struct. Note that -/// no changes made to this will be visible until `commit()` is called. You should call `commit()` during -/// vblank to ensure that you get no visual artifacts. -/// -/// This struct implements a sort of builder pattern, allowing you to chain settings together. -/// -/// # Examples -/// -/// ``` -/// # #![no_std] -/// # #![no_main] -/// # -/// # use agb::Gba; -/// use agb::display::object::Size; -/// -/// # #[agb::entry] -/// # fn main() -> ! { -/// # let mut gba = Gba::new(); -/// let mut object = gba.display.object.get(); -/// -/// let mut my_new_object = object.get_object_standard(); -/// my_new_object.set_x(50) -/// .set_y(50) -/// .set_sprite_Size(Size::S8x8) -/// .set_tile_id(7) -/// .show(); -/// -/// // some time later in vblank -/// my_new_object.commit(); -/// # loop {} -/// # } -/// ``` -pub struct ObjectStandard<'a> { - attributes: ObjectAttribute, - loan: ObjectLoan<'a>, +struct Storage { + location: u16, + count: u16, } -/// The affine object, with potential for using a transformation matrix to alter -/// how the sprite is rendered to screen. -pub struct ObjectAffine<'a> { - attributes: ObjectAttribute, - loan: ObjectLoan<'a>, - aff_loan: Option>, +pub struct Object<'a> { + sprite: SpriteBorrow<'a>, } -/// Refers to an affine matrix in the OAM. Includes both an index and the -/// components of the affine matrix. -pub struct AffineMatrix<'a> { - pub attributes: AffineMatrixAttributes, - loan: AffineLoan<'a>, +struct SpriteControllerInner { + palette: HashMap, + sprite: HashMap, } -/// The components of the affine matrix. The components are fixed point 8:8. -/// TODO is a type that can handle fixed point arithmetic. -pub struct AffineMatrixAttributes { - pub p_a: i16, - pub p_b: i16, - pub p_c: i16, - pub p_d: i16, +pub struct SpriteController { + inner: RefCell, } -#[allow(dead_code)] -enum Mode { - Normal = 0, - Affine = 1, - Hidden = 2, - AffineDouble = 3, -} +pub struct ObjectController {} -#[derive(Clone, Copy)] -pub enum Size { - // stored as attr0 attr1 - S8x8 = 0b00_00, - S16x16 = 0b00_01, - S32x32 = 0b00_10, - S64x64 = 0b00_11, +/// 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); - S16x8 = 0b01_00, - S32x8 = 0b01_01, - S32x16 = 0b01_10, - S64x32 = 0b01_11, +/// 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); - S8x16 = 0b10_00, - S8x32 = 0b10_01, - S16x32 = 0b10_10, - S32x64 = 0b10_11, -} - -impl ObjectStandard<'_> { - /// Commits the object to OAM such that the updated version is displayed on - /// screen. Recommend to do this during VBlank. - pub fn commit(&self) { - unsafe { self.attributes.commit(self.loan.index) } - } - - /// Sets the x coordinate of the sprite on screen. - pub fn set_x(&mut self, x: u16) -> &mut Self { - self.attributes.set_x(x); - - self - } - - /// Sets the y coordinate of the sprite on screen. - pub fn set_y(&mut self, y: u16) -> &mut Self { - self.attributes.set_y(y); - - self - } - - /// Sets the index of the tile to use as the sprite. Potentially a temporary function. - pub fn set_tile_id(&mut self, id: u16) -> &mut Self { - self.attributes.set_tile_id(id); - - self - } - - /// Sets whether the sprite is horizontally mirrored or not. - pub fn set_hflip(&mut self, hflip: bool) -> &mut Self { - self.attributes.set_hflip(hflip); - - self - } - - /// Sets the sprite size, will read tiles in x major order to construct this. - pub fn set_sprite_size(&mut self, size: Size) -> &mut Self { - self.attributes.set_size(size); - - self - } - - /// Show the object on screen. - pub fn show(&mut self) -> &mut Self { - self.attributes.set_mode(Mode::Normal); - - self - } - - /// Hide the object and do not render. - pub fn hide(&mut self) -> &mut Self { - self.attributes.set_mode(Mode::Hidden); - - self - } - - /// Sets the palette to use for this sprite - pub fn set_palette(&mut self, palette: u16) -> &mut Self { - self.attributes.set_palette(palette); - - self - } - - /// Sets the x and y position of the object, performing casts as nessesary - /// to fit within the bits allocated for this purpose. - pub fn set_position(&mut self, position: Vector2D) -> &mut Self { - let x = position.x as u16; - let y = position.y as u16; - self.attributes.set_x(x); - self.attributes.set_y(y); - - self - } - - /// Sets the priority (used for z ordering) of this sprite - pub fn set_priority(&mut self, p: Priority) -> &mut Self { - self.attributes.set_priority(p); - - self +impl Sprite { + fn get_id(&'static self) -> SpriteId { + SpriteId(self as *const _ as usize) } } -impl<'a> ObjectAffine<'a> { - /// Commits the object to OAM such that the updated version is displayed on - /// screen. Recommend to do this during VBlank. - pub fn commit(&self) { - unsafe { self.attributes.commit(self.loan.index) } - } - - /// Sets the x coordinate of the sprite on screen. - pub fn set_x(&mut self, x: u16) { - self.attributes.set_x(x) - } - /// Sets the y coordinate of the sprite on screen. - pub fn set_y(&mut self, y: u16) { - self.attributes.set_y(y) - } - /// Sets the index of the tile to use as the sprite. Potentially a temporary function. - pub fn set_tile_id(&mut self, id: u16) { - self.attributes.set_tile_id(id) - } - /// Sets the sprite size, will read tiles in x major order to construct this. - pub fn set_sprite_size(&mut self, size: Size) { - self.attributes.set_size(size); - } - - /// Show the object on screen. Panics if affine matrix has not been set. - pub fn show(&mut self) { - if self.aff_loan.is_none() { - panic!("affine matrix should be set") +impl SpriteController { + fn get_sprite(&self, sprite: &'static Sprite) -> Option { + let inner = self.inner.borrow_mut(); + let id = sprite.get_id(); + if let Some(storage) = inner.sprite.get_mut(&id) { + storage.count += 1; + Some(SpriteBorrow { + id, + controller: &self.inner, + }) + } else { + // allocate a new sprite + todo!(); } - self.attributes.set_mode(Mode::Affine) - } - /// Hide the object and do not render the sprite. - pub fn hide(&mut self) { - self.attributes.set_mode(Mode::Hidden) - } - - /// Sets the affine matrix to use. Changing the affine matrix will change - /// how the sprite is rendered. - pub fn set_affine_mat(&mut self, aff: &AffineMatrix<'a>) { - self.attributes.set_affine(aff.loan.my_index); - self.aff_loan = Some(aff.loan.clone()); - } - - /// Sets the x and y position of the object, performing casts as nessesary - /// to fit within the bits allocated for this purpose. - pub fn set_position(&mut self, position: Vector2D) { - let x = position.x as u16; - let y = position.y as u16; - self.attributes.set_x(x); - self.attributes.set_y(y); - } - - pub fn set_priority(&mut self, p: Priority) { - self.attributes.set_priority(p) } } -fn set_bits(current: u16, value: u16, length: u16, shift: u16) -> u16 { - let mask: u16 = (1 << length) - 1; - (current & !(mask << shift)) | ((value & mask) << shift) -} - -impl Drop for ObjectLoan<'_> { +impl<'a> Drop for SpriteBorrow<'a> { fn drop(&mut self) { - let attributes = ObjectAttribute::new(); - unsafe { - attributes.commit(self.index); - } - let mut objs = self.objects.borrow_mut(); - objs.set(self.index as usize, false); - } -} + let inner = self.controller.borrow_mut(); + let entry = inner + .sprite + .entry(self.id) + .and_replace_entry_with(|_, mut storage| { + storage.count -= 1; + if storage.count == 0 { + None + } else { + Some(storage) + } + }); -struct ObjectAttribute { - a0: u16, - a1: u16, - a2: u16, -} - -impl ObjectAttribute { - unsafe fn commit(&self, index: u8) { - OBJECT_ATTRIBUTE_MEMORY.set(index as usize * 4, self.a0); - OBJECT_ATTRIBUTE_MEMORY.set(index as usize * 4 + 1, self.a1); - OBJECT_ATTRIBUTE_MEMORY.set(index as usize * 4 + 2, self.a2); - } - - fn set_hflip(&mut self, hflip: bool) { - self.a1 = set_bits(self.a1, hflip as u16, 1, 0xC); - } - - fn set_size(&mut self, size: Size) { - let a1 = size as u16 & 0b11; - let a0 = (size as u16 >> 2) & 0b11; - - self.a0 = set_bits(self.a0, a0, 2, 0xE); - self.a1 = set_bits(self.a1, a1, 2, 0xE); - } - - fn set_palette(&mut self, palette: u16) { - self.a2 = set_bits(self.a2, palette, 4, 0xC); - } - - fn set_x(&mut self, x: u16) { - self.a1 = set_bits(self.a1, x, 9, 0); - } - - fn set_y(&mut self, y: u16) { - self.a0 = set_bits(self.a0, y, 8, 0) - } - - fn set_tile_id(&mut self, id: u16) { - self.a2 = set_bits(self.a2, id, 10, 0); - } - - fn set_mode(&mut self, mode: Mode) { - self.a0 = set_bits(self.a0, mode as u16, 2, 8); - } - - fn set_affine(&mut self, aff_id: u8) { - self.a1 = set_bits(self.a1, aff_id as u16, 5, 0x9); - } - - fn set_priority(&mut self, p: Priority) { - self.a2 = set_bits(self.a2, p as u16, 2, 0x0A); - } -} - -impl AffineMatrix<'_> { - /// Commits matrix to OAM, will cause any objects using this matrix to be updated. - pub fn commit(&self) { - unsafe { self.attributes.commit(self.loan.my_index) }; - } -} - -impl AffineMatrixAttributes { - #[allow(clippy::identity_op)] - unsafe fn commit(&self, index: u8) { - let index = index as usize * 4; - OBJECT_ATTRIBUTE_MEMORY.set((index + 0) * 4 + 3, self.p_a as u16); - OBJECT_ATTRIBUTE_MEMORY.set((index + 1) * 4 + 3, self.p_b as u16); - OBJECT_ATTRIBUTE_MEMORY.set((index + 2) * 4 + 3, self.p_c as u16); - OBJECT_ATTRIBUTE_MEMORY.set((index + 3) * 4 + 3, self.p_d as u16); - } -} - -impl ObjectAttribute { - fn new() -> Self { - let mut o = ObjectAttribute { - a0: 0, - a1: 0, - a2: 0, - }; - o.set_mode(Mode::Hidden); - o - } -} - -impl ObjectControl { - pub(crate) fn new() -> Self { - let o = ObjectAttribute::new(); - for index in 0..128 { - unsafe { o.commit(index) }; - } - ObjectControl { - objects: RefCell::new(Bitarray::new()), - affines: AffineArena::new(), - } - } - - fn set_sprite_tilemap_entry(&self, index: usize, data: u32) { - TILE_SPRITE.set(index, data); - } - - /// Copies raw palettes to the background palette without any checks. - pub fn set_sprite_palette_raw(&self, colour: &[u16]) { - for (index, &entry) in colour.iter().enumerate() { - self.set_sprite_palette_entry(index, entry) - } - } - fn set_sprite_palette_entry(&self, index: usize, colour: u16) { - PALETTE_SPRITE.set(index, colour) - } - - fn set_sprite_palette(&self, pal_index: u8, palette: &palette16::Palette16) { - for (colour_index, &colour) in palette.colours.iter().enumerate() { - PALETTE_SPRITE.set(pal_index as usize * 16 + colour_index, colour); - } - } - - pub fn set_sprite_palettes(&self, palettes: &[palette16::Palette16]) { - for (palette_index, entry) in palettes.iter().enumerate() { - self.set_sprite_palette(palette_index as u8, entry) - } - } - - /// Copies tiles to the sprite tilemap without any checks. - pub fn set_sprite_tilemap(&self, tiles: &[u32]) { - for (index, &tile) in tiles.iter().enumerate() { - self.set_sprite_tilemap_entry(index, tile) - } - } - - pub fn set_sprite_tilemap_at_idx(&self, idx: usize, tiles: &[u32]) { - for (index, &tile) in tiles.iter().enumerate() { - self.set_sprite_tilemap_entry(index + idx, tile) - } - } - - /// Enable objects on the GBA. - pub fn enable(&mut self) { - let disp = DISPLAY_CONTROL.get(); - let disp = disp | (1 << 0x0C); - DISPLAY_CONTROL.set(disp); - } - - /// Disable objects, objects won't be rendered. - pub fn disable(&mut self) { - let disp = DISPLAY_CONTROL.get(); - let disp = disp & !(1 << 0x0C); - DISPLAY_CONTROL.set(disp); - } - - fn get_unused_object_index(&self) -> u8 { - let mut objects = self.objects.borrow_mut(); - for index in 0..128 { - if !objects.get(index).unwrap() { - objects.set(index, true); - return index as u8; + match entry { + Entry::Vacant(_) => { + // free the underlying resource. + // palette might be unused too. } - } - panic!("object id must be less than 128"); - } - - /// Get an unused standard object. Panics if more than 128 objects are - /// obtained. - pub fn get_object_standard(&self) -> ObjectStandard { - let id = self.get_unused_object_index(); - ObjectStandard { - attributes: ObjectAttribute::new(), - loan: ObjectLoan { - objects: &self.objects, - index: id, - }, - } - } - - /// Get an unused affine object. Panics if more than 128 objects are - /// obtained. - pub fn get_object_affine(&self) -> ObjectAffine { - let id = self.get_unused_object_index(); - ObjectAffine { - attributes: ObjectAttribute::new(), - loan: ObjectLoan { - objects: &self.objects, - index: id, - }, - aff_loan: None, - } - } - - /// Get an unused affine matrix. Panics if more than 32 affine matricies are - /// obtained. - pub fn get_affine(&self) -> AffineMatrix { - AffineMatrix { - attributes: AffineMatrixAttributes { - p_a: 0, - p_b: 0, - p_c: 0, - p_d: 0, - }, - loan: self - .affines - .get_next_free() - .expect("there are no affines avaliable"), + _ => {} } } } -#[cfg(test)] -mod tests { - #[test_case] - fn get_and_release_object(gba: &mut crate::Gba) { - let objs = gba.display.object.get(); - - let _o1 = { - let o0 = objs.get_object_standard(); - let o1 = objs.get_object_standard(); - assert_eq!(o0.loan.index, 0); - assert_eq!(o1.loan.index, 1); - o1 - }; - - let o0 = objs.get_object_standard(); - assert_eq!(o0.loan.index, 0); - let o2 = objs.get_object_affine(); - assert_eq!(o2.loan.index, 2); - } - - #[test_case] - fn get_and_release_affine(gba: &mut crate::Gba) { - let objs = gba.display.object.get(); - - let _a1 = { - let a0 = objs.get_affine(); - let a1 = objs.get_affine(); - assert_eq!(a0.loan.my_index, 0); - assert_eq!(a1.loan.my_index, 1); - a1 - }; - - let a0 = objs.get_affine(); - assert_eq!(a0.loan.my_index, 0); - let a2 = objs.get_affine(); - assert_eq!(a2.loan.my_index, 2); - } -} +impl ObjectController {}