use libc::c_char;
use std::cell::Cell;
use std::error;
use std::ffi::{CStr, CString, NulError};
use std::fmt;
use std::mem::transmute;
use std::os::raw::c_void;
use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
use crate::sys;
#[repr(i32)]
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
pub enum Error {
NoMemError = sys::SDL_errorcode::SDL_ENOMEM as i32,
ReadError = sys::SDL_errorcode::SDL_EFREAD as i32,
WriteError = sys::SDL_errorcode::SDL_EFWRITE as i32,
SeekError = sys::SDL_errorcode::SDL_EFSEEK as i32,
UnsupportedError = sys::SDL_errorcode::SDL_UNSUPPORTED as i32,
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use self::Error::*;
match *self {
NoMemError => write!(f, "Out of memory"),
ReadError => write!(f, "Error reading from datastream"),
WriteError => write!(f, "Error writing to datastream"),
SeekError => write!(f, "Error seeking in datastream"),
UnsupportedError => write!(f, "Unknown SDL error"),
}
}
}
impl error::Error for Error {
fn description(&self) -> &str {
use self::Error::*;
match *self {
NoMemError => "out of memory",
ReadError => "error reading from datastream",
WriteError => "error writing to datastream",
SeekError => "error seeking in datastream",
UnsupportedError => "unknown SDL error",
}
}
}
static IS_MAIN_THREAD_DECLARED: AtomicBool = AtomicBool::new(false);
static SDL_COUNT: AtomicU32 = AtomicU32::new(0);
thread_local! {
static IS_MAIN_THREAD: Cell<bool> = Cell::new(false);
}
#[derive(Clone)]
pub struct Sdl {
sdldrop: SdlDrop,
}
impl Sdl {
#[inline]
#[doc(alias = "SDL_Init")]
fn new() -> Result<Sdl, String> {
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() {
return Err("Cannot initialize `Sdl` from more than one thread.".to_owned());
}
} else {
is_main_thread.set(true);
}
Ok(())
})?;
if SDL_COUNT.fetch_add(1, Ordering::Relaxed) == 0 {
let result;
unsafe {
result = sys::SDL_Init(0);
}
if result != 0 {
SDL_COUNT.store(0, Ordering::Relaxed);
return Err(get_error());
}
}
Ok(Sdl {
sdldrop: SdlDrop {
_anticonstructor: std::ptr::null_mut(),
},
})
}
#[inline]
pub fn audio(&self) -> Result<AudioSubsystem, String> {
AudioSubsystem::new(self)
}
#[inline]
pub fn event(&self) -> Result<EventSubsystem, String> {
EventSubsystem::new(self)
}
#[inline]
pub fn joystick(&self) -> Result<JoystickSubsystem, String> {
JoystickSubsystem::new(self)
}
#[inline]
pub fn haptic(&self) -> Result<HapticSubsystem, String> {
HapticSubsystem::new(self)
}
#[inline]
pub fn game_controller(&self) -> Result<GameControllerSubsystem, String> {
GameControllerSubsystem::new(self)
}
#[inline]
pub fn sensor(&self) -> Result<SensorSubsystem, String> {
SensorSubsystem::new(self)
}
#[inline]
pub fn timer(&self) -> Result<TimerSubsystem, String> {
TimerSubsystem::new(self)
}
#[inline]
pub fn video(&self) -> Result<VideoSubsystem, String> {
VideoSubsystem::new(self)
}
#[inline]
pub fn event_pump(&self) -> Result<EventPump, String> {
EventPump::new(self)
}
#[inline]
#[doc(hidden)]
pub fn sdldrop(&self) -> SdlDrop {
self.sdldrop.clone()
}
}
#[doc(hidden)]
#[derive(Debug)]
pub struct SdlDrop {
_anticonstructor: *mut c_void,
}
impl Clone for SdlDrop {
fn clone(&self) -> SdlDrop {
let prev_count = SDL_COUNT.fetch_add(1, Ordering::Relaxed);
assert!(prev_count > 0);
SdlDrop {
_anticonstructor: std::ptr::null_mut(),
}
}
}
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::SDL_Quit();
}
IS_MAIN_THREAD_DECLARED.swap(false, Ordering::SeqCst);
}
}
}
macro_rules! subsystem {
($name:ident, $flag:expr, $counter:ident, nosync) => {
static $counter: AtomicU32 = AtomicU32::new(0);
#[derive(Debug, Clone)]
pub struct $name {
_subsystem_drop: SubsystemDrop,
}
impl $name {
#[inline]
#[doc(alias = "SDL_InitSubSystem")]
fn new(sdl: &Sdl) -> Result<$name, String> {
if $counter.fetch_add(1, Ordering::Relaxed) == 0 {
let result;
unsafe {
result = sys::SDL_InitSubSystem($flag);
}
if result != 0 {
$counter.store(0, Ordering::Relaxed);
return Err(get_error());
}
}
Ok($name {
_subsystem_drop: SubsystemDrop {
_sdldrop: sdl.sdldrop.clone(),
counter: &$counter,
flag: $flag,
},
})
}
#[inline]
pub fn sdl(&self) -> Sdl {
Sdl {
sdldrop: self._subsystem_drop._sdldrop.clone(),
}
}
}
};
($name:ident, $flag:expr, $counter:ident, sync) => {
subsystem!($name, $flag, $counter, nosync);
unsafe impl Sync for $name {}
};
}
#[derive(Debug)]
struct SubsystemDrop {
_sdldrop: SdlDrop,
counter: &'static AtomicU32,
flag: u32,
}
impl Clone for SubsystemDrop {
fn clone(&self) -> SubsystemDrop {
let prev_count = self.counter.fetch_add(1, Ordering::Relaxed);
assert!(prev_count > 0);
SubsystemDrop {
_sdldrop: self._sdldrop.clone(),
counter: self.counter,
flag: self.flag,
}
}
}
impl Drop for SubsystemDrop {
#[inline]
#[doc(alias = "SDL_QuitSubSystem")]
fn drop(&mut self) {
let prev_count = self.counter.fetch_sub(1, Ordering::Relaxed);
assert!(prev_count > 0);
if prev_count == 1 {
unsafe {
sys::SDL_QuitSubSystem(self.flag);
}
}
}
}
subsystem!(AudioSubsystem, sys::SDL_INIT_AUDIO, AUDIO_COUNT, nosync);
subsystem!(
GameControllerSubsystem,
sys::SDL_INIT_GAMECONTROLLER,
GAMECONTROLLER_COUNT,
nosync
);
subsystem!(HapticSubsystem, sys::SDL_INIT_HAPTIC, HAPTIC_COUNT, nosync);
subsystem!(
JoystickSubsystem,
sys::SDL_INIT_JOYSTICK,
JOYSTICK_COUNT,
nosync
);
subsystem!(VideoSubsystem, sys::SDL_INIT_VIDEO, VIDEO_COUNT, nosync);
subsystem!(TimerSubsystem, sys::SDL_INIT_TIMER, TIMER_COUNT, sync);
subsystem!(EventSubsystem, sys::SDL_INIT_EVENTS, EVENTS_COUNT, sync);
subsystem!(SensorSubsystem, sys::SDL_INIT_SENSOR, SENSOR_COUNT, sync);
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, String> {
if IS_EVENT_PUMP_ALIVE.load(Ordering::Relaxed) {
Err("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::SDL_GetPlatform()).to_str().unwrap() }
}
#[inline]
#[doc(alias = "SDL_GetError")]
pub fn init() -> Result<Sdl, String> {
Sdl::new()
}
pub fn get_error() -> String {
unsafe {
let err = sys::SDL_GetError();
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::SDL_SetError(
b"%s\0".as_ptr() as *const c_char,
c_string.as_ptr() as *const c_char,
);
}
Ok(())
}
#[doc(alias = "SDL_Error")]
pub fn set_error_from_code(err: Error) {
unsafe {
sys::SDL_Error(transmute(err));
}
}
#[doc(alias = "SDL_ClearError")]
pub fn clear_error() {
unsafe {
sys::SDL_ClearError();
}
}