freya_native_core/
passes.rs

1use std::{
2    any::{
3        Any,
4        TypeId,
5    },
6    collections::BTreeMap,
7    marker::PhantomData,
8    ops::Deref,
9    sync::Arc,
10};
11
12use parking_lot::RwLock;
13use rustc_hash::{
14    FxHashMap,
15    FxHashSet,
16};
17use shipyard::{
18    Borrow,
19    BorrowInfo,
20    Component,
21    Unique,
22    UniqueView,
23    View,
24    WorkloadSystem,
25};
26
27use crate::{
28    node::{
29        FromAnyValue,
30        NodeType,
31    },
32    node_ref::{
33        NodeMaskBuilder,
34        NodeView,
35    },
36    real_dom::SendAnyMapWrapper,
37    tree::{
38        TreeRef,
39        TreeRefView,
40    },
41    NodeId,
42    NodeMask,
43    SendAnyMap,
44};
45
46#[derive(Default)]
47struct DirtyNodes {
48    nodes_dirty: FxHashSet<NodeId>,
49}
50
51impl DirtyNodes {
52    pub fn add_node(&mut self, node_id: NodeId) {
53        self.nodes_dirty.insert(node_id);
54    }
55
56    pub fn is_empty(&self) -> bool {
57        self.nodes_dirty.is_empty()
58    }
59
60    pub fn pop(&mut self) -> Option<NodeId> {
61        self.nodes_dirty.iter().next().copied().inspect(|id| {
62            self.nodes_dirty.remove(id);
63        })
64    }
65}
66
67/// Tracks the dirty nodes sorted by height for each pass. We resolve passes based on the height of the node in order to avoid resolving any node twice in a pass.
68#[derive(Clone, Unique)]
69pub struct DirtyNodeStates {
70    dirty: Arc<FxHashMap<TypeId, RwLock<BTreeMap<u16, DirtyNodes>>>>,
71}
72
73impl DirtyNodeStates {
74    pub fn with_passes(passes: impl Iterator<Item = TypeId>) -> Self {
75        Self {
76            dirty: Arc::new(
77                passes
78                    .map(|pass| (pass, RwLock::new(BTreeMap::new())))
79                    .collect(),
80            ),
81        }
82    }
83
84    pub fn insert(&self, pass_id: TypeId, node_id: NodeId, height: u16) {
85        if let Some(btree) = self.dirty.get(&pass_id) {
86            let mut write = btree.write();
87            if let Some(entry) = write.get_mut(&height) {
88                entry.add_node(node_id);
89            } else {
90                let mut entry = DirtyNodes::default();
91                entry.add_node(node_id);
92                write.insert(height, entry);
93            }
94        }
95    }
96
97    fn pop_front(&self, pass_id: TypeId) -> Option<(u16, NodeId)> {
98        let mut values = self.dirty.get(&pass_id)?.write();
99        let mut value = values.first_entry()?;
100        let height = *value.key();
101        let ids = value.get_mut();
102        let id = ids.pop()?;
103        if ids.is_empty() {
104            value.remove_entry();
105        }
106
107        Some((height, id))
108    }
109
110    fn pop_back(&self, pass_id: TypeId) -> Option<(u16, NodeId)> {
111        let mut values = self.dirty.get(&pass_id)?.write();
112        let mut value = values.last_entry()?;
113        let height = *value.key();
114        let ids = value.get_mut();
115        let id = ids.pop()?;
116        if ids.is_empty() {
117            value.remove_entry();
118        }
119
120        Some((height, id))
121    }
122}
123
124/// A state that is automatically inserted in a node with dependencies.
125pub trait State<V: FromAnyValue + Send + Sync = ()>: Any + Send + Sync {
126    /// This is a tuple of (T: State, ..) of states read from the parent required to update this state
127    type ParentDependencies: Dependancy;
128    /// This is a tuple of (T: State, ..) of states read from the children required to update this state
129    type ChildDependencies: Dependancy;
130    /// This is a tuple of (T: State, ..) of states read from the node required to update this state
131    type NodeDependencies: Dependancy;
132    /// This is a mask of what aspects of the node are required to update this state
133    const NODE_MASK: NodeMaskBuilder<'static>;
134
135    /// Does the state traverse into the shadow dom or pass over it. This should be true for layout and false for styles
136    const TRAVERSE_SHADOW_DOM: bool = false;
137
138    /// Filter out types of nodes that don't need this State
139    fn allow_node(node_type: &NodeType<V>) -> bool {
140        !node_type.is_text()
141    }
142
143    /// Update this state in a node, returns if the state was updated
144    fn update<'a>(
145        &mut self,
146        node_view: NodeView<V>,
147        node: <Self::NodeDependencies as Dependancy>::ElementBorrowed<'a>,
148        parent: Option<<Self::ParentDependencies as Dependancy>::ElementBorrowed<'a>>,
149        children: Vec<<Self::ChildDependencies as Dependancy>::ElementBorrowed<'a>>,
150        context: &SendAnyMap,
151    ) -> bool;
152
153    /// Create a new instance of this state
154    fn create<'a>(
155        node_view: NodeView<V>,
156        node: <Self::NodeDependencies as Dependancy>::ElementBorrowed<'a>,
157        parent: Option<<Self::ParentDependencies as Dependancy>::ElementBorrowed<'a>>,
158        children: Vec<<Self::ChildDependencies as Dependancy>::ElementBorrowed<'a>>,
159        context: &SendAnyMap,
160    ) -> Self;
161
162    /// Create a workload system for this state
163    fn workload_system(
164        type_id: TypeId,
165        dependants: Arc<Dependants>,
166        pass_direction: PassDirection,
167    ) -> WorkloadSystem;
168
169    /// Converts to a type erased version of the trait
170    fn to_type_erased() -> TypeErasedState<V>
171    where
172        Self: Sized,
173    {
174        let node_mask = Self::NODE_MASK.build();
175        TypeErasedState {
176            this_type_id: TypeId::of::<Self>(),
177            parent_dependancies_ids: Self::ParentDependencies::type_ids()
178                .iter()
179                .copied()
180                .collect(),
181            child_dependancies_ids: Self::ChildDependencies::type_ids()
182                .iter()
183                .copied()
184                .collect(),
185            node_dependancies_ids: Self::NodeDependencies::type_ids().iter().copied().collect(),
186            dependants: Default::default(),
187            mask: node_mask,
188            pass_direction: pass_direction::<V, Self>(),
189            enter_shadow_dom: Self::TRAVERSE_SHADOW_DOM,
190            workload: Self::workload_system,
191            phantom: PhantomData,
192        }
193    }
194}
195
196fn pass_direction<V: FromAnyValue + Send + Sync, S: State<V>>() -> PassDirection {
197    if S::ChildDependencies::type_ids()
198        .iter()
199        .any(|type_id| *type_id == TypeId::of::<S>())
200    {
201        PassDirection::ChildToParent
202    } else if S::ParentDependencies::type_ids()
203        .iter()
204        .any(|type_id| *type_id == TypeId::of::<S>())
205    {
206        PassDirection::ParentToChild
207    } else {
208        PassDirection::AnyOrder
209    }
210}
211
212#[doc(hidden)]
213#[derive(Borrow, BorrowInfo)]
214pub struct RunPassView<'a, V: FromAnyValue + Send + Sync = ()> {
215    pub tree: TreeRefView<'a>,
216    pub node_type: View<'a, NodeType<V>>,
217    node_states: UniqueView<'a, DirtyNodeStates>,
218    any_map: UniqueView<'a, SendAnyMapWrapper>,
219}
220
221// This is used by the macro
222/// Updates the given pass, marking any nodes that were changed
223#[doc(hidden)]
224pub fn run_pass<V: FromAnyValue + Send + Sync>(
225    type_id: TypeId,
226    dependants: &Dependants,
227    pass_direction: PassDirection,
228    view: RunPassView<V>,
229    mut update_node: impl FnMut(NodeId, &SendAnyMap, u16) -> bool,
230) {
231    let RunPassView {
232        tree,
233        node_states: dirty,
234        any_map: ctx,
235        ..
236    } = view;
237    let ctx = ctx.as_ref();
238    match pass_direction {
239        PassDirection::ParentToChild => {
240            while let Some((height, id)) = dirty.pop_front(type_id) {
241                if (update_node)(id, ctx, height) {
242                    dependants.mark_dirty(&dirty, id, &tree, height);
243                }
244            }
245        }
246        PassDirection::ChildToParent => {
247            while let Some((height, id)) = dirty.pop_back(type_id) {
248                if (update_node)(id, ctx, height) {
249                    dependants.mark_dirty(&dirty, id, &tree, height);
250                }
251            }
252        }
253        PassDirection::AnyOrder => {
254            while let Some((height, id)) = dirty.pop_back(type_id) {
255                if (update_node)(id, ctx, height) {
256                    dependants.mark_dirty(&dirty, id, &tree, height);
257                }
258            }
259        }
260    }
261}
262
263#[derive(Debug, Clone, Copy, PartialEq, Eq)]
264pub(crate) struct Dependant {
265    pub(crate) type_id: TypeId,
266    pub(crate) enter_shadow_dom: bool,
267}
268
269/// The states that depend on this state
270#[derive(Default, Debug, Clone, PartialEq, Eq)]
271pub struct Dependants {
272    /// The states in the parent direction that should be invalidated when this state is invalidated
273    pub(crate) parent: Vec<Dependant>,
274    /// The states in the child direction that should be invalidated when this state is invalidated
275    pub(crate) child: Vec<Dependant>,
276    /// The states in the node direction that should be invalidated when this state is invalidated
277    pub(crate) node: Vec<TypeId>,
278}
279
280impl Dependants {
281    fn mark_dirty(&self, dirty: &DirtyNodeStates, id: NodeId, tree: &impl TreeRef, height: u16) {
282        for &Dependant {
283            type_id,
284            enter_shadow_dom,
285        } in &self.child
286        {
287            for id in tree.children_ids_advanced(id, enter_shadow_dom) {
288                dirty.insert(type_id, id, height + 1);
289            }
290        }
291
292        for &Dependant {
293            type_id,
294            enter_shadow_dom,
295        } in &self.parent
296        {
297            if let Some(id) = tree.parent_id_advanced(id, enter_shadow_dom) {
298                dirty.insert(type_id, id, height - 1);
299            }
300        }
301
302        for dependant in &self.node {
303            dirty.insert(*dependant, id, height);
304        }
305    }
306}
307
308/// A type erased version of [`State`] that can be added to the [`crate::prelude::RealDom`] with [`crate::prelude::RealDom::new`]
309pub struct TypeErasedState<V: FromAnyValue + Send = ()> {
310    pub(crate) this_type_id: TypeId,
311    pub(crate) parent_dependancies_ids: FxHashSet<TypeId>,
312    pub(crate) child_dependancies_ids: FxHashSet<TypeId>,
313    pub(crate) node_dependancies_ids: FxHashSet<TypeId>,
314    pub(crate) dependants: Arc<Dependants>,
315    pub(crate) mask: NodeMask,
316    pub(crate) workload: fn(TypeId, Arc<Dependants>, PassDirection) -> WorkloadSystem,
317    pub(crate) pass_direction: PassDirection,
318    pub(crate) enter_shadow_dom: bool,
319    phantom: PhantomData<V>,
320}
321
322impl<V: FromAnyValue + Send> TypeErasedState<V> {
323    pub(crate) fn create_workload(&self) -> WorkloadSystem {
324        (self.workload)(
325            self.this_type_id,
326            self.dependants.clone(),
327            self.pass_direction,
328        )
329    }
330
331    pub(crate) fn combined_dependancy_type_ids(&self) -> impl Iterator<Item = TypeId> + '_ {
332        self.parent_dependancies_ids
333            .iter()
334            .chain(self.child_dependancies_ids.iter())
335            .chain(self.node_dependancies_ids.iter())
336            .copied()
337    }
338}
339
340/// The direction that a pass should be run in
341#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
342pub enum PassDirection {
343    /// The pass should be run from the root to the leaves
344    ParentToChild,
345    /// The pass should be run from the leaves to the root
346    ChildToParent,
347    /// The pass can be run in any order
348    AnyOrder,
349}
350
351/// A trait that is implemented for all the dependancies of a [`State`]
352pub trait Dependancy {
353    /// A tuple with all the elements of the dependancy as [`DependancyView`]
354    type ElementBorrowed<'a>;
355
356    /// Returns a list of all the [`TypeId`]s of the elements in the dependancy
357    fn type_ids() -> Box<[TypeId]> {
358        Box::new([])
359    }
360}
361
362macro_rules! impl_dependancy {
363    ($($t:ident),*) => {
364        impl< $($t: Send + Sync + Component),* > Dependancy for ($($t,)*) {
365            type ElementBorrowed<'a> = ($(DependancyView<'a, $t>,)*);
366
367            fn type_ids() -> Box<[TypeId]> {
368                Box::new([$(TypeId::of::<$t>()),*])
369            }
370        }
371    };
372}
373
374// TODO: track what components are actually read to update subscriptions
375// making this a wrapper makes it possible to implement that optimization without a breaking change
376/// A immutable view of a [`State`]
377pub struct DependancyView<'a, T> {
378    inner: &'a T,
379}
380
381impl<'a, T> DependancyView<'a, T> {
382    // This should only be used in the macro. This is not a public API or stable
383    #[doc(hidden)]
384    pub fn new(inner: &'a T) -> Self {
385        Self { inner }
386    }
387}
388
389impl<T> Deref for DependancyView<'_, T> {
390    type Target = T;
391
392    fn deref(&self) -> &Self::Target {
393        self.inner
394    }
395}
396
397impl_dependancy!();
398impl_dependancy!(A);
399impl_dependancy!(A, B);
400impl_dependancy!(A, B, C);
401impl_dependancy!(A, B, C, D);
402impl_dependancy!(A, B, C, D, E);
403impl_dependancy!(A, B, C, D, E, F);
404impl_dependancy!(A, B, C, D, E, F, G);
405impl_dependancy!(A, B, C, D, E, F, G, H);
406impl_dependancy!(A, B, C, D, E, F, G, H, I);
407impl_dependancy!(A, B, C, D, E, F, G, H, I, J);