use std::collections::BTreeMap;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Mutex, MutexGuard};
#[derive(Debug, Clone)]
pub struct Published {
pub json: String,
pub doc: &'static str,
pub schema: Option<serde_json::Value>,
}
#[derive(Debug, Default)]
pub struct Registry {
map: BTreeMap<String, Published>,
}
static REGISTRY: Mutex<Registry> = Mutex::new(Registry { map: BTreeMap::new() });
static ENABLED: AtomicBool = AtomicBool::new(cfg!(target_arch = "wasm32"));
pub fn enabled() -> bool {
ENABLED.load(Ordering::Relaxed)
}
pub fn set_enabled(on: bool) {
ENABLED.store(on, Ordering::Relaxed);
}
pub fn lock() -> MutexGuard<'static, Registry> {
REGISTRY.lock().unwrap_or_else(|p| p.into_inner())
}
pub fn publish(name: &str, doc: &'static str, json: &str) {
mirror_to_js(name, json);
lock().insert(name, doc, json, None);
}
#[cfg(feature = "automation")]
pub fn publish_typed<T: serde::Serialize + schemars::JsonSchema>(name: &str, doc: &'static str, value: &T) {
let json = serde_json::to_string(value).unwrap_or_else(|_| "null".into());
mirror_to_js(name, &json);
let mut r = lock();
let schema = if r.map.contains_key(name) { None } else { serde_json::to_value(schemars::schema_for!(T)).ok() };
r.insert(name, doc, &json, schema);
}
impl Registry {
fn insert(&mut self, name: &str, doc: &'static str, json: &str, schema: Option<serde_json::Value>) {
match self.map.get_mut(name) {
Some(p) => {
p.json.clear();
p.json.push_str(json);
p.doc = doc;
if schema.is_some() {
p.schema = schema;
}
}
None => {
self.map.insert(name.to_string(), Published { json: json.to_string(), doc, schema });
}
}
}
pub fn get(&self, name: &str) -> Option<&Published> {
self.map.get(name)
}
pub fn names(&self) -> Vec<&str> {
self.map.keys().map(String::as_str).collect()
}
pub fn iter(&self) -> impl Iterator<Item = (&str, &Published)> {
self.map.iter().map(|(k, v)| (k.as_str(), v))
}
pub fn hit_rects(&self, prefix: Option<&str>) -> BTreeMap<String, [f32; 4]> {
let mut out = BTreeMap::new();
for (name, p) in &self.map {
let Some(panel) = hit_blob_panel(name) else { continue };
let Ok(serde_json::Value::Object(map)) = serde_json::from_str::<serde_json::Value>(&p.json) else { continue };
for (key, rect) in map {
let full = format!("{panel}/{key}");
if let Some(pre) = prefix {
if !full.starts_with(pre) {
continue;
}
}
if let Some(a) = rect.as_array() {
if a.len() == 4 {
let f = |i: usize| a[i].as_f64().unwrap_or(0.0) as f32;
out.insert(full, [f(0), f(1), f(2), f(3)]);
}
}
}
}
out
}
}
pub fn hit_blob_panel(name: &str) -> Option<String> {
match name {
"__brepHit" => Some("history".into()),
"__brepToolbar" => Some("toolbar".into()),
"__brepWorkbenchToolbar" => Some("wbtoolbar".into()),
_ => name
.strip_prefix("__brep")
.and_then(|n| n.strip_suffix("Hit"))
.filter(|n| !n.is_empty())
.map(|n| n.to_ascii_lowercase()),
}
}
#[cfg(target_arch = "wasm32")]
fn mirror_to_js(name: &str, json: &str) {
if let Some(win) = web_sys::window() {
let _ = js_sys::Reflect::set(
&win,
&wasm_bindgen::JsValue::from_str(name),
&wasm_bindgen::JsValue::from_str(json),
);
}
}
#[cfg(not(target_arch = "wasm32"))]
fn mirror_to_js(_name: &str, _json: &str) {}