use std::cell::RefCell;
use std::collections::HashMap;
use wasm_bindgen::prelude::*;
pub(crate) type Record_ = serde_json::Map<String, serde_json::Value>;
thread_local! {
static SCENE_METADATA: RefCell<HashMap<String, Record_>> = RefCell::new(HashMap::new());
}
fn resolve(store: &HashMap<String, Record_>, name: &str) -> Record_ {
let mut chain: Vec<&Record_> = Vec::new();
let mut seen: Vec<String> = Vec::new();
let mut current = name.to_string();
while let Some(record) = store.get(¤t) {
if seen.contains(¤t) {
break; }
seen.push(current.clone());
chain.push(record);
match record.get("inheritsFrom").and_then(|v| v.as_str()) {
Some(parent) if !parent.is_empty() => current = parent.to_string(),
_ => break,
}
}
let mut merged = Record_::new();
for record in chain.iter().rev() {
for (key, value) in record.iter() {
merged.insert(key.clone(), value.clone());
}
}
merged
}
#[wasm_bindgen]
pub fn scene_metadata_set_json(name: &str, record_json: &str) -> Result<(), JsValue> {
let record: Record_ = serde_json::from_str(record_json)
.map_err(|error| JsValue::from_str(&format!("scene_metadata_set: {error}")))?;
SCENE_METADATA.with(|store| {
let mut store = store.borrow_mut();
if record.is_empty() {
store.remove(name);
} else {
store.insert(name.to_string(), record);
}
});
Ok(())
}
#[wasm_bindgen]
pub fn scene_metadata_get_json(name: &str) -> String {
SCENE_METADATA.with(|store| {
serde_json::Value::Object(resolve(&store.borrow(), name)).to_string()
})
}
#[wasm_bindgen]
pub fn scene_metadata_get_own_json(name: &str) -> String {
SCENE_METADATA.with(|store| {
store
.borrow()
.get(name)
.map(|record| serde_json::Value::Object(record.clone()).to_string())
.unwrap_or_else(|| "{}".to_string())
})
}
#[wasm_bindgen]
pub fn scene_metadata_remove(name: &str) {
SCENE_METADATA.with(|store| {
store.borrow_mut().remove(name);
});
}
#[wasm_bindgen]
pub fn scene_metadata_dump_json() -> String {
SCENE_METADATA.with(|store| {
serde_json::to_string(&*store.borrow()).unwrap_or_else(|_| "{}".to_string())
})
}
#[wasm_bindgen]
pub fn scene_metadata_load_json(all_json: &str) -> Result<(), JsValue> {
let all: HashMap<String, Record_> = serde_json::from_str(all_json)
.map_err(|error| JsValue::from_str(&format!("scene_metadata_load: {error}")))?;
SCENE_METADATA.with(|store| {
*store.borrow_mut() = all;
});
Ok(())
}
pub(crate) fn set_record(name: &str, record: serde_json::Map<String, serde_json::Value>) {
SCENE_METADATA.with(|store| {
let mut store = store.borrow_mut();
if record.is_empty() {
store.remove(name);
} else {
store.insert(name.to_string(), record);
}
});
}
pub(crate) fn take_store() -> HashMap<String, Record_> {
SCENE_METADATA.with(|store| std::mem::take(&mut *store.borrow_mut()))
}
pub(crate) fn restore_store(saved: HashMap<String, Record_>) {
SCENE_METADATA.with(|store| {
*store.borrow_mut() = saved;
});
}
#[must_use = "the store is only isolated while the guard is alive"]
pub struct IsolatedSceneMetadata {
saved: HashMap<String, Record_>,
}
impl IsolatedSceneMetadata {
pub fn begin() -> Self {
Self {
saved: take_store(),
}
}
}
impl Drop for IsolatedSceneMetadata {
fn drop(&mut self) {
restore_store(std::mem::take(&mut self.saved));
}
}
pub fn merge_record(name: &str, patch: &serde_json::Map<String, serde_json::Value>, overwrite: bool) {
if patch.is_empty() {
return;
}
SCENE_METADATA.with(|store| {
let mut store = store.borrow_mut();
let record = store.entry(name.to_string()).or_default();
for (key, value) in patch {
if overwrite || !record.contains_key(key) {
record.insert(key.clone(), value.clone());
}
}
});
}
#[wasm_bindgen]
pub fn scene_metadata_colors_json() -> String {
SCENE_METADATA.with(|store| {
let mut out = serde_json::Map::new();
for (name, record) in store.borrow().iter() {
if let Some(color) = record
.get(crate::COLOR_METADATA_KEY)
.and_then(|value| value.as_str())
{
out.insert(name.clone(), serde_json::Value::String(color.to_string()));
}
}
serde_json::Value::Object(out).to_string()
})
}
pub(crate) fn own_record(name: &str) -> Option<serde_json::Map<String, serde_json::Value>> {
SCENE_METADATA.with(|store| store.borrow().get(name).cloned())
}
#[wasm_bindgen]
pub fn topo_metadata_for_names_json(names_json: &str) -> Result<String, JsValue> {
let names: Vec<String> = serde_json::from_str(names_json)
.map_err(|error| JsValue::from_str(&format!("topo_metadata_for_names: {error}")))?;
SCENE_METADATA.with(|store| {
let store = store.borrow();
let mut out = serde_json::Map::new();
for name in names {
let resolved = resolve(&store, &name);
if !resolved.is_empty() {
out.insert(name, serde_json::Value::Object(resolved));
}
}
Ok(serde_json::Value::Object(out).to_string())
})
}