brep_kernel/feature_pipeline/
scene_metadata.rs1use std::cell::RefCell;
20use std::collections::HashMap;
21use wasm_bindgen::prelude::*;
22
23pub(crate) type Record_ = serde_json::Map<String, serde_json::Value>;
24
25thread_local! {
26 static SCENE_METADATA: RefCell<HashMap<String, Record_>> = RefCell::new(HashMap::new());
27}
28
29fn resolve(store: &HashMap<String, Record_>, name: &str) -> Record_ {
31 let mut chain: Vec<&Record_> = Vec::new();
32 let mut seen: Vec<String> = Vec::new();
33 let mut current = name.to_string();
34 while let Some(record) = store.get(¤t) {
35 if seen.contains(¤t) {
36 break; }
38 seen.push(current.clone());
39 chain.push(record);
40 match record.get("inheritsFrom").and_then(|v| v.as_str()) {
41 Some(parent) if !parent.is_empty() => current = parent.to_string(),
42 _ => break,
43 }
44 }
45 let mut merged = Record_::new();
46 for record in chain.iter().rev() {
47 for (key, value) in record.iter() {
48 merged.insert(key.clone(), value.clone());
49 }
50 }
51 merged
52}
53
54#[wasm_bindgen]
56pub fn scene_metadata_set_json(name: &str, record_json: &str) -> Result<(), JsValue> {
57 let record: Record_ = serde_json::from_str(record_json)
58 .map_err(|error| JsValue::from_str(&format!("scene_metadata_set: {error}")))?;
59 SCENE_METADATA.with(|store| {
60 let mut store = store.borrow_mut();
61 if record.is_empty() {
62 store.remove(name);
63 } else {
64 store.insert(name.to_string(), record);
65 }
66 });
67 Ok(())
68}
69
70#[wasm_bindgen]
72pub fn scene_metadata_get_json(name: &str) -> String {
73 SCENE_METADATA.with(|store| {
74 serde_json::Value::Object(resolve(&store.borrow(), name)).to_string()
75 })
76}
77
78#[wasm_bindgen]
80pub fn scene_metadata_get_own_json(name: &str) -> String {
81 SCENE_METADATA.with(|store| {
82 store
83 .borrow()
84 .get(name)
85 .map(|record| serde_json::Value::Object(record.clone()).to_string())
86 .unwrap_or_else(|| "{}".to_string())
87 })
88}
89
90#[wasm_bindgen]
92pub fn scene_metadata_remove(name: &str) {
93 SCENE_METADATA.with(|store| {
94 store.borrow_mut().remove(name);
95 });
96}
97
98#[wasm_bindgen]
100pub fn scene_metadata_dump_json() -> String {
101 SCENE_METADATA.with(|store| {
102 serde_json::to_string(&*store.borrow()).unwrap_or_else(|_| "{}".to_string())
103 })
104}
105
106#[wasm_bindgen]
108pub fn scene_metadata_load_json(all_json: &str) -> Result<(), JsValue> {
109 let all: HashMap<String, Record_> = serde_json::from_str(all_json)
110 .map_err(|error| JsValue::from_str(&format!("scene_metadata_load: {error}")))?;
111 SCENE_METADATA.with(|store| {
112 *store.borrow_mut() = all;
113 });
114 Ok(())
115}
116
117pub(crate) fn set_record(name: &str, record: serde_json::Map<String, serde_json::Value>) {
122 SCENE_METADATA.with(|store| {
123 let mut store = store.borrow_mut();
124 if record.is_empty() {
125 store.remove(name);
126 } else {
127 store.insert(name.to_string(), record);
128 }
129 });
130}
131
132pub(crate) fn take_store() -> HashMap<String, Record_> {
137 SCENE_METADATA.with(|store| std::mem::take(&mut *store.borrow_mut()))
138}
139
140pub(crate) fn restore_store(saved: HashMap<String, Record_>) {
142 SCENE_METADATA.with(|store| {
143 *store.borrow_mut() = saved;
144 });
145}
146
147#[must_use = "the store is only isolated while the guard is alive"]
168pub struct IsolatedSceneMetadata {
169 saved: HashMap<String, Record_>,
170}
171
172impl IsolatedSceneMetadata {
173 pub fn begin() -> Self {
175 Self {
176 saved: take_store(),
177 }
178 }
179}
180
181impl Drop for IsolatedSceneMetadata {
182 fn drop(&mut self) {
183 restore_store(std::mem::take(&mut self.saved));
184 }
185}
186
187pub fn merge_record(name: &str, patch: &serde_json::Map<String, serde_json::Value>, overwrite: bool) {
190 if patch.is_empty() {
191 return;
192 }
193 SCENE_METADATA.with(|store| {
194 let mut store = store.borrow_mut();
195 let record = store.entry(name.to_string()).or_default();
196 for (key, value) in patch {
197 if overwrite || !record.contains_key(key) {
198 record.insert(key.clone(), value.clone());
199 }
200 }
201 });
202}
203
204#[wasm_bindgen]
218pub fn scene_metadata_colors_json() -> String {
219 SCENE_METADATA.with(|store| {
220 let mut out = serde_json::Map::new();
221 for (name, record) in store.borrow().iter() {
222 if let Some(color) = record
223 .get(crate::COLOR_METADATA_KEY)
224 .and_then(|value| value.as_str())
225 {
226 out.insert(name.clone(), serde_json::Value::String(color.to_string()));
227 }
228 }
229 serde_json::Value::Object(out).to_string()
230 })
231}
232
233pub(crate) fn own_record(name: &str) -> Option<serde_json::Map<String, serde_json::Value>> {
237 SCENE_METADATA.with(|store| store.borrow().get(name).cloned())
238}
239
240#[wasm_bindgen]
243pub fn topo_metadata_for_names_json(names_json: &str) -> Result<String, JsValue> {
244 let names: Vec<String> = serde_json::from_str(names_json)
245 .map_err(|error| JsValue::from_str(&format!("topo_metadata_for_names: {error}")))?;
246 SCENE_METADATA.with(|store| {
247 let store = store.borrow();
248 let mut out = serde_json::Map::new();
249 for name in names {
250 let resolved = resolve(&store, &name);
251 if !resolved.is_empty() {
252 out.insert(name, serde_json::Value::Object(resolved));
253 }
254 }
255 Ok(serde_json::Value::Object(out).to_string())
256 })
257}
258
259#[cfg(test)]
260mod tests {
261 use super::*;
262
263 #[test]
264 fn inheritance_merges_child_wins_and_cycles_stop() {
265 scene_metadata_load_json("{}").unwrap();
266 scene_metadata_set_json("base", r#"{"material":"steel","layer":"A"}"#).unwrap();
267 scene_metadata_set_json("part", r#"{"inheritsFrom":"base","layer":"B"}"#).unwrap();
268 let resolved: serde_json::Value =
269 serde_json::from_str(&scene_metadata_get_json("part")).unwrap();
270 assert_eq!(resolved["material"], "steel");
271 assert_eq!(resolved["layer"], "B", "child wins");
272 let own: serde_json::Value =
274 serde_json::from_str(&scene_metadata_get_own_json("part")).unwrap();
275 assert!(own.get("material").is_none());
276 scene_metadata_set_json("a", r#"{"inheritsFrom":"b","x":1}"#).unwrap();
278 scene_metadata_set_json("b", r#"{"inheritsFrom":"a","y":2}"#).unwrap();
279 let cyc: serde_json::Value = serde_json::from_str(&scene_metadata_get_json("a")).unwrap();
280 assert_eq!(cyc["x"], 1);
281 assert_eq!(cyc["y"], 2);
282 let dump = scene_metadata_dump_json();
284 scene_metadata_load_json("{}").unwrap();
285 assert_eq!(scene_metadata_get_own_json("part"), "{}");
286 scene_metadata_load_json(&dump).unwrap();
287 let back: serde_json::Value =
288 serde_json::from_str(&scene_metadata_get_json("part")).unwrap();
289 assert_eq!(back["material"], "steel");
290 scene_metadata_load_json("{}").unwrap();
291 }
292}