sdl2/
sdl.rs

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