diff --git a/.travis.yml b/.travis.yml index 181d9f6..f2944ac 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,7 +14,7 @@ before_script: script: - cargo check && cargo check --release - # exclude the examples from the test build, just the lib + # Only run a test build for the library itself - cargo test --lib && cargo test --lib --release - cd book && mdbook build diff --git a/Makefile.toml b/Makefile.toml index 7296114..b8c3ac5 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -16,12 +16,12 @@ dependencies = ["create-target-dir"] command = "arm-none-eabi-as" args = ["crt0.s", "-o", "target/crt0.o"] -[tasks.build-debug] +[tasks.build-examples-debug] dependencies = ["assemble"] command = "cargo" args = ["xbuild", "--examples", "--target", "thumbv4-none-agb.json"] -[tasks.build-release] +[tasks.build-examples-release] dependencies = ["assemble"] command = "cargo" args = ["xbuild", "--examples", "--target", "thumbv4-none-agb.json", "--release"] @@ -56,4 +56,8 @@ fn main() -> std::io::Result<()> { ] [tasks.build] -dependencies = ["build-debug", "build-release", "pack-roms"] +dependencies = ["build-examples-debug", "build-examples-release", "pack-roms"] + +[tasks.test] +command = "cargo" +args = ["test", "--lib"] diff --git a/book/book.toml b/book/book.toml index e684d12..4088ba4 100644 --- a/book/book.toml +++ b/book/book.toml @@ -1,7 +1,6 @@ [book] -title = "Rust GBA Tutorials" +title = "Rust GBA Guide" authors = ["Lokathor"] -#description = "Rust GBA Tutorials." [build] build-dir = "../docs" diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index 1bef5fd..e61db97 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -5,6 +5,7 @@ * [Ch 0: Development Setup](ch00/index.md) * [Ch 1: Hello GBA](ch01/index.md) * [hello1](ch01/hello1.md) + * [Volatile](ch01/volatile.md) * [IO Registers](ch01/io_registers.md) * [The Display Control Register](ch01/the_display_control_register.md) * [Video Memory Intro](ch01/video_memory_intro.md) diff --git a/book/src/ch00/index.md b/book/src/ch00/index.md index 803eb2c..a4537ff 100644 --- a/book/src/ch00/index.md +++ b/book/src/ch00/index.md @@ -4,26 +4,25 @@ Before you can build a GBA game you'll have to follow some special steps to setup the development environment. Perhaps unfortunately, there's enough detail here to warrant a mini-chapter all on its own. -Before we begin I'd like to give a special thanks to **Ketsuban**, who is the -wizard that arranged for all of this to be able to happen and laid out the -details of the plan to the rest of the world. +Once again, extra special thanks to **Ketsuban**, who first dove into how to +make this all work with rust and then shared it with the world. ## Per System Setup -Obviously you need your computer to have a working rust installation. However, -you'll also need to ensure that you're using a nightly toolchain. You can run -`rustup default nightly` to set nightly as the system wide default toolchain, or -you can use a [toolchain +Obviously you need your computer to have a [working rust +installation](https://rustup.rs/). However, you'll also need to ensure that +you're using a nightly toolchain. You can run `rustup default nightly` to set +nightly as the system wide default toolchain, or you can use a [toolchain file](https://github.com/rust-lang-nursery/rustup.rs#the-toolchain-file) to use -nightly just on a specific project, but either way we'll be assuming nightly -from now on. +nightly just on a specific project, but either way we'll be assuming the use of +nightly from now on. -Next you need [devkitpro](https://devkitpro.org/wiki/Getting_Started). They've +Next, you need [devkitpro](https://devkitpro.org/wiki/Getting_Started). They've got a graphical installer for Windows, and `pacman` support on Linux. We'll be -using a few of their binutils for the `arm-none-eabi` target, and we'll also be -using some of their tools that are specific to GBA development, so _even if_ you -already have the right binutils for whatever reason, you'll still want devkitpro -for the `gbafix` utility. +using a few of general their binutils for the `arm-none-eabi` target, and we'll +also be using some of their tools that are specific to GBA development, so _even +if_ you already have the right binutils for whatever reason, you'll still want +devkitpro for the `gbafix` utility. * On Windows you'll want something like `C:\devkitpro\devkitARM\bin` and `C:\devkitpro\tools\bin` to be [added to your @@ -37,21 +36,20 @@ cargo will figure it all out for you. ## Per Project Setup -Now you'll need some particular files each time you want to start a new project. -You can find them in the root of the [rust-console/gba -repo](https://github.com/rust-console/gba). +Once the system wide tools are ready, you'll need some particular files each +time you want to start a new project. You can find them in the root of the +[rust-console/gba repo](https://github.com/rust-console/gba). -* `thumbv4-none-agb.json` describes the overall GBA to cargo-xbuild so it knows - what to do. This is actually a somewhat made up target name since there's no - official target name. The GBA is essentially the same as a normal - `thumbv4-none-eabi` device, but we give it the "agb" signifier so that later - on we'll be able to use rust's `cfg` ability to allow our code to know if it's - specifically targeting a GBA or some other similar device (like an NDS). +* `thumbv4-none-agb.json` describes the overall GBA to cargo-xbuild (and LLVM) + so it knows what to do. Technically the GBA is `thumbv4-none-eabi`, but we + change the `eabi` to `agb` so that we can distinguish it from other `eabi` + devices when using `cfg` flags. * `crt0.s` describes some ASM startup stuff. If you have more ASM to place here later on this is where you can put it. You also need to build it into a `crt0.o` file before it can actually be used, but we'll cover that below. -* `linker.ld` tells the linker more critical info about the layout expectations - that the GBA has about our program. +* `linker.ld` tells the linker all the critical info about the layout + expectations that the GBA has about our program, and that it should also + include the `crt0.o` file with our compiled rust code. ## Compiling @@ -72,13 +70,13 @@ Once you've got something to build, you perform the following steps: as `--release`, and options, such as `--bin foo` or `--examples`, that you'd expect `cargo` to accept. * You **can not** build and run tests this way, because they require `std`, - which the GBA doesn't have. You can still run some of your project's tests - with `cargo test`, but that builds for your local machine, so anything - specific to the GBA (such as reading and writing registers) won't be - testable that way. If you want to isolate and try out some piece code - running on the GBA you'll unfortunately have to make a demo for it in your - `examples/` directory and then run the demo in an emulator and see if it - does what you expect. + which the GBA doesn't have. If you want you can still run some of your + project's tests with `cargo test --lib` or similar, but that builds for your + local machine, so anything specific to the GBA (such as reading and writing + registers) won't be testable that way. If you want to isolate and try out + some piece code running on the GBA you'll unfortunately have to make a demo + for it in your `examples/` directory and then run the demo in an emulator + and see if it does what you expect. * The file extension is important. `cargo xbuild` takes it as a flag to compile dependencies with the same sysroot, so you can include crates normally. Well, creates that work in the GBA's limited environment, but you @@ -132,3 +130,6 @@ transfer to a flash cart there's a little more to do. And you're finally done! Of course, you probably want to make a script for all that, but it's up to you. +On our own project we have it mostly set up within a `Makefile.toml` which runs +using the [cargo-make](https://github.com/sagiegurari/cargo-make) plugin. It's +not really the best plugin, but it's what's available. diff --git a/book/src/ch01/hello1.md b/book/src/ch01/hello1.md index 2044d46..86ab0b5 100644 --- a/book/src/ch01/hello1.md +++ b/book/src/ch01/hello1.md @@ -1,5 +1,6 @@ # hello1 +Our first example will be a totally minimal, full magic number crazy town. Ready? Here goes: `hello1.rs` @@ -8,7 +9,6 @@ Ready? Here goes: #![feature(start)] #![no_std] -#[cfg(not(test))] #[panic_handler] fn panic(_info: &core::panic::PanicInfo) -> ! { loop {} @@ -26,17 +26,18 @@ fn main(_argc: isize, _argv: *const *const u8) -> isize { } ``` -Throw that into your project, build the program (as described back in Chapter -0), and give it a run. You should see a red, green, and blue dot close-ish to -the middle of the screen. If you don't, something already went wrong. Double -check things, phone a friend, write your senators, try asking Ketsuban on the -[Rust Community Discord](https://discordapp.com/invite/aVESxV8), until you're -able to get your three dots going. +Throw that into your project skeleton, build the program (as described back in +Chapter 0), and give it a run in your emulator. You should see a red, green, and +blue dot close-ish to the middle of the screen. If you don't, something already +went wrong. Double check things, phone a friend, write your senators, try asking +Ketsuban on the [Rust Community Discord](https://discordapp.com/invite/aVESxV8), +until you're able to get your three dots going. -## Explaining hello1 +## A basic hello1 explanation So, what just happened? Even if you're used to Rust that might look pretty -strange. We'll go over each part extra carefully. +strange. We'll go over most of the little parts right here, and then bigger +parts will get their own sections. ```rust #![feature(start)] @@ -60,7 +61,6 @@ There's no standard library available on the GBA, so we'll have to live a core only life. ```rust -#[cfg(not(test))] #[panic_handler] fn panic(_info: &core::panic::PanicInfo) -> ! { loop {} @@ -74,10 +74,6 @@ However, right now we don't know how to get any sort of message out to the user so... we do nothing at all. We _can't even return_ from here, so we just sit in an infinite loop. The player will have to reset the universe from the outside. -The `#[cfg(not(test))]` part makes this item only exist in the program when -we're _not_ in a test build. This is so that `cargo test` and such work right as -much as possible. - ```rust #[start] fn main(_argc: isize, _argv: *const *const u8) -> isize { @@ -148,46 +144,5 @@ magic numbers mean or do. * `0x06000000` is the start of Video RAM. So we write some magic to the display control register once, then we write some -other magic to three locations of magic to the Video RAM. We get three dots, -each in their own location... so that second part makes sense at least. - -We'll get into the magic number details in the other sections of this chapter. - -## Sidebar: Volatile - -We'll get into what all that is in a moment, but first let's ask ourselves: Why -are we doing _volatile_ writes? You've probably never used it before at all. -What is volatile anyway? - -Well, the optimizer is pretty aggressive some of the time, and so it'll skip -reads and writes when it thinks can. Like if you write to a pointer once, and -then again a moment later, and it didn't see any other reads in between, it'll -think that it can just skip doing that first write since it'll get overwritten -anyway. Sometimes that's right, but sometimes it's wrong. - -Marking a read or write as _volatile_ tells the compiler that it really must do -that action, and in the exact order that we wrote it out. It says that there -might even be special hardware side effects going on that the compiler isn't -aware of. In this case, the write to the display control register sets a video -mode, and the writes to the Video RAM set pixels that will show up on the -screen. - -Similar to "atomic" operations you might have heard about, all volatile -operations are enforced to happen in the exact order that you specify them, but -only relative to other volatile operations. So something like - -```rust -c.volatile_write(5); -a += b; -d.volatile_write(7); -``` - -might end up changing `a` either before or after the change to `c` (since the -value of `a` doesn't affect the write to `c`), but the write to `d` will -_always_ happen after the write to `c` even though the compiler doesn't see any -direct data dependency there. - -If you ever use volatile stuff on other platforms it's important to note that -volatile doesn't make things thread-safe, you still need atomic for that. -However, the GBA doesn't have threads, so we don't have to worry about thread -safety concerns. +other magic to three magic locations in the Video RAM. Somehow that shows three +dots. Gotta read on to find out why! diff --git a/book/src/ch01/hello2.md b/book/src/ch01/hello2.md index 23c413b..7c991d4 100644 --- a/book/src/ch01/hello2.md +++ b/book/src/ch01/hello2.md @@ -8,7 +8,6 @@ Okay so let's have a look again: #![feature(start)] #![no_std] -#[cfg(not(test))] #[panic_handler] fn panic(_info: &core::panic::PanicInfo) -> ! { loop {} @@ -28,10 +27,11 @@ fn main(_argc: isize, _argv: *const *const u8) -> isize { Now let's clean this up so that it's clearer what's going on. -First we'll label that display control stuff: +First we'll label that display control stuff, including using the `VolatilePtr` +type from the volatile explanation: ```rust -pub const DISPCNT: *mut u16 = 0x04000000 as *mut u16; +pub const DISPCNT: VolatilePtr = VolatilePtr(0x04000000 as *mut u16); pub const MODE3: u16 = 3; pub const BG2: u16 = 0b100_0000_0000; ``` @@ -43,9 +43,12 @@ pub const VRAM: usize = 0x06000000; pub const SCREEN_WIDTH: isize = 240; ``` -And then we want a small helper function for putting together a color value. +Note that VRAM has to be interpreted in different ways depending on mode, so we +just leave it as `usize` and we'll cast it into the right form closer to the +actual use. -Happily, this one can even be declared as a const function. At the time of +Next we want a small helper function for putting together a color value. +Happily, this one can even be declared as a `const` function. At the time of writing, we've got the "minimal const fn" support in nightly. It really is quite limited, but I'm happy to let rustc and LLVM pre-compute as much as they can when it comes to the GBA's tiny CPU. @@ -62,7 +65,7 @@ usually helps you think about it a lot better. ```rust pub unsafe fn mode3_pixel(col: isize, row: isize, color: u16) { - (VRAM as *mut u16).offset(col + row * SCREEN_WIDTH).write_volatile(color); + VolatilePtr(VRAM as *mut u16).offset(col + row * SCREEN_WIDTH).write(color); } ``` @@ -74,7 +77,6 @@ So now we've got this: #![feature(start)] #![no_std] -#[cfg(not(test))] #[panic_handler] fn panic(_info: &core::panic::PanicInfo) -> ! { loop {} @@ -83,7 +85,7 @@ fn panic(_info: &core::panic::PanicInfo) -> ! { #[start] fn main(_argc: isize, _argv: *const *const u8) -> isize { unsafe { - DISPCNT.write_volatile(MODE3 | BG2); + DISPCNT.write(MODE3 | BG2); mode3_pixel(120, 80, rgb16(31, 0, 0)); mode3_pixel(136, 80, rgb16(0, 31, 0)); mode3_pixel(120, 96, rgb16(0, 0, 31)); @@ -91,7 +93,22 @@ fn main(_argc: isize, _argv: *const *const u8) -> isize { } } -pub const DISPCNT: *mut u16 = 0x04000000 as *mut u16; +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)] +#[repr(transparent)] +pub struct VolatilePtr(pub *mut T); +impl VolatilePtr { + pub unsafe fn read(&self) -> T { + core::ptr::read_volatile(self.0) + } + pub unsafe fn write(&self, data: T) { + core::ptr::write_volatile(self.0, data); + } + pub unsafe fn offset(self, count: isize) -> Self { + VolatilePtr(self.0.wrapping_offset(count)) + } +} + +pub const DISPCNT: VolatilePtr = VolatilePtr(0x04000000 as *mut u16); pub const MODE3: u16 = 3; pub const BG2: u16 = 0b100_0000_0000; @@ -103,7 +120,7 @@ pub const fn rgb16(red: u16, green: u16, blue: u16) -> u16 { } pub unsafe fn mode3_pixel(col: isize, row: isize, color: u16) { - (VRAM as *mut u16).offset(col + row * SCREEN_WIDTH).write_volatile(color); + VolatilePtr(VRAM as *mut u16).offset(col + row * SCREEN_WIDTH).write(color); } ``` diff --git a/book/src/ch01/the_display_control_register.md b/book/src/ch01/the_display_control_register.md index 6c20fdf..3c3c668 100644 --- a/book/src/ch01/the_display_control_register.md +++ b/book/src/ch01/the_display_control_register.md @@ -107,6 +107,3 @@ First let's [convert that to binary](https://www.wolframalpha.com/input/?i=0x0403), and we get `0b100_0000_0011`. So, that's setting Mode 3 with background 2 enabled and nothing else special. - -However, I think we can do better than that. This is a prime target for more -newtyping as we attempt a `hello2` program. diff --git a/book/src/ch01/video_memory_intro.md b/book/src/ch01/video_memory_intro.md index 6b82d93..fb133cb 100644 --- a/book/src/ch01/video_memory_intro.md +++ b/book/src/ch01/video_memory_intro.md @@ -106,8 +106,8 @@ So at pixels `(120,80)`, `(136,80)`, and `(120,96)` we write three values. Once again we probably need to [convert them](https://www.wolframalpha.com/) into binary to make sense of it. -* 0x001F: 0b11111 -* 0x03E0: 0b11111_00000 -* 0x7C00: 0b11111_00000_00000 +* 0x001F: 0b0_00000_00000_11111 +* 0x03E0: 0b0_00000_11111_00000 +* 0x7C00: 0b0_11111_00000_00000 Ah, of course, a red pixel, a green pixel, and a blue pixel. diff --git a/book/src/ch01/volatile.md b/book/src/ch01/volatile.md new file mode 100644 index 0000000..92ce7e5 --- /dev/null +++ b/book/src/ch01/volatile.md @@ -0,0 +1,70 @@ +# Volatile + +Before we focus on what the numbers mean, first let's ask ourselves: Why are we +doing _volatile_ writes? You've probably never used that keywords before at all. +What _is_ volatile anyway? + +Well, the optimizer is pretty aggressive, and so it'll skip reads and writes +when it thinks can. Like if you write to a pointer once, and then again a moment +later, and it didn't see any other reads in between, it'll think that it can +just skip doing that first write since it'll get overwritten anyway. Sometimes +that's correct, but sometimes it's not. + +Marking a read or write as _volatile_ tells the compiler that it really must do +that action, and in the exact order that we wrote it out. It says that there +might even be special hardware side effects going on that the compiler isn't +aware of. In this case, the write to the display control register sets a video +mode, and the writes to the Video RAM set pixels that will show up on the +screen. + +Similar to "atomic" operations you might have heard about, all volatile +operations are enforced to happen in the exact order that you specify them, but +only relative to other volatile operations. So something like + +```rust +c.volatile_write(5); +a += b; +d.volatile_write(7); +``` + +might end up changing `a` either before or after the change to `c` (since the +value of `a` doesn't affect the write to `c`), but the write to `d` will +_always_ happen after the write to `c`, even though the compiler doesn't see any +direct data dependency there. + +If you ever go on to use volatile stuff on other platforms it's important to +note that volatile doesn't make things thread-safe, you still need atomic for +that. However, the GBA doesn't have threads, so we don't have to worry about +those sorts of thread safety concerns (there's interrupts, but that's another +matter). + +## Volatile by default + +Of course, writing out `volatile_write` every time is more than we wanna do. +There's clarity and then there's excessive. This is a chance to write our first +[newtype](https://doc.rust-lang.org/1.0.0/style/features/types/newtype.html). +Basically a type that's got the exact same binary representation as some other +type, but new methods and trait implementations. + +We want a `*mut T` that's volatile by default, and also when we offset it... +well the verdict is slightly unclear on how `offset` vs `wrapping_offset` work +when you're using pointers that you made up out of nowhere. I've asked the +experts and they genuinely weren't sure, so we'll make an `offset` method that +does a `wrapping_offset` just to be careful. + +```rust +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)] +#[repr(transparent)] +pub struct VolatilePtr(pub *mut T); +impl VolatilePtr { + pub unsafe fn read(&self) -> T { + core::ptr::read_volatile(self.0) + } + pub unsafe fn write(&self, data: T) { + core::ptr::write_volatile(self.0, data); + } + pub unsafe fn offset(self, count: isize) -> Self { + VolatilePtr(self.0.wrapping_offset(count)) + } +} +``` diff --git a/book/src/ch02/light_cycle.md b/book/src/ch02/light_cycle.md index f2e031a..3044b71 100644 --- a/book/src/ch02/light_cycle.md +++ b/book/src/ch02/light_cycle.md @@ -16,21 +16,21 @@ We need some better drawing operations this time around. pub unsafe fn mode3_clear_screen(color: u16) { let color = color as u32; let bulk_color = color << 16 | color; - let mut ptr = VRAM as *mut u32; + let mut ptr = VolatilePtr(VRAM as *mut u32); for _ in 0..SCREEN_HEIGHT { for _ in 0..(SCREEN_WIDTH / 2) { - ptr.write_volatile(bulk_color); + ptr.write(bulk_color); ptr = ptr.offset(1); } } } pub unsafe fn mode3_draw_pixel(col: isize, row: isize, color: u16) { - (VRAM as *mut u16).offset(col + row * SCREEN_WIDTH).write_volatile(color); + VolatilePtr(VRAM as *mut u16).offset(col + row * SCREEN_WIDTH).write(color); } pub unsafe fn mode3_read_pixel(col: isize, row: isize) -> u16 { - (VRAM as *mut u16).offset(col + row * SCREEN_WIDTH).read_volatile() + VolatilePtr(VRAM as *mut u16).offset(col + row * SCREEN_WIDTH).read() } ``` @@ -46,7 +46,7 @@ Now we just have to fill in the main function: #[start] fn main(_argc: isize, _argv: *const *const u8) -> isize { unsafe { - DISPCNT.write_volatile(MODE3 | BG2); + DISPCNT.write(MODE3 | BG2); } let mut px = SCREEN_WIDTH / 2; @@ -55,7 +55,7 @@ fn main(_argc: isize, _argv: *const *const u8) -> isize { loop { // read the input for this frame - let this_frame_keys = read_key_input(); + let this_frame_keys = key_input(); // adjust game state and wait for vblank px += 2 * this_frame_keys.column_direction() as isize; diff --git a/book/src/ch02/the_key_input_register.md b/book/src/ch02/the_key_input_register.md index 16bf6fa..b151de2 100644 --- a/book/src/ch02/the_key_input_register.md +++ b/book/src/ch02/the_key_input_register.md @@ -56,15 +56,15 @@ a `u16` and then wrap that in our newtype which will implement methods for reading and writing the key bits. ```rust -pub const KEYINPUT: *mut u16 = 0x400_0130 as *mut u16; +pub const KEYINPUT: VolatilePtr = VolatilePtr(0x400_0130 as *mut u16); /// A newtype over the key input state of the GBA. #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] #[repr(transparent)] pub struct KeyInputSetting(u16); -pub fn read_key_input() -> KeyInputSetting { - unsafe { KeyInputSetting(KEYINPUT.read_volatile()) } +pub fn key_input() -> KeyInputSetting { + unsafe { KeyInputSetting(KEYINPUT.read()) } } ``` @@ -98,7 +98,7 @@ a mask for only the 10 used bits we can flip the "low when pressed" values so that the entire result has active bits in all positions where a key is pressed. ```rust -pub fn read_key_input() -> KeyInputSetting { +pub fn key_input() -> KeyInputSetting { unsafe { KeyInputSetting(KEYINPUT.read_volatile() ^ 0b0000_0011_1111_1111) } } ``` diff --git a/book/src/ch02/the_vcount_register.md b/book/src/ch02/the_vcount_register.md index 3a427b9..20da70b 100644 --- a/book/src/ch02/the_vcount_register.md +++ b/book/src/ch02/the_vcount_register.md @@ -46,10 +46,10 @@ quickly during the blank period. So first we want a way to check the vcount value at all: ```rust -pub const VCOUNT: *mut u16 = 0x0400_0006 as *mut u16; +pub const VCOUNT: VolatilePtr = VolatilePtr(0x0400_0006 as *mut u16); -pub fn read_vcount() -> u16 { - unsafe { VCOUNT.read_volatile() } +pub fn vcount() -> u16 { + unsafe { VCOUNT.read() } } ``` @@ -59,11 +59,11 @@ Then we want two little helper functions to wait until VBlank and vdraw. pub const SCREEN_HEIGHT: isize = 160; pub fn wait_until_vblank() { - while read_vcount() < SCREEN_HEIGHT as u16 {} + while vcount() < SCREEN_HEIGHT as u16 {} } pub fn wait_until_vdraw() { - while read_vcount() >= SCREEN_HEIGHT as u16 {} + while vcount() >= SCREEN_HEIGHT as u16 {} } ``` diff --git a/book/src/ch03/index.md b/book/src/ch03/index.md index d2dfbd8..509119f 100644 --- a/book/src/ch03/index.md +++ b/book/src/ch03/index.md @@ -1 +1,17 @@ # Ch 3: Memory and Objects + +Alright so we can do some basic "movement", but we left a big trail in the video +memory of where we went. Most of the time that's not what we want at all. If we +want to draw something over top of our background without trashing the +background memory that's an "object" (but not in the "Object Oriented" sense). +You might recall that objects have their own layer that you can enable in the +display control register. + +Of course, once we're drawing these objects we'll want some scratch space to +work with them a bit, so we'll finally go over the GBA's full memory layout. + +And since most games are pretty boring without an RNG, we'll cover the kinds of +RNG that you might want to include in a GBA game. + +Then we'll do something or other that includes moving things and and RNG... +which is pretty much any game at all. diff --git a/book/src/introduction.md b/book/src/introduction.md index 3c4afd0..a202d07 100644 --- a/book/src/introduction.md +++ b/book/src/introduction.md @@ -1,8 +1,38 @@ # Introduction -Here's a book that'll help you program in Rust on the GBA. +Here's a book that'll help you program in Rust on the Game Boy Advance (GBA). -It's very "work in progress". At the moment there's only one demo program. +It's a work in progress of course, but so is most of everything in Rust. + +## Style and Purpose + +I'm out to teach you how to program in Rust on the GBA, obviously. However, +while there _is_ a [gba](https://github.com/rust-console/gba) crate, and while I +genuinely believe it to be a good and useful crate for GBA programming, we _will +not_ be using the `gba` crate within this book. In fact we won't be using any +crates at all. We can call it the [Handmade Hero](https://handmadehero.org/) +approach, if you like. + +I don't want to just teach you how to use the `gba` crate, I want to teach you +what you'd need to know to write the crate from scratch if it wasn't there. + +Each chapter of the book will focus on a few things you'll need to know about +GBA programming and then present a fully self-contained example that puts those +ideas into action. Just one file per example, no dependencies, no external +assets, no fuss. The examples will be in the text of the book within code +blocks, but also you can find them in the [examples +directory](https://github.com/rust-console/gba/tree/master/examples) of the repo +if you want to get them that way. + +## Expected Knowledge + +I will try not to ask too much of the reader ahead of time, but you are expected +to have already read [The Rust Book](https://doc.rust-lang.org/book/). + +It's very difficult to know when you've said something that someone else won't +already know about, or if you're presenting ideas out of order. If things aren't +clear please [file an issue](https://github.com/rust-console/gba/issues) and +we'll try to address it. ## Getting Help @@ -14,11 +44,12 @@ channel. * `Lokathor` is the fool who decided to write a crate and book for it. If it's _not_ a GBA specific question then you can probably ask any of the other -folks in the server as well. +folks in the server as well (there's a few hundred folks). -## Other Works +## Further Reading -If you want to read more about developing on the GBA there are some other good resources as well: +If you want to read more about developing on the GBA there are some other good +resources as well: * [Tonc](https://www.coranac.com/tonc/text/toc.htm), a tutorial series written for C, but it's what I based the ordering of this book's sections on. diff --git a/docs/ch00/index.html b/docs/ch00/index.html index a06e96c..d499d65 100644 --- a/docs/ch00/index.html +++ b/docs/ch00/index.html @@ -3,7 +3,7 @@ - Ch 0: Development Setup - Rust GBA Tutorials + Ch 0: Development Setup - Rust GBA Guide @@ -72,7 +72,7 @@
@@ -102,7 +102,7 @@
-

Rust GBA Tutorials

+

Rust GBA Guide

@@ -275,7 +275,10 @@ ROM is patched in place, so we don't even need to specify a new destination.

And you're finally done!

-

Of course, you probably want to make a script for all that, but it's up to you.

+

Of course, you probably want to make a script for all that, but it's up to you. +On our own project we have it mostly set up within a Makefile.toml which runs +using the cargo-make plugin. It's +not really the best plugin, but it's what's available.

diff --git a/docs/ch01/hello1.html b/docs/ch01/hello1.html index 258b519..6fef655 100644 --- a/docs/ch01/hello1.html +++ b/docs/ch01/hello1.html @@ -3,7 +3,7 @@ - hello1 - Rust GBA Tutorials + hello1 - Rust GBA Guide @@ -72,7 +72,7 @@
@@ -102,7 +102,7 @@
-

Rust GBA Tutorials

+

Rust GBA Guide

@@ -137,12 +137,12 @@

hello1

-

Ready? Here goes:

+

Our first example will be a totally minimal, full magic number crazy town. +Ready? Here goes:

hello1.rs

#![feature(start)]
 #![no_std]
 
-#[cfg(not(test))]
 #[panic_handler]
 fn panic(_info: &core::panic::PanicInfo) -> ! {
   loop {}
@@ -159,12 +159,12 @@ fn main(_argc: isize, _argv: *const *const u8) -> isize {
   }
 }
 
-

Throw that into your project, build the program (as described back in Chapter -0), and give it a run. You should see a red, green, and blue dot close-ish to -the middle of the screen. If you don't, something already went wrong. Double -check things, phone a friend, write your senators, try asking Ketsuban on the -Rust Community Discord, until you're -able to get your three dots going.

+

Throw that into your project skeleton, build the program (as described back in +Chapter 0), and give it a run in your emulator. You should see a red, green, and +blue dot close-ish to the middle of the screen. If you don't, something already +went wrong. Double check things, phone a friend, write your senators, try asking +Ketsuban on the Rust Community Discord, +until you're able to get your three dots going.

Explaining hello1

So, what just happened? Even if you're used to Rust that might look pretty strange. We'll go over each part extra carefully.

@@ -191,7 +191,6 @@ only life.


 # #![allow(unused_variables)]
 #fn main() {
-#[cfg(not(test))]
 #[panic_handler]
 fn panic(_info: &core::panic::PanicInfo) -> ! {
   loop {}
@@ -203,9 +202,6 @@ Basically, if we somehow trigger a panic, this is where the program goes.
 However, right now we don't know how to get any sort of message out to the user
 so... we do nothing at all. We can't even return from here, so we just sit in
 an infinite loop. The player will have to reset the universe from the outside.

-

The #[cfg(not(test))] part makes this item only exist in the program when -we're not in a test build. This is so that cargo test and such work right as -much as possible.

#[start]
 fn main(_argc: isize, _argv: *const *const u8) -> isize {
 
@@ -271,40 +267,8 @@ magic numbers mean or do.

So we write some magic to the display control register once, then we write some other magic to three locations of magic to the Video RAM. We get three dots, each in their own location... so that second part makes sense at least.

-

We'll get into the magic number details in the other sections of this chapter.

-

Sidebar: Volatile

-

We'll get into what all that is in a moment, but first let's ask ourselves: Why -are we doing volatile writes? You've probably never used it before at all. -What is volatile anyway?

-

Well, the optimizer is pretty aggressive some of the time, and so it'll skip -reads and writes when it thinks can. Like if you write to a pointer once, and -then again a moment later, and it didn't see any other reads in between, it'll -think that it can just skip doing that first write since it'll get overwritten -anyway. Sometimes that's right, but sometimes it's wrong.

-

Marking a read or write as volatile tells the compiler that it really must do -that action, and in the exact order that we wrote it out. It says that there -might even be special hardware side effects going on that the compiler isn't -aware of. In this case, the write to the display control register sets a video -mode, and the writes to the Video RAM set pixels that will show up on the -screen.

-

Similar to "atomic" operations you might have heard about, all volatile -operations are enforced to happen in the exact order that you specify them, but -only relative to other volatile operations. So something like

-

-# #![allow(unused_variables)]
-#fn main() {
-c.volatile_write(5);
-a += b;
-d.volatile_write(7);
-#}
-

might end up changing a either before or after the change to c (since the -value of a doesn't affect the write to c), but the write to d will -always happen after the write to c even though the compiler doesn't see any -direct data dependency there.

-

If you ever use volatile stuff on other platforms it's important to note that -volatile doesn't make things thread-safe, you still need atomic for that. -However, the GBA doesn't have threads, so we don't have to worry about thread -safety concerns.

+

But what exactly is going on? Well that's what the whole rest of this chapter +(and this book) is about.

@@ -317,7 +281,7 @@ safety concerns.

- @@ -335,7 +299,7 @@ safety concerns.

- diff --git a/docs/ch01/hello2.html b/docs/ch01/hello2.html index a24fb69..283efe2 100644 --- a/docs/ch01/hello2.html +++ b/docs/ch01/hello2.html @@ -3,7 +3,7 @@ - hello2 - Rust GBA Tutorials + hello2 - Rust GBA Guide @@ -72,7 +72,7 @@
@@ -102,7 +102,7 @@
-

Rust GBA Tutorials

+

Rust GBA Guide

@@ -142,7 +142,6 @@
#![feature(start)]
 #![no_std]
 
-#[cfg(not(test))]
 #[panic_handler]
 fn panic(_info: &core::panic::PanicInfo) -> ! {
   loop {}
@@ -202,7 +201,6 @@ pub unsafe fn mode3_pixel(col: isize, row: isize, color: u16) {
 
#![feature(start)]
 #![no_std]
 
-#[cfg(not(test))]
 #[panic_handler]
 fn panic(_info: &core::panic::PanicInfo) -> ! {
   loop {}
diff --git a/docs/ch01/index.html b/docs/ch01/index.html
index 6d3057e..4c8ae87 100644
--- a/docs/ch01/index.html
+++ b/docs/ch01/index.html
@@ -3,7 +3,7 @@
     
         
         
-        Ch 1: Hello GBA - Rust GBA Tutorials
+        Ch 1: Hello GBA - Rust GBA Guide
         
         
         
@@ -72,7 +72,7 @@
         
 
         
 
         
@@ -102,7 +102,7 @@
-

Rust GBA Tutorials

+

Rust GBA Guide

diff --git a/docs/ch01/io_registers.html b/docs/ch01/io_registers.html index f51df8b..b2e20e3 100644 --- a/docs/ch01/io_registers.html +++ b/docs/ch01/io_registers.html @@ -3,7 +3,7 @@ - IO Registers - Rust GBA Tutorials + IO Registers - Rust GBA Guide @@ -72,7 +72,7 @@
@@ -102,7 +102,7 @@
-

Rust GBA Tutorials

+

Rust GBA Guide

@@ -176,7 +176,7 @@ array index is.