bubbles/value/storage.rs
1//! [`VariableStorage`] trait and the default [`HashMapStorage`] implementation.
2
3use std::borrow::Cow;
4use std::collections::HashMap;
5
6use super::Value;
7
8/// Pluggable variable storage consumed by the runner.
9///
10/// Implement this trait to back variables with your game's own data model
11/// (e.g. an ECS component, a database row, or a save-file entry).
12///
13/// # Example
14///
15/// ```rust
16/// use bubbles::{HashMapStorage, Value, VariableStorage};
17///
18/// let mut s = HashMapStorage::new();
19/// s.set("$score", Value::Number(10.0));
20/// assert_eq!(s.get("$score"), Some(Value::Number(10.0)));
21/// assert_eq!(s.get("$missing"), None);
22/// ```
23pub trait VariableStorage {
24 /// Returns the current value of `name`, or `None` if the variable has not been set.
25 ///
26 /// This is the ergonomic read path: it always returns an owned [`Value`],
27 /// cloning if the backing store holds one by reference. New impls are
28 /// encouraged to override [`get_ref`](Self::get_ref) as well so hot
29 /// expression-evaluation paths can avoid cloning [`Value::Text`].
30 fn get(&self, name: &str) -> Option<Value>;
31 /// Stores `value` under `name`, replacing any previous value.
32 fn set(&mut self, name: &str, value: Value);
33
34 /// Returns a reference to the current value of `name`, or `None` if the
35 /// variable has not been set.
36 ///
37 /// The runner prefers this over [`get`](Self::get) during expression
38 /// evaluation so string variables can be observed without an allocation.
39 /// The default implementation simply forwards to [`get`](Self::get) and
40 /// wraps the result in [`Cow::Owned`], so existing implementations keep
41 /// working unchanged. Stores that already own their values (such as
42 /// [`HashMapStorage`]) should override this to return [`Cow::Borrowed`].
43 fn get_ref(&self, name: &str) -> Option<Cow<'_, Value>> {
44 self.get(name).map(Cow::Owned)
45 }
46}
47
48/// Default in-memory variable store backed by a [`HashMap`].
49///
50/// # Example
51///
52/// ```rust
53/// use bubbles::{HashMapStorage, Value, VariableStorage};
54///
55/// let mut storage = HashMapStorage::new();
56/// storage.set("$hp", Value::Number(100.0));
57/// storage.set("$name", Value::Text("Hero".into()));
58///
59/// assert_eq!(storage.get("$hp"), Some(Value::Number(100.0)));
60/// assert_eq!(storage.get("$name"), Some(Value::Text("Hero".into())));
61/// ```
62#[derive(Debug, Clone, Default)]
63#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
64pub struct HashMapStorage {
65 map: HashMap<String, Value>,
66}
67
68impl HashMapStorage {
69 /// Creates an empty store.
70 #[must_use]
71 pub fn new() -> Self {
72 Self::default()
73 }
74}
75
76impl VariableStorage for HashMapStorage {
77 fn get(&self, name: &str) -> Option<Value> {
78 self.map.get(name).cloned()
79 }
80
81 fn set(&mut self, name: &str, value: Value) {
82 self.map.insert(name.to_owned(), value);
83 }
84
85 fn get_ref(&self, name: &str) -> Option<Cow<'_, Value>> {
86 self.map.get(name).map(Cow::Borrowed)
87 }
88}
89
90#[cfg(test)]
91#[path = "storage_tests.rs"]
92mod tests;