keyinput section

This commit is contained in:
Lokathor 2018-11-12 14:57:28 -07:00
parent 44abf95841
commit cdb9be13d6
10 changed files with 724 additions and 8 deletions

View file

@ -11,3 +11,9 @@ Also, we will need a way to keep the program from running "too fast". On a
modern computer or console you do this with vsync info from the GPU and Monitor,
and on the GBA we'll be using vsync info from an IO register that tracks what
the display hardware is doing.
For this chapter we'll make a copy of `hello2.rs` named `snake.rs` and then fill
it in as we go. Normally you might not place the entire program into a single
source file, but since these are examples it's slightly better to have them be
completely self contained than it is to have them be "properly organized" for
the long term.

View file

@ -1,3 +1,210 @@
# The Key Input Register
TODO: describe all the stuff about key input
The Key Input Register is our next IO register. Its shorthand name is
[KEYINPUT](http://problemkaputt.de/gbatek.htm#gbakeypadinput) and it's a `u16`
at `0x4000130`. The entire register is obviosuly read only, you can't tell the
GBA what buttons are pressed.
Each button is exactly one bit:
| Bit | Button |
|:---:|:------:|
| 0 | A |
| 1 | B |
| 2 | Select |
| 3 | Start |
| 4 | Right |
| 5 | Left |
| 6 | Up |
| 7 | Down |
| 8 | R |
| 9 | L |
The higher bits above are not used at all.
Similar to other old hardware devices, the convention here is that a button's
bit is **clear when pressed, active when released**. In other words, when the
user is not touching the device at all the KEYINPUT value will read
`0b0000_0011_1111_1111`. There's similar values for when the user is pressing as
many buttons as possible, but since the left/right and up/down keys are on an
arrow pad the value can never be 0 since you can't ever press every single key
at once.
When dealing with key input, the register always shows the exact key values at
any moment you read it. Obviously that's what it should do, but what it means to
you as a programmer is that you should usually gather input once at the top of a
game frame and then use that single input poll as the input values across the
whole game frame.
Of course, you might want to know if a user's key state changed from frame to
frame. That's fairly easy too: We just store the last frame keys as well as the
current frame keys (it's only a `u16`) and then we can xor the two values.
Anything that shows up in the xor result is a key that changed. If it's changed
and it's now down, that means it was pushed this frame. If it's changed and it's
now up, that means it was released this frame.
The other major thing you might frequently want is to know "which way" the arrow
pad is pointing: Up/Down/None and Left/Right/None. Sounds like an enum to me.
Except that often time we'll have situations where the direction just needs to
be multiplied by a speed and applied as a delta to a position. We want to
support that as well as we can too.
## Key Input Code
Let's get down to some code. First we want to make a way to read the address as
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;
/// 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.volatile_read()) }
}
```
Now we want a way to check if a key is _being pressed_, since that's normally
how we think of things as a game designer and even as a player. That is, usually
you'd say "if you press A, then X happens" instead of "if you don't press A,
then X does not happen".
Normally we'd pick a constant for the bit we want, `&` it with our value, and
then check for `val != 0`. Since the bit we're looking for is `0` in the "true"
state we still pick the same constant and we still do the `&`, but we test with
`== 0`. Practically the same, right? Well, since I'm asking a rhetorical
question like that you can probably already guess that it's not the same. I was
shocked to learn this too.
All we have to do is ask our good friend
[Godbolt](https://rust.godbolt.org/z/d-8oCe) what's gonna happen when the code
compiles. The link there has the page set for the `stable` 1.30 compiler just so
that the link results stay consistent if you read this book in a year or
something. Also, we've set the target to `thumbv6m-none-eabi`, which is a
slightly later version of ARM than the actual GBA, but it's close enough for
just checking. Of course, in a full program small functions like these will
probably get inlined into the calling code and disappear entirely as they're
folded and refolded by the compiler, and so on. Still, we can see that in the
simple case when the compiler doesn't know any extra context, the `!=0` test is
4 instructions and the `==0` test is six instructions. Since we want to get
saving where we can, we'll always use a `!=0` test and we'll adjust how we
initially read the register to compensate.
```rust
pub fn read_key_input() -> KeyInputSetting {
unsafe { KeyInputSetting(KEYINPUT.volatile_read() ^ 0b1111_1111_1111_1111) }
}
```
Now we add a method for seeing if a key is pressed. In the full library there's
a more advanced version of this that's built up via macro, but for this example
we'll just name a bunch of `const` values and then have a method that takes a
value and says if that bit is on.
```rust
pub const KEY_A: u16 = 1 << 0;
pub const KEY_B: u16 = 1 << 1;
pub const KEY_SELECT: u16 = 1 << 2;
pub const KEY_START: u16 = 1 << 3;
pub const KEY_RIGHT: u16 = 1 << 4;
pub const KEY_LEFT: u16 = 1 << 5;
pub const KEY_UP: u16 = 1 << 6;
pub const KEY_DOWN: u16 = 1 << 7;
pub const KEY_R: u16 = 1 << 8;
pub const KEY_L: u16 = 1 << 9;
impl KeyInputSetting {
pub fn contains(&self, key: u16) -> bool {
(self.0 & key) != 0
}
}
```
Because each key is a unique bit you can even check for more than one key at
once by just adding two key values together.
```rust
let input_contains_a_and_l = input.contains(KEY_A + KEY_L);
```
And we wanted to save the state of an old frame and compare it to the current
frame to see what was different:
```rust
pub fn difference(&self, other: KeyInputSetting) -> KeyInputSetting {
KeyInputSetting(self.0 ^ other.0)
}
```
Anything that's "in" the difference output is a key that _changed_, and then if
the key reads as pressed this frame that means it was just pressed. The exact
mechanics of all the ways you might care to do something based on new key
presses is obviously quite varied, but it might be something like this:
```rust
let this_frame_diff = this_frame_input.difference(last_frame_input);
if this_frame_diff.contains(KEY_B) && this_frame_input.contains(KEY_B) {
// the user just pressed B, react in some way
}
```
And for the arrow pad, we'll make an enum that easily casts into `i32`. Whenever
we're working with stuff we can try to use `i32` / `isize` as often as possible
just because it's easier on the GBA's CPU if we stick to its native number size.
Having it be an enum lets us use `match` and be sure that we've covered all our
cases.
```rust
/// A "tribool" value helps us interpret the arrow pad.
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
#[repr(i32)]
pub enum TriBool {
Minus = -1,
Neutral = 0,
Plus = +1,
}
```
Now, how do we determine _which way_ is plus or minus? Well... I don't know.
Really. I'm not sure what the best one is because the GBA really wants the
origin at 0,0 with higher rows going down and higher cols going right. On the
other hand, all the normal math you and I learned in school is oriented with
increasing Y being upward on the page. So, at least for this demo, we're going
to go with what the GBA wants us to do and give it a try. If we don't end up
confusing ourselves then we can stick with that. Maybe we can cover it over
somehow later on.
```rust
pub fn column_direction(&self) -> TriBool {
if self.contains(KEY_RIGHT) {
TriBool::Plus
} else if self.contains(KEY_LEFT) {
TriBool::Minus
} else {
TriBool::Neutral
}
}
pub fn row_direction(&self) -> TriBool {
if self.contains(KEY_DOWN) {
TriBool::Plus
} else if self.contains(KEY_UP) {
TriBool::Minus
} else {
TriBool::Neutral
}
}
```
So then every frame we can check for `column_direction` and `row_direction` and
then apply those to the snake's current position to make it move around the
screen.
With that I think we're all done with user input for now. There's some other
things to eventually know about like key interrupts that you can set and stuff,
but we'll cover that later on because it's not necessary right now.

View file

@ -146,6 +146,11 @@ SNES had. As you can guess, we get key state info from an IO register.</p>
modern computer or console you do this with vsync info from the GPU and Monitor,
and on the GBA we'll be using vsync info from an IO register that tracks what
the display hardware is doing.</p>
<p>For this chapter we'll make a copy of <code>hello2.rs</code> named <code>snake.rs</code> and then fill
it in as we go. Normally you might not place the entire program into a single
source file, but since these are examples it's slightly better to have them be
completely self contained than it is to have them be &quot;properly organized&quot; for
the long term.</p>
</main>

View file

@ -137,7 +137,202 @@
<div id="content" class="content">
<main>
<a class="header" href="#the-key-input-register" id="the-key-input-register"><h1>The Key Input Register</h1></a>
<p>TODO: describe all the stuff about key input</p>
<p>The Key Input Register is our next IO register. Its shorthand name is
<a href="http://problemkaputt.de/gbatek.htm#gbakeypadinput">KEYINPUT</a> and it's a <code>u16</code>
at <code>0x4000130</code>. The entire register is obviosuly read only, you can't tell the
GBA what buttons are pressed.</p>
<p>Each button is exactly one bit:</p>
<table><thead><tr><th align="center"> Bit </th><th align="center"> Button </th></tr></thead><tbody>
<tr><td align="center"> 0 </td><td align="center"> A </td></tr>
<tr><td align="center"> 1 </td><td align="center"> B </td></tr>
<tr><td align="center"> 2 </td><td align="center"> Select </td></tr>
<tr><td align="center"> 3 </td><td align="center"> Start </td></tr>
<tr><td align="center"> 4 </td><td align="center"> Right </td></tr>
<tr><td align="center"> 5 </td><td align="center"> Left </td></tr>
<tr><td align="center"> 6 </td><td align="center"> Up </td></tr>
<tr><td align="center"> 7 </td><td align="center"> Down </td></tr>
<tr><td align="center"> 8 </td><td align="center"> R </td></tr>
<tr><td align="center"> 9 </td><td align="center"> L </td></tr>
</tbody></table>
<p>The higher bits above are not used at all.</p>
<p>Similar to other old hardware devices, the convention here is that a button's
bit is <strong>clear when pressed, active when released</strong>. In other words, when the
user is not touching the device at all the KEYINPUT value will read
<code>0b0000_0011_1111_1111</code>. There's similar values for when the user is pressing as
many buttons as possible, but since the left/right and up/down keys are on an
arrow pad the value can never be 0 since you can't ever press every single key
at once.</p>
<p>When dealing with key input, the register always shows the exact key values at
any moment you read it. Obviously that's what it should do, but what it means to
you as a programmer is that you should usually gather input once at the top of a
game frame and then use that single input poll as the input values across the
whole game frame.</p>
<p>Of course, you might want to know if a user's key state changed from frame to
frame. That's fairly easy too: We just store the last frame keys as well as the
current frame keys (it's only a <code>u16</code>) and then we can xor the two values.
Anything that shows up in the xor result is a key that changed. If it's changed
and it's now down, that means it was pushed this frame. If it's changed and it's
now up, that means it was released this frame.</p>
<p>The other major thing you might frequently want is to know &quot;which way&quot; the arrow
pad is pointing: Up/Down/None and Left/Right/None. Sounds like an enum to me.
Except that often time we'll have situations where the direction just needs to
be multiplied by a speed and applied as a delta to a position. We want to
support that as well as we can too.</p>
<a class="header" href="#key-input-code" id="key-input-code"><h2>Key Input Code</h2></a>
<p>Let's get down to some code. First we want to make a way to read the address as
a <code>u16</code> and then wrap that in our newtype which will implement methods for
reading and writing the key bits.</p>
<pre><pre class="playpen"><code class="language-rust">
# #![allow(unused_variables)]
#fn main() {
pub const KEYINPUT: *mut u16 = 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() -&gt; KeyInputSetting {
unsafe { KeyInputSetting(KEYINPUT.volatile_read()) }
}
#}</code></pre></pre>
<p>Now we want a way to check if a key is <em>being pressed</em>, since that's normally
how we think of things as a game designer and even as a player. That is, usually
you'd say &quot;if you press A, then X happens&quot; instead of &quot;if you don't press A,
then X does not happen&quot;.</p>
<p>Normally we'd pick a constant for the bit we want, <code>&amp;</code> it with our value, and
then check for <code>val != 0</code>. Since the bit we're looking for is <code>0</code> in the &quot;true&quot;
state we still pick the same constant and we still do the <code>&amp;</code>, but we test with
<code>== 0</code>. Practically the same, right? Well, since I'm asking a rhetorical
question like that you can probably already guess that it's not the same. I was
shocked to learn this too.</p>
<p>All we have to do is ask our good friend
<a href="https://rust.godbolt.org/z/d-8oCe">Godbolt</a> what's gonna happen when the code
compiles. The link there has the page set for the <code>stable</code> 1.30 compiler just so
that the link results stay consistent if you read this book in a year or
something. Also, we've set the target to <code>thumbv6m-none-eabi</code>, which is a
slightly later version of ARM than the actual GBA, but it's close enough for
just checking. Of course, in a full program small functions like these will
probably get inlined into the calling code and disappear entirely as they're
folded and refolded by the compiler, and so on. Still, we can see that in the
simple case when the compiler doesn't know any extra context, the <code>!=0</code> test is
4 instructions and the <code>==0</code> test is six instructions. Since we want to get
saving where we can, we'll always use a <code>!=0</code> test and we'll adjust how we
initially read the register to compensate.</p>
<pre><pre class="playpen"><code class="language-rust">
# #![allow(unused_variables)]
#fn main() {
pub fn read_key_input() -&gt; KeyInputSetting {
unsafe { KeyInputSetting(KEYINPUT.volatile_read() ^ 0b1111_1111_1111_1111) }
}
#}</code></pre></pre>
<p>Now we add a method for seeing if a key is pressed. In the full library there's
a more advanced version of this that's built up via macro, but for this example
we'll just name a bunch of <code>const</code> values and then have a method that takes a
value and says if that bit is on.</p>
<pre><pre class="playpen"><code class="language-rust">
# #![allow(unused_variables)]
#fn main() {
pub const KEY_A: u16 = 1 &lt;&lt; 0;
pub const KEY_B: u16 = 1 &lt;&lt; 1;
pub const KEY_SELECT: u16 = 1 &lt;&lt; 2;
pub const KEY_START: u16 = 1 &lt;&lt; 3;
pub const KEY_RIGHT: u16 = 1 &lt;&lt; 4;
pub const KEY_LEFT: u16 = 1 &lt;&lt; 5;
pub const KEY_UP: u16 = 1 &lt;&lt; 6;
pub const KEY_DOWN: u16 = 1 &lt;&lt; 7;
pub const KEY_R: u16 = 1 &lt;&lt; 8;
pub const KEY_L: u16 = 1 &lt;&lt; 9;
impl KeyInputSetting {
pub fn contains(&amp;self, key: u16) -&gt; bool {
(self.0 &amp; key) != 0
}
}
#}</code></pre></pre>
<p>Because each key is a unique bit you can even check for more than one key at
once by just adding two key values together.</p>
<pre><pre class="playpen"><code class="language-rust">
# #![allow(unused_variables)]
#fn main() {
let input_contains_a_and_l = input.contains(KEY_A + KEY_L);
#}</code></pre></pre>
<p>And we wanted to save the state of an old frame and compare it to the current
frame to see what was different:</p>
<pre><pre class="playpen"><code class="language-rust">
# #![allow(unused_variables)]
#fn main() {
pub fn difference(&amp;self, other: KeyInputSetting) -&gt; KeyInputSetting {
KeyInputSetting(self.0 ^ other.0)
}
#}</code></pre></pre>
<p>Anything that's &quot;in&quot; the difference output is a key that <em>changed</em>, and then if
the key reads as pressed this frame that means it was just pressed. The exact
mechanics of all the ways you might care to do something based on new key
presses is obviously quite varied, but it might be something like this:</p>
<pre><pre class="playpen"><code class="language-rust">
# #![allow(unused_variables)]
#fn main() {
let this_frame_diff = this_frame_input.difference(last_frame_input);
if this_frame_diff.contains(KEY_B) &amp;&amp; this_frame_input.contains(KEY_B) {
// the user just pressed B, react in some way
}
#}</code></pre></pre>
<p>And for the arrow pad, we'll make an enum that easily casts into <code>i32</code>. Whenever
we're working with stuff we can try to use <code>i32</code> / <code>isize</code> as often as possible
just because it's easier on the GBA's CPU if we stick to its native number size.
Having it be an enum lets us use <code>match</code> and be sure that we've covered all our
cases.</p>
<pre><pre class="playpen"><code class="language-rust">
# #![allow(unused_variables)]
#fn main() {
/// A &quot;tribool&quot; value helps us interpret the arrow pad.
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
#[repr(i32)]
pub enum TriBool {
Minus = -1,
Neutral = 0,
Plus = +1,
}
#}</code></pre></pre>
<p>Now, how do we determine <em>which way</em> is plus or minus? Well... I don't know.
Really. I'm not sure what the best one is because the GBA really wants the
origin at 0,0 with higher rows going down and higher cols going right. On the
other hand, all the normal math you and I learned in school is oriented with
increasing Y being upward on the page. So, at least for this demo, we're going
to go with what the GBA wants us to do and give it a try. If we don't end up
confusing ourselves then we can stick with that. Maybe we can cover it over
somehow later on.</p>
<pre><pre class="playpen"><code class="language-rust">
# #![allow(unused_variables)]
#fn main() {
pub fn column_direction(&amp;self) -&gt; TriBool {
if self.contains(KEY_RIGHT) {
TriBool::Plus
} else if self.contains(KEY_LEFT) {
TriBool::Minus
} else {
TriBool::Neutral
}
}
pub fn row_direction(&amp;self) -&gt; TriBool {
if self.contains(KEY_DOWN) {
TriBool::Plus
} else if self.contains(KEY_UP) {
TriBool::Minus
} else {
TriBool::Neutral
}
}
#}</code></pre></pre>
<p>So then every frame we can check for <code>column_direction</code> and <code>row_direction</code> and
then apply those to the snake's current position to make it move around the
screen.</p>
<p>With that I think we're all done with user input for now. There's some other
things to eventually know about like key interrupts that you can set and stuff,
but we'll cover that later on because it's not necessary right now.</p>
</main>

View file

@ -789,8 +789,208 @@ SNES had. As you can guess, we get key state info from an IO register.</p>
modern computer or console you do this with vsync info from the GPU and Monitor,
and on the GBA we'll be using vsync info from an IO register that tracks what
the display hardware is doing.</p>
<p>For this chapter we'll make a copy of <code>hello2.rs</code> named <code>snake.rs</code> and then fill
it in as we go. Normally you might not place the entire program into a single
source file, but since these are examples it's slightly better to have them be
completely self contained than it is to have them be &quot;properly organized&quot; for
the long term.</p>
<a class="header" href="#the-key-input-register" id="the-key-input-register"><h1>The Key Input Register</h1></a>
<p>TODO: describe all the stuff about key input</p>
<p>The Key Input Register is our next IO register. Its shorthand name is
<a href="http://problemkaputt.de/gbatek.htm#gbakeypadinput">KEYINPUT</a> and it's a <code>u16</code>
at <code>0x4000130</code>. The entire register is obviosuly read only, you can't tell the
GBA what buttons are pressed.</p>
<p>Each button is exactly one bit:</p>
<table><thead><tr><th align="center"> Bit </th><th align="center"> Button </th></tr></thead><tbody>
<tr><td align="center"> 0 </td><td align="center"> A </td></tr>
<tr><td align="center"> 1 </td><td align="center"> B </td></tr>
<tr><td align="center"> 2 </td><td align="center"> Select </td></tr>
<tr><td align="center"> 3 </td><td align="center"> Start </td></tr>
<tr><td align="center"> 4 </td><td align="center"> Right </td></tr>
<tr><td align="center"> 5 </td><td align="center"> Left </td></tr>
<tr><td align="center"> 6 </td><td align="center"> Up </td></tr>
<tr><td align="center"> 7 </td><td align="center"> Down </td></tr>
<tr><td align="center"> 8 </td><td align="center"> R </td></tr>
<tr><td align="center"> 9 </td><td align="center"> L </td></tr>
</tbody></table>
<p>The higher bits above are not used at all.</p>
<p>Similar to other old hardware devices, the convention here is that a button's
bit is <strong>clear when pressed, active when released</strong>. In other words, when the
user is not touching the device at all the KEYINPUT value will read
<code>0b0000_0011_1111_1111</code>. There's similar values for when the user is pressing as
many buttons as possible, but since the left/right and up/down keys are on an
arrow pad the value can never be 0 since you can't ever press every single key
at once.</p>
<p>When dealing with key input, the register always shows the exact key values at
any moment you read it. Obviously that's what it should do, but what it means to
you as a programmer is that you should usually gather input once at the top of a
game frame and then use that single input poll as the input values across the
whole game frame.</p>
<p>Of course, you might want to know if a user's key state changed from frame to
frame. That's fairly easy too: We just store the last frame keys as well as the
current frame keys (it's only a <code>u16</code>) and then we can xor the two values.
Anything that shows up in the xor result is a key that changed. If it's changed
and it's now down, that means it was pushed this frame. If it's changed and it's
now up, that means it was released this frame.</p>
<p>The other major thing you might frequently want is to know &quot;which way&quot; the arrow
pad is pointing: Up/Down/None and Left/Right/None. Sounds like an enum to me.
Except that often time we'll have situations where the direction just needs to
be multiplied by a speed and applied as a delta to a position. We want to
support that as well as we can too.</p>
<a class="header" href="#key-input-code" id="key-input-code"><h2>Key Input Code</h2></a>
<p>Let's get down to some code. First we want to make a way to read the address as
a <code>u16</code> and then wrap that in our newtype which will implement methods for
reading and writing the key bits.</p>
<pre><pre class="playpen"><code class="language-rust">
# #![allow(unused_variables)]
#fn main() {
pub const KEYINPUT: *mut u16 = 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() -&gt; KeyInputSetting {
unsafe { KeyInputSetting(KEYINPUT.volatile_read()) }
}
#}</code></pre></pre>
<p>Now we want a way to check if a key is <em>being pressed</em>, since that's normally
how we think of things as a game designer and even as a player. That is, usually
you'd say &quot;if you press A, then X happens&quot; instead of &quot;if you don't press A,
then X does not happen&quot;.</p>
<p>Normally we'd pick a constant for the bit we want, <code>&amp;</code> it with our value, and
then check for <code>val != 0</code>. Since the bit we're looking for is <code>0</code> in the &quot;true&quot;
state we still pick the same constant and we still do the <code>&amp;</code>, but we test with
<code>== 0</code>. Practically the same, right? Well, since I'm asking a rhetorical
question like that you can probably already guess that it's not the same. I was
shocked to learn this too.</p>
<p>All we have to do is ask our good friend
<a href="https://rust.godbolt.org/z/d-8oCe">Godbolt</a> what's gonna happen when the code
compiles. The link there has the page set for the <code>stable</code> 1.30 compiler just so
that the link results stay consistent if you read this book in a year or
something. Also, we've set the target to <code>thumbv6m-none-eabi</code>, which is a
slightly later version of ARM than the actual GBA, but it's close enough for
just checking. Of course, in a full program small functions like these will
probably get inlined into the calling code and disappear entirely as they're
folded and refolded by the compiler, and so on. Still, we can see that in the
simple case when the compiler doesn't know any extra context, the <code>!=0</code> test is
4 instructions and the <code>==0</code> test is six instructions. Since we want to get
saving where we can, we'll always use a <code>!=0</code> test and we'll adjust how we
initially read the register to compensate.</p>
<pre><pre class="playpen"><code class="language-rust">
# #![allow(unused_variables)]
#fn main() {
pub fn read_key_input() -&gt; KeyInputSetting {
unsafe { KeyInputSetting(KEYINPUT.volatile_read() ^ 0b1111_1111_1111_1111) }
}
#}</code></pre></pre>
<p>Now we add a method for seeing if a key is pressed. In the full library there's
a more advanced version of this that's built up via macro, but for this example
we'll just name a bunch of <code>const</code> values and then have a method that takes a
value and says if that bit is on.</p>
<pre><pre class="playpen"><code class="language-rust">
# #![allow(unused_variables)]
#fn main() {
pub const KEY_A: u16 = 1 &lt;&lt; 0;
pub const KEY_B: u16 = 1 &lt;&lt; 1;
pub const KEY_SELECT: u16 = 1 &lt;&lt; 2;
pub const KEY_START: u16 = 1 &lt;&lt; 3;
pub const KEY_RIGHT: u16 = 1 &lt;&lt; 4;
pub const KEY_LEFT: u16 = 1 &lt;&lt; 5;
pub const KEY_UP: u16 = 1 &lt;&lt; 6;
pub const KEY_DOWN: u16 = 1 &lt;&lt; 7;
pub const KEY_R: u16 = 1 &lt;&lt; 8;
pub const KEY_L: u16 = 1 &lt;&lt; 9;
impl KeyInputSetting {
pub fn contains(&amp;self, key: u16) -&gt; bool {
(self.0 &amp; key) != 0
}
}
#}</code></pre></pre>
<p>Because each key is a unique bit you can even check for more than one key at
once by just adding two key values together.</p>
<pre><pre class="playpen"><code class="language-rust">
# #![allow(unused_variables)]
#fn main() {
let input_contains_a_and_l = input.contains(KEY_A + KEY_L);
#}</code></pre></pre>
<p>And we wanted to save the state of an old frame and compare it to the current
frame to see what was different:</p>
<pre><pre class="playpen"><code class="language-rust">
# #![allow(unused_variables)]
#fn main() {
pub fn difference(&amp;self, other: KeyInputSetting) -&gt; KeyInputSetting {
KeyInputSetting(self.0 ^ other.0)
}
#}</code></pre></pre>
<p>Anything that's &quot;in&quot; the difference output is a key that <em>changed</em>, and then if
the key reads as pressed this frame that means it was just pressed. The exact
mechanics of all the ways you might care to do something based on new key
presses is obviously quite varied, but it might be something like this:</p>
<pre><pre class="playpen"><code class="language-rust">
# #![allow(unused_variables)]
#fn main() {
let this_frame_diff = this_frame_input.difference(last_frame_input);
if this_frame_diff.contains(KEY_B) &amp;&amp; this_frame_input.contains(KEY_B) {
// the user just pressed B, react in some way
}
#}</code></pre></pre>
<p>And for the arrow pad, we'll make an enum that easily casts into <code>i32</code>. Whenever
we're working with stuff we can try to use <code>i32</code> / <code>isize</code> as often as possible
just because it's easier on the GBA's CPU if we stick to its native number size.
Having it be an enum lets us use <code>match</code> and be sure that we've covered all our
cases.</p>
<pre><pre class="playpen"><code class="language-rust">
# #![allow(unused_variables)]
#fn main() {
/// A &quot;tribool&quot; value helps us interpret the arrow pad.
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
#[repr(i32)]
pub enum TriBool {
Minus = -1,
Neutral = 0,
Plus = +1,
}
#}</code></pre></pre>
<p>Now, how do we determine <em>which way</em> is plus or minus? Well... I don't know.
Really. I'm not sure what the best one is because the GBA really wants the
origin at 0,0 with higher rows going down and higher cols going right. On the
other hand, all the normal math you and I learned in school is oriented with
increasing Y being upward on the page. So, at least for this demo, we're going
to go with what the GBA wants us to do and give it a try. If we don't end up
confusing ourselves then we can stick with that. Maybe we can cover it over
somehow later on.</p>
<pre><pre class="playpen"><code class="language-rust">
# #![allow(unused_variables)]
#fn main() {
pub fn column_direction(&amp;self) -&gt; TriBool {
if self.contains(KEY_RIGHT) {
TriBool::Plus
} else if self.contains(KEY_LEFT) {
TriBool::Minus
} else {
TriBool::Neutral
}
}
pub fn row_direction(&amp;self) -&gt; TriBool {
if self.contains(KEY_DOWN) {
TriBool::Plus
} else if self.contains(KEY_UP) {
TriBool::Minus
} else {
TriBool::Neutral
}
}
#}</code></pre></pre>
<p>So then every frame we can check for <code>column_direction</code> and <code>row_direction</code> and
then apply those to the snake's current position to make it move around the
screen.</p>
<p>With that I think we're all done with user input for now. There's some other
things to eventually know about like key interrupts that you can set and stuff,
but we'll cover that later on because it's not necessary right now.</p>
<a class="header" href="#the-vcount-register" id="the-vcount-register"><h1>The VCount Register</h1></a>
<p>TODO: describe all the stuff about vcount</p>
<p>TODO: mention vblank and hblank</p>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

95
examples/snake.rs Normal file
View file

@ -0,0 +1,95 @@
#![feature(start)]
#![no_std]
#[cfg(not(test))]
#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! {
loop {}
}
#[start]
fn main(_argc: isize, _argv: *const *const u8) -> isize {
unsafe {
DISPCNT.write_volatile(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));
loop {}
}
}
pub const DISPCNT: *mut u16 = 0x04000000 as *mut u16;
pub const MODE3: u16 = 3;
pub const BG2: u16 = 0b100_0000_0000;
pub const VRAM: usize = 0x06000000;
pub const SCREEN_WIDTH: isize = 240;
pub const fn rgb16(red: u16, green: u16, blue: u16) -> u16 {
blue << 10 | green << 5 | red
}
pub unsafe fn mode3_pixel(col: isize, row: isize, color: u16) {
(VRAM as *mut u16).offset(col + row * SCREEN_WIDTH).write_volatile(color);
}
pub const KEYINPUT: *mut u16 = 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);
/// A "tribool" value helps us interpret the arrow pad.
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
#[repr(i32)]
pub enum TriBool {
Minus = -1,
Neutral = 0,
Plus = 1,
}
pub fn read_key_input() -> KeyInputSetting {
unsafe { KeyInputSetting(KEYINPUT.volatile_read() ^ 0b1111_1111_1111_1111) }
}
pub const KEY_A: u16 = 1 << 0;
pub const KEY_B: u16 = 1 << 1;
pub const KEY_SELECT: u16 = 1 << 2;
pub const KEY_START: u16 = 1 << 3;
pub const KEY_RIGHT: u16 = 1 << 4;
pub const KEY_LEFT: u16 = 1 << 5;
pub const KEY_UP: u16 = 1 << 6;
pub const KEY_DOWN: u16 = 1 << 7;
pub const KEY_R: u16 = 1 << 8;
pub const KEY_L: u16 = 1 << 9;
impl KeyInputSetting {
pub fn contains(&self, key: u16) -> bool {
(self.0 & key) != 0
}
pub fn difference(&self, other: KeyInputSetting) -> KeyInputSetting {
KeyInputSetting(self.0 ^ other.0)
}
pub fn column_direction(&self) -> TriBool {
if self.contains(KEY_RIGHT) {
TriBool::Plus
} else if self.contains(KEY_LEFT) {
TriBool::Minus
} else {
TriBool::Neutral
}
}
pub fn row_direction(&self) -> TriBool {
if self.contains(KEY_DOWN) {
TriBool::Plus
} else if self.contains(KEY_UP) {
TriBool::Minus
} else {
TriBool::Neutral
}
}
}

View file

@ -1,10 +1,8 @@
error_on_line_overflow = false
fn_args_density = "Compressed"
reorder_imported_names = true
merge_imports = true
reorder_imports = true
reorder_imports_in_group = true
use_try_shorthand = true
write_mode = "Overwrite"
tab_spaces = 2
max_width = 150
color = "Never"

View file

@ -383,6 +383,16 @@ pub const SIODATA8: VolatilePtr<u16> = VolatilePtr(0x400012A as *mut u16);
/// Key Status
pub const KEYINPUT: VolatilePtr<u16> = VolatilePtr(0x4000130 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);
#[allow(missing_docs)]
impl KeyInputSetting {
register_bit!(A_BIT, u16, 0b1, a_pressed, read_write);
}
/// Key Interrupt Control
pub const KEYCNT: VolatilePtr<u16> = VolatilePtr(0x4000132 as *mut u16);