Skip to main content

sdl_main_wrapper/
lib.rs

1#![no_std]
2
3#[cfg(feature = "alloc")]
4extern crate alloc;
5#[cfg(feature = "std")]
6extern crate std;
7
8#[cfg(feature = "args")]
9use alloc::ffi::CString;
10#[cfg(feature = "alloc")]
11use alloc::format;
12use core::ffi::{c_char, c_int, c_void};
13use sdl_sys_bindgen::*;
14
15/// Idiomatic Rust trait for SDL's callback-based application loop.
16///
17/// # Thread Safety
18///
19/// `Send` is required because the app state is passed across the FFI boundary
20/// and SDL may invoke callbacks from a thread it manages internally.
21pub trait SdlApp: Sized + Send {
22    /// Custom error type returned on initialization failure.
23    type Error: core::fmt::Debug;
24
25    /// Called once at startup. Return `Ok(Self)` to continue, or `Err` to terminate.
26    ///
27    /// # Memory Management
28    ///
29    /// The returned `Self` instance is moved onto the heap and its ownership
30    /// is handed to SDL. It will be passed back to `iterate`, `event`, and `quit`.
31    #[cfg(feature = "args")]
32    fn init(args: &[CString]) -> Result<Self, Self::Error>;
33    /// Called once at startup. Return `Ok(Self)` to continue, or `Err` to terminate.
34    ///
35    /// # Memory Management
36    ///
37    /// The returned `Self` instance is moved onto the heap and its ownership
38    /// is handed to SDL. It will be passed back to `iterate`, `event`, and `quit`.
39    #[cfg(not(feature = "args"))]
40    fn init() -> Result<Self, Self::Error>;
41
42    /// Called repeatedly to process a single frame.
43    fn iterate(&mut self) -> SDL_AppResult;
44
45    /// Called when an event arrives.
46    fn event(&mut self, event: &SDL_Event) -> SDL_AppResult;
47
48    /// Called before the app exits.
49    ///
50    /// After this method returns, the app state is automatically dropped and freed.
51    fn quit(&mut self, result: SDL_AppResult);
52}
53
54/// Starts the SDL application loop.
55pub fn run_app<A: SdlApp>() -> i32 {
56    // 1. The Init Trampoline
57    extern "C" fn c_init<A: SdlApp>(
58        appstate: *mut *mut c_void,
59        #[allow(unused_variables)] argc: c_int,
60        #[allow(unused_variables)] argv: *mut *mut c_char,
61    ) -> SDL_AppResult {
62        let init_result = {
63            #[cfg(feature = "args")]
64            {
65                let args = unsafe {
66                    let argc = argc as usize;
67                    std::vec::Vec::from_raw_parts(argv, argc, argc)
68                        .iter()
69                        .map(|arg| CString::from_raw(*arg))
70                        .collect::<std::vec::Vec<CString>>()
71                };
72                A::init(args.as_slice())
73            }
74            #[cfg(not(feature = "args"))]
75            {
76                A::init()
77            }
78        };
79
80        match init_result {
81            Ok(app) => {
82                unsafe {
83                    let ptr = SDL_malloc(core::mem::size_of::<A>());
84                    if ptr.is_null() {
85                        log_error(SDL_GetError());
86                        return SDL_AppResult::SDL_APP_FAILURE;
87                    }
88                    let state_ptr = ptr as *mut A;
89                    core::ptr::write(state_ptr, app);
90                    *appstate = ptr;
91                }
92                SDL_AppResult::SDL_APP_CONTINUE
93            }
94            #[allow(unused_variables)]
95            Err(e) => {
96                #[cfg(feature = "alloc")]
97                let err_msg = format!("{:?}\0", e);
98                #[cfg(not(feature = "alloc"))]
99                let err_msg = "SDL app initialization failed\0";
100                log_error(err_msg.as_ptr() as *const c_char);
101                SDL_AppResult::SDL_APP_FAILURE
102            }
103        }
104    }
105
106    // 2. The Iterate Trampoline
107    extern "C" fn c_iter<A: SdlApp>(appstate: *mut c_void) -> SDL_AppResult {
108        if appstate.is_null() {
109            return SDL_AppResult::SDL_APP_FAILURE;
110        }
111        let app = unsafe { &mut *(appstate as *mut A) };
112        app.iterate()
113    }
114
115    // 3. The Event Trampoline
116    extern "C" fn c_event<A: SdlApp>(
117        appstate: *mut c_void,
118        event: *mut SDL_Event,
119    ) -> SDL_AppResult {
120        if appstate.is_null() || event.is_null() {
121            return SDL_AppResult::SDL_APP_FAILURE;
122        }
123        let app = unsafe { &mut *(appstate as *mut A) };
124        app.event(unsafe { &*event })
125    }
126
127    // 4. The Quit Trampoline
128    extern "C" fn c_quit<A: SdlApp>(appstate: *mut c_void, result: SDL_AppResult) {
129        if !appstate.is_null() {
130            unsafe {
131                let mut app = core::ptr::read(appstate as *mut A);
132                app.quit(result);
133                SDL_free(appstate);
134            }
135        }
136    }
137
138    extern "C" fn enter_callbacks<A: SdlApp>(argc: c_int, argv: *mut *mut c_char) -> i32 {
139        unsafe {
140            SDL_EnterAppMainCallbacks(
141                argc,
142                argv,
143                Some(c_init::<A>),
144                Some(c_iter::<A>),
145                Some(c_event::<A>),
146                Some(c_quit::<A>),
147            )
148        }
149    }
150
151    unsafe {
152        #[cfg(feature = "args")]
153        let arg_count = std::env::args().count();
154        SDL_RunApp(
155            {
156                #[cfg(feature = "args")]
157                {
158                    arg_count as c_int
159                }
160                #[cfg(not(feature = "args"))]
161                {
162                    0
163                }
164            },
165            {
166                #[cfg(feature = "args")]
167                {
168                    let mut vec: std::vec::Vec<*mut c_char> =
169                        std::vec::Vec::with_capacity(arg_count);
170                    for arg in std::env::args() {
171                        let c_string = CString::new(arg);
172                        if let Ok(c_string) = c_string {
173                            vec.push(c_string.into_raw());
174                        }
175                    }
176                    vec.into_raw_parts().0
177                }
178                #[cfg(not(feature = "args"))]
179                {
180                    core::ptr::null_mut()
181                }
182            },
183            Some(enter_callbacks::<A>),
184            core::ptr::null_mut(), // reserved
185        )
186    }
187}
188
189fn log_error(msg: *const c_char) {
190    unsafe {
191        SDL_LogError(
192            SDL_LogCategory::SDL_LOG_CATEGORY_APPLICATION.0 as c_int,
193            msg,
194        );
195    }
196}