Checkpoint

Nathan Sobo created

Change summary

crates/gpui3/src/app.rs                         |  15 +
crates/gpui3/src/display_linker.rs              |  55 +++++++
crates/gpui3/src/gpui3.rs                       |   2 
crates/gpui3/src/platform.rs                    |  14 +
crates/gpui3/src/platform/mac.rs                |   4 
crates/gpui3/src/platform/mac/display.rs        |   5 
crates/gpui3/src/platform/mac/display_linker.rs | 148 +++++++++++++++---
crates/gpui3/src/platform/mac/platform.rs       |   9 
crates/gpui3/src/platform/test.rs               |   4 
crates/gpui3/src/window.rs                      |  20 +
10 files changed, 224 insertions(+), 52 deletions(-)

Detailed changes

crates/gpui3/src/app.rs 🔗

@@ -8,8 +8,8 @@ pub use model_context::*;
 use refineable::Refineable;
 
 use crate::{
-    current_platform, image_cache::ImageCache, AssetSource, Context, Executor, LayoutId,
-    MainThread, MainThreadOnly, Platform, RootView, SvgRenderer, Task, TextStyle,
+    current_platform, image_cache::ImageCache, AssetSource, Context, DisplayLinker, Executor,
+    LayoutId, MainThread, MainThreadOnly, Platform, RootView, SvgRenderer, Task, TextStyle,
     TextStyleRefinement, TextSystem, Window, WindowContext, WindowHandle, WindowId,
 };
 use anyhow::{anyhow, Result};
@@ -51,15 +51,15 @@ impl App {
         http_client: Arc<dyn HttpClient>,
     ) -> Self {
         let executor = platform.executor();
-        let text_system = Arc::new(TextSystem::new(platform.text_system()));
         let entities = EntityMap::new();
         let unit_entity = entities.insert(entities.reserve(), ());
         Self(Arc::new_cyclic(|this| {
             Mutex::new(AppContext {
                 this: this.clone(),
+                display_linker: Arc::new(DisplayLinker::new(platform.display_linker())),
+                text_system: Arc::new(TextSystem::new(platform.text_system())),
                 platform: MainThreadOnly::new(platform, executor.clone()),
                 executor,
-                text_system,
                 svg_renderer: SvgRenderer::new(asset_source),
                 image_cache: ImageCache::new(http_client),
                 pending_updates: 0,
@@ -97,6 +97,7 @@ pub struct AppContext {
     text_system: Arc<TextSystem>,
     pending_updates: usize,
     pub(crate) executor: Executor,
+    pub(crate) display_linker: Arc<DisplayLinker>,
     pub(crate) svg_renderer: SvgRenderer,
     pub(crate) image_cache: ImageCache,
     pub(crate) text_style_stack: Vec<TextStyleRefinement>,
@@ -359,9 +360,15 @@ impl MainThread<AppContext> {
             let id = cx.windows.insert(None);
             let handle = WindowHandle::new(id);
             let mut window = Window::new(handle.into(), options, cx);
+            let display_id = window.display_id;
             let root_view = build_root_view(&mut WindowContext::mutable(cx, &mut window));
             window.root_view.replace(root_view.into_any());
             cx.windows.get_mut(id).unwrap().replace(window);
+
+            cx.display_linker.on_next_frame(display_id, |_, _| {
+                dbg!("next frame");
+            });
+
             handle
         })
     }

crates/gpui3/src/display_linker.rs 🔗

@@ -0,0 +1,55 @@
+use crate::{DisplayId, PlatformDisplayLinker, VideoTimestamp};
+use collections::HashMap;
+use parking_lot::Mutex;
+use std::sync::Arc;
+
+type FrameCallback = Box<dyn FnOnce(&VideoTimestamp, &VideoTimestamp) + Send>;
+
+pub struct DisplayLinker {
+    platform_linker: Arc<dyn PlatformDisplayLinker>,
+    next_frame_callbacks: Arc<Mutex<HashMap<DisplayId, Vec<FrameCallback>>>>,
+}
+
+impl DisplayLinker {
+    pub(crate) fn new(platform_linker: Arc<dyn PlatformDisplayLinker>) -> Self {
+        Self {
+            platform_linker,
+            next_frame_callbacks: Default::default(),
+        }
+    }
+
+    pub(crate) fn on_next_frame(
+        &self,
+        display_id: DisplayId,
+        callback: impl FnOnce(&VideoTimestamp, &VideoTimestamp) + Send + 'static,
+    ) {
+        let next_frame_callbacks = self.next_frame_callbacks.clone();
+        let callback = Box::new(callback);
+        match self.next_frame_callbacks.lock().entry(display_id) {
+            collections::hash_map::Entry::Occupied(mut entry) => {
+                if entry.get().is_empty() {
+                    self.platform_linker.start(display_id)
+                }
+                entry.get_mut().push(callback)
+            }
+            collections::hash_map::Entry::Vacant(entry) => {
+                // let platform_linker = self.platform_linker.clone();
+                self.platform_linker.set_output_callback(
+                    display_id,
+                    Box::new(move |current_time, output_time| {
+                        for callback in next_frame_callbacks
+                            .lock()
+                            .get_mut(&display_id)
+                            .unwrap()
+                            .drain(..)
+                        {
+                            callback(current_time, output_time);
+                        }
+                        // platform_linker.stop(display_id);
+                    }),
+                );
+                entry.insert(vec![callback]);
+            }
+        }
+    }
+}

crates/gpui3/src/gpui3.rs 🔗

@@ -1,6 +1,7 @@
 mod app;
 mod assets;
 mod color;
+mod display_linker;
 mod element;
 mod elements;
 mod executor;
@@ -22,6 +23,7 @@ pub use anyhow::Result;
 pub use app::*;
 pub use assets::*;
 pub use color::*;
+pub use display_linker::*;
 pub use element::*;
 pub use elements::*;
 pub use executor::*;

crates/gpui3/src/platform.rs 🔗

@@ -42,6 +42,7 @@ pub(crate) fn current_platform() -> Arc<dyn Platform> {
 
 pub trait Platform: 'static {
     fn executor(&self) -> Executor;
+    fn display_linker(&self) -> Arc<dyn PlatformDisplayLinker>;
     fn text_system(&self) -> Arc<dyn PlatformTextSystem>;
 
     fn run(&self, on_finish_launching: Box<dyn 'static + FnOnce()>);
@@ -99,7 +100,6 @@ pub trait PlatformDisplay: Debug {
     fn id(&self) -> DisplayId;
     fn as_any(&self) -> &dyn Any;
     fn bounds(&self) -> Bounds<GlobalPixels>;
-    fn link(&self) -> Box<dyn PlatformDisplayLink>;
 }
 
 #[derive(PartialEq, Eq, Hash, Copy, Clone)]
@@ -156,10 +156,14 @@ pub trait PlatformDispatcher: Send + Sync {
     fn dispatch_on_main_thread(&self, task: Runnable);
 }
 
-pub trait PlatformDisplayLink {
-    // fn set_output_callback(&mut self, callback: Box<dyn FnMut(&VideoTimestamp, &VideoTimestamp)>);
-    fn start(&mut self);
-    fn stop(&mut self);
+pub trait PlatformDisplayLinker: Send + Sync {
+    fn set_output_callback(
+        &self,
+        display_id: DisplayId,
+        callback: Box<dyn FnMut(&VideoTimestamp, &VideoTimestamp)>,
+    );
+    fn start(&self, display_id: DisplayId);
+    fn stop(&self, display_id: DisplayId);
 }
 
 pub trait PlatformTextSystem: Send + Sync {

crates/gpui3/src/platform/mac.rs 🔗

@@ -2,7 +2,7 @@
 ///! an origin at the bottom left of the main display.
 mod dispatcher;
 mod display;
-// mod display_link;
+mod display_linker;
 mod events;
 mod metal_atlas;
 mod metal_renderer;
@@ -33,7 +33,7 @@ use std::{
 
 pub use dispatcher::*;
 pub use display::*;
-// pub use display_link::*;
+pub use display_linker::*;
 pub use metal_atlas::*;
 pub use platform::*;
 pub use text_system::*;

crates/gpui3/src/platform/mac/display.rs 🔗

@@ -98,9 +98,4 @@ impl PlatformDisplay for MacDisplay {
             display_bounds_from_native(native_bounds)
         }
     }
-
-    fn link(&self) -> Box<dyn crate::PlatformDisplayLink> {
-        unimplemented!()
-        // Box::new(unsafe { MacDisplayLink::new(self.0) })
-    }
 }

crates/gpui3/src/platform/mac/display_link.rs → crates/gpui3/src/platform/mac/display_linker.rs 🔗

@@ -1,41 +1,77 @@
-use crate::PlatformDisplayLink;
 use std::ffi::c_void;
 
+use crate::{DisplayId, PlatformDisplayLinker};
+use collections::HashMap;
+use parking_lot::Mutex;
 pub use sys::CVTimeStamp as VideoTimestamp;
 
-pub struct MacDisplayLink {
-    sys_link: sys::DisplayLink,
-    output_callback: Option<Box<dyn FnMut(&VideoTimestamp, &VideoTimestamp)>>,
+pub struct MacDisplayLinker {
+    links: Mutex<HashMap<DisplayId, MacDisplayLink>>,
 }
 
-impl MacDisplayLink {
-    pub unsafe fn new(display_id: u32) -> Self {
-        Self {
-            sys_link: sys::DisplayLink::on_display(display_id).unwrap(),
-            output_callback: None,
+struct MacDisplayLink {
+    output_callback: Box<dyn FnMut(&VideoTimestamp, &VideoTimestamp)>,
+    system_link: sys::DisplayLink,
+}
+
+unsafe impl Send for MacDisplayLink {}
+
+impl MacDisplayLinker {
+    pub fn new() -> Self {
+        MacDisplayLinker {
+            links: Default::default(),
         }
     }
 }
 
-impl PlatformDisplayLink for MacDisplayLink {
-    fn set_output_callback(&mut self, callback: Box<dyn FnMut(&VideoTimestamp, &VideoTimestamp)>) {
-        unsafe {
-            self.sys_link.set_output_callback(
-                trampoline,
-                self.output_callback.as_mut().unwrap()
-                    as *mut dyn FnMut(&VideoTimestamp, &VideoTimestamp)
-                    as *mut c_void,
+impl PlatformDisplayLinker for MacDisplayLinker {
+    fn set_output_callback(
+        &self,
+        display_id: DisplayId,
+        mut output_callback: Box<dyn FnMut(&VideoTimestamp, &VideoTimestamp)>,
+    ) {
+        if let Some(mut system_link) = unsafe { sys::DisplayLink::on_display(display_id.0) } {
+            unsafe {
+                system_link.set_output_callback(
+                    trampoline,
+                    output_callback.as_mut() as *mut dyn FnMut(_, _) as *mut c_void,
+                )
+            }
+
+            let previous = self.links.lock().insert(
+                display_id,
+                MacDisplayLink {
+                    output_callback,
+                    system_link,
+                },
             );
+            assert!(
+                previous.is_none(),
+                "You can currently only set an output callback once per display."
+            )
+        } else {
+            return log::warn!("DisplayLink could not be obtained for {:?}", display_id);
         }
-        self.output_callback = Some(callback);
     }
 
-    fn start(&mut self) {
-        unsafe { self.sys_link.start() }
+    fn start(&self, display_id: DisplayId) {
+        if let Some(link) = self.links.lock().get_mut(&display_id) {
+            unsafe {
+                link.system_link.start();
+            }
+        } else {
+            log::warn!("No DisplayLink callback registered for {:?}", display_id)
+        }
     }
 
-    fn stop(&mut self) {
-        unsafe { self.sys_link.stop() }
+    fn stop(&self, display_id: DisplayId) {
+        if let Some(link) = self.links.lock().get_mut(&display_id) {
+            unsafe {
+                link.system_link.stop();
+            }
+        } else {
+            log::warn!("No DisplayLink callback registered for {:?}", display_id)
+        }
     }
 }
 
@@ -48,11 +84,8 @@ unsafe extern "C" fn trampoline(
     context: *mut c_void,
 ) -> i32 {
     let output_callback = &mut (*(context as *mut MacDisplayLink)).output_callback;
-    if let Some(callback) = output_callback {
-        if let Some((current_time, output_time)) = current_time.as_ref().zip(output_time.as_ref()) {
-            // convert sys::CVTimeStamp to VideoTimestamp
-            callback(&current_time, &output_time);
-        }
+    if let Some((current_time, output_time)) = current_time.as_ref().zip(output_time.as_ref()) {
+        output_callback(&current_time, &output_time);
     }
     0
 }
@@ -61,9 +94,8 @@ mod sys {
     //! Derived from display-link crate under the fololwing license:
     //! https://github.com/BrainiumLLC/display-link/blob/master/LICENSE-MIT
     //! Apple docs: [CVDisplayLink](https://developer.apple.com/documentation/corevideo/cvdisplaylinkoutputcallback?language=objc)
-    #![allow(dead_code)]
+    #![allow(dead_code, non_upper_case_globals)]
 
-    pub use cocoa::quartzcore::CVTimeStamp;
     use foreign_types::{foreign_type, ForeignType};
     use std::{
         ffi::c_void,
@@ -90,6 +122,64 @@ mod sys {
         }
     }
 
+    #[repr(C)]
+    #[derive(Clone, Copy)]
+    pub struct CVTimeStamp {
+        pub version: u32,
+        pub video_time_scale: i32,
+        pub video_time: i64,
+        pub host_time: u64,
+        pub rate_scalar: f64,
+        pub video_refresh_period: i64,
+        pub smpte_time: CVSMPTETime,
+        pub flags: u64,
+        pub reserved: u64,
+    }
+
+    pub type CVTimeStampFlags = u64;
+
+    pub const kCVTimeStampVideoTimeValid: CVTimeStampFlags = 1 << 0;
+    pub const kCVTimeStampHostTimeValid: CVTimeStampFlags = 1 << 1;
+    pub const kCVTimeStampSMPTETimeValid: CVTimeStampFlags = 1 << 2;
+    pub const kCVTimeStampVideoRefreshPeriodValid: CVTimeStampFlags = 1 << 3;
+    pub const kCVTimeStampRateScalarValid: CVTimeStampFlags = 1 << 4;
+    pub const kCVTimeStampTopField: CVTimeStampFlags = 1 << 16;
+    pub const kCVTimeStampBottomField: CVTimeStampFlags = 1 << 17;
+    pub const kCVTimeStampVideoHostTimeValid: CVTimeStampFlags =
+        kCVTimeStampVideoTimeValid | kCVTimeStampHostTimeValid;
+    pub const kCVTimeStampIsInterlaced: CVTimeStampFlags =
+        kCVTimeStampTopField | kCVTimeStampBottomField;
+
+    #[repr(C)]
+    #[derive(Clone, Copy)]
+    pub struct CVSMPTETime {
+        pub subframes: i16,
+        pub subframe_divisor: i16,
+        pub counter: u32,
+        pub time_type: u32,
+        pub flags: u32,
+        pub hours: i16,
+        pub minutes: i16,
+        pub seconds: i16,
+        pub frames: i16,
+    }
+
+    pub type CVSMPTETimeType = u32;
+
+    pub const kCVSMPTETimeType24: CVSMPTETimeType = 0;
+    pub const kCVSMPTETimeType25: CVSMPTETimeType = 1;
+    pub const kCVSMPTETimeType30Drop: CVSMPTETimeType = 2;
+    pub const kCVSMPTETimeType30: CVSMPTETimeType = 3;
+    pub const kCVSMPTETimeType2997: CVSMPTETimeType = 4;
+    pub const kCVSMPTETimeType2997Drop: CVSMPTETimeType = 5;
+    pub const kCVSMPTETimeType60: CVSMPTETimeType = 6;
+    pub const kCVSMPTETimeType5994: CVSMPTETimeType = 7;
+
+    pub type CVSMPTETimeFlags = u32;
+
+    pub const kCVSMPTETimeValid: CVSMPTETimeFlags = 1 << 0;
+    pub const kCVSMPTETimeRunning: CVSMPTETimeFlags = 1 << 1;
+
     pub type CVDisplayLinkOutputCallback = unsafe extern "C" fn(
         display_link_out: *mut CVDisplayLink,
         // A pointer to the current timestamp. This represents the timestamp when the callback is called.

crates/gpui3/src/platform/mac/platform.rs 🔗

@@ -1,8 +1,9 @@
 use super::BoolExt;
 use crate::{
     AnyWindowHandle, ClipboardItem, CursorStyle, DisplayId, Event, Executor, MacDispatcher,
-    MacDisplay, MacTextSystem, MacWindow, PathPromptOptions, Platform, PlatformDisplay,
-    PlatformTextSystem, PlatformWindow, Result, SemanticVersion, WindowOptions,
+    MacDisplay, MacDisplayLinker, MacTextSystem, MacWindow, PathPromptOptions, Platform,
+    PlatformDisplay, PlatformDisplayLinker, PlatformTextSystem, PlatformWindow, Result,
+    SemanticVersion, WindowOptions,
 };
 use anyhow::anyhow;
 use block::ConcreteBlock;
@@ -347,6 +348,10 @@ impl Platform for MacPlatform {
         self.0.lock().executor.clone()
     }
 
+    fn display_linker(&self) -> Arc<dyn PlatformDisplayLinker> {
+        Arc::new(MacDisplayLinker::new())
+    }
+
     fn text_system(&self) -> Arc<dyn PlatformTextSystem> {
         self.0.lock().text_system.clone()
     }

crates/gpui3/src/platform/test.rs 🔗

@@ -15,6 +15,10 @@ impl Platform for TestPlatform {
         unimplemented!()
     }
 
+    fn display_linker(&self) -> std::sync::Arc<dyn crate::PlatformDisplayLinker> {
+        unimplemented!()
+    }
+
     fn text_system(&self) -> std::sync::Arc<dyn crate::PlatformTextSystem> {
         unimplemented!()
     }

crates/gpui3/src/window.rs 🔗

@@ -1,10 +1,11 @@
 use crate::{
     image_cache::RenderImageParams, px, AnyView, AppContext, AsyncWindowContext, AvailableSpace,
-    BorrowAppContext, Bounds, Context, Corners, DevicePixels, Effect, Element, EntityId, FontId,
-    GlyphId, Handle, Hsla, ImageData, IsZero, LayerId, LayoutId, MainThread, MainThreadOnly,
-    MonochromeSprite, Pixels, PlatformAtlas, PlatformWindow, Point, PolychromeSprite, Reference,
-    RenderGlyphParams, RenderSvgParams, ScaledPixels, Scene, SharedString, Size, Style,
-    TaffyLayoutEngine, Task, WeakHandle, WindowOptions, SUBPIXEL_VARIANTS,
+    BorrowAppContext, Bounds, Context, Corners, DevicePixels, DisplayId, Effect, Element, EntityId,
+    FontId, GlyphId, Handle, Hsla, ImageData, IsZero, LayerId, LayoutId, MainThread,
+    MainThreadOnly, MonochromeSprite, Pixels, PlatformAtlas, PlatformWindow, Point,
+    PolychromeSprite, Reference, RenderGlyphParams, RenderSvgParams, ScaledPixels, Scene,
+    SharedString, Size, Style, TaffyLayoutEngine, Task, WeakHandle, WindowOptions,
+    SUBPIXEL_VARIANTS,
 };
 use anyhow::Result;
 use smallvec::SmallVec;
@@ -16,6 +17,7 @@ pub struct AnyWindow {}
 pub struct Window {
     handle: AnyWindowHandle,
     platform_window: MainThreadOnly<Box<dyn PlatformWindow>>,
+    pub(crate) display_id: DisplayId, // todo!("make private again?")
     sprite_atlas: Arc<dyn PlatformAtlas>,
     rem_size: Pixels,
     content_size: Size<Pixels>,
@@ -35,6 +37,7 @@ impl Window {
         cx: &mut MainThread<AppContext>,
     ) -> Self {
         let platform_window = cx.platform().open_window(handle, options);
+        let display_id = platform_window.display().id();
         let sprite_atlas = platform_window.sprite_atlas();
         let mouse_position = platform_window.mouse_position();
         let content_size = platform_window.content_size();
@@ -46,6 +49,12 @@ impl Window {
                 cx.update_window(handle, |cx| {
                     cx.window.scene = Scene::new(scale_factor);
                     cx.window.content_size = content_size;
+                    cx.window.display_id = cx
+                        .window
+                        .platform_window
+                        .borrow_on_main_thread()
+                        .display()
+                        .id();
                     cx.window.dirty = true;
                 })
                 .log_err();
@@ -57,6 +66,7 @@ impl Window {
         Window {
             handle,
             platform_window,
+            display_id,
             sprite_atlas,
             rem_size: px(16.),
             content_size,