Compare commits

...
Sign in to create a new pull request.

19 commits

Author SHA1 Message Date
Andreas Rumpf
9f80fcfc2d bugfixes 2017-05-04 09:45:32 +02:00
Andreas Rumpf
8195ecf61b added track field 2017-05-04 09:41:11 +02:00
Roman Vishnevskii
e63a7dbb21 Delete useless comment 2017-05-03 14:48:36 +07:00
Roman Vishnevskii
cd78f5085f Simplify updateElement 2017-05-02 19:50:35 +07:00
Roman Vishnevskii
4c582d7c06 Avoid using node.childNodes 2017-05-02 19:46:20 +07:00
Andreas Rumpf
d3e1d28c89 optimized VDOM implementation for uibench 2017-05-01 09:58:10 +02:00
Andreas Rumpf
91679f8ae6 made it shorter again 2017-04-27 18:23:29 +02:00
Andreas Rumpf
8bdaab3f82 uibenchmark works 2017-04-27 17:57:04 +02:00
Andreas Rumpf
ebccc4e9a1 simplifications 2017-04-27 17:39:54 +02:00
Andreas Rumpf
ed70a0cbfc Merge branch 'uibench' of github.com:pragmagic/karax into uibench 2017-04-27 17:27:22 +02:00
Andreas Rumpf
393c336f90 simplifications 2017-04-27 17:26:01 +02:00
Roman Vishnevskii
e434517dd1 Uibench final fix 2017-04-27 22:22:18 +07:00
Roman Vishnevskii
2b9880a9a2 Uibench fix border-radius 2017-04-27 21:58:47 +07:00
Roman Vishnevskii
f5e9db55c3 Change className to class 2017-04-27 20:57:33 +07:00
Roman Vishnevskii
a304322286 Uibench fix td 2017-04-27 20:49:54 +07:00
Andreas Rumpf
443b24de3e more fixes 2017-04-27 14:16:34 +02:00
Andreas Rumpf
41df9b769f more progress 2017-04-27 14:05:11 +02:00
Andreas Rumpf
1ad978804c fixes 2017-04-27 13:47:00 +02:00
Roman Vishnevskii
23fc331626 Add uibench 2017-04-27 18:13:25 +07:00
6 changed files with 211 additions and 57 deletions

View file

@ -135,6 +135,8 @@ proc equalsShallow(a, b: VNode): bool =
if not equalsShallow(a[i], b[i]): return false if not equalsShallow(a[i], b[i]): return false
if not sameAttrs(a, b): return false if not sameAttrs(a, b): return false
if a.class != b.class: return false if a.class != b.class: return false
if a.track != 0 and b.track != 0:
if a.track != b.track: return false
# XXX test event listeners here? # XXX test event listeners here?
return true return true
@ -165,6 +167,7 @@ proc updateDirtyElements(parent, current: Node, newNode: VNode) =
#if dirtyCount <= 0: return #if dirtyCount <= 0: return
proc updateElement(parent, current: Node, newNode, oldNode: VNode) = proc updateElement(parent, current: Node, newNode, oldNode: VNode) =
newNode.dom = oldNode.dom
if not equalsShallow(newNode, oldNode): if not equalsShallow(newNode, oldNode):
detach(oldNode) detach(oldNode)
let n = vnodeToDom(newNode) let n = vnodeToDom(newNode)
@ -175,7 +178,6 @@ proc updateElement(parent, current: Node, newNode, oldNode: VNode) =
elif newNode.kind != VNodeKind.text: elif newNode.kind != VNodeKind.text:
let newLength = newNode.len let newLength = newNode.len
var oldLength = oldNode.len var oldLength = oldNode.len
let minLength = min(newLength, oldLength)
assert oldNode.kind == newNode.kind assert oldNode.kind == newNode.kind
when defined(simpleDiff): when defined(simpleDiff):
for i in 0..min(newLength, oldLength)-1: for i in 0..min(newLength, oldLength)-1:
@ -188,36 +190,46 @@ proc updateElement(parent, current: Node, newNode, oldNode: VNode) =
detach(oldNode[i]) detach(oldNode[i])
current.removeChild(current.lastChild) current.removeChild(current.lastChild)
else: else:
var commonPrefix = 0 var left = 0
while commonPrefix < minLength and var rightNew = newLength - 1
equalsTree(newNode[commonPrefix], oldNode[commonPrefix]): var rightOld = oldLength - 1
inc commonPrefix
var oldPos = oldLength - 1 while left <= min(rightNew, rightOld):
var newPos = newLength - 1 var minRight = min(rightOld, rightNew)
while oldPos >= commonPrefix and newPos >= commonPrefix and
equalsTree(newNode[newPos], oldNode[oldPos]):
dec oldPos
dec newPos
var pos = min(oldPos, newPos) + 1 while left <= minRight and
for i in commonPrefix..pos-1: equalsTree(newNode[left], oldNode[left]):
updateElement(current, current.childNodes[i], newNode[i], oldNode[i]) newNode[left] = oldNode[left]
inc left
var nextChildPos = oldPos + 1 while rightOld > left and rightNew > left and
while pos <= newPos: equalsTree(newNode[rightNew], oldNode[rightOld]):
if nextChildPos == oldLength: newNode[rightNew] = oldNode[rightOld]
current.appendChild(vnodeToDom(newNode[pos])) dec rightOld
else: dec rightNew
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: minRight = min(rightOld, rightNew)
detach(oldNode[i]) if left <= minRight:
current.removeChild(current.childNodes[pos]) updateElement(current, oldNode[left].dom, newNode[left], oldNode[left])
inc left
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
for i in left..rightOld:
current.removeChild(oldNode[i].dom)
detach(oldNode[i])
when false: when false:
var drawTimeout: Timeout var drawTimeout: Timeout
@ -256,6 +268,8 @@ proc redraw*() =
else: else:
dodraw() dodraw()
proc redrawForce*() = dodraw()
proc init(ev: Event) = proc init(ev: Event) =
reqFrame(dodraw) reqFrame(dodraw)
@ -263,6 +277,12 @@ proc setRenderer*(renderer: proc (): VNode) =
dorender = renderer dorender = renderer
window.onload = init window.onload = init
proc setRendererOnly*(renderer: proc (): VNode) =
dorender = renderer
proc setOnloadOnly*() =
window.onload = init
proc addEventHandler*(n: VNode; k: EventKind; action: EventHandler) = proc addEventHandler*(n: VNode; k: EventKind; action: EventHandler) =
## Implements the foundation of Karax's event management. ## Implements the foundation of Karax's event management.
## Karax DSL transforms ``tag(onEvent = handler)`` to ## Karax DSL transforms ``tag(onEvent = handler)`` to

View file

@ -4,7 +4,7 @@ from strutils import startsWith, toLowerAscii
const const
StmtContext = ["kout", "inc", "echo", "dec", "!"] StmtContext = ["kout", "inc", "echo", "dec", "!"]
SpecialAttrs = ["id", "class", "value", "key"] SpecialAttrs = ["id", "class", "value", "key", "track"]
proc getName(n: NimNode): string = proc getName(n: NimNode): string =
case n.kind case n.kind

View file

@ -106,8 +106,10 @@ type
# even index: key, odd index: value; done this way for memory efficiency: # even index: key, odd index: value; done this way for memory efficiency:
attrs: seq[cstring] attrs: seq[cstring]
events*: seq[(EventKind, EventHandler)] events*: seq[(EventKind, EventHandler)]
hash*: Hash when false:
validHash*: bool hash*: Hash
validHash*: bool
track*: int
dom*: Node ## the attached real DOM node. Can be 'nil' if the virtual node dom*: Node ## the attached real DOM node. Can be 'nil' if the virtual node
## is not part of the virtual DOM anymore. ## is not part of the virtual DOM anymore.
@ -130,6 +132,9 @@ proc dthunk*(name: cstring; args: varargs[VNode, vn]): VNode =
VNode(kind: VNodeKind.dthunk, text: name, key: -1, kids: @args) VNode(kind: VNodeKind.dthunk, text: name, key: -1, kids: @args)
proc eq*(a, b: VNode): bool = proc eq*(a, b: VNode): bool =
if a.track != 0 and b.track != 0:
# do not recurse here:
return a.track == b.track
if a.kind != b.kind: return false if a.kind != b.kind: return false
if a.id != b.id: return false if a.id != b.id: return false
if a.class != b.class: return false if a.class != b.class: return false
@ -146,7 +151,11 @@ proc eq*(a, b: VNode): bool =
proc setAttr*(n: VNode; key: cstring; val: cstring = "") = proc setAttr*(n: VNode; key: cstring; val: cstring = "") =
if n.attrs.isNil: if n.attrs.isNil:
n.attrs = @[key, val] when true:
let x = @[key, val]
shallowCopy n.attrs, x
else:
n.attrs = @[key, val]
else: else:
for i in countup(0, n.attrs.len-2, 2): for i in countup(0, n.attrs.len-2, 2):
if n.attrs[i] == key: if n.attrs[i] == key:
@ -161,6 +170,7 @@ proc getAttr*(n: VNode; key: cstring): cstring =
proc len*(x: VNode): int = x.kids.len proc len*(x: VNode): int = x.kids.len
proc `[]`*(x: VNode; idx: int): VNode = x.kids[idx] 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 add*(parent, kid: VNode) = parent.kids.add kid
proc newVNode*(kind: VNodeKind): VNode = VNode(kind: kind, key: -1) proc newVNode*(kind: VNodeKind): VNode = VNode(kind: kind, key: -1)
@ -216,32 +226,33 @@ proc toString*(n: VNode; result: var string; indent: int) =
for i in 1..indent: result.add ' ' for i in 1..indent: result.add ' '
result.add "\L</" & $n.kind & ">" result.add "\L</" & $n.kind & ">"
proc calcHash*(n: VNode) = when false:
if n.validHash: return proc calcHash*(n: VNode) =
n.validHash = true if n.validHash: return
var h: Hash = ord n.kind n.validHash = true
if n.id != nil: var h: Hash = ord n.kind
h &= "id" if n.id != nil:
h &= n.id h &= "id"
if n.class != nil: h &= n.id
h &= "class" if n.class != nil:
h &= n.class h &= "class"
if n.key >= 0: h &= n.class
h &= "k" if n.key >= 0:
h &= n.key h &= "k"
for k, v in attrs(n): h &= n.key
h &= " " for k, v in attrs(n):
h &= k h &= " "
h &= "=" h &= k
h &= v h &= "="
if n.kind == VNodeKind.text or n.text != nil: h &= v
h &= "t" if n.kind == VNodeKind.text or n.text != nil:
h &= n.text h &= "t"
else: h &= n.text
for child in items(n): else:
calcHash(child) for child in items(n):
h &= child.hash calcHash(child)
n.hash = h h &= child.hash
n.hash = h
proc `$`*(n: VNode): cstring = proc `$`*(n: VNode): cstring =
var res = "" var res = ""

16
uibench/index.html Normal file
View file

@ -0,0 +1,16 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link href="https://localvoid.github.io/uibench-base/0.1.0/styles.css" rel="stylesheet" />
<title>UI Benchmark: React</title>
</head>
<body>
<div id="ROOT"></div>
<script src="https://localvoid.github.io/uibench-base/0.1.0/uibench.js"></script>
<script src="nimcache/nim_uibench.js" type="text/javascript"></script>
</body>
</html>

106
uibench/nim_uibench.nim Normal file
View file

@ -0,0 +1,106 @@
import dom, vdom, karax, karaxdsl, jdict, jstrutils, kajax
type
HomeState {.importc.} = ref object
TableItemState {.importc.} = ref object
id: int
active: bool
props: seq[cstring]
TableState {.importc.} = ref object
items: seq[TableItemState]
AnimBoxState {.importc.} = ref object
id: int
time: int
AnimState {.importc.} = ref object
items: seq[AnimBoxState]
TreeNodeState {.importc.} = ref object
id: int
container: bool
children: seq[TreeNodeState]
TreeState {.importc.} = ref object
root: TreeNodeState
AppState {.importc.} = ref object
location: cstring
home: HomeState
table: TableState
anim: AnimState
tree: TreeState
var appState: AppState
proc createTableCell(id: cstring): VNode =
proc tableCellClick(ev: karax.Event; n: VNode) =
kout "Clicked" & id
result = buildHtml(td(class="TableCell", onclick=tableCellClick)):
text id
proc createTableRow(item: TableItemState): VNode =
let className = if item.active: cstring"TableRow active" else: cstring"TableRow"
result = buildHtml(tr(class=className, `data-id` = &item.id, track = item.id)):
createTableCell("#" & &item.id)
for i in 0..<len(item.props):
createTableCell(item.props[i])
proc tableCreateVNode(data: TableState): VNode =
result = buildHtml(table(class="Table")):
tbody:
for child in data.items:
createTableRow(child)
proc createAnimBox(item: AnimBoxState): VNode =
let time = item.time
let t10 = time mod 10
let color = float(t10) / 10
let divStyles = cstring"border-radius: " & &t10 &
"px; background: rgba(0,0,0," & cstring($color) & ")"
result = flatHtml(tdiv(class="AnimBox", `data-id` = &item.id, track = item.id,
style=divStyles))
proc animCreateVNode(data: AnimState): VNode =
result = buildHtml(tdiv(class="Anim")):
for child in data.items:
createAnimBox(child)
proc createTreeNode(data: TreeNodeState): VNode =
result = buildHtml(ul(class="TreeNode")):
for n in data.children:
if n.container:
createTreeNode(n)
else:
li(class="TreeLeaf"):
text &n.id
proc treeCreateVNode(data: TreeState): VNode =
result = buildHtml(tdiv(class="Tree")):
createTreeNode(data.root)
proc update(): VNode =
assert appState != nil
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)
result = buildHtml():
tdiv(class="Main"):
children
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"<pre>" & myToJson(samples) & cstring"</pre>"
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
run(a, b)

1
uibench/nim_uibench.nims Normal file
View file

@ -0,0 +1 @@
--path: "../src"