Skip to main content

bubbles/value/
storage.rs

1//! [`VariableStorage`] trait and the default [`HashMapStorage`] implementation.
2
3use std::collections::HashMap;
4
5use super::Value;
6
7/// Pluggable variable storage consumed by the runner.
8///
9/// Implement this trait to back variables with your game's own data model
10/// (e.g. an ECS component, a database row, or a save-file entry).
11///
12/// # Example
13///
14/// ```rust
15/// use bubbles::{HashMapStorage, Value, VariableStorage};
16///
17/// let mut s = HashMapStorage::new();
18/// s.set("$score", Value::Number(10.0));
19/// assert_eq!(s.get("$score"), Some(Value::Number(10.0)));
20/// assert_eq!(s.get("$missing"), None);
21/// ```
22pub trait VariableStorage {
23    /// Returns the current value of `name`, or `None` if the variable has not been set.
24    fn get(&self, name: &str) -> Option<Value>;
25    /// Stores `value` under `name`, replacing any previous value.
26    fn set(&mut self, name: &str, value: Value);
27}
28
29/// Default in-memory variable store backed by a [`HashMap`].
30///
31/// # Example
32///
33/// ```rust
34/// use bubbles::{HashMapStorage, Value, VariableStorage};
35///
36/// let mut storage = HashMapStorage::new();
37/// storage.set("$hp", Value::Number(100.0));
38/// storage.set("$name", Value::Text("Hero".into()));
39///
40/// assert_eq!(storage.get("$hp"), Some(Value::Number(100.0)));
41/// assert_eq!(storage.get("$name"), Some(Value::Text("Hero".into())));
42/// ```
43#[derive(Debug, Clone, Default)]
44#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
45pub struct HashMapStorage {
46    map: HashMap<String, Value>,
47}
48
49impl HashMapStorage {
50    /// Creates an empty store.
51    #[must_use]
52    pub fn new() -> Self {
53        Self::default()
54    }
55}
56
57impl VariableStorage for HashMapStorage {
58    fn get(&self, name: &str) -> Option<Value> {
59        self.map.get(name).cloned()
60    }
61
62    fn set(&mut self, name: &str, value: Value) {
63        self.map.insert(name.to_owned(), value);
64    }
65}
66
67#[cfg(test)]
68mod tests {
69    use super::*;
70
71    #[test]
72    fn get_unset_returns_none() {
73        let s = HashMapStorage::new();
74        assert!(s.get("$x").is_none());
75    }
76
77    #[test]
78    fn set_then_get_round_trips() {
79        let mut s = HashMapStorage::new();
80        s.set("$x", Value::Number(42.0));
81        assert_eq!(s.get("$x"), Some(Value::Number(42.0)));
82    }
83
84    #[test]
85    fn overwrite_updates_value() {
86        let mut s = HashMapStorage::new();
87        s.set("$x", Value::Bool(true));
88        s.set("$x", Value::Bool(false));
89        assert_eq!(s.get("$x"), Some(Value::Bool(false)));
90    }
91}