use std::cell::RefCell;
use std::sync::{Arc, OnceLock};
use bevy::platform::collections::HashMap;
use bevy::prelude::*;
use crate::element::{Element, ElementInfo};
#[derive(Resource, Clone)]
pub struct ExtRegistry {
decls: Vec<&'static Element>,
elements: HashMap<&'static str, Arc<ElementInfo>>,
fallback: Arc<ElementInfo>,
styles: crate::style::StyleRegistry,
}
static UNKNOWN_ELEMENT: Element = Element::new("node");
impl Default for ExtRegistry {
fn default() -> Self {
let styles = crate::style::StyleRegistry::default();
Self {
decls: Vec::new(),
elements: HashMap::default(),
fallback: Arc::new(ElementInfo::build(&UNKNOWN_ELEMENT, &styles)),
styles,
}
}
}
impl ExtRegistry {
pub fn from_app(app: &bevy::app::App) -> Self {
app.world()
.get_resource::<Self>()
.cloned()
.unwrap_or_default()
}
pub fn add_element(&mut self, element: &'static Element) {
if let Some(existing) = self.decls.iter().find(|e| e.name == element.name) {
assert!(
std::ptr::eq(*existing, element),
"bevy-react: element <{}> registered twice",
element.name
);
return;
}
self.decls.push(element);
self.rebuild_elements();
}
fn rebuild_elements(&mut self) {
self.elements = self
.decls
.iter()
.map(|e| (e.name, Arc::new(ElementInfo::build(e, &self.styles))))
.collect();
self.fallback = Arc::new(ElementInfo::build(&UNKNOWN_ELEMENT, &self.styles));
}
pub fn element_info(&self, kind: &str) -> Option<&Arc<ElementInfo>> {
self.elements.get(kind)
}
pub fn element_or_fallback(&self, kind: &str) -> &Arc<ElementInfo> {
self.elements.get(kind).unwrap_or(&self.fallback)
}
pub fn elements(&self) -> impl Iterator<Item = &'static Element> + '_ {
self.decls.iter().copied()
}
pub fn static_kind(&self, kind: &str) -> Option<&'static str> {
self.elements.get_key_value(kind).map(|(k, _)| *k)
}
pub fn flags_for_kind(&self, kind: &str) -> super::ElementFlags {
self.element_or_fallback(kind).decl.flags
}
pub fn add_style(&mut self, property: &'static dyn crate::style::AnyStyleProperty) {
self.styles.add(property);
self.rebuild_elements();
}
pub fn add_style_writer(&mut self, writer: &'static crate::style::Writer) {
self.styles.add_writer(writer);
self.rebuild_elements();
}
pub fn styles(&self) -> &crate::style::StyleRegistry {
&self.styles
}
}
#[doc(hidden)]
pub fn builtin_registry() -> ExtRegistry {
let mut registry = ExtRegistry::default();
for property in crate::style::props::CORE_STYLES {
registry.styles.add(*property);
}
for writer in crate::style::writers::CORE_WRITERS {
registry.styles.add_writer(writer);
}
for element in crate::elements::CORE_ELEMENTS {
registry.decls.push(element);
}
registry.rebuild_elements();
registry
}
pub fn core_registry() -> &'static ExtRegistry {
static CORE: OnceLock<ExtRegistry> = OnceLock::new();
CORE.get_or_init(builtin_registry)
}
#[cfg(test)]
pub(crate) fn install_builtin_registry() {
with_thread_registry(|r| r.is_none()).then(|| {
set_thread_registry(Arc::new(builtin_registry()));
});
}
#[derive(Resource, Clone, Default)]
pub struct ExtRegistrySlot(Arc<OnceLock<Arc<ExtRegistry>>>);
impl ExtRegistrySlot {
pub fn ready(registry: ExtRegistry) -> Self {
let slot = Self::default();
slot.fill(registry);
slot
}
pub fn fill(&self, registry: ExtRegistry) {
let _ = self.0.set(Arc::new(registry));
}
pub fn get(&self) -> Option<Arc<ExtRegistry>> {
self.0.get().cloned()
}
pub fn wait(&self) -> Arc<ExtRegistry> {
self.0.wait().clone()
}
}
thread_local! {
static THREAD_REGISTRY: RefCell<Option<Arc<ExtRegistry>>> = const { RefCell::new(None) };
}
pub fn set_thread_registry(registry: Arc<ExtRegistry>) {
THREAD_REGISTRY.with(|r| *r.borrow_mut() = Some(registry));
}
pub fn with_thread_registry<R>(f: impl FnOnce(Option<&ExtRegistry>) -> R) -> R {
THREAD_REGISTRY.with(|r| f(r.borrow().as_deref()))
}
pub struct FeatureHint {
pub feature: &'static str,
pub plugin: &'static str,
pub kinds: &'static [&'static str],
}
pub const KNOWN_FEATURES: &[FeatureHint] = &[
FeatureHint {
feature: "svg",
plugin: "SvgPlugin",
kinds: &[
"svg", "path", "rect", "circle", "ellipse", "line", "polyline", "polygon", "g",
],
},
FeatureHint {
feature: "anchor",
plugin: "AnchorPlugin",
kinds: &["anchor"],
},
FeatureHint {
feature: "canvas",
plugin: "CanvasPlugin",
kinds: &["canvas"],
},
FeatureHint {
feature: "portal",
plugin: "PortalPlugin",
kinds: &["portal"],
},
FeatureHint {
feature: "surface",
plugin: "SurfacePlugin",
kinds: &["surface"],
},
];
pub fn feature_hint(kind: &str) -> Option<&'static FeatureHint> {
KNOWN_FEATURES.iter().find(|f| f.kinds.contains(&kind))
}
pub(crate) fn warn_feature_missing_kind(kind: &str) {
if let Some(hint) = feature_hint(kind) {
crate::diag::report(
"featureMissing",
kind,
&format!(
"element <{kind}> needs the `{}` feature of `bevy-react` and `{}` \
(included in `ReactPlugins`)",
hint.feature, hint.plugin
),
);
}
}