Merge pull request #160 from gwilymk/make-agb-entry-create-gba-struct

Make agb entry create gba struct
This commit is contained in:
Gwilym Kuiper 2022-01-17 19:47:44 +00:00 committed by GitHub
commit a9e728a037
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 82 additions and 81 deletions

View file

@ -2,9 +2,9 @@
use proc_macro::TokenStream; use proc_macro::TokenStream;
use proc_macro2::Span; use proc_macro2::Span;
use quote::quote; use quote::{quote, ToTokens};
use rand::Rng; use rand::Rng;
use syn::{Ident, ItemFn, ReturnType, Type, Visibility}; use syn::{FnArg, Ident, ItemFn, Pat, ReturnType, Token, Type, Visibility};
#[proc_macro_attribute] #[proc_macro_attribute]
pub fn entry(args: TokenStream, input: TokenStream) -> TokenStream { 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.sig.constness.is_none()
&& f.vis == Visibility::Inherited && f.vis == Visibility::Inherited
&& f.sig.abi.is_none() && f.sig.abi.is_none()
&& f.sig.inputs.is_empty()
&& f.sig.generics.params.is_empty() && f.sig.generics.params.is_empty()
&& f.sig.generics.where_clause.is_none() && f.sig.generics.where_clause.is_none()
&& match f.sig.output { && match f.sig.output {
ReturnType::Type(_, ref ty) => matches!(**ty, Type::Never(_)), ReturnType::Type(_, ref ty) => matches!(**ty, Type::Never(_)),
_ => false, _ => 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!( assert!(
args.to_string() == "", args.to_string() == "",
"Must pass no args to #[agb::entry] macro" "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 attrs = f.attrs;
let stmts = f.block.stmts; 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!( quote!(
#[export_name = "main"] #[export_name = "main"]
#(#attrs)* #(#attrs)*
pub fn #fn_name() -> ! { pub fn #fn_name() -> ! {
let #mutable #argument_name = unsafe { #argument_type ::new_in_entry() };
#(#stmts)* #(#stmts)*
} }
) )

View file

@ -6,7 +6,7 @@ extern crate alloc;
use alloc::boxed::Box; use alloc::boxed::Box;
#[agb::entry] #[agb::entry]
fn main() -> ! { fn main(_gba: agb::Gba) -> ! {
loop { loop {
let b = Box::new(1); let b = Box::new(1);
agb::println!("dynamic allocation made to {:?}", &*b as *const _); agb::println!("dynamic allocation made to {:?}", &*b as *const _);

View file

@ -4,9 +4,7 @@
use agb::sound; use agb::sound;
#[agb::entry] #[agb::entry]
fn main() -> ! { fn main(gba: agb::Gba) -> ! {
let gba = agb::Gba::new();
gba.sound.enable(); gba.sound.enable();
let sweep_settings = sound::dmg::SweepSettings::default(); let sweep_settings = sound::dmg::SweepSettings::default();

View file

@ -9,8 +9,7 @@ struct Vector2D {
} }
#[agb::entry] #[agb::entry]
fn main() -> ! { fn main(mut gba: agb::Gba) -> ! {
let mut gba = agb::Gba::new();
let mut bitmap = gba.display.video.bitmap3(); let mut bitmap = gba.display.video.bitmap3();
let vblank = agb::interrupt::VBlank::get(); let vblank = agb::interrupt::VBlank::get();

View file

@ -4,8 +4,7 @@
use agb::display; use agb::display;
#[agb::entry] #[agb::entry]
fn main() -> ! { fn main(mut gba: agb::Gba) -> ! {
let mut gba = agb::Gba::new();
let mut bitmap = gba.display.video.bitmap4(); let mut bitmap = gba.display.video.bitmap4();
let vblank = agb::interrupt::VBlank::get(); let vblank = agb::interrupt::VBlank::get();

View file

@ -35,14 +35,13 @@ fn frame_ranger(count: u32, start: u32, end: u32, delay: u32) -> u16 {
} }
#[agb::entry] #[agb::entry]
fn main() -> ! { fn main(mut gba: agb::Gba) -> ! {
let map_as_grid: &[[u16; 32]; 32] = unsafe { let map_as_grid: &[[u16; 32]; 32] = unsafe {
(&MAP_MAP as *const [u16; 1024] as *const [[u16; 32]; 32]) (&MAP_MAP as *const [u16; 1024] as *const [[u16; 32]; 32])
.as_ref() .as_ref()
.unwrap() .unwrap()
}; };
let mut gba = agb::Gba::new();
let mut gfx = gba.display.video.tiled0(); let mut gfx = gba.display.video.tiled0();
let vblank = agb::interrupt::VBlank::get(); let vblank = agb::interrupt::VBlank::get();
let mut input = agb::input::ButtonController::new(); let mut input = agb::input::ButtonController::new();

View file

@ -10,8 +10,7 @@ use agb::{include_wav, Gba};
const DEAD_CODE: &[u8] = include_wav!("examples/JoshWoodward-DeadCode.wav"); const DEAD_CODE: &[u8] = include_wav!("examples/JoshWoodward-DeadCode.wav");
#[agb::entry] #[agb::entry]
fn main() -> ! { fn main(mut gba: Gba) -> ! {
let mut gba = Gba::new();
let mut input = ButtonController::new(); let mut input = ButtonController::new();
let vblank_provider = agb::interrupt::VBlank::get(); let vblank_provider = agb::interrupt::VBlank::get();

View file

@ -9,8 +9,7 @@ struct Vector2D {
} }
#[agb::entry] #[agb::entry]
fn main() -> ! { fn main(mut gba: agb::Gba) -> ! {
let mut gba = agb::Gba::new();
let vblank = agb::interrupt::VBlank::get(); let vblank = agb::interrupt::VBlank::get();
let mut input = agb::input::ButtonController::new(); let mut input = agb::input::ButtonController::new();

View file

@ -2,7 +2,7 @@
#![no_main] #![no_main]
#[agb::entry] #[agb::entry]
fn main() -> ! { fn main(_gba: agb::Gba) -> ! {
let count = agb::interrupt::Mutex::new(0); let count = agb::interrupt::Mutex::new(0);
agb::add_interrupt_handler!(agb::interrupt::Interrupt::VBlank, |key| { agb::add_interrupt_handler!(agb::interrupt::Interrupt::VBlank, |key| {
let mut count = count.lock_with_key(&key); let mut count = count.lock_with_key(&key);

View file

@ -4,9 +4,7 @@
use agb::display; use agb::display;
#[agb::entry] #[agb::entry]
fn main() -> ! { fn main(mut gba: agb::Gba) -> ! {
let mut gba = agb::Gba::new();
let mut bitmap = gba.display.video.bitmap3(); let mut bitmap = gba.display.video.bitmap3();
let mut input = agb::input::ButtonController::new(); let mut input = agb::input::ButtonController::new();

View file

@ -8,8 +8,7 @@ use agb::{include_wav, Gba};
const LET_IT_IN: &[u8] = include_wav!("examples/JoshWoodward-LetItIn.wav"); const LET_IT_IN: &[u8] = include_wav!("examples/JoshWoodward-LetItIn.wav");
#[agb::entry] #[agb::entry]
fn main() -> ! { fn main(mut gba: Gba) -> ! {
let mut gba = Gba::new();
let vblank_provider = agb::interrupt::VBlank::get(); let vblank_provider = agb::interrupt::VBlank::get();
let mut timer_controller = gba.timers.timers(); let mut timer_controller = gba.timers.timers();

View file

@ -4,8 +4,7 @@
use agb::{display, syscall}; use agb::{display, syscall};
#[agb::entry] #[agb::entry]
fn main() -> ! { fn main(mut gba: agb::Gba) -> ! {
let mut gba = agb::Gba::new();
let mut bitmap = gba.display.video.bitmap3(); let mut bitmap = gba.display.video.bitmap3();
for x in 0..display::WIDTH { for x in 0..display::WIDTH {

View file

@ -4,8 +4,7 @@
use agb::display::example_logo; use agb::display::example_logo;
#[agb::entry] #[agb::entry]
fn main() -> ! { fn main(mut gba: agb::Gba) -> ! {
let mut gba = agb::Gba::new();
let mut gfx = gba.display.video.tiled0(); let mut gfx = gba.display.video.tiled0();
example_logo::display_logo(&mut gfx); example_logo::display_logo(&mut gfx);

View file

@ -13,8 +13,7 @@ struct BackCosines {
} }
#[agb::entry] #[agb::entry]
fn main() -> ! { fn main(mut gba: agb::Gba) -> ! {
let mut gba = agb::Gba::new();
let mut gfx = gba.display.video.tiled0(); let mut gfx = gba.display.video.tiled0();
example_logo::display_logo(&mut gfx); example_logo::display_logo(&mut gfx);

View file

@ -114,7 +114,7 @@ pub use agb_image_converter::include_gfx;
/// This macro declares the entry point to your game written using `agb`. /// 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. /// Doing this will ensure that `agb` can correctly set up the environment to call your rust function on start up.
/// ///
/// # Examples /// # Examples
@ -125,9 +125,7 @@ pub use agb_image_converter::include_gfx;
/// use agb::Gba; /// use agb::Gba;
/// ///
/// #[agb::entry] /// #[agb::entry]
/// fn main() -> ! { /// fn main(mut gba: Gba) -> ! {
/// let mut gba = Gba::new();
///
/// loop {} /// loop {}
/// } /// }
/// ``` /// ```
@ -180,13 +178,7 @@ static mut GBASINGLE: single::Singleton<Gba> = 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 /// 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. /// 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 /// This is will be created for you via the #[agb::entry] attribute.
/// 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.
/// ///
/// # Examples /// # Examples
/// ///
@ -197,9 +189,7 @@ static mut GBASINGLE: single::Singleton<Gba> = single::Singleton::new(unsafe { G
/// use agb::Gba; /// use agb::Gba;
/// ///
/// #[agb::entry] /// #[agb::entry]
/// fn main() -> ! { /// fn main(mut gba: Gba) -> !
/// let mut gba = Gba::new();
///
/// // Do whatever you need to do with gba /// // Do whatever you need to do with gba
/// ///
/// loop {} /// loop {}
@ -219,15 +209,9 @@ pub struct Gba {
} }
impl Gba { impl Gba {
/// Creates a new instance of the Gba struct. #[doc(hidden)]
/// pub unsafe fn new_in_entry() -> Self {
/// Note that you can only create 1 instance, and trying to create a second will panic. GBASINGLE.take()
///
/// # Panics
///
/// Panics if you try to create the second instance.
pub fn new() -> Self {
unsafe { GBASINGLE.take() }
} }
const unsafe fn single_new() -> Self { const unsafe fn single_new() -> Self {
@ -240,12 +224,6 @@ impl Gba {
} }
} }
impl Default for Gba {
fn default() -> Self {
Self::new()
}
}
#[doc(hidden)] #[doc(hidden)]
pub trait Testable { pub trait Testable {
fn run(&self, gba: &mut Gba); fn run(&self, gba: &mut Gba);
@ -283,7 +261,11 @@ fn panic_implementation(info: &core::panic::PanicInfo) -> ! {
loop {} loop {}
} }
#[cfg(test)]
static mut TEST_GBA: Option<Gba> = None;
#[doc(hidden)] #[doc(hidden)]
#[cfg(test)]
pub fn test_runner(tests: &[&dyn Testable]) { pub fn test_runner(tests: &[&dyn Testable]) {
let mut mgba = mgba::Mgba::new().unwrap(); let mut mgba = mgba::Mgba::new().unwrap();
mgba.print( mgba.print(
@ -292,7 +274,7 @@ pub fn test_runner(tests: &[&dyn Testable]) {
) )
.unwrap(); .unwrap();
let mut gba = Gba::new(); let mut gba = unsafe { TEST_GBA.as_mut() }.unwrap();
for test in tests { for test in tests {
test.run(&mut gba); test.run(&mut gba);
@ -307,7 +289,8 @@ pub fn test_runner(tests: &[&dyn Testable]) {
#[cfg(test)] #[cfg(test)]
#[entry] #[entry]
fn agb_test_main() -> ! { fn agb_test_main(gba: Gba) -> ! {
unsafe { TEST_GBA = Some(gba) };
test_main(); test_main();
#[allow(clippy::empty_loop)] #[allow(clippy::empty_loop)]
loop {} loop {}

View file

@ -33,9 +33,7 @@ mod gfx {
// ensures that everything is in order. `agb` will call this after setting up the stack // ensures that everything is in order. `agb` will call this after setting up the stack
// and interrupt handlers correctly. // and interrupt handlers correctly.
#[agb::entry] #[agb::entry]
fn main() -> ! { fn main(mut gba: Gba) -> ! {
let mut gba = Gba::new();
let _tiled = gba.display.video.tiled0(); let _tiled = gba.display.video.tiled0();
let mut object = gba.display.object.get(); let mut object = gba.display.object.get();
gfx::load_sprite_data(&mut object); gfx::load_sprite_data(&mut object);

View file

@ -1,15 +1,15 @@
# The Gba struct # 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 # 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). 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'. 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! 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. Attempting to do so will result in a panic which by default crashes the game.
# How all agb games start # How all agb games start
@ -21,8 +21,6 @@ Replace the content of the `main` function with the following:
# #![no_main] # #![no_main]
# #[agb::entry] # #[agb::entry]
# fn main() -> ! { # fn main() -> ! {
let mut gba = agb::Gba::new();
loop {} // infinite loop for now 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 # What we did
This was a very simple but incredibly important part of any game using `agb`. 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! You are now ready to learn about display modes and how to start getting things onto the screen!

View file

@ -772,10 +772,8 @@ impl<'a, 'b, 'c> PlayingLevel<'a, 'b> {
} }
} }
#[no_mangle] #[agb::entry]
pub fn main() -> ! { fn main(mut agb: agb::Gba) -> ! {
let mut agb = agb::Gba::new();
splash_screen::show_splash_screen(&mut agb, splash_screen::SplashScreen::Start, None, None); splash_screen::show_splash_screen(&mut agb, splash_screen::SplashScreen::Start, None, None);
loop { loop {

View file

@ -2207,9 +2207,7 @@ mod tilemap {
} }
#[agb::entry] #[agb::entry]
fn main() -> ! { fn main(mut gba: agb::Gba) -> ! {
let mut gba = agb::Gba::new();
loop { loop {
game_with_level(&mut gba); game_with_level(&mut gba);
} }

View file

@ -12,12 +12,11 @@
use agb::{display, syscall}; 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 // 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] #[agb::entry]
fn main() -> ! { fn main(mut gba: agb::Gba) -> ! {
let mut gba = agb::Gba::new();
let mut bitmap = gba.display.video.bitmap3(); let mut bitmap = gba.display.video.bitmap3();
for x in 0..display::WIDTH { for x in 0..display::WIDTH {