use std::sync::OnceLock;
use bevy::platform::collections::HashMap;
use super::attribute::{AnyAttribute, same_attr};
use super::decl::Element;
use super::event::handler_prop;
use super::store::AttrDirty;
use crate::ext::TextRole;
use crate::style::{Invalidation, Style, StyleDirty, StyleRegistry, Writer, WriterMask};
fn reserved(name: &str) -> bool {
crate::protocol::props::is_common_prop(name)
|| matches!(name, "children" | "key" | "ref")
|| name
.strip_prefix("on")
.and_then(|rest| rest.chars().next())
.is_some_and(|c| c.is_ascii_uppercase())
}
pub struct ElementInfo {
pub decl: &'static Element,
attr_by_name: HashMap<&'static str, u8>,
event_props: Vec<(String, u8)>,
pub(crate) global_mask: WriterMask,
attr_readers: Vec<u64>,
style_reads: Vec<StyleDirty>,
pub(crate) ignored_styles: StyleDirty,
paint_attrs: u64,
default_style: OnceLock<Option<Style>>,
}
impl ElementInfo {
pub(crate) fn build(decl: &'static Element, styles: &StyleRegistry) -> Self {
let kind = decl.name;
assert!(
decl.attrs.len() <= 64 && decl.writers.len() <= 64 && decl.events.len() <= 64,
"bevy-react: element <{kind}> declares more than 64 attributes, writers, or events"
);
let mut attr_by_name = HashMap::default();
for (i, attr) in decl.attrs.iter().enumerate() {
let name = attr.name();
assert!(
!reserved(name),
"bevy-react: element <{kind}> attribute {name:?} is a reserved prop name"
);
assert!(
attr_by_name.insert(name, i as u8).is_none(),
"bevy-react: element <{kind}> declares attribute {name:?} twice"
);
}
let index_of = |attr: &dyn AnyAttribute| -> Option<u8> {
decl.attrs
.iter()
.position(|a| same_attr(*a, attr))
.map(|i| i as u8)
};
for attr in decl.required {
assert!(
index_of(*attr).is_some(),
"bevy-react: element <{kind}> requires attribute {:?} it does not list",
attr.name()
);
}
let mut event_props = Vec::new();
for (i, event) in decl.events.iter().enumerate() {
let prop = handler_prop(event.name());
assert!(
!crate::protocol::props::is_common_prop(&prop),
"bevy-react: element <{kind}> event {:?} collides with the common prop {prop:?}",
event.name()
);
assert!(
!event_props.iter().any(|(p, _)| *p == prop),
"bevy-react: element <{kind}> declares event {:?} twice",
event.name()
);
event_props.push((prop, i as u8));
}
let mut attr_readers = vec![0u64; decl.attrs.len()];
let mut style_reads = Vec::with_capacity(decl.writers.len());
let mut owned: Vec<std::any::TypeId> = Vec::new();
let mut masked = WriterMask::NONE;
for (bit, writer) in decl.writers.iter().enumerate() {
for attr in writer.attrs {
if let Some(index) = index_of(*attr) {
attr_readers[index as usize] |= 1 << bit;
}
}
style_reads.push(styles.dirty_of(writer.reads));
for key in writer.writes {
let (type_id, name) = key();
assert!(
!owned.contains(&type_id),
"bevy-react: two writers of <{kind}> write {name}"
);
owned.push(type_id);
masked = masked.union(styles.writers_writing(type_id));
}
}
masked = masked.union(styles.writer_bits(decl.suppress));
let global_mask = if decl.flags.node_less {
if decl.flags.text == TextRole::None {
WriterMask::NONE
} else {
styles.masks.span
}
} else {
styles.all_writers().without(masked)
};
let mut ignored_styles = StyleDirty::NONE;
if !decl.flags.node_less {
let own_reads = style_reads
.iter()
.fold(StyleDirty::NONE, |acc, s| acc.union(*s));
for (id, _) in styles.iter() {
let readers = styles.readers_of_id(id);
if !readers.is_empty()
&& !readers.intersects(global_mask)
&& !own_reads.contains(id)
{
ignored_styles.insert(id);
}
}
}
let paint_attrs = decl
.attrs
.iter()
.enumerate()
.filter(|(_, a)| a.invalidation().contains(Invalidation::PAINT))
.fold(0u64, |m, (i, _)| m | 1 << i);
Self {
decl,
attr_by_name,
event_props,
global_mask,
attr_readers,
style_reads,
ignored_styles,
paint_attrs,
default_style: OnceLock::new(),
}
}
pub fn name(&self) -> &'static str {
self.decl.name
}
pub fn attr(&self, name: &str) -> Option<(u8, &'static dyn AnyAttribute)> {
self.attr_by_name
.get(name)
.map(|&i| (i, self.decl.attrs[i as usize]))
}
pub fn event_for_prop(&self, prop: &str) -> Option<u8> {
self.event_props
.iter()
.find(|(p, _)| p == prop)
.map(|(_, i)| *i)
}
pub(crate) fn writers_for(&self, style: &StyleDirty, attrs: AttrDirty) -> u64 {
let mut mask = 0u64;
for (bit, reads) in self.style_reads.iter().enumerate() {
if style.is_all() || reads.intersects(style) {
mask |= 1 << bit;
}
}
let mut bits = attrs.0;
while bits != 0 {
let i = bits.trailing_zeros() as usize;
bits &= bits - 1;
if let Some(readers) = self.attr_readers.get(i) {
mask |= readers;
}
}
mask
}
pub(crate) fn all_writers(&self) -> u64 {
match self.decl.writers.len() {
64 => u64::MAX,
n => (1u64 << n) - 1,
}
}
pub(crate) fn writer_at(&self, bit: usize) -> &'static Writer {
self.decl.writers[bit]
}
pub(crate) fn attr_invalidation(&self, dirty: AttrDirty) -> Invalidation {
if dirty.0 & self.paint_attrs != 0 {
Invalidation::PAINT
} else {
Invalidation::NONE
}
}
pub fn default_style(&self) -> Option<&Style> {
self.default_style
.get_or_init(|| self.decl.default_style.map(|f| f()))
.as_ref()
}
pub fn fill_default_style(&self, style: &mut Option<Style>) {
if let Some(default) = self.default_style() {
style.get_or_insert_default().fill_defaults(default);
}
}
}
impl std::fmt::Debug for ElementInfo {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "ElementInfo({:?})", self.decl.name)
}
}