LCOV - code coverage report
Current view: top level - dashql-dataflow/src - routing.rs Coverage Total Hit
Test: DashQL 6e74948 Lines: 100.0 % 38 38
Test Date: 2026-11-06 17:46:50

            Line data    Source code
       1              : //! Orthogonal routes of the edges of the dataflow canvas around its boxes, for the edges that the layout did not
       2              : //! route: the edges of a box that was moved, or of a box that another box was moved onto.
       3              : //!
       4              : //! An edge leaves the right border of its source box and enters the left border of its target box, at the rows of
       5              : //! their statements. ortho-router builds a grid of the lines through the boxes grown by a margin and the stubs of
       6              : //! the ports, searches the shortest route with few bends through it, and spreads routes that share a channel apart.
       7              : 
       8              : use crate::layout::DataflowLayoutConfig;
       9              : use crate::ortho_router::{Connector, Options, Point, Port, Rect, Router, Side};
      10              : 
      11              : /// What a bend costs, as a length
      12              : const ROUTE_BEND_PENALTY: f64 = 40.0;
      13              : 
      14              : /// A box that routes go around
      15              : #[derive(Clone, Copy, Debug, PartialEq)]
      16              : pub struct RouteBox {
      17              :     pub x: f64,
      18              :     pub y: f64,
      19              :     pub width: f64,
      20              :     pub height: f64,
      21              : }
      22              : 
      23              : /// An edge to route: from a point on the right border of a box to a point on the left border of a box
      24              : #[derive(Clone, Copy, Debug, PartialEq)]
      25              : pub struct RouteRequestEdge {
      26              :     pub from: (f64, f64),
      27              :     pub to: (f64, f64),
      28              : }
      29              : 
      30              : /// Route edges around boxes as polylines of horizontal and vertical segments, `None` for an edge without a way
      31              : /// around the boxes. Routes keep `edge_box_gap` to the boxes, routes that share a channel spread `edge_gap` apart.
      32            2 : pub fn route_edges(boxes: &[RouteBox], edges: &[RouteRequestEdge], config: &DataflowLayoutConfig) -> Vec<Option<Vec<(f64, f64)>>> {
      33            9 :     let point = |(x, y): (f64, f64)| Point { x: x as f32, y: y as f32 };
      34            2 :     let options = Options { margin: config.edge_box_gap as f32, bend_penalty: ROUTE_BEND_PENALTY as f32, spacing: config.edge_gap as f32 };
      35            2 :     let mut router = Router::new(options)
      36            5 :         .with_obstacles(boxes.iter().map(|b| Rect::from_min_size(point((b.x, b.y)), b.width as f32, b.height as f32)));
      37            2 :     let connectors: Vec<Connector> = edges.iter().map(|e| Connector::new(Port::new(point(e.from), Side::Right), Port::new(point(e.to), Side::Left))).collect();
      38            2 :     router
      39            2 :         .route_all(&connectors)
      40            2 :         .into_iter()
      41           12 :         .map(|route| route.routed.then(|| route.points.iter().map(|p| (p.x as f64, p.y as f64)).collect()))
      42            2 :         .collect()
      43            2 : }
      44              : 
      45              : #[cfg(test)]
      46              : mod tests {
      47              :     use super::*;
      48              : 
      49            5 :     fn rect(x: f64, y: f64, width: f64, height: f64) -> RouteBox {
      50            5 :         RouteBox { x, y, width, height }
      51            5 :     }
      52              : 
      53              :     /// Does a polyline pass through the inside of a box?
      54            1 :     fn crosses(points: &[(f64, f64)], b: &RouteBox) -> bool {
      55            5 :         points.windows(2).any(|w| {
      56            5 :             let (x0, x1) = (w[0].0.min(w[1].0), w[0].0.max(w[1].0));
      57            5 :             let (y0, y1) = (w[0].1.min(w[1].1), w[0].1.max(w[1].1));
      58            5 :             x0 < b.x + b.width && x1 > b.x && y0 < b.y + b.height && y1 > b.y
      59            5 :         })
      60            1 :     }
      61              : 
      62              :     #[test]
      63            1 :     fn routes_around_a_box_in_the_way_with_right_angles() {
      64            1 :         let boxes = [rect(0.0, 0.0, 100.0, 60.0), rect(200.0, 0.0, 100.0, 60.0), rect(400.0, 0.0, 100.0, 60.0)];
      65            1 :         let routes = route_edges(&boxes, &[RouteRequestEdge { from: (100.0, 30.0), to: (400.0, 30.0) }], &DataflowLayoutConfig::default());
      66            1 :         let points = routes[0].as_ref().expect("a route");
      67            1 :         assert_eq!(points.first(), Some(&(100.0, 30.0)));
      68            1 :         assert_eq!(points.last(), Some(&(400.0, 30.0)));
      69            1 :         assert!(!crosses(points, &boxes[1]));
      70            5 :         assert!(points.windows(2).all(|w| w[0].0 == w[1].0 || w[0].1 == w[1].1));
      71            1 :     }
      72              : 
      73              :     #[test]
      74            1 :     fn routes_back_to_a_box_on_the_left_around_both() {
      75            1 :         let boxes = [rect(0.0, 0.0, 100.0, 60.0), rect(200.0, 0.0, 100.0, 60.0)];
      76            1 :         let routes = route_edges(&boxes, &[RouteRequestEdge { from: (300.0, 30.0), to: (0.0, 30.0) }], &DataflowLayoutConfig::default());
      77            1 :         let points = routes[0].as_ref().expect("a route");
      78            3 :         assert!(points.iter().any(|p| p.1 < 0.0 || p.1 > 60.0));
      79            5 :         assert!(points.windows(2).all(|w| w[0].0 == w[1].0 || w[0].1 == w[1].1));
      80            1 :     }
      81              : }
        

Generated by: LCOV version 2.0-1