// ============================================================
// Blackboard: Shared Context for Behavior Tree Nodes
//
// The Blackboard provides a key-value store that all nodes in
// a behavior tree share. It decouples the decision logic (nodes)
// from the world state, allowing nodes to query and modify
// shared state without direct references to each other.
//
// Supported value types: Bool, Int, Double, String
// Advanced: snapshot/restore for sandboxed subtree execution.
// ============================================================
///|
pub(all) enum Value {
Bool(Bool)
Int(Int)
Double(Double)
Str(String)
}
///|
/// Blackboard is a shared key-value context for all BT nodes.
pub struct Blackboard {
data : Map[String, Value]
// A stack of snapshots for nested sandbox scopes
snapshots : Array[Map[String, Value]]
}
///|
/// Create a new empty Blackboard.
pub fn Blackboard::new() -> Blackboard {
{ data: Map([]), snapshots: [] }
}
///|
/// Store a Bool under the given key.
pub fn Blackboard::set_bool(
self : Blackboard,
key : String,
val : Bool,
) -> Unit {
self.data.set(key, Value::Bool(val))
}
///|
/// Retrieve a Bool stored under the given key. Returns None if missing or wrong type.
pub fn Blackboard::get_bool(self : Blackboard, key : String) -> Bool? {
match self.data.get(key) {
Some(Value::Bool(b)) => Some(b)
_ => None
}
}
///|
/// Store an Int under the given key.
pub fn Blackboard::set_int(self : Blackboard, key : String, val : Int) -> Unit {
self.data.set(key, Value::Int(val))
}
///|
/// Retrieve an Int stored under the given key. Returns None if missing or wrong type.
pub fn Blackboard::get_int(self : Blackboard, key : String) -> Int? {
match self.data.get(key) {
Some(Value::Int(i)) => Some(i)
_ => None
}
}
///|
/// Store a Double under the given key.
pub fn Blackboard::set_double(
self : Blackboard,
key : String,
val : Double,
) -> Unit {
self.data.set(key, Value::Double(val))
}
///|
/// Retrieve a Double stored under the given key. Returns None if missing or wrong type.
pub fn Blackboard::get_double(self : Blackboard, key : String) -> Double? {
match self.data.get(key) {
Some(Value::Double(d)) => Some(d)
_ => None
}
}
///|
/// Store a String under the given key.
pub fn Blackboard::set_string(
self : Blackboard,
key : String,
val : String,
) -> Unit {
self.data.set(key, Value::Str(val))
}
///|
/// Retrieve a String stored under the given key. Returns None if missing or wrong type.
pub fn Blackboard::get_string(self : Blackboard, key : String) -> String? {
match self.data.get(key) {
Some(Value::Str(s)) => Some(s)
_ => None
}
}
///|
/// Remove a key from the blackboard. No-op if the key does not exist.
pub fn Blackboard::remove(self : Blackboard, key : String) -> Unit {
self.data.remove(key)
}
///|
/// Check whether the given key exists on the blackboard.
pub fn Blackboard::has(self : Blackboard, key : String) -> Bool {
self.data.contains(key)
}
///|
/// Take a snapshot of the current data. Subsequent changes can be rolled back
/// via `restore()`. Snapshots are stacked, so multiple nested saves are safe.
pub fn Blackboard::snapshot(self : Blackboard) -> Unit {
// Deep-copy the current map into the snapshot stack
let copy : Map[String, Value] = Map([])
self.data.each(fn(k, v) { copy.set(k, v) })
self.snapshots.push(copy)
}
///|
/// Roll back to the most recent snapshot taken with `snapshot()`.
/// If no snapshot exists, this is a no-op.
pub fn Blackboard::restore(self : Blackboard) -> Unit {
match self.snapshots.pop() {
None => ()
Some(saved) => {
// Clear current data and refill from saved snapshot
self.data.each(fn(k, _) { self.data.remove(k) })
saved.each(fn(k, v) { self.data.set(k, v) })
}
}
}
///|
/// Discard the most recent snapshot without restoring it.
pub fn Blackboard::discard_snapshot(self : Blackboard) -> Unit {
let _ = self.snapshots.pop()
}
///|
/// Returns the number of keys currently stored on the blackboard.
pub fn Blackboard::size(self : Blackboard) -> Int {
let count = Ref::new(0)
self.data.each(fn(_k, _v) { count.set(count.get() + 1) })
count.get()
}
///|
/// Clear all keys from the blackboard. Snapshots are not affected.
pub fn Blackboard::clear(self : Blackboard) -> Unit {
let keys : Array[String] = []
self.data.each(fn(k, _) { keys.push(k) })
for k in keys {
self.data.remove(k)
}
}