use libc::c_char;
use std::cell::Cell;
use std::error;
use std::ffi::{CStr, CString, NulError};
use std::fmt;
use std::marker::PhantomData;
use std::mem;
use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
use sys::init::{
SDL_INIT_AUDIO, SDL_INIT_CAMERA, SDL_INIT_EVENTS, SDL_INIT_GAMEPAD, SDL_INIT_HAPTIC,
SDL_INIT_JOYSTICK, SDL_INIT_SENSOR, SDL_INIT_VIDEO,
};
use crate::sys;
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct Error(pub(crate) String);
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(&self.0)
}
}
impl error::Error for Error {
fn description(&self) -> &str {
&self.0
}
}
impl Error {
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
}
static IS_MAIN_THREAD_DECLARED: AtomicBool = AtomicBool::new(false);
static SDL_COUNT: AtomicU32 = AtomicU32::new(0);
thread_local! {
static IS_MAIN_THREAD: Cell<bool> = const { Cell::new(false) };
}
#[derive(Clone)]
pub struct Sdl {
sdldrop: SdlDrop,
}
impl Sdl {
#[inline]
#[doc(alias = "SDL_Init")]
fn new() -> Result<Sdl, Error> {
let was_main_thread_declared = IS_MAIN_THREAD_DECLARED.swap(true, Ordering::SeqCst);
IS_MAIN_THREAD.with(|is_main_thread| {
if was_main_thread_declared {
if !is_main_thread.get() {
if !(cfg!(test) || cfg!(feature = "test-mode")) {
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()));
}
}
} else {
is_main_thread.set(true);
}
Ok(())
})?;
if SDL_COUNT.fetch_add(1, Ordering::Relaxed) == 0 {
let result;
unsafe {
result = sys::init::SDL_Init(sys::init::SDL_InitFlags::default());
}
if !result {
SDL_COUNT.store(0, Ordering::Relaxed);
return Err(get_error());
}
}
Ok(Sdl {
sdldrop: SdlDrop {
marker: PhantomData,
},
})
}
#[inline]
pub fn audio(&self) -> Result<AudioSubsystem, Error> {
AudioSubsystem::new(self)
}
#[inline]
pub fn event(&self) -> Result<EventSubsystem, Error> {
EventSubsystem::new(self)
}
#[inline]
pub fn joystick(&self) -> Result<JoystickSubsystem, Error> {
JoystickSubsystem::new(self)
}
#[inline]
pub fn haptic(&self) -> Result<HapticSubsystem, Error> {
HapticSubsystem::new(self)
}
#[inline]
pub fn gamepad(&self) -> Result<GamepadSubsystem, Error> {
GamepadSubsystem::new(self)
}
#[inline]
pub fn sensor(&self) -> Result<SensorSubsystem, Error> {
SensorSubsystem::new(self)
}
#[inline]
pub fn video(&self) -> Result<VideoSubsystem, Error> {
VideoSubsystem::new(self)
}
#[inline]
pub fn camera(&self) -> Result<CameraSubsystem, Error> {
CameraSubsystem::new(self)
}
#[inline]
pub fn event_pump(&self) -> Result<EventPump, Error> {
EventPump::new(self)
}
#[inline]
#[doc(hidden)]
pub fn sdldrop(&self) -> SdlDrop {
self.sdldrop.clone()
}
}
#[doc(hidden)]
#[derive(Debug)]
pub struct SdlDrop {
marker: PhantomData<*mut ()>,
}
impl SdlDrop {
unsafe fn new() -> Self {
Self {
marker: PhantomData,
}
}
}
impl Clone for SdlDrop {
fn clone(&self) -> SdlDrop {
let prev_count = SDL_COUNT.fetch_add(1, Ordering::Relaxed);
assert!(prev_count > 0);
SdlDrop {
marker: PhantomData,
}
}
}
impl Drop for SdlDrop {
#[inline]
#[doc(alias = "SDL_Quit")]
fn drop(&mut self) {
let prev_count = SDL_COUNT.fetch_sub(1, Ordering::Relaxed);
assert!(prev_count > 0);
if prev_count == 1 {
unsafe {
sys::init::SDL_Quit();
}
IS_MAIN_THREAD_DECLARED.store(false, Ordering::SeqCst);
}
}
}
macro_rules! subsystem {
($name:ident, $flag:expr, $counter:ident, nosync) => {
static $counter: AtomicU32 = AtomicU32::new(0);
#[derive(Debug)]
pub struct $name {
// Per subsystem all instances together keep one [`SdlDrop`].
marker: PhantomData<*mut ()>,
}
impl $name {
#[inline]
#[doc(alias = "SDL_InitSubSystem")]
fn new(sdl: &Sdl) -> Result<Self, Error> {
if $counter.fetch_add(1, Ordering::Relaxed) == 0 {
let result;
unsafe {
result = sys::init::SDL_InitSubSystem($flag);
}
if !result {
$counter.store(0, Ordering::Relaxed);
return Err(get_error());
}
mem::forget(sdl.sdldrop.clone());
}
Ok(Self {
marker: PhantomData,
})
}
#[doc = concat!("Create a [`", stringify!($name), "`] out of thin air.")]
#[doc = ""]
#[doc = concat!("This is probably not what you are looking for. To initialize the subsystem use [`", stringify!($name), "::new`].")]
#[doc = ""]
#[doc = "# Safety"]
#[doc = ""]
#[doc = concat!("For each time this is called, previously a [`", stringify!($name), "`] must have been passed to [`mem::forget`].")]
#[allow(dead_code)]
pub(crate) unsafe fn new_unchecked() -> Self {
Self {
marker: PhantomData,
}
}
}
impl Clone for $name {
fn clone(&self) -> Self {
let prev_count = $counter.fetch_add(1, Ordering::Relaxed);
assert!(prev_count > 0);
Self {
marker: PhantomData,
}
}
}
impl Drop for $name {
#[inline]
#[doc(alias = "SDL_QuitSubSystem")]
fn drop(&mut self) {
let prev_count = $counter.fetch_sub(1, Ordering::Relaxed);
assert!(prev_count > 0);
if prev_count == 1 {
unsafe {
sys::init::SDL_QuitSubSystem($flag);
let _ = SdlDrop::new();
}
}
}
}
};
($name:ident, $flag:expr, $counter:ident, sync) => {
subsystem!($name, $flag, $counter, nosync);
unsafe impl Sync for $name {}
};
}
subsystem!(AudioSubsystem, SDL_INIT_AUDIO, AUDIO_COUNT, nosync);
subsystem!(VideoSubsystem, SDL_INIT_VIDEO, VIDEO_COUNT, nosync);
subsystem!(JoystickSubsystem, SDL_INIT_JOYSTICK, JOYSTICK_COUNT, nosync);
subsystem!(HapticSubsystem, SDL_INIT_HAPTIC, HAPTIC_COUNT, nosync);
subsystem!(GamepadSubsystem, SDL_INIT_GAMEPAD, GAMEPAD_COUNT, nosync);
subsystem!(EventSubsystem, SDL_INIT_EVENTS, EVENT_COUNT, sync);
subsystem!(SensorSubsystem, SDL_INIT_SENSOR, SENSOR_COUNT, nosync);
subsystem!(CameraSubsystem, SDL_INIT_CAMERA, CAMERA_COUNT, nosync);
static IS_EVENT_PUMP_ALIVE: AtomicBool = AtomicBool::new(false);
pub struct EventPump {
_event_subsystem: EventSubsystem,
}
impl EventPump {
#[inline]
#[doc(alias = "SDL_InitSubSystem")]
fn new(sdl: &Sdl) -> Result<EventPump, Error> {
if IS_EVENT_PUMP_ALIVE.load(Ordering::Relaxed) {
Err(Error("an `EventPump` instance is already alive - there can only be one `EventPump` in use at a time.".to_owned()))
} else {
let _event_subsystem = sdl.event()?;
IS_EVENT_PUMP_ALIVE.store(true, Ordering::Relaxed);
Ok(EventPump { _event_subsystem })
}
}
}
impl Drop for EventPump {
#[inline]
#[doc(alias = "SDL_QuitSubSystem")]
fn drop(&mut self) {
assert!(IS_EVENT_PUMP_ALIVE.load(Ordering::Relaxed));
IS_EVENT_PUMP_ALIVE.store(false, Ordering::Relaxed);
}
}
#[inline]
#[doc(alias = "SDL_GetPlatform")]
pub fn get_platform() -> &'static str {
unsafe {
CStr::from_ptr(sys::platform::SDL_GetPlatform())
.to_str()
.unwrap()
}
}
#[inline]
pub fn init() -> Result<Sdl, Error> {
Sdl::new()
}
#[doc(alias = "SDL_GetError")]
pub fn get_error() -> Error {
unsafe {
let err = sys::error::SDL_GetError();
Error(CStr::from_ptr(err as *const _).to_str().unwrap().to_owned())
}
}
#[doc(alias = "SDL_SetError")]
pub fn set_error(err: &str) -> Result<(), NulError> {
let c_string = CString::new(err)?;
unsafe {
sys::error::SDL_SetError(
c"%s".as_ptr() as *const c_char,
c_string.as_ptr() as *const c_char,
);
}
Ok(())
}
#[doc(alias = "SDL_ClearError")]
pub fn clear_error() {
sys::error::SDL_ClearError();
}