Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add trait for linear allocation to enforce use of LinearAllocator at compile-time #188

Merged
merged 3 commits into from
May 19, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
7 changes: 4 additions & 3 deletions ctru-rs/examples/audio-filters.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#![feature(allocator_api)]

use std::f32::consts::PI;
use std::ops::Deref;

use ctru::linear::LinearAllocator;
use ctru::prelude::*;
Expand Down Expand Up @@ -64,10 +65,10 @@ fn main() {

// We create a buffer on the LINEAR memory that will hold our audio data.
// It's necessary for the buffer to live on the LINEAR memory sector since it needs to be accessed by the DSP processor.
let mut audio_data1 = Box::new_in([0u8; AUDIO_WAVE_LENGTH], LinearAllocator);
let mut audio_data1: Box<[_], _> = Box::new_in([0u8; AUDIO_WAVE_LENGTH], LinearAllocator);

// Fill the buffer with the first set of data. This simply writes a sine wave into the buffer.
fill_buffer(audio_data1.as_mut_slice(), NOTEFREQ[4]);
fill_buffer(&mut audio_data1, NOTEFREQ[4]);

// Clone the original buffer to obtain an equal buffer on the LINEAR memory used for double buffering.
let audio_data2 = audio_data1.clone();
Expand Down Expand Up @@ -154,7 +155,7 @@ fn main() {
}

// Double buffer alternation depending on the one used.
let current: &mut Wave = if altern {
let current: &mut Wave<_> = if altern {
&mut wave_info1
} else {
&mut wave_info2
Expand Down
33 changes: 33 additions & 0 deletions ctru-rs/src/linear.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@

use std::alloc::{AllocError, Allocator, Layout};
use std::ptr::NonNull;
use std::rc::{self, Rc};
use std::sync::{self, Arc};

use crate::sealed::Sealed;

// Implementing an `std::alloc::Allocator` type is the best way to handle this case, since it gives
// us full control over the normal `std` implementations (like `Box`). The only issue is that this is another unstable feature to add.
Expand Down Expand Up @@ -47,3 +51,32 @@
}
}
}

/// Trait indicating a type has been allocated using [`LinearAllocator`].
/// This can be used to enforce that a given slice was allocated in LINEAR memory.
#[diagnostic::on_unimplemented(

Check failure on line 57 in ctru-rs/src/linear.rs

View workflow job for this annotation

GitHub Actions / lint (nightly-2024-02-18)

`#[diagnostic]` attribute name space is experimental
message = "{Self} is not allocated with `ctru::linear::LinearAllocator`"
)]
pub trait LinearAllocation: Sealed {}

impl<T> Sealed for Vec<T, LinearAllocator> {}
impl<T> LinearAllocation for Vec<T, LinearAllocator> {}

impl<T: ?Sized> Sealed for Rc<T, LinearAllocator> {}
impl<T: ?Sized> LinearAllocation for Rc<T, LinearAllocator> {}

impl<T: ?Sized> Sealed for rc::Weak<T, LinearAllocator> {}
impl<T: ?Sized> LinearAllocation for rc::Weak<T, LinearAllocator> {}

impl<T: ?Sized> Sealed for Arc<T, LinearAllocator> {}
impl<T: ?Sized> LinearAllocation for Arc<T, LinearAllocator> {}

impl<T: ?Sized> Sealed for sync::Weak<T, LinearAllocator> {}
impl<T: ?Sized> LinearAllocation for sync::Weak<T, LinearAllocator> {}

impl<T: ?Sized> Sealed for Box<T, LinearAllocator> {}
impl<T: ?Sized> LinearAllocation for Box<T, LinearAllocator> {}

// We could also impl for various std::collections types, but it seems unlikely
// those would ever be used for this purpose in practice, since most of the type
// we're dereferencing to a &[T]. The workaround would just be to convert to a Vec.
9 changes: 7 additions & 2 deletions ctru-rs/src/services/ndsp/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@ pub mod wave;
use wave::{Status, Wave};

use crate::error::ResultCode;
use crate::linear::LinearAllocation;
use crate::services::ServiceReference;

use std::cell::{RefCell, RefMut};
use std::error;
use std::fmt;
use std::ops::Deref;
use std::sync::Mutex;

const NUMBER_OF_CHANNELS: u8 = 24;
Expand Down Expand Up @@ -523,7 +525,7 @@ impl Channel<'_> {
/// let ndsp = Ndsp::new()?;
/// let mut channel_0 = ndsp.channel(0)?;
///
/// # let audio_data = Box::new_in([0u8; 96], LinearAllocator);
/// # let audio_data: Box<[_], _> = Box::new_in([0u8; 96], LinearAllocator);
///
/// // Provide your own audio data.
/// let mut wave = Wave::new(audio_data, AudioFormat::PCM16Stereo, false);
Expand All @@ -537,7 +539,10 @@ impl Channel<'_> {
// TODO: Find a better way to handle the wave lifetime problem.
// These "alive wave" shenanigans are the most substantial reason why I'd like to fully re-write this service in Rust.
#[doc(alias = "ndspChnWaveBufAdd")]
pub fn queue_wave(&mut self, wave: &mut Wave) -> std::result::Result<(), Error> {
pub fn queue_wave<Buffer: LinearAllocation + Deref<Target = [u8]>>(
&mut self,
wave: &mut Wave<Buffer>,
) -> std::result::Result<(), Error> {
match wave.status() {
Status::Playing | Status::Queued => return Err(Error::WaveBusy(self.id)),
_ => (),
Expand Down
33 changes: 20 additions & 13 deletions ctru-rs/src/services/ndsp/wave.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,17 @@
//!
//! This modules has all methods and structs required to work with audio waves meant to be played via the [`ndsp`](crate::services::ndsp) service.

use std::ops::{Deref, DerefMut};

use super::{AudioFormat, Error};
use crate::linear::LinearAllocator;
use crate::linear::LinearAllocation;

/// Informational struct holding the raw audio data and playback info.
///
/// You can play audio [`Wave`]s by using [`Channel::queue_wave()`](super::Channel::queue_wave).
pub struct Wave {
pub struct Wave<Buffer: LinearAllocation + Deref<Target = [u8]>> {
/// Data block of the audio wave (and its format information).
buffer: Box<[u8], LinearAllocator>,
buffer: Buffer,
audio_format: AudioFormat,
// Holding the data with the raw format is necessary since `libctru` will access it.
pub(crate) raw_data: ctru_sys::ndspWaveBuf,
Expand All @@ -31,7 +33,10 @@ pub enum Status {
Done = ctru_sys::NDSP_WBUF_DONE,
}

impl Wave {
impl<Buffer> Wave<Buffer>
where
Buffer: LinearAllocation + Deref<Target = [u8]>,
{
/// Build a new playable wave object from a raw buffer on [LINEAR memory](`crate::linear`) and some info.
///
/// # Example
Expand All @@ -45,16 +50,12 @@ impl Wave {
/// use ctru::services::ndsp::{AudioFormat, wave::Wave};
///
/// // Zeroed box allocated in the LINEAR memory.
/// let audio_data = Box::new_in([0u8; 96], LinearAllocator);
/// let audio_data: Box<[_], _> = Box::new_in([0u8; 96], LinearAllocator);
///
/// let wave = Wave::new(audio_data, AudioFormat::PCM16Stereo, false);
/// # }
/// ```
pub fn new(
buffer: Box<[u8], LinearAllocator>,
audio_format: AudioFormat,
looping: bool,
) -> Self {
pub fn new(buffer: Buffer, audio_format: AudioFormat, looping: bool) -> Self {
let sample_count = buffer.len() / audio_format.size();

// Signal to the DSP processor the buffer's RAM sector.
Expand Down Expand Up @@ -98,7 +99,10 @@ impl Wave {
///
/// This function will return an error if the [`Wave`] is currently busy,
/// with the id to the channel in which it's queued.
pub fn get_buffer_mut(&mut self) -> Result<&mut [u8], Error> {
pub fn get_buffer_mut(&mut self) -> Result<&mut [u8], Error>
where
Buffer: DerefMut<Target = [u8]>,
{
match self.status() {
Status::Playing | Status::Queued => {
Err(Error::WaveBusy(self.played_on_channel.unwrap()))
Expand All @@ -117,7 +121,7 @@ impl Wave {
/// # let _runner = test_runner::GdbRunner::default();
/// #
/// # use ctru::linear::LinearAllocator;
/// # let _audio_data = Box::new_in([0u8; 96], LinearAllocator);
/// # let _audio_data: Box<[_], _> = Box::new_in([0u8; 96], LinearAllocator);
/// #
/// use ctru::services::ndsp::{AudioFormat, wave::{Wave, Status}};
///
Expand Down Expand Up @@ -199,7 +203,10 @@ impl TryFrom<u8> for Status {
}
}

impl Drop for Wave {
impl<Buffer> Drop for Wave<Buffer>
where
Buffer: LinearAllocation + Deref<Target = [u8]>,
{
fn drop(&mut self) {
// This was the only way I found I could check for improper drops of `Wave`.
// A panic was considered, but it would cause issues with drop order against `Ndsp`.
Expand Down
Loading