From f50b630f11c2ebbf32e22f867d2b9083d91c192d Mon Sep 17 00:00:00 2001 From: Dan Spencer Date: Thu, 27 Nov 2014 00:50:25 -0700 Subject: [PATCH] SDL audio is now Task-safe. Add rustrt crate. fixes #220 --- examples/audio-whitenoise.rs | 2 +- src/sdl2/audio.rs | 119 ++++++++++++++++++++++++++--------- src/sdl2/lib.rs | 1 + 3 files changed, 90 insertions(+), 32 deletions(-) diff --git a/examples/audio-whitenoise.rs b/examples/audio-whitenoise.rs index 6ccaa780..27591a42 100644 --- a/examples/audio-whitenoise.rs +++ b/examples/audio-whitenoise.rs @@ -22,7 +22,7 @@ fn main() { let desired_spec = AudioSpecDesired { freq: 44100, channels: 1, - callback: box MyCallback { rng: StdRng::new().unwrap(), volume: 1.0 } + callback: MyCallback { rng: StdRng::new().unwrap(), volume: 1.0 } }; // None: use default device diff --git a/src/sdl2/audio.rs b/src/sdl2/audio.rs index e3067691..1a26dce4 100644 --- a/src/sdl2/audio.rs +++ b/src/sdl2/audio.rs @@ -7,6 +7,7 @@ use std::c_vec::CVec; use libc; use libc::{c_int, size_t, c_void}; use libc::{uint8_t}; +use rustrt::task::Task; use get_error; use rwops::RWops; @@ -242,6 +243,41 @@ pub trait AudioCallback { fn callback(&mut self, &mut [T]); } +/// The userdata as seen by the SDL callback. +struct AudioCallbackUserdata { + task: AudioCallbackTask, + callback: CB +} + +/// A Task is required to use libstd from SDL's audio callback. +struct AudioCallbackTask { + /// Set to None if there was an error running a previous task. + task: Option> +} + +impl Drop for AudioCallbackTask { + /// Destroy the callback task. + fn drop(&mut self) { + use rustrt::local::Local; + use std::mem::replace; + + // Swap out the task with None in order to own it, since drop() only + // provides a reference. + match replace(&mut self.task, None) { + Some(task) => { + // pop current task + let old_task = Local::take(); + + task.destroy(); + + // put task back + Local::put(old_task); + }, + None => () + }; + } +} + /// A phantom type for retreiving the SDL_AudioFormat of a given generic type. /// All format types are returned as native-endian. /// @@ -263,50 +299,76 @@ impl AudioFormatNum for f32 { fn get_audio_format() -> ll::SDL_AudioFormat extern "C" fn audio_callback_marshall, CB: AudioCallback> (userdata: *const c_void, stream: *const uint8_t, len: c_int) { use std::raw::Slice; - use std::mem::{size_of, transmute}; + use std::mem::{replace, size_of, transmute}; unsafe { - let audio_callback: &mut CB = transmute(userdata); - let mut buf: &mut [T] = transmute(Slice { + let mut cb_userdata: &mut AudioCallbackUserdata = transmute(userdata); + let buf: &mut [T] = transmute(Slice { data: stream, len: len as uint / size_of::() }); - audio_callback.callback(buf); + + // Perform a dance to move tasks around without compiler errors + let new_task = match replace(&mut cb_userdata.task.task, None) { + Some(task) => { + let n = task.run(|| { + cb_userdata.callback.callback(buf); + }); + + if n.is_destroyed() { None } + else { Some(n) } + }, + None => None + }; + + replace(&mut cb_userdata.task.task, new_task); } } pub struct AudioSpecDesired, CB: AudioCallback> { pub freq: i32, pub channels: u8, - pub callback: Box + pub callback: CB } impl, CB: AudioCallback> AudioSpecDesired { - fn convert_to_ll(self) -> ll::SDL_AudioSpec { + fn convert_to_ll(freq: i32, channels: u8, userdata: &mut AudioCallbackUserdata) -> ll::SDL_AudioSpec { use std::mem::transmute; + unsafe { ll::SDL_AudioSpec { - freq: self.freq, + freq: freq, format: AudioFormatNum::::get_audio_format(), - channels: self.channels, + channels: channels, silence: 0, samples: 0, padding: 0, size: 0, callback: Some(audio_callback_marshall::), - userdata: transmute(self.callback) + userdata: transmute(userdata) } } } + fn callback_to_userdata(callback: CB) -> Box> { + let mut task = box Task::new(None, None); + task.name = Some("SDL audio callback".into_maybe_owned()); + + box AudioCallbackUserdata { + task: AudioCallbackTask { task: Some(task) }, + callback: callback + } + } + /// Opens a new audio device given the desired parameters and callback. /// Uses `SDL_OpenAudioDevice`. - pub fn open_audio_device(self, device: Option<&str>, iscapture: bool) -> SdlResult>> { + pub fn open_audio_device(self, device: Option<&str>, iscapture: bool) -> SdlResult> { use std::mem::uninitialized; - use std::mem::transmute; use std::ptr::null; use std::c_str::CString; use libc::c_char; - let desired = self.convert_to_ll(); + + let mut userdata = AudioSpecDesired::callback_to_userdata(self.callback); + let desired = AudioSpecDesired::convert_to_ll(self.freq, self.channels, &mut *userdata); let mut obtained = unsafe { uninitialized::() }; unsafe { @@ -322,8 +384,6 @@ impl, CB: AudioCallback> AudioSpecDesired { let device_id = ll::SDL_OpenAudioDevice(device_cstr_ptr, iscapture_flag, &desired, &mut obtained, 0); match device_id { 0 => { - // uninitialize the callback data to avoid memory leaks - let _: Box = transmute(desired.userdata); Err(get_error()) }, id => { @@ -332,12 +392,12 @@ impl, CB: AudioCallback> AudioSpecDesired { false => AudioDeviceID::PlaybackDevice(id) }; - let (spec, callback) = AudioSpec::convert_from_ll_box(obtained); + let spec = AudioSpec::convert_from_ll(obtained); Ok(AudioDevice { device_id: device_id, spec: spec, - callback_data: callback + userdata: userdata }) } } @@ -357,17 +417,14 @@ pub struct AudioSpec { } impl AudioSpec { - fn convert_from_ll_box(spec: ll::SDL_AudioSpec) -> (AudioSpec, Box) { - use std::mem::transmute; - unsafe { - (AudioSpec { - freq: spec.freq, - format: spec.format, - channels: spec.channels, - silence: spec.silence, - samples: spec.samples, - size: spec.size - }, transmute(spec.userdata)) + fn convert_from_ll(spec: ll::SDL_AudioSpec) -> AudioSpec { + AudioSpec { + freq: spec.freq, + format: spec.format, + channels: spec.channels, + silence: spec.silence, + samples: spec.samples, + size: spec.size } } } @@ -399,7 +456,7 @@ pub struct AudioDevice { /// Every audio device corresponds to an SDL_AudioSpec. spec: AudioSpec, /// Store the callback to keep it alive for the entire duration of `AudioDevice`. - callback_data: CB + userdata: Box> } impl AudioDevice { @@ -441,7 +498,7 @@ impl AudioDevice { /// but the callback data will be dropped. pub fn close_and_get_callback(self) -> CB { drop(self.device_id); - self.callback_data + self.userdata.callback } } @@ -451,11 +508,11 @@ pub struct AudioDeviceLockGuard<'a, CB: 'a> { } impl<'a, CB> Deref for AudioDeviceLockGuard<'a, CB> { - fn deref(&self) -> &CB { &self.device.callback_data } + fn deref(&self) -> &CB { &self.device.userdata.callback } } impl<'a, CB> DerefMut for AudioDeviceLockGuard<'a, CB> { - fn deref_mut(&mut self) -> &mut CB { &mut self.device.callback_data } + fn deref_mut(&mut self) -> &mut CB { &mut self.device.userdata.callback } } #[unsafe_destructor] diff --git a/src/sdl2/lib.rs b/src/sdl2/lib.rs index 06d12c91..cc916652 100644 --- a/src/sdl2/lib.rs +++ b/src/sdl2/lib.rs @@ -8,6 +8,7 @@ extern crate libc; extern crate collections; +extern crate rustrt; pub use sdl::*;