optimized diff algorithm further; fixed a stupid bug; cleaned up the tests
This commit is contained in:
parent
09a5e1c34e
commit
8a1424669f
5 changed files with 117 additions and 168 deletions
|
|
@ -20,3 +20,5 @@ proc `[]=`*[T](s: JSeq[T], i: int, v: T) {.importcpp: "#[#] = #", noSideEffect.}
|
||||||
proc newJSeq*[T](): JSeq[T] {.importcpp: "[@]".}
|
proc newJSeq*[T](): JSeq[T] {.importcpp: "[@]".}
|
||||||
proc len*[T](s: JSeq[T]): int {.importcpp: "#.length", noSideEffect.}
|
proc len*[T](s: JSeq[T]): int {.importcpp: "#.length", noSideEffect.}
|
||||||
proc add*[T](s: JSeq[T]; x: T) {.importcpp: "#.push(#)", noSideEffect.}
|
proc add*[T](s: JSeq[T]; x: T) {.importcpp: "#.push(#)", noSideEffect.}
|
||||||
|
|
||||||
|
proc shrink*[T](s: JSeq[T]; shorterLen: int) {.importcpp: "#.length = #", noSideEffect.}
|
||||||
|
|
|
||||||
|
|
@ -208,12 +208,13 @@ type
|
||||||
parent, current: Node
|
parent, current: Node
|
||||||
n: VNode
|
n: VNode
|
||||||
|
|
||||||
proc addPatch(patches: var seq[Patch]; k: PatchKind; parent, current: Node;
|
proc addPatch(patches: JSeq[Patch]; k: PatchKind; parent, current: Node;
|
||||||
n: VNode) =
|
n: VNode) =
|
||||||
patches.add(Patch(k: k, parent: parent, current: current, n: n))
|
patches.add(Patch(k: k, parent: parent, current: current, n: n))
|
||||||
|
|
||||||
proc apply(patches: seq[Patch]; kxi: KaraxInstance) =
|
proc apply(patches: JSeq[Patch]; kxi: KaraxInstance) =
|
||||||
for p in patches:
|
for i in 0..<patches.len:
|
||||||
|
let p = patches[i]
|
||||||
case p.k
|
case p.k
|
||||||
of pkReplace:
|
of pkReplace:
|
||||||
let nn = vnodeToDom(p.n, kxi)
|
let nn = vnodeToDom(p.n, kxi)
|
||||||
|
|
@ -230,8 +231,7 @@ proc apply(patches: seq[Patch]; kxi: KaraxInstance) =
|
||||||
let nn = vnodeToDom(p.n, kxi)
|
let nn = vnodeToDom(p.n, kxi)
|
||||||
p.parent.insertBefore(nn, p.current)
|
p.parent.insertBefore(nn, p.current)
|
||||||
|
|
||||||
proc diff(parent, current: Node; newNode, oldNode: VNode; patches: var seq[Patch];
|
proc diff(newNode, oldNode: VNode;parent, current: Node; patches: JSeq[Patch]): EqResult =
|
||||||
kxi: KaraxInstance): EqResult =
|
|
||||||
result = eq(newNode, oldNode)
|
result = eq(newNode, oldNode)
|
||||||
case result
|
case result
|
||||||
of identical:
|
of identical:
|
||||||
|
|
@ -239,7 +239,7 @@ proc diff(parent, current: Node; newNode, oldNode: VNode; patches: var seq[Patch
|
||||||
let newLength = newNode.len
|
let newLength = newNode.len
|
||||||
var oldLength = oldNode.len
|
var oldLength = oldNode.len
|
||||||
let minLength = min(newLength, oldLength)
|
let minLength = min(newLength, oldLength)
|
||||||
if minLength == 0: return result
|
if newLength == 0 and oldLength == 0: return result
|
||||||
|
|
||||||
assert oldNode.kind == newNode.kind
|
assert oldNode.kind == newNode.kind
|
||||||
var commonPrefix = 0
|
var commonPrefix = 0
|
||||||
|
|
@ -254,16 +254,16 @@ proc diff(parent, current: Node; newNode, oldNode: VNode; patches: var seq[Patch
|
||||||
|
|
||||||
let r = if oldNode.kind == VNodeKind.component or oldNode.kind == VNodeKind.vthunk or
|
let r = if oldNode.kind == VNodeKind.component or oldNode.kind == VNodeKind.vthunk or
|
||||||
oldNode.kind == VNodeKind.dthunk:
|
oldNode.kind == VNodeKind.dthunk:
|
||||||
diff(parent, current, a[i], b[j], patches, kxi)
|
diff(a[i], b[j], parent, current, patches)
|
||||||
else:
|
else:
|
||||||
diff(current, current.childNodes[j], a[i], b[j], patches, kxi)
|
diff(a[i], b[j], current, current.childNodes[j], patches)
|
||||||
case r
|
case r
|
||||||
of identical, changed, similar:
|
of identical, changed, similar:
|
||||||
a[i] = b[j]
|
a[i] = b[j]
|
||||||
action
|
action
|
||||||
of different:
|
of different:
|
||||||
# undo what 'diff' would have done:
|
# undo what 'diff' would have done:
|
||||||
setLen(patches, oldLen)
|
shrink(patches, oldLen)
|
||||||
if result != different: result = r
|
if result != different: result = r
|
||||||
break
|
break
|
||||||
#of similar:
|
#of similar:
|
||||||
|
|
@ -284,8 +284,8 @@ proc diff(parent, current: Node; newNode, oldNode: VNode; patches: var seq[Patch
|
||||||
|
|
||||||
var pos = min(oldPos, newPos) + 1
|
var pos = min(oldPos, newPos) + 1
|
||||||
for i in commonPrefix..pos-1:
|
for i in commonPrefix..pos-1:
|
||||||
if diff(current, current.childNodes[i],
|
if diff(newNode[i], oldNode[i], current, current.childNodes[i],
|
||||||
newNode[i], oldNode[i], patches, kxi) != different:
|
patches) != different:
|
||||||
newNode[i] = oldNode[i]
|
newNode[i] = oldNode[i]
|
||||||
else:
|
else:
|
||||||
result = different
|
result = different
|
||||||
|
|
@ -318,7 +318,7 @@ proc diff(parent, current: Node; newNode, oldNode: VNode; patches: var seq[Patch
|
||||||
detach(oldNode)
|
detach(oldNode)
|
||||||
patches.addPatch(pkReplace, parent, current, x.expanded)
|
patches.addPatch(pkReplace, parent, current, x.expanded)
|
||||||
else:
|
else:
|
||||||
let res = diff(parent, current, x.expanded, oldExpanded, patches, kxi)
|
let res = diff(x.expanded, oldExpanded, parent, current, patches)
|
||||||
if res != different:
|
if res != different:
|
||||||
x.expanded = oldExpanded
|
x.expanded = oldExpanded
|
||||||
assert oldExpanded.dom != nil, "old expanded.dom is nil"
|
assert oldExpanded.dom != nil, "old expanded.dom is nil"
|
||||||
|
|
@ -339,8 +339,8 @@ proc dodraw(kxi: KaraxInstance) =
|
||||||
replaceById(kxi.rootId, asdom)
|
replaceById(kxi.rootId, asdom)
|
||||||
else:
|
else:
|
||||||
let olddom = document.getElementById(kxi.rootId)
|
let olddom = document.getElementById(kxi.rootId)
|
||||||
var patches: seq[Patch] = @[]
|
var patches = newJSeq[Patch]()
|
||||||
discard diff(nil, olddom, newtree, kxi.currentTree, patches, kxi)
|
discard diff(newtree, kxi.currentTree, nil, olddom, patches)
|
||||||
patches.apply(kxi)
|
patches.apply(kxi)
|
||||||
kxi.currentTree = newtree
|
kxi.currentTree = newtree
|
||||||
#doAssert same(kxi.currentTree, document.getElementById(kxi.rootId))
|
#doAssert same(kxi.currentTree, document.getElementById(kxi.rootId))
|
||||||
|
|
@ -365,6 +365,8 @@ proc redraw*(kxi: KaraxInstance = kxi) =
|
||||||
else:
|
else:
|
||||||
dodraw(kxi)
|
dodraw(kxi)
|
||||||
|
|
||||||
|
proc redrawSync*(kxi: KaraxInstance = kxi) = dodraw(kxi)
|
||||||
|
|
||||||
proc init(ev: Event) =
|
proc init(ev: Event) =
|
||||||
reqFrame(proc () = kxi.dodraw)
|
reqFrame(proc () = kxi.dodraw)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>Diff dom tests</title>
|
<title>Diff dom tests</title>
|
||||||
<link rel="stylesheet" href="style.css">
|
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body id="body">
|
<body id="body">
|
||||||
|
|
|
||||||
|
|
@ -2,182 +2,107 @@
|
||||||
import kdom, vdom, times, karax, karaxdsl, jdict, jstrutils, parseutils, sequtils
|
import kdom, vdom, times, karax, karaxdsl, jdict, jstrutils, parseutils, sequtils
|
||||||
|
|
||||||
var
|
var
|
||||||
entries: seq[cstring]
|
entries: seq[cstring]
|
||||||
results: seq[cstring]
|
results: seq[cstring]
|
||||||
timeout: Timeout
|
|
||||||
|
|
||||||
proc reset() =
|
proc reset() =
|
||||||
results.add cstring"reset started"
|
entries = @[cstring("0"), cstring("1"), cstring("2"), cstring("3"), cstring("4"), cstring("5")]
|
||||||
entries = @[cstring("0"), cstring("1"), cstring("2"), cstring("3"), cstring("4"), cstring("5") ]
|
redrawSync()
|
||||||
redraw()
|
|
||||||
results.add cstring"reset finished"
|
|
||||||
|
|
||||||
proc checkOrder(order : seq[int]): bool =
|
proc checkOrder(order: seq[int]): bool =
|
||||||
var ul = getElementById("ul")
|
var ul = getElementById("ul")
|
||||||
if ul == nil or len(ul.children) != len(order):
|
if ul == nil or len(ul.children) != len(order):
|
||||||
return false
|
kout ul, len(order)
|
||||||
var pos = 0
|
return false
|
||||||
for child in ul.children:
|
var pos = 0
|
||||||
if child.id != $order[pos]:
|
for child in ul.children:
|
||||||
return false
|
if child.id != $order[pos]:
|
||||||
inc pos
|
kout pos
|
||||||
return true
|
return false
|
||||||
|
inc pos
|
||||||
|
return true
|
||||||
|
|
||||||
proc check1() =
|
proc check(name: cstring; order: seq[int]) =
|
||||||
let result = checkOrder(@[0, 1, 2, 3, 4, 7, 5])
|
let result = checkOrder(order)
|
||||||
if result:
|
results.add name & (if result: cstring" - OK" else: cstring" -FAIL")
|
||||||
results.add cstring"test1 - OK"
|
|
||||||
else:
|
|
||||||
results.add cstring"test1 - FAIL"
|
|
||||||
|
|
||||||
# result: 0 1 2 3 4 7 5
|
# result: 0 1 2 3 4 7 5
|
||||||
proc test1() =
|
proc test1() =
|
||||||
results.add cstring"test1 started"
|
results.add cstring"test1 started"
|
||||||
entries = @[cstring("0"), cstring("1"), cstring("2"), cstring("3"), cstring("4"), cstring("5") ]
|
entries = @[cstring("0"), cstring("1"), cstring("2"), cstring("3"), cstring("4"), cstring("5")]
|
||||||
entries.insert(cstring("7"), 5)
|
entries.insert(cstring("7"), 5)
|
||||||
redraw()
|
redrawSync()
|
||||||
timeout = setTimeout(check1, 20)
|
check("test1", @[0, 1, 2, 3, 4, 7, 5])
|
||||||
|
|
||||||
proc check2() =
|
|
||||||
let result = checkOrder(@[8, 0, 1, 2, 3, 4, 7, 5])
|
|
||||||
if result:
|
|
||||||
results.add cstring"test1 - OK"
|
|
||||||
else:
|
|
||||||
results.add cstring"test2 - FAIL"
|
|
||||||
|
|
||||||
# result: 8 0 1 2 3 4 7 5
|
|
||||||
proc test2() =
|
proc test2() =
|
||||||
results.add cstring"test2 started"
|
results.add cstring"test2 started"
|
||||||
entries = @[cstring("0"), cstring("1"), cstring("2"), cstring("3"), cstring("4"), cstring("5") ]
|
entries = @[cstring("0"), cstring("1"), cstring("2"), cstring("3"), cstring("4"), cstring("5")]
|
||||||
entries.insert(cstring("7"), 5)
|
entries.insert(cstring("7"), 5)
|
||||||
entries.insert(cstring("8"), 0)
|
entries.insert(cstring("8"), 0)
|
||||||
redraw()
|
redrawSync()
|
||||||
timeout = setTimeout(check2, 20)
|
check("test2", @[8, 0, 1, 2, 3, 4, 7, 5])
|
||||||
|
|
||||||
proc check3() =
|
|
||||||
let result = checkOrder(@[2, 3, 4, 1])
|
|
||||||
if result:
|
|
||||||
results.add cstring"test3 - OK"
|
|
||||||
else:
|
|
||||||
results.add cstring"test3 - FAIL"
|
|
||||||
|
|
||||||
# result: 2 3 4 1
|
|
||||||
proc test3() =
|
proc test3() =
|
||||||
results.add cstring"test3 started"
|
results.add cstring"test3 started"
|
||||||
entries = @[cstring("2"), cstring("3"), cstring("4"), cstring("1") ]
|
entries = @[cstring("2"), cstring("3"), cstring("4"), cstring("1")]
|
||||||
redraw()
|
redrawSync()
|
||||||
timeout = setTimeout(check3, 20)
|
check("test3", @[2, 3, 4, 1])
|
||||||
|
|
||||||
proc check4() =
|
|
||||||
let result = checkOrder(@[5, 6, 7, 8])
|
|
||||||
if result:
|
|
||||||
results.add cstring"test4 - OK"
|
|
||||||
else:
|
|
||||||
results.add cstring"test4 - FAIL"
|
|
||||||
|
|
||||||
# result: 5 6 7 8
|
|
||||||
proc test4() =
|
proc test4() =
|
||||||
results.add cstring"test4 started"
|
results.add cstring"test4 started"
|
||||||
entries = @[cstring("5"), cstring("6"), cstring("7"), cstring("8") ]
|
entries = @[cstring("5"), cstring("6"), cstring("7"), cstring("8") ]
|
||||||
redraw()
|
redrawSync()
|
||||||
timeout = setTimeout(check4, 20)
|
check("test4", @[5, 6, 7, 8])
|
||||||
|
|
||||||
proc check5() =
|
|
||||||
let result = checkOrder(@[0, 1, 3, 5, 4, 5])
|
|
||||||
if result:
|
|
||||||
results.add cstring"test5 - OK"
|
|
||||||
else:
|
|
||||||
results.add cstring"test5 - FAIL"
|
|
||||||
|
|
||||||
# result: 0 1 3 5 4 5
|
|
||||||
proc test5() =
|
proc test5() =
|
||||||
results.add cstring"test5 started"
|
results.add cstring"test5 started"
|
||||||
entries = @[cstring("0"), cstring("1"), cstring("3"), cstring("5"), cstring("4"), cstring("5") ]
|
entries = @[cstring("0"), cstring("1"), cstring("3"), cstring("5"), cstring("4"), cstring("5")]
|
||||||
redraw()
|
redrawSync()
|
||||||
timeout = setTimeout(check5, 20)
|
check("test 5", @[0, 1, 3, 5, 4, 5])
|
||||||
|
|
||||||
proc check6() =
|
|
||||||
let result = checkOrder(@[])
|
|
||||||
if result:
|
|
||||||
results.add cstring"test6 - OK"
|
|
||||||
else:
|
|
||||||
results.add cstring"test6 - FAIL"
|
|
||||||
|
|
||||||
# result: empty
|
|
||||||
proc test6() =
|
proc test6() =
|
||||||
results.add cstring"test6 started"
|
results.add cstring"test6 started"
|
||||||
entries = @[]
|
entries = @[]
|
||||||
redraw()
|
redrawSync()
|
||||||
timeout = setTimeout(check6, 20)
|
check("test 6", @[])
|
||||||
|
|
||||||
proc check7() =
|
|
||||||
let result = checkOrder(@[2])
|
|
||||||
if result:
|
|
||||||
results.add cstring"test7 - OK"
|
|
||||||
else:
|
|
||||||
results.add cstring"test7 - FAIL"
|
|
||||||
redraw()
|
|
||||||
|
|
||||||
# result: 2
|
# result: 2
|
||||||
proc test7() =
|
proc test7() =
|
||||||
results.add cstring"test7 started"
|
results.add cstring"test7 started"
|
||||||
entries = @[cstring("2")]
|
entries = @[cstring("2")]
|
||||||
redraw()
|
redrawSync()
|
||||||
timeout = setTimeout(check7, 20)
|
check("test 7", @[2])
|
||||||
|
|
||||||
proc createEntry(id: int): VNode =
|
proc createEntry(id: int): VNode =
|
||||||
result = buildHtml():
|
result = buildHtml():
|
||||||
button(id="" & $id):
|
button(id="" & $id):
|
||||||
text $id
|
text $id
|
||||||
|
|
||||||
proc createDom(): VNode =
|
proc createDom(): VNode =
|
||||||
result = buildHtml(tdiv()):
|
result = buildHtml(tdiv()):
|
||||||
ul(id="ul"):
|
ul(id="ul"):
|
||||||
for e in entries:
|
for e in entries:
|
||||||
createEntry(parseInt(e))
|
createEntry(parseInt(e))
|
||||||
for r in results:
|
for r in results:
|
||||||
tdiv:
|
tdiv:
|
||||||
text r
|
text r
|
||||||
|
|
||||||
proc onload() =
|
proc onload() =
|
||||||
for i in 0..5: # 0_000:
|
for i in 0..5: # 0_000:
|
||||||
entries.add(cstring($i))
|
entries.add(cstring($i))
|
||||||
|
test1()
|
||||||
|
reset()
|
||||||
|
test2()
|
||||||
|
reset()
|
||||||
|
test3()
|
||||||
|
reset()
|
||||||
|
test4()
|
||||||
|
reset()
|
||||||
|
test5()
|
||||||
|
reset()
|
||||||
|
test6()
|
||||||
|
reset()
|
||||||
|
test7()
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
onload()
|
|
||||||
setRenderer createDom
|
setRenderer createDom
|
||||||
|
onload()
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,19 @@
|
||||||
include "../src/karax"
|
include "../src/karax"
|
||||||
import "../src/karaxdsl"
|
import "../src/karaxdsl"
|
||||||
|
|
||||||
|
proc hasDom(n: Vnode) =
|
||||||
|
if n.kind in {VNodeKind.component, VNodeKind.vthunk, VNodeKind.dthunk}:
|
||||||
|
discard
|
||||||
|
else:
|
||||||
|
doAssert n.dom != nil
|
||||||
|
for i in 0..<n.len: hasDom(n[i])
|
||||||
|
|
||||||
proc doDiff(a, b: VNode) =
|
proc doDiff(a, b: VNode) =
|
||||||
var patches: seq[Patch] = @[]
|
var patches = newJSeq[Patch]()
|
||||||
echo diff(nil, vnodeToDom(a, kxi), b, a, patches, kxi)
|
echo diff(b, a, nil, vnodeToDom(a, kxi), patches)
|
||||||
echo patches
|
for i in 0..<patches.len:
|
||||||
|
echo patches[i]
|
||||||
|
#hasDom(kxi.currentTree)
|
||||||
|
|
||||||
proc testAppend() =
|
proc testAppend() =
|
||||||
let a = buildHtml(tdiv):
|
let a = buildHtml(tdiv):
|
||||||
|
|
@ -31,7 +40,19 @@ proc testInsert() =
|
||||||
li: text "C"
|
li: text "C"
|
||||||
doDiff(a, b)
|
doDiff(a, b)
|
||||||
|
|
||||||
|
proc testDelete() =
|
||||||
|
let a = buildHtml(tdiv):
|
||||||
|
ul:
|
||||||
|
li: text "A"
|
||||||
|
li: text "B"
|
||||||
|
li: text "C"
|
||||||
|
let b = buildHtml(tdiv):
|
||||||
|
ul:
|
||||||
|
discard
|
||||||
|
doDiff(a, b)
|
||||||
|
|
||||||
kxi = KaraxInstance(rootId: cstring"ROOT", renderer: proc (): VNode = discard)
|
kxi = KaraxInstance(rootId: cstring"ROOT", renderer: proc (): VNode = discard)
|
||||||
|
|
||||||
#testAppend()
|
testAppend()
|
||||||
testInsert()
|
testInsert()
|
||||||
|
testDelete()
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue