diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index 41bd45ceb..d63b155ed 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -23,6 +23,7 @@ pub(crate) mod lazynodes; pub(crate) mod mutations; pub(crate) mod nodes; pub(crate) mod scope; +pub(crate) mod scopearena; pub(crate) mod test_dom; pub(crate) mod threadsafe; pub(crate) mod util; @@ -43,6 +44,7 @@ pub(crate) mod innerlude { pub use crate::mutations::*; pub use crate::nodes::*; pub use crate::scope::*; + pub use crate::scopearena::*; pub use crate::test_dom::*; pub use crate::threadsafe::*; pub use crate::util::*; diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index fc7b7f76e..362eaa274 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -157,7 +157,7 @@ impl<'src> VNode<'src> { pub fn children(&self) -> &[VNode<'src>] { match &self { VNode::Fragment(f) => &f.children, - VNode::Component(c) => &c.children, + VNode::Component(c) => todo!("children are not accessible through this"), _ => &[], } } @@ -319,7 +319,7 @@ pub struct Listener<'bump> { pub struct VComponent<'src> { pub key: Option<&'src str>, - pub associated_scope: Cell>, + pub associated_scope: Cell>, // Function pointer to the FC that was used to generate this component pub user_fc: *const (), diff --git a/packages/core/src/scope.rs b/packages/core/src/scope.rs index 27bffd90f..026a53e65 100644 --- a/packages/core/src/scope.rs +++ b/packages/core/src/scope.rs @@ -1,5 +1,6 @@ use crate::innerlude::*; +use futures_channel::mpsc::UnboundedSender; use fxhash::FxHashMap; use std::{ any::{Any, TypeId}, @@ -42,9 +43,13 @@ pub type Context<'a> = &'a ScopeInner; /// use case they might have. pub struct ScopeInner { // Book-keeping about our spot in the arena - pub(crate) parent_idx: Option, + // yes, a raw pointer + // it's bump allocated so it's stable + // the safety of parent pointers is guaranteed by the logic in this crate + pub(crate) parent_scope: Option<*mut ScopeInner>, pub(crate) our_arena_idx: ScopeId, pub(crate) height: u32, + pub(crate) subtree: Cell, pub(crate) is_subtree_root: Cell, @@ -67,7 +72,7 @@ pub struct ScopeInner { // todo: move this into a centralized place - is more memory efficient pub(crate) shared_contexts: RefCell>>, - pub(crate) shared: EventChannel, + pub(crate) sender: UnboundedSender, } pub struct SelfReferentialItems<'a> { @@ -78,47 +83,11 @@ pub struct SelfReferentialItems<'a> { pub(crate) pending_effects: Vec>, } +pub struct ScopeVcomp { + // important things +} + impl ScopeInner { - // we are being created in the scope of an existing component (where the creator_node lifetime comes into play) - // we are going to break this lifetime by force in order to save it on ourselves. - // To make sure that the lifetime isn't truly broken, we receive a Weak RC so we can't keep it around after the parent dies. - // This should never happen, but is a good check to keep around - // - // Scopes cannot be made anywhere else except for this file - // Therefore, their lifetimes are connected exclusively to the virtual dom - pub(crate) fn new( - vcomp: &VComponent, - our_arena_idx: ScopeId, - parent_idx: Option, - height: u32, - subtree: u32, - shared: EventChannel, - ) -> Self { - let vcomp = unsafe { std::mem::transmute(vcomp as *const VComponent) }; - - Self { - shared, - parent_idx, - our_arena_idx, - height, - subtree: Cell::new(subtree), - is_subtree_root: Cell::new(false), - frames: ActiveFrame::new(), - vcomp, - - hooks: Default::default(), - shared_contexts: Default::default(), - - items: RefCell::new(SelfReferentialItems { - listeners: Default::default(), - borrowed_props: Default::default(), - suspended_nodes: Default::default(), - tasks: Default::default(), - pending_effects: Default::default(), - }), - } - } - /// This method cleans up any references to data held within our hook list. This prevents mutable aliasing from /// causing UB in our tree. /// @@ -131,7 +100,7 @@ impl ScopeInner { /// /// This also makes sure that drop order is consistent and predictable. All resources that rely on being dropped will /// be dropped. - pub(crate) fn ensure_drop_safety(&mut self, pool: &ResourcePool) { + pub(crate) fn ensure_drop_safety(&mut self) { // make sure we drop all borrowed props manually to guarantee that their drop implementation is called before we // run the hooks (which hold an &mut Reference) // right now, we don't drop @@ -147,13 +116,13 @@ impl ScopeInner { .get() .expect("VComponents should be associated with a valid Scope"); - if let Some(scope) = pool.get_scope_mut(&scope_id) { - scope.ensure_drop_safety(pool); + let scope = unsafe { &mut *scope_id }; - todo!("drop the component's props"); - // let mut drop_props = comp.drop_props.borrow_mut().take().unwrap(); - // drop_props(); - } + scope.ensure_drop_safety(); + + todo!("drop the component's props"); + // let mut drop_props = comp.drop_props.borrow_mut().take().unwrap(); + // drop_props(); }); // Now that all the references are gone, we can safely drop our own references in our listeners. @@ -204,7 +173,8 @@ impl ScopeInner { } // run the list of effects - pub(crate) fn run_effects(&mut self, pool: &ResourcePool) { + pub(crate) fn run_effects(&mut self) { + // pub(crate) fn run_effects(&mut self, pool: &ResourcePool) { for mut effect in self.items.get_mut().pending_effects.drain(..) { effect(); } @@ -213,11 +183,11 @@ impl ScopeInner { /// Render this component. /// /// Returns true if the scope completed successfully and false if running failed (IE a None error was propagated). - pub(crate) fn run_scope<'sel>(&'sel mut self, pool: &ResourcePool) -> bool { + pub(crate) fn run_scope<'sel>(&'sel mut self) -> bool { // Cycle to the next frame and then reset it // This breaks any latent references, invalidating every pointer referencing into it. // Remove all the outdated listeners - self.ensure_drop_safety(pool); + self.ensure_drop_safety(); // Safety: // - We dropped the listeners, so no more &mut T can be used while these are held @@ -356,7 +326,10 @@ impl ScopeInner { /// assert_eq!(base.parent(), None); /// ``` pub fn parent(&self) -> Option { - self.parent_idx + match self.parent_scope { + Some(p) => Some(unsafe { &*p }.our_arena_idx), + None => None, + } } /// Get the ID of this Scope within this Dioxus VirtualDOM. @@ -381,7 +354,7 @@ impl ScopeInner { /// ## Notice: you should prefer using prepare_update and get_scope_id pub fn schedule_update(&self) -> Rc { // pub fn schedule_update(&self) -> Rc { - let chan = self.shared.sender.clone(); + let chan = self.sender.clone(); let id = self.scope_id(); Rc::new(move || { chan.unbounded_send(SchedulerMsg::Immediate(id)); @@ -394,7 +367,7 @@ impl ScopeInner { /// /// This method should be used when you want to schedule an update for a component pub fn schedule_update_any(&self) -> Rc { - let chan = self.shared.sender.clone(); + let chan = self.sender.clone(); Rc::new(move |id| { chan.unbounded_send(SchedulerMsg::Immediate(id)); }) @@ -411,8 +384,7 @@ impl ScopeInner { /// /// `ScopeId` is not unique for the lifetime of the VirtualDom - a ScopeId will be reused if a component is unmounted. pub fn needs_update_any(&self, id: ScopeId) { - self.shared - .sender + self.sender .unbounded_send(SchedulerMsg::Immediate(id)) .unwrap(); } @@ -508,7 +480,7 @@ impl ScopeInner { /// rsx!(cx, div { "hello {state.0}" }) /// } /// ``` - pub fn provide_state(self, value: T) + pub fn provide_state(&self, value: T) where T: 'static, { @@ -520,11 +492,21 @@ impl ScopeInner { } /// Try to retrieve a SharedState with type T from the any parent Scope. - pub fn consume_state(self) -> Option> { - let getter = &self.shared.get_shared_context; - let ty = TypeId::of::(); - let idx = self.our_arena_idx; - getter(idx, ty).map(|f| f.downcast().unwrap()) + pub fn consume_state(&self) -> Option> { + if let Some(shared) = self.shared_contexts.borrow().get(&TypeId::of::()) { + Some(shared.clone().downcast::().unwrap()) + } else { + let mut search_parent = self.parent_scope; + + while let Some(parent_ptr) = search_parent { + let parent = unsafe { &*parent_ptr }; + if let Some(shared) = parent.shared_contexts.borrow().get(&TypeId::of::()) { + return Some(shared.clone().downcast::().unwrap()); + } + search_parent = parent.parent_scope; + } + None + } } /// Create a new subtree with this scope as the root of the subtree. @@ -542,7 +524,7 @@ impl ScopeInner { /// rsx!(cx, div { "Subtree {id}"}) /// }; /// ``` - pub fn create_subtree(self) -> Option { + pub fn create_subtree(&self) -> Option { self.new_subtree() } @@ -559,7 +541,7 @@ impl ScopeInner { /// rsx!(cx, div { "Subtree {id}"}) /// }; /// ``` - pub fn get_current_subtree(self) -> u32 { + pub fn get_current_subtree(&self) -> u32 { self.subtree() } diff --git a/packages/core/src/scopearena.rs b/packages/core/src/scopearena.rs new file mode 100644 index 000000000..e202723c7 --- /dev/null +++ b/packages/core/src/scopearena.rs @@ -0,0 +1,76 @@ +use std::cell::{Cell, RefCell}; + +use bumpalo::{boxed::Box as BumpBox, Bump}; +use futures_channel::mpsc::UnboundedSender; + +use crate::innerlude::*; + +// a slab-like arena with stable references even when new scopes are allocated +// uses a bump arena as a backing +// +// has an internal heuristics engine to pre-allocate arenas to the right size +pub(crate) struct ScopeArena { + bump: Bump, + scopes: Vec<*mut ScopeInner>, + free_scopes: Vec, +} + +impl ScopeArena { + pub fn new() -> Self { + Self { + bump: Bump::new(), + scopes: Vec::new(), + free_scopes: Vec::new(), + } + } + + pub fn new_with_key( + &mut self, + fc_ptr: *const (), + vcomp: &VComponent, + parent_scope: Option<*mut ScopeInner>, + height: u32, + subtree: u32, + sender: UnboundedSender, + ) -> ScopeId { + if let Some(id) = self.free_scopes.pop() { + // have already called drop on it - the slot is still chillin tho + let scope = unsafe { &mut *self.scopes[id.0 as usize] }; + + todo!("override the scope contents"); + id + } else { + let id = ScopeId(self.scopes.len()); + + let vcomp = unsafe { std::mem::transmute(vcomp as *const VComponent) }; + + let new_scope = ScopeInner { + sender, + parent_scope, + our_arena_idx: id, + height, + subtree: Cell::new(subtree), + is_subtree_root: Cell::new(false), + frames: ActiveFrame::new(), + vcomp, + + hooks: Default::default(), + shared_contexts: Default::default(), + + items: RefCell::new(SelfReferentialItems { + listeners: Default::default(), + borrowed_props: Default::default(), + suspended_nodes: Default::default(), + tasks: Default::default(), + pending_effects: Default::default(), + }), + }; + + let stable = self.bump.alloc(new_scope); + self.scopes.push(stable); + id + } + } + + // scopes never get dropepd +} diff --git a/packages/core/src/test_dom.rs b/packages/core/src/test_dom.rs index 332a23402..206276efc 100644 --- a/packages/core/src/test_dom.rs +++ b/packages/core/src/test_dom.rs @@ -6,15 +6,16 @@ use crate::nodes::IntoVNode; pub struct TestDom { bump: Bump, - scheduler: Scheduler, + // scheduler: Scheduler, } impl TestDom { pub fn new() -> TestDom { let bump = Bump::new(); let (sender, receiver) = futures_channel::mpsc::unbounded::(); - let scheduler = Scheduler::new(sender, receiver, 10, 100); - TestDom { bump, scheduler } + todo!() + // let scheduler = Scheduler::new(sender, receiver, 10, 100); + // TestDom { bump, scheduler } } pub fn new_factory(&self) -> NodeFactory { @@ -32,7 +33,7 @@ impl TestDom { pub fn diff<'a>(&'a self, old: &'a VNode<'a>, new: &'a VNode<'a>) -> Mutations<'a> { let mutations = Mutations::new(); - let mut machine = DiffMachine::new(mutations, &self.scheduler.pool); + let mut machine = DiffMachine::new(mutations, todo!()); machine.stack.push(DiffInstruction::Diff { new, old }); machine.mutations } @@ -40,7 +41,7 @@ impl TestDom { pub fn create<'a>(&'a self, left: Option>) -> Mutations<'a> { let old = self.bump.alloc(self.render_direct(left)); - let mut machine = DiffMachine::new(Mutations::new(), &self.scheduler.pool); + let mut machine = DiffMachine::new(Mutations::new(), todo!()); machine.stack.create_node(old, MountType::Append); @@ -56,14 +57,14 @@ impl TestDom { ) -> (Mutations<'a>, Mutations<'a>) { let (old, new) = (self.render(left), self.render(right)); - let mut machine = DiffMachine::new(Mutations::new(), &self.scheduler.pool); + let mut machine = DiffMachine::new(Mutations::new(), todo!()); machine.stack.create_node(old, MountType::Append); machine.work(|| false); let create_edits = machine.mutations; - let mut machine = DiffMachine::new(Mutations::new(), &self.scheduler.pool); + let mut machine = DiffMachine::new(Mutations::new(), todo!()); machine.stack.push(DiffInstruction::Diff { old, new }); diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index c0d3054a2..239474334 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -78,16 +78,7 @@ pub struct VirtualDom { // we need to keep the allocation around, but we don't necessarily use it _root_caller: Box, - // /// 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. - // /// - // /// This is wrapped in an UnsafeCell because we will need to get mutable access to unique values in unique bump arenas - // /// and rusts's guarantees cannot prove that this is safe. We will need to maintain the safety guarantees manually. - // pub pool: ResourcePool, - // - pub component_arena: Bump, - - pub free_components: VecDeque<*mut ScopeInner>, + pub scopes: ScopeArena, pub heuristics: FxHashMap, @@ -192,7 +183,17 @@ impl VirtualDom { sender: UnboundedSender, receiver: UnboundedReceiver, ) -> Self { - let mut component_arena = Bump::new(); + let mut scopes = ScopeArena::new(); + + let base_scope = scopes.new_with_key( + // + root as _, + boxed_comp.as_ref(), + None, + 0, + 0, + sender.clone(), + ); // let root_fc = Box::new(root); @@ -239,13 +240,11 @@ impl VirtualDom { // }); Self { - scheduler: todo!(), + scopes, base_scope: todo!(), root_fc: todo!(), root_props: todo!(), _root_caller: todo!(), - component_arena: todo!(), - free_components: todo!(), heuristics: todo!(), receiver, pending_garbage: todo!(), @@ -340,7 +339,7 @@ impl VirtualDom { /// apply_edits(edits); /// ``` pub fn rebuild(&mut self) -> Mutations { - self.rebuild(self.base_scope) + // self.rebuild(self.base_scope) } /// Compute a manual diff of the VirtualDOM between states. @@ -856,7 +855,7 @@ impl VirtualDom { /// Restart the entire VirtualDOM from scratch, wiping away any old state and components. /// /// Typically used to kickstart the VirtualDOM after initialization. - pub fn rebuild(&mut self, base_scope: ScopeId) -> Mutations { + pub fn rebuild_inner(&mut self, base_scope: ScopeId) -> Mutations { let mut shared = self.clone(); let mut diff_machine = DiffMachine::new(Mutations::new(), &mut shared);