sdl3/
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;
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
37/// True if the main thread has been declared. The main thread is declared when
38/// SDL is first initialized.
39static IS_MAIN_THREAD_DECLARED: AtomicBool = AtomicBool::new(false);
40
41/// Number of active `SdlDrop` objects keeping SDL alive.
42static SDL_COUNT: AtomicU32 = AtomicU32::new(0);
43
44thread_local! {
45    /// True if the current thread is the main thread.
46    static IS_MAIN_THREAD: Cell<bool> = const { Cell::new(false) };
47}
48
49/// The SDL context type. Initialize with `sdl3::init()`.
50///
51/// From a thread-safety perspective, `Sdl` represents the main thread.
52/// As such, `Sdl` is a useful type for ensuring that SDL types that can only
53/// be used on the main thread are initialized that way.
54///
55/// For instance, `SDL_PumpEvents()` is not thread safe, and may only be
56/// called on the main thread.
57/// All functionality that calls `SDL_PumpEvents()` is thus put into an
58/// `EventPump` type, which can only be obtained through `Sdl`.
59/// This guarantees that the only way to call event-pumping functions is on
60/// the main thread.
61#[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        // Check if we can safely initialize SDL on this thread.
71        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                    // Since 'cargo test' runs its tests in a separate thread, we must disable
77                    // this safety check during testing.
78                    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        // Initialize SDL.
89        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    /// Initializes the audio subsystem.
110    #[inline]
111    pub fn audio(&self) -> Result<AudioSubsystem, Error> {
112        AudioSubsystem::new(self)
113    }
114
115    /// Initializes the event subsystem.
116    #[inline]
117    pub fn event(&self) -> Result<EventSubsystem, Error> {
118        EventSubsystem::new(self)
119    }
120
121    /// Initializes the joystick subsystem.
122    #[inline]
123    pub fn joystick(&self) -> Result<JoystickSubsystem, Error> {
124        JoystickSubsystem::new(self)
125    }
126
127    /// Initializes the haptic subsystem.
128    #[inline]
129    pub fn haptic(&self) -> Result<HapticSubsystem, Error> {
130        HapticSubsystem::new(self)
131    }
132
133    /// Initializes the gamepad subsystem.
134    #[inline]
135    pub fn gamepad(&self) -> Result<GamepadSubsystem, Error> {
136        GamepadSubsystem::new(self)
137    }
138
139    /// Initializes the game controller subsystem.
140    #[inline]
141    pub fn sensor(&self) -> Result<SensorSubsystem, Error> {
142        SensorSubsystem::new(self)
143    }
144
145    /// Initializes the video subsystem.
146    #[inline]
147    pub fn video(&self) -> Result<VideoSubsystem, Error> {
148        VideoSubsystem::new(self)
149    }
150
151    /// Obtains the SDL event pump.
152    ///
153    /// At most one `EventPump` is allowed to be alive during the program's execution.
154    /// If this function is called while an `EventPump` instance is alive, the function will return
155    /// an error.
156    #[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/// When SDL is no longer in use, the library is quit.
169#[doc(hidden)]
170#[derive(Debug)]
171pub struct SdlDrop {
172    // Make it impossible to construct `SdlDrop` without access to this member,
173    // and opt out of Send and Sync.
174    marker: PhantomData<*mut ()>,
175}
176
177impl SdlDrop {
178    /// Create an [`SdlDrop`] out of thin air.
179    ///
180    /// This is probably not what you are looking for. To initialize SDL use [`Sdl::new`].
181    ///
182    /// # Safety
183    ///
184    /// For each time this is called, previously an [`SdlDrop`] must have been passed to
185    /// [`mem::forget`].
186    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
218// No subsystem can implement `Send` because the destructor, `SDL_QuitSubSystem`,
219// utilizes non-atomic reference counting and should thus be called on a single thread.
220// Some subsystems have functions designed to be thread-safe, such as adding a timer or accessing
221// the event queue. These subsystems implement `Sync`.
222
223macro_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            // Per subsystem all instances together keep one [`SdlDrop`].
230            // Subsystems cannot be moved or (usually) used on non-main threads.
231            /// This field makes sure [`Send`] and [`Sync`] are not implemented by default.
232            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                    // The first created subsystem instance "stores" an SdlDrop.
252                    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                        // The last dropped subsystem instance "retrieves" an SdlDrop and drops it.
281                        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);
298// The event queue can be read from other threads.
299subsystem!(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
305/// A thread-safe type that encapsulates SDL event-pumping functions.
306pub struct EventPump {
307    _event_subsystem: EventSubsystem,
308}
309
310impl EventPump {
311    /// Obtains the SDL event pump.
312    #[inline]
313    #[doc(alias = "SDL_InitSubSystem")]
314    fn new(sdl: &Sdl) -> Result<EventPump, Error> {
315        // Called on the main SDL thread.
316        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        // Called on the main SDL thread.
331        assert!(IS_EVENT_PUMP_ALIVE.load(Ordering::Relaxed));
332        IS_EVENT_PUMP_ALIVE.store(false, Ordering::Relaxed);
333    }
334}
335
336/// Get platform name
337#[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/// Initializes the SDL library.
348/// This must be called before using any other SDL function.
349///
350/// # Example
351/// ```no_run
352/// let sdl_context = sdl3::init().unwrap();
353/// let mut event_pump = sdl_context.event_pump().unwrap();
354///
355/// for event in event_pump.poll_iter() {
356///     // ...
357/// }
358///
359/// // SDL_Quit() is called here as `sdl_context` is dropped.
360/// ```
361#[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_Error")]
387// pub fn set_error_from_code(err: Error) {
388//     unsafe {
389//         sys::error::SDL_Error(transmute(err));
390//     }
391// }
392
393#[doc(alias = "SDL_ClearError")]
394pub fn clear_error() {
395    unsafe {
396        sys::error::SDL_ClearError();
397    }
398}