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}