use std::any::TypeId;
use bevy::platform::collections::HashMap;
use super::dirty::{MAX_PROPERTIES, StyleDirty};
use super::property::AnyStyleProperty;
use super::props::CORE_STYLES;
use super::writer::{Writer, WriterMask};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct PropId(pub u16);
pub(crate) fn core_id(name: &str) -> Option<PropId> {
static IDS: std::sync::OnceLock<HashMap<&'static str, PropId>> = std::sync::OnceLock::new();
IDS.get_or_init(|| {
CORE_STYLES
.iter()
.enumerate()
.map(|(i, p)| (p.name(), PropId(i as u16)))
.collect()
})
.get(name)
.copied()
}
fn core_id_of(property: &dyn AnyStyleProperty) -> Option<PropId> {
CORE_STYLES
.iter()
.position(|p| same_declaration(*p, property))
.map(|i| PropId(i as u16))
}
#[derive(Default, Clone)]
pub struct StyleRegistry {
props: Vec<Option<&'static dyn AnyStyleProperty>>,
by_name: HashMap<&'static str, PropId>,
writers: Vec<&'static Writer>,
owners: HashMap<TypeId, (usize, &'static str)>,
readers: Vec<WriterMask>,
pub(crate) masks: CoreWriterMasks,
stamped: Vec<PropId>,
}
#[derive(Debug, Default, Clone, Copy)]
pub(crate) struct CoreWriterMasks {
pub fresh_bundled: WriterMask,
pub span: WriterMask,
pub text_color: WriterMask,
pub text_font: WriterMask,
pub transition: WriterMask,
}
impl StyleRegistry {
pub fn add(&mut self, property: &'static dyn AnyStyleProperty) {
let name = property.name();
assert!(
!self.by_name.contains_key(name),
"bevy-react: style property {name:?} registered twice"
);
if self.props.len() < CORE_STYLES.len() {
self.props.resize(CORE_STYLES.len(), None);
}
let id = match core_id_of(property) {
Some(id) => id,
None => {
assert!(
self.props.len() < MAX_PROPERTIES,
"bevy-react: more than {MAX_PROPERTIES} style properties"
);
self.props.push(None);
PropId(self.props.len() as u16 - 1)
}
};
self.props[id.0 as usize] = Some(property);
self.by_name.insert(name, id);
self.rebuild_readers();
}
pub fn add_writer(&mut self, writer: &'static Writer) {
assert!(
!self.writers.iter().any(|w| std::ptr::eq(*w, writer)),
"bevy-react: style writer registered twice"
);
assert!(
self.writers.len() < 64,
"bevy-react: more than 64 style writers"
);
assert!(
writer.attrs.is_empty(),
"bevy-react: a global style writer cannot read element attributes — list it on \
the element instead"
);
let bit = self.writers.len();
for key in writer.writes {
let (type_id, name) = key();
assert!(
self.owners.insert(type_id, (bit, name)).is_none(),
"bevy-react: two style writers write {name}"
);
}
self.writers.push(writer);
self.rebuild_readers();
use super::writers as w;
self.masks = CoreWriterMasks {
fresh_bundled: self.writer_bits(w::FRESH_BUNDLED),
span: self.writer_bits(w::SPAN_WRITERS),
text_color: self.writer_bit(&w::TEXT_COLOR_WRITER),
text_font: self.writer_bit(&w::TEXT_FONT_WRITER),
transition: self.writer_bit(&w::TRANSITION_WRITER),
};
}
pub(crate) fn writer_at(&self, bit: usize) -> &'static Writer {
self.writers[bit]
}
fn rebuild_readers(&mut self) {
self.readers = vec![WriterMask::NONE; self.props.len()];
for (bit, writer) in self.writers.iter().enumerate() {
for property in writer.reads {
if let Some(id) = self.id_of(*property) {
self.readers[id.0 as usize].0 |= 1 << bit;
}
}
}
let stamp = self.writer_bit(&super::STAMP_WRITER);
self.stamped.clear();
for (i, property) in self.props.iter().enumerate() {
if i >= CORE_STYLES.len() && property.is_some() && self.readers[i].is_empty() {
self.stamped.push(PropId(i as u16));
self.readers[i] = stamp;
}
}
}
pub fn stamped(&self) -> &[PropId] {
&self.stamped
}
pub fn validate(&self) {
let mut seen: HashMap<std::any::TypeId, &'static str> = HashMap::default();
for &id in &self.stamped {
let Some(property) = self.by_id(id) else {
continue;
};
if let Some(other) = seen.insert(property.value_type_id(), property.name()) {
panic!(
"bevy-react: style properties {other:?} and {:?} are both stamped as \
`StyleValue<{}>` — give one its own value type (a newtype) or a writer",
property.name(),
property.value_type_name()
);
}
}
}
pub fn get(&self, name: &str) -> Option<&'static dyn AnyStyleProperty> {
self.id(name).and_then(|id| self.props[id.0 as usize])
}
pub fn id(&self, name: &str) -> Option<PropId> {
self.by_name.get(name).copied()
}
pub fn id_of(&self, property: &dyn AnyStyleProperty) -> Option<PropId> {
self.id(property.name())
.filter(|id| self.props[id.0 as usize].is_some_and(|p| same_declaration(p, property)))
}
pub fn by_id(&self, id: PropId) -> Option<&'static dyn AnyStyleProperty> {
self.props.get(id.0 as usize).copied().flatten()
}
pub fn iter(&self) -> impl Iterator<Item = (PropId, &'static dyn AnyStyleProperty)> + '_ {
self.props
.iter()
.enumerate()
.filter_map(|(i, p)| p.map(|p| (PropId(i as u16), p)))
}
pub fn len(&self) -> usize {
self.by_name.len()
}
pub fn is_empty(&self) -> bool {
self.by_name.is_empty()
}
pub fn dirty_of(&self, properties: &[&dyn AnyStyleProperty]) -> StyleDirty {
let mut dirty = StyleDirty::NONE;
for property in properties {
if let Some(id) = self.id_of(*property) {
dirty.insert(id);
}
}
dirty
}
pub fn writers_for(&self, dirty: &StyleDirty) -> WriterMask {
if dirty.is_all() {
return self.all_writers();
}
let mut mask = WriterMask::NONE;
for id in dirty.ids() {
if let Some(readers) = self.readers.get(id.0 as usize) {
mask = mask.union(*readers);
}
}
mask
}
pub fn all_writers(&self) -> WriterMask {
match self.writers.len() {
64 => WriterMask::ALL,
n => WriterMask((1u64 << n) - 1),
}
}
pub fn invalidation(
&self,
dirty: &StyleDirty,
old: &super::OldValues,
style: &super::Style,
node: &super::NodeCtx<'_>,
) -> super::Invalidation {
if dirty.is_all() {
return super::Invalidation::ALL;
}
let mut invalidation = super::Invalidation::NONE;
for id in dirty.ids() {
if let Some(property) = self.by_id(id) {
let previous = old.get(id).flatten();
invalidation =
invalidation.union(property.invalidation(previous, style.get_dyn(id), node));
}
}
invalidation
}
pub fn writers_writing(&self, type_id: TypeId) -> WriterMask {
self.owners
.get(&type_id)
.map_or(WriterMask::NONE, |(bit, _)| WriterMask(1 << bit))
}
pub fn readers_of_id(&self, id: PropId) -> WriterMask {
self.readers
.get(id.0 as usize)
.copied()
.unwrap_or(WriterMask::NONE)
}
pub fn readers_of(&self, property: &dyn AnyStyleProperty) -> WriterMask {
self.id_of(property)
.map_or(WriterMask::NONE, |id| self.readers[id.0 as usize])
}
pub fn writer_bit(&self, writer: &Writer) -> WriterMask {
self.writers
.iter()
.position(|w| std::ptr::eq(*w, writer))
.map_or(WriterMask::NONE, |i| WriterMask(1 << i))
}
pub fn writer_bits(&self, writers: &[&Writer]) -> WriterMask {
writers
.iter()
.fold(WriterMask::NONE, |m, w| m.union(self.writer_bit(w)))
}
}
fn same_declaration(a: &dyn AnyStyleProperty, b: &dyn AnyStyleProperty) -> bool {
std::ptr::addr_eq(
a as *const dyn AnyStyleProperty,
b as *const dyn AnyStyleProperty,
)
}