diff --git a/packages/core/.vscode/settings.json b/packages/core/.vscode/settings.json index 79505b018..1c68f97cc 100644 --- a/packages/core/.vscode/settings.json +++ b/packages/core/.vscode/settings.json @@ -1,3 +1,3 @@ { - "rust-analyzer.inlayHints.enable": false + "rust-analyzer.inlayHints.enable": true } diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index a922cceaf..4c8a2177d 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -107,7 +107,7 @@ use DomEdit::*; pub struct DiffMachine<'bump> { vdom: &'bump Scheduler, - pub mutations: &'bump mut Mutations<'bump>, + pub mutations: Mutations<'bump>, pub stack: DiffStack<'bump>, pub diffed: FxHashSet, @@ -116,7 +116,7 @@ pub struct DiffMachine<'bump> { impl<'bump> DiffMachine<'bump> { pub(crate) fn new( - edits: &'bump mut Mutations<'bump>, + edits: Mutations<'bump>, cur_scope: ScopeId, shared: &'bump Scheduler, ) -> Self { diff --git a/packages/core/src/hooks.rs b/packages/core/src/hooks.rs index ebe448ed5..b8808993e 100644 --- a/packages/core/src/hooks.rs +++ b/packages/core/src/hooks.rs @@ -208,14 +208,16 @@ where cx.render(LazyNodes::new(|f| { let bump = f.bump(); let g: &dyn FnOnce(SuspendedContext<'src>) -> DomTree<'src> = - bump.alloc(|sus| { - let out = value - .borrow() + bump.alloc(move |sus| { + let val = value.borrow(); + + let out = val .as_ref() .unwrap() .as_ref() .downcast_ref::() .unwrap(); + user_callback(sus, out) }); diff --git a/packages/core/src/scheduler.rs b/packages/core/src/scheduler.rs index 49d4bb656..94d0c53cd 100644 --- a/packages/core/src/scheduler.rs +++ b/packages/core/src/scheduler.rs @@ -95,7 +95,7 @@ pub struct Scheduler { // scheduler stuff pub current_priority: EventPriority, - pub pending_events: VecDeque, + pub ui_events: VecDeque, pub pending_immediates: VecDeque, @@ -131,7 +131,7 @@ impl Scheduler { }), get_shared_context: { let components = components.clone(); - Rc::new(|id, ty| { + Rc::new(move |id, ty| { let components = unsafe { &*components.get() }; let mut search: Option<&Scope> = components.get(id.0); while let Some(inner) = search.take() { @@ -159,7 +159,7 @@ impl Scheduler { raw_elements, // a storage for our receiver to dump into - pending_events: VecDeque::new(), + ui_events: VecDeque::new(), pending_immediates: VecDeque::new(), @@ -216,12 +216,14 @@ impl Scheduler { } pub fn reserve_node(&self) -> ElementId { - ElementId(self.raw_elements.insert(())) + todo!("reserving wip until it's fast enough again") + // ElementId(self.raw_elements.insert(())) } /// return the id, freeing the space of the original node pub fn collect_garbage(&self, id: ElementId) { - self.raw_elements.remove(id.0); + todo!("garabge collection currently WIP") + // self.raw_elements.remove(id.0); } pub fn insert_scope_with_key(&self, f: impl FnOnce(ScopeId) -> Scope) -> ScopeId { @@ -233,163 +235,101 @@ impl Scheduler { } pub fn clean_up_garbage(&mut self) { - let mut scopes_to_kill = Vec::new(); - let mut garbage_list = Vec::new(); + // let mut scopes_to_kill = Vec::new(); + // let mut garbage_list = Vec::new(); - for scope in self.garbage_scopes.drain() { - let scope = self.get_scope_mut(scope).unwrap(); - for node in scope.consume_garbage() { - garbage_list.push(node); - } + todo!("garbage collection is currently immediate") + // for scope in self.garbage_scopes.drain() { + // let scope = self.get_scope_mut(scope).unwrap(); + // for node in scope.consume_garbage() { + // garbage_list.push(node); + // } - while let Some(node) = garbage_list.pop() { - match &node { - VNode::Text(_) => { - self.collect_garbage(node.direct_id()); - } - VNode::Anchor(_) => { - self.collect_garbage(node.direct_id()); - } - VNode::Suspended(_) => { - self.collect_garbage(node.direct_id()); - } + // while let Some(node) = garbage_list.pop() { + // match &node { + // VNode::Text(_) => { + // self.collect_garbage(node.direct_id()); + // } + // VNode::Anchor(_) => { + // self.collect_garbage(node.direct_id()); + // } + // VNode::Suspended(_) => { + // self.collect_garbage(node.direct_id()); + // } - VNode::Element(el) => { - self.collect_garbage(node.direct_id()); - for child in el.children { - garbage_list.push(child); - } - } + // VNode::Element(el) => { + // self.collect_garbage(node.direct_id()); + // for child in el.children { + // garbage_list.push(child); + // } + // } - VNode::Fragment(frag) => { - for child in frag.children { - garbage_list.push(child); - } - } + // VNode::Fragment(frag) => { + // for child in frag.children { + // garbage_list.push(child); + // } + // } - VNode::Component(comp) => { - // TODO: run the hook destructors and then even delete the scope + // VNode::Component(comp) => { + // // TODO: run the hook destructors and then even delete the scope - let scope_id = comp.ass_scope.get().unwrap(); - let scope = self.get_scope(scope_id).unwrap(); - let root = scope.root(); - garbage_list.push(root); - scopes_to_kill.push(scope_id); - } - } - } - } + // let scope_id = comp.ass_scope.get().unwrap(); + // let scope = self.get_scope(scope_id).unwrap(); + // let root = scope.root(); + // garbage_list.push(root); + // scopes_to_kill.push(scope_id); + // } + // } + // } + // } - for scope in scopes_to_kill.drain(..) { - // - // kill em - } - } - - // channels don't have these methods, so we just implement our own wrapper - pub fn next_event(&mut self) -> Option { - // pop the last event off the internal queue - self.pending_events.pop_back().or_else(|| { - self.ui_event_receiver - .borrow_mut() - .try_next() - .ok() - .flatten() - }) + // for scope in scopes_to_kill.drain(..) { + // // + // // kill em + // } } pub fn manually_poll_events(&mut self) { - // 1. Poll the UI event receiver - // 2. Poll the set_state receiver - - // poll the immediates first, creating work out of them - let shared_receiver = self.immediate_receiver.clone(); - let mut receiver = shared_receiver.borrow_mut(); - while let Ok(Some(trigger)) = receiver.try_next() { - self.add_dirty_scope(trigger, EventPriority::Low); - } - - // next poll the UI events, - let mut receiver = self.ui_event_receiver.borrow_mut(); - while let Ok(Some(trigger)) = receiver.try_next() { - self.pending_events.push_back(trigger); + while let Ok(Some(msg)) = self.receiver.try_next() { + self.handle_channel_msg(msg); } } // Converts UI events into dirty scopes with various priorities pub fn consume_pending_events(&mut self) -> Result<()> { - while let Some(trigger) = self.pending_events.pop_back() { - match &trigger.event { - SyntheticEvent::AsyncEvent { .. } => {} + // while let Some(trigger) = self.ui_events.pop_back() { + // match &trigger.event { + // SyntheticEvent::ClipboardEvent(_) + // | SyntheticEvent::CompositionEvent(_) + // | SyntheticEvent::KeyboardEvent(_) + // | SyntheticEvent::FocusEvent(_) + // | SyntheticEvent::FormEvent(_) + // | SyntheticEvent::SelectionEvent(_) + // | SyntheticEvent::TouchEvent(_) + // | SyntheticEvent::UIEvent(_) + // | SyntheticEvent::WheelEvent(_) + // | SyntheticEvent::MediaEvent(_) + // | SyntheticEvent::AnimationEvent(_) + // | SyntheticEvent::TransitionEvent(_) + // | SyntheticEvent::ToggleEvent(_) + // | SyntheticEvent::MouseEvent(_) + // | SyntheticEvent::PointerEvent(_) => { + // if let Some(scope) = self.get_scope_mut(trigger.scope) { + // if let Some(element) = trigger.mounted_dom_id { + // scope.call_listener(trigger.event, element)?; - // This suspense system works, but it's not the most elegant solution. - // TODO: Replace this system - SyntheticEvent::SuspenseEvent { hook_idx, domnode } => { - todo!("suspense needs to be converted into its own channel"); + // // let receiver = self.immediate_receiver.clone(); + // // let mut receiver = receiver.borrow_mut(); - // // Safety: this handler is the only thing that can mutate shared items at this moment in tim - // let scope = diff_machine.get_scope_mut(&trigger.originator).unwrap(); - - // // safety: we are sure that there are no other references to the inner content of suspense hooks - // let hook = unsafe { scope.hooks.get_mut::(*hook_idx) }.unwrap(); - - // let cx = Context { scope, props: &() }; - // let scx = SuspendedContext { inner: cx }; - - // // generate the new node! - // let nodes: Option = (&hook.callback)(scx); - - // if let Some(nodes) = nodes { - // // allocate inside the finished frame - not the WIP frame - // let nodes = scope.frames.finished_frame().bump.alloc(nodes); - - // // push the old node's root onto the stack - // let real_id = domnode.get().ok_or(Error::NotMounted)?; - // diff_machine.edit_push_root(real_id); - - // // push these new nodes onto the diff machines stack - // let meta = diff_machine.create_vnode(&*nodes); - - // // replace the placeholder with the new nodes we just pushed on the stack - // diff_machine.edit_replace_with(1, meta.added_to_stack); - // } else { - // log::warn!( - // "Suspense event came through, but there were no generated nodes >:(." - // ); - // } - } - - SyntheticEvent::ClipboardEvent(_) - | SyntheticEvent::CompositionEvent(_) - | SyntheticEvent::KeyboardEvent(_) - | SyntheticEvent::FocusEvent(_) - | SyntheticEvent::FormEvent(_) - | SyntheticEvent::SelectionEvent(_) - | SyntheticEvent::TouchEvent(_) - | SyntheticEvent::UIEvent(_) - | SyntheticEvent::WheelEvent(_) - | SyntheticEvent::MediaEvent(_) - | SyntheticEvent::AnimationEvent(_) - | SyntheticEvent::TransitionEvent(_) - | SyntheticEvent::ToggleEvent(_) - | SyntheticEvent::MouseEvent(_) - | SyntheticEvent::PointerEvent(_) => { - if let Some(scope) = self.get_scope_mut(trigger.scope) { - if let Some(element) = trigger.mounted_dom_id { - scope.call_listener(trigger.event, element)?; - - let receiver = self.immediate_receiver.clone(); - let mut receiver = receiver.borrow_mut(); - - // Drain the immediates into the dirty scopes, setting the appropiate priorities - while let Ok(Some(dirty_scope)) = receiver.try_next() { - self.add_dirty_scope(dirty_scope, trigger.priority) - } - } - } - } - } - } + // // // Drain the immediates into the dirty scopes, setting the appropiate priorities + // // while let Ok(Some(dirty_scope)) = receiver.try_next() { + // // self.add_dirty_scope(dirty_scope, trigger.priority) + // // } + // } + // } + // } + // } + // } Ok(()) } @@ -400,13 +340,14 @@ impl Scheduler { } pub fn has_pending_events(&self) -> bool { - self.pending_events.len() > 0 + self.ui_events.len() > 0 } pub fn has_work(&self) -> bool { - self.high_priorty.has_work() - || self.medium_priority.has_work() - || self.low_priority.has_work() + todo!() + // self.high_priorty.has_work() + // || self.medium_priority.has_work() + // || self.low_priority.has_work() } pub fn has_pending_garbage(&self) -> bool { @@ -414,66 +355,104 @@ impl Scheduler { } fn get_current_fiber<'a>(&'a mut self) -> &mut DiffMachine<'a> { - let fib = match self.current_priority { - EventPriority::High => &mut self.high_priorty, - EventPriority::Medium => &mut self.medium_priority, - EventPriority::Low => &mut self.low_priority, - }; - unsafe { std::mem::transmute(fib) } + todo!() + // let fib = match self.current_priority { + // EventPriority::High => &mut self.high_priorty, + // EventPriority::Medium => &mut self.medium_priority, + // EventPriority::Low => &mut self.low_priority, + // }; + // unsafe { std::mem::transmute(fib) } } /// If a the fiber finishes its works (IE needs to be committed) the scheduler will drop the dirty scope + /// + /// + /// pub async fn work_with_deadline<'a>( &'a mut self, - mutations: &mut Mutations<'a>, deadline: &mut Pin>>, - ) -> bool { - // check if we need to elevate priority - self.current_priority = match ( - self.high_priorty.has_work(), - self.medium_priority.has_work(), - self.low_priority.has_work(), - ) { - (true, _, _) => EventPriority::High, - (false, true, _) => EventPriority::Medium, - (false, false, _) => EventPriority::Low, - }; + ) -> Vec> { + let mut committed_mutations = Vec::new(); - // let mut machine = DiffMachine::new(mutations, ScopeId(0), &self); + // TODO: + // the scheduler uses a bunch of different receivers to mimic a "topic" queue system. The futures-channel implementation + // doesn't really have a concept of a "topic" queue, so there's a lot of noise in the hand-rolled scheduler. We should + // explore abstracting the scheduler into a topic-queue channel system - similar to Kafka or something similar. + loop { + // Internalize any pending work since the last time we ran + self.manually_poll_events(); - let dirty_root = { - let dirty_roots = match self.current_priority { - EventPriority::High => &self.high_priorty.dirty_scopes, - EventPriority::Medium => &self.medium_priority.dirty_scopes, - EventPriority::Low => &self.low_priority.dirty_scopes, - }; - let mut height = 0; - let mut dirty_root = { - let root = dirty_roots.iter().next(); - if root.is_none() { - return true; - } - root.unwrap() - }; + // Wait for any new events if we have nothing to do + if !self.has_any_work() { + self.clean_up_garbage(); + let deadline_expired = self.wait_for_any_trigger(deadline).await; - for root in dirty_roots { - if let Some(scope) = self.get_scope(*root) { - if scope.height < height { - height = scope.height; - dirty_root = root; - } + if deadline_expired { + return committed_mutations; } } - dirty_root - }; - let fut = machine.diff_scope(*dirty_root).fuse(); - pin_mut!(fut); + // Create work from the pending event queue + self.consume_pending_events().unwrap(); - match futures_util::future::select(deadline, fut).await { - futures_util::future::Either::Left((deadline, work_fut)) => true, - futures_util::future::Either::Right((_, deadline_fut)) => false, + // Work through the current subtree, and commit the results when it finishes + // When the deadline expires, give back the work + let mut new_mutations = Mutations::new(); + // match self.work_with_deadline(&mut deadline).await { + // Some(mutations) => { + // // safety: the scheduler will never let us mutate + // let extended: Mutations<'static> = unsafe { std::mem::transmute(mutations) }; + // committed_mutations.push(extended) + // } + // None => return committed_mutations, + // } } + // // check if we need to elevate priority + // self.current_priority = match ( + // self.high_priorty.has_work(), + // self.medium_priority.has_work(), + // self.low_priority.has_work(), + // ) { + // (true, _, _) => EventPriority::High, + // (false, true, _) => EventPriority::Medium, + // (false, false, _) => EventPriority::Low, + // }; + + // // let mut machine = DiffMachine::new(mutations, ScopeId(0), &self); + + // let dirty_root = { + // let dirty_roots = match self.current_priority { + // EventPriority::High => &self.high_priorty.dirty_scopes, + // EventPriority::Medium => &self.medium_priority.dirty_scopes, + // EventPriority::Low => &self.low_priority.dirty_scopes, + // }; + // let mut height = 0; + // let mut dirty_root = { + // let root = dirty_roots.iter().next(); + // if root.is_none() { + // return true; + // } + // root.unwrap() + // }; + + // for root in dirty_roots { + // if let Some(scope) = self.get_scope(*root) { + // if scope.height < height { + // height = scope.height; + // dirty_root = root; + // } + // } + // } + // dirty_root + // }; + + // let fut = machine.diff_scope(*dirty_root).fuse(); + // pin_mut!(fut); + + // match futures_util::future::select(deadline, fut).await { + // futures_util::future::Either::Left((deadline, work_fut)) => true, + // futures_util::future::Either::Right((_, deadline_fut)) => false, + // } } // waits for a trigger, canceling early if the deadline is reached @@ -509,11 +488,12 @@ impl Scheduler { } pub fn add_dirty_scope(&mut self, scope: ScopeId, priority: EventPriority) { - match priority { - EventPriority::High => self.high_priorty.dirty_scopes.insert(scope), - EventPriority::Medium => self.medium_priority.dirty_scopes.insert(scope), - EventPriority::Low => self.low_priority.dirty_scopes.insert(scope), - }; + todo!() + // match priority { + // EventPriority::High => self.high_priorty.dirty_scopes.insert(scope), + // EventPriority::Medium => self.medium_priority.dirty_scopes.insert(scope), + // EventPriority::Low => self.low_priority.dirty_scopes.insert(scope), + // }; } } diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 524a5ff4c..c37a70a46 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -271,58 +271,9 @@ impl VirtualDom { pub async fn run_with_deadline<'s>( &'s mut self, deadline: impl Future, - ) -> Option>> { - let mut committed_mutations = Vec::new(); + ) -> Vec> { let mut deadline = Box::pin(deadline.fuse()); - - // TODO: - // the scheduler uses a bunch of different receivers to mimic a "topic" queue system. The futures-channel implementation - // doesn't really have a concept of a "topic" queue, so there's a lot of noise in the hand-rolled scheduler. We should - // explore abstracting the scheduler into a topic-queue channel system - similar to Kafka or something similar. - loop { - // Internalize any pending work since the last time we ran - self.scheduler.manually_poll_events(); - - // Wait for any new events if we have nothing to do - if !self.scheduler.has_any_work() { - self.scheduler.clean_up_garbage(); - let deadline_expired = self.scheduler.wait_for_any_trigger(&mut deadline).await; - - if deadline_expired { - return Some(committed_mutations); - } - } - - // Create work from the pending event queue - self.scheduler.consume_pending_events().unwrap(); - - // Work through the current subtree, and commit the results when it finishes - // When the deadline expires, give back the work - let mut new_mutations = Mutations::new(); - match self.scheduler.work_with_deadline(&mut deadline).await { - FiberResult::Done => { - // return Some(mutations); - // for edit in mutations.edits { - // committed_mutations.edits.push(edit); - // } - // committed_mutations.extend(&mut mutations); - - /* - quick return if there's no work left, so we can commit before the deadline expires - When we loop over again, we'll re-wait for any new work. - - I'm not quite sure how this *should* work. - - It makes sense to try and progress the DOM faster - */ - - // if !self.scheduler.has_any_work() { - // return Some(committed_mutations); - // } - } - FiberResult::Interrupted => return None, - } - } + self.scheduler.work_with_deadline(&mut deadline).await } pub fn get_event_sender(&self) -> futures_channel::mpsc::UnboundedSender {