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}