Make missing a vblank interrupt wait less painful (#374)

If you missed a vblank, then the next call to wait_for_vblank would
pointlessly wait rather than returning immediately. Meaning if you
missed a vblank by a few cycles, you'd be waiting for the entire next
one :(.

- [x] Changelog updated / no changelog update needed
This commit is contained in:
Gwilym Kuiper 2023-01-12 22:24:30 +00:00 committed by GitHub
commit 7525a6f572
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 38 additions and 21 deletions

View file

@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed ### Changed
- Text renderer can now be re-used which is useful for rpg style character/word at a time text boxes. - Text renderer can now be re-used which is useful for rpg style character/word at a time text boxes.
- If a vblank happens outside of `wait_for_vblank`, then next call will immediately return.
### Fixed ### Fixed
- Zero volume now plays no sound. - Zero volume now plays no sound.

View file

@ -7,7 +7,7 @@ use core::{
use alloc::boxed::Box; use alloc::boxed::Box;
use bare_metal::CriticalSection; use bare_metal::CriticalSection;
use crate::{display::DISPLAY_STATUS, memory_mapped::MemoryMapped}; use crate::{display::DISPLAY_STATUS, memory_mapped::MemoryMapped, sync::Static};
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub enum Interrupt { pub enum Interrupt {
@ -308,27 +308,43 @@ where
r r
} }
static NUM_VBLANKS: Static<usize> = Static::new(0); // overflows after 2.27 years
static HAS_CREATED_INTERRUPT: Static<bool> = Static::new(false);
#[non_exhaustive] #[non_exhaustive]
pub struct VBlank {} pub struct VBlank {
last_waited_number: Cell<usize>,
}
impl VBlank { impl VBlank {
/// Handles setting up everything required to be able to use the wait for /// Handles setting up everything required to be able to use the wait for
/// interrupt syscall. /// interrupt syscall.
#[must_use] #[must_use]
pub fn get() -> Self { pub fn get() -> Self {
interrupt_to_root(Interrupt::VBlank).add(); if !HAS_CREATED_INTERRUPT.read() {
VBlank {} let handler = add_interrupt_handler(Interrupt::VBlank, |_| {
NUM_VBLANKS.write(NUM_VBLANKS.read() + 1);
});
core::mem::forget(handler);
HAS_CREATED_INTERRUPT.write(true);
}
VBlank {
last_waited_number: Cell::new(NUM_VBLANKS.read()),
}
} }
/// Pauses CPU until vblank interrupt is triggered where code execution is /// Pauses CPU until vblank interrupt is triggered where code execution is
/// resumed. /// resumed.
pub fn wait_for_vblank(&self) { pub fn wait_for_vblank(&self) {
crate::syscall::wait_for_vblank(); let last_waited_number = self.last_waited_number.get();
} self.last_waited_number.set(NUM_VBLANKS.read() + 1);
}
impl Drop for VBlank { if last_waited_number < NUM_VBLANKS.read() {
fn drop(&mut self) { return;
interrupt_to_root(Interrupt::VBlank).reduce(); }
crate::syscall::wait_for_vblank();
} }
} }
@ -339,10 +355,13 @@ mod tests {
use core::cell::RefCell; use core::cell::RefCell;
#[test_case] #[test_case]
fn test_vblank_interrupt_handler(_gba: &mut crate::Gba) { fn test_can_create_and_destroy_interrupt_handlers(_gba: &mut crate::Gba) {
let mut counter = Mutex::new(RefCell::new(0));
let counter_2 = Mutex::new(RefCell::new(0));
let vblank = VBlank::get();
{ {
let counter = Mutex::new(RefCell::new(0));
let counter_2 = Mutex::new(RefCell::new(0));
let _a = add_interrupt_handler(Interrupt::VBlank, |key: CriticalSection| { let _a = add_interrupt_handler(Interrupt::VBlank, |key: CriticalSection| {
*counter.borrow(key).borrow_mut() += 1; *counter.borrow(key).borrow_mut() += 1;
}); });
@ -350,8 +369,6 @@ mod tests {
*counter_2.borrow(key).borrow_mut() += 1; *counter_2.borrow(key).borrow_mut() += 1;
}); });
let vblank = VBlank::get();
while free(|key| { while free(|key| {
*counter.borrow(key).borrow() < 100 || *counter_2.borrow(key).borrow() < 100 *counter.borrow(key).borrow() < 100 || *counter_2.borrow(key).borrow() < 100
}) { }) {
@ -359,11 +376,10 @@ mod tests {
} }
} }
assert_eq!( vblank.wait_for_vblank();
interrupt_to_root(Interrupt::VBlank).next.get(), vblank.wait_for_vblank();
core::ptr::null(),
"expected the interrupt table for vblank to be empty" assert_eq!(*counter.get_mut().get_mut(), 100);
);
} }
#[test_case] #[test_case]

View file

@ -56,7 +56,7 @@ pub fn wait_for_interrupt() {
/// The vblank interrupt handler [VBlank][crate::interrupt::VBlank] should be /// The vblank interrupt handler [VBlank][crate::interrupt::VBlank] should be
/// used instead of calling this function directly. /// used instead of calling this function directly.
pub fn wait_for_vblank() { pub(crate) fn wait_for_vblank() {
unsafe { unsafe {
asm!( asm!(
"swi {SWI}", "swi {SWI}",