From 23fc33162609fb111024a1e0369dfd855283a92a Mon Sep 17 00:00:00 2001 From: Roman Vishnevskii Date: Thu, 27 Apr 2017 18:13:25 +0700 Subject: [PATCH 01/20] Add uibench --- uibench/index.html | 13 ++++ uibench/nim_uibench.nim | 130 +++++++++++++++++++++++++++++++++++++++ uibench/nim_uibench.nims | 1 + 3 files changed, 144 insertions(+) create mode 100644 uibench/index.html create mode 100644 uibench/nim_uibench.nim create mode 100644 uibench/nim_uibench.nims diff --git a/uibench/index.html b/uibench/index.html new file mode 100644 index 0000000..6b3e9c0 --- /dev/null +++ b/uibench/index.html @@ -0,0 +1,13 @@ + + + + + + UI Benchmark: React + + +
+ + + + \ No newline at end of file diff --git a/uibench/nim_uibench.nim b/uibench/nim_uibench.nim new file mode 100644 index 0000000..4ff4cb3 --- /dev/null +++ b/uibench/nim_uibench.nim @@ -0,0 +1,130 @@ +import dom, vdom, karax, karaxdsl, jdict, jstrutils, kajax + +type + HomeState = object + TableItemState = object + id: int + active: bool + props: seq[cstring] + TableState = object + items: seq[TableItemState] + AnimBoxState = object + id: int + time: int + AnimState = object + items: seq[AnimBoxState] + TreeNodeState = object + id: int + container: bool + children: seq[TreeNodeState] + TreeState = object + root: TreeNodeState + AppState = object + location: cstring + home: HomeState + table: TableState + anim: AnimState + tree: TreeState + +proc init*(a: cstring, b: cstring) {.importc: "uibench.init", nodecl.} +proc run*(a: proc(state: AppState), b: proc(samples: any)) {.importc: "uibench.run", nodecl.} + +init(cstring"Nim-karax", cstring"0.6.1") + +var container = document.getElementById("#App") +var appState: AppState + +proc createTableCell(id: cstring): VNode = + proc tableCellClick(ev: karax.Event; n: VNode) = + kout "Clicked" & id + # ev.stopPropogation() + result = buildHtml(td(className="TableCell", onclick=tableCellClick)): + text id + + +proc createTableRow(item: TableItemState): VNode = + var children = createTableCell("#" & &item.id) + for i in 0..len(item.props): + children.add(createTableCell(item.props[i])) + var dataId = cstring($item.id) + + proc createRow(): VNode = + var className = cstring"TableRow" + if item.active: + className = cstring"TableRow active" + result = buildHtml(tr(class=className, `data-id`=dataId)): + children + result = createRow() + + +proc tableCreateVNode(data: TableState): VNode = + var childrens: seq[VNode] = @[] + for i in 0..len(data.items): + childrens.add createTableRow(data.items[i]) + + proc createTable(): VNode = + result = buildHtml(table(class="Table")): + tbody: + for child in childrens: + child + + result = createTable() + +proc createAnimBox(item: AnimBoxState): VNode = + var time = item.time + var dataId: cstring = &item.id + var color: float = float(time mod 10) / 10 + var divStyles: cstring = "borderRadius: " & &(time mod 10) & "px; background: rgba(0,0,0," & cstring($color) & ")" + result = flatHtml(tdiv(className="AnimBox", `data-id`=dataId, style=divStyles)) + +proc animCreateVNode(data: AnimState): VNode = + var items = data.items + var children: seq[VNode] = @[] + for i in 0..len(children): + var item = items[i] + children.add createAnimBox(item) + result = buildHtml(tdiv(className="Anim")): + for child in children: + child + +proc createTreeLeaf(data: TreeNodeState): VNode = + result = buildHtml(li(className="TreeLeaf")): + text &data.id + +proc createTreeNode(data: TreeNodeState): VNode = + var children: seq[VNode] = @[] + for i in 0..len(data.children): + var n = data.children[i] + if n.container: + children.add(createTreeNode(n)) + else: + children.add(createTreeLeaf(n)) + +proc treeCreateVNode(data: TreeState): VNode = + result = buildHtml(tdiv(className="Tree")): + createTreeNode(data.root) + + +proc update(): VNode = + let location = appState.location + var children: VNode = nil + if location == cstring"table": + children = tableCreateVNode(appState.table) + elif location == cstring"anim": + children = animCreateVNode(appState.anim) + elif location == cstring"tree": + children = treeCreateVNode(appState.tree) + proc createMain(): VNode = + result = buildHtml(tdiv(class="Main")): children + result = createMain() + + +proc a(state: AppState) = + appState = state + +proc b(samples: RootRef) = + document.body.innerHTML = cstring"
" & toJson(samples) & cstring"
" + redraw() + +setRenderer update +run(a, b) \ No newline at end of file diff --git a/uibench/nim_uibench.nims b/uibench/nim_uibench.nims new file mode 100644 index 0000000..532b206 --- /dev/null +++ b/uibench/nim_uibench.nims @@ -0,0 +1 @@ +--path: "../src" From 1ad978804c1dced318a1d6e20988b7acc1b4fadb Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Thu, 27 Apr 2017 13:47:00 +0200 Subject: [PATCH 02/20] fixes --- uibench/nim_uibench.nim | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/uibench/nim_uibench.nim b/uibench/nim_uibench.nim index 4ff4cb3..44da28f 100644 --- a/uibench/nim_uibench.nim +++ b/uibench/nim_uibench.nim @@ -1,25 +1,25 @@ import dom, vdom, karax, karaxdsl, jdict, jstrutils, kajax type - HomeState = object - TableItemState = object + HomeState {.importc.} = ref object + TableItemState {.importc.} = ref object id: int active: bool props: seq[cstring] - TableState = object + TableState {.importc.} = ref object items: seq[TableItemState] - AnimBoxState = object + AnimBoxState {.importc.} = ref object id: int time: int - AnimState = object + AnimState {.importc.} = ref object items: seq[AnimBoxState] - TreeNodeState = object + TreeNodeState {.importc.} = ref object id: int container: bool children: seq[TreeNodeState] - TreeState = object + TreeState {.importc.} = ref object root: TreeNodeState - AppState = object + AppState {.importc.} = ref object location: cstring home: HomeState table: TableState @@ -27,7 +27,7 @@ type tree: TreeState proc init*(a: cstring, b: cstring) {.importc: "uibench.init", nodecl.} -proc run*(a: proc(state: AppState), b: proc(samples: any)) {.importc: "uibench.run", nodecl.} +proc run*(a: proc(state: AppState), b: proc(samples: RootRef)) {.importc: "uibench.run", nodecl.} init(cstring"Nim-karax", cstring"0.6.1") @@ -44,7 +44,7 @@ proc createTableCell(id: cstring): VNode = proc createTableRow(item: TableItemState): VNode = var children = createTableCell("#" & &item.id) - for i in 0..len(item.props): + for i in 0.." & toJson(samples) & cstring"" redraw() +kout cstring"updating" setRenderer update +kout cstring"running" run(a, b) \ No newline at end of file From 41df9b769f2268b9206a4efa9747e6c8fb7c563f Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Thu, 27 Apr 2017 14:05:11 +0200 Subject: [PATCH 03/20] more progress --- uibench/index.html | 3 +++ uibench/nim_uibench.nim | 32 +++++++++++++------------------- 2 files changed, 16 insertions(+), 19 deletions(-) diff --git a/uibench/index.html b/uibench/index.html index 6b3e9c0..bc77eb5 100644 --- a/uibench/index.html +++ b/uibench/index.html @@ -9,5 +9,8 @@
+ +
+ \ No newline at end of file diff --git a/uibench/nim_uibench.nim b/uibench/nim_uibench.nim index 44da28f..4c2b467 100644 --- a/uibench/nim_uibench.nim +++ b/uibench/nim_uibench.nim @@ -41,12 +41,11 @@ proc createTableCell(id: cstring): VNode = result = buildHtml(td(className="TableCell", onclick=tableCellClick)): text id - proc createTableRow(item: TableItemState): VNode = var children = createTableCell("#" & &item.id) for i in 0.." & toJson(samples) & cstring"" - redraw() + #document.body.innerHTML = cstring"
" & toJson(samples) & cstring"
" + #redraw() + discard kout cstring"updating" setRenderer update From 443b24de3e3ba3d7011fdc14c1e325f8bbd1fdf2 Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Thu, 27 Apr 2017 14:16:34 +0200 Subject: [PATCH 04/20] more fixes --- src/karax.nim | 8 ++++++++ uibench/index.html | 5 +++-- uibench/nim_uibench.nim | 22 +++++++++++++--------- 3 files changed, 24 insertions(+), 11 deletions(-) diff --git a/src/karax.nim b/src/karax.nim index ed956d8..45056c5 100644 --- a/src/karax.nim +++ b/src/karax.nim @@ -256,6 +256,8 @@ proc redraw*() = else: dodraw() +proc redrawForce*() = dodraw() + proc init(ev: Event) = reqFrame(dodraw) @@ -263,6 +265,12 @@ proc setRenderer*(renderer: proc (): VNode) = dorender = renderer window.onload = init +proc setRendererOnly*(renderer: proc (): VNode) = + dorender = renderer + +proc setOnloadOnly*() = + window.onload = init + proc addEventHandler*(n: VNode; k: EventKind; action: EventHandler) = ## Implements the foundation of Karax's event management. ## Karax DSL transforms ``tag(onEvent = handler)`` to diff --git a/uibench/index.html b/uibench/index.html index bc77eb5..4d0e517 100644 --- a/uibench/index.html +++ b/uibench/index.html @@ -7,10 +7,11 @@
- -
+ + + \ No newline at end of file diff --git a/uibench/nim_uibench.nim b/uibench/nim_uibench.nim index 4c2b467..b8e453e 100644 --- a/uibench/nim_uibench.nim +++ b/uibench/nim_uibench.nim @@ -26,11 +26,6 @@ type anim: AnimState tree: TreeState -proc init*(a: cstring, b: cstring) {.importc: "uibench.init", nodecl.} -proc run*(a: proc(state: AppState), b: proc(samples: RootRef)) {.importc: "uibench.run", nodecl.} - -init(cstring"Nim-karax", cstring"0.6.1") - var container = document.getElementById("#App") var appState: AppState @@ -98,7 +93,9 @@ proc treeCreateVNode(data: TreeState): VNode = proc update(): VNode = - if appState == nil: return newVNode(VNodeKind.tdiv) + if appState == nil: + kout cstring"stupid fuck" + return newVNode(VNodeKind.tdiv) let location = appState.location var children: VNode = nil if location == cstring"table": @@ -113,15 +110,22 @@ proc update(): VNode = children proc a(state: AppState) = + kout cstring"setting state here" appState = state - redraw() + redrawForce() proc b(samples: RootRef) = + kout cstring"end called" #document.body.innerHTML = cstring"
" & toJson(samples) & cstring"
" #redraw() discard -kout cstring"updating" -setRenderer update +proc init*(a: cstring, b: cstring) {.importc: "uibench.init", nodecl.} +proc run*(a: proc(state: AppState), b: proc(samples: RootRef)) {.importc: "uibench.run", nodecl.} + +init(cstring"Nim-karax", cstring"0.6.1") + +setRendererOnly update kout cstring"running" + run(a, b) \ No newline at end of file From a30432228689d0d8eb739d84c968f442f0d15c00 Mon Sep 17 00:00:00 2001 From: Roman Vishnevskii Date: Thu, 27 Apr 2017 20:49:54 +0700 Subject: [PATCH 05/20] Uibench fix td --- uibench/nim_uibench.nim | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/uibench/nim_uibench.nim b/uibench/nim_uibench.nim index b8e453e..ad3378d 100644 --- a/uibench/nim_uibench.nim +++ b/uibench/nim_uibench.nim @@ -30,34 +30,43 @@ var container = document.getElementById("#App") var appState: AppState proc createTableCell(id: cstring): VNode = + kout cstring"createTableCell" proc tableCellClick(ev: karax.Event; n: VNode) = + kout cstring"tableCellClick" kout "Clicked" & id # ev.stopPropogation() result = buildHtml(td(className="TableCell", onclick=tableCellClick)): text id proc createTableRow(item: TableItemState): VNode = - var children = createTableCell("#" & &item.id) + kout cstring"createTableRow" + var children: seq[VNode] = @[] + children.add createTableCell("#" & &item.id) for i in 0.. Date: Thu, 27 Apr 2017 20:57:33 +0700 Subject: [PATCH 06/20] Change className to class --- uibench/nim_uibench.nim | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/uibench/nim_uibench.nim b/uibench/nim_uibench.nim index ad3378d..5e1d3af 100644 --- a/uibench/nim_uibench.nim +++ b/uibench/nim_uibench.nim @@ -35,7 +35,7 @@ proc createTableCell(id: cstring): VNode = kout cstring"tableCellClick" kout "Clicked" & id # ev.stopPropogation() - result = buildHtml(td(className="TableCell", onclick=tableCellClick)): + result = buildHtml(td(class="TableCell", onclick=tableCellClick)): text id proc createTableRow(item: TableItemState): VNode = @@ -71,7 +71,7 @@ proc createAnimBox(item: AnimBoxState): VNode = var dataId: cstring = &item.id var color: float = float(time mod 10) / 10 var divStyles: cstring = "borderRadius: " & &(time mod 10) & "px; background: rgba(0,0,0," & cstring($color) & ")" - result = flatHtml(tdiv(className="AnimBox", `data-id`=dataId, style=divStyles)) + result = flatHtml(tdiv(class="AnimBox", `data-id`=dataId, style=divStyles)) proc animCreateVNode(data: AnimState): VNode = kout cstring"animCreateVNode" @@ -80,13 +80,13 @@ proc animCreateVNode(data: AnimState): VNode = for i in 0.. Date: Thu, 27 Apr 2017 21:58:47 +0700 Subject: [PATCH 07/20] Uibench fix border-radius --- uibench/nim_uibench.nim | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/uibench/nim_uibench.nim b/uibench/nim_uibench.nim index 5e1d3af..e21eca8 100644 --- a/uibench/nim_uibench.nim +++ b/uibench/nim_uibench.nim @@ -70,14 +70,14 @@ proc createAnimBox(item: AnimBoxState): VNode = var time = item.time var dataId: cstring = &item.id var color: float = float(time mod 10) / 10 - var divStyles: cstring = "borderRadius: " & &(time mod 10) & "px; background: rgba(0,0,0," & cstring($color) & ")" + var divStyles: cstring = "border-radius: " & &(time mod 10) & "px; background: rgba(0,0,0," & cstring($color) & ")" result = flatHtml(tdiv(class="AnimBox", `data-id`=dataId, style=divStyles)) proc animCreateVNode(data: AnimState): VNode = kout cstring"animCreateVNode" var items = data.items var children: seq[VNode] = @[] - for i in 0.. Date: Thu, 27 Apr 2017 22:22:18 +0700 Subject: [PATCH 08/20] Uibench final fix --- uibench/index.html | 3 +-- uibench/nim_uibench.nim | 4 ++++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/uibench/index.html b/uibench/index.html index 4d0e517..5b2cb4c 100644 --- a/uibench/index.html +++ b/uibench/index.html @@ -6,7 +6,6 @@ UI Benchmark: React -
@@ -14,4 +13,4 @@ - \ No newline at end of file + diff --git a/uibench/nim_uibench.nim b/uibench/nim_uibench.nim index e21eca8..f6c214b 100644 --- a/uibench/nim_uibench.nim +++ b/uibench/nim_uibench.nim @@ -98,6 +98,10 @@ proc createTreeNode(data: TreeNodeState): VNode = children.add(createTreeNode(n)) else: children.add(createTreeLeaf(n)) + result = buildHtml(ul(class="TreeNode")): + for child in children: + child + proc treeCreateVNode(data: TreeState): VNode = kout cstring"treeCreateVNode" From 393c336f901f179147c78e7d8bbc51eda25567b1 Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Thu, 27 Apr 2017 17:26:01 +0200 Subject: [PATCH 09/20] simplifications --- uibench/nim_uibench.nim | 31 +++++++++---------------------- 1 file changed, 9 insertions(+), 22 deletions(-) diff --git a/uibench/nim_uibench.nim b/uibench/nim_uibench.nim index 5e1d3af..e88cef8 100644 --- a/uibench/nim_uibench.nim +++ b/uibench/nim_uibench.nim @@ -26,7 +26,6 @@ type anim: AnimState tree: TreeState -var container = document.getElementById("#App") var appState: AppState proc createTableCell(id: cstring): VNode = @@ -44,18 +43,13 @@ proc createTableRow(item: TableItemState): VNode = children.add createTableCell("#" & &item.id) for i in 0.. Date: Thu, 27 Apr 2017 17:39:54 +0200 Subject: [PATCH 10/20] simplifications --- uibench/nim_uibench.nim | 56 +++++++++++------------------------------ 1 file changed, 14 insertions(+), 42 deletions(-) diff --git a/uibench/nim_uibench.nim b/uibench/nim_uibench.nim index e1c644b..a9a7d96 100644 --- a/uibench/nim_uibench.nim +++ b/uibench/nim_uibench.nim @@ -29,79 +29,57 @@ type var appState: AppState proc createTableCell(id: cstring): VNode = - kout cstring"createTableCell" proc tableCellClick(ev: karax.Event; n: VNode) = - kout cstring"tableCellClick" kout "Clicked" & id - # ev.stopPropogation() result = buildHtml(td(class="TableCell", onclick=tableCellClick)): text id proc createTableRow(item: TableItemState): VNode = - kout cstring"createTableRow" - var children: seq[VNode] = @[] - children.add createTableCell("#" & &item.id) - for i in 0.." & toJson(samples) & cstring"" - #redraw() - discard + document.body.innerHTML = cstring"
" & toJson(samples) & cstring"
" proc init*(a: cstring, b: cstring) {.importc: "uibench.init", nodecl.} proc run*(a: proc(state: AppState), b: proc(samples: RootRef)) {.importc: "uibench.run", nodecl.} @@ -131,6 +105,4 @@ proc run*(a: proc(state: AppState), b: proc(samples: RootRef)) {.importc: "uiben init(cstring"Nim-karax", cstring"0.6.1") setRendererOnly update -kout cstring"running" - run(a, b) \ No newline at end of file From 8bdaab3f821ba902cf4d4d874714303bad1bc851 Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Thu, 27 Apr 2017 17:57:04 +0200 Subject: [PATCH 11/20] uibenchmark works --- uibench/nim_uibench.nim | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/uibench/nim_uibench.nim b/uibench/nim_uibench.nim index a9a7d96..2983686 100644 --- a/uibench/nim_uibench.nim +++ b/uibench/nim_uibench.nim @@ -96,8 +96,10 @@ proc a(state: AppState) = appState = state redrawForce() +proc myToJson[T](x: T): cstring {.importcpp: "JSON.stringify(#, null, ' ')".} + proc b(samples: RootRef) = - document.body.innerHTML = cstring"
" & toJson(samples) & cstring"
" + document.body.innerHTML = cstring"
" & myToJson(samples) & cstring"
" proc init*(a: cstring, b: cstring) {.importc: "uibench.init", nodecl.} proc run*(a: proc(state: AppState), b: proc(samples: RootRef)) {.importc: "uibench.run", nodecl.} From 91679f8ae607a1d6a721a0d68e6fcda5df5a8998 Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Thu, 27 Apr 2017 18:23:29 +0200 Subject: [PATCH 12/20] made it shorter again --- uibench/nim_uibench.nim | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/uibench/nim_uibench.nim b/uibench/nim_uibench.nim index 2983686..3d1f078 100644 --- a/uibench/nim_uibench.nim +++ b/uibench/nim_uibench.nim @@ -35,9 +35,7 @@ proc createTableCell(id: cstring): VNode = text id proc createTableRow(item: TableItemState): VNode = - var className = cstring"TableRow" - if item.active: - className = cstring"TableRow active" + let className = if item.active: cstring"TableRow active" else: cstring"TableRow" result = buildHtml(tr(class=className, `data-id` = &item.id)): createTableCell("#" & &item.id) for i in 0.. Date: Mon, 1 May 2017 09:58:10 +0200 Subject: [PATCH 13/20] optimized VDOM implementation for uibench --- src/vdom.nim | 64 ++++++++++++++++++++++++++++------------------------ 1 file changed, 35 insertions(+), 29 deletions(-) diff --git a/src/vdom.nim b/src/vdom.nim index 25f6c12..59fa1d6 100644 --- a/src/vdom.nim +++ b/src/vdom.nim @@ -106,8 +106,9 @@ type # even index: key, odd index: value; done this way for memory efficiency: attrs: seq[cstring] events*: seq[(EventKind, EventHandler)] - hash*: Hash - validHash*: bool + when false: + hash*: Hash + validHash*: bool dom*: Node ## the attached real DOM node. Can be 'nil' if the virtual node ## is not part of the virtual DOM anymore. @@ -146,7 +147,11 @@ proc eq*(a, b: VNode): bool = proc setAttr*(n: VNode; key: cstring; val: cstring = "") = if n.attrs.isNil: - n.attrs = @[key, val] + when true: + let x = @[key, val] + shallowCopy n.attrs, x + else: + n.attrs = @[key, val] else: for i in countup(0, n.attrs.len-2, 2): if n.attrs[i] == key: @@ -216,32 +221,33 @@ proc toString*(n: VNode; result: var string; indent: int) = for i in 1..indent: result.add ' ' result.add "\L" -proc calcHash*(n: VNode) = - if n.validHash: return - n.validHash = true - var h: Hash = ord n.kind - if n.id != nil: - h &= "id" - h &= n.id - if n.class != nil: - h &= "class" - h &= n.class - if n.key >= 0: - h &= "k" - h &= n.key - for k, v in attrs(n): - h &= " " - h &= k - h &= "=" - h &= v - if n.kind == VNodeKind.text or n.text != nil: - h &= "t" - h &= n.text - else: - for child in items(n): - calcHash(child) - h &= child.hash - n.hash = h +when false: + proc calcHash*(n: VNode) = + if n.validHash: return + n.validHash = true + var h: Hash = ord n.kind + if n.id != nil: + h &= "id" + h &= n.id + if n.class != nil: + h &= "class" + h &= n.class + if n.key >= 0: + h &= "k" + h &= n.key + for k, v in attrs(n): + h &= " " + h &= k + h &= "=" + h &= v + if n.kind == VNodeKind.text or n.text != nil: + h &= "t" + h &= n.text + else: + for child in items(n): + calcHash(child) + h &= child.hash + n.hash = h proc `$`*(n: VNode): cstring = var res = "" From 4c582d7c06ffc10b72c65702fb188e603a8ded9e Mon Sep 17 00:00:00 2001 From: Roman Vishnevskii Date: Tue, 2 May 2017 19:46:20 +0700 Subject: [PATCH 14/20] Avoid using node.childNodes --- src/karax.nim | 75 ++++++++++++++++++++++++++++++++------------------- src/vdom.nim | 1 + 2 files changed, 48 insertions(+), 28 deletions(-) diff --git a/src/karax.nim b/src/karax.nim index 45056c5..1ef0a9b 100644 --- a/src/karax.nim +++ b/src/karax.nim @@ -164,7 +164,13 @@ proc updateDirtyElements(parent, current: Node, newNode: VNode) = # leave early if we know there cannot be anything left to do: #if dirtyCount <= 0: return +proc setDomField(a, b: VNode) = + a.dom = b.dom + for i in 0..= commonPrefix and newPos >= commonPrefix and - equalsTree(newNode[newPos], oldNode[oldPos]): - dec oldPos - dec newPos + while rightOld > left and rightNew > left and + equalsTree(newNode[rightNew], oldNode[rightOld]): + #newNode[rightNew].dom = oldNode[rightOld].dom + setDomField(newNode[rightNew], oldNode[rightOld]) + dec rightOld + dec rightNew + + minRight = min(rightOld, rightNew) + if left <= minRight: + updateElement(current, oldNode[left].dom, newNode[left], oldNode[left]) + inc left - var pos = min(oldPos, newPos) + 1 - for i in commonPrefix..pos-1: - updateElement(current, current.childNodes[i], newNode[i], oldNode[i]) + if left < minRight: + updateElement(current, oldNode[rightOld].dom, newNode[rightNew], oldNode[rightOld]) + dec rightNew + dec rightOld + + var nextChildPos = rightOld + 1 + while left <= rightNew: + if nextChildPos == oldLength: + current.appendChild(vnodeToDom(newNode[left])) + else: + current.insertBefore(vnodeToDom(newNode[left]), oldNode[rightOld + 1].dom) + # added new Node, so old state of VDOM have one more Node + inc oldLength + inc left + inc nextChildPos - var nextChildPos = oldPos + 1 - while pos <= newPos: - if nextChildPos == oldLength: - current.appendChild(vnodeToDom(newNode[pos])) - else: - 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 pos..oldPos: - detach(oldNode[i]) - current.removeChild(current.childNodes[pos]) + for i in left..rightOld: + current.removeChild(oldNode[i].dom) + detach(oldNode[i]) when false: var drawTimeout: Timeout diff --git a/src/vdom.nim b/src/vdom.nim index 59fa1d6..48574de 100644 --- a/src/vdom.nim +++ b/src/vdom.nim @@ -166,6 +166,7 @@ proc getAttr*(n: VNode; key: cstring): cstring = proc len*(x: VNode): int = x.kids.len proc `[]`*(x: VNode; idx: int): VNode = x.kids[idx] +proc `[]=`*(x: VNode; idx: int, y: VNode) = x.kids[idx] = y proc add*(parent, kid: VNode) = parent.kids.add kid proc newVNode*(kind: VNodeKind): VNode = VNode(kind: kind, key: -1) From cd78f5085f0619b0cbbeb6c1f7f67a40d855eb85 Mon Sep 17 00:00:00 2001 From: Roman Vishnevskii Date: Tue, 2 May 2017 19:50:35 +0700 Subject: [PATCH 15/20] Simplify updateElement --- src/karax.nim | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/karax.nim b/src/karax.nim index 1ef0a9b..23dc324 100644 --- a/src/karax.nim +++ b/src/karax.nim @@ -222,17 +222,16 @@ proc updateElement(parent, current: Node, newNode, oldNode: VNode) = updateElement(current, oldNode[rightOld].dom, newNode[rightNew], oldNode[rightOld]) dec rightNew dec rightOld - - var nextChildPos = rightOld + 1 + + var posNextElem = rightOld + 1 + var isPushBack = posNextElem == oldLength while left <= rightNew: - if nextChildPos == oldLength: + if isPushBack: current.appendChild(vnodeToDom(newNode[left])) else: - current.insertBefore(vnodeToDom(newNode[left]), oldNode[rightOld + 1].dom) + current.insertBefore(vnodeToDom(newNode[left]), oldNode[posNextElem].dom) # added new Node, so old state of VDOM have one more Node - inc oldLength inc left - inc nextChildPos for i in left..rightOld: current.removeChild(oldNode[i].dom) From e63a7dbb21490464fd59a0ba77bb02e9af098de3 Mon Sep 17 00:00:00 2001 From: Roman Vishnevskii Date: Wed, 3 May 2017 14:48:36 +0700 Subject: [PATCH 16/20] Delete useless comment --- src/karax.nim | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/karax.nim b/src/karax.nim index 23dc324..85a126a 100644 --- a/src/karax.nim +++ b/src/karax.nim @@ -164,11 +164,6 @@ proc updateDirtyElements(parent, current: Node, newNode: VNode) = # leave early if we know there cannot be anything left to do: #if dirtyCount <= 0: return -proc setDomField(a, b: VNode) = - a.dom = b.dom - for i in 0.. left and rightNew > left and equalsTree(newNode[rightNew], oldNode[rightOld]): - #newNode[rightNew].dom = oldNode[rightOld].dom - setDomField(newNode[rightNew], oldNode[rightOld]) + newNode[rightNew] = oldNode[rightOld] dec rightOld dec rightNew @@ -230,7 +223,6 @@ proc updateElement(parent, current: Node, newNode, oldNode: VNode) = current.appendChild(vnodeToDom(newNode[left])) else: current.insertBefore(vnodeToDom(newNode[left]), oldNode[posNextElem].dom) - # added new Node, so old state of VDOM have one more Node inc left for i in left..rightOld: From 78a47158a1da96f5ca652bc72cf61f785a8c0cf2 Mon Sep 17 00:00:00 2001 From: Roman Vishnevskii Date: Thu, 4 May 2017 14:35:44 +0700 Subject: [PATCH 17/20] Introduce track field --- src/karaxdsl.nim | 2 +- src/vdom.nim | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/karaxdsl.nim b/src/karaxdsl.nim index 5ba525a..5553d8f 100644 --- a/src/karaxdsl.nim +++ b/src/karaxdsl.nim @@ -4,7 +4,7 @@ from strutils import startsWith, toLowerAscii const StmtContext = ["kout", "inc", "echo", "dec", "!"] - SpecialAttrs = ["id", "class", "value", "key"] + SpecialAttrs = ["id", "class", "value", "key", "track"] proc getName(n: NimNode): string = case n.kind diff --git a/src/vdom.nim b/src/vdom.nim index 48574de..a80fc10 100644 --- a/src/vdom.nim +++ b/src/vdom.nim @@ -102,6 +102,7 @@ type kind*: VNodeKind key*: VKey id*, class*, text*: cstring + track*: int kids: seq[VNode] # even index: key, odd index: value; done this way for memory efficiency: attrs: seq[cstring] @@ -166,7 +167,7 @@ proc getAttr*(n: VNode; key: cstring): cstring = proc len*(x: VNode): int = x.kids.len proc `[]`*(x: VNode; idx: int): VNode = x.kids[idx] -proc `[]=`*(x: VNode; idx: int, y: VNode) = x.kids[idx] = y +proc `[]=`*(x: VNode; idx: int; y: VNode) = x.kids[idx] = y proc add*(parent, kid: VNode) = parent.kids.add kid proc newVNode*(kind: VNodeKind): VNode = VNode(kind: kind, key: -1) From 1b56477e5989b5026510059a815d8023af09ffe9 Mon Sep 17 00:00:00 2001 From: Roman Vishnevskii Date: Wed, 10 May 2017 14:38:16 +0700 Subject: [PATCH 18/20] Cross comparing (debug) --- src/karax.nim | 193 ++++++++++++++++++++++++++++++++-------- tests/diffDomTests.html | 1 - tests/diffDomTests.nim | 38 ++++---- 3 files changed, 173 insertions(+), 59 deletions(-) diff --git a/src/karax.nim b/src/karax.nim index 85a126a..ed4ab39 100644 --- a/src/karax.nim +++ b/src/karax.nim @@ -130,9 +130,6 @@ proc equalsShallow(a, b: VNode): bool = if a.text != b.text: return false elif a.kind == VNodeKind.vthunk or a.kind == VNodeKind.dthunk: if a.text != b.text: return false - if a.len != b.len: return false - for i in 0..= left and rightNew >= left and equalsShallow(newNode[rightNew], oldNode[rightOld]): + updateElement(current, oldNode[rightOld].dom, newNode[rightNew], oldNode[rightOld]) + dec rightOld + dec rightNew + + var leftOld = left + var leftNew = left - while left <= min(rightNew, rightOld): - var minRight = min(rightOld, rightNew) - - while left <= minRight and - equalsTree(newNode[left], oldNode[left]): - newNode[left] = oldNode[left] - inc left + + var flag = false + #if rightOld >= leftOld and rightNew >= leftNew and equalsShallow(oldNode[leftOld], newNode[rightNew]): + kout cstring("current") + kout printChildren(current) + kout cstring("oldNode") + kout printChildren(oldNode) + kout cstring("newNode") + kout printChildren(newNode) + flag = true + + var step = 1 + while rightOld >= leftOld and rightNew >= leftNew and equalsShallow(oldNode[leftOld], newNode[rightNew]): + kout cstring($oldNode[leftOld].id & " " & $newNode[rightNew].id) + kout cstring("pos: " & $leftOld & " " & $rightNew) - while rightOld > left and rightNew > left and - equalsTree(newNode[rightNew], oldNode[rightOld]): - newNode[rightNew] = oldNode[rightOld] - dec rightOld - dec rightNew - - minRight = min(rightOld, rightNew) - if left <= minRight: - updateElement(current, oldNode[left].dom, newNode[left], oldNode[left]) - inc left + var nextNode: Node = nil + if rightNew + 1 < newLength: + nextNode = newNode[rightNew + 1].dom - if left < minRight: - updateElement(current, oldNode[rightOld].dom, newNode[rightNew], oldNode[rightOld]) - dec rightNew - dec rightOld - - var posNextElem = rightOld + 1 - var isPushBack = posNextElem == oldLength - while left <= rightNew: - if isPushBack: - current.appendChild(vnodeToDom(newNode[left])) - else: - current.insertBefore(vnodeToDom(newNode[left]), oldNode[posNextElem].dom) - inc left + updateElement(current, oldNode[leftOld].dom, newNode[rightNew], oldNode[leftOld]) + if nextNode == nil: + current.appendChild(oldNode[leftOld].dom) + kout cstring("append") + else: + kout cstring("insertBefore") + current.insertBefore(oldNode[leftOld].dom, nextNode) + #kout cstring("step = " & $step & "; leftOld = " & $leftOld & "; rightNew = " & $rightNew) + kout cstring($oldNode[leftOld]) + inc step + inc leftOld + dec rightNew + + if flag: + kout cstring("after") + kout printChildren(current) + - for i in left..rightOld: - current.removeChild(oldNode[i].dom) - detach(oldNode[i]) + while rightOld >= leftOld and rightNew >= leftNew: + updateElement(current, current[leftOld], newNode[leftNew], oldNode[leftOld]) + inc leftNew + inc leftOld + + var isPushBack = (rightNew + 1 == newLength) + var nextNode: Node = nil + if not isPushBack: + nextNode = newNode[rightNew + 1].dom + while leftNew <= rightNew: + var node = vnodeToDom(newNode[leftNew])ды + if isPushBack: + current.appendChild(node) + else: + current.insertBefore(node, nextNode) + inc leftNew + + for i in leftOld..rightOld: + current.removeChild(oldNode[i].dom) + detach(oldNode[i]) + + kout cstring("----------------") + kout cstring("before") + kout before + kout cstring("finish") + kout printChildren(current) + kout cstring("----------------") + kout cstring("----------------") + + + # var leftNew = left + # var leftOld = left + # while rightOld >= leftOld and rightNew >= leftNew and equalsShallow(oldNode[leftOld], newNode[rightNew]): + # var nextNode: Node = nil + # if rightNew + 1 < newLength: + # nextNode = newNode[rightNew + 1].dom + # if nextNode == nil: + # current.appendChild(oldNode[leftOld].dom) + # else: + # current.insertBefore(oldNode[leftOld].dom, nextNode) + # inc leftOld + # dec rightNew + + # var lastTarget: Node = nil + # if leftOld <= rightOld: + # lastTarget = oldNode[leftOld].dom + # while rightOld >= leftOld and rightNew >= leftNew and equalsShallow(oldNode[rightOld], newNode[leftNew]): + # current.insertBefore(oldNode[rightOld].dom, lastTarget) + # lastTarget = oldNode[rightOld].dom + # inc leftNew + # dec rightOld + + # if rightOld < leftOld: + # var isPushBack = leftOld < oldLength + # for i in leftNew..rightNew: + # if isPushBack: + # current.appendChild(vnodeToDom(newNode[i])) + # else: + # current.insertBefore(vnodeToDom(newNode[i]), oldNode[leftOld].dom) + # elif rightNew < leftNew: + # for i in leftOld..rightOld: + # current.removeChild(oldNode[i].dom) + # detach(oldNode[i]) + # else: + # while rightOld >= leftOld and rightNew >= leftNew: + # updateElement(current, oldNode[leftOld].dom, newNode[leftNew], oldNode[leftOld]) + # inc leftNew + # inc leftOld + # if rightOld < leftOld: + # while rightNew >= leftNew: + # current.appendChild(vnodeToDom(newNode[leftNew])) + # inc leftNew + # elif rightNew < leftNew: + # while rightOld >= leftOld: + # current.removeChild(oldNode[leftOld].dom) + # detach(oldNode[leftOld]) + # inc leftOld when false: var drawTimeout: Timeout diff --git a/tests/diffDomTests.html b/tests/diffDomTests.html index 5ecf130..cb6ab28 100644 --- a/tests/diffDomTests.html +++ b/tests/diffDomTests.html @@ -3,7 +3,6 @@ Diff dom tests - diff --git a/tests/diffDomTests.nim b/tests/diffDomTests.nim index 7fdcf85..403980a 100644 --- a/tests/diffDomTests.nim +++ b/tests/diffDomTests.nim @@ -41,7 +41,7 @@ proc test1() = proc check2() = let result = checkOrder(@[8, 0, 1, 2, 3, 4, 7, 5]) if result: - results.add cstring"test1 - OK" + results.add cstring"test2 - OK" else: results.add cstring"test2 - FAIL" @@ -134,7 +134,7 @@ proc createDom(): VNode = result = buildHtml(tdiv()): ul(id="ul"): for e in entries: - createEntry(parseInt(e)) + createEntry(jstrutils.parseInt(e)) for r in results: tdiv: text r @@ -147,20 +147,20 @@ proc onload() = var dtTest = 500 var t = dtReset - timeout = setTimeout(test1, t) - t += dtTest - timeout = setTimeout(reset, t) - 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(test2, t) + # t += dtTest + # timeout = setTimeout(reset, t) + # t += dtReset - timeout = setTimeout(test3, 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 @@ -172,12 +172,12 @@ proc onload() = timeout = setTimeout(reset, t) t += dtReset - timeout = setTimeout(test6, 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) + # timeout = setTimeout(test7, t) onload() setRenderer createDom From ae448f947f782881647fd0bd31ef416776857d58 Mon Sep 17 00:00:00 2001 From: Roman Vishnevskii Date: Wed, 10 May 2017 17:38:51 +0700 Subject: [PATCH 19/20] Add LIS opt --- src/karax.nim | 326 ++++++++++++++++++++++++----------------- tests/diffDomTests.nim | 34 ++--- 2 files changed, 208 insertions(+), 152 deletions(-) diff --git a/src/karax.nim b/src/karax.nim index ed4ab39..e661b78 100644 --- a/src/karax.nim +++ b/src/karax.nim @@ -1,6 +1,6 @@ ## Karax -- Single page applications for Nim. -import dom, vdom, jstrutils, components, jdict +import dom, vdom, jstrutils, components, jdict, tables export dom.Event @@ -123,28 +123,20 @@ proc replaceById(id: cstring; newTree: Node) = #newTree.id = id proc equalsShallow(a, b: VNode): bool = - if a.kind != b.kind: return false - if a.id != b.id: return false - if a.key != b.key: return false - if a.kind == VNodeKind.text: - if a.text != b.text: return false - elif a.kind == VNodeKind.vthunk or a.kind == VNodeKind.dthunk: - if a.text != b.text: return false - if not sameAttrs(a, b): return false - if a.class != b.class: return false - # XXX test event listeners here? - return true - -proc equalsTree(a, b: VNode): bool = - when false: - # hashing is too fragile now with component support: - if not a.validHash: - a.calcHash() - if not b.validHash: - b.calcHash() - return a.hash == b.hash + if a.key == -1 and b.key == -1: + return eq(a, b) else: - result = eq(a, b) + if a.kind != b.kind: return false + if a.id != b.id: return false + if a.key != b.key: return false + if a.kind == VNodeKind.text: + if a.text != b.text: return false + elif a.kind == VNodeKind.vthunk or a.kind == VNodeKind.dthunk: + if a.text != b.text: return false + if not sameAttrs(a, b): return false + if a.class != b.class: return false + # XXX test event listeners here? + return true proc updateDirtyElements(parent, current: Node, newNode: VNode) = if newNode.key >= 0 and isDirty(newNode.key): @@ -162,26 +154,68 @@ proc updateDirtyElements(parent, current: Node, newNode: VNode) = #if dirtyCount <= 0: return proc printChildren(parent: Node): cstring = - if parent != nil and parent.hasChildNodes: - var it = parent.firstChild - result = "" - while it != nil: - if it.id == nil: - result.add(" nil") - else: - result.add(" " & $it.id) - it = it.nextSibling + discard + # if parent != nil and parent.hasChildNodes: + # var it = parent.firstChild + # result = "" + # while it != nil: + # if it.id == nil: + # result.add(" nil") + # else: + # result.add(" " & $it.id) + # it = it.nextSibling proc printChildren(parent: VNode): cstring = - if parent != nil: - result = "" - for i in 0..parent.len-1: - if parent[i] == nil or parent[i].id == nil: - kout cstring("nil") - else: - result.add(" " & $(parent[i].id)) + discard + # if parent != nil: + # result = "" + # for i in 0..parent.len-1: + # if parent[i] == nil or parent[i].id == nil: + # result.add(" nil") + # else: + # result.add(" " & $(parent[i].id)) -proc updateElement(parent, current: Node, newNode, oldNode: VNode) = +proc print(s: cstring, ident: int) = + discard + # var result = "" + # for i in 0..ident: + # result.add " " + # result.add(s) + # kout cstring(result) + +proc longestIncreasingSubsequence(a: seq[int]): seq[int] = + result.add 0 + var parent = newSeq[int](len(a)) + for i in 0.. 0: + parent[i] = result[left - 1] + result[left] = i + + var pos = len(result) + var v = result[pos - 1] + while pos > 0: + result[pos] = v + v = parent[v] + dec pos + +proc updateElement(parent, current: Node, newNode, oldNode: VNode, ident: int = 0) = newNode.dom = oldNode.dom if not equalsShallow(newNode, oldNode): detach(oldNode) @@ -207,21 +241,21 @@ proc updateElement(parent, current: Node, newNode, oldNode: VNode) = current.removeChild(current.lastChild) else: #kout cstring("start") - kout cstring("----------------") - kout cstring("----------------") + print("----------------", ident) + print("----------------", ident) var before = printChildren(current) # maximal common prefix var left = 0 while left < minLength and equalsShallow(newNode[left], oldNode[left]): - updateElement(current, oldNode[left].dom, newNode[left], oldNode[left]) + updateElement(current, oldNode[left].dom, newNode[left], oldNode[left], ident + 1) inc left # maximal common suffix var rightOld = oldLength - 1 var rightNew = newLength - 1 while rightOld >= left and rightNew >= left and equalsShallow(newNode[rightNew], oldNode[rightOld]): - updateElement(current, oldNode[rightOld].dom, newNode[rightNew], oldNode[rightOld]) + updateElement(current, oldNode[rightOld].dom, newNode[rightNew], oldNode[rightOld], ident + 1) dec rightOld dec rightNew @@ -231,118 +265,140 @@ proc updateElement(parent, current: Node, newNode, oldNode: VNode) = var flag = false #if rightOld >= leftOld and rightNew >= leftNew and equalsShallow(oldNode[leftOld], newNode[rightNew]): - kout cstring("current") - kout printChildren(current) - kout cstring("oldNode") - kout printChildren(oldNode) - kout cstring("newNode") - kout printChildren(newNode) + print("current", ident) + print(printChildren(current), ident) + print("oldNode", ident) + print(printChildren(oldNode), ident) + print("newNode", ident) + print(printChildren(newNode), ident) flag = true - var step = 1 + # cross comparing while rightOld >= leftOld and rightNew >= leftNew and equalsShallow(oldNode[leftOld], newNode[rightNew]): - kout cstring($oldNode[leftOld].id & " " & $newNode[rightNew].id) - kout cstring("pos: " & $leftOld & " " & $rightNew) + print($oldNode[leftOld].id & " " & $newNode[rightNew].id, ident) + print("pos: " & $leftOld & " " & $rightNew, ident) var nextNode: Node = nil if rightNew + 1 < newLength: nextNode = newNode[rightNew + 1].dom - - updateElement(current, oldNode[leftOld].dom, newNode[rightNew], oldNode[leftOld]) + print("update", ident) + updateElement(current, oldNode[leftOld].dom, newNode[rightNew], oldNode[leftOld], ident + 1) + print("update", ident) if nextNode == nil: current.appendChild(oldNode[leftOld].dom) - kout cstring("append") + print("append", ident) else: - kout cstring("insertBefore") + print("insertBefore", ident) current.insertBefore(oldNode[leftOld].dom, nextNode) - #kout cstring("step = " & $step & "; leftOld = " & $leftOld & "; rightNew = " & $rightNew) - kout cstring($oldNode[leftOld]) - inc step + print($oldNode[leftOld], ident) inc leftOld dec rightNew - + + while rightOld >= leftOld and rightNew >= leftNew and equalsShallow(oldNode[rightOld], newNode[leftNew]): + var nextNode: Node = oldNode[leftOld].dom + updateElement(current, oldNode[rightOld].dom, newNode[leftNew], oldNode[rightOld], ident + 1) + current.insertBefore(oldNode[rightOld].dom, nextNode) + inc leftNew + dec rightOld + if flag: - kout cstring("after") - kout printChildren(current) - + print("after", ident) + print(printChildren(current), ident) - while rightOld >= leftOld and rightNew >= leftNew: - updateElement(current, current[leftOld], newNode[leftNew], oldNode[leftOld]) - inc leftNew - inc leftOld - - var isPushBack = (rightNew + 1 == newLength) - var nextNode: Node = nil - if not isPushBack: - nextNode = newNode[rightNew + 1].dom - while leftNew <= rightNew: - var node = vnodeToDom(newNode[leftNew])ды - if isPushBack: - current.appendChild(node) - else: - current.insertBefore(node, nextNode) - inc leftNew + var isKeyed = true + for i in leftNew..rightNew: + if newNode[i].key == -1: + isKeyed = false + break for i in leftOld..rightOld: - current.removeChild(oldNode[i].dom) - detach(oldNode[i]) + if oldNode[i].key == -1: + isKeyed = false + if not isKeyed: + break - kout cstring("----------------") - kout cstring("before") - kout before - kout cstring("finish") - kout printChildren(current) - kout cstring("----------------") - kout cstring("----------------") - + if isKeyed: + if rightNew > leftNew: + # remove redundant old nodes + for i in leftOld..rightOld: + current.removeChild(oldNode[i].dom) + detach(oldNode[i]) + else: + # permute elements using LIS + var positionByKey = newTable[VKey, int]() + var positions = newSeq[int]() + for i in leftOld..rightOld: + positionByKey[oldNode[i].key] = i + for i in leftNew..rightNew: + if positionByKey.hasKey(newNode[i].key): + positions.add positionByKey[newNode[i].key] + + var lis = longestIncreasingSubsequence(positions) + var lisPos = 0 + var isNotRedundant = newSeq[bool](rightOld - leftOld + 1) + for i in leftNew..rightNew: + if lisPos < len(lis): + var index = lis[lisPos] + if oldNode[index].key == newNode[i].key: + isNotRedundant[index - leftOld] = true + updateElement(current, oldNode[index].dom, newNode[i], oldNode[index], ident + 1) + inc lisPos + else: + if positionByKey.hasKey(newNode[i].key): + var oldPos = positionByKey[newNode[i].key] + isNotRedundant[oldPos - leftOld] = true + current.insertBefore(oldNode[oldPos].dom, oldNode[index].dom) + else: + current.insertBefore(vnodeToDom(newNode[i]), oldNode[index].dom) + else: + if positionByKey.hasKey(newNode[i].key): + var oldPos = positionByKey[newNode[i].key] + isNotRedundant[oldPos - leftOld] = true + current.appendChild(oldNode[oldPos].dom) + else: + current.appendChild(vnodeToDom(newNode[i])) + + # remove redundant old nodes + for i in leftOld..rightOld: + if not isNotRedundant[i]: + current.removeChild(oldNode[i].dom) + else: + # simply diff + print($leftOld & " " & $rightOld & " " & $leftNew & " " & $rightNew, ident) + while rightOld >= leftOld and rightNew >= leftNew: + updateElement(current, oldNode[leftOld].dom, newNode[leftNew], oldNode[leftOld], ident + 1) + inc leftNew + inc leftOld + + print("TEMP", ident) + print(printChildren(current), ident) + print($leftOld & " " & $rightOld & " " & $leftNew & " " & $rightNew, ident) + print("other part start", ident) + var isPushBack = (rightNew + 1 == newLength) + var nextNode: Node = nil + if not isPushBack: + print($(rightNew + 1), ident) + nextNode = newNode[rightNew + 1].dom + while leftNew <= rightNew: + var node = vnodeToDom(newNode[leftNew]) + if isPushBack: + current.appendChild(node) + else: + current.insertBefore(node, nextNode) + inc leftNew + print("other part finish", ident) - # var leftNew = left - # var leftOld = left - # while rightOld >= leftOld and rightNew >= leftNew and equalsShallow(oldNode[leftOld], newNode[rightNew]): - # var nextNode: Node = nil - # if rightNew + 1 < newLength: - # nextNode = newNode[rightNew + 1].dom - # if nextNode == nil: - # current.appendChild(oldNode[leftOld].dom) - # else: - # current.insertBefore(oldNode[leftOld].dom, nextNode) - # inc leftOld - # dec rightNew + for i in leftOld..rightOld: + current.removeChild(oldNode[i].dom) + detach(oldNode[i]) - # var lastTarget: Node = nil - # if leftOld <= rightOld: - # lastTarget = oldNode[leftOld].dom - # while rightOld >= leftOld and rightNew >= leftNew and equalsShallow(oldNode[rightOld], newNode[leftNew]): - # current.insertBefore(oldNode[rightOld].dom, lastTarget) - # lastTarget = oldNode[rightOld].dom - # inc leftNew - # dec rightOld - - # if rightOld < leftOld: - # var isPushBack = leftOld < oldLength - # for i in leftNew..rightNew: - # if isPushBack: - # current.appendChild(vnodeToDom(newNode[i])) - # else: - # current.insertBefore(vnodeToDom(newNode[i]), oldNode[leftOld].dom) - # elif rightNew < leftNew: - # for i in leftOld..rightOld: - # current.removeChild(oldNode[i].dom) - # detach(oldNode[i]) - # else: - # while rightOld >= leftOld and rightNew >= leftNew: - # updateElement(current, oldNode[leftOld].dom, newNode[leftNew], oldNode[leftOld]) - # inc leftNew - # inc leftOld - # if rightOld < leftOld: - # while rightNew >= leftNew: - # current.appendChild(vnodeToDom(newNode[leftNew])) - # inc leftNew - # elif rightNew < leftNew: - # while rightOld >= leftOld: - # current.removeChild(oldNode[leftOld].dom) - # detach(oldNode[leftOld]) - # inc leftOld + print("----------------", ident) + print("before", ident) + print(before, ident) + print("finish", ident) + print(printChildren(current), ident) + print("----------------", ident) + print("----------------", ident) when false: var drawTimeout: Timeout diff --git a/tests/diffDomTests.nim b/tests/diffDomTests.nim index 403980a..597f9b8 100644 --- a/tests/diffDomTests.nim +++ b/tests/diffDomTests.nim @@ -147,20 +147,20 @@ proc onload() = var dtTest = 500 var t = dtReset - # timeout = setTimeout(test1, t) - # t += dtTest - # timeout = setTimeout(reset, t) - # 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(test2, t) + t += dtTest + timeout = setTimeout(reset, t) + t += dtReset - # timeout = setTimeout(test3, 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 @@ -172,12 +172,12 @@ proc onload() = timeout = setTimeout(reset, t) t += dtReset - # timeout = setTimeout(test6, 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) + timeout = setTimeout(test7, t) onload() setRenderer createDom From e88f9e4c96bd5e6f21bc9469e116fd35b4639f10 Mon Sep 17 00:00:00 2001 From: Roman Vishnevskii Date: Wed, 10 May 2017 18:51:13 +0700 Subject: [PATCH 20/20] Fix empty LIS --- src/karax.nim | 29 ++++++++++++++++++++++++----- tests/diffDomTests.nim | 8 ++++---- 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/src/karax.nim b/src/karax.nim index e661b78..0e58c4b 100644 --- a/src/karax.nim +++ b/src/karax.nim @@ -1,6 +1,6 @@ ## Karax -- Single page applications for Nim. -import dom, vdom, jstrutils, components, jdict, tables +import dom, vdom, jstrutils, components, jdict export dom.Event @@ -184,6 +184,9 @@ proc print(s: cstring, ident: int) = # kout cstring(result) proc longestIncreasingSubsequence(a: seq[int]): seq[int] = + if len(a) == 0: + return @[] + result.add 0 var parent = newSeq[int](len(a)) for i in 0.. leftNew: # remove redundant old nodes @@ -325,14 +336,22 @@ proc updateElement(parent, current: Node, newNode, oldNode: VNode, ident: int = detach(oldNode[i]) else: # permute elements using LIS - var positionByKey = newTable[VKey, int]() + var positionByKey = newJDict[VKey, int]() var positions = newSeq[int]() for i in leftOld..rightOld: positionByKey[oldNode[i].key] = i for i in leftNew..rightNew: - if positionByKey.hasKey(newNode[i].key): + if positionByKey.contains(newNode[i].key): positions.add positionByKey[newNode[i].key] + #if len(positions) > 0: + # kout cstring("new segment len = " & $(rightNew - leftNew + 1)) + # kout cstring("old segment len = " & $(rightOld - leftOld + 1)) + # var t = "" + # for i in 0..