1
0
Fork 0
forked from wry/wry

Bridge interrupted phased retargets

This commit is contained in:
atagen 2026-05-28 10:57:36 +10:00
parent e7f9a5cb09
commit 09305ab026

View file

@ -7,7 +7,8 @@ use {
RetainedToplevel,
expand_damage_rect,
multiphase::{
MultiphaseRequest, MultiphaseWindow, MultiphaseWindowHierarchy,
MultiphasePhase, MultiphasePlan, MultiphasePlanFailure, MultiphaseRequest,
MultiphaseWindow, MultiphaseWindowHierarchy,
partition_motion_groups, plan_no_overlap_with_diagnostics, validate_phase_paths,
},
spawn_in_start_rect,
@ -205,6 +206,56 @@ fn layout_animation_group_uses_plain(
.any(|&idx| candidates[idx].style == AnimationStyle::Plain)
}
fn bridged_retarget_plan(
request: &MultiphaseRequest,
candidates: &[LayoutAnimationCandidate],
group: &[usize],
bridge_paths: &[Vec<(Rect, Rect)>],
bridge_phase_count: usize,
follow_phases: &[MultiphasePhase],
) -> Result<MultiphasePlan, MultiphasePlanFailure> {
let mut paths = vec![];
for (group_pos, &idx) in group.iter().enumerate() {
let candidate = &candidates[idx];
let window = request.windows[group_pos];
let Some(bridge_path) = bridge_paths.get(group_pos) else {
return Err(MultiphasePlanFailure::NoPattern);
};
let mut path = bridge_path.clone();
let mut current = path
.last()
.map(|(_, to)| *to)
.unwrap_or(window.from);
while path.len() < bridge_phase_count {
path.push((current, current));
}
if current != candidate.old {
return Err(MultiphasePlanFailure::NoPattern);
}
for phase in follow_phases {
match phase
.steps
.iter()
.find(|step| step.node_id == candidate.node_id)
{
Some(step) => {
if step.from != current {
return Err(MultiphasePlanFailure::NoPattern);
}
path.push((step.from, step.to));
current = step.to;
}
None => path.push((current, current)),
}
}
if current != window.to {
return Err(MultiphasePlanFailure::NoPattern);
}
paths.push(path);
}
validate_phase_paths(request, &paths)
}
pub struct State {
pub pid: c::pid_t,
pub kb_ctx: KbvmContext,
@ -1721,6 +1772,9 @@ impl State {
if self.start_existing_phased_retarget(candidates, windows, group, &request, now_nsec) {
return true;
}
if self.start_bridged_phased_retarget(candidates, windows, group, &request, now_nsec) {
return true;
}
let plan = match plan_no_overlap_with_diagnostics(&request) {
Ok(plan) => plan,
Err(diagnostic) => {
@ -1770,6 +1824,97 @@ impl State {
self.start_multiphase_plan(candidates, windows, group, &plan.phases, now_nsec)
}
fn start_bridged_phased_retarget(
self: &Rc<Self>,
candidates: &[LayoutAnimationCandidate],
windows: &[MultiphaseWindow],
group: &[usize],
request: &MultiphaseRequest,
now_nsec: u64,
) -> bool {
let mut bridge_paths = vec![];
let mut bridge_phase_count = 0;
let mut has_bridge = false;
for &idx in group {
let candidate = &candidates[idx];
let window = windows[idx];
if window.from == candidate.old {
bridge_paths.push(vec![]);
continue;
}
let Some(path) =
self.animations
.phased_route_to(candidate.node_id, candidate.old, now_nsec)
else {
return false;
};
if !path.is_empty() {
has_bridge = true;
bridge_phase_count = bridge_phase_count.max(path.len());
}
bridge_paths.push(path);
}
if !has_bridge {
return false;
}
let settled_windows: Vec<_> = group
.iter()
.map(|&idx| {
let candidate = &candidates[idx];
MultiphaseWindow::with_hierarchy(
candidate.node_id,
candidate.old,
candidate.new,
candidate.hierarchy,
)
})
.collect();
let Some(first) = settled_windows.first() else {
return false;
};
let mut bounds = first.from.union(first.to);
for window in &settled_windows[1..] {
bounds = bounds.union(window.from).union(window.to);
}
let settled_request = MultiphaseRequest {
bounds,
windows: settled_windows,
clearance: self.layout_animation_clearance(),
};
let follow_plan = match plan_no_overlap_with_diagnostics(&settled_request) {
Ok(plan) => plan,
Err(diagnostic) => {
log::debug!(
"bridged phased retarget follow-up rejected for group {:?}: {:?}",
group,
diagnostic
);
return false;
}
};
let plan = match bridged_retarget_plan(
request,
candidates,
group,
&bridge_paths,
bridge_phase_count,
&follow_plan.phases,
) {
Ok(plan) => plan,
Err(error) => {
log::debug!(
"bridged phased retarget rejected for group {:?}: {:?}",
group,
error
);
return false;
}
};
log::debug!("bridging active phased animation for group {:?}", group);
self.start_multiphase_plan(candidates, windows, group, &plan.phases, now_nsec)
}
fn start_multiphase_plan(
self: &Rc<Self>,
candidates: &[LayoutAnimationCandidate],
@ -2512,10 +2657,24 @@ mod tests {
}
fn candidate(node_id: u32, style: AnimationStyle) -> LayoutAnimationCandidate {
candidate_rects(
node_id,
rect(0, 0, 100, 100),
rect(100, 0, 200, 100),
style,
)
}
fn candidate_rects(
node_id: u32,
old: Rect,
new: Rect,
style: AnimationStyle,
) -> LayoutAnimationCandidate {
LayoutAnimationCandidate {
node_id: NodeId(node_id),
old: rect(0, 0, 100, 100),
new: rect(100, 0, 200, 100),
old,
new,
curve: AnimationCurve::Linear,
style,
hierarchy: MultiphaseWindowHierarchy::default(),
@ -2533,6 +2692,57 @@ mod tests {
assert!(layout_animation_group_uses_plain(&candidates, &[0, 1]));
}
#[test]
fn bridged_retarget_handles_second_rotation_interrupt() {
let a_left = rect(0, 0, 100, 100);
let c_mid = rect(100, 0, 200, 100);
let c_left = a_left;
let a_mid = c_mid;
let c_current = rect(150, 50, 250, 100);
let c_mid_lane = rect(100, 50, 200, 100);
let candidates = vec![
candidate_rects(1, a_left, a_mid, AnimationStyle::Multiphase),
candidate_rects(3, c_mid, c_left, AnimationStyle::Multiphase),
];
let request = MultiphaseRequest {
bounds: rect(0, 0, 250, 100),
windows: vec![
MultiphaseWindow::new(NodeId(1), a_left, a_mid),
MultiphaseWindow::new(NodeId(3), c_current, c_left),
],
clearance: 0,
};
let settled_request = MultiphaseRequest {
bounds: rect(0, 0, 200, 100),
windows: vec![
MultiphaseWindow::new(NodeId(1), a_left, a_mid),
MultiphaseWindow::new(NodeId(3), c_mid, c_left),
],
clearance: 0,
};
let follow_plan = plan_no_overlap_with_diagnostics(&settled_request).unwrap();
let bridge_paths = vec![vec![], vec![(c_current, c_mid_lane), (c_mid_lane, c_mid)]];
let plan = bridged_retarget_plan(
&request,
&candidates,
&[0, 1],
&bridge_paths,
2,
&follow_plan.phases,
)
.unwrap();
assert!(plan
.phases
.iter()
.any(|phase| phase.steps.iter().any(|step| step.node_id == NodeId(1))));
assert!(plan
.phases
.iter()
.any(|phase| phase.steps.iter().any(|step| step.node_id == NodeId(3))));
}
#[test]
fn layout_animation_candidates_coalesce_duplicate_nodes() {
let source = MultiphaseHierarchyPosition {