1
1
use libc:: c_char;
2
+ use std:: cell:: Cell ;
2
3
use std:: error;
3
4
use std:: ffi:: { CStr , CString , NulError } ;
4
5
use std:: fmt;
5
6
use std:: mem:: transmute;
6
- use std:: rc:: Rc ;
7
+ use std:: os:: raw:: c_void;
8
+ use std:: sync:: atomic:: { AtomicBool , AtomicU32 , Ordering } ;
7
9
8
10
use crate :: sys;
9
11
@@ -45,10 +47,17 @@ impl error::Error for Error {
45
47
}
46
48
}
47
49
48
- use std:: sync:: atomic:: AtomicBool ;
49
- /// Only one Sdl context can be alive at a time.
50
- /// Set to false by default (not alive).
51
- static IS_SDL_CONTEXT_ALIVE : AtomicBool = AtomicBool :: new ( false ) ;
50
+ /// True if the main thread has been declared. The main thread is declared when
51
+ /// SDL is first initialized.
52
+ static IS_MAIN_THREAD_DECLARED : AtomicBool = AtomicBool :: new ( false ) ;
53
+
54
+ /// Number of active `SdlDrop` objects keeping SDL alive.
55
+ static SDL_COUNT : AtomicU32 = AtomicU32 :: new ( 0 ) ;
56
+
57
+ thread_local ! {
58
+ /// True if the current thread is the main thread.
59
+ static IS_MAIN_THREAD : Cell <bool > = Cell :: new( false ) ;
60
+ }
52
61
53
62
/// The SDL context type. Initialize with `sdl2::init()`.
54
63
///
@@ -64,31 +73,42 @@ static IS_SDL_CONTEXT_ALIVE: AtomicBool = AtomicBool::new(false);
64
73
/// the main thread.
65
74
#[ derive( Clone ) ]
66
75
pub struct Sdl {
67
- sdldrop : Rc < SdlDrop > ,
76
+ sdldrop : SdlDrop ,
68
77
}
69
78
70
79
impl Sdl {
71
80
#[ inline]
72
81
#[ doc( alias = "SDL_Init" ) ]
73
82
fn new ( ) -> Result < Sdl , String > {
74
- unsafe {
75
- use std:: sync:: atomic:: Ordering ;
76
-
77
- // Atomically switch the `IS_SDL_CONTEXT_ALIVE` global to true
78
- let was_alive = IS_SDL_CONTEXT_ALIVE . swap ( true , Ordering :: Relaxed ) ;
79
-
80
- if was_alive {
81
- Err ( "Cannot initialize `Sdl` more than once at a time." . to_owned ( ) )
82
- } else if sys:: SDL_Init ( 0 ) == 0 {
83
- // Initialize SDL without any explicit subsystems (flags = 0).
84
- Ok ( Sdl {
85
- sdldrop : Rc :: new ( SdlDrop ) ,
86
- } )
83
+ // Check if we can safely initialize SDL on this thread.
84
+ let was_main_thread_declared = IS_MAIN_THREAD_DECLARED . swap ( true , Ordering :: SeqCst ) ;
85
+
86
+ IS_MAIN_THREAD . with ( |is_main_thread| {
87
+ if was_main_thread_declared {
88
+ if !is_main_thread. get ( ) {
89
+ return Err ( "Cannot initialize `Sdl` from more than once thread." . to_owned ( ) ) ;
90
+ }
87
91
} else {
88
- IS_SDL_CONTEXT_ALIVE . swap ( false , Ordering :: Relaxed ) ;
89
- Err ( get_error ( ) )
92
+ is_main_thread. set ( true ) ;
93
+ }
94
+ Ok ( ( ) )
95
+ } ) ?;
96
+
97
+ // Initialize SDL.
98
+ if SDL_COUNT . fetch_add ( 1 , Ordering :: Relaxed ) == 0 {
99
+ let result;
100
+
101
+ unsafe {
102
+ result = sys:: SDL_Init ( 0 ) ;
103
+ }
104
+
105
+ if result != 0 {
106
+ SDL_COUNT . store ( 0 , Ordering :: Relaxed ) ;
107
+ return Err ( get_error ( ) ) ;
90
108
}
91
109
}
110
+
111
+ Ok ( Sdl { sdldrop : SdlDrop { _anticonstructor : std:: ptr:: null_mut ( ) } } )
92
112
}
93
113
94
114
/// Initializes the audio subsystem.
@@ -151,27 +171,38 @@ impl Sdl {
151
171
152
172
#[ inline]
153
173
#[ doc( hidden) ]
154
- pub fn sdldrop ( & self ) -> Rc < SdlDrop > {
174
+ pub fn sdldrop ( & self ) -> SdlDrop {
155
175
self . sdldrop . clone ( )
156
176
}
157
177
}
158
178
159
- /// When SDL is no longer in use (the refcount in an `Rc<SdlDrop>` reaches 0) , the library is quit.
179
+ /// When SDL is no longer in use, the library is quit.
160
180
#[ doc( hidden) ]
161
181
#[ derive( Debug ) ]
162
- pub struct SdlDrop ;
182
+ pub struct SdlDrop {
183
+ // Make it impossible to construct `SdlDrop` without access to this member,
184
+ // and opt out of Send and Sync.
185
+ _anticonstructor : * mut c_void ,
186
+ }
187
+
188
+ impl Clone for SdlDrop {
189
+ fn clone ( & self ) -> SdlDrop {
190
+ let prev_count = SDL_COUNT . fetch_add ( 1 , Ordering :: Relaxed ) ;
191
+ assert ! ( prev_count > 0 ) ;
192
+ SdlDrop { _anticonstructor : std:: ptr:: null_mut ( ) }
193
+ }
194
+ }
163
195
164
196
impl Drop for SdlDrop {
165
197
#[ inline]
166
198
#[ doc( alias = "SDL_Quit" ) ]
167
199
fn drop ( & mut self ) {
168
- use std:: sync:: atomic:: Ordering ;
169
-
170
- let was_alive = IS_SDL_CONTEXT_ALIVE . swap ( false , Ordering :: Relaxed ) ;
171
- assert ! ( was_alive) ;
172
-
173
- unsafe {
174
- sys:: SDL_Quit ( ) ;
200
+ let prev_count = SDL_COUNT . fetch_sub ( 1 , Ordering :: Relaxed ) ;
201
+ assert ! ( prev_count > 0 ) ;
202
+ if prev_count == 1 {
203
+ unsafe {
204
+ sys:: SDL_Quit ( ) ;
205
+ }
175
206
}
176
207
}
177
208
}
@@ -182,64 +213,42 @@ impl Drop for SdlDrop {
182
213
// the event queue. These subsystems implement `Sync`.
183
214
184
215
macro_rules! subsystem {
185
- ( $name: ident, $flag: expr) => {
186
- impl $name {
187
- #[ inline]
188
- #[ doc( alias = "SDL_InitSubSystem" ) ]
189
- fn new( sdl: & Sdl ) -> Result <$name, String > {
190
- let result = unsafe { sys:: SDL_InitSubSystem ( $flag) } ;
191
-
192
- if result == 0 {
193
- Ok ( $name {
194
- _subsystem_drop: Rc :: new( SubsystemDrop {
195
- _sdldrop: sdl. sdldrop. clone( ) ,
196
- flag: $flag,
197
- } ) ,
198
- } )
199
- } else {
200
- Err ( get_error( ) )
201
- }
202
- }
203
- }
204
- } ;
205
- ( $name: ident, $flag: expr, nosync) => {
216
+ ( $name: ident, $flag: expr, $counter: ident, nosync) => {
217
+ static $counter: AtomicU32 = AtomicU32 :: new( 0 ) ;
218
+
206
219
#[ derive( Debug , Clone ) ]
207
220
pub struct $name {
208
221
/// Subsystems cannot be moved or (usually) used on non-main threads.
209
222
/// Luckily, Rc restricts use to the main thread.
210
- _subsystem_drop: Rc < SubsystemDrop > ,
223
+ _subsystem_drop: SubsystemDrop ,
211
224
}
212
225
213
226
impl $name {
214
- /// Obtain an SDL context.
215
227
#[ inline]
216
- pub fn sdl( & self ) -> Sdl {
217
- Sdl {
218
- sdldrop: self . _subsystem_drop. _sdldrop. clone( ) ,
219
- }
220
- }
221
- }
228
+ #[ doc( alias = "SDL_InitSubSystem" ) ]
229
+ fn new( sdl: & Sdl ) -> Result <$name, String > {
230
+ if $counter. fetch_add( 1 , Ordering :: Relaxed ) == 0 {
231
+ let result;
222
232
223
- subsystem!( $name, $flag) ;
224
- } ;
225
- ( $name: ident, $flag: expr, sync) => {
226
- pub struct $name {
227
- /// Subsystems cannot be moved or (usually) used on non-main threads.
228
- /// Luckily, Rc restricts use to the main thread.
229
- _subsystem_drop: Rc <SubsystemDrop >,
230
- }
231
- unsafe impl Sync for $name { }
233
+ unsafe {
234
+ result = sys:: SDL_InitSubSystem ( $flag) ;
235
+ }
232
236
233
- impl std:: clone:: Clone for $name {
234
- #[ inline]
235
- fn clone( & self ) -> $name {
236
- $name {
237
- _subsystem_drop: self . _subsystem_drop. clone( ) ,
237
+ if result != 0 {
238
+ $counter. store( 0 , Ordering :: Relaxed ) ;
239
+ return Err ( get_error( ) ) ;
240
+ }
238
241
}
242
+
243
+ Ok ( $name {
244
+ _subsystem_drop: SubsystemDrop {
245
+ _sdldrop: sdl. sdldrop. clone( ) ,
246
+ counter: & $counter,
247
+ flag: $flag,
248
+ } ,
249
+ } )
239
250
}
240
- }
241
251
242
- impl $name {
243
252
/// Obtain an SDL context.
244
253
#[ inline]
245
254
pub fn sdl( & self ) -> Sdl {
@@ -248,49 +257,69 @@ macro_rules! subsystem {
248
257
}
249
258
}
250
259
}
251
-
252
- subsystem!( $name, $flag) ;
260
+ } ;
261
+ ( $name: ident, $flag: expr, $counter: ident, sync) => {
262
+ subsystem!( $name, $flag, $counter, nosync) ;
263
+ unsafe impl Sync for $name { }
253
264
} ;
254
265
}
255
266
256
267
/// When a subsystem is no longer in use (the refcount in an `Rc<SubsystemDrop>` reaches 0),
257
268
/// the subsystem is quit.
258
- #[ derive( Debug , Clone ) ]
269
+ #[ derive( Debug ) ]
259
270
struct SubsystemDrop {
260
- _sdldrop : Rc < SdlDrop > ,
271
+ _sdldrop : SdlDrop ,
272
+ counter : & ' static AtomicU32 ,
261
273
flag : u32 ,
262
274
}
263
275
276
+ impl Clone for SubsystemDrop {
277
+ fn clone ( & self ) -> SubsystemDrop {
278
+ let prev_count = self . counter . fetch_add ( 1 , Ordering :: Relaxed ) ;
279
+ assert ! ( prev_count > 0 ) ;
280
+ SubsystemDrop {
281
+ _sdldrop : self . _sdldrop . clone ( ) ,
282
+ counter : self . counter ,
283
+ flag : self . flag ,
284
+ }
285
+ }
286
+ }
287
+
264
288
impl Drop for SubsystemDrop {
265
289
#[ inline]
266
290
#[ doc( alias = "SDL_QuitSubSystem" ) ]
267
291
fn drop ( & mut self ) {
268
- unsafe {
269
- sys:: SDL_QuitSubSystem ( self . flag ) ;
292
+ let prev_count = self . counter . fetch_sub ( 1 , Ordering :: Relaxed ) ;
293
+ assert ! ( prev_count > 0 ) ;
294
+ if prev_count == 1 {
295
+ unsafe {
296
+ sys:: SDL_QuitSubSystem ( self . flag ) ;
297
+ }
270
298
}
271
299
}
272
300
}
273
301
274
- subsystem ! ( AudioSubsystem , sys:: SDL_INIT_AUDIO , nosync) ;
302
+ subsystem ! ( AudioSubsystem , sys:: SDL_INIT_AUDIO , AUDIO_COUNT , nosync) ;
275
303
subsystem ! (
276
304
GameControllerSubsystem ,
277
305
sys:: SDL_INIT_GAMECONTROLLER ,
306
+ GAMECONTROLLER_COUNT ,
278
307
nosync
279
308
) ;
280
- subsystem ! ( HapticSubsystem , sys:: SDL_INIT_HAPTIC , nosync) ;
281
- subsystem ! ( JoystickSubsystem , sys:: SDL_INIT_JOYSTICK , nosync) ;
282
- subsystem ! ( VideoSubsystem , sys:: SDL_INIT_VIDEO , nosync) ;
309
+ subsystem ! ( HapticSubsystem , sys:: SDL_INIT_HAPTIC , HAPTIC_COUNT , nosync) ;
310
+ subsystem ! ( JoystickSubsystem , sys:: SDL_INIT_JOYSTICK , JOYSTICK_COUNT , nosync) ;
311
+ subsystem ! ( VideoSubsystem , sys:: SDL_INIT_VIDEO , VIDEO_COUNT , nosync) ;
283
312
// Timers can be added on other threads.
284
- subsystem ! ( TimerSubsystem , sys:: SDL_INIT_TIMER , sync) ;
313
+ subsystem ! ( TimerSubsystem , sys:: SDL_INIT_TIMER , TIMER_COUNT , sync) ;
285
314
// The event queue can be read from other threads.
286
- subsystem ! ( EventSubsystem , sys:: SDL_INIT_EVENTS , sync) ;
287
- subsystem ! ( SensorSubsystem , sys:: SDL_INIT_SENSOR , sync) ;
315
+ subsystem ! ( EventSubsystem , sys:: SDL_INIT_EVENTS , EVENTS_COUNT , sync) ;
316
+ subsystem ! ( SensorSubsystem , sys:: SDL_INIT_SENSOR , SENSOR_COUNT , sync) ;
288
317
289
- static mut IS_EVENT_PUMP_ALIVE : bool = false ;
318
+ static IS_EVENT_PUMP_ALIVE : AtomicBool = AtomicBool :: new ( false ) ;
290
319
291
320
/// A thread-safe type that encapsulates SDL event-pumping functions.
292
321
pub struct EventPump {
293
- _sdldrop : Rc < SdlDrop > ,
322
+ _event_subsystem : EventSubsystem ,
294
323
}
295
324
296
325
impl EventPump {
@@ -299,24 +328,12 @@ impl EventPump {
299
328
#[ doc( alias = "SDL_InitSubSystem" ) ]
300
329
fn new ( sdl : & Sdl ) -> Result < EventPump , String > {
301
330
// Called on the main SDL thread.
302
-
303
- unsafe {
304
- if IS_EVENT_PUMP_ALIVE {
305
- Err ( "an `EventPump` instance is already alive - there can only be one `EventPump` in use at a time." . to_owned ( ) )
306
- } else {
307
- // Initialize the events subsystem, just in case none of the other subsystems have done it yet.
308
- let result = sys:: SDL_InitSubSystem ( sys:: SDL_INIT_EVENTS ) ;
309
-
310
- if result == 0 {
311
- IS_EVENT_PUMP_ALIVE = true ;
312
-
313
- Ok ( EventPump {
314
- _sdldrop : sdl. sdldrop . clone ( ) ,
315
- } )
316
- } else {
317
- Err ( get_error ( ) )
318
- }
319
- }
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 } )
320
337
}
321
338
}
322
339
}
@@ -326,12 +343,8 @@ impl Drop for EventPump {
326
343
#[ doc( alias = "SDL_QuitSubSystem" ) ]
327
344
fn drop ( & mut self ) {
328
345
// Called on the main SDL thread.
329
-
330
- unsafe {
331
- assert ! ( IS_EVENT_PUMP_ALIVE ) ;
332
- sys:: SDL_QuitSubSystem ( sys:: SDL_INIT_EVENTS ) ;
333
- IS_EVENT_PUMP_ALIVE = false ;
334
- }
346
+ assert ! ( IS_EVENT_PUMP_ALIVE . load( Ordering :: Relaxed ) ) ;
347
+ IS_EVENT_PUMP_ALIVE . store ( false , Ordering :: Relaxed ) ;
335
348
}
336
349
}
337
350
0 commit comments