//! A named, type-checked scratch store passed between generation passes //! (spec §4.6). //! //! Passes never call each other; they communicate only through the //! [`crate::pass::GenContext`]. The [`Blackboard`] is the side channel for data //! that isn't a tile, region, or edge — for example a list of room centers a //! partitioner computes and a connector later reads. //! //! Slots are addressed by a string **key** *and* checked against the requested //! type at retrieval. Keying by name (rather than by bare `TypeId`) lets two //! passes store the *same* type under different keys without colliding — e.g. //! two `Vec` under `"a"` and `"b"`. //! //! Determinism note (spec §7): the backing [`std::collections::HashMap`] is fine //! here because access is always *keyed* — we never iterate the map to produce //! generated output, so its (unspecified) iteration order can't leak into a //! `Map`. use std::any::Any; use std::collections::HashMap; /// A name-keyed, type-checked store of arbitrary values shared across passes. /// /// Each value is boxed as a `dyn Any` so slots of different concrete types can /// live in one map; retrieval downcasts back to the requested type and returns /// [`None`] on a type mismatch (or a missing key). #[derive(Default)] pub struct Blackboard { /// Boxed values keyed by name. `Box` erases the concrete type at /// storage time; [`get`](Blackboard::get) / [`take`](Blackboard::take) /// recover it by downcasting. slots: HashMap>, } impl Blackboard { /// Creates an empty blackboard. pub fn new() -> Self { Blackboard::default() } /// Stores `value` under `key`, replacing any existing value at that key. /// /// `T: 'static` because the value is type-erased into a `Box`, and /// only `'static` types can be `Any`. Storing a different type at an /// already-used key simply overwrites the old slot. pub fn insert(&mut self, key: &str, value: T) { self.slots.insert(key.to_string(), Box::new(value)); } /// Returns a shared reference to the value at `key`, if it exists **and** is /// of type `T`. /// /// Returns [`None`] when the key is absent *or* when the stored value is a /// different type than `T` — a wrong-type read is never a panic. pub fn get(&self, key: &str) -> Option<&T> { self.slots.get(key).and_then(|boxed| boxed.downcast_ref::()) } /// Removes the value at `key` and returns it, if it exists **and** is of /// type `T`. /// /// If the key is present but holds a different type, the value is left in /// place and [`None`] is returned (the wrong-type read does not consume the /// slot). This is the move-out counterpart to [`get`](Blackboard::get), for /// when a pass wants to take ownership of the stored value. pub fn take(&mut self, key: &str) -> Option { // Only remove if the type matches; otherwise re-insert untouched so a // type mismatch is non-destructive. match self.slots.remove(key) { Some(boxed) => match boxed.downcast::() { Ok(value) => Some(*value), Err(original) => { self.slots.insert(key.to_string(), original); None } }, None => None, } } } #[cfg(test)] mod tests { use super::*; /// Two `Vec` under different keys coexist without collision, and each /// reads back exactly what was stored. #[test] fn distinct_keys_do_not_collide() { let mut bb = Blackboard::new(); bb.insert("a", vec![1, 2, 3]); bb.insert("b", vec![4, 5]); assert_eq!(bb.get::>("a"), Some(&vec![1, 2, 3])); assert_eq!(bb.get::>("b"), Some(&vec![4, 5])); } /// `get` with the wrong type returns `None` rather than panicking. #[test] fn get_wrong_type_is_none() { let mut bb = Blackboard::new(); bb.insert("n", 42_i32); assert_eq!(bb.get::("n"), Some(&42)); // Same key, wrong type. assert_eq!(bb.get::("n"), None); } /// A missing key returns `None` for both `get` and `take`. #[test] fn missing_key_is_none() { let mut bb = Blackboard::new(); assert_eq!(bb.get::("absent"), None); assert_eq!(bb.take::("absent"), None); } /// `take` moves the value out and removes the slot. #[test] fn take_moves_value_out() { let mut bb = Blackboard::new(); bb.insert("v", vec![7, 8, 9]); assert_eq!(bb.take::>("v"), Some(vec![7, 8, 9])); // The slot is gone after a successful take. assert_eq!(bb.get::>("v"), None); } /// `take` with the wrong type returns `None` and leaves the slot intact. #[test] fn take_wrong_type_preserves_slot() { let mut bb = Blackboard::new(); bb.insert("n", 99_i32); assert_eq!(bb.take::("n"), None); // The correct-typed value is still retrievable. assert_eq!(bb.get::("n"), Some(&99)); } /// `insert` overwrites an existing slot. #[test] fn insert_overwrites() { let mut bb = Blackboard::new(); bb.insert("k", 1_i32); bb.insert("k", 2_i32); assert_eq!(bb.get::("k"), Some(&2)); } }