123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295 |
- // use crate::{changelist::EditList, nodes::VNode};
- use crate::{dodriodiff::DiffMachine, nodes::VNode};
- use crate::{events::EventTrigger, innerlude::*};
- use any::Any;
- use bumpalo::Bump;
- use generational_arena::{Arena, Index};
- use std::{
- any::{self, TypeId},
- borrow::BorrowMut,
- cell::{RefCell, UnsafeCell},
- collections::{vec_deque, VecDeque},
- future::Future,
- marker::PhantomData,
- rc::Rc,
- sync::atomic::AtomicUsize,
- };
- /// An integrated virtual node system that progresses events and diffs UI trees.
- /// Differences are converted into patches which a renderer can use to draw the UI.
- pub struct VirtualDom {
- // pub struct VirtualDom<P: Properties> {
- /// All mounted components are arena allocated to make additions, removals, and references easy to work with
- /// A generational arean is used to re-use slots of deleted scopes without having to resize the underlying arena.
- pub(crate) components: Arena<Scope>,
- /// The index of the root component.
- base_scope: Index,
- event_queue: Rc<RefCell<VecDeque<LifecycleEvent>>>,
- // Mark the root props with P, even though they're held by the root component
- // This is done so we don't have a "generic" vdom, making it easier to hold references to it, especially when the holders
- // don't care about the generic props type
- // Most implementations that use the VirtualDom won't care about the root props anyways.
- #[doc(hidden)]
- _root_prop_type: std::any::TypeId,
- }
- impl VirtualDom {
- /// Create a new instance of the Dioxus Virtual Dom with no properties for the root component.
- ///
- /// This means that the root component must either consumes its own context, or statics are used to generate the page.
- /// The root component can access things like routing in its context.
- pub fn new(root: FC<()>) -> Self {
- Self::new_with_props(root, ())
- }
- /// Start a new VirtualDom instance with a dependent props.
- /// Later, the props can be updated by calling "update" with a new set of props, causing a set of re-renders.
- ///
- /// This is useful when a component tree can be driven by external state (IE SSR) but it would be too expensive
- /// to toss out the entire tree.
- pub fn new_with_props<P: Properties + 'static>(root: FC<P>, root_props: P) -> Self {
- // 1. Create the component arena
- // 2. Create the base scope (can never be removed)
- // 3. Create the lifecycle queue
- // 4. Create the event queue
- todo!();
- // Arena allocate all the components
- // This should make it *really* easy to store references in events and such
- // let mut components = Arena::new();
- // Create a reference to the component in the arena
- // let base_scope = components.insert(Scope::new(root, None));
- // // Create a new mount event with no root container
- // let first_event = LifecycleEvent::mount(base_scope, None, 0, root_props);
- // // Create an event queue with a mount for the base scope
- // let event_queue = Rc::new(RefCell::new(vec![first_event].into_iter().collect()));
- // let _root_prop_type = TypeId::of::<P>();
- // Self {
- // components,
- // base_scope,
- // event_queue,
- // _root_prop_type,
- // }
- }
- /// With access to the virtual dom, schedule an update to the Root component's props
- pub fn update_props<P: Properties + 'static>(&mut self, new_props: P) -> Result<()> {
- // Ensure the props match
- if TypeId::of::<P>() != self._root_prop_type {
- return Err(Error::WrongProps);
- }
- self.event_queue
- .as_ref()
- .borrow_mut()
- .push_back(LifecycleEvent {
- event_type: LifecycleType::PropsChanged {
- props: Box::new(new_props),
- },
- component_index: self.base_scope,
- });
- Ok(())
- }
- /// Schedule a future update for a component from outside the vdom!
- ///
- /// This lets services external to the virtual dom interact directly with the component and event system.
- pub fn queue_update() {}
- /// Pop an event off the event queue and process it
- /// Update the root props, and progress
- /// Takes a bump arena to allocate into, making the diff phase as fast as possible
- pub fn progress(&mut self) -> Result<()> {
- let event = self
- .event_queue
- .as_ref()
- .borrow_mut()
- .pop_front()
- .ok_or(Error::NoEvent)?;
- self.process_event(event)
- }
- /// This method is the most sophisticated way of updating the virtual dom after an external event has been triggered.
- ///
- /// Given a synthetic event, the component that triggered the event, and the index of the callback, this runs the virtual
- /// dom to completion, tagging components that need updates, compressing events together, and finally emitting a single
- /// change list.
- ///
- /// If implementing an external renderer, this is the perfect method to combine with an async event loop that waits on
- /// listeners.
- ///
- /// ```ignore
- ///
- ///
- ///
- ///
- /// ```
- pub async fn progress_with_event(&mut self, evt: EventTrigger) -> Result<()> {
- // pub async fn progress_with_event(&mut self, evt: EventTrigger) -> Result<EditList<'_>> {
- let EventTrigger {
- component_id,
- listener_id,
- event,
- } = evt;
- let component = self
- .components
- .get(component_id)
- // todo: update this with a dedicated error type so implementors know what went wrong
- .expect("Component should exist if an event was triggered");
- let listener = component
- .listeners
- .get(listener_id as usize)
- .expect("Listener should exist if it was triggered")
- .as_ref();
- // Run the callback
- // This should cause internal state to progress, dumping events into the event queue
- // todo: integrate this with a tracing mechanism exposed to a dev tool
- listener();
- // Run through our events, tagging which Indexes are receiving updates
- // Prop updates take prescedence over subscription updates
- // Run all prop updates *first* as they will cascade into children.
- // *then* run the non-prop updates that were not already covered by props
- let mut affected_components = Vec::new();
- // It's essentially draining the vec, but with some dancing to release the RefMut
- // We also want to be able to push events into the queue from processing the event
- while let Some(event) = {
- let new_evt = self.event_queue.as_ref().borrow_mut().pop_front();
- new_evt
- } {
- affected_components.push(event.component_index);
- self.process_event(event)?;
- }
- let diff_bump = Bump::new();
- let diff_machine = DiffMachine::new(&diff_bump);
- Ok(())
- }
- pub async fn progress_completely(&mut self) -> Result<()> {
- Ok(())
- }
- /// Using mutable access to the Virtual Dom, progress a given lifecycle event
- ///
- ///
- ///
- ///
- ///
- ///
- fn process_event(
- &mut self,
- LifecycleEvent {
- component_index: index,
- event_type,
- }: LifecycleEvent,
- ) -> Result<()> {
- let scope = self.components.get_mut(index).ok_or(Error::NoEvent)?;
- match event_type {
- // Component needs to be mounted to the virtual dom
- LifecycleType::Mount { to, under, props } => {
- if let Some(other) = to {
- // mount to another component
- } else {
- // mount to the root
- }
- // let g = props.as_ref();
- // scope.run(g);
- // scope.run(runner, props, dom);
- }
- // The parent for this component generated new props and the component needs update
- LifecycleType::PropsChanged { props } => {
- //
- }
- // Component was successfully mounted to the dom
- LifecycleType::Mounted {} => {
- //
- }
- // Component was removed from the DOM
- // Run any destructors and cleanup for the hooks and the dump the component
- LifecycleType::Removed {} => {
- let f = self.components.remove(index);
- // let f = dom.components.remove(index);
- }
- // Component was messaged via the internal subscription service
- LifecycleType::Messaged => {
- //
- }
- // Event from renderer was fired with a given listener ID
- //
- LifecycleType::Callback { listener_id } => {}
- // Run any post-render callbacks on a component
- LifecycleType::Rendered => {}
- }
- Ok(())
- }
- }
- pub struct LifecycleEvent {
- pub component_index: Index,
- pub event_type: LifecycleType,
- }
- /// The internal lifecycle event system is managed by these
- /// Right now, we box the properties and but them in the enum
- /// Later, we could directly call the chain of children without boxing
- /// We could try to reuse the boxes somehow
- pub enum LifecycleType {
- Mount {
- to: Option<Index>,
- under: usize,
- props: Box<dyn Properties>,
- },
- PropsChanged {
- props: Box<dyn Properties>,
- },
- Rendered,
- Mounted,
- Removed,
- Messaged,
- Callback {
- listener_id: i32,
- },
- }
- impl LifecycleEvent {
- // helper method for shortcutting to the enum type
- // probably not necessary
- fn mount<P: Properties + 'static>(
- which: Index,
- to: Option<Index>,
- under: usize,
- props: P,
- ) -> Self {
- Self {
- component_index: which,
- event_type: LifecycleType::Mount {
- to,
- under,
- props: Box::new(props),
- },
- }
- }
- }
|