From 0d623fc5b00d37e899bdec35d05e0daec7a99708 Mon Sep 17 00:00:00 2001 From: Gwilym Kuiper Date: Sun, 16 Jan 2022 21:38:04 +0000 Subject: [PATCH 1/6] Pass a Gba struct argument to the agb entry --- agb-macros/src/lib.rs | 48 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 44 insertions(+), 4 deletions(-) diff --git a/agb-macros/src/lib.rs b/agb-macros/src/lib.rs index a00985eb..b20b52f7 100644 --- a/agb-macros/src/lib.rs +++ b/agb-macros/src/lib.rs @@ -2,9 +2,9 @@ use proc_macro::TokenStream; use proc_macro2::Span; -use quote::quote; +use quote::{quote, ToTokens}; use rand::Rng; -use syn::{Ident, ItemFn, ReturnType, Type, Visibility}; +use syn::{FnArg, Ident, ItemFn, Pat, ReturnType, Token, Type, Visibility}; #[proc_macro_attribute] pub fn entry(args: TokenStream, input: TokenStream) -> TokenStream { @@ -15,16 +15,43 @@ pub fn entry(args: TokenStream, input: TokenStream) -> TokenStream { f.sig.constness.is_none() && f.vis == Visibility::Inherited && f.sig.abi.is_none() - && f.sig.inputs.is_empty() && f.sig.generics.params.is_empty() && f.sig.generics.where_clause.is_none() && match f.sig.output { ReturnType::Type(_, ref ty) => matches!(**ty, Type::Never(_)), _ => false, }, - "#[agb::entry] must have signature [unsafe] fn () -> !" + "#[agb::entry] must have signature [unsafe] fn (mut agb::Gba) -> !" ); + // Check that the function signature takes 1 argument, agb::Gba + let arguments: Vec<_> = f.sig.inputs.iter().collect(); + + assert_eq!( + arguments.len(), + 1, + "#[agb::entry] must have signature [unsafe] fn (mut agb::Gba) -> !, but got {} arguments", + arguments.len(), + ); + + let (argument_type, (argument_name, is_mutable)) = match arguments[0] { + FnArg::Typed(pat_type) => ( + pat_type.ty.to_token_stream(), + match &*pat_type.pat { + Pat::Ident(ident) => { + assert!( + ident.attrs.is_empty() && ident.by_ref.is_none() && ident.subpat.is_none(), + "#[agb::entry] must have signature [unsafe] fn (mut agb::Gba) -> !" + ); + + (ident.ident.clone(), ident.mutability.is_some()) + } + _ => panic!("Expected first argument to #[agb::entry] to be a basic identifier"), + }, + ), + _ => panic!("Expected first argument to #[agb::entry] to not be self"), + }; + assert!( args.to_string() == "", "Must pass no args to #[agb::entry] macro" @@ -35,10 +62,23 @@ pub fn entry(args: TokenStream, input: TokenStream) -> TokenStream { let attrs = f.attrs; let stmts = f.block.stmts; + let mutable = if is_mutable { + Some(Token![mut](Span::call_site())) + } else { + None + }; + + assert!( + argument_type.to_string().ends_with("Gba"), + "Expected first argument to have type 'Gba'" + ); + quote!( #[export_name = "main"] #(#attrs)* pub fn #fn_name() -> ! { + let #mutable #argument_name = #argument_type ::new(); + #(#stmts)* } ) From cf9ea504f6339a4a03730062c41dda2ab6e11bc1 Mon Sep 17 00:00:00 2001 From: Gwilym Kuiper Date: Sun, 16 Jan 2022 21:38:30 +0000 Subject: [PATCH 2/6] Update agb tests and all the examples --- agb/examples/allocation.rs | 2 +- agb/examples/beep.rs | 4 +--- agb/examples/bitmap3.rs | 3 +-- agb/examples/bitmap4.rs | 3 +-- agb/examples/chicken.rs | 3 +-- agb/examples/mixer_basic.rs | 3 +-- agb/examples/multiple_video.rs | 3 +-- agb/examples/output.rs | 2 +- agb/examples/panic.rs | 4 +--- agb/examples/stereo_sound.rs | 3 +-- agb/examples/syscall.rs | 3 +-- agb/examples/test_logo.rs | 3 +-- agb/examples/wave.rs | 3 +-- agb/src/lib.rs | 9 +++++++-- 14 files changed, 20 insertions(+), 28 deletions(-) diff --git a/agb/examples/allocation.rs b/agb/examples/allocation.rs index 67a8e3bf..64f0ddd3 100644 --- a/agb/examples/allocation.rs +++ b/agb/examples/allocation.rs @@ -6,7 +6,7 @@ extern crate alloc; use alloc::boxed::Box; #[agb::entry] -fn main() -> ! { +fn main(_gba: agb::Gba) -> ! { loop { let b = Box::new(1); agb::println!("dynamic allocation made to {:?}", &*b as *const _); diff --git a/agb/examples/beep.rs b/agb/examples/beep.rs index 39518009..dd399d30 100644 --- a/agb/examples/beep.rs +++ b/agb/examples/beep.rs @@ -4,9 +4,7 @@ use agb::sound; #[agb::entry] -fn main() -> ! { - let gba = agb::Gba::new(); - +fn main(gba: agb::Gba) -> ! { gba.sound.enable(); let sweep_settings = sound::dmg::SweepSettings::default(); diff --git a/agb/examples/bitmap3.rs b/agb/examples/bitmap3.rs index 98c668d2..fb4c41f5 100644 --- a/agb/examples/bitmap3.rs +++ b/agb/examples/bitmap3.rs @@ -9,8 +9,7 @@ struct Vector2D { } #[agb::entry] -fn main() -> ! { - let mut gba = agb::Gba::new(); +fn main(mut gba: agb::Gba) -> ! { let mut bitmap = gba.display.video.bitmap3(); let vblank = agb::interrupt::VBlank::get(); diff --git a/agb/examples/bitmap4.rs b/agb/examples/bitmap4.rs index 47a8ccbc..a46f6c72 100644 --- a/agb/examples/bitmap4.rs +++ b/agb/examples/bitmap4.rs @@ -4,8 +4,7 @@ use agb::display; #[agb::entry] -fn main() -> ! { - let mut gba = agb::Gba::new(); +fn main(mut gba: agb::Gba) -> ! { let mut bitmap = gba.display.video.bitmap4(); let vblank = agb::interrupt::VBlank::get(); diff --git a/agb/examples/chicken.rs b/agb/examples/chicken.rs index aec196fa..359a4811 100644 --- a/agb/examples/chicken.rs +++ b/agb/examples/chicken.rs @@ -35,14 +35,13 @@ fn frame_ranger(count: u32, start: u32, end: u32, delay: u32) -> u16 { } #[agb::entry] -fn main() -> ! { +fn main(mut gba: agb::Gba) -> ! { let map_as_grid: &[[u16; 32]; 32] = unsafe { (&MAP_MAP as *const [u16; 1024] as *const [[u16; 32]; 32]) .as_ref() .unwrap() }; - let mut gba = agb::Gba::new(); let mut gfx = gba.display.video.tiled0(); let vblank = agb::interrupt::VBlank::get(); let mut input = agb::input::ButtonController::new(); diff --git a/agb/examples/mixer_basic.rs b/agb/examples/mixer_basic.rs index fdb39b15..f699dc8a 100644 --- a/agb/examples/mixer_basic.rs +++ b/agb/examples/mixer_basic.rs @@ -10,8 +10,7 @@ use agb::{include_wav, Gba}; const DEAD_CODE: &[u8] = include_wav!("examples/JoshWoodward-DeadCode.wav"); #[agb::entry] -fn main() -> ! { - let mut gba = Gba::new(); +fn main(mut gba: Gba) -> ! { let mut input = ButtonController::new(); let vblank_provider = agb::interrupt::VBlank::get(); diff --git a/agb/examples/multiple_video.rs b/agb/examples/multiple_video.rs index 8de24f16..f37c69c4 100644 --- a/agb/examples/multiple_video.rs +++ b/agb/examples/multiple_video.rs @@ -9,8 +9,7 @@ struct Vector2D { } #[agb::entry] -fn main() -> ! { - let mut gba = agb::Gba::new(); +fn main(mut gba: agb::Gba) -> ! { let vblank = agb::interrupt::VBlank::get(); let mut input = agb::input::ButtonController::new(); diff --git a/agb/examples/output.rs b/agb/examples/output.rs index 4a5c06ae..85e9cf65 100644 --- a/agb/examples/output.rs +++ b/agb/examples/output.rs @@ -2,7 +2,7 @@ #![no_main] #[agb::entry] -fn main() -> ! { +fn main(_gba: agb::Gba) -> ! { let count = agb::interrupt::Mutex::new(0); agb::add_interrupt_handler!(agb::interrupt::Interrupt::VBlank, |key| { let mut count = count.lock_with_key(&key); diff --git a/agb/examples/panic.rs b/agb/examples/panic.rs index 0a740826..71ff6e8c 100644 --- a/agb/examples/panic.rs +++ b/agb/examples/panic.rs @@ -4,9 +4,7 @@ use agb::display; #[agb::entry] -fn main() -> ! { - let mut gba = agb::Gba::new(); - +fn main(mut gba: agb::Gba) -> ! { let mut bitmap = gba.display.video.bitmap3(); let mut input = agb::input::ButtonController::new(); diff --git a/agb/examples/stereo_sound.rs b/agb/examples/stereo_sound.rs index 1667e259..7807673c 100644 --- a/agb/examples/stereo_sound.rs +++ b/agb/examples/stereo_sound.rs @@ -8,8 +8,7 @@ use agb::{include_wav, Gba}; const LET_IT_IN: &[u8] = include_wav!("examples/JoshWoodward-LetItIn.wav"); #[agb::entry] -fn main() -> ! { - let mut gba = Gba::new(); +fn main(mut gba: Gba) -> ! { let vblank_provider = agb::interrupt::VBlank::get(); let mut timer_controller = gba.timers.timers(); diff --git a/agb/examples/syscall.rs b/agb/examples/syscall.rs index 995fda9d..4650f3ef 100644 --- a/agb/examples/syscall.rs +++ b/agb/examples/syscall.rs @@ -4,8 +4,7 @@ use agb::{display, syscall}; #[agb::entry] -fn main() -> ! { - let mut gba = agb::Gba::new(); +fn main(mut gba: agb::Gba) -> ! { let mut bitmap = gba.display.video.bitmap3(); for x in 0..display::WIDTH { diff --git a/agb/examples/test_logo.rs b/agb/examples/test_logo.rs index 806678fb..8cfc8164 100644 --- a/agb/examples/test_logo.rs +++ b/agb/examples/test_logo.rs @@ -4,8 +4,7 @@ use agb::display::example_logo; #[agb::entry] -fn main() -> ! { - let mut gba = agb::Gba::new(); +fn main(mut gba: agb::Gba) -> ! { let mut gfx = gba.display.video.tiled0(); example_logo::display_logo(&mut gfx); diff --git a/agb/examples/wave.rs b/agb/examples/wave.rs index 239f60c8..79b11081 100644 --- a/agb/examples/wave.rs +++ b/agb/examples/wave.rs @@ -13,8 +13,7 @@ struct BackCosines { } #[agb::entry] -fn main() -> ! { - let mut gba = agb::Gba::new(); +fn main(mut gba: agb::Gba) -> ! { let mut gfx = gba.display.video.tiled0(); example_logo::display_logo(&mut gfx); diff --git a/agb/src/lib.rs b/agb/src/lib.rs index d5204c45..4848eb63 100644 --- a/agb/src/lib.rs +++ b/agb/src/lib.rs @@ -283,7 +283,11 @@ fn panic_implementation(info: &core::panic::PanicInfo) -> ! { loop {} } +#[cfg(test)] +static mut TEST_GBA: Option = None; + #[doc(hidden)] +#[cfg(test)] pub fn test_runner(tests: &[&dyn Testable]) { let mut mgba = mgba::Mgba::new().unwrap(); mgba.print( @@ -292,7 +296,7 @@ pub fn test_runner(tests: &[&dyn Testable]) { ) .unwrap(); - let mut gba = Gba::new(); + let mut gba = unsafe { TEST_GBA.as_mut() }.unwrap(); for test in tests { test.run(&mut gba); @@ -307,7 +311,8 @@ pub fn test_runner(tests: &[&dyn Testable]) { #[cfg(test)] #[entry] -fn agb_test_main() -> ! { +fn agb_test_main(gba: Gba) -> ! { + unsafe { TEST_GBA = Some(gba) }; test_main(); #[allow(clippy::empty_loop)] loop {} From 9feab2b689e7d21028700dd5a495bbc651cc9444 Mon Sep 17 00:00:00 2001 From: Gwilym Kuiper Date: Sun, 16 Jan 2022 21:44:17 +0000 Subject: [PATCH 3/6] Update the big examples too --- book/games/pong/src/main.rs | 4 +--- examples/the-hat-chooses-the-wizard/src/main.rs | 6 ++---- examples/the-purple-night/src/main.rs | 4 +--- 3 files changed, 4 insertions(+), 10 deletions(-) diff --git a/book/games/pong/src/main.rs b/book/games/pong/src/main.rs index b559d005..c5fa5e78 100644 --- a/book/games/pong/src/main.rs +++ b/book/games/pong/src/main.rs @@ -33,9 +33,7 @@ mod gfx { // ensures that everything is in order. `agb` will call this after setting up the stack // and interrupt handlers correctly. #[agb::entry] -fn main() -> ! { - let mut gba = Gba::new(); - +fn main(mut gba: Gba) -> ! { let _tiled = gba.display.video.tiled0(); let mut object = gba.display.object.get(); gfx::load_sprite_data(&mut object); diff --git a/examples/the-hat-chooses-the-wizard/src/main.rs b/examples/the-hat-chooses-the-wizard/src/main.rs index d4aa6574..75f24596 100644 --- a/examples/the-hat-chooses-the-wizard/src/main.rs +++ b/examples/the-hat-chooses-the-wizard/src/main.rs @@ -772,10 +772,8 @@ impl<'a, 'b, 'c> PlayingLevel<'a, 'b> { } } -#[no_mangle] -pub fn main() -> ! { - let mut agb = agb::Gba::new(); - +#[agb::entry] +fn main(mut agb: agb::Gba) -> ! { splash_screen::show_splash_screen(&mut agb, splash_screen::SplashScreen::Start, None, None); loop { diff --git a/examples/the-purple-night/src/main.rs b/examples/the-purple-night/src/main.rs index 4d5df465..bdb52e2c 100644 --- a/examples/the-purple-night/src/main.rs +++ b/examples/the-purple-night/src/main.rs @@ -2207,9 +2207,7 @@ mod tilemap { } #[agb::entry] -fn main() -> ! { - let mut gba = agb::Gba::new(); - +fn main(mut gba: agb::Gba) -> ! { loop { game_with_level(&mut gba); } From f3e3782699b1374696ed33e821bfbb31059d34a5 Mon Sep 17 00:00:00 2001 From: Gwilym Kuiper Date: Sun, 16 Jan 2022 21:46:53 +0000 Subject: [PATCH 4/6] Update the book and the template --- book/src/pong/02_the_gba_struct.md | 10 ++++------ template/src/main.rs | 7 +++---- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/book/src/pong/02_the_gba_struct.md b/book/src/pong/02_the_gba_struct.md index d2d7046e..6cde93ff 100644 --- a/book/src/pong/02_the_gba_struct.md +++ b/book/src/pong/02_the_gba_struct.md @@ -1,15 +1,15 @@ # The Gba struct -In this section, we'll cover the importance of the Gba struct and how to create it. +In this section, we'll cover the importance of the Gba struct and how it gets created for you. # The importance of the Gba struct Almost all interaction with the Game Boy Advance's hardware goes through the [Gba singleton struct](https://docs.rs/agb/latest/agb/struct.Gba.html). -Your games using `agb` will typically create this in the `main` function and then handle the abstractions in there. +You should not create the Gba struct yourself, instead having it be passed into your main function. The Gba struct is used to take advantage of rust's borrow checker, and lean on it to ensure that access to the Game Boy Advance hardware is done 'sensibly'. You won't have to worry about 2 bits of your code modifying data in the wrong way! -This struct is a 'singleton', so you cannot create 2 instances of it at once. +This struct is a 'singleton', so you cannot create another instance of it. Attempting to do so will result in a panic which by default crashes the game. # How all agb games start @@ -21,8 +21,6 @@ Replace the content of the `main` function with the following: # #![no_main] # #[agb::entry] # fn main() -> ! { -let mut gba = agb::Gba::new(); - loop {} // infinite loop for now # } ``` @@ -36,6 +34,6 @@ Although there isn't much to see at the moment (just a black screen), you can st # What we did This was a very simple but incredibly important part of any game using `agb`. -All interactions with the hardware are gated via the Gba struct, so it must be created at the start of your `main` function and never again. +All interactions with the hardware are gated via the Gba struct, which you never create yourself. You are now ready to learn about display modes and how to start getting things onto the screen! \ No newline at end of file diff --git a/template/src/main.rs b/template/src/main.rs index 48348109..1b6a9fd1 100644 --- a/template/src/main.rs +++ b/template/src/main.rs @@ -12,12 +12,11 @@ use agb::{display, syscall}; -// The main function must take 0 arguments and never return. The agb::entry decorator +// The main function must take 1 arguments and never return. The agb::entry decorator // ensures that everything is in order. `agb` will call this after setting up the stack -// and interrupt handlers correctly. +// and interrupt handlers correctly. It will also handle creating the `Gba` struct for you. #[agb::entry] -fn main() -> ! { - let mut gba = agb::Gba::new(); +fn main(mut gba: agb::Gba) -> ! { let mut bitmap = gba.display.video.bitmap3(); for x in 0..display::WIDTH { From 040ff2eb0c1026ad2d5bbf281bd9fe7ddfd439dd Mon Sep 17 00:00:00 2001 From: Gwilym Kuiper Date: Sun, 16 Jan 2022 21:48:45 +0000 Subject: [PATCH 5/6] Update the rust doc --- agb/src/lib.rs | 26 +++++--------------------- 1 file changed, 5 insertions(+), 21 deletions(-) diff --git a/agb/src/lib.rs b/agb/src/lib.rs index 4848eb63..cdbc9959 100644 --- a/agb/src/lib.rs +++ b/agb/src/lib.rs @@ -114,7 +114,7 @@ pub use agb_image_converter::include_gfx; /// This macro declares the entry point to your game written using `agb`. /// -/// It is already included in the template, but your `main` function must be annotated with `#[agb::entry]`, take no arguments and never return. +/// It is already included in the template, but your `main` function must be annotated with `#[agb::entry]`, takes 1 argument and never returns. /// Doing this will ensure that `agb` can correctly set up the environment to call your rust function on start up. /// /// # Examples @@ -125,9 +125,7 @@ pub use agb_image_converter::include_gfx; /// use agb::Gba; /// /// #[agb::entry] -/// fn main() -> ! { -/// let mut gba = Gba::new(); -/// +/// fn main(mut gba: Gba) -> ! { /// loop {} /// } /// ``` @@ -180,13 +178,7 @@ static mut GBASINGLE: single::Singleton = single::Singleton::new(unsafe { G /// The Gba struct is used to control access to the Game Boy Advance's hardware in a way which makes it the /// borrow checker's responsibility to ensure no clashes of global resources. /// -/// This is typically created once at the start of the main function and then the various fields are used -/// to ensure mutually exclusive use of the various hardware registers. It provides a gateway into the main -/// usage of `agb` library. -/// -/// # Panics -/// -/// Calling this twice will panic. +/// This is will be created for you via the #[agb::entry] attribute. /// /// # Examples /// @@ -197,9 +189,7 @@ static mut GBASINGLE: single::Singleton = single::Singleton::new(unsafe { G /// use agb::Gba; /// /// #[agb::entry] -/// fn main() -> ! { -/// let mut gba = Gba::new(); -/// +/// fn main(mut gba: Gba) -> ! /// // Do whatever you need to do with gba /// /// loop {} @@ -219,13 +209,7 @@ pub struct Gba { } impl Gba { - /// Creates a new instance of the Gba struct. - /// - /// Note that you can only create 1 instance, and trying to create a second will panic. - /// - /// # Panics - /// - /// Panics if you try to create the second instance. + #[doc(hidden)] pub fn new() -> Self { unsafe { GBASINGLE.take() } } From 6d8d72077f01a73d6c1be212cfbd2ac8b3b1dbfd Mon Sep 17 00:00:00 2001 From: Gwilym Kuiper Date: Sun, 16 Jan 2022 21:50:18 +0000 Subject: [PATCH 6/6] Make it harder to call by accident --- agb-macros/src/lib.rs | 2 +- agb/src/lib.rs | 10 ++-------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/agb-macros/src/lib.rs b/agb-macros/src/lib.rs index b20b52f7..486e8e9a 100644 --- a/agb-macros/src/lib.rs +++ b/agb-macros/src/lib.rs @@ -77,7 +77,7 @@ pub fn entry(args: TokenStream, input: TokenStream) -> TokenStream { #[export_name = "main"] #(#attrs)* pub fn #fn_name() -> ! { - let #mutable #argument_name = #argument_type ::new(); + let #mutable #argument_name = unsafe { #argument_type ::new_in_entry() }; #(#stmts)* } diff --git a/agb/src/lib.rs b/agb/src/lib.rs index cdbc9959..f5cb0f62 100644 --- a/agb/src/lib.rs +++ b/agb/src/lib.rs @@ -210,8 +210,8 @@ pub struct Gba { impl Gba { #[doc(hidden)] - pub fn new() -> Self { - unsafe { GBASINGLE.take() } + pub unsafe fn new_in_entry() -> Self { + GBASINGLE.take() } const unsafe fn single_new() -> Self { @@ -224,12 +224,6 @@ impl Gba { } } -impl Default for Gba { - fn default() -> Self { - Self::new() - } -} - #[doc(hidden)] pub trait Testable { fn run(&self, gba: &mut Gba);