Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
186 changes: 54 additions & 132 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,9 @@ use prelude::{GbaCell, IrqFn};

mod macros;

#[cfg(test)]
mod test_harness;

#[cfg(feature = "on_gba")]
mod asm_runtime;
#[cfg(feature = "on_gba")]
Expand Down Expand Up @@ -143,31 +146,35 @@ impl<const N: usize> Align4<[u8; N]> {
/// * If the number of bytes isn't a multiple of 4
#[inline]
#[must_use]
pub fn as_u32_slice(&self) -> &[u32] {
assert!(self.0.len() % 4 == 0);
// Safety: our struct is aligned to 4, so the pointer will already be
// aligned, we only need to check the length
unsafe {
let data: *const u8 = self.0.as_ptr();
let len: usize = self.0.len();
core::slice::from_raw_parts(data.cast::<u32>(), len / 4)
}
pub const fn as_u32_slice(&self) -> &[u32] {
self.as_slice()
}

/// Views these bytes as a slice of `u16`
/// ## Panics
/// * If the number of bytes isn't a multiple of 2
#[inline]
#[must_use]
pub fn as_u16_slice(&self) -> &[u16] {
assert!(self.0.len() % 2 == 0);
// Safety: our struct is aligned to 4, so the pointer will already be
// aligned, we only need to check the length
unsafe {
let data: *const u8 = self.0.as_ptr();
let len: usize = self.0.len();
core::slice::from_raw_parts(data.cast::<u16>(), len / 2)
pub const fn as_u16_slice(&self) -> &[u16] {
self.as_slice()
}

/// Views these bytes as a slice of `T`
/// ## Panics
/// * If the number of bytes isn't a multiple of T
/// * If the alignment of T isn't 4, 2, or 1
#[inline]
#[must_use]
pub const fn as_slice<T: Sized>(&self) -> &[T] {
const {
assert!(N % (size_of::<T>() + (size_of::<T>() % align_of::<T>())) == 0);
assert!(
align_of::<T>() == 4 || align_of::<T>() == 2 || align_of::<T>() == 1
);
}
let data: *const u8 = self.0.as_ptr();
let len = const { N / size_of::<T>() };
unsafe { core::slice::from_raw_parts(data.cast::<T>(), len) }
}
}

Expand All @@ -179,121 +186,6 @@ macro_rules! include_aligned_bytes {
}};
}

#[cfg(test)]
mod test_harness {
use crate::prelude::*;
use crate::{bios, mem, mgba};
use core::fmt::Write;

#[panic_handler]
fn panic(info: &core::panic::PanicInfo) -> ! {
DISPSTAT.write(DisplayStatus::new().with_irq_vblank(true));
BG_PALETTE.index(0).write(Color::from_rgb(25, 10, 5));
IE.write(IrqBits::VBLANK);
IME.write(true);
VBlankIntrWait();
VBlankIntrWait();
VBlankIntrWait();

// the Fatal one kills emulation after one line / 256 bytes
// so emit all the information as Error first
if let Ok(mut log) =
mgba::MgbaBufferedLogger::try_new(mgba::MgbaMessageLevel::Error)
{
writeln!(log, "[failed]").ok();
write!(log, "{}", info).ok();
}

if let Ok(mut log) =
mgba::MgbaBufferedLogger::try_new(mgba::MgbaMessageLevel::Fatal)
{
if let Some(loc) = info.location() {
write!(log, "panic at {loc}! see mgba error log for details.").ok();
} else {
write!(log, "panic! see mgba error log for details.").ok();
}
}

IE.write(IrqBits::new());
bios::IntrWait(true, IrqBits::new());
loop {}
}

pub(crate) trait UnitTest {
fn run(&self);
}

impl<T: Fn()> UnitTest for T {
fn run(&self) {
if let Ok(mut log) =
mgba::MgbaBufferedLogger::try_new(mgba::MgbaMessageLevel::Info)
{
write!(log, "{}...", core::any::type_name::<T>()).ok();
}

self();

if let Ok(mut log) =
mgba::MgbaBufferedLogger::try_new(mgba::MgbaMessageLevel::Info)
{
writeln!(log, "[ok]").ok();
}
}
}

pub(crate) fn test_runner(tests: &[&dyn UnitTest]) {
if let Ok(mut log) =
mgba::MgbaBufferedLogger::try_new(mgba::MgbaMessageLevel::Info)
{
write!(log, "Running {} tests", tests.len()).ok();
}

for test in tests {
test.run();
}
if let Ok(mut log) =
mgba::MgbaBufferedLogger::try_new(mgba::MgbaMessageLevel::Info)
{
write!(log, "Tests finished successfully").ok();
}
}

#[no_mangle]
extern "C" fn main() {
DISPCNT.write(DisplayControl::new().with_video_mode(VideoMode::_0));
BG_PALETTE.index(0).write(Color::new());

crate::test_main();

BG_PALETTE.index(0).write(Color::from_rgb(5, 15, 25));
BG_PALETTE.index(1).write(Color::new());
BG0CNT
.write(BackgroundControl::new().with_charblock(0).with_screenblock(31));
DISPCNT.write(
DisplayControl::new().with_video_mode(VideoMode::_0).with_show_bg0(true),
);

// some niceties for people without mgba-test-runner
let tsb = TEXT_SCREENBLOCKS.get_frame(31).unwrap();
unsafe {
mem::set_u32x80_unchecked(
tsb.into_block::<1024>().as_mut_ptr().cast(),
0,
12,
);
}
Cga8x8Thick.bitunpack_4bpp(CHARBLOCK0_4BPP.as_region(), 0);

let row = tsb.get_row(9).unwrap().iter().skip(6);
for (addr, ch) in row.zip(b"all tests passed!") {
addr.write(TextEntry::new().with_tile(*ch as u16));
}

DISPSTAT.write(DisplayStatus::new());
bios::IntrWait(true, IrqBits::new());
}
}

#[cfg(test)]
mod test {
use super::Align4;
Expand All @@ -304,4 +196,34 @@ mod test {
assert_eq!(a.as_u16_slice(), &[0x100_u16.to_le(), 0x302_u16.to_le()]);
assert_eq!(a.as_u32_slice(), &[0x3020100_u32.to_le()]);
}

#[test_case]
fn align4_as_generic() {
// with padding
#[repr(C, align(4))]
#[derive(PartialEq, Debug)]
struct FiveByte([u8; 5]);

assert_eq!(
Align4(*b"hello...world...").as_slice::<FiveByte>(),
&[FiveByte(*b"hello"), FiveByte(*b"world")]
);

// and without
#[repr(C, align(2))]
#[derive(PartialEq, Debug)]
struct ThreeHalfWords(u16, u16, u16);

assert_eq!(
Align4([
0x11u8, 0x11u8, 0x22u8, 0x22u8, 0x33u8, 0x33u8, 0x44u8, 0x44u8, 0x55u8,
0x55u8, 0x66u8, 0x66u8
])
.as_slice::<ThreeHalfWords>(),
&[
ThreeHalfWords(0x1111, 0x2222, 0x3333),
ThreeHalfWords(0x4444, 0x5555, 0x6666)
]
);
}
}
109 changes: 109 additions & 0 deletions src/test_harness.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
use crate::{bios, mem, mgba, prelude::*};
use core::fmt::Write;

#[panic_handler]
fn panic(info: &core::panic::PanicInfo) -> ! {
DISPSTAT.write(DisplayStatus::new().with_irq_vblank(true));
BG_PALETTE.index(0).write(Color::from_rgb(25, 10, 5));
IE.write(IrqBits::VBLANK);
IME.write(true);
VBlankIntrWait();
VBlankIntrWait();
VBlankIntrWait();

// the Fatal one kills emulation after one line / 256 bytes
// so emit all the information as Error first
if let Ok(mut log) =
mgba::MgbaBufferedLogger::try_new(mgba::MgbaMessageLevel::Error)
{
writeln!(log, "[failed]").ok();
write!(log, "{}", info).ok();
}

if let Ok(mut log) =
mgba::MgbaBufferedLogger::try_new(mgba::MgbaMessageLevel::Fatal)
{
if let Some(loc) = info.location() {
write!(log, "panic at {loc}! see mgba error log for details.").ok();
} else {
write!(log, "panic! see mgba error log for details.").ok();
}
}

IE.write(IrqBits::new());
bios::IntrWait(true, IrqBits::new());
loop {}
}

pub(crate) trait UnitTest {
fn run(&self);
}

impl<T: Fn()> UnitTest for T {
fn run(&self) {
if let Ok(mut log) =
mgba::MgbaBufferedLogger::try_new(mgba::MgbaMessageLevel::Info)
{
write!(log, "{}...", core::any::type_name::<T>()).ok();
}

self();

if let Ok(mut log) =
mgba::MgbaBufferedLogger::try_new(mgba::MgbaMessageLevel::Info)
{
writeln!(log, "[ok]").ok();
}
}
}

pub(crate) fn test_runner(tests: &[&dyn UnitTest]) {
if let Ok(mut log) =
mgba::MgbaBufferedLogger::try_new(mgba::MgbaMessageLevel::Info)
{
write!(log, "Running {} tests", tests.len()).ok();
}

for test in tests {
test.run();
}
if let Ok(mut log) =
mgba::MgbaBufferedLogger::try_new(mgba::MgbaMessageLevel::Info)
{
write!(log, "Tests finished successfully").ok();
}
}

#[no_mangle]
extern "C" fn main() {
DISPCNT.write(DisplayControl::new().with_video_mode(VideoMode::_0));
BG_PALETTE.index(0).write(Color::new());

crate::test_main();

BG_PALETTE.index(0).write(Color::from_rgb(5, 15, 25));
BG_PALETTE.index(1).write(Color::new());
BG0CNT.write(BackgroundControl::new().with_charblock(0).with_screenblock(31));
DISPCNT.write(
DisplayControl::new().with_video_mode(VideoMode::_0).with_show_bg0(true),
);

// some niceties for people without mgba-test-runner
let tsb = TEXT_SCREENBLOCKS.get_frame(31).unwrap();
unsafe {
mem::set_u32x80_unchecked(
tsb.into_block::<1024>().as_mut_ptr().cast(),
0,
12,
);
}
Cga8x8Thick.bitunpack_4bpp(CHARBLOCK0_4BPP.as_region(), 0);

let row = tsb.get_row(9).unwrap().iter().skip(6);
for (addr, ch) in row.zip(b"all tests passed!") {
addr.write(TextEntry::new().with_tile(*ch as u16));
}

DISPSTAT.write(DisplayStatus::new());
bios::IntrWait(true, IrqBits::new());
}
Loading