From 4d524233429052bde29e36e416e79eea430d299d Mon Sep 17 00:00:00 2001 From: Simon Lydell Date: Mon, 8 Apr 2024 22:21:52 +0200 Subject: [PATCH 001/123] WIP --- src/Elm/Kernel/VirtualDom.js | 241 +++++++++++++++++------------------ 1 file changed, 117 insertions(+), 124 deletions(-) diff --git a/src/Elm/Kernel/VirtualDom.js b/src/Elm/Kernel/VirtualDom.js index 9fe8504e..b1713f4d 100644 --- a/src/Elm/Kernel/VirtualDom.js +++ b/src/Elm/Kernel/VirtualDom.js @@ -17,6 +17,9 @@ import VirtualDom exposing (toHandlerInt) // HELPERS +// Flips between true and false every render. +var _VirtualDom_even = true; + var _VirtualDom_divertHrefToApp; var _VirtualDom_doc = typeof document !== 'undefined' ? document : {}; @@ -46,6 +49,24 @@ var _VirtualDom_init = F4(function(virtualNode, flagDecoder, debugMetadata, args return {}; }); +function _VirtualDom_wrap(object) +{ + // Add a non-enumerable property to not break Elm's equality checks. + // You aren’t supposed to compare virtual nodes, but since it’s possible + // to not break people who do, why not? + return Object.defineProperty(object, "_", { + value: { + nodes: [], + i0: 0, + i1: 0 + }, + writable: true + }); +} + + + + // TEXT @@ -53,10 +74,10 @@ var _VirtualDom_init = F4(function(virtualNode, flagDecoder, debugMetadata, args function _VirtualDom_text(string) { - return { + return _VirtualDom_wrap({ $: __2_TEXT, - __text: string - }; + __text: string, + }); } @@ -68,22 +89,18 @@ var _VirtualDom_nodeNS = F2(function(namespace, tag) { return F2(function(factList, kidList) { - for (var kids = [], descendantsCount = 0; kidList.b; kidList = kidList.b) // WHILE_CONS + for (var kids = []; kidList.b; kidList = kidList.b) // WHILE_CONS { - var kid = kidList.a; - descendantsCount += (kid.__descendantsCount || 0); - kids.push(kid); + kids.push(kidList.a); } - descendantsCount += kids.length; - return { + return _VirtualDom_wrap({ $: __2_NODE, __tag: tag, __facts: _VirtualDom_organizeFacts(factList), __kids: kids, - __namespace: namespace, - __descendantsCount: descendantsCount - }; + __namespace: namespace + }); }); }); @@ -99,22 +116,18 @@ var _VirtualDom_keyedNodeNS = F2(function(namespace, tag) { return F2(function(factList, kidList) { - for (var kids = [], descendantsCount = 0; kidList.b; kidList = kidList.b) // WHILE_CONS + for (var kids = []; kidList.b; kidList = kidList.b) // WHILE_CONS { - var kid = kidList.a; - descendantsCount += (kid.b.__descendantsCount || 0); - kids.push(kid); + kids.push(kidList.a); } - descendantsCount += kids.length; - return { + return _VirtualDom_wrap({ $: __2_KEYED_NODE, __tag: tag, __facts: _VirtualDom_organizeFacts(factList), __kids: kids, - __namespace: namespace, - __descendantsCount: descendantsCount - }; + __namespace: namespace + }); }); }); @@ -128,13 +141,13 @@ var _VirtualDom_keyedNode = _VirtualDom_keyedNodeNS(undefined); function _VirtualDom_custom(factList, model, render, diff) { - return { + return _VirtualDom_wrap({ $: __2_CUSTOM, __facts: _VirtualDom_organizeFacts(factList), __model: model, __render: render, __diff: diff - }; + }); } @@ -147,8 +160,7 @@ var _VirtualDom_map = F2(function(tagger, node) return { $: __2_TAGGER, __tagger: tagger, - __node: node, - __descendantsCount: 1 + (node.__descendantsCount || 0) + __node: node }; }); @@ -696,9 +708,11 @@ function _VirtualDom_equalEvents(x, y) // function _VirtualDom_diff(x, y) { - var patches = []; - _VirtualDom_diffHelp(x, y, patches, 0); - return patches; + // var patches = []; + // _VirtualDom_diffHelp(x, y, patches, 0); + // return patches; + // Hack to provide the new virtual dom node to `_VirtualDom_applyPatches` without making changes in elm/browser. + return y; } @@ -716,16 +730,60 @@ function _VirtualDom_pushPatch(patches, type, index, data) } -function _VirtualDom_diffHelp(x, y, patches, index) +function _VirtualDom_diffHelp(x, y, sendToApp) { - if (x === y) - { + // Note: We can’t exit early if `x === y` because: + // - Event listeners may need to reference a new `sendToApp`. + // - .i0 or .i1 may need to be reset. + + // Remember: When virtualizing already existing DOM, we can’t know + // where `map` and `lazy` nodes should be, and which ones are `Keyed`. + // So it’s important to not redraw fully when just the new virtual dom node + // is a `map` or `lazy` or `Keyed`, to avoid unnecessary DOM changes on startup. + + while (x.$ === __2_TAGGER) { + x = x.__node; + } + + if (y.$ === __2_TAGGER) { + _VirtualDom_diffHelp(x, y.__node, function (msg) { return sendToApp(y.__tagger(msg)) }); + return; + } + + if (x.$ === __2_THUNK) { + if (y.$ === __2_THUNK) { + var xRefs = x.__refs; + var yRefs = y.__refs; + var i = xRefs.length; + var same = i === yRefs.length; + while (same && i--) + { + same = xRefs[i] === yRefs[i]; + } + if (same) + { + y.__node = x.__node; + return; + } + y.__node = y.__thunk(); + _VirtualDom_diffHelp(x.__node, y.__node, sendToApp); + } else { + _VirtualDom_diffHelp(x.__node, y, sendToApp); + } + return; + } + + if (y.$ === __2_THUNK) { + _VirtualDom_diffHelp(x, y.__thunk(), sendToApp); return; } var xType = x.$; var yType = y.$; + var xDom = x._; + var yDom = y._; + // Bail if you run into different types of nodes. Implies that the // structure has changed significantly and it's not worth a diff. if (xType !== yType) @@ -735,86 +793,33 @@ function _VirtualDom_diffHelp(x, y, patches, index) y = _VirtualDom_dekey(y); yType = __2_NODE; } + else if (xType === __2_KEYED_NODE && yType === __2_NODE) + { + x = _VirtualDom_dekey(x); + xType = __2_NODE; + } else { + // TODO: Redraw instead of pushing patch _VirtualDom_pushPatch(patches, __3_REDRAW, index, y); return; } } + // Reset the counter not used during this render. + if (_VirtualDom_even) { + yDom.i1 = 0; + } else { + yDom.i0 = 0; + } + // Now we know that both nodes are the same $. switch (yType) { - case __2_THUNK: - var xRefs = x.__refs; - var yRefs = y.__refs; - var i = xRefs.length; - var same = i === yRefs.length; - while (same && i--) - { - same = xRefs[i] === yRefs[i]; - } - if (same) - { - y.__node = x.__node; - return; - } - y.__node = y.__thunk(); - var subPatches = []; - _VirtualDom_diffHelp(x.__node, y.__node, subPatches, 0); - subPatches.length > 0 && _VirtualDom_pushPatch(patches, __3_THUNK, index, subPatches); - return; - - case __2_TAGGER: - // gather nested taggers - var xTaggers = x.__tagger; - var yTaggers = y.__tagger; - var nesting = false; - - var xSubNode = x.__node; - while (xSubNode.$ === __2_TAGGER) - { - nesting = true; - - typeof xTaggers !== 'object' - ? xTaggers = [xTaggers, xSubNode.__tagger] - : xTaggers.push(xSubNode.__tagger); - - xSubNode = xSubNode.__node; - } - - var ySubNode = y.__node; - while (ySubNode.$ === __2_TAGGER) - { - nesting = true; - - typeof yTaggers !== 'object' - ? yTaggers = [yTaggers, ySubNode.__tagger] - : yTaggers.push(ySubNode.__tagger); - - ySubNode = ySubNode.__node; - } - - // Just bail if different numbers of taggers. This implies the - // structure of the virtual DOM has changed. - if (nesting && xTaggers.length !== yTaggers.length) - { - _VirtualDom_pushPatch(patches, __3_REDRAW, index, y); - return; - } - - // check if taggers are "the same" - if (nesting ? !_VirtualDom_pairwiseRefEqual(xTaggers, yTaggers) : xTaggers !== yTaggers) - { - _VirtualDom_pushPatch(patches, __3_TAGGER, index, yTaggers); - } - - // diff everything below the taggers - _VirtualDom_diffHelp(xSubNode, ySubNode, patches, index + 1); - return; - case __2_TEXT: - if (x.__text !== y.__text) + // TODO: Can do optimizations like this with `x === y` to skip work + // when equal and we should only reset i0/i1 and update functions. + if (x !== y && x.__text !== y.__text) { _VirtualDom_pushPatch(patches, __3_TEXT, index, y.__text); } @@ -845,20 +850,6 @@ function _VirtualDom_diffHelp(x, y, patches, index) } } -// assumes the incoming arrays are the same length -function _VirtualDom_pairwiseRefEqual(as, bs) -{ - for (var i = 0; i < as.length; i++) - { - if (as[i] !== bs[i]) - { - return false; - } - } - - return true; -} - function _VirtualDom_diffNodes(x, y, patches, index, diffKids) { // Bail if obvious indicators have changed. Implies more serious @@ -1351,15 +1342,18 @@ function _VirtualDom_addDomNodesHelp(domNode, vNode, patches, i, low, high, even // APPLY PATCHES -function _VirtualDom_applyPatches(rootDomNode, oldVirtualNode, patches, eventNode) +function _VirtualDom_applyPatches(rootDomNode, oldVirtualNode, newVirtualNode, sendToApp) { - if (patches.length === 0) - { - return rootDomNode; - } - - _VirtualDom_addDomNodes(rootDomNode, oldVirtualNode, patches, eventNode); - return _VirtualDom_applyPatchesHelp(rootDomNode, patches); + // if (patches.length === 0) + // { + // return rootDomNode; + // } + + // _VirtualDom_addDomNodes(rootDomNode, oldVirtualNode, patches, eventNode); + // return _VirtualDom_applyPatchesHelp(rootDomNode, patches); + _VirtualDom_diffHelp(oldVirtualNode, newVirtualNode, sendToApp); + _VirtualDom_even = !_VirtualDom_even; + return newVirtualNode._.nodes[0]; } function _VirtualDom_applyPatchesHelp(rootDomNode, patches) @@ -1579,7 +1573,6 @@ function _VirtualDom_dekey(keyedNode) __tag: keyedNode.__tag, __facts: keyedNode.__facts, __kids: kids, - __namespace: keyedNode.__namespace, - __descendantsCount: keyedNode.__descendantsCount + __namespace: keyedNode.__namespace }; } From 01468bfa0e3febdf06c4431a03e5435df0fcbfd8 Mon Sep 17 00:00:00 2001 From: Simon Lydell Date: Tue, 9 Apr 2024 20:14:23 +0200 Subject: [PATCH 002/123] Preserve ._ when dekey --- src/Elm/Kernel/VirtualDom.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Elm/Kernel/VirtualDom.js b/src/Elm/Kernel/VirtualDom.js index b1713f4d..52c63095 100644 --- a/src/Elm/Kernel/VirtualDom.js +++ b/src/Elm/Kernel/VirtualDom.js @@ -781,9 +781,6 @@ function _VirtualDom_diffHelp(x, y, sendToApp) var xType = x.$; var yType = y.$; - var xDom = x._; - var yDom = y._; - // Bail if you run into different types of nodes. Implies that the // structure has changed significantly and it's not worth a diff. if (xType !== yType) @@ -808,9 +805,9 @@ function _VirtualDom_diffHelp(x, y, sendToApp) // Reset the counter not used during this render. if (_VirtualDom_even) { - yDom.i1 = 0; + y._.i1 = 0; } else { - yDom.i0 = 0; + y._.i0 = 0; } // Now we know that both nodes are the same $. @@ -1568,11 +1565,14 @@ function _VirtualDom_dekey(keyedNode) kids[i] = keyedKids[i].b; } - return { + return Object.defineProperty({ $: __2_NODE, __tag: keyedNode.__tag, __facts: keyedNode.__facts, __kids: kids, __namespace: keyedNode.__namespace - }; + }, "_", { + value: keyedNode._, + writable: true + }); } From b8b5b44226b68e7aa4cb8d92b9e8a06947c957e6 Mon Sep 17 00:00:00 2001 From: Simon Lydell Date: Fri, 21 Jun 2024 15:30:34 +0100 Subject: [PATCH 003/123] Begin adding and removing nodes --- src/Elm/Kernel/VirtualDom.js | 126 +++++++++++++++++------------------ 1 file changed, 61 insertions(+), 65 deletions(-) diff --git a/src/Elm/Kernel/VirtualDom.js b/src/Elm/Kernel/VirtualDom.js index 52c63095..75bc379a 100644 --- a/src/Elm/Kernel/VirtualDom.js +++ b/src/Elm/Kernel/VirtualDom.js @@ -59,8 +59,7 @@ function _VirtualDom_wrap(object) nodes: [], i0: 0, i1: 0 - }, - writable: true + } }); } @@ -451,22 +450,7 @@ function _VirtualDom_render(vNode, eventNode) if (tag === __2_TAGGER) { - var subNode = vNode.__node; - var tagger = vNode.__tagger; - - while (subNode.$ === __2_TAGGER) - { - typeof tagger !== 'object' - ? tagger = [tagger, subNode.__tagger] - : tagger.push(subNode.__tagger); - - subNode = subNode.__node; - } - - var subEventRoot = { __tagger: tagger, __parent: eventNode }; - var domNode = _VirtualDom_render(subNode, subEventRoot); - domNode.elm_event_node_ref = subEventRoot; - return domNode; + return _VirtualDom_render(vNode.__node, function (msg) { return eventNode(vNode.__tagger(msg)) }); } if (tag === __2_CUSTOM) @@ -697,15 +681,6 @@ function _VirtualDom_equalEvents(x, y) // DIFF -// TODO: Should we do patches like in iOS? -// -// type Patch -// = At Int Patch -// | Batch (List Patch) -// | Change ... -// -// How could it not be better? -// function _VirtualDom_diff(x, y) { // var patches = []; @@ -730,10 +705,10 @@ function _VirtualDom_pushPatch(patches, type, index, data) } -function _VirtualDom_diffHelp(x, y, sendToApp) +function _VirtualDom_diffHelp(x, y, eventNode) { // Note: We can’t exit early if `x === y` because: - // - Event listeners may need to reference a new `sendToApp`. + // - Event listeners may need to reference a new `eventNode`. // - .i0 or .i1 may need to be reset. // Remember: When virtualizing already existing DOM, we can’t know @@ -746,7 +721,7 @@ function _VirtualDom_diffHelp(x, y, sendToApp) } if (y.$ === __2_TAGGER) { - _VirtualDom_diffHelp(x, y.__node, function (msg) { return sendToApp(y.__tagger(msg)) }); + _VirtualDom_diffHelp(x, y.__node, function (msg) { return eventNode(y.__tagger(msg)) }); return; } @@ -763,21 +738,35 @@ function _VirtualDom_diffHelp(x, y, sendToApp) if (same) { y.__node = x.__node; + // TODO: Still need to visit everything to reset counters and update event listeners. return; } y.__node = y.__thunk(); - _VirtualDom_diffHelp(x.__node, y.__node, sendToApp); + _VirtualDom_diffHelp(x.__node, y.__node, eventNode); } else { - _VirtualDom_diffHelp(x.__node, y, sendToApp); + _VirtualDom_diffHelp(x.__node, y, eventNode); } return; } if (y.$ === __2_THUNK) { - _VirtualDom_diffHelp(x, y.__thunk(), sendToApp); + _VirtualDom_diffHelp(x, y.__thunk(), eventNode); return; } + var domNode; + + // Get DOM node, increase counter, and reset the counter not used during this render. + if (_VirtualDom_even) { + domNode = x._.nodes[y._.i0]; + y._.i0++; + y._.i1 = 0; + } else { + domNode = x._.nodes[y._.i1]; + y._.i1++; + y._.i0 = 0; + } + var xType = x.$; var yType = y.$; @@ -803,13 +792,6 @@ function _VirtualDom_diffHelp(x, y, sendToApp) } } - // Reset the counter not used during this render. - if (_VirtualDom_even) { - y._.i1 = 0; - } else { - y._.i0 = 0; - } - // Now we know that both nodes are the same $. switch (yType) { @@ -818,16 +800,16 @@ function _VirtualDom_diffHelp(x, y, sendToApp) // when equal and we should only reset i0/i1 and update functions. if (x !== y && x.__text !== y.__text) { - _VirtualDom_pushPatch(patches, __3_TEXT, index, y.__text); + domNode.replaceData(0, domNode.length, patch.__data); } return; case __2_NODE: - _VirtualDom_diffNodes(x, y, patches, index, _VirtualDom_diffKids); + _VirtualDom_diffNodes(x, y, eventNode, _VirtualDom_diffKids); return; case __2_KEYED_NODE: - _VirtualDom_diffNodes(x, y, patches, index, _VirtualDom_diffKeyedKids); + _VirtualDom_diffNodes(x, y, eventNode, _VirtualDom_diffKeyedKids); return; case __2_CUSTOM: @@ -847,7 +829,7 @@ function _VirtualDom_diffHelp(x, y, sendToApp) } } -function _VirtualDom_diffNodes(x, y, patches, index, diffKids) +function _VirtualDom_diffNodes(x, y, eventNode, diffKids) { // Bail if obvious indicators have changed. Implies more serious // structural changes such that it's not worth it to diff. @@ -860,7 +842,7 @@ function _VirtualDom_diffNodes(x, y, patches, index, diffKids) var factsDiff = _VirtualDom_diffFacts(x.__facts, y.__facts); factsDiff && _VirtualDom_pushPatch(patches, __3_FACTS, index, factsDiff); - diffKids(x, y, patches, index); + diffKids(x, y, eventNode); } @@ -940,7 +922,7 @@ function _VirtualDom_diffFacts(x, y, category) // DIFF KIDS -function _VirtualDom_diffKids(xParent, yParent, patches, index) +function _VirtualDom_diffKids(xParent, yParent, eventNode) { var xKids = xParent.__kids; var yKids = yParent.__kids; @@ -952,17 +934,38 @@ function _VirtualDom_diffKids(xParent, yParent, patches, index) if (xLen > yLen) { - _VirtualDom_pushPatch(patches, __3_REMOVE_LAST, index, { - __length: yLen, - __diff: xLen - yLen - }); + // _VirtualDom_pushPatch(patches, __3_REMOVE_LAST, index, { + // __length: yLen, + // __diff: xLen - yLen + // }); + var diff = xLen - yLen; + for (var i = 0; i < diff; i++) + { + var child = xKids[yLen]; + var domNode = child._.nodes.splice(_VirtualDom_even ? child.i0 : child.i1, 1); + domNode.removeChild(domNode[0]); + } } else if (xLen < yLen) { - _VirtualDom_pushPatch(patches, __3_APPEND, index, { - __length: xLen, - __kids: yKids - }); + // _VirtualDom_pushPatch(patches, __3_APPEND, index, { + // __length: xLen, + // __kids: yKids + // }); + var theEnd = domNode.childNodes[xLen]; + for (var i = xLen; i < yKids.length; i++) + { + var child = yKids[i]; + var domNode = _VirtualDom_render(child, eventNode); + domNode.insertBefore(domNode, theEnd); + if (_VirtualDom_even) { + child._.nodes.splice(child._.i0, 0, domNode); + child._.i0++; + } else { + child._.nodes.splice(child._.i1, 0, domNode); + child._.i1++; + } + } } // PAIRWISE DIFF EVERYTHING ELSE @@ -970,8 +973,7 @@ function _VirtualDom_diffKids(xParent, yParent, patches, index) for (var minLen = xLen < yLen ? xLen : yLen, i = 0; i < minLen; i++) { var xKid = xKids[i]; - _VirtualDom_diffHelp(xKid, yKids[i], patches, ++index); - index += xKid.__descendantsCount || 0; + _VirtualDom_diffHelp(xKid, yKids[i], eventNode); } } @@ -1339,7 +1341,7 @@ function _VirtualDom_addDomNodesHelp(domNode, vNode, patches, i, low, high, even // APPLY PATCHES -function _VirtualDom_applyPatches(rootDomNode, oldVirtualNode, newVirtualNode, sendToApp) +function _VirtualDom_applyPatches(rootDomNode, oldVirtualNode, newVirtualNode, eventNode) { // if (patches.length === 0) // { @@ -1348,7 +1350,7 @@ function _VirtualDom_applyPatches(rootDomNode, oldVirtualNode, newVirtualNode, s // _VirtualDom_addDomNodes(rootDomNode, oldVirtualNode, patches, eventNode); // return _VirtualDom_applyPatchesHelp(rootDomNode, patches); - _VirtualDom_diffHelp(oldVirtualNode, newVirtualNode, sendToApp); + _VirtualDom_diffHelp(oldVirtualNode, newVirtualNode, eventNode); _VirtualDom_even = !_VirtualDom_even; return newVirtualNode._.nodes[0]; } @@ -1448,11 +1450,6 @@ function _VirtualDom_applyPatchRedraw(domNode, vNode, eventNode) var parentNode = domNode.parentNode; var newNode = _VirtualDom_render(vNode, eventNode); - if (!newNode.elm_event_node_ref) - { - newNode.elm_event_node_ref = domNode.elm_event_node_ref; - } - if (parentNode && newNode !== domNode) { parentNode.replaceChild(newNode, domNode); @@ -1572,7 +1569,6 @@ function _VirtualDom_dekey(keyedNode) __kids: kids, __namespace: keyedNode.__namespace }, "_", { - value: keyedNode._, - writable: true + value: keyedNode._ }); } From 68aec1843a97e927317cc03139632ba9ba38e0e4 Mon Sep 17 00:00:00 2001 From: Simon Lydell Date: Mon, 24 Jun 2024 18:59:09 +0100 Subject: [PATCH 004/123] Proper redraw call --- src/Elm/Kernel/VirtualDom.js | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/Elm/Kernel/VirtualDom.js b/src/Elm/Kernel/VirtualDom.js index 75bc379a..1b35ff69 100644 --- a/src/Elm/Kernel/VirtualDom.js +++ b/src/Elm/Kernel/VirtualDom.js @@ -786,8 +786,7 @@ function _VirtualDom_diffHelp(x, y, eventNode) } else { - // TODO: Redraw instead of pushing patch - _VirtualDom_pushPatch(patches, __3_REDRAW, index, y); + _VirtualDom_applyPatchRedraw(domNode, y, eventNode); return; } } @@ -805,17 +804,17 @@ function _VirtualDom_diffHelp(x, y, eventNode) return; case __2_NODE: - _VirtualDom_diffNodes(x, y, eventNode, _VirtualDom_diffKids); + _VirtualDom_diffNodes(domNode, x, y, eventNode, _VirtualDom_diffKids); return; case __2_KEYED_NODE: - _VirtualDom_diffNodes(x, y, eventNode, _VirtualDom_diffKeyedKids); + _VirtualDom_diffNodes(domNode, x, y, eventNode, _VirtualDom_diffKeyedKids); return; case __2_CUSTOM: if (x.__render !== y.__render) { - _VirtualDom_pushPatch(patches, __3_REDRAW, index, y); + _VirtualDom_applyPatchRedraw(domNode, y, eventNode); return; } @@ -829,13 +828,13 @@ function _VirtualDom_diffHelp(x, y, eventNode) } } -function _VirtualDom_diffNodes(x, y, eventNode, diffKids) +function _VirtualDom_diffNodes(domNode, x, y, eventNode, diffKids) { // Bail if obvious indicators have changed. Implies more serious // structural changes such that it's not worth it to diff. if (x.__tag !== y.__tag || x.__namespace !== y.__namespace) { - _VirtualDom_pushPatch(patches, __3_REDRAW, index, y); + _VirtualDom_applyPatchRedraw(domNode, y, eventNode); return; } @@ -1454,7 +1453,13 @@ function _VirtualDom_applyPatchRedraw(domNode, vNode, eventNode) { parentNode.replaceChild(newNode, domNode); } - return newNode; + + // `.i0` or `.i1` has already been incremented at this point, so remove 1. + if (_VirtualDom_even) { + x._.nodes[y._.i0 - 1] = newNode; + } else { + x._.nodes[y._.i1 - 1] = newNode; + } } From d6b29d4fdfb064b65248602b9b2a8f8c93257e98 Mon Sep 17 00:00:00 2001 From: Simon Lydell Date: Mon, 24 Jun 2024 19:21:06 +0100 Subject: [PATCH 005/123] Begin lazy visit --- src/Elm/Kernel/VirtualDom.js | 58 +++++++++++++++++++++++++++++++++++- 1 file changed, 57 insertions(+), 1 deletion(-) diff --git a/src/Elm/Kernel/VirtualDom.js b/src/Elm/Kernel/VirtualDom.js index 1b35ff69..c87a9fda 100644 --- a/src/Elm/Kernel/VirtualDom.js +++ b/src/Elm/Kernel/VirtualDom.js @@ -738,7 +738,11 @@ function _VirtualDom_diffHelp(x, y, eventNode) if (same) { y.__node = x.__node; - // TODO: Still need to visit everything to reset counters and update event listeners. + // We still need to visit every node inside the lazy node, to + // make sure that the event listeners get the current + // `eventNode`, and to increase and reset counters. This is + // cheaper than calling `view`, diffing and rendering at least. + _VirtualDom_lazyVisit(y, eventNode); return; } y.__node = y.__thunk(); @@ -811,6 +815,7 @@ function _VirtualDom_diffHelp(x, y, eventNode) _VirtualDom_diffNodes(domNode, x, y, eventNode, _VirtualDom_diffKeyedKids); return; + // TODO: Custom. case __2_CUSTOM: if (x.__render !== y.__render) { @@ -828,6 +833,57 @@ function _VirtualDom_diffHelp(x, y, eventNode) } } +function _VirtualDom_lazyVisit(y, eventNode) +{ + switch (y.$) + { + case __2_TAGGER: + _VirtualDom_lazyVisit(y.__node, function (msg) { return eventNode(y.__tagger(msg)) }); + return; + + case __2_THUNK: + _VirtualDom_lazyVisit(y.__node, eventNode); + return; + } + + var domNode; + + // Get DOM node, increase counter, and reset the counter not used during this render. + if (_VirtualDom_even) { + domNode = x._.nodes[y._.i0]; + y._.i0++; + y._.i1 = 0; + } else { + domNode = x._.nodes[y._.i1]; + y._.i1++; + y._.i0 = 0; + } + + // TODO: Apply eventNode to event listeners. First do that in the regular flow, though. + switch (y.$) + { + case __2_TEXT: + return; + + case __2_NODE: + for (var i = 0; i < y.kids.length; i++) + { + _VirtualDom_lazyVisit(yKids[i], eventNode); + } + return; + + case __2_KEYED_NODE: + for (var i = 0; i < y.kids.length; i++) + { + _VirtualDom_lazyVisit(yKids[i].b, eventNode); + } + return; + + case __2_CUSTOM: + return; + } +} + function _VirtualDom_diffNodes(domNode, x, y, eventNode, diffKids) { // Bail if obvious indicators have changed. Implies more serious From 5e8e07c6809dcdf12ab96adf466613ce99269c22 Mon Sep 17 00:00:00 2001 From: Simon Lydell Date: Mon, 24 Jun 2024 19:47:04 +0100 Subject: [PATCH 006/123] Add TODO for facts --- src/Elm/Kernel/VirtualDom.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Elm/Kernel/VirtualDom.js b/src/Elm/Kernel/VirtualDom.js index c87a9fda..e3404fbd 100644 --- a/src/Elm/Kernel/VirtualDom.js +++ b/src/Elm/Kernel/VirtualDom.js @@ -905,9 +905,7 @@ function _VirtualDom_diffNodes(domNode, x, y, eventNode, diffKids) // DIFF FACTS -// TODO Instead of creating a new diff object, it's possible to just test if -// there *is* a diff. During the actual patch, do the diff again and make the -// modifications directly. This way, there's no new allocations. Worth it? +// TODO: Basically steal the approach from safe-virtual-dom. function _VirtualDom_diffFacts(x, y, category) { var diff; From 1818df4e3f5357fcd78489ca13f86595c4878f3b Mon Sep 17 00:00:00 2001 From: Simon Lydell Date: Mon, 24 Jun 2024 19:58:17 +0100 Subject: [PATCH 007/123] Make sure eventNode is up-to-date for lazy --- src/Elm/Kernel/VirtualDom.js | 40 +++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/src/Elm/Kernel/VirtualDom.js b/src/Elm/Kernel/VirtualDom.js index e3404fbd..6d2c3edd 100644 --- a/src/Elm/Kernel/VirtualDom.js +++ b/src/Elm/Kernel/VirtualDom.js @@ -585,6 +585,7 @@ function _VirtualDom_applyEvents(domNode, eventNode, events) if (oldHandler.$ === newHandler.$) { oldCallback.__handler = newHandler; + oldCallback.__eventNode = eventNode; continue; } domNode.removeEventListener(key, oldCallback); @@ -599,6 +600,20 @@ function _VirtualDom_applyEvents(domNode, eventNode, events) } } +function _VirtualDom_lazyUpdateEvents(domNode, eventNode) +{ + var allCallbacks = domNode.elmFs; + + if (allCallbacks) + { + for (var key in allCallbacks) + { + var oldCallback = allCallbacks[key]; + oldCallback.__eventNode = eventNode; + } + } +} + // PASSIVE EVENTS @@ -619,11 +634,12 @@ catch(e) {} // EVENT HANDLERS -function _VirtualDom_makeCallback(eventNode, initialHandler) +function _VirtualDom_makeCallback(initialEventNode, initialHandler) { function callback(event) { var handler = callback.__handler; + var eventNode = callback.__eventNode; var result = __Json_runHelp(handler.a, event); if (!__Result_isOk(result)) @@ -646,27 +662,11 @@ function _VirtualDom_makeCallback(eventNode, initialHandler) (tag == 2 ? value.b : tag == 3 && value.__$preventDefault) && event.preventDefault(), eventNode ); - var tagger; - var i; - while (tagger = currentEventNode.__tagger) - { - if (typeof tagger == 'function') - { - message = tagger(message); - } - else - { - for (var i = tagger.length; i--; ) - { - message = tagger[i](message); - } - } - currentEventNode = currentEventNode.__parent; - } currentEventNode(message, stopPropagation); // stopPropagation implies isSync } callback.__handler = initialHandler; + callback.__eventNode = initialEventNode; return callback; } @@ -859,13 +859,13 @@ function _VirtualDom_lazyVisit(y, eventNode) y._.i0 = 0; } - // TODO: Apply eventNode to event listeners. First do that in the regular flow, though. switch (y.$) { case __2_TEXT: return; case __2_NODE: + _VirtualDom_lazyUpdateEvents(domNode, eventNode); for (var i = 0; i < y.kids.length; i++) { _VirtualDom_lazyVisit(yKids[i], eventNode); @@ -873,6 +873,7 @@ function _VirtualDom_lazyVisit(y, eventNode) return; case __2_KEYED_NODE: + _VirtualDom_lazyUpdateEvents(domNode, eventNode); for (var i = 0; i < y.kids.length; i++) { _VirtualDom_lazyVisit(yKids[i].b, eventNode); @@ -880,6 +881,7 @@ function _VirtualDom_lazyVisit(y, eventNode) return; case __2_CUSTOM: + _VirtualDom_lazyUpdateEvents(domNode, eventNode); return; } } From 4c7e94a9520dec8e02f18e39f36080463bfaea7c Mon Sep 17 00:00:00 2001 From: Simon Lydell Date: Mon, 24 Jun 2024 20:00:27 +0100 Subject: [PATCH 008/123] Implement custom patch --- src/Elm/Kernel/VirtualDom.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Elm/Kernel/VirtualDom.js b/src/Elm/Kernel/VirtualDom.js index 6d2c3edd..32110b78 100644 --- a/src/Elm/Kernel/VirtualDom.js +++ b/src/Elm/Kernel/VirtualDom.js @@ -815,7 +815,6 @@ function _VirtualDom_diffHelp(x, y, eventNode) _VirtualDom_diffNodes(domNode, x, y, eventNode, _VirtualDom_diffKeyedKids); return; - // TODO: Custom. case __2_CUSTOM: if (x.__render !== y.__render) { @@ -827,7 +826,7 @@ function _VirtualDom_diffHelp(x, y, eventNode) factsDiff && _VirtualDom_pushPatch(patches, __3_FACTS, index, factsDiff); var patch = y.__diff(x.__model, y.__model); - patch && _VirtualDom_pushPatch(patches, __3_CUSTOM, index, patch); + patch && patch(domNode); return; } From a28f6a4f67ac64cc6319f5e4544897e461c9a047 Mon Sep 17 00:00:00 2001 From: Simon Lydell Date: Mon, 24 Jun 2024 20:01:18 +0100 Subject: [PATCH 009/123] Add TODO for keyed kids --- src/Elm/Kernel/VirtualDom.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Elm/Kernel/VirtualDom.js b/src/Elm/Kernel/VirtualDom.js index 32110b78..59af29bf 100644 --- a/src/Elm/Kernel/VirtualDom.js +++ b/src/Elm/Kernel/VirtualDom.js @@ -1036,6 +1036,7 @@ function _VirtualDom_diffKids(xParent, yParent, eventNode) // KEYED DIFF +// TODO: Keyed kids. function _VirtualDom_diffKeyedKids(xParent, yParent, patches, rootIndex) { var localPatches = []; From 4e9a1e1dc2098bd2daec9dd072eb688e709cc92f Mon Sep 17 00:00:00 2001 From: Simon Lydell Date: Mon, 24 Jun 2024 20:07:20 +0100 Subject: [PATCH 010/123] Re-introduce `x === y` optimization --- src/Elm/Kernel/VirtualDom.js | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/src/Elm/Kernel/VirtualDom.js b/src/Elm/Kernel/VirtualDom.js index 59af29bf..e33ad7cb 100644 --- a/src/Elm/Kernel/VirtualDom.js +++ b/src/Elm/Kernel/VirtualDom.js @@ -707,9 +707,11 @@ function _VirtualDom_pushPatch(patches, type, index, data) function _VirtualDom_diffHelp(x, y, eventNode) { - // Note: We can’t exit early if `x === y` because: - // - Event listeners may need to reference a new `eventNode`. - // - .i0 or .i1 may need to be reset. + if (x === y) + { + _VirtualDom_quickVisit(y, eventNode); + return; + } // Remember: When virtualizing already existing DOM, we can’t know // where `map` and `lazy` nodes should be, and which ones are `Keyed`. @@ -742,7 +744,7 @@ function _VirtualDom_diffHelp(x, y, eventNode) // make sure that the event listeners get the current // `eventNode`, and to increase and reset counters. This is // cheaper than calling `view`, diffing and rendering at least. - _VirtualDom_lazyVisit(y, eventNode); + _VirtualDom_quickVisit(y, eventNode); return; } y.__node = y.__thunk(); @@ -799,9 +801,7 @@ function _VirtualDom_diffHelp(x, y, eventNode) switch (yType) { case __2_TEXT: - // TODO: Can do optimizations like this with `x === y` to skip work - // when equal and we should only reset i0/i1 and update functions. - if (x !== y && x.__text !== y.__text) + if (x.__text !== y.__text) { domNode.replaceData(0, domNode.length, patch.__data); } @@ -832,16 +832,19 @@ function _VirtualDom_diffHelp(x, y, eventNode) } } -function _VirtualDom_lazyVisit(y, eventNode) +// When we know that a node does not need updating, just quickly visit its children to: +// - Update event listeners’ reference to the current `eventNode`. +// - Reset .i0 or .i1. +function _VirtualDom_quickVisit(y, eventNode) { switch (y.$) { case __2_TAGGER: - _VirtualDom_lazyVisit(y.__node, function (msg) { return eventNode(y.__tagger(msg)) }); + _VirtualDom_quickVisit(y.__node, function (msg) { return eventNode(y.__tagger(msg)) }); return; case __2_THUNK: - _VirtualDom_lazyVisit(y.__node, eventNode); + _VirtualDom_quickVisit(y.__node, eventNode); return; } @@ -867,7 +870,7 @@ function _VirtualDom_lazyVisit(y, eventNode) _VirtualDom_lazyUpdateEvents(domNode, eventNode); for (var i = 0; i < y.kids.length; i++) { - _VirtualDom_lazyVisit(yKids[i], eventNode); + _VirtualDom_quickVisit(yKids[i], eventNode); } return; @@ -875,7 +878,7 @@ function _VirtualDom_lazyVisit(y, eventNode) _VirtualDom_lazyUpdateEvents(domNode, eventNode); for (var i = 0; i < y.kids.length; i++) { - _VirtualDom_lazyVisit(yKids[i].b, eventNode); + _VirtualDom_quickVisit(yKids[i].b, eventNode); } return; From 5146f1fdd27ba1710373e5fc05876b7ac7406848 Mon Sep 17 00:00:00 2001 From: Simon Lydell Date: Mon, 24 Jun 2024 20:13:51 +0100 Subject: [PATCH 011/123] Add TODO for virtualize