#![no_std]
#![deny(unsafe_op_in_unsafe_fn)]
#![doc = include_str!("../README.md.inc")]
#![cfg_attr(all(feature = "nightly", doc), feature(doc_cfg))] #![cfg_attr(feature = "nightly", feature(try_trait_v2))]
#[cfg(feature = "alloc")]
extern crate alloc;
#[cfg(feature = "std")]
extern crate std;
use core::{
ffi::{c_int, c_void},
ptr,
};
use sdl3_sys::{
init::SDL_AppResult,
log::{SDL_LogCategory, SDL_LogCritical},
};
#[cfg(all(feature = "log-errors", feature = "nightly"))]
use {
core::fmt::Display,
sdl3_sys::{error::SDL_SetError, log::SDL_LogError},
};
#[cfg(feature = "nightly")]
use core::{convert::Infallible, ops::FromResidual};
#[cfg(doc)]
use state::SyncPtr;
pub use sdl3_main_macros::main;
pub use sdl3_main_macros::app_impl;
pub use sdl3_main_macros::app_init;
pub use sdl3_main_macros::app_iterate;
pub use sdl3_main_macros::app_event;
pub use sdl3_main_macros::app_quit;
macro_rules! defer {
($($tt:tt)*) => {
let _defer = $crate::Defer(Some(move || {{ $($tt)* };}));
};
}
struct Defer<F: FnOnce()>(Option<F>);
impl<F: FnOnce()> Drop for Defer<F> {
fn drop(&mut self) {
if let Some(f) = self.0.take() {
f();
}
}
}
pub mod app;
mod main_thread;
pub mod state;
pub use main_thread::{
run_async_on_main_thread, run_sync_on_main_thread, MainThreadData, MainThreadToken,
};
use state::{AppState, BorrowMut, BorrowRef, BorrowVal, ConsumeMut, ConsumeRef, ConsumeVal};
#[doc(hidden)]
pub mod __internal {
pub use ::sdl3_sys;
#[cfg(feature = "alloc")]
use alloc::boxed::Box;
use core::{
ffi::{c_char, c_int},
ptr,
};
use sdl3_sys::main::SDL_RunApp;
#[cfg(feature = "std")]
use std::{
any::Any,
cell::UnsafeCell,
mem::MaybeUninit,
panic::{catch_unwind, resume_unwind, UnwindSafe},
};
#[cfg(feature = "std")]
pub struct Shuttle<T>(UnsafeCell<MaybeUninit<Result<T, Box<dyn Any + Send>>>>);
#[cfg(feature = "std")]
unsafe impl<T> Sync for Shuttle<T> {}
#[cfg(feature = "std")]
impl<T> Shuttle<T> {
#[allow(clippy::new_without_default)]
pub const fn new() -> Self {
Self(UnsafeCell::new(MaybeUninit::uninit()))
}
pub unsafe fn capture(&self, f: impl FnOnce() -> T + UnwindSafe) {
match catch_unwind(f) {
Ok(value) => {
unsafe { self.0.get().write(MaybeUninit::new(Ok(value))) };
}
Err(unwind) => {
unsafe { self.0.get().write(MaybeUninit::new(Err(unwind))) };
}
}
}
pub unsafe fn resume(&self) -> T {
match unsafe { (*self.0.get()).assume_init_read() } {
Ok(value) => value,
Err(unwind) => resume_unwind(unwind),
}
}
}
#[cfg(feature = "std")]
impl Shuttle<()> {
pub unsafe fn capture_and_continue<T>(
&self,
unwind_val: T,
f: impl FnOnce() -> T + UnwindSafe,
) -> T {
match catch_unwind(f) {
Ok(value) => {
unsafe { self.0.get().write(MaybeUninit::new(Ok(()))) };
value
}
Err(unwind) => {
unsafe { self.0.get().write(MaybeUninit::new(Err(unwind))) };
unwind_val
}
}
}
}
#[cfg(feature = "std")]
#[inline(always)] pub unsafe fn run_app(
main_fn: unsafe extern "C" fn(c_int, *mut *mut c_char) -> c_int,
) -> c_int {
#[cfg(windows)]
{
unsafe { SDL_RunApp(0, ptr::null_mut(), Some(main_fn), ptr::null_mut()) }
}
#[cfg(not(windows))]
{
use std::env;
use std::vec::Vec;
let args = env::args_os();
let mut cargs = Vec::with_capacity(args.len());
for arg in args {
let mut carg;
#[cfg(unix)]
{
use std::{borrow::ToOwned, os::unix::ffi::OsStringExt};
carg = arg.to_owned().into_vec();
}
#[cfg(not(unix))]
{
carg = arg.to_string_lossy().into_owned().into_bytes();
}
carg.reserve_exact(1);
carg.push(0);
cargs.push(carg.into_boxed_slice());
}
let mut ptrargs = Vec::with_capacity(cargs.len() + 1);
for carg in cargs.iter_mut() {
ptrargs.push(carg.as_mut_ptr() as *mut c_char);
}
ptrargs.push(ptr::null_mut());
unsafe {
SDL_RunApp(
cargs.len().try_into().expect("too many arguments"),
ptrargs.as_mut_ptr(),
Some(main_fn),
ptr::null_mut(),
)
}
}
}
}
pub trait IntoAppResult: Sized {
fn into_sdl_app_result(self) -> SDL_AppResult;
#[inline(always)]
fn into_app_result(self) -> AppResult {
self.into_sdl_app_result().into()
}
}
impl IntoAppResult for () {
#[inline(always)]
fn into_sdl_app_result(self) -> SDL_AppResult {
SDL_AppResult::CONTINUE
}
#[inline(always)]
fn into_app_result(self) -> AppResult {
AppResult::Continue
}
}
impl IntoAppResult for SDL_AppResult {
#[inline(always)]
fn into_sdl_app_result(self) -> SDL_AppResult {
self
}
#[inline(always)]
fn into_app_result(self) -> AppResult {
self.into()
}
}
impl IntoAppResult for AppResult {
#[inline(always)]
fn into_sdl_app_result(self) -> SDL_AppResult {
self.into()
}
#[inline(always)]
fn into_app_result(self) -> AppResult {
self
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum AppResult {
Continue,
Success,
Failure,
}
impl From<AppResult> for SDL_AppResult {
#[inline(always)]
fn from(value: AppResult) -> Self {
match value {
AppResult::Continue => Self::CONTINUE,
AppResult::Success => Self::SUCCESS,
AppResult::Failure => Self::FAILURE,
}
}
}
impl From<SDL_AppResult> for AppResult {
#[inline]
fn from(value: SDL_AppResult) -> Self {
match value {
SDL_AppResult::CONTINUE => Self::Continue,
SDL_AppResult::SUCCESS => Self::Success,
SDL_AppResult::FAILURE => Self::Failure,
SDL_AppResult(value) => {
#[cold]
#[inline(never)]
fn unknown_app_result_value(result: c_int) -> AppResult {
unsafe {
SDL_LogCritical(
SDL_LogCategory::APPLICATION.into(),
c"Unrecognized app result value: %d".as_ptr(),
result,
)
};
AppResult::Failure
}
unknown_app_result_value(value)
}
}
}
}
#[cfg(all(feature = "nightly", feature = "log-errors"))]
impl<E: Display> FromResidual<Result<Infallible, E>> for AppResult {
fn from_residual(residual: Result<Infallible, E>) -> Self {
let Err(err) = residual;
let err = ::alloc::format!("{err}\0");
unsafe {
SDL_LogError(0, c"%s".as_ptr(), err.as_ptr());
SDL_SetError(c"%s".as_ptr(), err.as_ptr());
};
AppResult::Failure
}
}
#[cfg(all(feature = "nightly", not(feature = "log-errors")))]
impl<E> FromResidual<Result<Infallible, E>> for AppResult {
#[inline(always)]
fn from_residual(_residual: Result<Infallible, E>) -> Self {
AppResult::Failure
}
}
#[cfg(feature = "nightly")]
impl FromResidual<Option<Infallible>> for AppResult {
#[inline(always)]
fn from_residual(_residual: Option<Infallible>) -> Self {
AppResult::Failure
}
}
#[derive(Debug)]
pub enum AppResultWithState<S: AppState> {
Continue(S),
Success(Option<S>),
Failure(Option<S>),
}
#[cfg(all(feature = "nightly", feature = "log-errors"))]
impl<S: AppState, E: Display> FromResidual<Result<Infallible, E>> for AppResultWithState<S> {
fn from_residual(residual: Result<Infallible, E>) -> Self {
let Err(err) = residual;
let err = ::alloc::format!("{err}\0");
unsafe {
SDL_LogError(0, c"%s".as_ptr(), err.as_ptr());
SDL_SetError(c"%s".as_ptr(), err.as_ptr());
};
AppResultWithState::Failure(None)
}
}
#[cfg(all(feature = "nightly", not(feature = "log-errors")))]
impl<S: AppState, E> FromResidual<Result<Infallible, E>> for AppResultWithState<S> {
#[inline(always)]
fn from_residual(_residual: Result<Infallible, E>) -> Self {
AppResultWithState::Failure(None)
}
}
#[cfg(feature = "nightly")]
impl<S: AppState> FromResidual<Option<Infallible>> for AppResultWithState<S> {
#[inline(always)]
fn from_residual(_residual: Option<Infallible>) -> Self {
AppResultWithState::Failure(None)
}
}
impl<S: AppState> AppResultWithState<S> {
pub fn into_raw(self) -> (SDL_AppResult, *mut c_void) {
match self {
Self::Continue(s) => (SDL_AppResult::CONTINUE, s.into_raw()),
Self::Success(s) => (
SDL_AppResult::SUCCESS,
s.map(|s| s.into_raw()).unwrap_or(ptr::null_mut()),
),
Self::Failure(s) => (
SDL_AppResult::FAILURE,
s.map(|s| s.into_raw()).unwrap_or(ptr::null_mut()),
),
}
}
}