Skip to main content

sdl3_main/
lib.rs

1#![no_std]
2#![deny(unsafe_op_in_unsafe_fn)]
3#![doc = include_str!("../README.md.inc")]
4#![cfg_attr(all(feature = "nightly", doc), feature(doc_cfg))] // https://github.com/rust-lang/rust/issues/43781
5#![cfg_attr(feature = "nightly", feature(try_trait_v2))] // https://github.com/rust-lang/rust/issues/84277
6
7#[cfg(feature = "alloc")]
8extern crate alloc;
9
10#[cfg(feature = "std")]
11extern crate std;
12
13use core::{
14    ffi::{c_int, c_void},
15    ptr,
16};
17use sdl3_sys::{
18    init::SDL_AppResult,
19    log::{SDL_LogCategory, SDL_LogCritical},
20};
21
22#[cfg(all(feature = "log-errors", feature = "nightly"))]
23use {
24    core::fmt::Display,
25    sdl3_sys::{error::SDL_SetError, log::SDL_LogError},
26};
27
28#[cfg(feature = "nightly")]
29use core::{convert::Infallible, ops::FromResidual};
30
31#[cfg(doc)]
32use state::SyncPtr;
33
34/// Use this attribute macro if you want to provide your own main, but run it through SDL.
35///
36/// Supported signatures: (all of these can be safe or unsafe)
37/// ```custom,{.rust}
38/// fn()
39/// fn() -> c_int
40/// fn() -> bool
41/// fn(argc: c_int, argv: *mut *mut c_char)
42/// fn(argc: c_int, argv: *mut *mut c_char) -> c_int
43/// fn(argc: c_int, argv: *mut *mut c_char) -> bool
44/// fn() -> impl Termination
45/// ```
46///
47/// Example:
48/// ```rust
49/// #[sdl3_main::main]
50/// fn my_main() {
51///     println!("main called through SDL!");
52/// }
53/// ```
54pub use sdl3_main_macros::main;
55
56/// This attribute macro can be applied to an `impl` block for a type to assign the associated
57/// functions or methods named `app_init`, `app_iterate`, `app_event` and `app_quit` to the
58/// respective sdl main callbacks, as if the corresponding attribute macros were used.
59/// All four must be defined in a single impl block, but `app_quit` is optional and will be
60/// defined as an empty function if omitted.
61///
62/// This is functionally the same as marking those functions with the respective attribute
63/// macros, but works with methods and uses the type the block is implemented for as the
64/// app state type.
65///
66/// See the documentation for [`app_init`], [`app_iterate`], [`app_event`] and [`app_quit`]
67/// for information about supported function signatures. `app_impl` also supports methods,
68/// taking `self` as the app state.
69///
70/// Example:
71/// ```rust
72/// use sdl3_main::{app_impl, AppResult};
73/// use sdl3_sys::events::SDL_Event;
74/// use std::sync::Mutex;
75///
76/// struct MyAppState {
77///     // ...
78/// }
79///
80/// #[app_impl]
81/// impl MyAppState {
82///     fn app_init() -> Option<Box<Mutex<MyAppState>>> {
83///         todo!()
84///     }
85///
86///     fn app_iterate(&mut self) -> AppResult {
87///         todo!()
88///     }
89///
90///     fn app_event(&mut self, event: &SDL_Event) -> AppResult {
91///         todo!()
92///     }
93/// }
94/// ```
95pub use sdl3_main_macros::app_impl;
96
97/// The function tagged with `app_init` is called by SDL at the start of the program on the main thread.
98///
99/// See the SDL documentation for this function:
100/// [`SDL_AppInit`](https://docs.rs/sdl3-sys/latest/sdl3_sys/main/fn.SDL_AppInit.html)
101///
102/// The function tagged with this attribute determines the type of the app state, if any.
103/// You can use a raw signature that doesn't handle the app state for you (safe or unsafe):
104/// ```custom,{.rust}
105/// fn(appstate: *mut *mut c_void, argc: c_int, argv: *mut *mut c_char) -> SDL_AppResult
106/// fn(appstate: *mut *mut c_void, argc: c_int, argv: *mut *mut c_char) -> sdl3_main::AppResult
107/// fn(argc: c_int, argv: *mut *mut c_char) -> SDL_AppResult
108/// fn(argc: c_int, argv: *mut *mut c_char) -> sdl3_main::AppResult
109/// fn(appstate: *mut *mut c_void) -> SDL_AppResult
110/// fn(appstate: *mut *mut c_void) -> sdl3_main::AppResult
111/// fn() -> SDL_AppResult
112/// fn() -> sdl3_main::AppResult
113/// ```
114///
115/// Or you can use one of these signatures with an app state type `S` (safe or unsafe):
116/// ```custom,{.rust}
117/// fn() -> Option<S>
118/// fn() -> sdl3_main::AppResultWithState<S>
119/// ```
120///
121/// The app state type must implement `Send` and `Sync`. You can define your own app state type
122/// by implementing the `sdl3_main::AppState` trait, or use one of these predefined types:
123/// ```custom,{.rust}
124/// ()
125/// sdl3_main::state::SyncPtr<_>
126/// Box<_>
127/// Arc<_>
128/// ```
129///
130/// App states can be borrowed as many different types, depending on the app state. Everything
131/// can be borrowed as `()` or `*mut c_void`.
132///
133/// | AppState | Can be borrowed as |
134/// | -------- | ------------------ |
135/// | [`SyncPtr<T>`] | <ul><li>`SyncPtr<T>`</li><li>`NonNull<T>` (may panic)</li><li>`Option<NonNull<T>>`</li></ul> |
136/// | `Box<T>` | <ul><li>`&T`</li><li>`NonNull<T>`</li></ul> |
137/// | `Box<Mutex<T>>` | <ul><li>`&T`</li><li>`&mut T`</li></ul> |
138/// | `Box<RwLock<T>>` | <ul><li>`&T`</li><li>`&mut T`</li></ul> |
139/// | `Arc<T>` | <ul><li>`Arc<T>`</li><li>`&Arc<T>`</li><li>`&T`</li><li>`NonNull<T>`</li></ul> |
140/// | `Arc<Mutex<T>>` | <ul><li>`&T`</li><li>`&mut T`</li></ul> |
141/// | `Arc<RwLock<T>>` | <ul><li>`&T`</li><li>`&mut T`</li></ul> |
142pub use sdl3_main_macros::app_init;
143
144/// The function tagged with `app_iterate` is called continuously by SDL on the main thread while the app is running.
145///
146/// It will only be called if `app_init` returned continue status, and keep getting called
147/// for as long as `app_iterate` and `app_event` return continue status.
148///
149/// See the SDL documentation for this function:
150/// [`SDL_AppIterate`](https://docs.rs/sdl3-sys/latest/sdl3_sys/main/fn.SDL_AppIterate.html)
151///
152/// Supported signatures (safe or unsafe), where `B` is a borrowed app state as defined in [`app_init`]:
153/// ```custom,{.rust}
154/// fn() -> impl sdl3_main::IntoAppResult
155/// fn(B) -> impl sdl3_main::IntoAppResult
156/// ```
157pub use sdl3_main_macros::app_iterate;
158
159/// The function tagged with `app_event` is called by SDL when an event is delivered. This may get called on the main thread
160/// or on another thread.
161///
162/// See the SDL documentation for this function:
163/// [`SDL_AppEvent`](https://docs.rs/sdl3-sys/latest/sdl3_sys/main/fn.SDL_AppEvent.html)
164///
165/// Supported signatures (safe or unsafe), where `B` is a borrowed app state as defined in [`app_init`],
166/// and `E` is any supported event type:
167/// ```custom,{.rust}
168/// fn() -> impl sdl3_main::IntoAppResult
169/// fn(E) -> impl sdl3_main::IntoAppResult
170/// fn(B, E) -> impl sdl3_main::IntoAppResult
171/// ```
172///
173/// Predefined supported event types:
174/// ```custom,{.rust}
175/// SDL_Event
176/// &SDL_Event
177/// &mut SDL_Event
178/// *const SDL_Event
179/// *mut SDL_Event
180/// ```
181/// You can add support for your own event types by implementing the `PassEventVal`, `PassEventRef` and/or `PassEventMut` traits.
182pub use sdl3_main_macros::app_event;
183
184/// The function tagged with `app_quit` is called by SDL on the main thread when the app quits.
185///
186/// This will get called regardless of the return status of `app_init`, so if that fails the
187/// app state may not be available. If you're using an app state type you should take it as
188/// an `Option` to avoid a panic in that case.
189///
190/// See the SDL documentation for this function:
191/// [`SDL_AppQuit`](https://docs.rs/sdl3-sys/latest/sdl3_sys/main/fn.SDL_AppQuit.html)
192///
193/// Supported signatures (safe or unsafe), where `B` is a borrowed app state, and `S` is the app state
194/// itself as defined in [`app_init`]:
195/// ```custom,{.rust}
196/// fn()
197/// fn(Option<B>)
198/// fn(Option<B>, SDL_AppResult)
199/// fn(Option<B>, sdl3_main::AppResult)
200/// fn(Option<S>)
201/// fn(Option<S>, SDL_AppResult)
202/// fn(Option<S>, sdl3_main::AppResult)
203/// fn(B)
204/// fn(B, SDL_AppResult)
205/// fn(B, sdl3_main::AppResult)
206/// fn(S)
207/// fn(S, SDL_AppResult)
208/// fn(S, sdl3_main::AppResult)
209/// ```
210pub use sdl3_main_macros::app_quit;
211
212macro_rules! defer {
213    ($($tt:tt)*) => {
214        let _defer = $crate::Defer(Some(move || {{ $($tt)* };}));
215    };
216}
217
218struct Defer<F: FnOnce()>(Option<F>);
219
220impl<F: FnOnce()> Drop for Defer<F> {
221    fn drop(&mut self) {
222        if let Some(f) = self.0.take() {
223            f();
224        }
225    }
226}
227
228pub mod app;
229mod main_thread;
230pub mod state;
231
232pub use main_thread::{
233    run_async_on_main_thread, run_sync_on_main_thread, MainThreadData, MainThreadToken,
234};
235use state::{AppState, BorrowMut, BorrowRef, BorrowVal, ConsumeMut, ConsumeRef, ConsumeVal};
236
237#[doc(hidden)]
238pub mod __internal {
239    pub use ::sdl3_sys;
240
241    #[cfg(feature = "alloc")]
242    use alloc::boxed::Box;
243    use core::{
244        ffi::{c_char, c_int},
245        ptr,
246    };
247    use sdl3_sys::main::SDL_RunApp;
248    #[cfg(feature = "std")]
249    use std::{
250        any::Any,
251        cell::UnsafeCell,
252        mem::MaybeUninit,
253        panic::{catch_unwind, resume_unwind, UnwindSafe},
254    };
255
256    #[cfg(feature = "std")]
257    pub struct Shuttle<T>(UnsafeCell<MaybeUninit<Result<T, Box<dyn Any + Send>>>>);
258
259    #[cfg(feature = "std")]
260    unsafe impl<T> Sync for Shuttle<T> {}
261
262    #[cfg(feature = "std")]
263    impl<T> Shuttle<T> {
264        #[allow(clippy::new_without_default)]
265        pub const fn new() -> Self {
266            Self(UnsafeCell::new(MaybeUninit::uninit()))
267        }
268
269        /// # Safety
270        /// - This is not thread safe!
271        /// - Calling this after a previous capture without a corresponding resume will
272        ///   leak/forget the previous capture
273        pub unsafe fn capture(&self, f: impl FnOnce() -> T + UnwindSafe) {
274            match catch_unwind(f) {
275                Ok(value) => {
276                    unsafe { self.0.get().write(MaybeUninit::new(Ok(value))) };
277                }
278                Err(unwind) => {
279                    unsafe { self.0.get().write(MaybeUninit::new(Err(unwind))) };
280                }
281            }
282        }
283
284        /// # Safety
285        /// - This is not thread safe!
286        /// - It's UB to call this without having called `capture`
287        /// - It's UB to call this more than once after each call to `capture`
288        pub unsafe fn resume(&self) -> T {
289            match unsafe { (*self.0.get()).assume_init_read() } {
290                Ok(value) => value,
291                Err(unwind) => resume_unwind(unwind),
292            }
293        }
294    }
295
296    #[cfg(feature = "std")]
297    impl Shuttle<()> {
298        /// # Safety
299        /// - This is not thread safe!
300        /// - It's UB to call this more than once before each call to `resume`
301        pub unsafe fn capture_and_continue<T>(
302            &self,
303            unwind_val: T,
304            f: impl FnOnce() -> T + UnwindSafe,
305        ) -> T {
306            match catch_unwind(f) {
307                Ok(value) => {
308                    unsafe { self.0.get().write(MaybeUninit::new(Ok(()))) };
309                    value
310                }
311                Err(unwind) => {
312                    unsafe { self.0.get().write(MaybeUninit::new(Err(unwind))) };
313                    unwind_val
314                }
315            }
316        }
317    }
318
319    #[cfg(feature = "std")]
320    #[inline(always)] // this is only called once
321    pub unsafe fn run_app(
322        main_fn: unsafe extern "C" fn(c_int, *mut *mut c_char) -> c_int,
323    ) -> c_int {
324        #[cfg(windows)]
325        {
326            // On Windows, SDL_RunApp gets arguments from the win api if passed NULL
327            unsafe { SDL_RunApp(0, ptr::null_mut(), Some(main_fn), ptr::null_mut()) }
328        }
329        #[cfg(not(windows))]
330        {
331            use std::env;
332            use std::vec::Vec;
333
334            // copy arguments so we can null-terminate them
335            let args = env::args_os();
336
337            let mut cargs = Vec::with_capacity(args.len());
338            for arg in args {
339                let mut carg;
340                #[cfg(unix)]
341                {
342                    use std::{borrow::ToOwned, os::unix::ffi::OsStringExt};
343                    carg = arg.to_owned().into_vec();
344                }
345                #[cfg(not(unix))]
346                {
347                    // yolo
348                    carg = arg.to_string_lossy().into_owned().into_bytes();
349                }
350                carg.reserve_exact(1);
351                carg.push(0);
352                cargs.push(carg.into_boxed_slice());
353            }
354
355            let mut ptrargs = Vec::with_capacity(cargs.len() + 1);
356            for carg in cargs.iter_mut() {
357                ptrargs.push(carg.as_mut_ptr() as *mut c_char);
358            }
359            ptrargs.push(ptr::null_mut());
360
361            unsafe {
362                SDL_RunApp(
363                    cargs.len().try_into().expect("too many arguments"),
364                    ptrargs.as_mut_ptr(),
365                    Some(main_fn),
366                    ptr::null_mut(),
367                )
368            }
369        }
370    }
371}
372
373/// This trait is used for converting a type into an [`SDL_AppResult`] or [`AppResult`].
374///
375/// `()` implements this trait and turns into [`SDL_AppResult::CONTINUE`]
376pub trait IntoAppResult: Sized {
377    fn into_sdl_app_result(self) -> SDL_AppResult;
378
379    #[inline(always)]
380    fn into_app_result(self) -> AppResult {
381        self.into_sdl_app_result().into()
382    }
383}
384
385impl IntoAppResult for () {
386    #[inline(always)]
387    fn into_sdl_app_result(self) -> SDL_AppResult {
388        SDL_AppResult::CONTINUE
389    }
390
391    #[inline(always)]
392    fn into_app_result(self) -> AppResult {
393        AppResult::Continue
394    }
395}
396
397impl IntoAppResult for SDL_AppResult {
398    #[inline(always)]
399    fn into_sdl_app_result(self) -> SDL_AppResult {
400        self
401    }
402
403    #[inline(always)]
404    fn into_app_result(self) -> AppResult {
405        self.into()
406    }
407}
408
409impl IntoAppResult for AppResult {
410    #[inline(always)]
411    fn into_sdl_app_result(self) -> SDL_AppResult {
412        self.into()
413    }
414
415    #[inline(always)]
416    fn into_app_result(self) -> AppResult {
417        self
418    }
419}
420
421/// This is the Rust enum equivalent to [`SDL_AppResult`].
422#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
423pub enum AppResult {
424    /// Continue running
425    Continue,
426
427    /// Quit with success status
428    Success,
429
430    /// Quit with failure status
431    Failure,
432}
433
434impl From<AppResult> for SDL_AppResult {
435    #[inline(always)]
436    fn from(value: AppResult) -> Self {
437        match value {
438            AppResult::Continue => Self::CONTINUE,
439            AppResult::Success => Self::SUCCESS,
440            AppResult::Failure => Self::FAILURE,
441        }
442    }
443}
444
445impl From<SDL_AppResult> for AppResult {
446    #[inline]
447    fn from(value: SDL_AppResult) -> Self {
448        match value {
449            SDL_AppResult::CONTINUE => Self::Continue,
450            SDL_AppResult::SUCCESS => Self::Success,
451            SDL_AppResult::FAILURE => Self::Failure,
452            SDL_AppResult(value) => {
453                #[cold]
454                #[inline(never)]
455                fn unknown_app_result_value(result: c_int) -> AppResult {
456                    unsafe {
457                        SDL_LogCritical(
458                            SDL_LogCategory::APPLICATION.into(),
459                            c"Unrecognized app result value: %d".as_ptr(),
460                            result,
461                        )
462                    };
463                    AppResult::Failure
464                }
465                unknown_app_result_value(value)
466            }
467        }
468    }
469}
470
471#[cfg(all(feature = "nightly", feature = "log-errors"))]
472impl<E: Display> FromResidual<Result<Infallible, E>> for AppResult {
473    fn from_residual(residual: Result<Infallible, E>) -> Self {
474        let Err(err) = residual;
475        let err = ::alloc::format!("{err}\0");
476        unsafe {
477            SDL_LogError(0, c"%s".as_ptr(), err.as_ptr());
478            SDL_SetError(c"%s".as_ptr(), err.as_ptr());
479        };
480        AppResult::Failure
481    }
482}
483
484#[cfg(all(feature = "nightly", not(feature = "log-errors")))]
485impl<E> FromResidual<Result<Infallible, E>> for AppResult {
486    #[inline(always)]
487    fn from_residual(_residual: Result<Infallible, E>) -> Self {
488        AppResult::Failure
489    }
490}
491
492#[cfg(feature = "nightly")]
493impl FromResidual<Option<Infallible>> for AppResult {
494    #[inline(always)]
495    fn from_residual(_residual: Option<Infallible>) -> Self {
496        AppResult::Failure
497    }
498}
499
500/// An [`AppResult`] with an app state, for returning from the function tagged with [`app_init`].
501#[derive(Debug)]
502pub enum AppResultWithState<S: AppState> {
503    /// Continue running
504    Continue(S),
505
506    /// Quit with success status
507    Success(Option<S>),
508
509    /// Quit with failure status
510    Failure(Option<S>),
511}
512
513#[cfg(all(feature = "nightly", feature = "log-errors"))]
514impl<S: AppState, E: Display> FromResidual<Result<Infallible, E>> for AppResultWithState<S> {
515    fn from_residual(residual: Result<Infallible, E>) -> Self {
516        let Err(err) = residual;
517        let err = ::alloc::format!("{err}\0");
518        unsafe {
519            SDL_LogError(0, c"%s".as_ptr(), err.as_ptr());
520            SDL_SetError(c"%s".as_ptr(), err.as_ptr());
521        };
522        AppResultWithState::Failure(None)
523    }
524}
525
526#[cfg(all(feature = "nightly", not(feature = "log-errors")))]
527impl<S: AppState, E> FromResidual<Result<Infallible, E>> for AppResultWithState<S> {
528    #[inline(always)]
529    fn from_residual(_residual: Result<Infallible, E>) -> Self {
530        AppResultWithState::Failure(None)
531    }
532}
533
534#[cfg(feature = "nightly")]
535impl<S: AppState> FromResidual<Option<Infallible>> for AppResultWithState<S> {
536    #[inline(always)]
537    fn from_residual(_residual: Option<Infallible>) -> Self {
538        AppResultWithState::Failure(None)
539    }
540}
541
542impl<S: AppState> AppResultWithState<S> {
543    pub fn into_raw(self) -> (SDL_AppResult, *mut c_void) {
544        match self {
545            Self::Continue(s) => (SDL_AppResult::CONTINUE, s.into_raw()),
546            Self::Success(s) => (
547                SDL_AppResult::SUCCESS,
548                s.map(|s| s.into_raw()).unwrap_or(ptr::null_mut()),
549            ),
550            Self::Failure(s) => (
551                SDL_AppResult::FAILURE,
552                s.map(|s| s.into_raw()).unwrap_or(ptr::null_mut()),
553            ),
554        }
555    }
556}