11use std:: {
2+ cell:: { Cell , RefCell , RefMut } ,
23 ffi:: { c_char, c_void} ,
34 io:: Write ,
45 mem:: MaybeUninit ,
@@ -53,15 +54,20 @@ macro_rules! c_str {
5354
5455/// The Core global state.
5556struct Core {
56- env_callback : retro_environment_t ,
57- video_callback : retro_video_refresh_t ,
58- audio_callback : retro_audio_sample_batch_t ,
59- input_poll_callback : retro_input_poll_t ,
60- input_state_callback : retro_input_state_t ,
61- state : Option < GameBoy > ,
57+ env_callback : Cell < retro_environment_t > ,
58+ video_callback : Cell < retro_video_refresh_t > ,
59+ audio_callback : Cell < retro_audio_sample_batch_t > ,
60+ input_poll_callback : Cell < retro_input_poll_t > ,
61+ input_state_callback : Cell < retro_input_state_t > ,
62+ state : RefCell < Option < GameBoy > > ,
6263
6364 // A double buffer of the lcd screen pixels. Updated on vblank.
64- screen_buffer : [ u8 ; SCREEN_WIDTH as usize * SCREEN_HEIGHT as usize ] ,
65+ screen_buffer : RefCell < [ u8 ; SCREEN_WIDTH as usize * SCREEN_HEIGHT as usize ] > ,
66+
67+ #[ cfg( debug_assertions) ]
68+ /// Use to assert that the libretro API is single-threaded. (Maybe libretro can call the API
69+ /// from multiple threads, but let's assume it doesn't)
70+ owner_thread : std:: thread:: ThreadId ,
6571}
6672impl Default for Core {
6773 fn default ( ) -> Self {
@@ -72,24 +78,32 @@ impl Default for Core {
7278 input_poll_callback : Default :: default ( ) ,
7379 input_state_callback : Default :: default ( ) ,
7480 state : Default :: default ( ) ,
75- screen_buffer : [ 0 ; SCREEN_WIDTH as usize * SCREEN_HEIGHT as usize ] ,
81+ screen_buffer : [ 0 ; SCREEN_WIDTH as usize * SCREEN_HEIGHT as usize ] . into ( ) ,
82+
83+ #[ cfg( debug_assertions) ]
84+ owner_thread : std:: thread:: current ( ) . id ( ) ,
7685 }
7786 }
7887}
88+ impl Core {
89+ fn state_mut ( & self ) -> RefMut < GameBoy > {
90+ RefMut :: map ( self . state . borrow_mut ( ) , |x| x. as_mut ( ) . unwrap ( ) )
91+ }
92+ }
7993
8094static mut CORE : Option < Core > = None ;
8195
82- fn core ( ) -> & ' static mut Core {
83- // RetroLib promises that it will not call the API from multiple threads, so this is safe, right?
84- // Actually this is unsafe because the caller could leak the mutable reference. This should be a
85- // RefCell or something.
86- // In fact, I am already doing UB, because I am calling core() in retro_run and in the
87- // v_blank_callback, creating two mutable references to CORE at the same time!
96+ fn core ( ) -> & ' static Core {
97+ // SAFETY: the libretro API is single-threaded, so we can garantee there is no concurrent acess
98+ // to this function. CORE is never mutate after being initialized, so subsequent calls to this
99+ // function will not invalidate previous returned references.
88100 unsafe {
89- if CORE . is_none ( ) {
90- CORE = Some ( Core :: default ( ) ) ;
91- }
92- CORE . as_mut ( ) . unwrap ( )
101+ let core = CORE . get_or_insert_with ( Core :: default) ;
102+
103+ // assert this is single-threaded
104+ debug_assert_eq ! ( core. owner_thread, std:: thread:: current( ) . id( ) ) ;
105+
106+ core
93107 }
94108}
95109
@@ -100,7 +114,7 @@ pub extern "C" fn retro_api_version() -> u32 {
100114
101115#[ no_mangle]
102116pub extern "C" fn retro_set_environment ( callback : retro_environment_t ) {
103- core ( ) . env_callback = callback;
117+ core ( ) . env_callback . set ( callback) ;
104118 let mut log_callback: retro_log_callback = retro_log_callback { log : None } ;
105119
106120 if let Some ( callback) = callback {
@@ -192,26 +206,27 @@ extern "C" fn retro_load_game(info: Option<&retro_game_info>) -> bool {
192206 let mut gb = GameBoy :: new ( None , cartridge) ;
193207 gb. sound . get_mut ( ) . sample_frequency = SAMPLE_RATE ;
194208 gb. v_blank = Some ( Box :: new ( |gb| {
195- core ( ) . screen_buffer = gb. ppu . get_mut ( ) . screen . packed ( ) ;
209+ * core ( ) . screen_buffer . borrow_mut ( ) = gb. ppu . get_mut ( ) . screen . packed ( ) ;
196210 } ) ) ;
197211
198- core ( ) . state = Some ( gb) ;
212+ * core ( ) . state . borrow_mut ( ) = Some ( gb) ;
199213
200214 true
201215}
202216
203217#[ no_mangle]
204218pub extern "C" fn retro_run ( ) {
205219 let core = core ( ) ;
206- let Some ( state) = & mut core. state else {
207- log:: error!( "GameBoy is not loaded?" ) ;
208- return ;
209- } ;
210-
211- state. joypad = 0xff ;
212- if let ( Some ( input_poll) , Some ( input_state) ) =
213- ( core. input_poll_callback , core. input_state_callback )
214- {
220+ // let Some(state) = &mut core.state else {
221+ // log::error!("GameBoy is not loaded?");
222+ // return;
223+ // };
224+
225+ core. state_mut ( ) . joypad = 0xff ;
226+ if let ( Some ( input_poll) , Some ( input_state) ) = (
227+ core. input_poll_callback . get ( ) ,
228+ core. input_state_callback . get ( ) ,
229+ ) {
215230 let key_map = [
216231 RETRO_DEVICE_ID_JOYPAD_RIGHT ,
217232 RETRO_DEVICE_ID_JOYPAD_LEFT ,
@@ -229,18 +244,18 @@ pub extern "C" fn retro_run() {
229244 for ( i, id) in key_map. iter ( ) . copied ( ) . enumerate ( ) {
230245 let value = unsafe { input_state ( 0 , RETRO_DEVICE_JOYPAD , 0 , id) } ;
231246 if value != 0 {
232- state . joypad &= !( 1 << i) ;
247+ core . state_mut ( ) . joypad &= !( 1 << i) ;
233248 }
234249 }
235250 }
236251
237- let target = state . clock_count + gameroy:: consts:: FRAME_CYCLES ;
238- while state . clock_count < target {
239- Interpreter ( state ) . interpret_op ( ) ;
252+ let target = core . state_mut ( ) . clock_count + gameroy:: consts:: FRAME_CYCLES ;
253+ while core . state_mut ( ) . clock_count < target {
254+ Interpreter ( & mut core . state_mut ( ) ) . interpret_op ( ) ;
240255 }
241256
242- if let Some ( callback) = core. video_callback {
243- let frame = core. screen_buffer . map ( |c| {
257+ if let Some ( callback) = core. video_callback . get ( ) {
258+ let frame = core. screen_buffer . borrow_mut ( ) . map ( |c| {
244259 /// 0RGB1555 format
245260 /// FIXME: this format is deprecated
246261 #[ allow( clippy:: unusual_byte_groupings) ]
@@ -262,9 +277,10 @@ pub extern "C" fn retro_run() {
262277 }
263278 }
264279
265- if let Some ( callback) = core. audio_callback {
280+ if let Some ( callback) = core. audio_callback . get ( ) {
266281 unsafe {
267- let buffer = state. sound . get_mut ( ) . get_output ( state. clock_count ) ;
282+ let clock_count = core. state_mut ( ) . clock_count ;
283+ let buffer = core. state_mut ( ) . sound . get_mut ( ) . get_output ( clock_count) ;
268284 let buffer: Vec < i16 > = buffer. into_iter ( ) . map ( |x| ( x as i16 - 128 ) * 30 ) . collect ( ) ;
269285 ( callback) ( buffer. as_ptr ( ) , buffer. len ( ) as u64 / 2 ) ;
270286 }
@@ -274,25 +290,25 @@ pub extern "C" fn retro_run() {
274290#[ no_mangle]
275291pub extern "C" fn retro_set_video_refresh ( callback : retro_video_refresh_t ) {
276292 log:: info!( "set video refresh" ) ;
277- core ( ) . video_callback = callback;
293+ core ( ) . video_callback . set ( callback) ;
278294}
279295
280296#[ no_mangle]
281297pub extern "C" fn retro_set_audio_sample_batch ( callback : retro_audio_sample_batch_t ) {
282298 log:: info!( "set audio sample batch" ) ;
283- core ( ) . audio_callback = callback;
299+ core ( ) . audio_callback . set ( callback) ;
284300}
285301
286302#[ no_mangle]
287303pub extern "C" fn retro_set_input_poll ( callback : retro_input_poll_t ) {
288304 log:: info!( "set input poll" ) ;
289- core ( ) . input_poll_callback = callback;
305+ core ( ) . input_poll_callback . set ( callback) ;
290306}
291307
292308#[ no_mangle]
293309pub extern "C" fn retro_set_input_state ( callback : retro_input_state_t ) {
294310 log:: info!( "set input state" ) ;
295- core ( ) . input_state_callback = callback;
311+ core ( ) . input_state_callback . set ( callback) ;
296312}
297313
298314#[ no_mangle]
0 commit comments