Fix collective resize waits and fullscreen ownership
This commit is contained in:
1 parent
d5a829eb16
commit
a2f5a7ffba
2 files changed
+193
-10
No files matched your search
@@ -993,4 +993,54 @@ fn resize_dependencies_protocol_and_pixels() {
|
|||||||
"stacked replacement lost canonical presented source"
|
"stacked replacement lost canonical presented source"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
// Fullscreen geometry is output-owned. Keep its own configure barrier,
|
||||||
|
// but release it independently of a slow sibling in the same column.
|
||||||
|
state.set_window_fullscreen(d_id, true);
|
||||||
|
state.display_handle.flush_clients().unwrap();
|
||||||
|
ack_configure(&mut d_wire, 3);
|
||||||
|
dispatch(&mut events, &mut state);
|
||||||
|
assert!(
|
||||||
|
state.windows.transaction(&d_id).is_some(),
|
||||||
|
"fullscreen ack alone released its barrier"
|
||||||
|
);
|
||||||
|
let before = state.windows.geometry(&d_id).unwrap().visual.current;
|
||||||
|
state.advance_animations_at(Duration::from_millis(16), Duration::from_millis(10016));
|
||||||
|
assert_eq!(state.windows.geometry(&d_id).unwrap().visual.current, before);
|
||||||
|
|
||||||
|
request(&mut d_wire, 2, 1, &[6, 0, 0], None);
|
||||||
|
request(&mut d_wire, 2, 6, &[], None);
|
||||||
|
dispatch(&mut events, &mut state);
|
||||||
|
assert!(
|
||||||
|
state.windows.transaction(&d_id).is_none(),
|
||||||
|
"own fullscreen configure did not release after commit"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
state.windows.transaction(&b_id).is_some(),
|
||||||
|
"other tile must still be pending"
|
||||||
|
);
|
||||||
|
assert!(!state.presentation_dependencies.reflow_blocked(d_id, &state.windows));
|
||||||
|
assert!(state.windows.geometry(&d_id).unwrap().is_zooming());
|
||||||
|
// Consume the release tick without charging the preceding client wait.
|
||||||
|
state.advance_animations_at(Duration::ZERO, Duration::from_millis(10016));
|
||||||
|
state.advance_animations_at(Duration::from_millis(16), Duration::from_millis(10032));
|
||||||
|
assert_ne!(
|
||||||
|
state.windows.geometry(&d_id).unwrap().visual.current,
|
||||||
|
before,
|
||||||
|
"fullscreen zoom stayed held by an unrelated column resize after its own commit"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Returning to the column needs its real reflow wait again, even after
|
||||||
|
// the returning client's own configure has committed.
|
||||||
|
state.set_window_fullscreen(d_id, false);
|
||||||
|
state.display_handle.flush_clients().unwrap();
|
||||||
|
ack_configure(&mut d_wire, 3);
|
||||||
|
request(&mut d_wire, 2, 1, &[6, 0, 0], None);
|
||||||
|
request(&mut d_wire, 2, 6, &[], None);
|
||||||
|
dispatch(&mut events, &mut state);
|
||||||
|
assert!(state.windows.transaction(&d_id).is_none());
|
||||||
|
assert!(state.windows.transaction(&b_id).is_some());
|
||||||
|
assert!(state.presentation_dependencies.reflow_blocked(d_id, &state.windows));
|
||||||
|
let returning = state.windows.geometry(&d_id).unwrap().visual.current;
|
||||||
|
state.advance_animations_at(Duration::from_millis(16), Duration::from_millis(10048));
|
||||||
|
assert_eq!(state.windows.geometry(&d_id).unwrap().visual.current, returning);
|
||||||
}
|
}
|
||||||
@@ -206,28 +206,53 @@ fn viewport_dependencies(
|
|||||||
owners.extend(inputs.iter().map(|input| input.commit));
|
owners.extend(inputs.iter().map(|input| input.commit));
|
||||||
owners.sort_by_key(|owner| owner.window.0);
|
owners.sort_by_key(|owner| owner.window.0);
|
||||||
owners.dedup();
|
owners.dedup();
|
||||||
let evaluate = |only: Option<ClientCommit>, exclude: Option<ClientCommit>| {
|
let evaluate = |only: Option<ClientCommit>, exclude: Option<ClientCommit>, retained: Option<&HashSet<WindowId>>| {
|
||||||
let mut start = None;
|
let mut start = None;
|
||||||
for step in &previous.steps {
|
for step in &previous.steps {
|
||||||
let widths = step
|
let widths = step
|
||||||
.inputs
|
.inputs
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|input| only.is_none_or(|only| input.commit == only) && exclude != Some(input.commit))
|
.filter(|input| {
|
||||||
|
only.is_none_or(|only| input.commit == only)
|
||||||
|
&& exclude != Some(input.commit)
|
||||||
|
&& retained.is_none_or(|owners| owners.contains(&input.commit.window))
|
||||||
|
})
|
||||||
.map(|input| (input.commit.window, input.width.0, input.width.1))
|
.map(|input| (input.commit.window, input.width.0, input.width.1))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
start = Some(step.target.with_held_widths(&widths, start));
|
start = Some(step.target.with_held_widths(&widths, start));
|
||||||
}
|
}
|
||||||
start.unwrap_or(layout.viewport_x())
|
start.unwrap_or(layout.viewport_x())
|
||||||
};
|
};
|
||||||
let held = evaluate(None, None);
|
let held = evaluate(None, None, None);
|
||||||
previous.waits = owners
|
previous.waits = owners
|
||||||
.into_iter()
|
.iter()
|
||||||
|
.copied()
|
||||||
.filter(|owner| {
|
.filter(|owner| {
|
||||||
previous.overflow
|
previous.overflow
|
||||||
|| (evaluate(Some(*owner), None) - layout.viewport_x()).abs() > 0.001
|
|| (evaluate(Some(*owner), None, None) - layout.viewport_x()).abs() > 0.001
|
||||||
|| (held - evaluate(None, Some(*owner))).abs() > 0.001
|
|| (held - evaluate(None, Some(*owner), None)).abs() > 0.001
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
// Page packing is nonlinear: all held widths can change the page even
|
||||||
|
// when neither a single input nor removing one input exposes the change.
|
||||||
|
// Retain a collective wait in that case, removing inputs that are not
|
||||||
|
// needed to keep the held target different from the requested target.
|
||||||
|
if previous.waits.is_empty() && (held - layout.viewport_x()).abs() > 0.001 {
|
||||||
|
let mut collective = owners.iter().map(|owner| owner.window).collect::<HashSet<_>>();
|
||||||
|
for owner in &owners {
|
||||||
|
collective.remove(&owner.window);
|
||||||
|
if (evaluate(None, None, Some(&collective)) - layout.viewport_x()).abs() <= 0.001 {
|
||||||
|
collective.insert(owner.window);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
previous.waits = owners
|
||||||
|
.into_iter()
|
||||||
|
.filter(|owner| collective.contains(&owner.window))
|
||||||
|
.collect();
|
||||||
|
}
|
||||||
|
|
||||||
// Keep a bounded recipe while any of its inputs is pending: a partial
|
// Keep a bounded recipe while any of its inputs is pending: a partial
|
||||||
// commit can expose a page-packing dependency previously masked by others.
|
// commit can expose a page-packing dependency previously masked by others.
|
||||||
if previous.steps.iter().all(|step| step.inputs.is_empty()) {
|
if previous.steps.iter().all(|step| step.inputs.is_empty()) {
|
||||||
@@ -272,17 +297,25 @@ impl Ferese {
|
|||||||
window: *slow,
|
window: *slow,
|
||||||
serial: transaction.serial(),
|
serial: transaction.serial(),
|
||||||
};
|
};
|
||||||
|
// Fullscreen bounds come from the output, not a column. Its
|
||||||
|
// own configure still gates presentation; tiled reflow waits
|
||||||
|
// apply again when the window returns to normal geometry.
|
||||||
let linked = id == *slow
|
let linked = id == *slow
|
||||||
|| match &workspace.layout {
|
|| (workspace.fullscreen != Some(id)
|
||||||
|
&& match &workspace.layout {
|
||||||
WorkspaceLayout::Scrolling(layout) => own_column.is_some_and(|own| {
|
WorkspaceLayout::Scrolling(layout) => own_column.is_some_and(|own| {
|
||||||
layout
|
layout
|
||||||
.columns()
|
.columns()
|
||||||
.iter()
|
.iter()
|
||||||
.position(|column| column.windows.contains(slow))
|
.position(|column| column.windows.contains(slow))
|
||||||
.is_some_and(|source| source == own || (source < own && transaction.width_changes()))
|
.is_some_and(|source| {
|
||||||
|
source == own || (source < own && transaction.width_changes())
|
||||||
|
})
|
||||||
}),
|
}),
|
||||||
WorkspaceLayout::Tree(_) => workspace.layout.contains(id) && workspace.layout.contains(*slow),
|
WorkspaceLayout::Tree(_) => {
|
||||||
};
|
workspace.layout.contains(id) && workspace.layout.contains(*slow)
|
||||||
|
}
|
||||||
|
});
|
||||||
if linked {
|
if linked {
|
||||||
waits.push(commit);
|
waits.push(commit);
|
||||||
}
|
}
|
||||||
@@ -476,4 +509,104 @@ mod tests {
|
|||||||
assert!(!dependencies.needs_tick());
|
assert!(!dependencies.needs_tick());
|
||||||
assert!(dependencies.reflow.is_empty() && dependencies.viewport.is_empty());
|
assert!(dependencies.reflow.is_empty() && dependencies.viewport.is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn collective_page_waits_survive_redundant_pending_widths() {
|
||||||
|
use ferese_layout::{ColumnWidth, ScrollingLayout};
|
||||||
|
let bounds = Rect::new(0.0, 0.0, 200.0, 100.0);
|
||||||
|
let gaps = GapConfig {
|
||||||
|
inner: 0.0,
|
||||||
|
outer: 0.0,
|
||||||
|
smart: false,
|
||||||
|
};
|
||||||
|
let mut layout = ScrollingLayout::with_default_width(ColumnWidth::Fixed(40.0));
|
||||||
|
layout.set_focus_strategy(ViewportFocusStrategy::Paged);
|
||||||
|
let ids = [
|
||||||
|
WindowId(1),
|
||||||
|
WindowId(2),
|
||||||
|
WindowId(3),
|
||||||
|
WindowId(4),
|
||||||
|
WindowId(5),
|
||||||
|
WindowId(6),
|
||||||
|
];
|
||||||
|
for (i, id) in ids.iter().enumerate() {
|
||||||
|
layout.insert(*id, i.checked_sub(1).map(|j| ids[j])).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (id, width) in ids.into_iter().zip([40.0, 40.0, 80.0, 80.0, 40.0, 40.0]) {
|
||||||
|
layout.set_column_width(id, ColumnWidth::Fixed(width)).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
layout.focus(ids[4]).unwrap();
|
||||||
|
layout
|
||||||
|
.geometry_with_constraints(bounds, gaps, &HashMap::new(), Some(ids[4]))
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(layout.viewport_x(), 160.0);
|
||||||
|
let widths = [
|
||||||
|
(ids[0], 120.0, 40.0),
|
||||||
|
(ids[1], 120.0, 40.0),
|
||||||
|
(ids[4], 80.0, 40.0),
|
||||||
|
(ids[5], 160.0, 40.0),
|
||||||
|
];
|
||||||
|
let pending = widths
|
||||||
|
.into_iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(i, (id, source, target))| {
|
||||||
|
(
|
||||||
|
id,
|
||||||
|
ResizeTransaction::new((i as u32 + 10).into(), Duration::ZERO)
|
||||||
|
.with_column_width(Some(source), Some(target)),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
assert_eq!(layout.viewport_target().unwrap().with_held_widths(&widths, None), 320.0);
|
||||||
|
let graph = viewport_dependencies(&layout, &pending, Default::default());
|
||||||
|
assert!(
|
||||||
|
!graph.waits.is_empty(),
|
||||||
|
"the collectively different page needs a commit wait"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
!graph.waits.iter().any(|wait| wait.window == ids[5]),
|
||||||
|
"a column after focus cannot own this page wait"
|
||||||
|
);
|
||||||
|
let reversed = pending.iter().copied().rev().collect::<Vec<_>>();
|
||||||
|
assert_eq!(
|
||||||
|
viewport_dependencies(&layout, &reversed, Default::default()).waits,
|
||||||
|
graph.waits
|
||||||
|
);
|
||||||
|
|
||||||
|
// Exercise every partial-commit order, including a redundant owner
|
||||||
|
// committing before an owner in the retained collective group.
|
||||||
|
for mask in 0..1 << pending.len() {
|
||||||
|
let remaining = pending
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.filter(|(index, _)| mask & (1 << index) != 0)
|
||||||
|
.map(|(_, input)| *input)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let held_widths = remaining
|
||||||
|
.iter()
|
||||||
|
.map(|(id, transaction)| {
|
||||||
|
let (source, target) = transaction.column_width().unwrap();
|
||||||
|
(*id, source, target)
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let held = layout.viewport_target().unwrap().with_held_widths(&held_widths, None);
|
||||||
|
let partial = viewport_dependencies(&layout, &remaining, graph.clone());
|
||||||
|
if (held - layout.viewport_x()).abs() > 0.001 {
|
||||||
|
assert!(
|
||||||
|
!partial.waits.is_empty(),
|
||||||
|
"collective wait lost after partial commits: {mask}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
!partial.waits.iter().any(|wait| wait.window == ids[5]),
|
||||||
|
"unrelated column blocked mask {mask}"
|
||||||
|
);
|
||||||
|
if remaining.is_empty() {
|
||||||
|
assert!(partial.steps.is_empty() && partial.waits.is_empty());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user