From adf3e1b7695e83b0bfb7e91257e11aea89bb5c84 Mon Sep 17 00:00:00 2001 From: Roman Vishnevskii Date: Wed, 5 Apr 2017 18:00:22 +0700 Subject: [PATCH 1/9] DOM diff problem: max prefix and suffix mutable --- karax.nim | 39 +++++++++++++++++++++++++++++---------- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/karax.nim b/karax.nim index 888a2e9..ac3b432 100644 --- a/karax.nim +++ b/karax.nim @@ -119,22 +119,41 @@ proc updateElement(parent, current: Node, newNode, oldNode: VNode) = elif newNode.kind != VNodeKind.text: let newLength = newNode.len let oldLength = oldNode.len + let minLength = min(newLength, oldLength) assert oldNode.kind == newNode.kind when false: if current.nodeName != toTag[oldNode.kind]: kout current.nodeName kout toTag[oldNode.kind] assert false - for i in 0..min(newLength, oldLength)-1: - updateElement(current, current.childNodes[i], - newNode[i], - oldNode[i]) - if newLength > oldLength: - for i in oldLength..newLength-1: - current.appendChild(vnodeToDom(newNode[i])) - elif oldLength > newLength: - for i in countdown(oldLength-1, newLength): - current.removeChild(current.lastChild) + + var commonPrefix = 0 + while commonPrefix < minLength and newNode[commonPrefix] == oldNode[commonPrefix]: + inc commonPrefix + + var oldPos = oldLength - 1 + var newPos = newLength - 1 + while oldPos >= 0 and newPos >= 0 and newNode[newPos] == oldNode[oldPos]: + dec oldPos + dec newPos + + var pos = commonPrefix + while pos <= newPos and pos <= oldPos: + updateElement(current, current.childNodes[pos], + newNode[pos], + oldNode[pos]) + inc pos + + while pos <= newPos: + if newPos == newLength - 1: + current.appendChild(vnodeToDom(newNode[pos])) + else: + current.insertBefore(vnodeToDom(newNode[pos]), current.childNodes[pos + 1]) + inc pos + + while pos <= oldPos: + current.removeChild(current.childNodes[pos]) + inc pos proc dodraw() = let newtree = dorender() From 477bb60b14ff9e869ebf1f9d5a41a5447281205c Mon Sep 17 00:00:00 2001 From: Roman Vishnevskii Date: Thu, 6 Apr 2017 17:46:01 +0700 Subject: [PATCH 2/9] DOM diff problem: first success --- karax.nim | 177 ++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 157 insertions(+), 20 deletions(-) diff --git a/karax.nim b/karax.nim index ac3b432..8ffcbe1 100644 --- a/karax.nim +++ b/karax.nim @@ -109,13 +109,32 @@ proc equals(a, b: VNode): bool = a.class = b.class return true +proc equalsTree(a, b : VNode): bool = + # kout cstring("equalsTree: (" & $a.kind & " = " & a.id & ") -> (" & + # $b.kind & " = " & b.id & ")") + # assert a != nil and b != nil + # kout cstring("equalsTree") + if not equals(a, b): + return false + else: + if len(a) != len(b): + return false + for i in 0..len(a)-1: + if not equalsTree(a[i], b[i]): + return false + return true + proc updateElement(parent, current: Node, newNode, oldNode: VNode) = + #kout cstring("updateElement: (" & current.nodeName & " = " & current.id & ")") if not equals(newNode, oldNode): let n = vnodeToDom(newNode) if parent == nil: replaceById("ROOT", n) else: parent.replaceChild(n, current) + kout cstring("ReplaceChild") + #kout cstring("ReplaceChild: (" & current.nodeName & " = " & newNode.id & ") -> (" & + # n.nodeName & " = " & oldNode.id & ")") elif newNode.kind != VNodeKind.text: let newLength = newNode.len let oldLength = oldNode.len @@ -126,34 +145,152 @@ proc updateElement(parent, current: Node, newNode, oldNode: VNode) = kout current.nodeName kout toTag[oldNode.kind] assert false - - var commonPrefix = 0 - while commonPrefix < minLength and newNode[commonPrefix] == oldNode[commonPrefix]: - inc commonPrefix + var commonPrefix = 0 + while commonPrefix < minLength and equalsTree(newNode[commonPrefix], oldNode[commonPrefix]): + inc commonPrefix + var oldPos = oldLength - 1 var newPos = newLength - 1 - while oldPos >= 0 and newPos >= 0 and newNode[newPos] == oldNode[oldPos]: + while oldPos >= commonPrefix and newPos >= commonPrefix and equalsTree(newNode[newPos], oldNode[oldPos]): dec oldPos dec newPos - var pos = commonPrefix - while pos <= newPos and pos <= oldPos: - updateElement(current, current.childNodes[pos], - newNode[pos], - oldNode[pos]) - inc pos - - while pos <= newPos: - if newPos == newLength - 1: - current.appendChild(vnodeToDom(newNode[pos])) - else: - current.insertBefore(vnodeToDom(newNode[pos]), current.childNodes[pos + 1]) - inc pos + if commonPrefix + newLength - newPos > 1000: + kout cstring($(commonPrefix + newLength - newPos)) - while pos <= oldPos: - current.removeChild(current.childNodes[pos]) + var pos = min(oldPos, newPos) + 1 + + # kout cstring("commonPrefix = " & $commonPrefix) + # kout cstring("oldPos = " & $oldPos) + # kout cstring("newPos = " & $newPos) + # kout cstring("newLength = " & $newLength) + # kout cstring("oldLength = " & $oldLength) + # kout cstring("-----------") + # for i in items(newNode): + # kout cstring(i.id) + # kout cstring("-----") + # for i in items(oldNode): + # kout cstring(i.id) + + for i in commonPrefix..pos-1: + updateElement(current, current.childNodes[i], + newNode[i], + oldNode[i]) + #kout cstring(newNode[i].id & " " & oldNode[i].id) + #kout cstring("----------") + + var nextChildPos = oldPos + 1 + while pos <= newPos: + if nextChildPos == oldLength: + current.appendChild(vnodeToDom(newNode[pos])) + kout cstring"appendChild" + #kout cstring("pos = " & $pos) + #kout cstring("newLength = " & $newLength) + else: + kout cstring"insertBefore" + current.insertBefore(vnodeToDom(newNode[pos]), current.childNodes[nextChildPos]) inc pos + inc nextChildPos + + for i in 0..oldPos-pos: + kout cstring"removeChild" + current.removeChild(current.childNodes[pos]) + + # var itNew = newPos + 1 + # var itOld = oldPos + 1 + # while itNew < newLength and itOld < oldLength: + # updateElement(current, current.childNodes[itNew], + # newNode[itNew], + # oldNode[itOld]) + + # kout cstring("commonPrefix = " & $commonPrefix) + # kout cstring("oldPos = " & $oldPos) + # kout cstring("newPos = " & $newPos) + # kout cstring("newLength = " & $newLength) + # kout cstring("oldLength = " & $oldLength) + + # for i in 0..min(newLength, oldLength)-1: + # updateElement(current, current.childNodes[i], + # newNode[i], + # oldNode[i]) + # if newLength > oldLength: + # for i in oldLength..newLength-1: + # kout cstring"appendChild" + # current.appendChild(vnodeToDom(newNode[i])) + # elif oldLength > newLength: + # for i in countdown(oldLength-1, newLength): + # kout cstring"removeChild" + # current.removeChild(current.lastChild) + +proc updateElement1(parent, current: Node, newNode, oldNode: VNode) = + if not equals(newNode, oldNode): + let n = vnodeToDom(newNode) + if parent == nil: + replaceById("ROOT", n) + else: + parent.replaceChild(n, current) + kout cstring"ReplaceChild" + elif newNode.kind != VNodeKind.text: + let newLength = newNode.len + let oldLength = oldNode.len + let minLength = min(newLength, oldLength) + assert oldNode.kind == newNode.kind + when false: + if current.nodeName != toTag[oldNode.kind]: + kout current.nodeName + kout toTag[oldNode.kind] + assert false + + # var commonPrefix = 0 + # while commonPrefix < minLength and equals(newNode[commonPrefix], oldNode[commonPrefix]): + # inc commonPrefix + + # var oldPos = oldLength - 1 + # var newPos = newLength - 1 + # while oldPos >= commonPrefix and newPos >= commonPrefix and equals(newNode[newPos], oldNode[oldPos]): + # dec oldPos + # dec newPos + + # var pos = min(oldPos, newPos) + 1 + # var nextChildPos = oldPos + 1 + # while pos <= newPos: + # if nextChildPos == oldLength: + # current.appendChild(vnodeToDom(newNode[pos])) + # kout cstring"appendChild" + # else: + # kout cstring"insertBefore" + # current.insertBefore(vnodeToDom(newNode[pos]), current.childNodes[nextChildPos]) + # inc pos + # inc nextChildPos + + # for i in 0..oldPos-pos: + # kout cstring"removeChild" + # current.removeChild(current.childNodes[pos]) + + # # kout cstring("commonPrefix = " & $commonPrefix) + # # kout cstring("oldPos = " & $oldPos) + # # kout cstring("newPos = " & $newPos) + # # kout cstring("newLength = " & $newLength) + # # kout cstring("oldLength = " & $oldLength) + + # for i in 0..newLength-1: + # updateElement(current, current.childNodes[i], + # newNode[i], + # oldNode[i]) + + for i in 0..min(newLength, oldLength)-1: + updateElement(current, current.childNodes[i], + newNode[i], + oldNode[i]) + if newLength > oldLength: + for i in oldLength..newLength-1: + kout cstring"appendChild" + current.appendChild(vnodeToDom(newNode[i])) + elif oldLength > newLength: + for i in countdown(oldLength-1, newLength): + kout cstring"removeChild" + current.removeChild(current.lastChild) proc dodraw() = let newtree = dorender() From c75295d3345d51185b312c8461098a39a9b4694b Mon Sep 17 00:00:00 2001 From: Roman Vishnevskii Date: Fri, 7 Apr 2017 18:49:11 +0700 Subject: [PATCH 3/9] DOM diff problem: fix bug, tests --- diffDomTests.html | 19 +++++++++++ diffDomTests.nim | 62 ++++++++++++++++++++++++++++++++++ karax.nim | 86 +++++++++-------------------------------------- todoapp.nim | 3 ++ 4 files changed, 99 insertions(+), 71 deletions(-) create mode 100644 diffDomTests.html create mode 100644 diffDomTests.nim diff --git a/diffDomTests.html b/diffDomTests.html new file mode 100644 index 0000000..5ecf130 --- /dev/null +++ b/diffDomTests.html @@ -0,0 +1,19 @@ + + + + + Diff dom tests + + + + + +
+ + + + + + diff --git a/diffDomTests.nim b/diffDomTests.nim new file mode 100644 index 0000000..359b00e --- /dev/null +++ b/diffDomTests.nim @@ -0,0 +1,62 @@ + +import vdom, karax, karaxdsl, jdict, jstrutils, parseutils, sequtils + +var + entries: seq[cstring] + +# result: 0 1 2 3 4 7 5 +proc test1(ev: Event; n: VNode) = + entries = @[cstring("0"), cstring("1"), cstring("2"), cstring("3"), cstring("4"), cstring("5") ] + entries.insert(cstring("7"), 5) + +# result: 8 0 1 2 3 4 7 5 +proc test2(ev: Event; n: VNode) = + entries = @[cstring("0"), cstring("1"), cstring("2"), cstring("3"), cstring("4"), cstring("5") ] + entries.insert(cstring("7"), 5) + entries.insert(cstring("8"), 0) + +# result: 2 3 4 1 +proc test3(ev: Event; n: VNode) = + entries = @[cstring("0"), cstring("1"), cstring("2"), cstring("3"), cstring("4"), cstring("5") ] + entries = @[cstring("2"), cstring("3"), cstring("4"), cstring("1") ] + +# result: 5 6 7 8 +proc test4(ev: Event; n: VNode) = + entries = @[cstring("0"), cstring("1"), cstring("2"), cstring("3"), cstring("4"), cstring("5") ] + entries = @[cstring("5"), cstring("6"), cstring("7"), cstring("8") ] + +# result: 0 1 3 5 4 5 +proc test5(ev: Event; n: VNode) = + entries = @[cstring("0"), cstring("1"), cstring("2"), cstring("3"), cstring("4"), cstring("5") ] + entries = @[cstring("0"), cstring("1"), cstring("3"), cstring("5"), cstring("4"), cstring("5") ] + + +proc createEntry(id: int): VNode = + result = buildHtml(): + button(id="" & $id): + text $id + +proc createDom(): VNode = + result = buildHtml(tdiv()): + button(onclick=test1): + text "Test1" + button(onclick=test2): + text "Test2" + button(onclick=test3): + text "Test3" + button(onclick=test4): + text "Test4" + button(onclick=test5): + text "Test5" + ul(): + for e in entries: + li: + createEntry(parseInt(e)) + + +setRenderer createDom + +proc onload(session: cstring) {.exportc.} = + for i in 0..5: # 0_000: + entries.add(cstring($i)) + init() diff --git a/karax.nim b/karax.nim index 8ffcbe1..3cf67d2 100644 --- a/karax.nim +++ b/karax.nim @@ -137,7 +137,7 @@ proc updateElement(parent, current: Node, newNode, oldNode: VNode) = # n.nodeName & " = " & oldNode.id & ")") elif newNode.kind != VNodeKind.text: let newLength = newNode.len - let oldLength = oldNode.len + var oldLength = oldNode.len let minLength = min(newLength, oldLength) assert oldNode.kind == newNode.kind when false: @@ -181,15 +181,27 @@ proc updateElement(parent, current: Node, newNode, oldNode: VNode) = #kout cstring("----------") var nextChildPos = oldPos + 1 + # kout cstring("pos = " & $pos) + # kout cstring("nextChildPos = " & $nextChildPos) while pos <= newPos: if nextChildPos == oldLength: current.appendChild(vnodeToDom(newNode[pos])) - kout cstring"appendChild" + # kout cstring"appendChild " & newNode[pos].id + # kout cstring("commonPrefix = " & $commonPrefix) + # kout cstring("oldPos = " & $oldPos) + # kout cstring("newPos = " & $newPos) + # kout cstring("newLength = " & $newLength) + # kout cstring("oldLength = " & $oldLength) + + # kout cstring("pos = " & $pos) + # kout cstring("nextChildPos = " & $nextChildPos) + # kout cstring("-----------") #kout cstring("pos = " & $pos) #kout cstring("newLength = " & $newLength) else: - kout cstring"insertBefore" + kout cstring"insertBefore " & newNode[pos].id current.insertBefore(vnodeToDom(newNode[pos]), current.childNodes[nextChildPos]) + inc oldLength inc pos inc nextChildPos @@ -223,74 +235,6 @@ proc updateElement(parent, current: Node, newNode, oldNode: VNode) = # kout cstring"removeChild" # current.removeChild(current.lastChild) -proc updateElement1(parent, current: Node, newNode, oldNode: VNode) = - if not equals(newNode, oldNode): - let n = vnodeToDom(newNode) - if parent == nil: - replaceById("ROOT", n) - else: - parent.replaceChild(n, current) - kout cstring"ReplaceChild" - elif newNode.kind != VNodeKind.text: - let newLength = newNode.len - let oldLength = oldNode.len - let minLength = min(newLength, oldLength) - assert oldNode.kind == newNode.kind - when false: - if current.nodeName != toTag[oldNode.kind]: - kout current.nodeName - kout toTag[oldNode.kind] - assert false - - # var commonPrefix = 0 - # while commonPrefix < minLength and equals(newNode[commonPrefix], oldNode[commonPrefix]): - # inc commonPrefix - - # var oldPos = oldLength - 1 - # var newPos = newLength - 1 - # while oldPos >= commonPrefix and newPos >= commonPrefix and equals(newNode[newPos], oldNode[oldPos]): - # dec oldPos - # dec newPos - - # var pos = min(oldPos, newPos) + 1 - # var nextChildPos = oldPos + 1 - # while pos <= newPos: - # if nextChildPos == oldLength: - # current.appendChild(vnodeToDom(newNode[pos])) - # kout cstring"appendChild" - # else: - # kout cstring"insertBefore" - # current.insertBefore(vnodeToDom(newNode[pos]), current.childNodes[nextChildPos]) - # inc pos - # inc nextChildPos - - # for i in 0..oldPos-pos: - # kout cstring"removeChild" - # current.removeChild(current.childNodes[pos]) - - # # kout cstring("commonPrefix = " & $commonPrefix) - # # kout cstring("oldPos = " & $oldPos) - # # kout cstring("newPos = " & $newPos) - # # kout cstring("newLength = " & $newLength) - # # kout cstring("oldLength = " & $oldLength) - - # for i in 0..newLength-1: - # updateElement(current, current.childNodes[i], - # newNode[i], - # oldNode[i]) - - for i in 0..min(newLength, oldLength)-1: - updateElement(current, current.childNodes[i], - newNode[i], - oldNode[i]) - if newLength > oldLength: - for i in oldLength..newLength-1: - kout cstring"appendChild" - current.appendChild(vnodeToDom(newNode[i])) - elif oldLength > newLength: - for i in countdown(oldLength-1, newLength): - kout cstring"removeChild" - current.removeChild(current.lastChild) proc dodraw() = let newtree = dorender() diff --git a/todoapp.nim b/todoapp.nim index 63875a6..ea71c66 100644 --- a/todoapp.nim +++ b/todoapp.nim @@ -119,3 +119,6 @@ proc onload(session: cstring) {.exportc.} = for i in 0..1: # 0_000: entries.add((cstring"Entry " & &i, false)) init() + +for i in 0..10_000: + entries.add((cstring"Entry " & &i, false)) \ No newline at end of file From 0f7b314e49efc9c610ddc2f5398901cf458e243c Mon Sep 17 00:00:00 2001 From: Roman Vishnevskii Date: Fri, 7 Apr 2017 18:52:32 +0700 Subject: [PATCH 4/9] DOM diff problem: tests fix --- diffDomTests.nim | 3 --- 1 file changed, 3 deletions(-) diff --git a/diffDomTests.nim b/diffDomTests.nim index 359b00e..4556a86 100644 --- a/diffDomTests.nim +++ b/diffDomTests.nim @@ -17,17 +17,14 @@ proc test2(ev: Event; n: VNode) = # result: 2 3 4 1 proc test3(ev: Event; n: VNode) = - entries = @[cstring("0"), cstring("1"), cstring("2"), cstring("3"), cstring("4"), cstring("5") ] entries = @[cstring("2"), cstring("3"), cstring("4"), cstring("1") ] # result: 5 6 7 8 proc test4(ev: Event; n: VNode) = - entries = @[cstring("0"), cstring("1"), cstring("2"), cstring("3"), cstring("4"), cstring("5") ] entries = @[cstring("5"), cstring("6"), cstring("7"), cstring("8") ] # result: 0 1 3 5 4 5 proc test5(ev: Event; n: VNode) = - entries = @[cstring("0"), cstring("1"), cstring("2"), cstring("3"), cstring("4"), cstring("5") ] entries = @[cstring("0"), cstring("1"), cstring("3"), cstring("5"), cstring("4"), cstring("5") ] From c268e53bd9d2aca611b79aa135b852697bb8d277 Mon Sep 17 00:00:00 2001 From: Roman Vishnevskii Date: Fri, 7 Apr 2017 19:02:23 +0700 Subject: [PATCH 5/9] DOM diff problem: add tests --- diffDomTests.nim | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/diffDomTests.nim b/diffDomTests.nim index 4556a86..e8fefe9 100644 --- a/diffDomTests.nim +++ b/diffDomTests.nim @@ -27,6 +27,13 @@ proc test4(ev: Event; n: VNode) = proc test5(ev: Event; n: VNode) = entries = @[cstring("0"), cstring("1"), cstring("3"), cstring("5"), cstring("4"), cstring("5") ] +# result: empty +proc test6(ev: Event; n: VNode) = + entries = @[] + +# result: 2 +proc test7(ev: Event; n: VNode) = + entries = @[cstring("2")] proc createEntry(id: int): VNode = result = buildHtml(): @@ -45,6 +52,10 @@ proc createDom(): VNode = text "Test4" button(onclick=test5): text "Test5" + button(onclick=test6): + text "Test6" + button(onclick=test7): + text "Test7" ul(): for e in entries: li: From 291c779192ad79cd74fa18cb25363aa98b60e380 Mon Sep 17 00:00:00 2001 From: Roman Vishnevskii Date: Sat, 8 Apr 2017 14:43:39 +0700 Subject: [PATCH 6/9] DOM diff problem: tests are automatic --- diffDomTests.nim | 140 ++++++++++++++++++++++++++++++++++++++--------- karax.nim | 10 ++-- 2 files changed, 119 insertions(+), 31 deletions(-) diff --git a/diffDomTests.nim b/diffDomTests.nim index e8fefe9..e3014f8 100644 --- a/diffDomTests.nim +++ b/diffDomTests.nim @@ -1,39 +1,104 @@ -import vdom, karax, karaxdsl, jdict, jstrutils, parseutils, sequtils +import vdom, times, karax, karaxdsl, jdict, jstrutils, parseutils, sequtils var entries: seq[cstring] + timeout : Timeout + +proc reset() = + kout cstring"reset started" + entries = @[cstring("0"), cstring("1"), cstring("2"), cstring("3"), cstring("4"), cstring("5") ] + redraw() + kout cstring"reset finished" + +proc checkOrder(order : seq[int]) = + var ul = getElementById("ul") + assert ul != nil + assert len(ul.children) == len(order) + var pos = 0 + for child in ul.children: + assert child.id == $order[pos] + inc pos + +proc check1() = + checkOrder(@[0, 1, 2, 3, 4, 7, 5]) + kout cstring"test1 finished" # result: 0 1 2 3 4 7 5 -proc test1(ev: Event; n: VNode) = +proc test1() = + kout cstring"test1 started" entries = @[cstring("0"), cstring("1"), cstring("2"), cstring("3"), cstring("4"), cstring("5") ] entries.insert(cstring("7"), 5) + redraw() + timeout = setTimeout(check1, 20) + +proc check2() = + checkOrder(@[8, 0, 1, 2, 3, 4, 7, 5]) + kout cstring"test2 finished" # result: 8 0 1 2 3 4 7 5 -proc test2(ev: Event; n: VNode) = +proc test2() = + kout cstring"test2 started" entries = @[cstring("0"), cstring("1"), cstring("2"), cstring("3"), cstring("4"), cstring("5") ] entries.insert(cstring("7"), 5) entries.insert(cstring("8"), 0) + redraw() + timeout = setTimeout(check2, 20) + +proc check3() = + checkOrder(@[2, 3, 4, 1]) + kout cstring"test3 finished" # result: 2 3 4 1 -proc test3(ev: Event; n: VNode) = +proc test3() = + kout cstring"test3 started" entries = @[cstring("2"), cstring("3"), cstring("4"), cstring("1") ] + redraw() + timeout = setTimeout(check3, 20) + +proc check4() = + checkOrder(@[5, 6, 7, 8]) + kout cstring"test4 finished" # result: 5 6 7 8 -proc test4(ev: Event; n: VNode) = +proc test4() = + kout cstring"test4 started" entries = @[cstring("5"), cstring("6"), cstring("7"), cstring("8") ] + redraw() + timeout = setTimeout(check4, 20) + +proc check5() = + checkOrder(@[0, 1, 3, 5, 4, 5]) + kout cstring"test5 finished" # result: 0 1 3 5 4 5 -proc test5(ev: Event; n: VNode) = +proc test5() = + kout cstring"test5 started" entries = @[cstring("0"), cstring("1"), cstring("3"), cstring("5"), cstring("4"), cstring("5") ] + redraw() + timeout = setTimeout(check5, 20) +proc check6() = + checkOrder(@[]) + kout cstring"test6 finished" + # result: empty -proc test6(ev: Event; n: VNode) = +proc test6() = + kout cstring"test6 started" entries = @[] + redraw() + timeout = setTimeout(check6, 20) + +proc check7() = + checkOrder(@[2]) + kout cstring"test7 finished" # result: 2 -proc test7(ev: Event; n: VNode) = +proc test7() = + kout cstring"test7 started" entries = @[cstring("2")] + redraw() + timeout = setTimeout(check7, 20) proc createEntry(id: int): VNode = result = buildHtml(): @@ -42,25 +107,9 @@ proc createEntry(id: int): VNode = proc createDom(): VNode = result = buildHtml(tdiv()): - button(onclick=test1): - text "Test1" - button(onclick=test2): - text "Test2" - button(onclick=test3): - text "Test3" - button(onclick=test4): - text "Test4" - button(onclick=test5): - text "Test5" - button(onclick=test6): - text "Test6" - button(onclick=test7): - text "Test7" - ul(): + ul(id="ul"): for e in entries: - li: - createEntry(parseInt(e)) - + createEntry(parseInt(e)) setRenderer createDom @@ -68,3 +117,42 @@ proc onload(session: cstring) {.exportc.} = for i in 0..5: # 0_000: entries.add(cstring($i)) init() + + var dtReset = 100 + var dtTest = 500 + + var t = dtReset + timeout = setTimeout(test1, t) + t += dtTest + timeout = setTimeout(reset, t) + t += dtReset + + timeout = setTimeout(test2, t) + t += dtTest + timeout = setTimeout(reset, t) + t += dtReset + + timeout = setTimeout(test3, t) + t += dtTest + timeout = setTimeout(reset, t) + t += dtReset + + timeout = setTimeout(test4, t) + t += dtTest + timeout = setTimeout(reset, t) + t += dtReset + + timeout = setTimeout(test5, t) + t += dtTest + timeout = setTimeout(reset, t) + t += dtReset + + timeout = setTimeout(test6, t) + t += dtTest + timeout = setTimeout(reset, t) + t += dtReset + + timeout = setTimeout(test7, t) + t += dtTest + timeout = setTimeout(reset, t) + t += dtReset diff --git a/karax.nim b/karax.nim index 3cf67d2..16c453c 100644 --- a/karax.nim +++ b/karax.nim @@ -79,7 +79,7 @@ proc setTimeout*(action: proc(); ms: int): Timeout {.importc, nodecl.} proc clearTimeout*(t: Timeout) {.importc, nodecl.} #proc targetElem*(e: Event): Element = cast[Element](e.target) -#proc getElementById*(id: cstring): Element {.importc: "document.getElementById", nodecl.} +proc getElementById*(id: cstring): Element {.importc: "document.getElementById", nodecl.} #proc getElementsByClassName*(cls: cstring): seq[Element] {.importc: # "document.getElementsByClassName", nodecl.} @@ -132,7 +132,7 @@ proc updateElement(parent, current: Node, newNode, oldNode: VNode) = replaceById("ROOT", n) else: parent.replaceChild(n, current) - kout cstring("ReplaceChild") + kout cstring("---- replaceChild") #kout cstring("ReplaceChild: (" & current.nodeName & " = " & newNode.id & ") -> (" & # n.nodeName & " = " & oldNode.id & ")") elif newNode.kind != VNodeKind.text: @@ -186,7 +186,7 @@ proc updateElement(parent, current: Node, newNode, oldNode: VNode) = while pos <= newPos: if nextChildPos == oldLength: current.appendChild(vnodeToDom(newNode[pos])) - # kout cstring"appendChild " & newNode[pos].id + kout cstring"---- appendChild" # kout cstring("commonPrefix = " & $commonPrefix) # kout cstring("oldPos = " & $oldPos) # kout cstring("newPos = " & $newPos) @@ -199,14 +199,14 @@ proc updateElement(parent, current: Node, newNode, oldNode: VNode) = #kout cstring("pos = " & $pos) #kout cstring("newLength = " & $newLength) else: - kout cstring"insertBefore " & newNode[pos].id + kout cstring"---- insertBefore" current.insertBefore(vnodeToDom(newNode[pos]), current.childNodes[nextChildPos]) inc oldLength inc pos inc nextChildPos for i in 0..oldPos-pos: - kout cstring"removeChild" + kout cstring"---- removeChild" current.removeChild(current.childNodes[pos]) # var itNew = newPos + 1 From 686caaaf770f59a066cefe3e9ec9cc0f16b346a7 Mon Sep 17 00:00:00 2001 From: Roman Vishnevskii Date: Sat, 8 Apr 2017 15:17:19 +0700 Subject: [PATCH 7/9] DOM diff problem: tests write verdict to console --- diffDomTests.nim | 59 +++++++++++++++++++++++++++++++++--------------- karax.nim | 11 ++++----- 2 files changed, 45 insertions(+), 25 deletions(-) diff --git a/diffDomTests.nim b/diffDomTests.nim index e3014f8..2fa9ccf 100644 --- a/diffDomTests.nim +++ b/diffDomTests.nim @@ -11,18 +11,23 @@ proc reset() = redraw() kout cstring"reset finished" -proc checkOrder(order : seq[int]) = +proc checkOrder(order : seq[int]): bool = var ul = getElementById("ul") - assert ul != nil - assert len(ul.children) == len(order) + if ul == nil or len(ul.children) != len(order): + return false var pos = 0 for child in ul.children: - assert child.id == $order[pos] + if child.id != $order[pos]: + return false inc pos + return true proc check1() = - checkOrder(@[0, 1, 2, 3, 4, 7, 5]) - kout cstring"test1 finished" + let result = checkOrder(@[0, 1, 2, 3, 4, 7, 5]) + if result: + kout cstring"test1 - OK" + else: + kout cstring"test1 - FAIL" # result: 0 1 2 3 4 7 5 proc test1() = @@ -33,8 +38,11 @@ proc test1() = timeout = setTimeout(check1, 20) proc check2() = - checkOrder(@[8, 0, 1, 2, 3, 4, 7, 5]) - kout cstring"test2 finished" + let result = checkOrder(@[8, 0, 1, 2, 3, 4, 7, 5]) + if result: + kout cstring"test1 - OK" + else: + kout cstring"test2 - FAIL" # result: 8 0 1 2 3 4 7 5 proc test2() = @@ -46,8 +54,11 @@ proc test2() = timeout = setTimeout(check2, 20) proc check3() = - checkOrder(@[2, 3, 4, 1]) - kout cstring"test3 finished" + let result = checkOrder(@[2, 3, 4, 1]) + if result: + kout cstring"test3 - OK" + else: + kout cstring"test3 - FAIL" # result: 2 3 4 1 proc test3() = @@ -57,8 +68,11 @@ proc test3() = timeout = setTimeout(check3, 20) proc check4() = - checkOrder(@[5, 6, 7, 8]) - kout cstring"test4 finished" + let result = checkOrder(@[5, 6, 7, 8]) + if result: + kout cstring"test4 - OK" + else: + kout cstring"test4 - FAIL" # result: 5 6 7 8 proc test4() = @@ -68,8 +82,11 @@ proc test4() = timeout = setTimeout(check4, 20) proc check5() = - checkOrder(@[0, 1, 3, 5, 4, 5]) - kout cstring"test5 finished" + let result = checkOrder(@[0, 1, 3, 5, 4, 5]) + if result: + kout cstring"test5 - OK" + else: + kout cstring"test5 - FAIL" # result: 0 1 3 5 4 5 proc test5() = @@ -79,8 +96,11 @@ proc test5() = timeout = setTimeout(check5, 20) proc check6() = - checkOrder(@[]) - kout cstring"test6 finished" + let result = checkOrder(@[]) + if result: + kout cstring"test6 - OK" + else: + kout cstring"test6 - FAIL" # result: empty proc test6() = @@ -90,8 +110,11 @@ proc test6() = timeout = setTimeout(check6, 20) proc check7() = - checkOrder(@[2]) - kout cstring"test7 finished" + let result = checkOrder(@[2]) + if result: + kout cstring"test7 - OK" + else: + kout cstring"test7 - FAIL" # result: 2 proc test7() = diff --git a/karax.nim b/karax.nim index 16c453c..d4e60d6 100644 --- a/karax.nim +++ b/karax.nim @@ -132,7 +132,7 @@ proc updateElement(parent, current: Node, newNode, oldNode: VNode) = replaceById("ROOT", n) else: parent.replaceChild(n, current) - kout cstring("---- replaceChild") + #kout cstring("---- replaceChild") #kout cstring("ReplaceChild: (" & current.nodeName & " = " & newNode.id & ") -> (" & # n.nodeName & " = " & oldNode.id & ")") elif newNode.kind != VNodeKind.text: @@ -156,9 +156,6 @@ proc updateElement(parent, current: Node, newNode, oldNode: VNode) = dec oldPos dec newPos - if commonPrefix + newLength - newPos > 1000: - kout cstring($(commonPrefix + newLength - newPos)) - var pos = min(oldPos, newPos) + 1 # kout cstring("commonPrefix = " & $commonPrefix) @@ -186,7 +183,7 @@ proc updateElement(parent, current: Node, newNode, oldNode: VNode) = while pos <= newPos: if nextChildPos == oldLength: current.appendChild(vnodeToDom(newNode[pos])) - kout cstring"---- appendChild" + #kout cstring"---- appendChild" # kout cstring("commonPrefix = " & $commonPrefix) # kout cstring("oldPos = " & $oldPos) # kout cstring("newPos = " & $newPos) @@ -199,14 +196,14 @@ proc updateElement(parent, current: Node, newNode, oldNode: VNode) = #kout cstring("pos = " & $pos) #kout cstring("newLength = " & $newLength) else: - kout cstring"---- insertBefore" + #kout cstring"---- insertBefore" current.insertBefore(vnodeToDom(newNode[pos]), current.childNodes[nextChildPos]) inc oldLength inc pos inc nextChildPos for i in 0..oldPos-pos: - kout cstring"---- removeChild" + #kout cstring"---- removeChild" current.removeChild(current.childNodes[pos]) # var itNew = newPos + 1 From ff8bbf1676086eed6ba2a4db14c5048d20e27e20 Mon Sep 17 00:00:00 2001 From: Roman Vishnevskii Date: Sat, 8 Apr 2017 16:13:38 +0700 Subject: [PATCH 8/9] DOM diff problem: delete useless comments --- karax.nim | 55 ++----------------------------------------------------- 1 file changed, 2 insertions(+), 53 deletions(-) diff --git a/karax.nim b/karax.nim index d4e60d6..43ed60b 100644 --- a/karax.nim +++ b/karax.nim @@ -110,10 +110,6 @@ proc equals(a, b: VNode): bool = return true proc equalsTree(a, b : VNode): bool = - # kout cstring("equalsTree: (" & $a.kind & " = " & a.id & ") -> (" & - # $b.kind & " = " & b.id & ")") - # assert a != nil and b != nil - # kout cstring("equalsTree") if not equals(a, b): return false else: @@ -125,16 +121,12 @@ proc equalsTree(a, b : VNode): bool = return true proc updateElement(parent, current: Node, newNode, oldNode: VNode) = - #kout cstring("updateElement: (" & current.nodeName & " = " & current.id & ")") if not equals(newNode, oldNode): let n = vnodeToDom(newNode) if parent == nil: replaceById("ROOT", n) else: parent.replaceChild(n, current) - #kout cstring("---- replaceChild") - #kout cstring("ReplaceChild: (" & current.nodeName & " = " & newNode.id & ") -> (" & - # n.nodeName & " = " & oldNode.id & ")") elif newNode.kind != VNodeKind.text: let newLength = newNode.len var oldLength = oldNode.len @@ -157,68 +149,26 @@ proc updateElement(parent, current: Node, newNode, oldNode: VNode) = dec newPos var pos = min(oldPos, newPos) + 1 - - # kout cstring("commonPrefix = " & $commonPrefix) - # kout cstring("oldPos = " & $oldPos) - # kout cstring("newPos = " & $newPos) - # kout cstring("newLength = " & $newLength) - # kout cstring("oldLength = " & $oldLength) - # kout cstring("-----------") - # for i in items(newNode): - # kout cstring(i.id) - # kout cstring("-----") - # for i in items(oldNode): - # kout cstring(i.id) - for i in commonPrefix..pos-1: updateElement(current, current.childNodes[i], newNode[i], oldNode[i]) - #kout cstring(newNode[i].id & " " & oldNode[i].id) - #kout cstring("----------") var nextChildPos = oldPos + 1 - # kout cstring("pos = " & $pos) - # kout cstring("nextChildPos = " & $nextChildPos) while pos <= newPos: if nextChildPos == oldLength: current.appendChild(vnodeToDom(newNode[pos])) - #kout cstring"---- appendChild" - # kout cstring("commonPrefix = " & $commonPrefix) - # kout cstring("oldPos = " & $oldPos) - # kout cstring("newPos = " & $newPos) - # kout cstring("newLength = " & $newLength) - # kout cstring("oldLength = " & $oldLength) - - # kout cstring("pos = " & $pos) - # kout cstring("nextChildPos = " & $nextChildPos) - # kout cstring("-----------") - #kout cstring("pos = " & $pos) - #kout cstring("newLength = " & $newLength) else: - #kout cstring"---- insertBefore" current.insertBefore(vnodeToDom(newNode[pos]), current.childNodes[nextChildPos]) + # added new Node, so old state of VDOM have one more Node inc oldLength inc pos inc nextChildPos for i in 0..oldPos-pos: - #kout cstring"---- removeChild" current.removeChild(current.childNodes[pos]) - # var itNew = newPos + 1 - # var itOld = oldPos + 1 - # while itNew < newLength and itOld < oldLength: - # updateElement(current, current.childNodes[itNew], - # newNode[itNew], - # oldNode[itOld]) - - # kout cstring("commonPrefix = " & $commonPrefix) - # kout cstring("oldPos = " & $oldPos) - # kout cstring("newPos = " & $newPos) - # kout cstring("newLength = " & $newLength) - # kout cstring("oldLength = " & $oldLength) - + # previous version of DOM diff algorithm # for i in 0..min(newLength, oldLength)-1: # updateElement(current, current.childNodes[i], # newNode[i], @@ -232,7 +182,6 @@ proc updateElement(parent, current: Node, newNode, oldNode: VNode) = # kout cstring"removeChild" # current.removeChild(current.lastChild) - proc dodraw() = let newtree = dorender() newtree.id = "ROOT" From c065b8f709aa45a538505347b76109b503ff6969 Mon Sep 17 00:00:00 2001 From: Roman Vishnevskii Date: Sat, 8 Apr 2017 18:58:08 +0700 Subject: [PATCH 9/9] DOM diff problem: hashing --- diffDomTests.nim | 3 --- karax.nim | 69 +++++++++++++++++++++++++++++++++++++++++------- vdom.nim | 20 ++++++++++++++ 3 files changed, 79 insertions(+), 13 deletions(-) diff --git a/diffDomTests.nim b/diffDomTests.nim index 2fa9ccf..fe808f6 100644 --- a/diffDomTests.nim +++ b/diffDomTests.nim @@ -176,6 +176,3 @@ proc onload(session: cstring) {.exportc.} = t += dtReset timeout = setTimeout(test7, t) - t += dtTest - timeout = setTimeout(reset, t) - t += dtReset diff --git a/karax.nim b/karax.nim index 43ed60b..2dc8847 100644 --- a/karax.nim +++ b/karax.nim @@ -109,16 +109,65 @@ proc equals(a, b: VNode): bool = a.class = b.class return true +proc getStringHash(s: string): uint64 = + result = 0 + for c in s: + result = (result * 257 + uint64(c)) mod 1000000007 + +proc calcHash(a : VNode) = + if a.validHash: + return + var result : uint64 = 0 + for i in a: + i.calcHash() + result = result + i.hash + a.validHash = true + a.hash = (getStringHash(getVNodeData(a)) * 71 + result) mod 1000000007 + proc equalsTree(a, b : VNode): bool = - if not equals(a, b): - return false - else: - if len(a) != len(b): - return false - for i in 0..len(a)-1: - if not equalsTree(a[i], b[i]): - return false - return true + if not a.validHash: + a.calcHash() + if not b.validHash: + b.calcHash() + + return a.hash == b.hash + + # if not equals(a, b): + # return false + # else: + # if len(a) != len(b): + # return false + # for i in 0..len(a)-1: + # if not equalsTree(a[i], b[i]): + # return false + # return true + + # kout cstring("a " & $int64(a.hash) & " " & "b " & $int64(b.hash)) + + # if not equals(a, b): + # if a.hash == b.hash: + # kout cstring("false " & getVNodeData(a) & " " & $int64(a.hash) & " " & getVNodeData(b) & " " & $int64(b.hash)) + # kout cstring($int64(getStringHash(getVNodeData(a))) & " " & $int64(getStringHash(getVNodeData(b)))) + # for c in getVNodeData(a): + # kout cstring($int64(uint64(c))) + # kout "-------" + # for c in getVNodeData(b): + # kout cstring($int64(uint64(c))) + + # return false + # else: + # if len(a) != len(b): + # if a.hash == b.hash: + # kout cstring("false " & getVNodeData(a) & " " & $int64(a.hash) & " " & getVNodeData(b) & " " & $int64(b.hash)) + # return false + # for i in 0..len(a)-1: + # if not equalsTree(a[i], b[i]): + # if a.hash == b.hash: + # kout cstring("false " & getVNodeData(a) & " " & $int64(a.hash) & " " & getVNodeData(b) & " " & $int64(b.hash)) + # return false + # if a.hash != b.hash: + # kout cstring("true " & getVNodeData(a) & " " & getVNodeData(b)) + # return true proc updateElement(parent, current: Node, newNode, oldNode: VNode) = if not equals(newNode, oldNode): @@ -137,7 +186,7 @@ proc updateElement(parent, current: Node, newNode, oldNode: VNode) = kout current.nodeName kout toTag[oldNode.kind] assert false - + var commonPrefix = 0 while commonPrefix < minLength and equalsTree(newNode[commonPrefix], oldNode[commonPrefix]): inc commonPrefix diff --git a/vdom.nim b/vdom.nim index 6d94d97..2e777ac 100644 --- a/vdom.nim +++ b/vdom.nim @@ -38,6 +38,8 @@ type # even index: key, odd index: value; done this way for memory efficiency: attrs: seq[cstring] events*: seq[(EventKind, EventHandler)] + hash*: uint64 + validHash*: bool proc value*(n: VNode): cstring = n.text proc `value=`*(n: VNode; v: cstring) = n.text = v @@ -129,6 +131,24 @@ proc toString*(n: VNode; result: var string; indent: int) = for i in 1..indent: result.add ' ' result.add "\L" +proc getVNodeData*(n: VNode): string = + result.add "<" & $n.kind + toStringAttr(id) + toStringAttr(class) + + #for k, v in attrs(n): + # result.add " " & $k & " = " & $v + + result.add ">" + if n.kind == VNodeKind.text: + result.add n.text + else: + if n.text != nil: + result.add " value = " + result.add n.text + result.add "" + + proc `$`*(n: VNode): cstring = var res = "" toString(n, res, 0)