Draw only once on next frame callbacks

Antonio Scandurra created

Change summary

crates/gpui3/src/app.rs                         | 16 +++--
crates/gpui3/src/display_linker.rs              | 56 -------------------
crates/gpui3/src/elements/img.rs                |  4 -
crates/gpui3/src/gpui3.rs                       |  2 
crates/gpui3/src/platform/mac/metal_renderer.rs |  2 
crates/gpui3/src/window.rs                      | 45 ++++++++++++--
6 files changed, 47 insertions(+), 78 deletions(-)

Detailed changes

crates/gpui3/src/app.rs 🔗

@@ -8,9 +8,9 @@ pub use model_context::*;
 use refineable::Refineable;
 
 use crate::{
-    current_platform, image_cache::ImageCache, AssetSource, Context, DisplayLinker, Executor,
-    LayoutId, MainThread, MainThreadOnly, Platform, RootView, SvgRenderer, Task, TextStyle,
-    TextStyleRefinement, TextSystem, Window, WindowContext, WindowHandle, WindowId,
+    current_platform, image_cache::ImageCache, AssetSource, Context, DisplayId, Executor, LayoutId,
+    MainThread, MainThreadOnly, Platform, PlatformDisplayLinker, RootView, SvgRenderer, Task,
+    TextStyle, TextStyleRefinement, TextSystem, Window, WindowContext, WindowHandle, WindowId,
 };
 use anyhow::{anyhow, Result};
 use collections::{HashMap, VecDeque};
@@ -56,13 +56,14 @@ impl App {
         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())),
+                pending_updates: 0,
+                display_linker: platform.display_linker(),
+                next_frame_callbacks: Default::default(),
                 platform: MainThreadOnly::new(platform, executor.clone()),
                 executor,
                 svg_renderer: SvgRenderer::new(asset_source),
                 image_cache: ImageCache::new(http_client),
-                pending_updates: 0,
                 text_style_stack: Vec::new(),
                 state_stacks_by_type: HashMap::default(),
                 unit_entity,
@@ -90,14 +91,16 @@ impl App {
 }
 
 type Handlers = SmallVec<[Arc<dyn Fn(&mut AppContext) -> bool + Send + Sync + 'static>; 2]>;
+type FrameCallback = Box<dyn FnOnce(&mut WindowContext) + Send>;
 
 pub struct AppContext {
     this: Weak<Mutex<AppContext>>,
     platform: MainThreadOnly<dyn Platform>,
     text_system: Arc<TextSystem>,
     pending_updates: usize,
+    pub(crate) display_linker: Arc<dyn PlatformDisplayLinker>,
+    pub(crate) next_frame_callbacks: HashMap<DisplayId, Vec<FrameCallback>>,
     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>,
@@ -146,7 +149,6 @@ impl AppContext {
     }
 
     fn flush_effects(&mut self) {
-        dbg!("flush effects");
         while let Some(effect) = self.pending_effects.pop_front() {
             match effect {
                 Effect::Notify(entity_id) => self.apply_notify_effect(entity_id),

crates/gpui3/src/display_linker.rs 🔗

@@ -1,56 +0,0 @@
-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);
-                    }),
-                );
-                self.platform_linker.start(display_id);
-                entry.insert(vec![callback]);
-            }
-        }
-    }
-}

crates/gpui3/src/elements/img.rs 🔗

@@ -77,9 +77,7 @@ impl<S: Send + Sync + 'static> Element for Img<S> {
             } else {
                 cx.spawn(|_, mut cx| async move {
                     if image_future.await.log_err().is_some() {
-                        cx.on_next_frame(|cx| {
-                            cx.notify();
-                        });
+                        cx.on_next_frame(|cx| cx.notify());
                     }
                 })
                 .detach()

crates/gpui3/src/gpui3.rs 🔗

@@ -1,7 +1,6 @@
 mod app;
 mod assets;
 mod color;
-mod display_linker;
 mod element;
 mod elements;
 mod executor;
@@ -23,7 +22,6 @@ 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/mac/metal_renderer.rs 🔗

@@ -131,8 +131,6 @@ impl MetalRenderer {
     }
 
     pub fn draw(&mut self, scene: &mut Scene) {
-        dbg!("draw scene");
-
         let layer = self.layer.clone();
         let viewport_size = layer.drawable_size();
         let viewport_size: Size<DevicePixels> = size(

crates/gpui3/src/window.rs 🔗

@@ -144,11 +144,42 @@ impl<'a, 'w> WindowContext<'a, 'w> {
     }
 
     pub fn on_next_frame(&mut self, f: impl FnOnce(&mut WindowContext) + Send + 'static) {
-        let cx = self.to_async();
+        let f = Box::new(f);
         let display_id = self.window.display_id;
-        self.display_linker.on_next_frame(display_id, move |_, _| {
-            cx.update(f).ok();
-        });
+        let async_cx = self.to_async();
+        let app_cx = self.app_mut();
+        match app_cx.next_frame_callbacks.entry(display_id) {
+            collections::hash_map::Entry::Occupied(mut entry) => {
+                if entry.get().is_empty() {
+                    app_cx.display_linker.start(display_id);
+                }
+                entry.get_mut().push(f);
+            }
+            collections::hash_map::Entry::Vacant(entry) => {
+                app_cx.display_linker.set_output_callback(
+                    display_id,
+                    Box::new(move |_current_time, _output_time| {
+                        let _ = async_cx.update(|cx| {
+                            let callbacks = cx
+                                .next_frame_callbacks
+                                .get_mut(&display_id)
+                                .unwrap()
+                                .drain(..)
+                                .collect::<Vec<_>>();
+                            for callback in callbacks {
+                                callback(cx);
+                            }
+
+                            if cx.next_frame_callbacks.get(&display_id).unwrap().is_empty() {
+                                cx.display_linker.stop(display_id);
+                            }
+                        });
+                    }),
+                );
+                app_cx.display_linker.start(display_id);
+                entry.insert(vec![f]);
+            }
+        }
     }
 
     pub fn spawn<Fut, R>(
@@ -590,11 +621,9 @@ impl<'a, 'w, S: Send + Sync + 'static> ViewContext<'a, 'w, S> {
     }
 
     pub fn on_next_frame(&mut self, f: impl FnOnce(&mut S, &mut ViewContext<S>) + Send + 'static) {
-        let mut cx = self.to_async();
         let entity = self.handle();
-        let display_id = self.window.display_id;
-        self.display_linker.on_next_frame(display_id, move |_, _| {
-            entity.update(&mut cx, f).ok();
+        self.window_cx.on_next_frame(move |cx| {
+            entity.update(cx, f).ok();
         });
     }