Preserve settled route budgets and paths during edge previews
This commit is contained in:
@@ -38,6 +38,9 @@ pub struct RoutedEdge {
|
||||
pub pin_fractions: Vec<f32>,
|
||||
pub warnings: Vec<RouteWarning>,
|
||||
pub custom_suspended: bool,
|
||||
// Derived solver state, never persisted. A preview must inherit the same
|
||||
// remaining work budget as this edge had in stable-ID settled traversal.
|
||||
search_budget_before: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
@@ -395,6 +398,36 @@ fn route_diagram_impl(
|
||||
r.inflate(CLEARANCE)
|
||||
})
|
||||
.collect();
|
||||
let cached = preview.and_then(|(baseline, changed)| {
|
||||
(edge.id == changed)
|
||||
.then(|| baseline.edges.get(&edge.id))
|
||||
.flatten()
|
||||
});
|
||||
if let Some(cached) = cached {
|
||||
budget = cached.search_budget_before;
|
||||
if cached.pins == pins && cached.custom_suspended == custom_suspended {
|
||||
// Label-only editing cannot change the connection beneath the
|
||||
// pointer, including a settled fallback with routing warnings.
|
||||
let mut updated = cached.clone();
|
||||
updated.label = make_label(
|
||||
project,
|
||||
edge.id,
|
||||
&updated.points,
|
||||
route.and_then(|r| r.label.as_ref()),
|
||||
&obstacles,
|
||||
);
|
||||
for &point in &updated.points {
|
||||
bounds.point(point);
|
||||
}
|
||||
if !updated.label.text.is_empty() {
|
||||
bounds.rect(updated.label.bounds.position, updated.label.bounds.size);
|
||||
}
|
||||
record_segments(&mut previous, &updated.points);
|
||||
edges.insert(edge.id, updated);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
let search_budget_before = budget;
|
||||
let mut warnings = Vec::new();
|
||||
if custom_suspended {
|
||||
warnings.push(RouteWarning::PromotedEndpoints);
|
||||
@@ -449,6 +482,7 @@ fn route_diagram_impl(
|
||||
pin_fractions,
|
||||
warnings,
|
||||
custom_suspended,
|
||||
search_budget_before,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -614,6 +614,63 @@ fn single_edge_preview_matches_settled_geometry_and_preserves_other_routes() {
|
||||
assert_eq!(preview.bounds, complete.bounds);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exhausted_search_budget_survives_label_and_pin_previews() {
|
||||
let (mut p, d, original) = maze();
|
||||
// Valid reuse of one typed port pair: many distinct exchanges through the
|
||||
// same multi-turn corridor exhaust the shared per-view search budget.
|
||||
let template = p.relations.remove(&original).unwrap();
|
||||
for i in 0..1500 {
|
||||
let mut edge = template.clone();
|
||||
edge.id = Id::from_u128(100 + i);
|
||||
edge.alias = format!("budget_flow_{i}");
|
||||
p.relations.insert(edge.id, edge);
|
||||
}
|
||||
validate_candidate(&p).unwrap();
|
||||
let view = projected(&p, d);
|
||||
let baseline = route_diagram(&p, &p.diagrams[&d], &view);
|
||||
let exhausted = baseline
|
||||
.edges
|
||||
.values()
|
||||
.find(|edge| edge.warnings.contains(&RouteWarning::SearchBudgetExhausted))
|
||||
.expect("Fixture must exhaust the cumulative budget");
|
||||
let id = exhausted.id;
|
||||
set_route(
|
||||
&mut p,
|
||||
d,
|
||||
id,
|
||||
vec![],
|
||||
Some(EdgeLabel {
|
||||
fraction: exhausted.label.fraction,
|
||||
offset: point(0., -40.),
|
||||
}),
|
||||
);
|
||||
let label_preview = route_edge_preview(&p, &p.diagrams[&d], &view, &baseline, id);
|
||||
assert_eq!(label_preview.edges[&id].points, exhausted.points);
|
||||
assert_eq!(
|
||||
label_preview.edges[&id].pin_fractions,
|
||||
exhausted.pin_fractions
|
||||
);
|
||||
assert_eq!(label_preview.edges[&id].warnings, exhausted.warnings);
|
||||
assert_ne!(
|
||||
label_preview.edges[&id].label.center,
|
||||
exhausted.label.center
|
||||
);
|
||||
assert_eq!(
|
||||
label_preview.edges[&id],
|
||||
route_diagram(&p, &p.diagrams[&d], &view).edges[&id]
|
||||
);
|
||||
// Adding a pin requires actual rerouting. Its inherited pre-edge budget
|
||||
// must agree with the full solve, rather than obtaining a fresh view budget.
|
||||
p.diagrams.get_mut(&d).unwrap().routes[0]
|
||||
.waypoints
|
||||
.push(point(650., 400.));
|
||||
let pin_preview = route_edge_preview(&p, &p.diagrams[&d], &view, &baseline, id);
|
||||
let full = route_diagram(&p, &p.diagrams[&d], &view);
|
||||
assert_eq!(pin_preview.edges[&id], full.edges[&id]);
|
||||
assert!(pin_preview.edges[&id].points.contains(&point(650., 400.)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn interpolation_projection_empty_and_repeated_points_are_well_defined() {
|
||||
assert_eq!(point_along_polyline(&[], 0.5), None);
|
||||
|
||||
Reference in New Issue
Block a user