Extract a platform::Lifecycle trait

Nathan Sobo created

This will allow us to make platform::Platform be Send + Sync and keep the lifecycle on the main thread.

Change summary

gpui/src/app.rs                   |  42 +++++---
gpui/src/platform.rs              |   6 
gpui/src/platform/mac.rs          |   8 +
gpui/src/platform/mac/platform.rs | 158 +++++++++++++++++---------------
gpui/src/platform/test.rs         |  40 +++++---
5 files changed, 143 insertions(+), 111 deletions(-)

Detailed changes

gpui/src/app.rs 🔗

@@ -112,10 +112,12 @@ impl App {
         asset_source: A,
         f: F,
     ) -> T {
+        let lifecycle = platform::test::lifecycle();
         let platform = platform::test::platform();
         let foreground = Rc::new(executor::Foreground::test());
         let cx = Rc::new(RefCell::new(MutableAppContext::new(
             foreground,
+            Rc::new(lifecycle),
             Rc::new(platform),
             asset_source,
         )));
@@ -129,11 +131,13 @@ impl App {
         Fn: FnOnce(TestAppContext) -> F,
         F: Future<Output = T>,
     {
+        let lifecycle = Rc::new(platform::test::lifecycle());
         let platform = Rc::new(platform::test::platform());
         let foreground = Rc::new(executor::Foreground::test());
         let cx = TestAppContext(
             Rc::new(RefCell::new(MutableAppContext::new(
                 foreground.clone(),
+                lifecycle,
                 platform.clone(),
                 asset_source,
             ))),
@@ -146,16 +150,18 @@ impl App {
     }
 
     pub fn new(asset_source: impl AssetSource) -> Result<Self> {
+        let lifecycle = platform::current::lifecycle();
         let platform = platform::current::platform();
         let foreground = Rc::new(executor::Foreground::platform(platform.dispatcher())?);
         let app = Self(Rc::new(RefCell::new(MutableAppContext::new(
             foreground,
+            lifecycle.clone(),
             platform.clone(),
             asset_source,
         ))));
 
         let cx = app.0.clone();
-        platform.on_menu_command(Box::new(move |command, arg| {
+        lifecycle.on_menu_command(Box::new(move |command, arg| {
             let mut cx = cx.borrow_mut();
             if let Some(key_window_id) = cx.platform.key_window_id() {
                 if let Some((presenter, _)) = cx.presenters_and_platform_windows.get(&key_window_id)
@@ -181,8 +187,8 @@ impl App {
     {
         let cx = self.0.clone();
         self.0
-            .borrow()
-            .platform
+            .borrow_mut()
+            .lifecycle
             .on_become_active(Box::new(move || callback(&mut *cx.borrow_mut())));
         self
     }
@@ -193,8 +199,8 @@ impl App {
     {
         let cx = self.0.clone();
         self.0
-            .borrow()
-            .platform
+            .borrow_mut()
+            .lifecycle
             .on_resign_active(Box::new(move || callback(&mut *cx.borrow_mut())));
         self
     }
@@ -204,9 +210,12 @@ impl App {
         F: 'static + FnMut(Event, &mut MutableAppContext) -> bool,
     {
         let cx = self.0.clone();
-        self.0.borrow().platform.on_event(Box::new(move |event| {
-            callback(event, &mut *cx.borrow_mut())
-        }));
+        self.0
+            .borrow_mut()
+            .lifecycle
+            .on_event(Box::new(move |event| {
+                callback(event, &mut *cx.borrow_mut())
+            }));
         self
     }
 
@@ -216,8 +225,8 @@ impl App {
     {
         let cx = self.0.clone();
         self.0
-            .borrow()
-            .platform
+            .borrow_mut()
+            .lifecycle
             .on_open_files(Box::new(move |paths| {
                 callback(paths, &mut *cx.borrow_mut())
             }));
@@ -228,8 +237,8 @@ impl App {
     where
         F: 'static + FnOnce(&mut MutableAppContext),
     {
-        let platform = self.0.borrow().platform.clone();
-        platform.run(Box::new(move || {
+        let lifecycle = self.0.borrow().lifecycle.clone();
+        lifecycle.run(Box::new(move || {
             let mut cx = self.0.borrow_mut();
             on_finish_launching(&mut *cx);
         }))
@@ -516,6 +525,7 @@ type GlobalActionCallback = dyn FnMut(&dyn Any, &mut MutableAppContext);
 
 pub struct MutableAppContext {
     weak_self: Option<rc::Weak<RefCell<Self>>>,
+    lifecycle: Rc<dyn platform::Lifecycle>,
     platform: Rc<dyn platform::Platform>,
     assets: Arc<AssetCache>,
     cx: AppContext,
@@ -537,14 +547,16 @@ pub struct MutableAppContext {
 }
 
 impl MutableAppContext {
-    pub fn new(
+    fn new(
         foreground: Rc<executor::Foreground>,
+        lifecycle: Rc<dyn platform::Lifecycle>,
         platform: Rc<dyn platform::Platform>,
         asset_source: impl AssetSource,
     ) -> Self {
         let fonts = platform.fonts();
         Self {
             weak_self: None,
+            lifecycle,
             platform,
             assets: Arc::new(AssetCache::new(asset_source)),
             cx: AppContext {
@@ -704,8 +716,8 @@ impl MutableAppContext {
         result
     }
 
-    pub fn set_menus(&self, menus: Vec<Menu>) {
-        self.platform.set_menus(menus);
+    pub fn set_menus(&mut self, menus: Vec<Menu>) {
+        self.lifecycle.set_menus(menus);
     }
 
     fn prompt<F>(

gpui/src/platform.rs 🔗

@@ -27,14 +27,17 @@ use std::{
     sync::Arc,
 };
 
-pub trait Platform {
+pub(crate) trait Lifecycle {
     fn on_menu_command(&self, callback: Box<dyn FnMut(&str, Option<&dyn Any>)>);
     fn on_become_active(&self, callback: Box<dyn FnMut()>);
     fn on_resign_active(&self, callback: Box<dyn FnMut()>);
     fn on_event(&self, callback: Box<dyn FnMut(Event) -> bool>);
     fn on_open_files(&self, callback: Box<dyn FnMut(Vec<PathBuf>)>);
+    fn set_menus(&self, menus: Vec<Menu>);
     fn run(&self, on_finish_launching: Box<dyn FnOnce() -> ()>);
+}
 
+pub trait Platform {
     fn dispatcher(&self) -> Arc<dyn Dispatcher>;
     fn fonts(&self) -> Arc<dyn FontSystem>;
 
@@ -59,7 +62,6 @@ pub trait Platform {
     fn quit(&self);
     fn write_to_clipboard(&self, item: ClipboardItem);
     fn read_from_clipboard(&self) -> Option<ClipboardItem>;
-    fn set_menus(&self, menus: Vec<Menu>);
 }
 
 pub trait Dispatcher: Send + Sync {

gpui/src/platform/mac.rs 🔗

@@ -11,11 +11,15 @@ mod window;
 use cocoa::base::{BOOL, NO, YES};
 pub use dispatcher::Dispatcher;
 pub use fonts::FontSystem;
-use platform::MacPlatform;
+use platform::{MacLifecycle, MacPlatform};
 use std::rc::Rc;
 use window::Window;
 
-pub fn platform() -> Rc<dyn super::Platform> {
+pub(crate) fn lifecycle() -> Rc<dyn super::Lifecycle> {
+    Rc::new(MacLifecycle::default())
+}
+
+pub(crate) fn platform() -> Rc<dyn super::Platform> {
     Rc::new(MacPlatform::new())
 }
 

gpui/src/platform/mac/platform.rs 🔗

@@ -32,7 +32,7 @@ use std::{
     sync::Arc,
 };
 
-const MAC_PLATFORM_IVAR: &'static str = "platform";
+const MAC_LIFECYCLE_IVAR: &'static str = "lifecycle";
 static mut APP_CLASS: *const Class = ptr::null();
 static mut APP_DELEGATE_CLASS: *const Class = ptr::null();
 
@@ -40,7 +40,7 @@ static mut APP_DELEGATE_CLASS: *const Class = ptr::null();
 unsafe fn build_classes() {
     APP_CLASS = {
         let mut decl = ClassDecl::new("GPUIApplication", class!(NSApplication)).unwrap();
-        decl.add_ivar::<*mut c_void>(MAC_PLATFORM_IVAR);
+        decl.add_ivar::<*mut c_void>(MAC_LIFECYCLE_IVAR);
         decl.add_method(
             sel!(sendEvent:),
             send_event as extern "C" fn(&mut Object, Sel, id),
@@ -50,7 +50,7 @@ unsafe fn build_classes() {
 
     APP_DELEGATE_CLASS = {
         let mut decl = ClassDecl::new("GPUIApplicationDelegate", class!(NSResponder)).unwrap();
-        decl.add_ivar::<*mut c_void>(MAC_PLATFORM_IVAR);
+        decl.add_ivar::<*mut c_void>(MAC_LIFECYCLE_IVAR);
         decl.add_method(
             sel!(applicationDidFinishLaunching:),
             did_finish_launching as extern "C" fn(&mut Object, Sel, id),
@@ -75,43 +75,26 @@ unsafe fn build_classes() {
     }
 }
 
-pub struct MacPlatform {
-    dispatcher: Arc<Dispatcher>,
-    fonts: Arc<FontSystem>,
-    callbacks: RefCell<Callbacks>,
-    menu_item_actions: RefCell<Vec<(String, Option<Box<dyn Any>>)>>,
-    pasteboard: id,
-    text_hash_pasteboard_type: id,
-    metadata_pasteboard_type: id,
-}
+#[derive(Default)]
+pub struct MacLifecycle(RefCell<MacLifecycleState>);
 
 #[derive(Default)]
-struct Callbacks {
+pub struct MacLifecycleState {
     become_active: Option<Box<dyn FnMut()>>,
     resign_active: Option<Box<dyn FnMut()>>,
     event: Option<Box<dyn FnMut(crate::Event) -> bool>>,
     menu_command: Option<Box<dyn FnMut(&str, Option<&dyn Any>)>>,
     open_files: Option<Box<dyn FnMut(Vec<PathBuf>)>>,
     finish_launching: Option<Box<dyn FnOnce() -> ()>>,
+    menu_actions: Vec<(String, Option<Box<dyn Any>>)>,
 }
 
-impl MacPlatform {
-    pub fn new() -> Self {
-        Self {
-            dispatcher: Arc::new(Dispatcher),
-            fonts: Arc::new(FontSystem::new()),
-            callbacks: Default::default(),
-            menu_item_actions: Default::default(),
-            pasteboard: unsafe { NSPasteboard::generalPasteboard(nil) },
-            text_hash_pasteboard_type: unsafe { ns_string("zed-text-hash") },
-            metadata_pasteboard_type: unsafe { ns_string("zed-metadata") },
-        }
-    }
-
+impl MacLifecycle {
     unsafe fn create_menu_bar(&self, menus: Vec<Menu>) -> id {
         let menu_bar = NSMenu::new(nil).autorelease();
-        let mut menu_item_actions = self.menu_item_actions.borrow_mut();
-        menu_item_actions.clear();
+        let mut state = self.0.borrow_mut();
+
+        state.menu_actions.clear();
 
         for menu_config in menus {
             let menu_bar_item = NSMenuItem::new(nil).autorelease();
@@ -170,9 +153,9 @@ impl MacPlatform {
                                 .autorelease();
                         }
 
-                        let tag = menu_item_actions.len() as NSInteger;
+                        let tag = state.menu_actions.len() as NSInteger;
                         let _: () = msg_send![item, setTag: tag];
-                        menu_item_actions.push((action.to_string(), arg));
+                        state.menu_actions.push((action.to_string(), arg));
                     }
                 }
 
@@ -185,43 +168,38 @@ impl MacPlatform {
 
         menu_bar
     }
-
-    unsafe fn read_from_pasteboard(&self, kind: id) -> Option<&[u8]> {
-        let data = self.pasteboard.dataForType(kind);
-        if data == nil {
-            None
-        } else {
-            Some(slice::from_raw_parts(
-                data.bytes() as *mut u8,
-                data.length() as usize,
-            ))
-        }
-    }
 }
 
-impl platform::Platform for MacPlatform {
+impl platform::Lifecycle for MacLifecycle {
     fn on_become_active(&self, callback: Box<dyn FnMut()>) {
-        self.callbacks.borrow_mut().become_active = Some(callback);
+        self.0.borrow_mut().become_active = Some(callback);
     }
 
     fn on_resign_active(&self, callback: Box<dyn FnMut()>) {
-        self.callbacks.borrow_mut().resign_active = Some(callback);
+        self.0.borrow_mut().resign_active = Some(callback);
     }
 
     fn on_event(&self, callback: Box<dyn FnMut(crate::Event) -> bool>) {
-        self.callbacks.borrow_mut().event = Some(callback);
+        self.0.borrow_mut().event = Some(callback);
     }
 
     fn on_menu_command(&self, callback: Box<dyn FnMut(&str, Option<&dyn Any>)>) {
-        self.callbacks.borrow_mut().menu_command = Some(callback);
+        self.0.borrow_mut().menu_command = Some(callback);
     }
 
     fn on_open_files(&self, callback: Box<dyn FnMut(Vec<PathBuf>)>) {
-        self.callbacks.borrow_mut().open_files = Some(callback);
+        self.0.borrow_mut().open_files = Some(callback);
+    }
+
+    fn set_menus(&self, menus: Vec<Menu>) {
+        unsafe {
+            let app: id = msg_send![APP_CLASS, sharedApplication];
+            app.setMainMenu_(self.create_menu_bar(menus));
+        }
     }
 
     fn run(&self, on_finish_launching: Box<dyn FnOnce() -> ()>) {
-        self.callbacks.borrow_mut().finish_launching = Some(on_finish_launching);
+        self.0.borrow_mut().finish_launching = Some(on_finish_launching);
 
         unsafe {
             let app: id = msg_send![APP_CLASS, sharedApplication];
@@ -229,18 +207,52 @@ impl platform::Platform for MacPlatform {
             app.setDelegate_(app_delegate);
 
             let self_ptr = self as *const Self as *const c_void;
-            (*app).set_ivar(MAC_PLATFORM_IVAR, self_ptr);
-            (*app_delegate).set_ivar(MAC_PLATFORM_IVAR, self_ptr);
+            (*app).set_ivar(MAC_LIFECYCLE_IVAR, self_ptr);
+            (*app_delegate).set_ivar(MAC_LIFECYCLE_IVAR, self_ptr);
 
             let pool = NSAutoreleasePool::new(nil);
             app.run();
             pool.drain();
 
-            (*app).set_ivar(MAC_PLATFORM_IVAR, null_mut::<c_void>());
-            (*app.delegate()).set_ivar(MAC_PLATFORM_IVAR, null_mut::<c_void>());
+            (*app).set_ivar(MAC_LIFECYCLE_IVAR, null_mut::<c_void>());
+            (*app.delegate()).set_ivar(MAC_LIFECYCLE_IVAR, null_mut::<c_void>());
         }
     }
+}
 
+pub struct MacPlatform {
+    dispatcher: Arc<Dispatcher>,
+    fonts: Arc<FontSystem>,
+    pasteboard: id,
+    text_hash_pasteboard_type: id,
+    metadata_pasteboard_type: id,
+}
+
+impl MacPlatform {
+    pub fn new() -> Self {
+        Self {
+            dispatcher: Arc::new(Dispatcher),
+            fonts: Arc::new(FontSystem::new()),
+            pasteboard: unsafe { NSPasteboard::generalPasteboard(nil) },
+            text_hash_pasteboard_type: unsafe { ns_string("zed-text-hash") },
+            metadata_pasteboard_type: unsafe { ns_string("zed-metadata") },
+        }
+    }
+
+    unsafe fn read_from_pasteboard(&self, kind: id) -> Option<&[u8]> {
+        let data = self.pasteboard.dataForType(kind);
+        if data == nil {
+            None
+        } else {
+            Some(slice::from_raw_parts(
+                data.bytes() as *mut u8,
+                data.length() as usize,
+            ))
+        }
+    }
+}
+
+impl platform::Platform for MacPlatform {
     fn dispatcher(&self) -> Arc<dyn platform::Dispatcher> {
         self.dispatcher.clone()
     }
@@ -434,26 +446,19 @@ impl platform::Platform for MacPlatform {
             }
         }
     }
-
-    fn set_menus(&self, menus: Vec<Menu>) {
-        unsafe {
-            let app: id = msg_send![APP_CLASS, sharedApplication];
-            app.setMainMenu_(self.create_menu_bar(menus));
-        }
-    }
 }
 
-unsafe fn get_platform(object: &mut Object) -> &MacPlatform {
-    let platform_ptr: *mut c_void = *object.get_ivar(MAC_PLATFORM_IVAR);
+unsafe fn get_lifecycle(object: &mut Object) -> &MacLifecycle {
+    let platform_ptr: *mut c_void = *object.get_ivar(MAC_LIFECYCLE_IVAR);
     assert!(!platform_ptr.is_null());
-    &*(platform_ptr as *const MacPlatform)
+    &*(platform_ptr as *const MacLifecycle)
 }
 
 extern "C" fn send_event(this: &mut Object, _sel: Sel, native_event: id) {
     unsafe {
         if let Some(event) = Event::from_native(native_event, None) {
-            let platform = get_platform(this);
-            if let Some(callback) = platform.callbacks.borrow_mut().event.as_mut() {
+            let platform = get_lifecycle(this);
+            if let Some(callback) = platform.0.borrow_mut().event.as_mut() {
                 if callback(event) {
                     return;
                 }
@@ -469,23 +474,24 @@ extern "C" fn did_finish_launching(this: &mut Object, _: Sel, _: id) {
         let app: id = msg_send![APP_CLASS, sharedApplication];
         app.setActivationPolicy_(NSApplicationActivationPolicyRegular);
 
-        let platform = get_platform(this);
-        if let Some(callback) = platform.callbacks.borrow_mut().finish_launching.take() {
+        let platform = get_lifecycle(this);
+        let callback = platform.0.borrow_mut().finish_launching.take();
+        if let Some(callback) = callback {
             callback();
         }
     }
 }
 
 extern "C" fn did_become_active(this: &mut Object, _: Sel, _: id) {
-    let platform = unsafe { get_platform(this) };
-    if let Some(callback) = platform.callbacks.borrow_mut().become_active.as_mut() {
+    let platform = unsafe { get_lifecycle(this) };
+    if let Some(callback) = platform.0.borrow_mut().become_active.as_mut() {
         callback();
     }
 }
 
 extern "C" fn did_resign_active(this: &mut Object, _: Sel, _: id) {
-    let platform = unsafe { get_platform(this) };
-    if let Some(callback) = platform.callbacks.borrow_mut().resign_active.as_mut() {
+    let platform = unsafe { get_lifecycle(this) };
+    if let Some(callback) = platform.0.borrow_mut().resign_active.as_mut() {
         callback();
     }
 }
@@ -506,19 +512,19 @@ extern "C" fn open_files(this: &mut Object, _: Sel, _: id, paths: id) {
             })
             .collect::<Vec<_>>()
     };
-    let platform = unsafe { get_platform(this) };
-    if let Some(callback) = platform.callbacks.borrow_mut().open_files.as_mut() {
+    let platform = unsafe { get_lifecycle(this) };
+    if let Some(callback) = platform.0.borrow_mut().open_files.as_mut() {
         callback(paths);
     }
 }
 
 extern "C" fn handle_menu_item(this: &mut Object, _: Sel, item: id) {
     unsafe {
-        let platform = get_platform(this);
-        if let Some(callback) = platform.callbacks.borrow_mut().menu_command.as_mut() {
+        let platform = get_lifecycle(this);
+        if let Some(callback) = platform.0.borrow_mut().menu_command.as_mut() {
             let tag: NSInteger = msg_send![item, tag];
             let index = tag as usize;
-            if let Some((action, arg)) = platform.menu_item_actions.borrow().get(index) {
+            if let Some((action, arg)) = platform.0.borrow_mut().menu_actions.get(index) {
                 callback(action, arg.as_ref().map(Box::as_ref));
             }
         }

gpui/src/platform/test.rs 🔗

@@ -8,6 +8,8 @@ use std::{
     sync::Arc,
 };
 
+pub(crate) struct Lifecycle;
+
 pub(crate) struct Platform {
     dispatcher: Arc<dyn super::Dispatcher>,
     fonts: Arc<dyn super::FontSystem>,
@@ -27,6 +29,24 @@ pub struct Window {
     pub(crate) last_prompt: RefCell<Option<Box<dyn FnOnce(usize)>>>,
 }
 
+impl super::Lifecycle for Lifecycle {
+    fn on_menu_command(&self, _: Box<dyn FnMut(&str, Option<&dyn Any>)>) {}
+
+    fn on_become_active(&self, _: Box<dyn FnMut()>) {}
+
+    fn on_resign_active(&self, _: Box<dyn FnMut()>) {}
+
+    fn on_event(&self, _: Box<dyn FnMut(crate::Event) -> bool>) {}
+
+    fn on_open_files(&self, _: Box<dyn FnMut(Vec<std::path::PathBuf>)>) {}
+
+    fn set_menus(&self, _: Vec<crate::Menu>) {}
+
+    fn run(&self, _on_finish_launching: Box<dyn FnOnce() -> ()>) {
+        unimplemented!()
+    }
+}
+
 impl Platform {
     fn new() -> Self {
         Self {
@@ -54,20 +74,6 @@ impl Platform {
 }
 
 impl super::Platform for Platform {
-    fn on_menu_command(&self, _: Box<dyn FnMut(&str, Option<&dyn Any>)>) {}
-
-    fn on_become_active(&self, _: Box<dyn FnMut()>) {}
-
-    fn on_resign_active(&self, _: Box<dyn FnMut()>) {}
-
-    fn on_event(&self, _: Box<dyn FnMut(crate::Event) -> bool>) {}
-
-    fn on_open_files(&self, _: Box<dyn FnMut(Vec<std::path::PathBuf>)>) {}
-
-    fn run(&self, _on_finish_launching: Box<dyn FnOnce() -> ()>) {
-        unimplemented!()
-    }
-
     fn dispatcher(&self) -> Arc<dyn super::Dispatcher> {
         self.dispatcher.clone()
     }
@@ -91,8 +97,6 @@ impl super::Platform for Platform {
         None
     }
 
-    fn set_menus(&self, _menus: Vec<crate::Menu>) {}
-
     fn quit(&self) {}
 
     fn prompt_for_paths(
@@ -175,6 +179,10 @@ impl super::Window for Window {
     }
 }
 
+pub(crate) fn lifecycle() -> Lifecycle {
+    Lifecycle
+}
+
 pub(crate) fn platform() -> Platform {
     Platform::new()
 }