@@ -388,16 +388,23 @@ impl MainWindow {
388388
389389pub struct App {
390390 main_window : Option < MainWindow > ,
391+ runtime : Option < tokio:: runtime:: Runtime > ,
391392 gilrs : Option < Gilrs > ,
392393 event_loop_proxy : EventLoopProxy < RuffleEvent > ,
393394 preferences : GlobalPreferences ,
394395 font_database : fontdb:: Database ,
395396}
396397
398+ /// Enters the tokio runtime context.
399+ /// This cannot be a method, as the borrow-checker would complain.
400+ macro_rules! enter_runtime {
401+ ( $this: expr) => {
402+ let _guard = $this. runtime. as_ref( ) . map( |runtime| runtime. enter( ) ) ;
403+ } ;
404+ }
405+
397406impl App {
398- pub async fn new (
399- preferences : GlobalPreferences ,
400- ) -> Result < ( Self , EventLoop < RuffleEvent > ) , Error > {
407+ pub fn new ( preferences : GlobalPreferences ) -> Result < ( Self , EventLoop < RuffleEvent > ) , Error > {
401408 let event_loop = EventLoop :: with_user_event ( ) . build ( ) ?;
402409
403410 let mut font_database = fontdb:: Database :: default ( ) ;
@@ -409,10 +416,12 @@ impl App {
409416 } )
410417 . ok ( ) ;
411418 let event_loop_proxy = event_loop. create_proxy ( ) ;
419+ let runtime = tokio:: runtime:: Runtime :: new ( ) ?;
412420
413421 Ok ( (
414422 Self {
415423 main_window : None ,
424+ runtime : Some ( runtime) ,
416425 gilrs,
417426 event_loop_proxy,
418427 font_database,
@@ -425,6 +434,8 @@ impl App {
425434
426435impl ApplicationHandler < RuffleEvent > for App {
427436 fn new_events ( & mut self , event_loop : & ActiveEventLoop , cause : StartCause ) {
437+ enter_runtime ! ( self ) ;
438+
428439 if cause == StartCause :: Init {
429440 let movie_url = self . preferences . cli . movie_url . clone ( ) ;
430441 let icon_bytes = include_bytes ! ( "../assets/favicon-32.rgba" ) ;
@@ -533,6 +544,8 @@ impl ApplicationHandler<RuffleEvent> for App {
533544 fn resumed ( & mut self , _event_loop : & ActiveEventLoop ) { }
534545
535546 fn user_event ( & mut self , event_loop : & ActiveEventLoop , event : RuffleEvent ) {
547+ enter_runtime ! ( self ) ;
548+
536549 match ( & mut self . main_window , event) {
537550 ( Some ( main_window) , RuffleEvent :: TaskPoll ( task) ) => main_window. player . poll ( task) ,
538551
@@ -636,12 +649,16 @@ impl ApplicationHandler<RuffleEvent> for App {
636649 _window_id : WindowId ,
637650 event : WindowEvent ,
638651 ) {
652+ enter_runtime ! ( self ) ;
653+
639654 if let Some ( main_window) = & mut self . main_window {
640655 main_window. window_event ( event_loop, event) ;
641656 }
642657 }
643658
644659 fn about_to_wait ( & mut self , event_loop : & ActiveEventLoop ) {
660+ enter_runtime ! ( self ) ;
661+
645662 if let Some ( main_window) = & mut self . main_window {
646663 main_window. about_to_wait ( self . gilrs . as_mut ( ) ) ;
647664
@@ -655,6 +672,21 @@ impl ApplicationHandler<RuffleEvent> for App {
655672 }
656673 }
657674 }
675+
676+ fn exiting ( & mut self , _event_loop : & ActiveEventLoop ) {
677+ // `MainWindow` needs a tokio context to properly drop.
678+ {
679+ enter_runtime ! ( self ) ;
680+ let _ = self . main_window . take ( ) ;
681+ }
682+
683+ // Manually stop the tokio runtime: this makes sure that any pending Player-bound futures
684+ // are properly cancelled and put back on the winit event loop before it closes, preventing
685+ // them from being dropped on the wrong thread and causing a panic.
686+ if let Some ( runtime) = self . runtime . take ( ) {
687+ runtime. shutdown_timeout ( std:: time:: Duration :: from_secs ( 1 ) ) ;
688+ }
689+ }
658690}
659691
660692enum LoadingState {
0 commit comments