Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/facett

facett-flow/examples/flow_lab/scenarios.rs

//! Runtime fixture choices are separate from compiled features. A plain scene
//! remains plain even in an all-features binary.
#[cfg(feature = "host")]
use facett_core::Elm;
use facett_flow::{FlowGraph, Graph};
use serde_json::{Value, json};

pub const NAMES: &[&str] = &[
    "plain",
    "host",
    "labels",
    "boxes",
    "arrows",
    "status",
    "rate",
    "layered",
    "compact-state",
    "hit-test",
    "lod",
    "all",
];

pub struct Scenario {
    pub name: String,
    pub build_ms: f64,
    #[cfg(feature = "host")]
    pub scene: Option<facett_flow::FlowScene>,
}
impl Scenario {
    pub fn validate(name: &str) -> Result<(), String> {
        if !NAMES.contains(&name) {
            return Err(format!("--scenario must be one of {}", NAMES.join(", ")));
        }
        for feature in if name == "all" {
            &NAMES[1..NAMES.len() - 1]
        } else {
            std::slice::from_ref(&name)
        } {
            if *feature != "plain" && !facett_flow::compiled_features().contains(feature) {
                return Err(format!("scenario {name} requires Cargo feature {feature}"));
            }
        }
        Ok(())
    }
    pub fn new(name: &str, mut graph: Graph) -> Result<(FlowGraph, Self), String> {
        Self::validate(name)?;
        let start = std::time::Instant::now();
        #[cfg(feature = "layered")]
        if name == "layered" || name == "all" {
            graph.layered(Default::default())?;
        }
        #[cfg(not(feature = "layered"))]
        let _ = &mut graph;
        let mut flow = FlowGraph::new("native-flow", graph)?;
        #[cfg(feature = "labels")]
        if name == "labels" {
            flow.set_labels(true);
        }
        #[cfg(feature = "compact-state")]
        if name == "compact-state" || name == "all" {
            flow.set_compact_state(true);
        }
        #[cfg(not(any(feature = "labels", feature = "compact-state")))]
        let _ = &mut flow;
        #[cfg(feature = "host")]
        let scene = if [
            "host", "boxes", "arrows", "status", "rate", "hit-test", "lod", "all",
        ]
        .contains(&name)
        {
            let mut scene =
                facett_flow::FlowScene::with_key((*flow.state().graph).clone(), 0x464c_4142)?;
            #[cfg(feature = "boxes")]
            if name == "boxes" || name == "all" {
                scene.set_sizes(facett_flow::attr::Sizes {
                    nodes: vec![[20.0, 12.0]; scene.graph().nodes.len()],
                })?;
            }
            #[cfg(feature = "arrows")]
            if name == "arrows" || name == "all" {
                scene.set_arrows(true);
            }
            #[cfg(feature = "status")]
            if name == "status" || name == "all" {
                use facett_flow::attr::{Status, StatusTable};
                let value = |i: usize| match i % 4 {
                    0 => None,
                    1 => Some(Status::Ok),
                    2 => Some(Status::Slow),
                    _ => Some(Status::Error),
                };
                scene.set_status(StatusTable {
                    nodes: (0..scene.graph().nodes.len()).map(value).collect(),
                    edges: (0..scene.graph().edges.len()).map(value).collect(),
                })?;
            }
            #[cfg(feature = "rate")]
            if name == "rate" || name == "all" {
                scene.set_rates(facett_flow::attr::Rates {
                    edges: (0..scene.graph().edges.len())
                        .map(|i| match i % 4 {
                            0 => None,
                            1 => Some(0.0),
                            2 => Some(10.0),
                            _ => Some(100.0),
                        })
                        .collect(),
                })?;
            }
            #[cfg(not(any(
                feature = "boxes",
                feature = "arrows",
                feature = "status",
                feature = "rate"
            )))]
            let _ = &mut scene;
            Some(scene)
        } else {
            None
        };
        Ok((
            flow,
            Self {
                name: name.into(),
                build_ms: start.elapsed().as_secs_f64() * 1000.0,
                #[cfg(feature = "host")]
                scene,
            },
        ))
    }
    pub fn metadata(&self) -> Value {
        json!({"name":self.name,"scene_build_ms":self.build_ms,
        "sizes_world":if self.name=="boxes" || self.name=="all"{Some([20,12])}else{None},
        "label_budget":if self.name=="labels" || self.name=="all"{Some(2000)}else{None},
        "rate_fixture":if self.name=="rate" || self.name=="all"{Some("repeating None,0,10,100")}else{None},
        "status_fixture":if self.name=="status" || self.name=="all"{Some("repeating None,Ok,Slow,Error")}else{None},
        "min_packet_px":if self.name=="lod" || self.name=="all"{12.0}else{0.0},
        "hit_probe":self.name=="hit-test" || self.name=="all",
        "compact_read_each_frame":self.name=="compact-state" || self.name=="all"})
    }
    pub fn bounds(&self, flow: &FlowGraph) -> egui::Rect {
        #[cfg(feature = "host")]
        if let Some(scene) = &self.scene {
            return scene.bounds();
        }
        flow.bounds()
    }
    pub fn draw(&self, flow: &mut FlowGraph, ui: &mut egui::Ui) {
        #[cfg(feature = "host")]
        if let Some(scene) = &self.scene {
            let (rect, _) = ui.allocate_exact_size(ui.available_size(), egui::Sense::hover());
            let view = flow.state().camera.view(rect, scene.bounds());
            let opts = facett_flow::PaintOpts {
                seconds: flow.state().seconds,
                moving: !flow.state().reduced_motion
                    && facett_core::look::effects_policy(ui).allows_decorative_motion(),
                clustered: flow.state().clustered,
                cpu: facett_flow::CpuStyle::Full {
                    max_edges: usize::MAX,
                },
                #[cfg(feature = "lod")]
                min_packet_px: if self.name == "lod" || self.name == "all" {
                    12.0
                } else {
                    0.0
                },
                ..Default::default()
            };
            let painted = facett_flow::paint(
                ui,
                egui::Id::new("flow-lab-host"),
                rect,
                scene,
                &view,
                &opts,
            );
            #[cfg(feature = "labels")]
            if self.name == "all" && !flow.state().clustered {
                use facett_core::legibility::{LabelCandidate, PlaceOpts, place_labels};
                let col = facett_core::theme(ui).text;
                let galleys: Vec<_> = painted
                    .on_screen
                    .iter()
                    .map(|n| {
                        ui.painter().layout_no_wrap(
                            scene.graph().nodes[n.node].label.clone(),
                            egui::FontId::proportional(11.0),
                            col,
                        )
                    })
                    .collect();
                let candidates: Vec<_> = painted
                    .on_screen
                    .iter()
                    .zip(&galleys)
                    .map(|(n, g)| {
                        LabelCandidate::new(n.rect.center() + egui::vec2(0.0, 12.0), g.size(), 0.0)
                    })
                    .collect();
                for label in place_labels(&candidates, rect, PlaceOpts::default()).placed {
                    ui.painter().with_clip_rect(rect).galley(
                        label.rect.center() - galleys[label.index].size() * 0.5,
                        galleys[label.index].clone(),
                        col,
                    );
                }
            }
            let _ = painted;
            #[cfg(feature = "hit-test")]
            if self.name == "hit-test" || self.name == "all" {
                std::hint::black_box(
                    scene.hit_with_candidates(view.world(rect.center()), 4.0 / view.scale),
                );
            }
            return;
        }
        facett_core::Facet::ui(flow, ui);
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn runtime_scene_selection_is_explicit_and_validates_features() {
        assert!(Scenario::validate("plain").is_ok());
        assert!(Scenario::validate("unknown").is_err());
        let (_, plain) = Scenario::new("plain", Graph::demo(20)).unwrap();
        assert_eq!(plain.metadata()["name"], "plain");
        #[cfg(feature = "host")]
        assert!(plain.scene.is_none());
        for name in NAMES.iter().filter(|n| **n != "plain") {
            if Scenario::validate(name).is_ok() {
                let (flow, scenario) = Scenario::new(name, Graph::demo(20)).unwrap();
                assert_eq!(scenario.metadata()["name"], *name);
                assert!(scenario.bounds(&flow).is_finite());
            }
        }
    }
    #[cfg(all(feature = "boxes", feature = "status", feature = "rate"))]
    #[test]
    fn attribute_scenarios_apply_real_parallel_tables() {
        let (_, boxes) = Scenario::new("boxes", Graph::demo(20)).unwrap();
        assert_eq!(
            boxes.scene.unwrap().node_rect(0).unwrap().size(),
            egui::vec2(20.0, 12.0)
        );
        let (_, status) = Scenario::new("status", Graph::demo(20)).unwrap();
        assert!(status.scene.unwrap().status().unwrap().nodes[1].is_some());
        let (_, rate) = Scenario::new("rate", Graph::demo(20)).unwrap();
        assert_eq!(
            &rate.scene.unwrap().rates().unwrap().edges[..4],
            &[None, Some(0.0), Some(10.0), Some(100.0)]
        );
    }
}

#[cfg(all(
    test,
    feature = "host",
    feature = "labels",
    feature = "boxes",
    feature = "arrows",
    feature = "status",
    feature = "rate",
    feature = "layered",
    feature = "compact-state",
    feature = "hit-test",
    feature = "lod"
))]
#[test]
#[ignore = "constructs the required 10k and 100k active-feature fixtures"]
fn combined_scene_accepts_required_benchmark_sizes() {
    for count in [10_000, 100_000] {
        let (flow, scenario) = Scenario::new("all", Graph::hub_demo(count)).unwrap();
        assert_eq!(flow.state().graph.nodes.len(), count);
        let scene = scenario.scene.as_ref().unwrap();
        assert_eq!(scene.graph().nodes.len(), count);
        assert_eq!(scene.status().unwrap().nodes.len(), count);
        assert_eq!(scene.rates().unwrap().edges.len(), count - 1);
        println!("{}", scenario.metadata());
    }
}