thorium 0.4.0

Lokathor does stuff, ium
Documentation
//! This module defines the interface between platform binary and game library.

use super::*;
pub use core::time::Duration;

/// Debug bitmap buffer
#[derive(Debug)]
#[allow(missing_docs)]
pub struct Bitmap {
  /// u32 order: A R G B
  pub memory: *mut u8,
  /// width in pixels
  pub width: i32,
  /// height in pixels
  pub height: i32,
  /// pitch in BYTES, pass directly to .offset
  pub pitch: isize,
}

/// A single moment in a 2-channel sound output stream
///
/// Reminder: a sample has no fixed duration. The duration is set by the samples
/// per second of the playback device.
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
#[repr(C)]
#[allow(missing_docs)]
pub struct StereoI16 {
  // Note(Lokathor): The fields here are ordered to match SDL2's channel
  // ordering. See https://wiki.libsdl.org/SDL_AudioSpec
  pub left: i16,
  pub right: i16,
}

#[derive(Clone, Default)]
#[allow(missing_docs)]
pub struct SNESController {
  pub arrow_up: bool,
  pub arrow_down: bool,
  pub arrow_left: bool,
  pub arrow_right: bool,
  pub north: bool,
  pub south: bool,
  pub east: bool,
  pub west: bool,
  pub l: bool,
  pub r: bool,
  pub start: bool,
  pub select: bool,
}
impl core::fmt::Debug for SNESController {
  #[rustfmt::skip]
  fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
    write!(f, "SNESController {{")?;
    if self.arrow_up { write!(f, " arrow_up, ")?; }
    if self.arrow_down { write!(f, " arrow_down, ")?; }
    if self.arrow_left { write!(f, " arrow_left, ")?; }
    if self.arrow_right { write!(f, " arrow_right, ")?; }
    if self.north { write!(f, " north, ")?; }
    if self.south { write!(f, " south, ")?; }
    if self.east { write!(f, " east, ")?; }
    if self.west { write!(f, " west, ")?; }
    if self.l { write!(f, " l, ")?; }
    if self.r { write!(f, " r, ")?; }
    if self.start { write!(f, " start, ")?; }
    if self.select { write!(f, " select, ")?; }
    write!(f, "}}")
  }
}

/// Right now we only accept xbox controller input.
#[derive(Debug, Clone, Default)]
#[allow(missing_docs)]
pub struct GameInput {
  pub keyboard: SNESController,
  pub gamepads: Vec<SNESController>,
}

/// Debug struct for the game's memory.
#[derive(Debug, Default, Clone)]
#[allow(missing_docs)]
pub struct GameMemory {
  pub config: crate::dmg_render::DMGConfig,
  pub pal_index: usize,
  pub px: u8,
  pub py: u8,
  pub l_was_down: bool,
  pub r_was_down: bool,
  pub east_was_down: bool,
  pub south_was_down: bool,
  pub north_was_down: bool,
  // sounds
  pub apu: DMGAPU,
}

/// Responds to input, renders the frame, and prepares sound.
#[cfg(feature = "dynamic_link")]
pub type GameUpdateAndRenderFn = extern "C" fn(&mut GameMemory, &GameInput, &mut Bitmap);
/// Responds to input, renders the frame, and prepares sound.
#[cfg(not(feature = "dynamic_link"))]
pub type GameUpdateAndRenderFn = fn(&mut GameMemory, &GameInput, &mut Bitmap);

/// Fills the sound buffer based on the prepared sound.
///
/// This must be very quick, less than 1ms.
#[cfg(feature = "dynamic_link")]
pub type GameGetSoundSamplesFn = extern "C" fn(&mut GameMemory, i32, &mut [StereoI16]);
/// Fills the sound buffer based on the prepared sound.
///
/// This must be very quick, less than 1ms.
#[cfg(not(feature = "dynamic_link"))]
pub type GameGetSoundSamplesFn = fn(&mut GameMemory, i32, &mut [StereoI16]);

/// `file!():line!()> expr: expr_value`, one per expression
///
/// only works with `debug_assertions` on.
#[macro_export]
macro_rules! dump {
  ($($n:expr),*) => (if cfg!(debug_assertions) {
    $(std::println!(concat!("{}:{}> ",stringify!($n),": {:?}"),file!(),line!(),$n);)*
  })
}

/// `file!():line!()>`, a space, and then the message.
///
/// only works with `debug_assertions` on.
#[macro_export]
macro_rules! debugln {
  ($($arg:tt)*) => (if cfg!(debug_assertions) {
    // This particular prefix was chosen because that's what VS Code will
    // recognize and then let you ctrl+click to jump to that point in that file.
    std::print!("{}:{}> ",file!(),line!());
    std::println!($($arg)*);
  })
}

/// Read a value out of a [thread_local](std::thread_local)
/// [Cell](core::cell::Cell)
///
/// ```
/// use core::cell::Cell;
/// use thorium::tlc_read;
///
/// thread_local! {
///   static YES: Cell<bool> = Cell::new(true);
/// }
/// fn main() {
///   assert!(tlc_read!(YES));
/// }
/// ```
#[macro_export]
macro_rules! tlc_read {
  ($cell_name:ident) => {
    $cell_name.with(Cell::get)
  };
}

/// Write a new value to a [thread_local](std::thread_local)
/// [Cell](core::cell::Cell)
///
/// ```
/// use core::cell::Cell;
/// use thorium::{tlc_read, tlc_write};
///
/// thread_local! {
///   static VAL: Cell<i32> = Cell::new(0);
/// }
/// fn main() {
///   tlc_write!(VAL, 7);
///   assert_eq!(tlc_read!(VAL), 7);
/// }
/// ```
#[macro_export]
macro_rules! tlc_write {
  ($cell_name:ident, $new_value:expr) => {
    $cell_name.with(|cell| cell.set($new_value));
  };
}

/// Modify a [thread_local](std::thread_local) [Cell](core::cell::Cell) with the
/// provided function.
///
/// ```
/// use core::cell::Cell;
/// use thorium::{tlc_read, tlc_modify};
///
/// thread_local! {
///   static VAL: Cell<i32> = Cell::new(3);
/// }
/// fn main() {
///   tlc_modify!(VAL, |v| v + 2);
///   assert_eq!(tlc_read!(VAL), 5);
/// }
/// ```
#[macro_export]
macro_rules! tlc_modify {
  ($cell_name:ident, $modify_func:expr) => {
    $cell_name.with(|cell| cell.set($modify_func(cell.get())));
  };
}

/// Assists in defining a newtype wrapper over some base type.
///
/// 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 numeric types and that
/// it's safe to have a `0` value, so it automatically provides a `const fn`
/// method for `new` that just wraps `0`. Also, it derives Debug, Clone, Copy,
/// Default, PartialEq, and Eq. If all this is not desired you can add `, no
/// frills` to the invocation.
#[macro_export]
macro_rules! newtype {
  ($(#[$attr:meta])* $new_name:ident, $v:vis $old_name:ty) => {
    $(#[$attr])*
    #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
    #[repr(transparent)]
    pub struct $new_name($v $old_name);
    impl $new_name {
      /// A `const` "zero value" constructor
      pub const fn new() -> Self {
        $new_name(0)
      }
    }
  };
  ($(#[$attr:meta])* $new_name:ident, $v:vis $old_name:ty, no frills) => {
    $(#[$attr])*
    #[repr(transparent)]
    pub struct $new_name($v $old_name);
  };
}
/// Assists in defining a newtype that's an enum.
///
/// First give `NewType = OldType,`, then define the tags and their explicit
/// values with zero or more entries of `TagName = base_value,`. In both cases
/// you can place doc comments or other attributes directly on to the type
/// declaration or the tag declaration.
///
/// The generated enum will get an appropriate `repr` attribute as well as
/// Debug, Clone, Copy, PartialEq, and Eq
#[macro_export]
macro_rules! newtype_enum {
  (
    $(#[$struct_attr:meta])*
    $new_name:ident = $old_name:ident,
    $($(#[$tag_attr:meta])* $tag_name:ident = $base_value:expr,)*
  ) => {
    $(#[$struct_attr])*
    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
    #[repr($old_name)]
    pub enum $new_name {
      $(
        $(#[$tag_attr])*
        $tag_name = $base_value,
      )*
    }
  };
}