sdl2/
sdl.rs

1use std::error;
2use std::ffi::{CStr, CString, NulError};
3use std::fmt;
4use std::rc::Rc;
5use libc::{c_char, uint32_t};
6use std::mem::transmute;
7
8use crate::sys;
9
10#[repr(i32)]
11#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
12pub enum Error {
13    NoMemError = sys::SDL_errorcode::SDL_ENOMEM as i32,
14    ReadError = sys::SDL_errorcode::SDL_EFREAD as i32,
15    WriteError = sys::SDL_errorcode::SDL_EFWRITE as i32,
16    SeekError = sys::SDL_errorcode::SDL_EFSEEK as i32,
17    UnsupportedError = sys::SDL_errorcode::SDL_UNSUPPORTED as i32
18}
19
20impl fmt::Display for Error {
21    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
22        use self::Error::*;
23
24        match *self {
25            NoMemError => write!(f, "Out of memory"),
26            ReadError => write!(f, "Error reading from datastream"),
27            WriteError => write!(f, "Error writing to datastream"),
28            SeekError => write!(f, "Error seeking in datastream"),
29            UnsupportedError => write!(f, "Unknown SDL error")
30        }
31    }
32}
33
34impl error::Error for Error {
35    fn description(&self) -> &str {
36        use self::Error::*;
37
38        match *self {
39            NoMemError => "out of memory",
40            ReadError => "error reading from datastream",
41            WriteError => "error writing to datastream",
42            SeekError => "error seeking in datastream",
43            UnsupportedError => "unknown SDL error"
44        }
45    }
46}
47
48use std::sync::atomic::{AtomicBool};
49/// Only one Sdl context can be alive at a time.
50/// Set to false by default (not alive).
51static IS_SDL_CONTEXT_ALIVE: AtomicBool = AtomicBool::new(false);
52
53/// The SDL context type. Initialize with `sdl2::init()`.
54///
55/// From a thread-safety perspective, `Sdl` represents the main thread.
56/// As such, `Sdl` is a useful type for ensuring that SDL types that can only
57/// be used on the main thread are initialized that way.
58///
59/// For instance, `SDL_PumpEvents()` is not thread safe, and may only be
60/// called on the main thread.
61/// All functionality that calls `SDL_PumpEvents()` is thus put into an
62/// `EventPump` type, which can only be obtained through `Sdl`.
63/// This guarantees that the only way to call event-pumping functions is on
64/// the main thread.
65#[derive(Clone)]
66pub struct Sdl {
67    sdldrop: Rc<SdlDrop>
68}
69
70impl Sdl {
71    #[inline]
72    fn new() -> Result<Sdl, String> {
73        unsafe {
74            use std::sync::atomic::Ordering;
75
76            // Atomically switch the `IS_SDL_CONTEXT_ALIVE` global to true
77            let was_alive = IS_SDL_CONTEXT_ALIVE.swap(true, Ordering::Relaxed);
78
79            if was_alive {
80                Err("Cannot initialize `Sdl` more than once at a time.".to_owned())
81            } else if sys::SDL_Init(0) == 0 {
82                // Initialize SDL without any explicit subsystems (flags = 0).
83                Ok(Sdl {
84                    sdldrop: Rc::new(SdlDrop)
85                })
86            } else {
87                IS_SDL_CONTEXT_ALIVE.swap(false, Ordering::Relaxed);
88                Err(get_error())
89            }
90        }
91    }
92
93    /// Initializes the audio subsystem.
94    #[inline]
95    pub fn audio(&self) -> Result<AudioSubsystem, String> { AudioSubsystem::new(self) }
96
97    /// Initializes the event subsystem.
98    #[inline]
99    pub fn event(&self) -> Result<EventSubsystem, String> { EventSubsystem::new(self) }
100
101    /// Initializes the joystick subsystem.
102    #[inline]
103    pub fn joystick(&self) -> Result<JoystickSubsystem, String> { JoystickSubsystem::new(self) }
104
105    /// Initializes the haptic subsystem.
106    #[inline]
107    pub fn haptic(&self) -> Result<HapticSubsystem, String> { HapticSubsystem::new(self) }
108
109    /// Initializes the game controller subsystem.
110    #[inline]
111    pub fn game_controller(&self) -> Result<GameControllerSubsystem, String> { GameControllerSubsystem::new(self) }
112
113    /// Initializes the timer subsystem.
114    #[inline]
115    pub fn timer(&self) -> Result<TimerSubsystem, String> { TimerSubsystem::new(self) }
116
117    /// Initializes the video subsystem.
118    #[inline]
119    pub fn video(&self) -> Result<VideoSubsystem, String> { VideoSubsystem::new(self) }
120
121    /// Obtains the SDL event pump.
122    ///
123    /// At most one `EventPump` is allowed to be alive during the program's execution.
124    /// If this function is called while an `EventPump` instance is alive, the function will return
125    /// an error.
126    #[inline]
127    pub fn event_pump(&self) -> Result<EventPump, String> {
128        EventPump::new(self)
129    }
130
131    #[inline]
132    #[doc(hidden)]
133    pub fn sdldrop(&self) -> Rc<SdlDrop> {
134        self.sdldrop.clone()
135    }
136}
137
138/// When SDL is no longer in use (the refcount in an `Rc<SdlDrop>` reaches 0), the library is quit.
139#[doc(hidden)]
140#[derive(Debug)]
141pub struct SdlDrop;
142
143impl Drop for SdlDrop {
144    #[inline]
145    fn drop(&mut self) {
146        use std::sync::atomic::Ordering;
147
148        let was_alive = IS_SDL_CONTEXT_ALIVE.swap(false, Ordering::Relaxed);
149        assert!(was_alive);
150
151        unsafe { sys::SDL_Quit(); }
152    }
153}
154
155// No subsystem can implement `Send` because the destructor, `SDL_QuitSubSystem`,
156// utilizes non-atomic reference counting and should thus be called on a single thread.
157// Some subsystems have functions designed to be thread-safe, such as adding a timer or accessing
158// the event queue. These subsystems implement `Sync`.
159
160macro_rules! subsystem {
161    ($name:ident, $flag:expr) => (
162        impl $name {
163            #[inline]
164            fn new(sdl: &Sdl) -> Result<$name, String> {
165                let result = unsafe { sys::SDL_InitSubSystem($flag) };
166
167                if result == 0 {
168                    Ok($name {
169                        _subsystem_drop: Rc::new(SubsystemDrop {
170                            _sdldrop: sdl.sdldrop.clone(),
171                            flag: $flag
172                        })
173                    })
174                } else {
175                    Err(get_error())
176                }
177            }
178        }
179    );
180    ($name:ident, $flag:expr, nosync) => (
181        #[derive(Debug, Clone)]
182        pub struct $name {
183            /// Subsystems cannot be moved or (usually) used on non-main threads.
184            /// Luckily, Rc restricts use to the main thread.
185            _subsystem_drop: Rc<SubsystemDrop>
186        }
187
188        impl $name {
189            /// Obtain an SDL context.
190            #[inline]
191            pub fn sdl(&self) -> Sdl {
192                Sdl { sdldrop: self._subsystem_drop._sdldrop.clone() }
193            }
194        }
195
196        subsystem!($name, $flag);
197    );
198    ($name:ident, $flag:expr, sync) => (
199        pub struct $name {
200            /// Subsystems cannot be moved or (usually) used on non-main threads.
201            /// Luckily, Rc restricts use to the main thread.
202            _subsystem_drop: Rc<SubsystemDrop>
203        }
204        unsafe impl Sync for $name {}
205
206        impl $name {
207            #[inline]
208            pub fn clone(&self) -> $name {
209                $name {
210                    _subsystem_drop: self._subsystem_drop.clone()
211                }
212            }
213
214            /// Obtain an SDL context.
215            #[inline]
216            pub fn sdl(&self) -> Sdl {
217                Sdl { sdldrop: self._subsystem_drop._sdldrop.clone() }
218            }
219        }
220
221        subsystem!($name, $flag);
222    )
223}
224
225/// When a subsystem is no longer in use (the refcount in an `Rc<SubsystemDrop>` reaches 0),
226/// the subsystem is quit.
227#[derive(Debug, Clone)]
228struct SubsystemDrop {
229    _sdldrop: Rc<SdlDrop>,
230    flag: uint32_t
231}
232
233impl Drop for SubsystemDrop {
234    #[inline]
235    fn drop(&mut self) {
236        unsafe { sys::SDL_QuitSubSystem(self.flag); }
237    }
238}
239
240subsystem!(AudioSubsystem, sys::SDL_INIT_AUDIO, nosync);
241subsystem!(GameControllerSubsystem, sys::SDL_INIT_GAMECONTROLLER, nosync);
242subsystem!(HapticSubsystem, sys::SDL_INIT_HAPTIC, nosync);
243subsystem!(JoystickSubsystem, sys::SDL_INIT_JOYSTICK, nosync);
244subsystem!(VideoSubsystem, sys::SDL_INIT_VIDEO, nosync);
245// Timers can be added on other threads.
246subsystem!(TimerSubsystem, sys::SDL_INIT_TIMER, sync);
247// The event queue can be read from other threads.
248subsystem!(EventSubsystem, sys::SDL_INIT_EVENTS, sync);
249
250static mut IS_EVENT_PUMP_ALIVE: bool = false;
251
252/// A thread-safe type that encapsulates SDL event-pumping functions.
253pub struct EventPump {
254    _sdldrop: Rc<SdlDrop>
255}
256
257impl EventPump {
258    /// Obtains the SDL event pump.
259    #[inline]
260    fn new(sdl: &Sdl) -> Result<EventPump, String> {
261        // Called on the main SDL thread.
262
263        unsafe {
264            if IS_EVENT_PUMP_ALIVE {
265                Err("an `EventPump` instance is already alive - there can only be one `EventPump` in use at a time.".to_owned())
266            } else {
267                // Initialize the events subsystem, just in case none of the other subsystems have done it yet.
268                let result = sys::SDL_InitSubSystem(sys::SDL_INIT_EVENTS);
269
270                if result == 0 {
271                    IS_EVENT_PUMP_ALIVE = true;
272
273                    Ok(EventPump {
274                        _sdldrop: sdl.sdldrop.clone()
275                    })
276                } else {
277                    Err(get_error())
278                }
279            }
280        }
281    }
282}
283
284impl Drop for EventPump {
285    #[inline]
286    fn drop(&mut self) {
287        // Called on the main SDL thread.
288
289        unsafe {
290            assert!(IS_EVENT_PUMP_ALIVE);
291            sys::SDL_QuitSubSystem(sys::SDL_INIT_EVENTS);
292            IS_EVENT_PUMP_ALIVE = false;
293        }
294    }
295}
296
297/// Get platform name
298#[inline]
299pub fn get_platform() -> &'static str {
300  unsafe {
301    CStr::from_ptr(sys::SDL_GetPlatform()).to_str().unwrap()
302  }
303}
304
305/// Initializes the SDL library.
306/// This must be called before using any other SDL function.
307///
308/// # Example
309/// ```no_run
310/// let sdl_context = sdl2::init().unwrap();
311/// let mut event_pump = sdl_context.event_pump().unwrap();
312///
313/// for event in event_pump.poll_iter() {
314///     // ...
315/// }
316///
317/// // SDL_Quit() is called here as `sdl_context` is dropped.
318/// ```
319#[inline]
320pub fn init() -> Result<Sdl, String> { Sdl::new() }
321
322pub fn get_error() -> String {
323    unsafe {
324        let err = sys::SDL_GetError();
325        CStr::from_ptr(err as *const _).to_str().unwrap().to_owned()
326    }
327}
328
329pub fn set_error(err: &str) -> Result<(), NulError> {
330    let c_string = r#try!(CString::new(err));
331    Ok(unsafe {
332        sys::SDL_SetError(c_string.as_ptr() as *const c_char);
333    })
334}
335
336pub fn set_error_from_code(err: Error) {
337    unsafe { sys::SDL_Error(transmute(err)); }
338}
339
340pub fn clear_error() {
341    unsafe { sys::SDL_ClearError(); }
342}