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;
8use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
9use sys::init::{
10    SDL_INIT_AUDIO, SDL_INIT_CAMERA, SDL_INIT_EVENTS, SDL_INIT_GAMEPAD, SDL_INIT_HAPTIC,
11    SDL_INIT_JOYSTICK, SDL_INIT_SENSOR, SDL_INIT_VIDEO,
12};
13
14use crate::sys;
15
16#[derive(Debug, Clone, Eq, PartialEq, Hash)]
17pub struct Error(pub(crate) String);
18
19impl fmt::Display for Error {
20    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
21        f.write_str(&self.0)
22    }
23}
24
25impl error::Error for Error {
26    fn description(&self) -> &str {
27        &self.0
28    }
29}
30
31impl Error {
32    pub fn is_empty(&self) -> bool {
33        self.0.is_empty()
34    }
35}
36
37static IS_MAIN_THREAD_DECLARED: AtomicBool = AtomicBool::new(false);
40
41static SDL_COUNT: AtomicU32 = AtomicU32::new(0);
43
44thread_local! {
45    static IS_MAIN_THREAD: Cell<bool> = const { Cell::new(false) };
47}
48
49#[derive(Clone)]
62pub struct Sdl {
63    sdldrop: SdlDrop,
64}
65
66impl Sdl {
67    #[inline]
68    #[doc(alias = "SDL_Init")]
69    fn new() -> Result<Sdl, Error> {
70        let was_main_thread_declared = IS_MAIN_THREAD_DECLARED.swap(true, Ordering::SeqCst);
72
73        IS_MAIN_THREAD.with(|is_main_thread| {
74            if was_main_thread_declared {
75                if !is_main_thread.get() {
76                    if !(cfg!(test) || cfg!(feature = "test-mode")) {
79                        return Err(Error("Cannot initialize `Sdl` from a thread other than the main thread.  For testing, you can disable this check with the feature 'test-mode'.".to_owned()));
80                    }
81        }
82            } else {
83                is_main_thread.set(true);
84            }
85            Ok(())
86        })?;
87
88        if SDL_COUNT.fetch_add(1, Ordering::Relaxed) == 0 {
90            let result;
91
92            unsafe {
93                result = sys::init::SDL_Init(0);
94            }
95
96            if !result {
97                SDL_COUNT.store(0, Ordering::Relaxed);
98                return Err(get_error());
99            }
100        }
101
102        Ok(Sdl {
103            sdldrop: SdlDrop {
104                marker: PhantomData,
105            },
106        })
107    }
108
109    #[inline]
111    pub fn audio(&self) -> Result<AudioSubsystem, Error> {
112        AudioSubsystem::new(self)
113    }
114
115    #[inline]
117    pub fn event(&self) -> Result<EventSubsystem, Error> {
118        EventSubsystem::new(self)
119    }
120
121    #[inline]
123    pub fn joystick(&self) -> Result<JoystickSubsystem, Error> {
124        JoystickSubsystem::new(self)
125    }
126
127    #[inline]
129    pub fn haptic(&self) -> Result<HapticSubsystem, Error> {
130        HapticSubsystem::new(self)
131    }
132
133    #[inline]
135    pub fn gamepad(&self) -> Result<GamepadSubsystem, Error> {
136        GamepadSubsystem::new(self)
137    }
138
139    #[inline]
141    pub fn sensor(&self) -> Result<SensorSubsystem, Error> {
142        SensorSubsystem::new(self)
143    }
144
145    #[inline]
147    pub fn video(&self) -> Result<VideoSubsystem, Error> {
148        VideoSubsystem::new(self)
149    }
150
151    #[inline]
157    pub fn event_pump(&self) -> Result<EventPump, Error> {
158        EventPump::new(self)
159    }
160
161    #[inline]
162    #[doc(hidden)]
163    pub fn sdldrop(&self) -> SdlDrop {
164        self.sdldrop.clone()
165    }
166}
167
168#[doc(hidden)]
170#[derive(Debug)]
171pub struct SdlDrop {
172    marker: PhantomData<*mut ()>,
175}
176
177impl SdlDrop {
178    unsafe fn new() -> Self {
187        Self {
188            marker: PhantomData,
189        }
190    }
191}
192
193impl Clone for SdlDrop {
194    fn clone(&self) -> SdlDrop {
195        let prev_count = SDL_COUNT.fetch_add(1, Ordering::Relaxed);
196        assert!(prev_count > 0);
197        SdlDrop {
198            marker: PhantomData,
199        }
200    }
201}
202
203impl Drop for SdlDrop {
204    #[inline]
205    #[doc(alias = "SDL_Quit")]
206    fn drop(&mut self) {
207        let prev_count = SDL_COUNT.fetch_sub(1, Ordering::Relaxed);
208        assert!(prev_count > 0);
209        if prev_count == 1 {
210            unsafe {
211                sys::init::SDL_Quit();
212            }
213            IS_MAIN_THREAD_DECLARED.store(false, Ordering::SeqCst);
214        }
215    }
216}
217
218macro_rules! subsystem {
224    ($name:ident, $flag:expr, $counter:ident, nosync) => {
225        static $counter: AtomicU32 = AtomicU32::new(0);
226
227        #[derive(Debug)]
228        pub struct $name {
229            marker: PhantomData<*mut ()>,
233        }
234
235        impl $name {
236            #[inline]
237            #[doc(alias = "SDL_InitSubSystem")]
238            fn new(sdl: &Sdl) -> Result<Self, Error> {
239                if $counter.fetch_add(1, Ordering::Relaxed) == 0 {
240                    let result;
241
242                    unsafe {
243                        result = sys::init::SDL_InitSubSystem($flag);
244                    }
245
246                    if !result {
247                        $counter.store(0, Ordering::Relaxed);
248                        return Err(get_error());
249                    }
250
251                    mem::forget(sdl.sdldrop.clone());
253                }
254
255                Ok(Self {
256                    marker: PhantomData,
257                })
258            }
259        }
260
261        impl Clone for $name {
262            fn clone(&self) -> Self {
263                let prev_count = $counter.fetch_add(1, Ordering::Relaxed);
264                assert!(prev_count > 0);
265                Self {
266                    marker: PhantomData,
267                }
268            }
269        }
270
271        impl Drop for $name {
272            #[inline]
273            #[doc(alias = "SDL_QuitSubSystem")]
274            fn drop(&mut self) {
275                let prev_count = $counter.fetch_sub(1, Ordering::Relaxed);
276                assert!(prev_count > 0);
277                if prev_count == 1 {
278                    unsafe {
279                        sys::init::SDL_QuitSubSystem($flag);
280                        let _ = SdlDrop::new();
282                    }
283                }
284            }
285        }
286    };
287    ($name:ident, $flag:expr, $counter:ident, sync) => {
288        subsystem!($name, $flag, $counter, nosync);
289        unsafe impl Sync for $name {}
290    };
291}
292
293subsystem!(AudioSubsystem, SDL_INIT_AUDIO, AUDIO_COUNT, nosync);
294subsystem!(VideoSubsystem, SDL_INIT_VIDEO, VIDEO_COUNT, nosync);
295subsystem!(JoystickSubsystem, SDL_INIT_JOYSTICK, JOYSTICK_COUNT, nosync);
296subsystem!(HapticSubsystem, SDL_INIT_HAPTIC, HAPTIC_COUNT, nosync);
297subsystem!(GamepadSubsystem, SDL_INIT_GAMEPAD, GAMEPAD_COUNT, nosync);
298subsystem!(EventSubsystem, SDL_INIT_EVENTS, EVENT_COUNT, sync);
300subsystem!(SensorSubsystem, SDL_INIT_SENSOR, SENSOR_COUNT, nosync);
301subsystem!(CameraSubsystem, SDL_INIT_CAMERA, CAMERA_COUNT, nosync);
302
303static IS_EVENT_PUMP_ALIVE: AtomicBool = AtomicBool::new(false);
304
305pub struct EventPump {
307    _event_subsystem: EventSubsystem,
308}
309
310impl EventPump {
311    #[inline]
313    #[doc(alias = "SDL_InitSubSystem")]
314    fn new(sdl: &Sdl) -> Result<EventPump, Error> {
315        if IS_EVENT_PUMP_ALIVE.load(Ordering::Relaxed) {
317            Err(Error("an `EventPump` instance is already alive - there can only be one `EventPump` in use at a time.".to_owned()))
318        } else {
319            let _event_subsystem = sdl.event()?;
320            IS_EVENT_PUMP_ALIVE.store(true, Ordering::Relaxed);
321            Ok(EventPump { _event_subsystem })
322        }
323    }
324}
325
326impl Drop for EventPump {
327    #[inline]
328    #[doc(alias = "SDL_QuitSubSystem")]
329    fn drop(&mut self) {
330        assert!(IS_EVENT_PUMP_ALIVE.load(Ordering::Relaxed));
332        IS_EVENT_PUMP_ALIVE.store(false, Ordering::Relaxed);
333    }
334}
335
336#[inline]
338#[doc(alias = "SDL_GetPlatform")]
339pub fn get_platform() -> &'static str {
340    unsafe {
341        CStr::from_ptr(sys::platform::SDL_GetPlatform())
342            .to_str()
343            .unwrap()
344    }
345}
346
347#[inline]
362#[doc(alias = "SDL_GetError")]
363pub fn init() -> Result<Sdl, Error> {
364    Sdl::new()
365}
366
367pub fn get_error() -> Error {
368    unsafe {
369        let err = sys::error::SDL_GetError();
370        Error(CStr::from_ptr(err as *const _).to_str().unwrap().to_owned())
371    }
372}
373
374#[doc(alias = "SDL_SetError")]
375pub fn set_error(err: &str) -> Result<(), NulError> {
376    let c_string = CString::new(err)?;
377    unsafe {
378        sys::error::SDL_SetError(
379            c"%s".as_ptr() as *const c_char,
380            c_string.as_ptr() as *const c_char,
381        );
382    }
383    Ok(())
384}
385
386#[doc(alias = "SDL_ClearError")]
394pub fn clear_error() {
395    unsafe {
396        sys::error::SDL_ClearError();
397    }
398}