Skip to main content

rustial_engine/
layer.rs

1//! # Map layer abstraction
2//!
3//! This module defines the [`Layer`] trait, the central abstraction for
4//! everything that can be drawn on the map. Concrete layer types (raster
5//! tiles, vector features, 3D models, custom overlays, ...) implement this
6//! trait so the engine can manage them uniformly through the
7//! [`LayerStack`](crate::layers::LayerStack).
8//!
9//! ## Design principles
10//!
11//! * **Framework-agnostic** -- the trait carries no GPU or windowing types.
12//!   Renderers (Bevy, WGPU, ...) consume the trait objects via downcasting
13//!   through [`as_any`](Layer::as_any) / [`as_any_mut`](Layer::as_any_mut).
14//!
15//! * **Thread-safe** -- `Send + Sync` is required so the engine state can
16//!   be shared across threads (e.g. behind an `RwLock` in `MapHandle`).
17//!
18//! * **Identifiable** -- every layer receives a unique [`LayerId`] at
19//!   construction time. Names are human-readable labels and *not*
20//!   guaranteed to be unique; use `id()` when you need a stable key.
21//!
22//! * **Minimal required surface** -- only `name`, `visible`, `set_visible`,
23//!   `as_any`, and `as_any_mut` *must* be implemented. Everything else
24//!   (`opacity`, `z_index`, `id`) ships with sensible defaults so new
25//!   layer types can start small and opt into more control later.
26//! ---------------------------------------------------------------------------
27
28use std::any::Any;
29use std::fmt;
30use std::sync::atomic::{AtomicU64, Ordering};
31
32// -- LayerId ---------------------------------------------------------------
33
34/// Monotonically increasing counter for [`LayerId`] generation.
35///
36/// Starts at 1 so that `LayerId(0)` can serve as an obvious sentinel /
37/// "no id" value in debug output.
38static NEXT_LAYER_ID: AtomicU64 = AtomicU64::new(1);
39
40/// A globally unique, opaque identifier for a map layer.
41///
42/// Identifiers are assigned via [`LayerId::next()`] using a lock-free
43/// atomic counter and are guaranteed unique within the lifetime of the
44/// process. They are *not* stable across process restarts and should
45/// not be serialized.
46///
47/// ```
48/// use rustial_engine::LayerId;
49///
50/// let a = LayerId::next();
51/// let b = LayerId::next();
52/// assert_ne!(a, b);
53/// ```
54#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
55pub struct LayerId(u64);
56
57impl LayerId {
58    /// Allocate the next unique layer identifier.
59    ///
60    /// This is a lock-free operation (`Relaxed` ordering is sufficient
61    /// because uniqueness only requires that no two calls return the
62    /// same value; there is no happens-before dependency on the counter
63    /// itself).
64    #[inline]
65    pub fn next() -> Self {
66        Self(NEXT_LAYER_ID.fetch_add(1, Ordering::Relaxed))
67    }
68
69    /// Return the raw `u64` value (useful for logging / diagnostics).
70    #[inline]
71    pub fn as_u64(self) -> u64 {
72        self.0
73    }
74}
75
76impl fmt::Debug for LayerId {
77    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
78        write!(f, "LayerId({})", self.0)
79    }
80}
81
82impl fmt::Display for LayerId {
83    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
84        write!(f, "{}", self.0)
85    }
86}
87
88// -- LayerKind -------------------------------------------------------------
89
90/// Discriminant for concrete layer types known to the engine.
91//
92// Used by [`Layer::kind()`] to enable O(1) enum dispatch in the engine
93// update loop, replacing trial-and-error `downcast_mut` chains.
94//
95// Custom / third-party layer types return [`LayerKind::Custom`] and can
96// still be accessed via [`Layer::as_any`] / [`as_any_mut`](Layer::as_any_mut).
97#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
98pub enum LayerKind {
99    /// Solid background layer used for frame clear colour / backdrop.
100    Background,
101    /// Terrain hillshade styling layer.
102    Hillshade,
103    /// Raster tile layer (slippy-map tiles).
104    Tile,
105    /// Vector feature layer (GeoJSON polygons, lines, points).
106    Vector,
107    /// 3D model placement layer.
108    Model,
109    /// Engine-owned visualization overlay layer.
110    ///
111    /// This covers reusable map-coupled scientific overlays such as
112    /// grid scalars, grid extrusions, instanced columns, and point clouds.
113    Visualization,
114    /// A layer type not known to the engine core.
115    ///
116    /// Renderers or host applications can still downcast via `as_any`.
117    Custom,
118}
119
120// -- Layer trait ------------------------------------------------------------
121
122/// A named, renderable layer in the map scene.
123///
124pub trait Layer: Send + Sync {
125    // -- Identity ----------------------------------------------------------
126
127    /// A process-unique identifier for this layer.
128    ///
129    /// Override this to return the [`LayerId`] you stored at construction
130    /// time. The default implementation allocates a *new* id on every
131    /// call, which is correct for one-shot queries but wasteful if
132    /// called repeatedly -- concrete types should store the id instead.
133    fn id(&self) -> LayerId {
134        LayerId::next()
135    }
136
137    /// Human-readable name for UI and debug output.
138    ///
139    /// Names are **not** required to be unique. Use [`id()`](Layer::id)
140    /// when you need a stable, unique key.
141    fn name(&self) -> &str;
142
143    // -- Layer kind --------------------------------------------------------
144
145    /// The concrete layer type for enum-based dispatch.
146    ///
147    /// The engine's update loop uses this to avoid trial-and-error
148    /// downcasting.  Built-in types ([`TileLayer`](crate::layers::TileLayer),
149    /// [`HillshadeLayer`](crate::layers::HillshadeLayer),
150    /// [`VectorLayer`](crate::layers::VectorLayer),
151    /// [`ModelLayer`](crate::layers::ModelLayer)) return their
152    /// corresponding [`LayerKind`] variant.  Custom types default to
153    /// [`LayerKind::Custom`].
154    fn kind(&self) -> LayerKind {
155        LayerKind::Custom
156    }
157
158    // -- Visibility --------------------------------------------------------
159
160    /// Whether this layer participates in the current frame.
161    ///
162    /// Invisible layers are skipped entirely during the update loop
163    /// (no tile fetches, no tessellation, no draw calls).
164    fn visible(&self) -> bool;
165
166    /// Toggle visibility on or off.
167    fn set_visible(&mut self, visible: bool);
168
169    // -- Opacity -----------------------------------------------------------
170
171    /// Layer opacity in the range `[0.0, 1.0]`.
172    ///
173    /// Renderers should multiply the per-fragment alpha by this value.
174    /// The default is fully opaque (`1.0`).
175    fn opacity(&self) -> f32 {
176        1.0
177    }
178
179    /// Set the layer opacity.
180    ///
181    /// Implementations **must** clamp the value to `[0.0, 1.0]`.
182    /// The default implementation is a no-op -- override it if the
183    /// concrete type stores opacity.
184    fn set_opacity(&mut self, _opacity: f32) {}
185
186    // -- Ordering ----------------------------------------------------------
187
188    /// Hint for render ordering within the layer stack.
189    ///
190    /// Lower values are drawn first (further from the viewer). The
191    /// engine uses the stack position as the primary sort key; `z_index`
192    /// acts as a secondary key for layers that want to override their
193    /// natural stack position without being physically reordered.
194    ///
195    /// The default is `0` (no override).
196    fn z_index(&self) -> i32 {
197        0
198    }
199
200    // -- Downcasting -------------------------------------------------------
201
202    /// Borrow the layer as `&dyn Any` for concrete type access.
203    ///
204    /// This enables safe downcasting in the engine update loop and in
205    /// renderer-specific code:
206    ///
207    /// ```rust,ignore
208    /// if let Some(tile_layer) = layer.as_any().downcast_ref::<TileLayer>() {
209    ///     // ...
210    /// }
211    /// ```
212    fn as_any(&self) -> &dyn Any;
213
214    /// Borrow the layer as `&mut dyn Any` for mutable concrete type access.
215    fn as_any_mut(&mut self) -> &mut dyn Any;
216}
217
218// Allow `Box<dyn Layer>` to be debug-printed (e.g. in `LayerStack`
219// debug output). We intentionally *don't* add `Debug` as a super-trait
220// because it would force every implementor to derive/implement Debug,
221// which may not always be feasible (e.g. layers holding opaque FFI
222// handles). Instead we provide a blanket `Debug` impl for the trait
223// object itself.
224impl fmt::Debug for dyn Layer {
225    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
226        f.debug_struct("Layer")
227            .field("id", &self.id())
228            .field("name", &self.name())
229            .field("visible", &self.visible())
230            .field("opacity", &self.opacity())
231            .field("z_index", &self.z_index())
232            .finish()
233    }
234}
235
236// Also cover the `dyn Layer + Send + Sync` object type that appears
237// when the trait object is stored behind `Box`.
238impl fmt::Debug for dyn Layer + Send + Sync {
239    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
240        f.debug_struct("Layer")
241            .field("id", &self.id())
242            .field("name", &self.name())
243            .field("visible", &self.visible())
244            .field("opacity", &self.opacity())
245            .field("z_index", &self.z_index())
246            .finish()
247    }
248}
249
250// -- Tests -----------------------------------------------------------------
251
252#[cfg(test)]
253mod tests {
254    use super::*;
255
256    /// Minimal concrete layer for testing.
257    struct StubLayer {
258        id: LayerId,
259        name: String,
260        visible: bool,
261        opacity: f32,
262    }
263
264    impl StubLayer {
265        fn new(name: &str) -> Self {
266            Self {
267                id: LayerId::next(),
268                name: name.to_owned(),
269                visible: true,
270                opacity: 1.0,
271            }
272        }
273    }
274
275    impl Layer for StubLayer {
276        fn id(&self) -> LayerId {
277            self.id
278        }
279        fn name(&self) -> &str {
280            &self.name
281        }
282        fn visible(&self) -> bool {
283            self.visible
284        }
285        fn set_visible(&mut self, v: bool) {
286            self.visible = v;
287        }
288        fn opacity(&self) -> f32 {
289            self.opacity
290        }
291        fn set_opacity(&mut self, o: f32) {
292            self.opacity = o.clamp(0.0, 1.0);
293        }
294        fn as_any(&self) -> &dyn Any {
295            self
296        }
297        fn as_any_mut(&mut self) -> &mut dyn Any {
298            self
299        }
300    }
301
302    #[test]
303    fn layer_id_uniqueness() {
304        let a = LayerId::next();
305        let b = LayerId::next();
306        assert_ne!(a, b);
307        assert!(b.as_u64() > a.as_u64());
308    }
309
310    #[test]
311    fn layer_id_display() {
312        let id = LayerId::next();
313        let s = format!("{id}");
314        assert!(!s.is_empty());
315        assert!(s.parse::<u64>().is_ok());
316    }
317
318    #[test]
319    fn layer_id_debug() {
320        let id = LayerId::next();
321        let s = format!("{id:?}");
322        assert!(s.starts_with("LayerId("));
323    }
324
325    #[test]
326    fn stub_defaults() {
327        let layer = StubLayer::new("base");
328        assert_eq!(layer.name(), "base");
329        assert!(layer.visible());
330        assert!((layer.opacity() - 1.0).abs() < f32::EPSILON);
331        assert_eq!(layer.z_index(), 0);
332    }
333
334    #[test]
335    fn set_visibility() {
336        let mut layer = StubLayer::new("base");
337        layer.set_visible(false);
338        assert!(!layer.visible());
339        layer.set_visible(true);
340        assert!(layer.visible());
341    }
342
343    #[test]
344    fn opacity_clamped() {
345        let mut layer = StubLayer::new("base");
346        layer.set_opacity(1.5);
347        assert!((layer.opacity() - 1.0).abs() < f32::EPSILON);
348        layer.set_opacity(-0.5);
349        assert!((layer.opacity() - 0.0).abs() < f32::EPSILON);
350        layer.set_opacity(0.42);
351        assert!((layer.opacity() - 0.42).abs() < f32::EPSILON);
352    }
353
354    #[test]
355    fn downcast_works() {
356        let layer: Box<dyn Layer> = Box::new(StubLayer::new("down"));
357        assert!(layer.as_any().downcast_ref::<StubLayer>().is_some());
358    }
359
360    #[test]
361    fn downcast_mut_works() {
362        let mut layer: Box<dyn Layer> = Box::new(StubLayer::new("down"));
363        assert!(layer.as_any_mut().downcast_mut::<StubLayer>().is_some());
364    }
365
366    #[test]
367    fn trait_object_debug() {
368        let layer: Box<dyn Layer> = Box::new(StubLayer::new("dbg"));
369        let s = format!("{layer:?}");
370        assert!(s.contains("dbg"));
371        assert!(s.contains("Layer"));
372    }
373
374    #[test]
375    fn id_is_stable() {
376        let layer = StubLayer::new("stable");
377        let id1 = layer.id();
378        let id2 = layer.id();
379        assert_eq!(id1, id2);
380    }
381}