use std::{
any::{
Any,
TypeId,
},
collections::BTreeMap,
marker::PhantomData,
ops::Deref,
sync::Arc,
};
use parking_lot::RwLock;
use rustc_hash::{
FxHashMap,
FxHashSet,
};
use shipyard::{
Borrow,
BorrowInfo,
Component,
Unique,
UniqueView,
View,
WorkloadSystem,
};
use crate::{
node::{
FromAnyValue,
NodeType,
},
node_ref::{
NodeMaskBuilder,
NodeView,
},
real_dom::{
DirtyNodesResult,
SendAnyMapWrapper,
},
tree::{
TreeRef,
TreeRefView,
},
NodeId,
NodeMask,
SendAnyMap,
};
#[derive(Default)]
struct DirtyNodes {
nodes_dirty: FxHashSet<NodeId>,
}
impl DirtyNodes {
pub fn add_node(&mut self, node_id: NodeId) {
self.nodes_dirty.insert(node_id);
}
pub fn is_empty(&self) -> bool {
self.nodes_dirty.is_empty()
}
pub fn pop(&mut self) -> Option<NodeId> {
self.nodes_dirty.iter().next().copied().inspect(|id| {
self.nodes_dirty.remove(id);
})
}
}
#[derive(Clone, Unique)]
pub struct DirtyNodeStates {
dirty: Arc<FxHashMap<TypeId, RwLock<BTreeMap<u16, DirtyNodes>>>>,
}
impl DirtyNodeStates {
pub fn with_passes(passes: impl Iterator<Item = TypeId>) -> Self {
Self {
dirty: Arc::new(
passes
.map(|pass| (pass, RwLock::new(BTreeMap::new())))
.collect(),
),
}
}
pub fn insert(&self, pass_id: TypeId, node_id: NodeId, height: u16) {
if let Some(btree) = self.dirty.get(&pass_id) {
let mut write = btree.write();
if let Some(entry) = write.get_mut(&height) {
entry.add_node(node_id);
} else {
let mut entry = DirtyNodes::default();
entry.add_node(node_id);
write.insert(height, entry);
}
}
}
fn pop_front(&self, pass_id: TypeId) -> Option<(u16, NodeId)> {
let mut values = self.dirty.get(&pass_id)?.write();
let mut value = values.first_entry()?;
let height = *value.key();
let ids = value.get_mut();
let id = ids.pop()?;
if ids.is_empty() {
value.remove_entry();
}
Some((height, id))
}
fn pop_back(&self, pass_id: TypeId) -> Option<(u16, NodeId)> {
let mut values = self.dirty.get(&pass_id)?.write();
let mut value = values.last_entry()?;
let height = *value.key();
let ids = value.get_mut();
let id = ids.pop()?;
if ids.is_empty() {
value.remove_entry();
}
Some((height, id))
}
}
pub trait State<V: FromAnyValue + Send + Sync = ()>: Any + Send + Sync {
type ParentDependencies: Dependancy;
type ChildDependencies: Dependancy;
type NodeDependencies: Dependancy;
const NODE_MASK: NodeMaskBuilder<'static>;
const TRAVERSE_SHADOW_DOM: bool = false;
fn update<'a>(
&mut self,
node_view: NodeView<V>,
node: <Self::NodeDependencies as Dependancy>::ElementBorrowed<'a>,
parent: Option<<Self::ParentDependencies as Dependancy>::ElementBorrowed<'a>>,
children: Vec<<Self::ChildDependencies as Dependancy>::ElementBorrowed<'a>>,
context: &SendAnyMap,
) -> bool;
fn create<'a>(
node_view: NodeView<V>,
node: <Self::NodeDependencies as Dependancy>::ElementBorrowed<'a>,
parent: Option<<Self::ParentDependencies as Dependancy>::ElementBorrowed<'a>>,
children: Vec<<Self::ChildDependencies as Dependancy>::ElementBorrowed<'a>>,
context: &SendAnyMap,
) -> Self;
fn workload_system(
type_id: TypeId,
dependants: Arc<Dependants>,
pass_direction: PassDirection,
) -> WorkloadSystem;
fn to_type_erased() -> TypeErasedState<V>
where
Self: Sized,
{
let node_mask = Self::NODE_MASK.build();
TypeErasedState {
this_type_id: TypeId::of::<Self>(),
parent_dependancies_ids: Self::ParentDependencies::type_ids()
.iter()
.copied()
.collect(),
child_dependancies_ids: Self::ChildDependencies::type_ids()
.iter()
.copied()
.collect(),
node_dependancies_ids: Self::NodeDependencies::type_ids().iter().copied().collect(),
dependants: Default::default(),
mask: node_mask,
pass_direction: pass_direction::<V, Self>(),
enter_shadow_dom: Self::TRAVERSE_SHADOW_DOM,
workload: Self::workload_system,
phantom: PhantomData,
}
}
}
fn pass_direction<V: FromAnyValue + Send + Sync, S: State<V>>() -> PassDirection {
if S::ChildDependencies::type_ids()
.iter()
.any(|type_id| *type_id == TypeId::of::<S>())
{
PassDirection::ChildToParent
} else if S::ParentDependencies::type_ids()
.iter()
.any(|type_id| *type_id == TypeId::of::<S>())
{
PassDirection::ParentToChild
} else {
PassDirection::AnyOrder
}
}
#[doc(hidden)]
#[derive(Borrow, BorrowInfo)]
pub struct RunPassView<'a, V: FromAnyValue + Send + Sync = ()> {
pub tree: TreeRefView<'a>,
pub node_type: View<'a, NodeType<V>>,
dirty_nodes_result: UniqueView<'a, DirtyNodesResult>,
node_states: UniqueView<'a, DirtyNodeStates>,
any_map: UniqueView<'a, SendAnyMapWrapper>,
}
#[doc(hidden)]
pub fn run_pass<V: FromAnyValue + Send + Sync>(
type_id: TypeId,
dependants: Arc<Dependants>,
pass_direction: PassDirection,
view: RunPassView<V>,
mut update_node: impl FnMut(NodeId, &SendAnyMap, u16) -> bool,
) {
let RunPassView {
tree,
dirty_nodes_result: nodes_updated,
node_states: dirty,
any_map: ctx,
..
} = view;
let ctx = ctx.as_ref();
match pass_direction {
PassDirection::ParentToChild => {
while let Some((height, id)) = dirty.pop_front(type_id) {
if (update_node)(id, ctx, height) {
nodes_updated.insert(id);
dependants.mark_dirty(&dirty, id, &tree, height);
}
}
}
PassDirection::ChildToParent => {
while let Some((height, id)) = dirty.pop_back(type_id) {
if (update_node)(id, ctx, height) {
nodes_updated.insert(id);
dependants.mark_dirty(&dirty, id, &tree, height);
}
}
}
PassDirection::AnyOrder => {
while let Some((height, id)) = dirty.pop_back(type_id) {
if (update_node)(id, ctx, height) {
nodes_updated.insert(id);
dependants.mark_dirty(&dirty, id, &tree, height);
}
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct Dependant {
pub(crate) type_id: TypeId,
pub(crate) enter_shadow_dom: bool,
}
#[derive(Default, Debug, Clone, PartialEq, Eq)]
pub struct Dependants {
pub(crate) parent: Vec<Dependant>,
pub(crate) child: Vec<Dependant>,
pub(crate) node: Vec<TypeId>,
}
impl Dependants {
fn mark_dirty(&self, dirty: &DirtyNodeStates, id: NodeId, tree: &impl TreeRef, height: u16) {
for &Dependant {
type_id,
enter_shadow_dom,
} in &self.child
{
for id in tree.children_ids_advanced(id, enter_shadow_dom) {
dirty.insert(type_id, id, height + 1);
}
}
for &Dependant {
type_id,
enter_shadow_dom,
} in &self.parent
{
if let Some(id) = tree.parent_id_advanced(id, enter_shadow_dom) {
dirty.insert(type_id, id, height - 1);
}
}
for dependant in &self.node {
dirty.insert(*dependant, id, height);
}
}
}
pub struct TypeErasedState<V: FromAnyValue + Send = ()> {
pub(crate) this_type_id: TypeId,
pub(crate) parent_dependancies_ids: FxHashSet<TypeId>,
pub(crate) child_dependancies_ids: FxHashSet<TypeId>,
pub(crate) node_dependancies_ids: FxHashSet<TypeId>,
pub(crate) dependants: Arc<Dependants>,
pub(crate) mask: NodeMask,
pub(crate) workload: fn(TypeId, Arc<Dependants>, PassDirection) -> WorkloadSystem,
pub(crate) pass_direction: PassDirection,
pub(crate) enter_shadow_dom: bool,
phantom: PhantomData<V>,
}
impl<V: FromAnyValue + Send> TypeErasedState<V> {
pub(crate) fn create_workload(&self) -> WorkloadSystem {
(self.workload)(
self.this_type_id,
self.dependants.clone(),
self.pass_direction,
)
}
pub(crate) fn combined_dependancy_type_ids(&self) -> impl Iterator<Item = TypeId> + '_ {
self.parent_dependancies_ids
.iter()
.chain(self.child_dependancies_ids.iter())
.chain(self.node_dependancies_ids.iter())
.copied()
}
}
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
pub enum PassDirection {
ParentToChild,
ChildToParent,
AnyOrder,
}
pub trait Dependancy {
type ElementBorrowed<'a>;
fn type_ids() -> Box<[TypeId]> {
Box::new([])
}
}
macro_rules! impl_dependancy {
($($t:ident),*) => {
impl< $($t: Send + Sync + Component),* > Dependancy for ($($t,)*) {
type ElementBorrowed<'a> = ($(DependancyView<'a, $t>,)*);
fn type_ids() -> Box<[TypeId]> {
Box::new([$(TypeId::of::<$t>()),*])
}
}
};
}
pub struct DependancyView<'a, T> {
inner: &'a T,
}
impl<'a, T> DependancyView<'a, T> {
#[doc(hidden)]
pub fn new(inner: &'a T) -> Self {
Self { inner }
}
}
impl<'a, T> Deref for DependancyView<'a, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.inner
}
}
impl_dependancy!();
impl_dependancy!(A);
impl_dependancy!(A, B);
impl_dependancy!(A, B, C);
impl_dependancy!(A, B, C, D);
impl_dependancy!(A, B, C, D, E);
impl_dependancy!(A, B, C, D, E, F);
impl_dependancy!(A, B, C, D, E, F, G);
impl_dependancy!(A, B, C, D, E, F, G, H);
impl_dependancy!(A, B, C, D, E, F, G, H, I);
impl_dependancy!(A, B, C, D, E, F, G, H, I, J);