percydiff.rs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  1. //! A primitive diffing algorithm
  2. //!
  3. //!
  4. //!
  5. //!
  6. //!
  7. use std::{collections::HashMap, mem};
  8. use crate::innerlude::*;
  9. use crate::patch::Patch;
  10. use fxhash::{FxBuildHasher, FxHashMap, FxHashSet};
  11. use generational_arena::Index;
  12. pub struct DiffMachine {
  13. immediate_queue: Vec<Index>,
  14. diffed: FxHashSet<Index>,
  15. need_to_diff: FxHashSet<Index>,
  16. marked_for_removal: Vec<Index>,
  17. }
  18. impl DiffMachine {
  19. pub fn new() -> Self {
  20. Self {
  21. immediate_queue: vec![],
  22. diffed: FxHashSet::default(),
  23. need_to_diff: FxHashSet::default(),
  24. marked_for_removal: vec![],
  25. }
  26. }
  27. /// Given two VirtualNode's generate Patch's that would turn the old virtual node's
  28. /// real DOM node equivalent into the new VirtualNode's real DOM node equivalent.
  29. pub fn diff<'a>(&mut self, old: &'a VNode, new: &'a VNode) -> Vec<Patch<'a>> {
  30. self.diff_recursive(&old, &new, &mut 0)
  31. }
  32. pub fn diff_recursive<'a, 'b>(
  33. &mut self,
  34. old: &'a VNode,
  35. new: &'a VNode,
  36. cur_node_idx: &'b mut usize,
  37. ) -> Vec<Patch<'a>> {
  38. let mut patches = vec![];
  39. let mut replace = false;
  40. // Different enum variants, replace!
  41. if mem::discriminant(old) != mem::discriminant(new) {
  42. replace = true;
  43. }
  44. if let (VNode::Element(old_element), VNode::Element(new_element)) = (old, new) {
  45. // Replace if there are different element tags
  46. if old_element.tag_name != new_element.tag_name {
  47. // if old_element.tag != new_element.tag {
  48. replace = true;
  49. }
  50. // Replace if two elements have different keys
  51. // TODO: More robust key support. This is just an early stopgap to allow you to force replace
  52. // an element... say if it's event changed. Just change the key name for now.
  53. // In the future we want keys to be used to create a Patch::ReOrder to re-order siblings
  54. // todo!
  55. // if old_element.attributes.get("key").is_some()
  56. // && old_element.attrs.get("key") != new_element.attrs.get("key")
  57. // {
  58. // replace = true;
  59. // }
  60. }
  61. // Handle replacing of a node
  62. if replace {
  63. patches.push(Patch::Replace(*cur_node_idx, &new));
  64. if let VNode::Element(old_element_node) = old {
  65. for child in old_element_node.children.iter() {
  66. increment_node_idx_for_children(child, cur_node_idx);
  67. }
  68. }
  69. return patches;
  70. }
  71. // The following comparison can only contain identical variants, other
  72. // cases have already been handled above by comparing variant
  73. // discriminants.
  74. match (old, new) {
  75. // We're comparing two text nodes
  76. (VNode::Text(old_text), VNode::Text(new_text)) => {
  77. if old_text != new_text {
  78. patches.push(Patch::ChangeText(*cur_node_idx, &new_text));
  79. }
  80. }
  81. // We're comparing two element nodes
  82. (VNode::Element(old_element), VNode::Element(new_element)) => {
  83. // let b: HashMap<&str, &str, FxBuildHasher> = HashMap::new()
  84. let old_attrs = old_element
  85. .attributes
  86. .iter()
  87. .map(|f| (f.name, f.value))
  88. .collect::<HashMap<&'static str, &str, FxBuildHasher>>();
  89. let new_attrs = old_element
  90. .attributes
  91. .iter()
  92. .map(|f| (f.name, f.value))
  93. .collect::<HashMap<&'static str, &str, FxBuildHasher>>();
  94. let mut add_attributes = FxHashMap::<&'static str, &str>::default();
  95. // [("blah", "blah")]
  96. // .into_iter()
  97. // .map(|f| (f.0, f.1))
  98. // .collect::<HashMap<&'static str, &str, FxBuildHasher>>();
  99. // let mut add_attribute = HashMap::<&str, &str, FxBuildHasher>::new();
  100. let mut remove_attributes: Vec<&str> = vec![];
  101. // TODO: -> split out into func
  102. for (new_attr_name, new_attr_val) in new_attrs.iter() {
  103. // for (new_attr_name, new_attr_val) in new_element.attrs.iter() {
  104. match old_attrs.get(new_attr_name) {
  105. // match old_element.attrs.get(new_attr_name) {
  106. Some(ref old_attr_val) => {
  107. if old_attr_val != &new_attr_val {
  108. add_attributes.insert(new_attr_name, new_attr_val);
  109. }
  110. }
  111. None => {
  112. add_attributes.insert(new_attr_name, new_attr_val);
  113. }
  114. };
  115. }
  116. // TODO: -> split out into func
  117. for (old_attr_name, old_attr_val) in old_attrs.iter() {
  118. // for (old_attr_name, old_attr_val) in old_element.attrs.iter() {
  119. if add_attributes.get(&old_attr_name[..]).is_some() {
  120. continue;
  121. };
  122. match new_attrs.get(old_attr_name) {
  123. // match new_element.attrs.get(old_attr_name) {
  124. Some(ref new_attr_val) => {
  125. if new_attr_val != &old_attr_val {
  126. remove_attributes.push(old_attr_name);
  127. }
  128. }
  129. None => {
  130. remove_attributes.push(old_attr_name);
  131. }
  132. };
  133. }
  134. if add_attributes.len() > 0 {
  135. patches.push(Patch::AddAttributes(*cur_node_idx, add_attributes));
  136. }
  137. if remove_attributes.len() > 0 {
  138. patches.push(Patch::RemoveAttributes(*cur_node_idx, remove_attributes));
  139. }
  140. let old_child_count = old_element.children.len();
  141. let new_child_count = new_element.children.len();
  142. if new_child_count > old_child_count {
  143. let append_patch: Vec<&'a VNode> =
  144. new_element.children[old_child_count..].iter().collect();
  145. patches.push(Patch::AppendChildren(*cur_node_idx, append_patch))
  146. }
  147. if new_child_count < old_child_count {
  148. patches.push(Patch::TruncateChildren(*cur_node_idx, new_child_count))
  149. }
  150. let min_count = std::cmp::min(old_child_count, new_child_count);
  151. for index in 0..min_count {
  152. *cur_node_idx = *cur_node_idx + 1;
  153. let old_child = &old_element.children[index];
  154. let new_child = &new_element.children[index];
  155. patches.append(&mut self.diff_recursive(&old_child, &new_child, cur_node_idx))
  156. }
  157. if new_child_count < old_child_count {
  158. for child in old_element.children[min_count..].iter() {
  159. increment_node_idx_for_children(child, cur_node_idx);
  160. }
  161. }
  162. }
  163. (VNode::Suspended, _)
  164. | (_, VNode::Suspended)
  165. | (VNode::Component(_), _)
  166. | (_, VNode::Component(_)) => {
  167. todo!("cant yet handle these two")
  168. }
  169. (VNode::Text(_), VNode::Element(_))
  170. | (VirtualNode::Element(_), VirtualNode::Text(_)) => {
  171. unreachable!("Unequal variant discriminants should already have been handled");
  172. }
  173. };
  174. // new_root.create_element()
  175. patches
  176. }
  177. }
  178. fn increment_node_idx_for_children<'a, 'b>(old: &'a VirtualNode, cur_node_idx: &'b mut usize) {
  179. *cur_node_idx += 1;
  180. if let VirtualNode::Element(element_node) = old {
  181. for child in element_node.children.iter() {
  182. increment_node_idx_for_children(&child, cur_node_idx);
  183. }
  184. }
  185. }
  186. // #[cfg(test)]
  187. mod tests {
  188. use bumpalo::Bump;
  189. use super::*;
  190. fn test_diff(
  191. tree1: impl Fn(&Bump) -> VNode<'_>,
  192. tree2: impl Fn(&Bump) -> VNode<'_>,
  193. expected_patches: Vec<Patch>,
  194. description: &'static str,
  195. ) {
  196. let bump = Bump::new();
  197. let nodes1 = tree1(&bump);
  198. let nodes2 = tree1(&bump);
  199. let mut machine = DiffMachine::new();
  200. let patches = machine.diff(&nodes1, &nodes2);
  201. patches
  202. .iter()
  203. .zip(expected_patches.iter())
  204. .for_each(|f| assert_eq!(compare_patch(f.0, f.1), true, "{}", description));
  205. }
  206. // todo: make this actually perform real comparisons
  207. // by default, nothing is derived for vnodes or patches
  208. fn compare_patch(patch1: &Patch, patch2: &Patch) -> bool {
  209. match (patch1, patch2) {
  210. (Patch::AppendChildren(_, _), Patch::AppendChildren(_, _)) => true,
  211. (Patch::AppendChildren(_, _), _) => false,
  212. (Patch::TruncateChildren(_, _), Patch::TruncateChildren(_, _)) => true,
  213. (Patch::TruncateChildren(_, _), _) => false,
  214. (Patch::Replace(_, _), Patch::Replace(_, _)) => true,
  215. (Patch::Replace(_, _), _) => false,
  216. (Patch::AddAttributes(_, _), Patch::AddAttributes(_, _)) => true,
  217. (Patch::AddAttributes(_, _), _) => false,
  218. (Patch::RemoveAttributes(_, _), Patch::RemoveAttributes(_, _)) => true,
  219. (Patch::RemoveAttributes(_, _), _) => false,
  220. (Patch::ChangeText(_, _), Patch::ChangeText(_, _)) => true,
  221. (Patch::ChangeText(_, _), _) => false,
  222. }
  223. }
  224. fn printdiff(
  225. tree1: impl for<'a> Fn(&'a Bump) -> VNode<'a>,
  226. tree2: impl for<'a> Fn(&'a Bump) -> VNode<'a>,
  227. desc: &'static str,
  228. ) {
  229. let bump = Bump::new();
  230. let nodes1 = tree1(&bump);
  231. let nodes2 = tree2(&bump);
  232. let mut machine = DiffMachine::new();
  233. let patches = machine.diff(&nodes1, &nodes2);
  234. patches.iter().for_each(|f| match f {
  235. Patch::AppendChildren(idx, a) => {
  236. println!("AppendChildren");
  237. }
  238. Patch::TruncateChildren(idx, a) => {
  239. println!("TruncateChildren");
  240. }
  241. Patch::Replace(idx, a) => {
  242. println!("Replace");
  243. }
  244. Patch::AddAttributes(idx, a) => {
  245. println!("AddAttributes");
  246. }
  247. Patch::RemoveAttributes(idx, a) => {
  248. println!("RemoveAttributes");
  249. }
  250. Patch::ChangeText(idx, a) => {
  251. println!("ChangeText");
  252. }
  253. });
  254. }
  255. #[test]
  256. fn example_diff() {
  257. printdiff(
  258. html! { <div> </div> },
  259. html! { <div>"Hello world!" </div> },
  260. "demo the difference between two simple dom tree",
  261. );
  262. printdiff(
  263. html! {
  264. <div>
  265. "Hello world!"
  266. </div>
  267. },
  268. html! {
  269. <div>
  270. <div>
  271. "Hello world!"
  272. "Hello world!"
  273. "Hello world!"
  274. "Hello world!"
  275. "Hello world!"
  276. </div>
  277. </div>
  278. },
  279. "demo the difference between two simple dom tree",
  280. );
  281. }
  282. }