rustial-engine 0.0.1

Framework-agnostic 2.5D map engine for rustial
Documentation
//! # Map layer abstraction
//!
//! This module defines the [`Layer`] trait, the central abstraction for
//! everything that can be drawn on the map. Concrete layer types (raster
//! tiles, vector features, 3D models, custom overlays, ...) implement this
//! trait so the engine can manage them uniformly through the
//! [`LayerStack`](crate::layers::LayerStack).
//!
//! ## Design principles
//!
//! * **Framework-agnostic** -- the trait carries no GPU or windowing types.
//!   Renderers (Bevy, WGPU, ...) consume the trait objects via downcasting
//!   through [`as_any`](Layer::as_any) / [`as_any_mut`](Layer::as_any_mut).
//!
//! * **Thread-safe** -- `Send + Sync` is required so the engine state can
//!   be shared across threads (e.g. behind an `RwLock` in `MapHandle`).
//!
//! * **Identifiable** -- every layer receives a unique [`LayerId`] at
//!   construction time. Names are human-readable labels and *not*
//!   guaranteed to be unique; use `id()` when you need a stable key.
//!
//! * **Minimal required surface** -- only `name`, `visible`, `set_visible`,
//!   `as_any`, and `as_any_mut` *must* be implemented. Everything else
//!   (`opacity`, `z_index`, `id`) ships with sensible defaults so new
//!   layer types can start small and opt into more control later.
//! ---------------------------------------------------------------------------

use std::any::Any;
use std::fmt;
use std::sync::atomic::{AtomicU64, Ordering};

// -- LayerId ---------------------------------------------------------------

/// Monotonically increasing counter for [`LayerId`] generation.
///
/// Starts at 1 so that `LayerId(0)` can serve as an obvious sentinel /
/// "no id" value in debug output.
static NEXT_LAYER_ID: AtomicU64 = AtomicU64::new(1);

/// A globally unique, opaque identifier for a map layer.
///
/// Identifiers are assigned via [`LayerId::next()`] using a lock-free
/// atomic counter and are guaranteed unique within the lifetime of the
/// process. They are *not* stable across process restarts and should
/// not be serialized.
///
/// ```
/// use rustial_engine::LayerId;
///
/// let a = LayerId::next();
/// let b = LayerId::next();
/// assert_ne!(a, b);
/// ```
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct LayerId(u64);

impl LayerId {
    /// Allocate the next unique layer identifier.
    ///
    /// This is a lock-free operation (`Relaxed` ordering is sufficient
    /// because uniqueness only requires that no two calls return the
    /// same value; there is no happens-before dependency on the counter
    /// itself).
    #[inline]
    pub fn next() -> Self {
        Self(NEXT_LAYER_ID.fetch_add(1, Ordering::Relaxed))
    }

    /// Return the raw `u64` value (useful for logging / diagnostics).
    #[inline]
    pub fn as_u64(self) -> u64 {
        self.0
    }
}

impl fmt::Debug for LayerId {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "LayerId({})", self.0)
    }
}

impl fmt::Display for LayerId {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.0)
    }
}

// -- LayerKind -------------------------------------------------------------

/// Discriminant for concrete layer types known to the engine.
//
// Used by [`Layer::kind()`] to enable O(1) enum dispatch in the engine
// update loop, replacing trial-and-error `downcast_mut` chains.
//
// Custom / third-party layer types return [`LayerKind::Custom`] and can
// still be accessed via [`Layer::as_any`] / [`as_any_mut`](Layer::as_any_mut).
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum LayerKind {
    /// Solid background layer used for frame clear colour / backdrop.
    Background,
    /// Terrain hillshade styling layer.
    Hillshade,
    /// Raster tile layer (slippy-map tiles).
    Tile,
    /// Vector feature layer (GeoJSON polygons, lines, points).
    Vector,
    /// 3D model placement layer.
    Model,
    /// Engine-owned visualization overlay layer.
    ///
    /// This covers reusable map-coupled scientific overlays such as
    /// grid scalars, grid extrusions, instanced columns, and point clouds.
    Visualization,
    /// A layer type not known to the engine core.
    ///
    /// Renderers or host applications can still downcast via `as_any`.
    Custom,
}

// -- Layer trait ------------------------------------------------------------

/// A named, renderable layer in the map scene.
///
pub trait Layer: Send + Sync {
    // -- Identity ----------------------------------------------------------

    /// A process-unique identifier for this layer.
    ///
    /// Override this to return the [`LayerId`] you stored at construction
    /// time. The default implementation allocates a *new* id on every
    /// call, which is correct for one-shot queries but wasteful if
    /// called repeatedly -- concrete types should store the id instead.
    fn id(&self) -> LayerId {
        LayerId::next()
    }

    /// Human-readable name for UI and debug output.
    ///
    /// Names are **not** required to be unique. Use [`id()`](Layer::id)
    /// when you need a stable, unique key.
    fn name(&self) -> &str;

    // -- Layer kind --------------------------------------------------------

    /// The concrete layer type for enum-based dispatch.
    ///
    /// The engine's update loop uses this to avoid trial-and-error
    /// downcasting.  Built-in types ([`TileLayer`](crate::layers::TileLayer),
    /// [`HillshadeLayer`](crate::layers::HillshadeLayer),
    /// [`VectorLayer`](crate::layers::VectorLayer),
    /// [`ModelLayer`](crate::layers::ModelLayer)) return their
    /// corresponding [`LayerKind`] variant.  Custom types default to
    /// [`LayerKind::Custom`].
    fn kind(&self) -> LayerKind {
        LayerKind::Custom
    }

    // -- Visibility --------------------------------------------------------

    /// Whether this layer participates in the current frame.
    ///
    /// Invisible layers are skipped entirely during the update loop
    /// (no tile fetches, no tessellation, no draw calls).
    fn visible(&self) -> bool;

    /// Toggle visibility on or off.
    fn set_visible(&mut self, visible: bool);

    // -- Opacity -----------------------------------------------------------

    /// Layer opacity in the range `[0.0, 1.0]`.
    ///
    /// Renderers should multiply the per-fragment alpha by this value.
    /// The default is fully opaque (`1.0`).
    fn opacity(&self) -> f32 {
        1.0
    }

    /// Set the layer opacity.
    ///
    /// Implementations **must** clamp the value to `[0.0, 1.0]`.
    /// The default implementation is a no-op -- override it if the
    /// concrete type stores opacity.
    fn set_opacity(&mut self, _opacity: f32) {}

    // -- Ordering ----------------------------------------------------------

    /// Hint for render ordering within the layer stack.
    ///
    /// Lower values are drawn first (further from the viewer). The
    /// engine uses the stack position as the primary sort key; `z_index`
    /// acts as a secondary key for layers that want to override their
    /// natural stack position without being physically reordered.
    ///
    /// The default is `0` (no override).
    fn z_index(&self) -> i32 {
        0
    }

    // -- Downcasting -------------------------------------------------------

    /// Borrow the layer as `&dyn Any` for concrete type access.
    ///
    /// This enables safe downcasting in the engine update loop and in
    /// renderer-specific code:
    ///
    /// ```rust,ignore
    /// if let Some(tile_layer) = layer.as_any().downcast_ref::<TileLayer>() {
    ///     // ...
    /// }
    /// ```
    fn as_any(&self) -> &dyn Any;

    /// Borrow the layer as `&mut dyn Any` for mutable concrete type access.
    fn as_any_mut(&mut self) -> &mut dyn Any;
}

// Allow `Box<dyn Layer>` to be debug-printed (e.g. in `LayerStack`
// debug output). We intentionally *don't* add `Debug` as a super-trait
// because it would force every implementor to derive/implement Debug,
// which may not always be feasible (e.g. layers holding opaque FFI
// handles). Instead we provide a blanket `Debug` impl for the trait
// object itself.
impl fmt::Debug for dyn Layer {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("Layer")
            .field("id", &self.id())
            .field("name", &self.name())
            .field("visible", &self.visible())
            .field("opacity", &self.opacity())
            .field("z_index", &self.z_index())
            .finish()
    }
}

// Also cover the `dyn Layer + Send + Sync` object type that appears
// when the trait object is stored behind `Box`.
impl fmt::Debug for dyn Layer + Send + Sync {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("Layer")
            .field("id", &self.id())
            .field("name", &self.name())
            .field("visible", &self.visible())
            .field("opacity", &self.opacity())
            .field("z_index", &self.z_index())
            .finish()
    }
}

// -- Tests -----------------------------------------------------------------

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

    /// Minimal concrete layer for testing.
    struct StubLayer {
        id: LayerId,
        name: String,
        visible: bool,
        opacity: f32,
    }

    impl StubLayer {
        fn new(name: &str) -> Self {
            Self {
                id: LayerId::next(),
                name: name.to_owned(),
                visible: true,
                opacity: 1.0,
            }
        }
    }

    impl Layer for StubLayer {
        fn id(&self) -> LayerId {
            self.id
        }
        fn name(&self) -> &str {
            &self.name
        }
        fn visible(&self) -> bool {
            self.visible
        }
        fn set_visible(&mut self, v: bool) {
            self.visible = v;
        }
        fn opacity(&self) -> f32 {
            self.opacity
        }
        fn set_opacity(&mut self, o: f32) {
            self.opacity = o.clamp(0.0, 1.0);
        }
        fn as_any(&self) -> &dyn Any {
            self
        }
        fn as_any_mut(&mut self) -> &mut dyn Any {
            self
        }
    }

    #[test]
    fn layer_id_uniqueness() {
        let a = LayerId::next();
        let b = LayerId::next();
        assert_ne!(a, b);
        assert!(b.as_u64() > a.as_u64());
    }

    #[test]
    fn layer_id_display() {
        let id = LayerId::next();
        let s = format!("{id}");
        assert!(!s.is_empty());
        assert!(s.parse::<u64>().is_ok());
    }

    #[test]
    fn layer_id_debug() {
        let id = LayerId::next();
        let s = format!("{id:?}");
        assert!(s.starts_with("LayerId("));
    }

    #[test]
    fn stub_defaults() {
        let layer = StubLayer::new("base");
        assert_eq!(layer.name(), "base");
        assert!(layer.visible());
        assert!((layer.opacity() - 1.0).abs() < f32::EPSILON);
        assert_eq!(layer.z_index(), 0);
    }

    #[test]
    fn set_visibility() {
        let mut layer = StubLayer::new("base");
        layer.set_visible(false);
        assert!(!layer.visible());
        layer.set_visible(true);
        assert!(layer.visible());
    }

    #[test]
    fn opacity_clamped() {
        let mut layer = StubLayer::new("base");
        layer.set_opacity(1.5);
        assert!((layer.opacity() - 1.0).abs() < f32::EPSILON);
        layer.set_opacity(-0.5);
        assert!((layer.opacity() - 0.0).abs() < f32::EPSILON);
        layer.set_opacity(0.42);
        assert!((layer.opacity() - 0.42).abs() < f32::EPSILON);
    }

    #[test]
    fn downcast_works() {
        let layer: Box<dyn Layer> = Box::new(StubLayer::new("down"));
        assert!(layer.as_any().downcast_ref::<StubLayer>().is_some());
    }

    #[test]
    fn downcast_mut_works() {
        let mut layer: Box<dyn Layer> = Box::new(StubLayer::new("down"));
        assert!(layer.as_any_mut().downcast_mut::<StubLayer>().is_some());
    }

    #[test]
    fn trait_object_debug() {
        let layer: Box<dyn Layer> = Box::new(StubLayer::new("dbg"));
        let s = format!("{layer:?}");
        assert!(s.contains("dbg"));
        assert!(s.contains("Layer"));
    }

    #[test]
    fn id_is_stable() {
        let layer = StubLayer::new("stable");
        let id1 = layer.id();
        let id2 = layer.id();
        assert_eq!(id1, id2);
    }
}