Coalesce followed view updates only within one frame

Co-Authored-By: Nathan Sobo <nathan@zed.dev>
This commit is contained in:
Max Brunsfeld 2022-03-22 17:03:24 -07:00
parent 4435d9b106
commit 880eaa268b
2 changed files with 115 additions and 27 deletions

View file

@ -1224,7 +1224,17 @@ impl MutableAppContext {
} }
fn defer(&mut self, callback: Box<dyn FnOnce(&mut MutableAppContext)>) { fn defer(&mut self, callback: Box<dyn FnOnce(&mut MutableAppContext)>) {
self.pending_effects.push_back(Effect::Deferred(callback)) self.pending_effects.push_back(Effect::Deferred {
callback,
after_window_update: false,
})
}
pub fn after_window_update(&mut self, callback: impl 'static + FnOnce(&mut MutableAppContext)) {
self.pending_effects.push_back(Effect::Deferred {
callback: Box::new(callback),
after_window_update: true,
})
} }
pub(crate) fn notify_model(&mut self, model_id: usize) { pub(crate) fn notify_model(&mut self, model_id: usize) {
@ -1640,6 +1650,7 @@ impl MutableAppContext {
fn flush_effects(&mut self) { fn flush_effects(&mut self) {
self.pending_flushes = self.pending_flushes.saturating_sub(1); self.pending_flushes = self.pending_flushes.saturating_sub(1);
let mut after_window_update_callbacks = Vec::new();
if !self.flushing_effects && self.pending_flushes == 0 { if !self.flushing_effects && self.pending_flushes == 0 {
self.flushing_effects = true; self.flushing_effects = true;
@ -1675,7 +1686,16 @@ impl MutableAppContext {
Effect::ViewNotification { window_id, view_id } => { Effect::ViewNotification { window_id, view_id } => {
self.notify_view_observers(window_id, view_id) self.notify_view_observers(window_id, view_id)
} }
Effect::Deferred(callback) => callback(self), Effect::Deferred {
callback,
after_window_update,
} => {
if after_window_update {
after_window_update_callbacks.push(callback);
} else {
callback(self)
}
}
Effect::ModelRelease { model_id, model } => { Effect::ModelRelease { model_id, model } => {
self.notify_release_observers(model_id, model.as_any()) self.notify_release_observers(model_id, model.as_any())
} }
@ -1706,14 +1726,20 @@ impl MutableAppContext {
self.update_windows(); self.update_windows();
} }
if self.pending_effects.is_empty() {
for callback in after_window_update_callbacks.drain(..) {
callback(self);
}
if self.pending_effects.is_empty() { if self.pending_effects.is_empty() {
self.flushing_effects = false; self.flushing_effects = false;
self.pending_notifications.clear(); self.pending_notifications.clear();
break; break;
} else {
refreshing = false;
} }
} }
refreshing = false;
}
} }
} }
} }
@ -2347,7 +2373,10 @@ pub enum Effect {
window_id: usize, window_id: usize,
view_id: usize, view_id: usize,
}, },
Deferred(Box<dyn FnOnce(&mut MutableAppContext)>), Deferred {
callback: Box<dyn FnOnce(&mut MutableAppContext)>,
after_window_update: bool,
},
ModelRelease { ModelRelease {
model_id: usize, model_id: usize,
model: Box<dyn AnyModel>, model: Box<dyn AnyModel>,
@ -2413,7 +2442,7 @@ impl Debug for Effect {
.field("window_id", window_id) .field("window_id", window_id)
.field("view_id", view_id) .field("view_id", view_id)
.finish(), .finish(),
Effect::Deferred(_) => f.debug_struct("Effect::Deferred").finish(), Effect::Deferred { .. } => f.debug_struct("Effect::Deferred").finish(),
Effect::ModelRelease { model_id, .. } => f Effect::ModelRelease { model_id, .. } => f
.debug_struct("Effect::ModelRelease") .debug_struct("Effect::ModelRelease")
.field("model_id", model_id) .field("model_id", model_id)
@ -2945,6 +2974,18 @@ impl<'a, T: View> ViewContext<'a, T> {
})) }))
} }
pub fn after_window_update(
&mut self,
callback: impl 'static + FnOnce(&mut T, &mut ViewContext<T>),
) {
let handle = self.handle();
self.app.after_window_update(move |cx| {
handle.update(cx, |view, cx| {
callback(view, cx);
})
})
}
pub fn propagate_action(&mut self) { pub fn propagate_action(&mut self) {
self.app.halt_action_dispatch = false; self.app.halt_action_dispatch = false;
} }
@ -4424,7 +4465,7 @@ mod tests {
use smol::future::poll_once; use smol::future::poll_once;
use std::{ use std::{
cell::Cell, cell::Cell,
sync::atomic::{AtomicUsize, Ordering::SeqCst}, sync::atomic::{AtomicBool, AtomicUsize, Ordering::SeqCst},
}; };
#[crate::test(self)] #[crate::test(self)]
@ -4622,6 +4663,58 @@ mod tests {
assert_eq!(*events.borrow(), ["before notify"]); assert_eq!(*events.borrow(), ["before notify"]);
} }
#[crate::test(self)]
fn test_defer_and_after_window_update(cx: &mut MutableAppContext) {
struct View {
render_count: usize,
}
impl Entity for View {
type Event = usize;
}
impl super::View for View {
fn render(&mut self, _: &mut RenderContext<Self>) -> ElementBox {
post_inc(&mut self.render_count);
Empty::new().boxed()
}
fn ui_name() -> &'static str {
"View"
}
}
let (_, view) = cx.add_window(Default::default(), |_| View { render_count: 0 });
let called_defer = Rc::new(AtomicBool::new(false));
let called_after_window_update = Rc::new(AtomicBool::new(false));
view.update(cx, |this, cx| {
assert_eq!(this.render_count, 1);
cx.defer({
let called_defer = called_defer.clone();
move |this, _| {
assert_eq!(this.render_count, 1);
called_defer.store(true, SeqCst);
}
});
cx.after_window_update({
let called_after_window_update = called_after_window_update.clone();
move |this, cx| {
assert_eq!(this.render_count, 2);
called_after_window_update.store(true, SeqCst);
cx.notify();
}
});
assert!(!called_defer.load(SeqCst));
assert!(!called_after_window_update.load(SeqCst));
cx.notify();
});
assert!(called_defer.load(SeqCst));
assert!(called_after_window_update.load(SeqCst));
assert_eq!(view.read(cx).render_count, 3);
}
#[crate::test(self)] #[crate::test(self)]
fn test_view_handles(cx: &mut MutableAppContext) { fn test_view_handles(cx: &mut MutableAppContext) {
struct View { struct View {

View file

@ -458,26 +458,21 @@ impl<T: Item> ItemHandle for ViewHandle<T> {
&& !pending_update_scheduled.load(SeqCst) && !pending_update_scheduled.load(SeqCst)
{ {
pending_update_scheduled.store(true, SeqCst); pending_update_scheduled.store(true, SeqCst);
cx.spawn({ cx.after_window_update({
let pending_update = pending_update.clone(); let pending_update = pending_update.clone();
let pending_update_scheduled = pending_update_scheduled.clone(); let pending_update_scheduled = pending_update_scheduled.clone();
move |this, mut cx| async move { move |this, cx| {
this.update(&mut cx, |this, cx| {
pending_update_scheduled.store(false, SeqCst); pending_update_scheduled.store(false, SeqCst);
this.update_followers( this.update_followers(
proto::update_followers::Variant::UpdateView( proto::update_followers::Variant::UpdateView(proto::UpdateView {
proto::UpdateView {
id: item.id() as u64, id: item.id() as u64,
variant: pending_update.borrow_mut().take(), variant: pending_update.borrow_mut().take(),
leader_id: leader_id.map(|id| id.0), leader_id: leader_id.map(|id| id.0),
}, }),
),
cx, cx,
); );
});
} }
}) });
.detach();
} }
} }