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}