From 2a5ce5f1bfeb7b11b043d3dbcb5327f85050fc9a Mon Sep 17 00:00:00 2001 From: Dan Spencer Date: Sat, 21 Feb 2015 18:00:24 -0700 Subject: [PATCH] Add `Sdl` type --- examples/audio-whitenoise.rs | 2 +- examples/demo.rs | 4 +- examples/game_controller.rs | 4 +- examples/joystick.rs | 4 +- examples/renderer-texture.rs | 4 +- examples/renderer-yuv.rs | 4 +- src/sdl2/sdl.rs | 113 +++++++++++++++++++++++++++++++---- tests/lib.rs | 4 -- 8 files changed, 106 insertions(+), 33 deletions(-) diff --git a/examples/audio-whitenoise.rs b/examples/audio-whitenoise.rs index b7fef4c1..28d44a44 100644 --- a/examples/audio-whitenoise.rs +++ b/examples/audio-whitenoise.rs @@ -20,7 +20,7 @@ impl AudioCallback for MyCallback { } fn main() { - sdl2::init(sdl2::INIT_AUDIO); + let _sdl_context = sdl2::init(sdl2::INIT_AUDIO).unwrap(); let desired_spec = AudioSpecDesired { freq: 44100, diff --git a/examples/demo.rs b/examples/demo.rs index d27daa86..e5aba438 100644 --- a/examples/demo.rs +++ b/examples/demo.rs @@ -8,7 +8,7 @@ use sdl2::event::Event::{Quit, KeyDown}; use sdl2::keycode::KeyCode; pub fn main() { - sdl2::init(sdl2::INIT_VIDEO); + let sdl_context = sdl2::init(sdl2::INIT_VIDEO).unwrap(); let window = match Window::new("rust-sdl2 demo: Video", WindowPos::PosCentered, WindowPos::PosCentered, 800, 600, OPENGL) { Ok(window) => window, @@ -36,6 +36,4 @@ pub fn main() { _ => {} } } - - sdl2::quit(); } diff --git a/examples/game_controller.rs b/examples/game_controller.rs index 066793f7..60f07658 100644 --- a/examples/game_controller.rs +++ b/examples/game_controller.rs @@ -10,7 +10,7 @@ use std::time::duration::Duration; use std::num::SignedInt; fn main() { - sdl2::init(sdl2::INIT_GAME_CONTROLLER); + let sdl_context = sdl2::init(sdl2::INIT_GAME_CONTROLLER).unwrap(); let available = match joystick::num_joysticks() { @@ -73,6 +73,4 @@ fn main() { _ => (), } } - - sdl2::quit(); } diff --git a/examples/joystick.rs b/examples/joystick.rs index 40fc8852..c30aa188 100644 --- a/examples/joystick.rs +++ b/examples/joystick.rs @@ -7,7 +7,7 @@ use std::time::duration::Duration; use std::num::SignedInt; fn main() { - sdl2::init(sdl2::INIT_JOYSTICK); + let sdl_context = sdl2::init(sdl2::INIT_JOYSTICK).unwrap(); let available = match num_joysticks() { @@ -59,6 +59,4 @@ fn main() { _ => (), } } - - sdl2::quit(); } diff --git a/examples/renderer-texture.rs b/examples/renderer-texture.rs index a61bf146..2552510e 100644 --- a/examples/renderer-texture.rs +++ b/examples/renderer-texture.rs @@ -9,7 +9,7 @@ use sdl2::event::Event::{Quit, KeyDown}; use sdl2::keycode::KeyCode; pub fn main() { - sdl2::init(sdl2::INIT_VIDEO); + let sdl_context = sdl2::init(sdl2::INIT_VIDEO).unwrap(); let window = match Window::new("rust-sdl2 demo: Renderer + Texture", WindowPos::PosCentered, WindowPos::PosCentered, 800, 600, OPENGL) { Ok(window) => window, @@ -51,6 +51,4 @@ pub fn main() { _ => {} } } - - sdl2::quit(); } diff --git a/examples/renderer-yuv.rs b/examples/renderer-yuv.rs index 63110499..a019688e 100644 --- a/examples/renderer-yuv.rs +++ b/examples/renderer-yuv.rs @@ -9,7 +9,7 @@ use sdl2::event::Event::{Quit, KeyDown}; use sdl2::keycode::KeyCode; pub fn main() { - sdl2::init(sdl2::INIT_VIDEO); + let sdl_context = sdl2::init(sdl2::INIT_VIDEO).unwrap(); let window = match Window::new("rust-sdl2 demo: YUV", WindowPos::PosCentered, WindowPos::PosCentered, 800, 600, SHOWN) { Ok(window) => window, @@ -67,6 +67,4 @@ pub fn main() { _ => {} } } - - sdl2::quit(); } diff --git a/src/sdl2/sdl.rs b/src/sdl2/sdl.rs index 70b9f4e3..c2ca45d8 100644 --- a/src/sdl2/sdl.rs +++ b/src/sdl2/sdl.rs @@ -1,4 +1,5 @@ use std::ffi::{c_str_to_bytes, CString}; +use std::marker::{NoCopy, PhantomData}; use sys::sdl as ll; @@ -27,30 +28,116 @@ pub enum Error { pub type SdlResult = Result; -pub fn init(flags: InitFlag) -> bool { - unsafe { - ll::SDL_Init(flags.bits()) == 0 +use std::sync::atomic::{AtomicBool, ATOMIC_BOOL_INIT}; +/// Only one Sdl context can be alive at a time. +/// Set to false by default (not alive). +static IS_SDL_CONTEXT_ALIVE: AtomicBool = ATOMIC_BOOL_INIT; + +/// The SDL context type. Initialize with `sdl2::init()`. +/// +/// From a thread-safety perspective, `Sdl` represents the main thread. +/// Only one instance of `Sdl` is allowed per process, and cannot be moved or +/// used across non-main threads. +/// +/// As such, `Sdl` is a useful type for ensuring that SDL types that can only +/// be used on the main thread are initialized that way. +/// +/// For instance, `SDL_PumpEvents()` is not thread safe, and may only be +/// called on the main thread. +/// All functionality that calls `SDL_PumpEvents()` is thus put into an +/// `EventPump` type, which can only be obtained through `Sdl`. +/// This guarantees that the only way to call event-pumping functions is on +/// the main thread. +pub struct Sdl { + _marker: NoCopy +} + +impl !Send for Sdl {} +impl !Sync for Sdl {} + +impl Sdl { + /// Initializes specific SDL subsystems. + pub fn init_subsystem(&self, flags: InitFlag) -> SdlResult { + unsafe { + if ll::SDL_InitSubSystem(flags.bits()) == 0 { + Ok(Subsystem { + flags: flags, + _marker: PhantomData + }) + } else { + Err(get_error()) + } + } + } + + /// Returns the mask of the specified subsystems which have previously been initialized. + pub fn was_init(&self, flags: InitFlag) -> InitFlag { + unsafe { + let raw = ll::SDL_WasInit(flags.bits()); + flags & InitFlag::from_bits(raw).unwrap() + } } } -pub fn init_subsystem(flags: InitFlag) -> bool { - unsafe { - ll::SDL_InitSubSystem(flags.bits()) == 0 +impl Drop for Sdl { + fn drop(&mut self) { + use std::sync::atomic::Ordering; + + let was_alive = IS_SDL_CONTEXT_ALIVE.swap(false, Ordering::Relaxed); + assert!(was_alive); + + unsafe { ll::SDL_Quit(); } } } -pub fn quit_subsystem(flags: InitFlag) { - unsafe { ll::SDL_QuitSubSystem(flags.bits()); } +/// A RAII value representing initalized SDL subsystems. See `sdl2::Sdl::init_subsystem()`. +/// +/// Subsystem initialization is ref-counted. Once `Subsystem::drop()` is called, +/// the specified subsystems' ref-counts are decremented via `SDL_QuitSubSystem`. +pub struct Subsystem<'sdl> { + flags: InitFlag, + _marker: PhantomData<&'sdl Sdl> } -pub fn quit() { - unsafe { ll::SDL_Quit(); } +#[unsafe_destructor] +impl<'sdl> Drop for Subsystem<'sdl> { + fn drop(&mut self) { + unsafe { ll::SDL_QuitSubSystem(self.flags.bits()); } + } } -pub fn was_inited(flags: InitFlag) -> InitFlag { +/// Initializes the SDL library. +/// This must be called before using any other SDL function. +/// +/// # Example +/// ```no_run +/// let sdl_context = sdl2::init(sdl2::INIT_EVERYTHING).unwrap(); +/// +/// let mut event_pump = sdl_context.event_pump(); +/// for event in event_pump.poll_iter() { +/// // ... +/// } +/// +/// // SDL_Quit() is called here as `sdl_context` is dropped. +/// ``` +pub fn init(flags: InitFlag) -> SdlResult { unsafe { - let raw = ll::SDL_WasInit(flags.bits()); - flags & InitFlag::from_bits(raw).unwrap() + use std::sync::atomic::Ordering; + + // Atomically switch the `IS_SDL_CONTEXT_ALIVE` global to true + let was_alive = IS_SDL_CONTEXT_ALIVE.swap(true, Ordering::Relaxed); + + if was_alive { + IS_SDL_CONTEXT_ALIVE.swap(false, Ordering::Relaxed); + Err(format!("Cannot have more than one `Sdl` in use at the same time")) + } else { + if ll::SDL_Init(flags.bits()) == 0 { + Ok(Sdl { _marker: NoCopy }) + } else { + IS_SDL_CONTEXT_ALIVE.swap(false, Ordering::Relaxed); + Err(get_error()) + } + } } } diff --git a/tests/lib.rs b/tests/lib.rs index c7c6c98b..ddbf986c 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -4,8 +4,6 @@ extern crate sdl2; #[test] fn audio_spec_wav() { - sdl2::init(sdl2::INIT_AUDIO); - let wav = sdl2::audio::AudioSpecWAV::load_wav(&Path::new("./tests/sine.wav")).unwrap(); assert_eq!(wav.freq, 22050); @@ -14,6 +12,4 @@ fn audio_spec_wav() { let buffer = wav.get_buffer(); assert_eq!(buffer.len(), 4410); - - sdl2::quit(); }