mirror of
https://github.com/italicsjenga/agb.git
synced 2024-12-23 16:21:33 +11:00
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:
commit
7525a6f572
|
@ -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.
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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}",
|
||||||
|
|
Loading…
Reference in a new issue