Skip to main content

satteri_plugin_api/
data.rs

1use rustc_hash::FxHashMap;
2use std::any::{Any, TypeId};
3
4/// A value that can be stored in the untyped data map (interoperable with JS node.data).
5#[derive(Debug, Clone)]
6pub enum DataValue {
7    String(String),
8    Bool(bool),
9    Int(i64),
10    Float(f64),
11    Null,
12}
13
14impl DataValue {
15    pub fn as_str(&self) -> Option<&str> {
16        if let DataValue::String(s) = self {
17            Some(s)
18        } else {
19            None
20        }
21    }
22    pub fn as_bool(&self) -> Option<bool> {
23        if let DataValue::Bool(b) = self {
24            Some(*b)
25        } else {
26            None
27        }
28    }
29    pub fn as_int(&self) -> Option<i64> {
30        if let DataValue::Int(i) = self {
31            Some(*i)
32        } else {
33            None
34        }
35    }
36}
37
38/// Untyped data map: maps (node_id, key) → DataValue.
39/// This is the Rust-side of the JS node.data map.
40/// When a JS plugin runs after a Rust plugin, this gets synced to the JS DataMap.
41#[derive(Debug, Default)]
42pub struct DataMap {
43    inner: FxHashMap<(u32, String), DataValue>,
44}
45
46impl DataMap {
47    pub fn new() -> Self {
48        Self::default()
49    }
50
51    pub fn set(&mut self, node_id: u32, key: &str, value: DataValue) {
52        self.inner.insert((node_id, key.to_string()), value);
53    }
54
55    pub fn get(&self, node_id: u32, key: &str) -> Option<&DataValue> {
56        self.inner.get(&(node_id, key.to_string()))
57    }
58
59    pub fn remove(&mut self, node_id: u32, key: &str) {
60        self.inner.remove(&(node_id, key.to_string()));
61    }
62
63    pub fn has(&self, node_id: u32, key: &str) -> bool {
64        self.inner.contains_key(&(node_id, key.to_string()))
65    }
66
67    /// Iterate all entries for a given node_id
68    pub fn entries_for_node(&self, node_id: u32) -> impl Iterator<Item = (&str, &DataValue)> {
69        self.inner
70            .iter()
71            .filter(move |((id, _), _)| *id == node_id)
72            .map(|((_, key), val)| (key.as_str(), val))
73    }
74
75    pub fn len(&self) -> usize {
76        self.inner.len()
77    }
78    pub fn is_empty(&self) -> bool {
79        self.inner.is_empty()
80    }
81}
82
83/// Typed data map: stores strongly-typed data keyed by TypeId + node_id.
84/// Rust-only, never crosses to JS.
85pub struct TypedDataMap {
86    inner: FxHashMap<(u32, TypeId), Box<dyn Any + Send + Sync>>,
87}
88
89impl TypedDataMap {
90    pub fn new() -> Self {
91        Self {
92            inner: FxHashMap::default(),
93        }
94    }
95
96    pub fn set<T: Any + Send + Sync>(&mut self, node_id: u32, value: T) {
97        self.inner
98            .insert((node_id, TypeId::of::<T>()), Box::new(value));
99    }
100
101    pub fn get<T: Any + Send + Sync>(&self, node_id: u32) -> Option<&T> {
102        self.inner
103            .get(&(node_id, TypeId::of::<T>()))
104            .and_then(|boxed| boxed.downcast_ref::<T>())
105    }
106
107    pub fn remove<T: Any + Send + Sync>(&mut self, node_id: u32) {
108        self.inner.remove(&(node_id, TypeId::of::<T>()));
109    }
110
111    pub fn has<T: Any + Send + Sync>(&self, node_id: u32) -> bool {
112        self.inner.contains_key(&(node_id, TypeId::of::<T>()))
113    }
114}
115
116impl Default for TypedDataMap {
117    fn default() -> Self {
118        Self::new()
119    }
120}