mirror of
https://github.com/italicsjenga/gba.git
synced 2025-02-23 23:07:49 +11:00
fixed point and stuff
This commit is contained in:
parent
046e80851f
commit
58d739dd9e
10 changed files with 457 additions and 42 deletions
|
@ -12,6 +12,7 @@ license = "Apache-2.0"
|
||||||
publish = false
|
publish = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
typenum = "1.10"
|
||||||
gba-proc-macro = "0.2.1"
|
gba-proc-macro = "0.2.1"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
|
|
|
@ -89,10 +89,6 @@ the standard library types to be used "for free" once it was set up, or just a
|
||||||
custom allocator that's GBA specific if Rust's global allocator style isn't a
|
custom allocator that's GBA specific if Rust's global allocator style isn't a
|
||||||
good fit for the GBA (I honestly haven't looked into it).
|
good fit for the GBA (I honestly haven't looked into it).
|
||||||
|
|
||||||
## LLVM Intrinsics
|
|
||||||
|
|
||||||
TODO: explain that we'll occasionally have to provide some intrinsics.
|
|
||||||
|
|
||||||
## Bare Metal Panic
|
## Bare Metal Panic
|
||||||
|
|
||||||
TODO: expand this
|
TODO: expand this
|
||||||
|
@ -114,3 +110,10 @@ TODO: expand this
|
||||||
* Sending the message also automatically zeroes the output buffer.
|
* Sending the message also automatically zeroes the output buffer.
|
||||||
* View the output within the "Tools" menu, "View Logs...". Note that the Fatal
|
* View the output within the "Tools" menu, "View Logs...". Note that the Fatal
|
||||||
message, if any doesn't get logged.
|
message, if any doesn't get logged.
|
||||||
|
|
||||||
|
TODO: this will probably fail without a `__clzsi2` implementation, which is a
|
||||||
|
good seg for the next section
|
||||||
|
|
||||||
|
## LLVM Intrinsics
|
||||||
|
|
||||||
|
TODO: explain that we'll occasionally have to provide some intrinsics.
|
||||||
|
|
|
@ -1,13 +1,258 @@
|
||||||
# Fixed Only
|
# Fixed Only
|
||||||
|
|
||||||
In addition to not having the standard library available, we don't even have a
|
In addition to not having much of the standard library available, we don't even
|
||||||
floating point unit available! We can't do floating point math in hardware! We
|
have a floating point unit available! We can't do floating point math in
|
||||||
could still do floating point math as software computations if we wanted, but
|
hardware! We _could_ still do floating point math as pure software computations
|
||||||
that's a slow, slow thing to do.
|
if we wanted, but that's a slow, slow thing to do.
|
||||||
|
|
||||||
Instead let's learn about another way to have fractional values called "Fixed
|
Are there faster ways? It's the same answer as always: "Yes, but not without a
|
||||||
Point"
|
tradeoff."
|
||||||
|
|
||||||
## Fixed Point
|
The faster way is to represent fractional values using a system called a [Fixed
|
||||||
|
Point Representation](https://en.wikipedia.org/wiki/Fixed-point_arithmetic).
|
||||||
|
What do we trade away? Numeric range.
|
||||||
|
|
||||||
TODO: describe fixed point, make some types, do the impls, all that.
|
* Floating point math stores bits for base value and for exponent all according
|
||||||
|
to a single [well defined](https://en.wikipedia.org/wiki/IEEE_754) standard
|
||||||
|
for how such a complicated thing works.
|
||||||
|
* Fixed point math takes a normal integer (either signed or unsigned) and then
|
||||||
|
just "mentally associates" it (so to speak) with a fractional value for its
|
||||||
|
"units". If you have 3 and it's in units of 1/2, then you have 3/2, or 1.5
|
||||||
|
using decimal notation. If your number is 256 and it's in units of 1/256th
|
||||||
|
then the value is 1.0 in decimal notation.
|
||||||
|
|
||||||
|
Floating point math requires dedicated hardware to perform quickly, but it can
|
||||||
|
"trade" precision when it needs to represent extremely large or small values.
|
||||||
|
|
||||||
|
Fixed point math is just integral math, which our GBA is reasonably good at, but
|
||||||
|
because your number is associated with a fixed fraction your results can get out
|
||||||
|
of range very easily.
|
||||||
|
|
||||||
|
## Representing A Fixed Point Value
|
||||||
|
|
||||||
|
So we want to associate our numbers with a mental note of what units they're in:
|
||||||
|
|
||||||
|
* [PhantomData](https://doc.rust-lang.org/core/marker/struct.PhantomData.html)
|
||||||
|
is a type that tells the compiler "please remember this extra type info" when
|
||||||
|
you add it as a field to a struct. It goes away at compile time, so it's
|
||||||
|
perfect for us to use as space for a note to ourselves without causing runtime
|
||||||
|
overhead.
|
||||||
|
* The [typenum](https://crates.io/crates/typenum) crate is the best way to
|
||||||
|
represent a number within a type in Rust. Since our values on the GBA are
|
||||||
|
always specified as a number of fractional bits to count the number as, we can
|
||||||
|
put `typenum` types such as `U8` or `U14` into our `PhantomData` to keep track
|
||||||
|
of what's going on.
|
||||||
|
|
||||||
|
Now, those of you who know me, or perhaps just know my reputation, will of
|
||||||
|
course _immediately_ question what happened to the real Lokathor. I do not care
|
||||||
|
for most crates, and I particularly don't care for using a crate in teaching
|
||||||
|
situations. However, `typenum` has a number of factors on its side that let me
|
||||||
|
suggest it in this situation:
|
||||||
|
|
||||||
|
* It's version 1.10 with a total of 21 versions and nearly 700k downloads, so we
|
||||||
|
can expect that the major troubles have been shaken out and that it will remain
|
||||||
|
fairly stable for quite some time to come.
|
||||||
|
* It has no further dependencies that it's going to drag into the compilation.
|
||||||
|
* It happens all at compile time, so it's not clogging up our actual game with
|
||||||
|
any nonsense.
|
||||||
|
* The (interesting) subject of "how do you do math inside Rust's trait system?" is
|
||||||
|
totally separate from the concern that we're trying to focus on here.
|
||||||
|
|
||||||
|
Therefore, we will consider it acceptable to use this crate.
|
||||||
|
|
||||||
|
Now the `typenum` crate defines a whole lot, but we'll focus down to just a
|
||||||
|
single type at the moment:
|
||||||
|
[UInt](https://docs.rs/typenum/1.10.0/typenum/uint/struct.UInt.html) is a
|
||||||
|
type-level unsigned value. It's like `u8` or `u16`, but while they're types that
|
||||||
|
then have values, each `UInt` construction statically equates to a specific
|
||||||
|
value. Like how the `()` type only has one value, which is also called `()`. In
|
||||||
|
this case, you wrap up `UInt` around smaller `UInt` values and a `B1` or `B0`
|
||||||
|
value to build up the binary number that you want at the type level.
|
||||||
|
|
||||||
|
In other words, instead of writing
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let six = 0b110;
|
||||||
|
```
|
||||||
|
|
||||||
|
We write
|
||||||
|
|
||||||
|
```rust
|
||||||
|
type U6 = UInt<UInt<UInt<UTerm, B1>, B1>, B0>;
|
||||||
|
```
|
||||||
|
|
||||||
|
Wild, I know. If you look into the `typenum` crate you can do math and stuff
|
||||||
|
with these type level numbers, and we will a little bit below, but to start off
|
||||||
|
we _just_ need to store one in some `PhantomData`.
|
||||||
|
|
||||||
|
### A struct For Fixed Point
|
||||||
|
|
||||||
|
Our actual type for a fixed point value looks like this:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use core::marker::PhantomData;
|
||||||
|
use typenum::marker_traits::Unsigned;
|
||||||
|
|
||||||
|
/// Fixed point `T` value with `F` fractional bits.
|
||||||
|
#[derive(Debug, Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
#[repr(transparent)]
|
||||||
|
pub struct Fx<T, F: Unsigned> {
|
||||||
|
bits: T,
|
||||||
|
_phantom: PhantomData<F>,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This says that `Fx<T,F>` is a generic type that holds some base number type `T`
|
||||||
|
and a `F` type that's marking off how many fractional bits we're using. We only
|
||||||
|
want people giving unsigned type-level values for the `PhantomData` type, so we
|
||||||
|
use the trait bound `F: Unsigned`.
|
||||||
|
|
||||||
|
We use
|
||||||
|
[repr(transparent)](https://github.com/rust-lang/rfcs/blob/master/text/1758-repr-transparent.md)
|
||||||
|
here to ensure that `Fx` will always be treated just like the base type in the
|
||||||
|
final program (in terms of bit pattern and ABI).
|
||||||
|
|
||||||
|
If you go and check, this is _basically_ how the existing general purpose crates
|
||||||
|
for fixed point math represent their numbers. They're a little fancier about it
|
||||||
|
because they have to cover every case, and we only have to cover our GBA case.
|
||||||
|
|
||||||
|
That's quite a bit to type though. We probably want to make a few type aliases
|
||||||
|
for things to be easier to look at. Unfortunately there's [no standard
|
||||||
|
notation](https://en.wikipedia.org/wiki/Fixed-point_arithmetic#Notation) for how
|
||||||
|
you write a fixed point type. We also have to limit ourselves to what's valid
|
||||||
|
for use in a Rust type too. I like the `fx` thing, so we'll use that for signed
|
||||||
|
and then `fxu` if we need an unsigned value.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
/// Alias for an `i16` fixed point value with 8 fractional bits.
|
||||||
|
pub type fx8_8 = Fx<i16,U8>;
|
||||||
|
```
|
||||||
|
|
||||||
|
Rust will complain about having `non_camel_case_types`, and you can shut that
|
||||||
|
warning up by putting an `#[allow(non_camel_case_types)]` attribute on the type
|
||||||
|
alias directly, or you can use `#![allow(non_camel_case_types)]` at the very top
|
||||||
|
of the module to shut up that warning for the whole module (which is what I
|
||||||
|
did).
|
||||||
|
|
||||||
|
## Constructing A Fixed Point Value
|
||||||
|
|
||||||
|
So how do we actually _make_ one of these values? Well, we can always just wrap or unwrap any value in our `Fx` type:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
impl<T, F: Unsigned> Fx<T, F> {
|
||||||
|
/// Uses the provided value directly.
|
||||||
|
pub fn from_raw(r: T) -> Self {
|
||||||
|
Fx {
|
||||||
|
num: r,
|
||||||
|
phantom: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Unwraps the inner value.
|
||||||
|
pub fn into_raw(self) -> T {
|
||||||
|
self.num
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
I'd like to use the `From` trait of course, but it was giving me some trouble, i
|
||||||
|
think because of the orphan rule. Oh well.
|
||||||
|
|
||||||
|
If we want to be particular to the fact that these are supposed to be
|
||||||
|
_numbers_... that gets tricky. Rust is actually quite bad at being generic about
|
||||||
|
number types. You can use the [num](https://crates.io/crates/num) crate, or you
|
||||||
|
can just use a macro and invoke it once per type. Guess what we're gonna do.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
macro_rules! fixed_point_methods {
|
||||||
|
($t:ident) => {
|
||||||
|
impl<F: Unsigned> Fx<$t, F> {
|
||||||
|
/// Gives 0 for this type.
|
||||||
|
pub fn zero() -> Self {
|
||||||
|
Fx {
|
||||||
|
num: 0,
|
||||||
|
phantom: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gives the smallest positive non-zero value.
|
||||||
|
pub fn precision() -> Self {
|
||||||
|
Fx {
|
||||||
|
num: 1,
|
||||||
|
phantom: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Makes a value with the integer part shifted into place.
|
||||||
|
pub fn from_int_part(i: $t) -> Self {
|
||||||
|
Fx {
|
||||||
|
num: i << F::to_u8(),
|
||||||
|
phantom: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fixed_point_methods! {u8}
|
||||||
|
fixed_point_methods! {i8}
|
||||||
|
fixed_point_methods! {i16}
|
||||||
|
fixed_point_methods! {u16}
|
||||||
|
fixed_point_methods! {i32}
|
||||||
|
fixed_point_methods! {u32}
|
||||||
|
```
|
||||||
|
|
||||||
|
Now _you'd think_ that those can all be `const`, but at the moment you can't
|
||||||
|
have a `const` function with a bound on any trait other than `Sized`, so they
|
||||||
|
have to be normal functions.
|
||||||
|
|
||||||
|
Also, we're doing something a little interesting there with `from_int_part`. We
|
||||||
|
can take our `F` type and get it as a value instead of a type using `to_u8`.
|
||||||
|
|
||||||
|
## Casting Values
|
||||||
|
|
||||||
|
Next, once we have a value in one type, we need to be able to move it into
|
||||||
|
another type. A particular `Fx` type is a base number type and a fractional
|
||||||
|
count, so there's two ways we might want to move it.
|
||||||
|
|
||||||
|
For casting the base type it's a little weird. Because there's so many number
|
||||||
|
types, and we can't be generic about them when using `as`, we'd have to make
|
||||||
|
like 30 functions (6 base number types we're using, times 5 target number types
|
||||||
|
you could cast to). Instead, we'll write it just once, and let the user pass a
|
||||||
|
closure that does the cast.
|
||||||
|
|
||||||
|
We can put this as part of the basic impl block that `from_raw` and `into_raw`
|
||||||
|
are part of. If can avoid having code inside a macro we'll do it just because
|
||||||
|
macros are messy.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
/// Casts the base type, keeping the fractional bit quantity the same.
|
||||||
|
pub fn cast_inner<Z, C: Fn(T) -> Z>(self, op: C) -> Fx<Z, F> {
|
||||||
|
Fx {
|
||||||
|
num: op(self.num),
|
||||||
|
phantom: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
It's... not the best to have to pass in the casting operation like that.
|
||||||
|
Hopefully we won't have to use it much.
|
||||||
|
|
||||||
|
Also we might want to change the amount of fractional bits in a number. Oh,
|
||||||
|
gosh, this one is kinda complicated.
|
||||||
|
|
||||||
|
## Addition / Subtraction
|
||||||
|
|
||||||
|
## Multiplication / Division
|
||||||
|
|
||||||
|
## Trigonometry
|
||||||
|
|
||||||
|
## Just Using A Crate
|
||||||
|
|
||||||
|
If you feel too intimidated by all of this then I'll suggest to you that the
|
||||||
|
[fixed](https://crates.io/crates/fixed) crate seems to be the best crate
|
||||||
|
available for fixed point math.
|
||||||
|
|
||||||
|
_I have not tested its use on the GBA myself_.
|
||||||
|
|
||||||
|
It's just my recommendation from looking at the docs of the various options
|
||||||
|
available.
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
# Newtype
|
# Newtype
|
||||||
|
|
||||||
|
TODO: we've already used newtype twice by now (fixed point values and volatile
|
||||||
|
addresses), so we need to adjust how we start this section.
|
||||||
|
|
||||||
There's a great Zero Cost abstraction that we'll be using a lot that you might
|
There's a great Zero Cost abstraction that we'll be using a lot that you might
|
||||||
not already be familiar with: we're talking about the "Newtype Pattern"!
|
not already be familiar with: we're talking about the "Newtype Pattern"!
|
||||||
|
|
||||||
|
@ -27,13 +30,13 @@ cost at compile time.
|
||||||
pub struct PixelColor(u16);
|
pub struct PixelColor(u16);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
TODO: we've already talked about repr(transparent) by now
|
||||||
|
|
||||||
Ah, except that, as I'm sure you remember from [The
|
Ah, except that, as I'm sure you remember from [The
|
||||||
Rustonomicon](https://doc.rust-lang.org/nomicon/other-reprs.html#reprtransparent)
|
Rustonomicon](https://doc.rust-lang.org/nomicon/other-reprs.html#reprtransparent)
|
||||||
(and from [the
|
(and from the RFC too, of course), if we have a single field struct that's
|
||||||
RFC](https://github.com/rust-lang/rfcs/blob/master/text/1758-repr-transparent.md)
|
sometimes different from having just the bare value, so we should be using
|
||||||
too, of course), if we have a single field struct that's sometimes different
|
`#[repr(transparent)]` with our newtypes.
|
||||||
from having just the bare value, so we should be using `#[repr(transparent)]`
|
|
||||||
with our newtypes.
|
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
|
|
|
@ -20,12 +20,13 @@ macro_rules! const_assert {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Constructs an RGB value with a `const_assert!` that the input is in range.
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! const_rgb {
|
macro_rules! const_rgb {
|
||||||
($r:expr, $g:expr, $b:expr) => {{
|
($r:expr, $g:expr, $b:expr) => {{
|
||||||
const_assert!($r);
|
const_assert!($r <= 31);
|
||||||
const_assert!($g);
|
const_assert!($g <= 31);
|
||||||
const_assert!($b);
|
const_assert!($b <= 31);
|
||||||
Color::new($r, $g, $b)
|
Color::new($r, $g, $b)
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
38
src/builtins.rs
Normal file
38
src/builtins.rs
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
#![allow(missing_docs)]
|
||||||
|
|
||||||
|
//! The module to provide "builtin" functions that LLVM expects.
|
||||||
|
//!
|
||||||
|
//! You shouldn't need to call anything in here yourself, it just has to be in
|
||||||
|
//! the translation unit and LLVM will find it.
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn __clzsi2(mut x: usize) -> usize {
|
||||||
|
let mut y: usize;
|
||||||
|
let mut n: usize = 32;
|
||||||
|
y = x >> 16;
|
||||||
|
if y != 0 {
|
||||||
|
n = n - 16;
|
||||||
|
x = y;
|
||||||
|
}
|
||||||
|
y = x >> 8;
|
||||||
|
if y != 0 {
|
||||||
|
n = n - 8;
|
||||||
|
x = y;
|
||||||
|
}
|
||||||
|
y = x >> 4;
|
||||||
|
if y != 0 {
|
||||||
|
n = n - 4;
|
||||||
|
x = y;
|
||||||
|
}
|
||||||
|
y = x >> 2;
|
||||||
|
if y != 0 {
|
||||||
|
n = n - 2;
|
||||||
|
x = y;
|
||||||
|
}
|
||||||
|
y = x >> 1;
|
||||||
|
if y != 0 {
|
||||||
|
n - 2
|
||||||
|
} else {
|
||||||
|
n - x
|
||||||
|
}
|
||||||
|
}
|
|
@ -39,4 +39,3 @@ impl<T> VolatilePtr<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: kill all this with fire
|
// TODO: kill all this with fire
|
||||||
|
|
||||||
|
|
86
src/fixed.rs
Normal file
86
src/fixed.rs
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
#![allow(non_camel_case_types)]
|
||||||
|
|
||||||
|
//! Module for fixed point math types and operations.
|
||||||
|
|
||||||
|
use core::{convert::From, marker::PhantomData};
|
||||||
|
use typenum::{marker_traits::Unsigned, U8};
|
||||||
|
|
||||||
|
/// Fixed point `T` value with `F` fractional bits.
|
||||||
|
#[derive(Debug, Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
#[repr(transparent)]
|
||||||
|
pub struct Fx<T, F: Unsigned> {
|
||||||
|
num: T,
|
||||||
|
phantom: PhantomData<F>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, F: Unsigned> Fx<T, F> {
|
||||||
|
/// Uses the provided value directly.
|
||||||
|
pub fn from_raw(r: T) -> Self {
|
||||||
|
Fx {
|
||||||
|
num: r,
|
||||||
|
phantom: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Unwraps the inner value.
|
||||||
|
pub fn into_raw(self) -> T {
|
||||||
|
self.num
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Casts the base type, keeping the fractional bit quantity the same.
|
||||||
|
pub fn cast_inner<Z, C: Fn(T) -> Z>(self, op: C) -> Fx<Z, F> {
|
||||||
|
Fx {
|
||||||
|
num: op(self.num),
|
||||||
|
phantom: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! fixed_point_methods {
|
||||||
|
($t:ident) => {
|
||||||
|
impl<F: Unsigned> Fx<$t, F> {
|
||||||
|
/// Gives 0 for this type.
|
||||||
|
pub fn zero() -> Self {
|
||||||
|
Fx {
|
||||||
|
num: 0,
|
||||||
|
phantom: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gives the smallest positive non-zero value.
|
||||||
|
pub fn precision() -> Self {
|
||||||
|
Fx {
|
||||||
|
num: 1,
|
||||||
|
phantom: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Makes a value with the integer part shifted into place.
|
||||||
|
pub fn from_int_part(i: $t) -> Self {
|
||||||
|
Fx {
|
||||||
|
num: i << F::to_u8(),
|
||||||
|
phantom: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gives the raw inner value.
|
||||||
|
pub fn into_inner(&self) -> $t {
|
||||||
|
self.num
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Changes the fractional bit quantity, keeping the base type the same.
|
||||||
|
pub fn change_bit_quantity<N: Unsigned>(&self) -> Fx<$t, N> {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fixed_point_methods! {u8}
|
||||||
|
fixed_point_methods! {i8}
|
||||||
|
fixed_point_methods! {i16}
|
||||||
|
fixed_point_methods! {u16}
|
||||||
|
fixed_point_methods! {i32}
|
||||||
|
fixed_point_methods! {u32}
|
||||||
|
|
||||||
|
/// Alias for an `i16` fixed point value with 8 fractional bits.
|
||||||
|
pub type fx8_8 = Fx<i16, U8>;
|
|
@ -15,7 +15,7 @@
|
||||||
|
|
||||||
// TODO(lokathor): IO Register newtypes.
|
// TODO(lokathor): IO Register newtypes.
|
||||||
|
|
||||||
use gba_proc_macro::{newtype, register_bit};
|
use gba_proc_macro::register_bit;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
@ -25,9 +25,10 @@ use super::*;
|
||||||
pub const DISPCNT: VolatilePtr<u16> = VolatilePtr(0x400_0000 as *mut u16);
|
pub const DISPCNT: VolatilePtr<u16> = VolatilePtr(0x400_0000 as *mut u16);
|
||||||
|
|
||||||
newtype!(
|
newtype!(
|
||||||
|
/// A newtype over the various display control options that you have on a GBA.
|
||||||
|
#[derive(Debug, Copy, Clone, Default, PartialEq, Eq)]
|
||||||
DisplayControlSetting,
|
DisplayControlSetting,
|
||||||
u16,
|
u16
|
||||||
"A newtype over the various display control options that you have on a GBA."
|
|
||||||
);
|
);
|
||||||
|
|
||||||
#[allow(missing_docs)]
|
#[allow(missing_docs)]
|
||||||
|
@ -412,10 +413,14 @@ pub enum TriBool {
|
||||||
Plus = 1,
|
Plus = 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
newtype!(KeyInputSetting, u16, "A newtype over the key input state of the GBA");
|
newtype! {
|
||||||
|
/// Records a particular key press combination.
|
||||||
|
#[derive(Debug, Copy, Clone, Default, PartialEq, Eq)]
|
||||||
|
KeyInput, u16
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(missing_docs)]
|
#[allow(missing_docs)]
|
||||||
impl KeyInputSetting {
|
impl KeyInput {
|
||||||
register_bit!(A_BIT, u16, 1, a_pressed);
|
register_bit!(A_BIT, u16, 1, a_pressed);
|
||||||
register_bit!(B_BIT, u16, 1 << 1, b_pressed);
|
register_bit!(B_BIT, u16, 1 << 1, b_pressed);
|
||||||
register_bit!(SELECT_BIT, u16, 1 << 2, select_pressed);
|
register_bit!(SELECT_BIT, u16, 1 << 2, select_pressed);
|
||||||
|
@ -428,8 +433,8 @@ impl KeyInputSetting {
|
||||||
register_bit!(L_BIT, u16, 1 << 9, l_pressed);
|
register_bit!(L_BIT, u16, 1 << 9, l_pressed);
|
||||||
|
|
||||||
/// Takes the difference between these keys and another set of keys.
|
/// Takes the difference between these keys and another set of keys.
|
||||||
pub fn difference(self, other: KeyInputSetting) -> KeyInputSetting {
|
pub fn difference(self, other: Self) -> Self {
|
||||||
KeyInputSetting(self.0 ^ other.0)
|
KeyInput(self.0 ^ other.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gives the arrow pad value as a tribool, with Plus being increased column
|
/// Gives the arrow pad value as a tribool, with Plus being increased column
|
||||||
|
@ -458,11 +463,11 @@ impl KeyInputSetting {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the current state of the keys
|
/// Gets the current state of the keys
|
||||||
pub fn key_input() -> KeyInputSetting {
|
pub fn key_input() -> KeyInput {
|
||||||
// Note(Lokathor): The 10 used bits are "low when pressed" style, but the 6
|
// Note(Lokathor): The 10 used bits are "low when pressed" style, but the 6
|
||||||
// unused bits are always low, so we XOR with this mask to get a result where
|
// unused bits are always low, so we XOR with this mask to get a result where
|
||||||
// the only active bits are currently pressed keys.
|
// the only active bits are currently pressed keys.
|
||||||
unsafe { KeyInputSetting(KEYINPUT.read() ^ 0b0000_0011_1111_1111) }
|
unsafe { KeyInput(KEYINPUT.read() ^ 0b0000_0011_1111_1111) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Key Interrupt Control
|
/// Key Interrupt Control
|
||||||
|
|
58
src/lib.rs
58
src/lib.rs
|
@ -1,12 +1,12 @@
|
||||||
#![cfg_attr(not(test), no_std)]
|
#![cfg_attr(not(test), no_std)]
|
||||||
#![cfg_attr(not(test), feature(asm))]
|
#![cfg_attr(not(test), feature(asm))]
|
||||||
#![warn(missing_docs)]
|
#![warn(missing_docs)]
|
||||||
//#![allow(clippy::cast_lossless)]
|
#![allow(clippy::cast_lossless)]
|
||||||
#![deny(clippy::float_arithmetic)]
|
#![deny(clippy::float_arithmetic)]
|
||||||
|
|
||||||
//! This crate helps you write GBA ROMs.
|
//! This crate helps you write GBA ROMs.
|
||||||
//!
|
//!
|
||||||
//! # SAFETY POLICY
|
//! ## SAFETY POLICY
|
||||||
//!
|
//!
|
||||||
//! Some parts of this crate are safe wrappers around unsafe operations. This is
|
//! Some parts of this crate are safe wrappers around unsafe operations. This is
|
||||||
//! good, and what you'd expect from a Rust crate.
|
//! good, and what you'd expect from a Rust crate.
|
||||||
|
@ -16,21 +16,55 @@
|
||||||
//!
|
//!
|
||||||
//! **Do not** use this crate in programs that aren't running on the GBA. If you
|
//! **Do not** use this crate in programs that aren't running on the GBA. If you
|
||||||
//! do, it's a giant bag of Undefined Behavior.
|
//! do, it's a giant bag of Undefined Behavior.
|
||||||
//!
|
|
||||||
//! # TESTING POLICY
|
|
||||||
//!
|
|
||||||
//! It is the intent of the crate authors that as much of the crate as possible
|
|
||||||
//! be written so that you can use `cargo test` for at least some parts of your
|
|
||||||
//! code without everything exploding instantly. To that end, where possible we
|
|
||||||
//! attempt to use `cfg` flags to make things safe for `cargo test`. Hopefully
|
|
||||||
//! we got it all.
|
|
||||||
|
|
||||||
pub mod core_extras;
|
/// Assists in defining a newtype wrapper over some base type.
|
||||||
pub(crate) use crate::core_extras::*;
|
///
|
||||||
|
/// Note that rustdoc and derives are all the "meta" stuff, so you can write all
|
||||||
|
/// of your docs and derives in front of your newtype in the same way you would
|
||||||
|
/// for a normal struct. Then the inner type to be wrapped it name.
|
||||||
|
///
|
||||||
|
/// The macro _assumes_ that you'll be using it to wrap zero safe numeric types,
|
||||||
|
/// so it automatically provides a `const fn` method for `new` that just wraps
|
||||||
|
/// `0`. If this is not desired you can add `, no frills` to the invocation.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// ```
|
||||||
|
/// newtype! {
|
||||||
|
/// /// Records a particular key press combination.
|
||||||
|
/// #[derive(Debug, Copy, Clone, Default, PartialEq, Eq)]
|
||||||
|
/// KeyInput, u16
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! newtype {
|
||||||
|
($(#[$attr:meta])* $new_name:ident, $old_name:ident) => {
|
||||||
|
$(#[$attr])*
|
||||||
|
#[repr(transparent)]
|
||||||
|
pub struct $new_name($old_name);
|
||||||
|
impl $new_name {
|
||||||
|
/// A `const` "zero value" constructor
|
||||||
|
pub const fn new() -> Self {
|
||||||
|
$new_name(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
($(#[$attr:meta])* $new_name:ident, $old_name:ident, no frills) => {
|
||||||
|
$(#[$attr])*
|
||||||
|
#[repr(transparent)]
|
||||||
|
pub struct $new_name($old_name);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod builtins;
|
||||||
|
|
||||||
|
pub mod fixed;
|
||||||
|
|
||||||
#[cfg(not(test))]
|
#[cfg(not(test))]
|
||||||
pub mod bios;
|
pub mod bios;
|
||||||
|
|
||||||
|
pub mod core_extras;
|
||||||
|
pub(crate) use crate::core_extras::*;
|
||||||
|
|
||||||
pub mod io_registers;
|
pub mod io_registers;
|
||||||
|
|
||||||
pub mod video_ram;
|
pub mod video_ram;
|
||||||
|
|
Loading…
Add table
Reference in a new issue