// use crate::{changelist::EditList, nodes::VNode}; use crate::{error::Error, innerlude::*}; use crate::{patch::Edit, scope::Scope}; use generational_arena::Arena; use std::{ any::TypeId, borrow::{Borrow, BorrowMut}, cell::RefCell, collections::{BTreeMap, BTreeSet}, rc::{Rc, Weak}, }; use thiserror::private::AsDynError; // We actually allocate the properties for components in their parent's properties // We then expose a handle to use those props for render in the form of "OpaqueComponent" pub(crate) type OpaqueComponent<'a> = dyn for<'b> Fn(Context<'b>) -> DomTree + 'a; /// 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 { /// All mounted components are arena allocated to make additions, removals, and references easy to work with /// A generational arena is used to re-use slots of deleted scopes without having to resize the underlying arena. pub components: Arena, /// The index of the root component. /// Will not be ready if the dom is fresh pub base_scope: ScopeIdx, pub(crate) update_schedule: UpdateFunnel, // a strong allocation to the "caller" for the original props #[doc(hidden)] _root_caller: Rc>, // Type of the original props. This is done so VirtualDom does not need to be generic. #[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(root: FC

, root_props: P) -> Self { let mut components = Arena::new(); // let prr = Rc::new(root_props); // the root is kept around with a "hard" allocation let root_caller: Rc = Rc::new(move |ctx| { // // let p2 = &root_props; // let p2 = prr.clone(); root(ctx, &root_props) }); // we then expose this to the component with a weak allocation let weak_caller: Weak = Rc::downgrade(&root_caller); let base_scope = components.insert_with(move |myidx| Scope::new(weak_caller, myidx, None)); Self { components, _root_caller: root_caller, base_scope, update_schedule: UpdateFunnel::default(), _root_prop_type: TypeId::of::

(), } } // consume the top of the diff machine event cycle and dump edits into the edit list pub fn step(&mut self, event: LifeCycleEvent) -> Result<()> { Ok(()) } /// Performs a *full* rebuild of the virtual dom, returning every edit required to generate the actual dom. pub fn rebuild<'s>(&'s mut self) -> Result> { // Diff from the top let mut diff_machine = DiffMachine::new(); let very_unsafe_components = &mut self.components as *mut generational_arena::Arena; let component = self .components .get_mut(self.base_scope) .ok_or_else(|| Error::FatalInternal("Acquring base component should never fail"))?; component.run_scope()?; diff_machine.diff_node(component.old_frame(), component.new_frame()); // log::debug!("New events generated: {:#?}", diff_machine.lifecycle_events); // chew down the the lifecycle events until all dirty nodes are computed while let Some(event) = diff_machine.lifecycle_events.pop_front() { match event { // A new component has been computed from the diffing algorithm // create a new component in the arena, run it, move the diffing machine to this new spot, and then diff it // this will flood the lifecycle queue with new updates LifeCycleEvent::Mount { caller, id, scope } => { log::debug!("Mounting a new component"); // We're modifying the component arena while holding onto references into the assoiated bump arenas of its children // those references are stable, even if the component arena moves around in memory, thanks to the bump arenas. // However, there is no way to convey this to rust, so we need to use unsafe to pierce through the lifetime. unsafe { let p = &mut *(very_unsafe_components); // todo, hook up the parent/child indexes properly let idx = p.insert_with(|f| Scope::new(caller, f, None)); let c = p.get_mut(idx).unwrap(); let real_scope = scope.upgrade().unwrap(); *real_scope.as_ref().borrow_mut() = Some(idx); c.run_scope()?; diff_machine.change_list.load_known_root(id); diff_machine.diff_node(c.old_frame(), c.new_frame()); } } LifeCycleEvent::PropsChanged { caller, id, scope } => { log::debug!("PROPS CHANGED"); let idx = scope.upgrade().unwrap().as_ref().borrow().unwrap(); unsafe { let p = &mut *(very_unsafe_components); let c = p.get_mut(idx).unwrap(); c.update_caller(caller); c.run_scope()?; diff_machine.change_list.load_known_root(id); diff_machine.diff_node(c.old_frame(), c.new_frame()); } // break 'render; } LifeCycleEvent::SameProps { caller, id, scope } => { log::debug!("SAME PROPS RECEIVED"); // // break 'render; } LifeCycleEvent::Remove => { // // break 'render; } LifeCycleEvent::Replace { caller, id, .. } => {} } // } else { // break 'render; // } } let edits: Vec> = diff_machine.consume(); Ok(edits) } /// 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. /// /// Note: this method is not async and does not provide suspense-like functionality. It is up to the renderer to provide the /// executor and handlers for suspense as show in the example. /// /// ```ignore /// let (sender, receiver) = channel::new(); /// sender.send(EventTrigger::start()); /// /// let mut dom = VirtualDom::new(); /// dom.suspense_handler(|event| sender.send(event)); /// /// while let Ok(diffs) = dom.progress_with_event(receiver.recv().await) { /// render(diffs); /// } /// /// ``` pub fn progress_with_event(&mut self, event: EventTrigger) -> Result> { let component = self .components .get_mut(event.component_id) .expect("Borrowing should not fail"); component.call_listener(event); /* -> call listener -> sort through accumulated queue by the top -> run each component, running its children -> add new updates to the tree of updates (these are dirty) -> */ // component.run_scope()?; // let mut diff_machine = DiffMachine::new(); // let mut diff_machine = DiffMachine::new(&self.diff_bump); // diff_machine.diff_node(component.old_frame(), component.new_frame()); // Ok(diff_machine.consume()) self.rebuild() } } #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct HierarchyMarker { source: ScopeIdx, } #[derive(Debug, Default, PartialEq, Clone)] pub struct UpdateFunnel(Rc>>); impl UpdateFunnel { fn schedule_update(&self, source: ScopeIdx) -> impl Fn() { let inner = self.clone(); move || { inner .0 .as_ref() .borrow_mut() .push(HierarchyMarker { source }) } } } macro_rules! UpdateFunnel { (root: $root:expr) => { VirtualDom::new($root) }; } // #[test] // fn test_new_vdom() { // let dom = UpdateFunnel! { // root: |ctx, props| { // ctx.render(rsx!{ // }) // } // }; // }