mirror of
https://github.com/italicsjenga/gba.git
synced 2025-01-11 03:21:30 +11:00
Add RNG (#142)
* Add random * Add GBA random example * switch macro_export to pub(crate) * rename u32_to_u32 macro * remove useless pub use * Comment out extra u32 to u16 macro * Comment random_color example * formatting * More commenting * Add/fix some docs * Fix some doc links * Remove use of u64 - Halve bit widths where u64 was used * Add Gen32::next_color()
This commit is contained in:
parent
2956493be9
commit
ec0ca7d804
69
examples/random_color.rs
Normal file
69
examples/random_color.rs
Normal file
|
@ -0,0 +1,69 @@
|
|||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use gba::prelude::*;
|
||||
|
||||
#[panic_handler]
|
||||
#[allow(unused)]
|
||||
fn panic(info: &core::panic::PanicInfo) -> ! {
|
||||
// This kills the emulation with a message if we're running inside an
|
||||
// emulator we support (mGBA or NO$GBA), or just crashes the game if we
|
||||
// aren't.
|
||||
//fatal!("{}", info);
|
||||
|
||||
loop {
|
||||
DISPCNT.read();
|
||||
}
|
||||
}
|
||||
|
||||
/// Performs a busy loop until VBlank starts.
|
||||
///
|
||||
/// This is very inefficient, and please keep following the lessons until we
|
||||
/// cover how interrupts work!
|
||||
pub fn spin_until_vblank() {
|
||||
while VCOUNT.read() < 160 {}
|
||||
}
|
||||
|
||||
/// Performs a busy loop until VDraw starts.
|
||||
///
|
||||
/// This is very inefficient, and please keep following the lessons until we
|
||||
/// cover how interrupts work!
|
||||
pub fn spin_until_vdraw() {
|
||||
while VCOUNT.read() >= 160 {}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub fn main() -> ! {
|
||||
const SETTING: DisplayControl = DisplayControl::new().with_display_mode(3).with_display_bg2(true);
|
||||
DISPCNT.write(SETTING);
|
||||
// Create default RNG
|
||||
let mut rng = RNG::default();
|
||||
|
||||
let mut px: usize = 0;
|
||||
let mut py: usize = 0;
|
||||
let mut color;
|
||||
|
||||
loop {
|
||||
// Generate color from RNG
|
||||
color = rng.next_color();
|
||||
// now we wait
|
||||
spin_until_vblank();
|
||||
// Draw pixels to screen
|
||||
mode3::bitmap_xy(px, py).write(color);
|
||||
mode3::bitmap_xy(px, py + 1).write(color);
|
||||
mode3::bitmap_xy(px + 1, py).write(color);
|
||||
mode3::bitmap_xy(px + 1, py + 1).write(color);
|
||||
// Increment x and y, wrap as needed
|
||||
px += 2;
|
||||
if px >= mode3::WIDTH {
|
||||
px = 0;
|
||||
py += 2;
|
||||
if py >= mode3::HEIGHT {
|
||||
py = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// now we wait again
|
||||
spin_until_vdraw();
|
||||
}
|
||||
}
|
|
@ -34,6 +34,8 @@ pub mod prelude {
|
|||
pub use crate::save::*;
|
||||
#[cfg(target_arch = "arm")]
|
||||
pub use crate::sync::*;
|
||||
#[cfg(target_arch = "arm")]
|
||||
pub use crate::random::*;
|
||||
}
|
||||
|
||||
pub mod mmio_types;
|
||||
|
@ -58,6 +60,8 @@ pub mod save;
|
|||
#[cfg(target_arch = "arm")]
|
||||
pub mod debugging;
|
||||
|
||||
#[cfg(target_arch = "arm")]
|
||||
pub mod random;
|
||||
/*
|
||||
extern "C" {
|
||||
/// This marks the end of the `.data` and `.bss` sections in IWRAM.
|
||||
|
|
51
src/random.rs
Normal file
51
src/random.rs
Normal file
|
@ -0,0 +1,51 @@
|
|||
|
||||
|
||||
//! Module for random number generation
|
||||
//!
|
||||
//! This module provides functions and utilites for randomly generated values
|
||||
//! # Usage
|
||||
//! ```rust
|
||||
//! use gba::random::RNG;
|
||||
//!
|
||||
//! let mut rng = RNG::seed(123, 321);
|
||||
//! let x = rng.next_u32();
|
||||
//! ```
|
||||
//!
|
||||
//!
|
||||
//!
|
||||
|
||||
macro_rules! make_jump_lcgX {
|
||||
($(#[$attr:meta])* $f:ident, $u:ty) => {
|
||||
$(#[$attr])*
|
||||
/// Gives the state `delta` steps from now in `log(delta)` time.
|
||||
#[must_use]
|
||||
#[inline(always)]
|
||||
const fn $f(mut delta: $u, state: $u, mult: $u, inc: $u) -> $u {
|
||||
let mut cur_mult: $u = mult;
|
||||
let mut cur_plus: $u = inc;
|
||||
let mut acc_mult: $u = 1;
|
||||
let mut acc_plus: $u = 0;
|
||||
while delta > 0 {
|
||||
if (delta & 1) > 0 {
|
||||
acc_mult = acc_mult.wrapping_mul(cur_mult);
|
||||
acc_plus = acc_plus.wrapping_mul(cur_mult).wrapping_add(cur_plus);
|
||||
}
|
||||
cur_plus = cur_mult.wrapping_add(1).wrapping_mul(cur_plus);
|
||||
cur_mult = cur_mult.wrapping_mul(cur_mult);
|
||||
delta /= 2;
|
||||
}
|
||||
acc_mult.wrapping_mul(state).wrapping_add(acc_plus)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
mod gen32;
|
||||
pub use gen32::*;
|
||||
|
||||
mod pcg32;
|
||||
pub use pcg32::*;
|
||||
|
||||
mod bounded_rand;
|
||||
pub use bounded_rand::*;
|
||||
|
||||
mod algorithms;
|
30
src/random/algorithms.rs
Normal file
30
src/random/algorithms.rs
Normal file
|
@ -0,0 +1,30 @@
|
|||
/// Advances a PCG with 32 bits of state.
|
||||
macro_rules! pcg_core_state32 {
|
||||
($state:expr, $inc:expr) => {
|
||||
$state.wrapping_mul(PCG_MULTIPLIER_32).wrapping_add($inc)
|
||||
};
|
||||
}
|
||||
pub(crate) use pcg_core_state32;
|
||||
/// Generates u32 from u32 state
|
||||
macro_rules! pcg_rxs_m_xs_u32_to_u32 {
|
||||
($state: expr) => {{
|
||||
$state ^= ($state >> (4 + ($state >> 28) as u32)).wrapping_mul(277803737u32);
|
||||
$state ^ ($state >> 22)
|
||||
}};
|
||||
}
|
||||
pub(crate) use pcg_rxs_m_xs_u32_to_u32;
|
||||
|
||||
// Alternative for u32 to u16
|
||||
// macro_rules! pcg_xsh_rr_u32_to_u16 {
|
||||
// ($state: expr) => {
|
||||
// ((($state ^ ($state >> 18)) >> 11) as u16).rotate_right($state >> 27) as u16
|
||||
// };
|
||||
// }
|
||||
// pub(crate) use pcg_xsh_rr_u32_to_u16;
|
||||
/// Generates u16 from u32 state
|
||||
macro_rules! pcg_xsh_rs_u32_to_u16 {
|
||||
($state: expr) => {
|
||||
(($state ^ ($state >> 6)) >> (6 + ($state >> 29))) as u16
|
||||
};
|
||||
}
|
||||
pub(crate) use pcg_xsh_rs_u32_to_u16;
|
73
src/random/bounded_rand.rs
Normal file
73
src/random/bounded_rand.rs
Normal file
|
@ -0,0 +1,73 @@
|
|||
use super::*;
|
||||
|
||||
/// Stores the values to sample a number in `0 .. N`
|
||||
///
|
||||
/// Making one of these performs a division operation. In comparison,
|
||||
/// [`Gen32::next_bounded`] will avoid needing to do a division much of the
|
||||
/// time. Thus, unless you need to sample repeatedly from a specific bounded
|
||||
/// range, simply calling `next_bounded` directly might be more efficient.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct BoundedRandU16 {
|
||||
/// number of possible outputs. outputs will be in `0 .. count`
|
||||
count: u16,
|
||||
/// Multiplication threshold thing.
|
||||
///
|
||||
/// <https://arxiv.org/abs/1805.10941>
|
||||
threshold: u16,
|
||||
}
|
||||
impl BoundedRandU16 {
|
||||
/// Constructs a new `BoundedRandU32`.
|
||||
///
|
||||
/// ## Panics
|
||||
/// If the count is 0.
|
||||
#[inline]
|
||||
pub const fn new(count: u16) -> Self {
|
||||
let threshold = count.wrapping_neg() % count;
|
||||
Self { count, threshold }
|
||||
}
|
||||
|
||||
/// Constructs a new `BoundedRandU32`, or `None` on failure.
|
||||
///
|
||||
/// ## Failure
|
||||
/// If the count is 0.
|
||||
#[inline]
|
||||
pub const fn try_new(count: u16) -> Option<Self> {
|
||||
if count > 0 {
|
||||
Some(Self::new(count))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// The number of possible outputs.
|
||||
#[inline]
|
||||
pub const fn count(self) -> u16 {
|
||||
self.count
|
||||
}
|
||||
|
||||
/// Given a `u32`, place it into this bounded range.
|
||||
///
|
||||
/// ## Failure
|
||||
/// * If the value is such that it doesn't fit evenly it is rejected.
|
||||
#[inline]
|
||||
pub const fn place_in_range(self, val: u16) -> Option<u16> {
|
||||
let mul: u32 = (val as u32).wrapping_mul(self.count as u32);
|
||||
let low_part: u16 = mul as u16;
|
||||
if low_part < self.threshold {
|
||||
None
|
||||
} else {
|
||||
//debug_assert!(((mul >> 32) as u32) < self.count());
|
||||
Some((mul >> 16) as u16)
|
||||
}
|
||||
}
|
||||
|
||||
/// Given a gen, sample from the gen until `place_in_range` succeeds.
|
||||
#[inline]
|
||||
pub fn sample<G: Gen32 + ?Sized>(self, gen: &mut G) -> u16 {
|
||||
loop {
|
||||
if let Some(output) = self.place_in_range(gen.next_u16()) {
|
||||
return output;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
134
src/random/gen32.rs
Normal file
134
src/random/gen32.rs
Normal file
|
@ -0,0 +1,134 @@
|
|||
use core::convert::{TryFrom, TryInto};
|
||||
|
||||
use crate::mmio_types::Color;
|
||||
|
||||
/// A Generator with 32 bits of output per step.
|
||||
pub trait Gen32 {
|
||||
/// Generates the next 32 bits of output.
|
||||
fn next_u32(&mut self) -> u32;
|
||||
/// Generates the next 16-bits of output
|
||||
fn next_u16(&mut self) -> u16;
|
||||
|
||||
/// Produces a `Color`
|
||||
fn next_color(&mut self) -> Color {
|
||||
Color(self.next_u16() & 0b0111111111111111)
|
||||
}
|
||||
/// Produce a `bool`
|
||||
#[inline(always)]
|
||||
fn next_bool(&mut self) -> bool {
|
||||
(self.next_u32() as i32) < 0
|
||||
}
|
||||
|
||||
/// Produce a `u8`
|
||||
#[inline(always)]
|
||||
fn next_u8(&mut self) -> u8 {
|
||||
(self.next_u16() >> 8) as u8
|
||||
}
|
||||
|
||||
/// Produce a `u64`
|
||||
#[inline(always)]
|
||||
fn next_u64(&mut self) -> u64 {
|
||||
let l = self.next_u32() as u64;
|
||||
let h = self.next_u32() as u64;
|
||||
h << 32 | l
|
||||
}
|
||||
|
||||
/// Gives a value within `0 .. B`
|
||||
///
|
||||
/// This is often more efficient than making a
|
||||
/// [`BoundedRandU32`](crate::random::BoundedRandU32) if you don't need to use a
|
||||
/// specific bound value more than once.
|
||||
///
|
||||
/// ## Panics
|
||||
/// * If the input is 0.
|
||||
#[inline]
|
||||
fn next_bounded(&mut self, b: u16) -> u16 {
|
||||
assert!(b != 0, "Gen32::next_bounded> Bound must be non-zero.");
|
||||
let mut x = self.next_u16() as u32;
|
||||
let mut mul = (b as u32).wrapping_mul(x);
|
||||
let mut low = mul as u16;
|
||||
if low < b {
|
||||
let threshold = b.wrapping_neg() % b;
|
||||
while low < threshold {
|
||||
x = self.next_u32() as u32;
|
||||
mul = (b as u32).wrapping_mul(x);
|
||||
low = mul as u16;
|
||||
}
|
||||
}
|
||||
let high = (mul >> 16) as u16;
|
||||
high
|
||||
}
|
||||
|
||||
/// Gets a value out of the slice given (by copy).
|
||||
///
|
||||
/// * The default impl will not pick past index `u16::MAX`.
|
||||
#[inline(always)]
|
||||
fn pick<T>(&mut self, buf: &[T]) -> T
|
||||
where
|
||||
Self: Sized,
|
||||
T: Copy,
|
||||
{
|
||||
let end: u16 = saturating_usize_as_u16(buf.len());
|
||||
buf[usize::try_from(self.next_bounded(end)).unwrap()]
|
||||
}
|
||||
|
||||
/// Gets a value out of the slice given (by shared ref).
|
||||
///
|
||||
/// * The default impl will not pick past index `u16::MAX`.
|
||||
#[inline(always)]
|
||||
fn pick_ref<'b, T>(&mut self, buf: &'b [T]) -> &'b T
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let end: u16 = saturating_usize_as_u16(buf.len());
|
||||
&buf[usize::try_from(self.next_bounded(end)).unwrap()]
|
||||
}
|
||||
|
||||
/// Gets a value out of the slice given (by unique ref).
|
||||
///
|
||||
/// * The default impl will not pick past index `u16::MAX`.
|
||||
#[inline(always)]
|
||||
fn pick_mut<'b, T>(&mut self, buf: &'b mut [T]) -> &'b mut T
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let end: u16 = saturating_usize_as_u16(buf.len());
|
||||
&mut buf[usize::try_from(self.next_bounded(end)).unwrap()]
|
||||
}
|
||||
|
||||
/// Shuffles a slice in `O(len)` time.
|
||||
///
|
||||
/// * The default impl shuffles only the first `u16::MAX` elements.
|
||||
#[inline]
|
||||
fn shuffle<T>(&mut self, buf: &mut [T])
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
// Note(Lokathor): The "standard" Fisher-Yates shuffle goes backward from
|
||||
// the end of the slice, but this version allows us to access memory forward
|
||||
// from the start to the end, so that we play more nicely with the
|
||||
// fetch-ahead of most modern CPUs.
|
||||
let mut possibility_count: u16 = buf.len().try_into().unwrap_or(u16::max_value());
|
||||
let mut this_index: usize = 0;
|
||||
let end = buf.len() - 1;
|
||||
while this_index < end {
|
||||
let offset = self.next_bounded(possibility_count) as usize;
|
||||
buf.swap(this_index, this_index + offset);
|
||||
possibility_count -= 1;
|
||||
this_index += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Asserts that `Gen32` is an object-safe trait.
|
||||
const _: [&mut dyn Gen32; 0] = [];
|
||||
|
||||
/// Converts the `usize` into a `u16`, or gives `u16::MAX` if that wouldn't fit.
|
||||
#[inline(always)]
|
||||
const fn saturating_usize_as_u16(val: usize) -> u16 {
|
||||
if val <= u16::MAX as usize {
|
||||
val as u16
|
||||
} else {
|
||||
u16::MAX
|
||||
}
|
||||
}
|
108
src/random/pcg32.rs
Normal file
108
src/random/pcg32.rs
Normal file
|
@ -0,0 +1,108 @@
|
|||
use super::{algorithms::*, Gen32};
|
||||
|
||||
/// A default seed for any PCG.
|
||||
///
|
||||
/// Truncate to fit, as necessary.
|
||||
pub const DEFAULT_PCG_SEED: u128 = 201526561274146932589719779721328219291;
|
||||
|
||||
/// A default `inc` for any PCG.
|
||||
///
|
||||
/// Truncate to fit, as necessary.
|
||||
pub const DEFAULT_PCG_INC: u128 = 34172814569070222299;
|
||||
|
||||
// Other multipliers: 0xffffffff0e703b65 0xf2fc5985
|
||||
const PCG_MULTIPLIER_32: u32 = 0xf13283ad;
|
||||
|
||||
make_jump_lcgX!(jump_lcg32, u32);
|
||||
|
||||
/// A [permuted congruential
|
||||
/// generator](https://en.wikipedia.org/wiki/Permuted_congruential_generator)
|
||||
/// with 32 bits of output per step.
|
||||
///
|
||||
/// * Generally you should create new generator values with the
|
||||
/// [`seed`](Self::seed) constructor. This will shuffle around the inputs
|
||||
/// somewhat, so it will work alright even with "boring" input values like
|
||||
/// `seed(0,0)` or whatever.
|
||||
/// * If you want to exactly save/restore a generator use the `Into` and `From`
|
||||
/// impls to convert the generator into and from a `[u32; 2]`.
|
||||
/// * The methods on this type are quite minimal. You're expected to use the
|
||||
/// [`Gen32`] trait to provide most of the useful operations.
|
||||
///
|
||||
/// Full list of methods can be found in the [`Gen32`] trait
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct RNG {
|
||||
/// 32 bit state, which is used to generate the ouput
|
||||
state: u32,
|
||||
/// `inc` used when advancing the state forward
|
||||
inc: u32,
|
||||
}
|
||||
|
||||
impl RNG {
|
||||
/// Seed a new generator.
|
||||
/// Used to create a new random number generator.
|
||||
pub const fn seed(seed: u32, inc: u32) -> Self {
|
||||
let inc = (inc << 1) | 1;
|
||||
let mut state = pcg_core_state32!(0_u32, inc);
|
||||
state = state.wrapping_add(seed);
|
||||
state = pcg_core_state32!(state, inc);
|
||||
Self { state, inc }
|
||||
}
|
||||
|
||||
/// Gets the next 32-bits of output.
|
||||
#[inline]
|
||||
pub fn next_u32(&mut self) -> u32 {
|
||||
// LLVM do the instruction-level parallelism plz ;_;
|
||||
let out = pcg_rxs_m_xs_u32_to_u32!(self.state);
|
||||
self.state = pcg_core_state32!(self.state, self.inc);
|
||||
out
|
||||
}
|
||||
/// Gets the next 16-bits of output.
|
||||
#[inline]
|
||||
pub fn next_u16(&mut self) -> u16 {
|
||||
let out = pcg_xsh_rs_u32_to_u16!(self.state);
|
||||
self.state = pcg_core_state32!(self.state, self.inc);
|
||||
out
|
||||
}
|
||||
/// Jumps the generator by `delta` steps forward.
|
||||
///
|
||||
/// The generator sequence loops, so if you want to go "backwards" you can
|
||||
/// just subtract the number of steps you want to go back from `u32::MAX` and
|
||||
/// jump by that amount.
|
||||
#[inline]
|
||||
pub fn jump(&mut self, delta: u32) {
|
||||
self.state = jump_lcg32(delta, self.state, PCG_MULTIPLIER_32, self.inc);
|
||||
}
|
||||
}
|
||||
|
||||
impl Gen32 for RNG {
|
||||
#[inline(always)]
|
||||
fn next_u32(&mut self) -> u32 {
|
||||
RNG::next_u32(self)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn next_u16(&mut self) -> u16 {
|
||||
RNG::next_u16(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for RNG {
|
||||
fn default() -> Self {
|
||||
const THE_DEFAULT: RNG = RNG::seed(DEFAULT_PCG_SEED as _, DEFAULT_PCG_INC as _);
|
||||
THE_DEFAULT
|
||||
}
|
||||
}
|
||||
|
||||
impl From<[u32; 2]> for RNG {
|
||||
fn from([state, inc]: [u32; 2]) -> Self {
|
||||
Self { state, inc }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RNG> for [u32; 2] {
|
||||
fn from(pcg: RNG) -> Self {
|
||||
[pcg.state, pcg.inc]
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -38,7 +38,7 @@
|
|||
//! Then, call [`set_timer_for_timeout`] to set the timer you intend to use to
|
||||
//! track the timeout that prevents errors with the save media from hanging your
|
||||
//! game. For more information on GBA timers, see the
|
||||
//! [`timers`](`crate::io::timers`) module's documentation.
|
||||
//! [`timers`](`crate::mmio_types::TimerControl`) module's documentation.
|
||||
//!
|
||||
//! ```rust
|
||||
//! # use gba::save;
|
||||
|
@ -276,7 +276,7 @@ impl SaveAccess {
|
|||
self.access.verify(offset, buffer)
|
||||
}
|
||||
|
||||
/// Returns whether this save media requires the use of [`prepare_write`].
|
||||
/// Returns whether this save media requires the use of [`SaveAccess::prepare_write`].
|
||||
pub fn requires_prepare_write(&self) -> bool {
|
||||
self.info.requires_prepare_write
|
||||
}
|
||||
|
|
|
@ -92,7 +92,7 @@ impl Timeout {
|
|||
}
|
||||
|
||||
/// Returns whether a number of milliseconds has passed since the last call
|
||||
/// to [`start`].
|
||||
/// to [`Timeout::start()`].
|
||||
pub fn is_timeout_met(&self, check_ms: u16) -> bool {
|
||||
self.active && check_ms * 17 < self.timer_l.read()
|
||||
}
|
||||
|
|
|
@ -196,7 +196,7 @@ unsafe fn transfer<T: Copy>(dst: *mut T, src: *const T) {
|
|||
/// variable.
|
||||
///
|
||||
/// This type only works with owned values. If you need to work with borrows,
|
||||
/// consider using [`Mutex`] instead.
|
||||
/// consider using [`super::Mutex`] instead.
|
||||
///
|
||||
/// ## Performance
|
||||
///
|
||||
|
@ -218,7 +218,7 @@ unsafe fn transfer<T: Copy>(dst: *mut T, src: *const T) {
|
|||
/// dangerous as disabling IRQs can cause your program to miss out on important
|
||||
/// interrupts such as V-Blank.
|
||||
///
|
||||
/// Consider using [`Mutex`] instead if you need to use a large amount of
|
||||
/// Consider using [`super::Mutex`] instead if you need to use a large amount of
|
||||
/// operations that would cause IRQs to be disabled. Also consider using
|
||||
/// `#[repr(align(4))]` to force proper alignment for your type.
|
||||
pub struct Static<T> {
|
||||
|
|
Loading…
Reference in a new issue