facett-core 0.1.7

facett — visual kernel: render a node/edge Scene into egui (wgpu fast path to come)
Documentation
//! **The graphs-over-maps seam** — a z-ordered [`LayerStack`].
//!
//! The render doctrine's "graphs-overlaid-on-maps = LATER" line is unblocked by
//! adding the *seam* now (not the feature): a shared camera (see
//! [`super::camera::Camera`]) plus a z-ordered stack of [`Layer`]s so a graph skin
//! can later composite over a map skin without a retrofit. This milestone ships
//! the struct + its z-order contract only — **no consumer is wired**; the skins
//! still draw themselves directly.

/// What a layer *is* — the domain kind drawn at this z. Opaque to the kernel: the
/// stack only orders layers; it never interprets the kind. Extend as skins land.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum LayerKind {
    /// A 2D vector basemap (facett-map).
    Map2d,
    /// A 3D extruded map (facett-map3d).
    Map3d,
    /// A graph drawn over the base (facett-graphview / its skins).
    Graph,
    /// A caller-supplied overlay (decorations, ravens, HUD).
    Overlay,
}

/// One stackable layer: its paint **z** (lower draws first / underneath) and its
/// [`LayerKind`]. The renderer composites layers in ascending z.
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Layer {
    /// Paint order key — ascending z paints back→front (a higher-z graph layer
    /// composites *over* a lower-z map layer). Ties keep insertion order (stable).
    pub z: f32,
    pub kind: LayerKind,
}

impl Layer {
    pub fn new(z: f32, kind: LayerKind) -> Self {
        Self { z, kind }
    }
}

/// A z-ordered set of [`Layer`]s — the composition seam for graphs-over-maps. Push
/// layers in any order; [`LayerStack::ordered`] yields them back→front (ascending
/// z), with insertion order breaking ties so the result is deterministic.
#[derive(Clone, Debug, Default)]
pub struct LayerStack {
    layers: Vec<Layer>,
}

impl LayerStack {
    pub fn new() -> Self {
        Self::default()
    }

    /// Push a layer; returns its insertion index (its tie-break key).
    pub fn push(&mut self, layer: Layer) -> usize {
        self.layers.push(layer);
        self.layers.len() - 1
    }

    /// Convenience: push `kind` at `z`.
    pub fn add(&mut self, z: f32, kind: LayerKind) -> usize {
        self.push(Layer::new(z, kind))
    }

    pub fn len(&self) -> usize {
        self.layers.len()
    }
    pub fn is_empty(&self) -> bool {
        self.layers.is_empty()
    }

    /// The layers in **paint order** — ascending z, insertion order breaking ties.
    /// Back→front: the renderer draws element 0 first (underneath), the last on top.
    pub fn ordered(&self) -> Vec<Layer> {
        let mut idx: Vec<usize> = (0..self.layers.len()).collect();
        // Stable sort on z keeps insertion order for equal z (the tie-break rule).
        idx.sort_by(|&a, &b| {
            self.layers[a].z.partial_cmp(&self.layers[b].z).unwrap_or(std::cmp::Ordering::Equal)
        });
        idx.into_iter().map(|i| self.layers[i]).collect()
    }

    /// The topmost layer (largest z; last-inserted wins a tie), or `None` if empty.
    pub fn top(&self) -> Option<Layer> {
        self.ordered().last().copied()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    /// INJECT-ASSERT: layers come back ascending-z regardless of push order, so a
    /// graph pushed under (higher z) a map composites on top.
    #[test]
    fn layer_stack_orders_back_to_front_by_z() {
        let mut s = LayerStack::new();
        s.add(10.0, LayerKind::Graph); // pushed first, but highest z
        s.add(0.0, LayerKind::Map3d); // base
        s.add(5.0, LayerKind::Map2d);
        let order = s.ordered();
        assert_eq!(order.len(), 3);
        assert_eq!(order[0].kind, LayerKind::Map3d, "lowest z drawn first (underneath)");
        assert_eq!(order[1].kind, LayerKind::Map2d);
        assert_eq!(order[2].kind, LayerKind::Graph, "highest z composites on top");
        assert_eq!(s.top().unwrap().kind, LayerKind::Graph);
    }

    /// INJECT-ASSERT: equal z keeps insertion order (stable tie-break) — two
    /// overlays at the same z paint in the order the host added them.
    #[test]
    fn layer_stack_breaks_ties_by_insertion_order() {
        let mut s = LayerStack::new();
        s.add(1.0, LayerKind::Map2d);
        let first = s.add(2.0, LayerKind::Overlay);
        let second = s.add(2.0, LayerKind::Graph);
        let _ = (first, second);
        let order = s.ordered();
        assert_eq!(order[0].kind, LayerKind::Map2d);
        assert_eq!(order[1].kind, LayerKind::Overlay, "first-inserted of the tie paints first");
        assert_eq!(order[2].kind, LayerKind::Graph);
    }

    #[test]
    fn empty_stack_has_no_top() {
        let s = LayerStack::new();
        assert!(s.is_empty());
        assert!(s.top().is_none());
    }
}