use crate::factory::RenderReturn; use crate::innerlude::{VComponent, VFragment, VText}; use crate::mutations::Mutation; use crate::mutations::Mutation::*; use crate::nodes::VNode; use crate::nodes::{DynamicNode, TemplateNode}; use crate::virtual_dom::VirtualDom; use crate::{AttributeValue, ScopeId, SuspenseContext, TemplateAttribute}; impl<'b: 'static> VirtualDom { /// Create a new template [`VNode`] and write it to the [`Mutations`] buffer. /// /// This method pushes the ScopeID to the internal scopestack and returns the number of nodes created. pub(crate) fn create_scope(&mut self, scope: ScopeId, template: &'b VNode<'b>) -> usize { self.scope_stack.push(scope); let out = self.create(template); self.scope_stack.pop(); out } /// Create this template and write its mutations pub(crate) fn create(&mut self, template: &'b VNode<'b>) -> usize { // The best renderers will have templates prehydrated and registered // Just in case, let's create the template using instructions anyways if !self.templates.contains_key(&template.template.id) { self.register_template(template); } // Walk the roots, creating nodes and assigning IDs // todo: adjust dynamic nodes to be in the order of roots and then leaves (ie BFS) let mut dynamic_attrs = template.template.attr_paths.iter().enumerate().peekable(); let mut dynamic_nodes = template.template.node_paths.iter().enumerate().peekable(); let cur_scope = self.scope_stack.last().copied().unwrap(); let mut on_stack = 0; for (root_idx, root) in template.template.roots.iter().enumerate() { on_stack += match root { TemplateNode::Element { .. } | TemplateNode::Text(_) => { self.mutations.push(LoadTemplate { name: template.template.id, index: root_idx, }); 1 } TemplateNode::DynamicText(id) | TemplateNode::Dynamic(id) => { match &template.dynamic_nodes[*id] { DynamicNode::Fragment(frag) => match frag { VFragment::Empty(slot) => { let id = self.next_element(template, template.template.node_paths[*id]); slot.set(id); self.mutations.push(CreatePlaceholder { id }); 1 } VFragment::NonEmpty(_) => self.create_dynamic_node( template, &template.dynamic_nodes[*id], *id, ), }, DynamicNode::Component { .. } => { self.create_dynamic_node(template, &template.dynamic_nodes[*id], *id) } DynamicNode::Text(VText { id: slot, value }) => { let id = self.next_element(template, template.template.node_paths[*id]); slot.set(id); self.mutations.push(CreateTextNode { value, id }); 1 } } } }; // we're on top of a node that has a dynamic attribute for a descendant // Set that attribute now before the stack gets in a weird state while let Some((mut attr_id, path)) = dynamic_attrs.next_if(|(_, p)| p[0] == root_idx as u8) { let id = self.next_element(template, template.template.attr_paths[attr_id]); self.mutations.push(AssignId { path: &path[1..], id, }); loop { let attribute = template.dynamic_attrs.get(attr_id).unwrap(); attribute.mounted_element.set(id); match &attribute.value { AttributeValue::Text(value) => self.mutations.push(SetAttribute { name: attribute.name, value: *value, ns: attribute.namespace, id, }), AttributeValue::Bool(value) => self.mutations.push(SetBoolAttribute { name: attribute.name, value: *value, id, }), AttributeValue::Listener(_) => self.mutations.push(NewEventListener { // all listeners start with "on" event_name: &attribute.name[2..], scope: cur_scope, id, }), AttributeValue::Float(_) => todo!(), AttributeValue::Int(_) => todo!(), AttributeValue::Any(_) => todo!(), AttributeValue::None => todo!(), } // Only push the dynamic attributes forward if they match the current path (same element) match dynamic_attrs.next_if(|(_, p)| *p == path) { Some((next_attr_id, _)) => attr_id = next_attr_id, None => break, } } } // We're on top of a node that has a dynamic child for a descendant // Skip any node that's a root let mut start = None; let mut end = None; while let Some((idx, p)) = dynamic_nodes.next_if(|(_, p)| p[0] == root_idx as u8) { if p.len() == 1 { continue; } if start.is_none() { start = Some(idx); } end = Some(idx); } if let (Some(start), Some(end)) = (start, end) { for idx in start..=end { let node = &template.dynamic_nodes[idx]; let m = self.create_dynamic_node(template, node, idx); if m > 0 { self.mutations.push(ReplacePlaceholder { m, path: &template.template.node_paths[idx][1..], }); } } } } on_stack } /// Insert a new template into the VirtualDom's template registry fn register_template(&mut self, template: &'b VNode<'b>) { for node in template.template.roots { self.create_static_node(template, node); } self.mutations.template_mutations.push(SaveTemplate { name: template.template.id, m: template.template.roots.len(), }); self.templates .insert(template.template.id, template.template); } pub(crate) fn create_static_node( &mut self, template: &'b VNode<'b>, node: &'b TemplateNode<'static>, ) { match *node { // Todo: create the children's template TemplateNode::Dynamic(idx) => { let id = self.next_element(template, template.template.node_paths[idx]); self.mutations .template_mutations .push(CreatePlaceholder { id }) } TemplateNode::Text(value) => self .mutations .template_mutations .push(CreateStaticText { value }), TemplateNode::DynamicText { .. } => self .mutations .template_mutations .push(CreateStaticText { value: "d" }), TemplateNode::Element { attrs, children, namespace, tag, inner_opt, } => { let id = self.next_element(template, &[]); // never gets referenced, empty path is fine, I think? self.mutations.template_mutations.push(CreateElement { name: tag, namespace, id, }); self.mutations .template_mutations .extend(attrs.into_iter().filter_map(|attr| match attr { TemplateAttribute::Static { name, value, namespace, .. } => Some(SetAttribute { name, value, id, ns: *namespace, }), _ => None, })); if children.is_empty() && inner_opt { return; } children .into_iter() .for_each(|child| self.create_static_node(template, child)); self.mutations .template_mutations .push(AppendChildren { m: children.len() }) } } } pub(crate) fn create_dynamic_node( &mut self, template: &'b VNode<'b>, node: &'b DynamicNode<'b>, idx: usize, ) -> usize { use DynamicNode::*; match node { Text(text) => self.create_dynamic_text(template, text, idx), Fragment(frag) => self.create_fragment(frag, template, idx), Component(component) => self.create_component_node(template, component, idx), } } fn create_dynamic_text( &mut self, template: &'b VNode<'b>, text: &'b VText<'b>, idx: usize, ) -> usize { // Allocate a dynamic element reference for this text node let new_id = self.next_element(template, template.template.node_paths[idx]); // Make sure the text node is assigned to the correct element text.id.set(new_id); // Add the mutation to the list self.mutations.push(HydrateText { id: new_id, path: &template.template.node_paths[idx][1..], value: text.value, }); // Since we're hydrating an existing node, we don't create any new nodes 0 } pub(crate) fn create_fragment( &mut self, frag: &'b VFragment<'b>, template: &'b VNode<'b>, idx: usize, ) -> usize { match frag { VFragment::NonEmpty(nodes) => { nodes.iter().fold(0, |acc, child| acc + self.create(child)) } VFragment::Empty(slot) => { // Allocate a dynamic element reference for this text node let id = self.next_element(template, template.template.node_paths[idx]); // Make sure the text node is assigned to the correct element slot.set(id); // Assign the ID to the existing node in the template self.mutations.push(AssignId { path: &template.template.node_paths[idx][1..], id, }); // Since the placeholder is already in the DOM, we don't create any new nodes 0 } } } fn create_component_node( &mut self, template: &'b VNode<'b>, component: &'b VComponent<'b>, idx: usize, ) -> usize { let props = component.props.replace(None).unwrap(); let prop_ptr = unsafe { std::mem::transmute(props.as_ref()) }; let scope = self.new_scope(prop_ptr).id; component.props.replace(Some(props)); component.scope.set(Some(scope)); let return_nodes = unsafe { self.run_scope(scope).extend_lifetime_ref() }; use RenderReturn::*; match return_nodes { Sync(Ok(t)) => self.mount_component(scope, t, idx), Sync(Err(_e)) => todo!("Propogate error upwards"), Async(_) => self.mount_component_placeholder(template, idx, scope), } } fn mount_component(&mut self, scope: ScopeId, template: &'b VNode<'b>, idx: usize) -> usize { // Keep track of how many mutations are in the buffer in case we need to split them out if a suspense boundary // is encountered let mutations_to_this_point = self.mutations.len(); // Create the component's root element let created = self.create_scope(scope, template); // If there are no suspense leaves below us, then just don't bother checking anything suspense related if self.collected_leaves.is_empty() { return created; } // If running the scope has collected some leaves and *this* component is a boundary, then handle the suspense let boundary = match self.scopes[scope.0].has_context::() { Some(boundary) => boundary, _ => return created, }; // Since this is a boundary, use its placeholder within the template as the placeholder for the suspense tree let new_id = self.next_element(template, template.template.node_paths[idx]); // Now connect everything to the boundary self.scopes[scope.0].placeholder.set(Some(new_id)); // This involves breaking off the mutations to this point, and then creating a new placeholder for the boundary // Note that we break off dynamic mutations only - since static mutations aren't rendered immediately let split_off = unsafe { std::mem::transmute::, Vec>( self.mutations.split_off(mutations_to_this_point), ) }; boundary.mutations.borrow_mut().edits.extend(split_off); boundary.created_on_stack.set(created); boundary .waiting_on .borrow_mut() .extend(self.collected_leaves.drain(..)); // Now assign the placeholder in the DOM self.mutations.push(AssignId { id: new_id, path: &template.template.node_paths[idx][1..], }); 0 } /// Take the rendered nodes from a component and handle them if they were async /// /// IE simply assign an ID to the placeholder fn mount_component_placeholder( &mut self, template: &VNode, idx: usize, scope: ScopeId, ) -> usize { let new_id = self.next_element(template, template.template.node_paths[idx]); // Set the placeholder of the scope self.scopes[scope.0].placeholder.set(Some(new_id)); // Since the placeholder is already in the DOM, we don't create any new nodes self.mutations.push(AssignId { id: new_id, path: &template.template.node_paths[idx][1..], }); 0 } }