Implement fixpoint loop in run_rewrite_passes (#696)

This implements the last feature from the Pass Specification RFC 🎉
This commit is contained in:
Olivier FAURE 2024-10-20 19:12:18 +02:00 committed by GitHub
parent 40d1987161
commit f403337219
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 72 additions and 30 deletions

View File

@ -35,10 +35,9 @@ impl<'a> DriverCtx<'a> {
}
pub fn content_changed(&self) -> bool {
self.main_root_widget
.ctx
.widget_state
.needs_rewrite_passes()
|| self.main_root_widget.ctx.global_state.focus_changed()
let ctx = &self.main_root_widget.ctx;
ctx.widget_state.needs_rewrite_passes()
|| ctx.widget_state.needs_render()
|| ctx.global_state.focus_changed()
}
}

View File

@ -653,6 +653,8 @@ impl MasonryState<'_> {
tracing::warn!("Tried to handle a signal whilst suspended or before window created");
return;
};
let mut needs_redraw = false;
while let Some(signal) = self.render_root.pop_signal() {
match signal {
render_root::RenderRootSignal::Action(action, widget_id) => {
@ -674,11 +676,11 @@ impl MasonryState<'_> {
window.set_ime_cursor_area(position, size);
}
render_root::RenderRootSignal::RequestRedraw => {
window.request_redraw();
needs_redraw = true;
}
render_root::RenderRootSignal::RequestAnimFrame => {
// TODO
window.request_redraw();
needs_redraw = true;
}
render_root::RenderRootSignal::TakeFocus => {
window.focus_window();
@ -695,6 +697,12 @@ impl MasonryState<'_> {
}
}
}
// If we're processing a lot of actions, we may have a lot of pending redraws.
// We batch them up to avoid redundant requests.
if needs_redraw {
window.request_redraw();
}
}
pub fn get_window_state(&self) -> &WindowState {

View File

@ -75,6 +75,12 @@ fn compose_widget(
pub(crate) fn run_compose_pass(root: &mut RenderRoot) {
let _span = info_span!("compose").entered();
// If widgets are moved, pointer-related info may be stale.
// For instance, the "hovered" widget may have moved and no longer be under the pointer.
if root.root_state().needs_compose {
root.global_state.needs_pointer_pass = true;
}
let (root_widget, root_state) = root.widget_arena.get_pair_mut(root.root.id());
compose_widget(
&mut root.global_state,

View File

@ -83,7 +83,10 @@ pub(crate) fn run_on_pointer_event_pass(root: &mut RenderRoot, event: &PointerEv
debug!("Running ON_POINTER_EVENT pass with {}", event.short_name());
}
root.last_mouse_pos = event.position();
if event.position() != root.last_mouse_pos {
root.global_state.needs_pointer_pass = true;
root.last_mouse_pos = event.position();
}
let target_widget_id = get_target_widget(root, event.position());

View File

@ -211,6 +211,7 @@ pub(crate) fn run_layout_pass(root: &mut RenderRoot) {
}
let _span = info_span!("layout").entered();
root.global_state.needs_pointer_pass = true;
let window_size = root.get_kurbo_size();
let bc = match root.size_policy {

View File

@ -520,6 +520,11 @@ pub(crate) fn run_update_scroll_pass(root: &mut RenderRoot) {
// --- MARK: UPDATE POINTER ---
pub(crate) fn run_update_pointer_pass(root: &mut RenderRoot) {
if !root.global_state.needs_pointer_pass {
return;
}
root.global_state.needs_pointer_pass = false;
let pointer_pos = root.last_mouse_pos.map(|pos| (pos.x, pos.y).into());
// Release pointer capture if target can no longer hold it.

View File

@ -74,6 +74,8 @@ pub(crate) struct RenderRootState {
pub(crate) mutate_callbacks: Vec<MutateCallback>,
pub(crate) is_ime_active: bool,
pub(crate) scenes: HashMap<WidgetId, Scene>,
/// Whether data set in the pointer pass has been invalidated.
pub(crate) needs_pointer_pass: bool,
}
pub(crate) struct MutateCallback {
@ -161,6 +163,7 @@ impl RenderRoot {
mutate_callbacks: Vec::new(),
is_ime_active: false,
scenes: HashMap::new(),
needs_pointer_pass: false,
},
widget_arena: WidgetArena {
widgets: TreeArena::new(),
@ -428,23 +431,38 @@ impl RenderRoot {
///
/// See Pass Spec RFC for details. (TODO - Link to doc instead.)
pub(crate) fn run_rewrite_passes(&mut self) {
// TODO - Rerun passes if invalidation flags are still set
const REWRITE_PASSES_MAX: usize = 4;
// Note: this code doesn't do any short-circuiting, because each pass is
// expected to have its own early exits.
// Calling a run_xxx_pass (or root_xxx) should always be very fast if
// the pass doesn't need to do anything.
for _ in 0..REWRITE_PASSES_MAX {
// Note: this code doesn't do any short-circuiting, because each pass is
// expected to have its own early exits.
// Calling a run_xxx_pass (or root_xxx) should always be very fast if
// the pass doesn't need to do anything.
run_mutate_pass(self);
run_update_widget_tree_pass(self);
run_update_disabled_pass(self);
run_update_stashed_pass(self);
run_update_focus_chain_pass(self);
run_update_focus_pass(self);
run_layout_pass(self);
run_update_scroll_pass(self);
run_compose_pass(self);
run_update_pointer_pass(self);
run_mutate_pass(self);
run_update_widget_tree_pass(self);
run_update_disabled_pass(self);
run_update_stashed_pass(self);
run_update_focus_chain_pass(self);
run_update_focus_pass(self);
run_layout_pass(self);
run_update_scroll_pass(self);
run_compose_pass(self);
run_update_pointer_pass(self);
if !self.root_state().needs_rewrite_passes()
&& !self.global_state.needs_rewrite_passes()
{
break;
}
}
if self.root_state().needs_rewrite_passes() || self.global_state.needs_rewrite_passes() {
warn!("All rewrite passes have run {REWRITE_PASSES_MAX} times, but invalidations are still set");
// To avoid an infinite loop, we delay re-running the passes until the next frame.
self.global_state
.emit_signal(RenderRootSignal::RequestRedraw);
}
if self.root_state().needs_anim {
self.global_state
@ -455,10 +473,7 @@ impl RenderRoot {
// tree needs to be rebuilt. Usually both happen at the same time.
// A redraw will trigger a rebuild of the accessibility tree.
// TODO - We assume that a relayout will trigger a repaint
if self.root_state().needs_paint
|| self.root_state().needs_accessibility
|| self.root_state().needs_layout
{
if self.root_state().needs_paint || self.root_state().needs_accessibility {
self.global_state
.emit_signal(RenderRootSignal::RequestRedraw);
}
@ -556,6 +571,10 @@ impl RenderRootState {
pub(crate) fn is_focused(&self, id: WidgetId) -> bool {
self.focused_widget == Some(id)
}
pub(crate) fn needs_rewrite_passes(&self) -> bool {
self.needs_pointer_pass || self.focused_widget != self.next_focused_widget
}
}
impl RenderRootSignal {

View File

@ -293,10 +293,11 @@ impl WidgetState {
pub(crate) fn needs_rewrite_passes(&self) -> bool {
self.needs_layout
|| self.needs_compose
|| self.needs_paint
|| self.needs_accessibility
|| self.needs_anim
|| self.needs_update_disabled
|| self.needs_update_stashed
}
pub(crate) fn needs_render(&self) -> bool {
self.needs_anim || self.needs_paint || self.needs_accessibility
}
}