diff --git a/src/karax.nim b/src/karax.nim index 147965c..adbd2f2 100644 --- a/src/karax.nim +++ b/src/karax.nim @@ -21,6 +21,9 @@ type k: PatchKind parent, current: Node n: VNode + PatchV = object + parent, newChild: VNode + pos: int type KaraxInstance* = ref object ## underlying karax instance. Usually you don't have @@ -34,7 +37,11 @@ type renderId: int patches: seq[Patch] # we reuse this to save allocations patchLen: int - recursion: int + patchesV: seq[PatchV] + patchLenV: int + runCount: int + when defined(stats): + recursion: int var @@ -251,6 +258,17 @@ proc addPatch(kxi: KaraxInstance; ka: PatchKind; parenta, currenta: Node; kxi.patches[L].n = na inc kxi.patchLen +proc addPatchV(kxi: KaraxInstance; parent: VNode; pos: int; newChild: VNode) = + let L = kxi.patchLenV + if L >= kxi.patchesV.len: + # allocate more space: + kxi.patchesV.add(PatchV(parent: parent, newChild: newChild, pos: pos)) + else: + kxi.patchesV[L].parent = parent + kxi.patchesV[L].newChild = newChild + kxi.patchesV[L].pos = pos + inc kxi.patchLenV + proc apply(kxi: KaraxInstance) = for i in 0.. 100: - echo "newNode ", newNode.kind, " oldNode ", oldNode.kind, " eq ", eq(newNode, oldNode) - if oldNode.kind == VNodeKind.text: - echo oldNode.text - #return - #doAssert false, "overflow!" - inc kxi.recursion + when defined(stats): + if kxi.recursion > 100: + echo "newNode ", newNode.kind, " oldNode ", oldNode.kind, " eq ", eq(newNode, oldNode) + if oldNode.kind == VNodeKind.text: + echo oldNode.text + #return + #doAssert false, "overflow!" + inc kxi.recursion result = eq(newNode, oldNode) case result of identical, similar: @@ -310,6 +333,7 @@ proc diff(newNode, oldNode: VNode; parent, current: Node; kxi: KaraxInstance): E template eqAndUpdate(a: VNode; i: int; b: VNode; j: int; info, action: untyped) = let oldLen = kxi.patchLen + let oldLenV = kxi.patchLenV assert i < a.len assert j < b.len let r = if isSpecial: @@ -322,11 +346,13 @@ proc diff(newNode, oldNode: VNode; parent, current: Node; kxi: KaraxInstance): E a[i] = b[j] action of usenewNode: - b[j] = a[i] + #b[j] = a[i] + kxi.addPatchV(b, j, a[i]) action of different: # undo what 'diff' would have done: kxi.patchLen = oldLen + kxi.patchLenV = oldLenV if result != different: result = r break # compute common prefix: @@ -403,7 +429,8 @@ proc diff(newNode, oldNode: VNode; parent, current: Node; kxi: KaraxInstance): E detach(oldNode) kxi.addPatch(pkReplace, parent, current, newNode) of usenewNode: doAssert(false, "eq returned usenewNode") - dec kxi.recursion + when defined(stats): + dec kxi.recursion when defined(stats): proc depth(n: VNode; total: var int): int = @@ -416,6 +443,7 @@ when defined(stats): proc dodraw(kxi: KaraxInstance) = if kxi.renderer.isNil: return let newtree = kxi.renderer() + inc kxi.runCount newtree.id = kxi.rootId kxi.toFocus = nil if kxi.currentTree == nil: @@ -428,7 +456,7 @@ proc dodraw(kxi: KaraxInstance) = #kout cstring"patch len ", patches.len apply(kxi) kxi.currentTree = newtree - doAssert same(kxi.currentTree, document.getElementById(kxi.rootId)) + #doAssert same(kxi.currentTree, document.getElementById(kxi.rootId)) if not kxi.postRenderCallback.isNil: kxi.postRenderCallback() @@ -437,8 +465,8 @@ proc dodraw(kxi: KaraxInstance) = if kxi.toFocus != nil: kxi.toFocus.focus() kxi.renderId = 0 - kxi.recursion = 0 when defined(stats): + kxi.recursion = 0 var total = 0 echo "depth ", depth(kxi.currentTree, total), " total ", total @@ -467,7 +495,8 @@ proc setRenderer*(renderer: proc (): VNode, root: cstring = "ROOT", ## Setup Karax. Usually the return value can be ignored. result = KaraxInstance(rootId: root, renderer: renderer, postRenderCallback: clientPostRenderCallback, - patches: newSeq[Patch](60)) + patches: newSeq[Patch](60), + patchesV: newSeq[PatchV](30)) kxi = result window.onload = init diff --git a/tests/diffDomTests.nim b/tests/diffDomTests.nim index e28c6f6..56f8b9f 100644 --- a/tests/diffDomTests.nim +++ b/tests/diffDomTests.nim @@ -26,7 +26,6 @@ proc check(name: cstring; order: seq[int]) = let result = checkOrder(order) results.add name & (if result: cstring" - OK" else: cstring" -FAIL") -# result: 0 1 2 3 4 7 5 proc test1() = results.add cstring"test1 started" entries = @[cstring("0"), cstring("1"), cstring("2"), cstring("3"), cstring("4"), cstring("5")] diff --git a/tests/difftest.nim b/tests/difftest.nim index 8bd4d0f..3fbb800 100644 --- a/tests/difftest.nim +++ b/tests/difftest.nim @@ -43,8 +43,10 @@ proc testAppend() = ul: li: text "A" li: text "B" - li: text "C" - doDiff(a, b, "pkAppend li C") + li: + tdiv: + text "C" + doDiff(a, b, "pkAppend li div C") proc testInsert() = let a = buildHtml(tdiv): @@ -88,12 +90,80 @@ proc testDelete() = "pkDetach li B", "pkRemove nil", "pkDetach li C", "pkRemove nil") +proc testDeleteMiddle() = + let a = buildHtml(tdiv): + ul: + li: + tdiv: text "A" + li: + tdiv: text "B" + li: + tdiv: text "C" + li: + tdiv: text "D" + li: + tdiv: text "E" + li: + tdiv: text "F" + li: + tdiv: text "G" + li: + tdiv: text "H" + let b = buildHtml(tdiv): + ul: + li: + tdiv: text "A" + li: + tdiv: text "B" + li: + tdiv: text "C" + li: + tdiv: text "D" + li: + tdiv: text "E" + li: + tdiv: text "F" + li: + tdiv: text "H" + doDiff(a, b, "pkDetach li div G", "pkRemove nil") + +proc createEntry(id: cstring): VNode = + result = buildHtml(): + button(id="" & id): + text id + +proc createEntries(entries: seq[cstring]): VNode = + result = buildHtml(tdiv()): + ul(id="ul"): + for e in entries: + createEntry(e) + for r in entries: + tdiv: + text r + +proc testWild() = + var entries = @[cstring("0"), cstring("1"), cstring("2"), cstring("3"), cstring("4"), cstring"7", cstring("5")] + let a = createEntries(entries) + entries = @[cstring("0"), cstring("1"), cstring("2"), cstring("3"), cstring("4"), cstring("5")] + let b = createEntries(entries) + doDiff(a, b, "pkDetach button 7", "pkRemove nil", "pkDetach div 7", "pkRemove nil") + +proc testWildInsert() = + var entries = @[cstring("0"), cstring("1"), cstring("2"), cstring("3"), cstring("4"), cstring("5")] + let a = createEntries(entries) + entries = @[cstring("0"), cstring("1"), cstring("2"), cstring("3"), cstring("4"), cstring"7", cstring("5")] + let b = createEntries(entries) + doDiff(a, b, "pkInsertBefore button 7", "pkInsertBefore div 7") + kxi = KaraxInstance(rootId: cstring"ROOT", renderer: proc (): VNode = discard) testAppend() testInsert() testInsert2() testDelete() +testWild() +testWildInsert() +testDeleteMiddle() if err == 0: echo "Success" else: