first version; wip
This commit is contained in:
parent
bb846644b5
commit
7c08c6bb76
4 changed files with 212 additions and 5 deletions
181
experiments/nextgen.nim
Normal file
181
experiments/nextgen.nim
Normal file
|
|
@ -0,0 +1,181 @@
|
||||||
|
|
||||||
|
import kdom, jdict, jstrutils
|
||||||
|
|
||||||
|
type
|
||||||
|
Message = enum
|
||||||
|
Unchanged
|
||||||
|
Changed
|
||||||
|
Mark
|
||||||
|
Inserted
|
||||||
|
Deleted
|
||||||
|
|
||||||
|
State = object
|
||||||
|
stale: int
|
||||||
|
outdated: bool
|
||||||
|
phantom: bool
|
||||||
|
|
||||||
|
type
|
||||||
|
ReactiveBase* = ref object of RootObj ## everything that is a "reactive"
|
||||||
|
## value derives from that
|
||||||
|
sinks*: seq[proc(msg: Message, pos: int)]
|
||||||
|
Reactive*[T] = ref object of ReactiveBase
|
||||||
|
value*: T
|
||||||
|
|
||||||
|
RString* = Reactive[cstring]
|
||||||
|
# RTime* = Reactive[Time]
|
||||||
|
RInt* = Reactive[int]
|
||||||
|
|
||||||
|
RBool* = Reactive[bool]
|
||||||
|
|
||||||
|
RSeq*[T] = ref object of ReactiveBase
|
||||||
|
s: seq[T]
|
||||||
|
L: RInt
|
||||||
|
|
||||||
|
proc addSink[T](x: Reactive[T]; sink: proc(msg: Message; pos: int)) =
|
||||||
|
x.sinks.add sink
|
||||||
|
|
||||||
|
proc broadcast(x: ReactiveBase, msg: Message; pos = 0) =
|
||||||
|
for s in x.sinks: s(msg, pos)
|
||||||
|
|
||||||
|
var toTrack: proc (msg: Message; pos: int) = nil
|
||||||
|
|
||||||
|
proc now[T](x: Reactive[T]): T =
|
||||||
|
if toTrack != nil:
|
||||||
|
x.addSink toTrack
|
||||||
|
result = x.value
|
||||||
|
|
||||||
|
proc `:=`[T](x: Reactive[T], f: proc(): T) =
|
||||||
|
var state: State
|
||||||
|
|
||||||
|
toTrack = proc(msg: Message; pos: int) =
|
||||||
|
if not state.phantom:
|
||||||
|
case msg.kind:
|
||||||
|
of Changed, Unchanged:
|
||||||
|
state.outdated = state.outdated or (msg.kind == Changed)
|
||||||
|
dec state.stale
|
||||||
|
if state.stale == 0:
|
||||||
|
if state.outdated:
|
||||||
|
var t = f()
|
||||||
|
let thisChanged = if x.value != t: Changed else: Unchanged
|
||||||
|
x.value = t
|
||||||
|
x.broadcast(thisChanged, pos)
|
||||||
|
state.outdated = false
|
||||||
|
else:
|
||||||
|
x.broadcast(Unchanged, pos)
|
||||||
|
of Mark:
|
||||||
|
if state.stale == 0:
|
||||||
|
x.broadcast(msg, pos)
|
||||||
|
inc state.stale
|
||||||
|
of Inserted, Deleted:
|
||||||
|
dec state.stale
|
||||||
|
if state.stale == 0:
|
||||||
|
x.broadcast(msg, pos)
|
||||||
|
#state.phantom = true
|
||||||
|
|
||||||
|
x.value = f()
|
||||||
|
toTrack = nil
|
||||||
|
|
||||||
|
proc `<-`[T](x: Reactive[T], val: T) =
|
||||||
|
if x.value != val:
|
||||||
|
x.value = val
|
||||||
|
x.broadcast(Mark)
|
||||||
|
x.broadcast(Changed)
|
||||||
|
|
||||||
|
proc changed*(x: ReactiveBase) =
|
||||||
|
x.broadcast(Mark)
|
||||||
|
x.broadcast(Changed)
|
||||||
|
|
||||||
|
proc subscribe[T](x: Reactive[T], f: proc(x: T)) =
|
||||||
|
let reactor = proc (msg: Message, pos: int) =
|
||||||
|
case msg:
|
||||||
|
of Mark: discard
|
||||||
|
of Changed, Unchanged: f(x.value)
|
||||||
|
of Inserted, Deleted: discard
|
||||||
|
x.addSink reactor
|
||||||
|
|
||||||
|
template lift1(op: untyped) =
|
||||||
|
proc op[T](a: Reactive[T]): (proc(): T) =
|
||||||
|
result = proc(): T = op(a.now)
|
||||||
|
|
||||||
|
template lift2(op: untyped) =
|
||||||
|
proc op[T](a, b: Reactive[T]): (proc(): T) =
|
||||||
|
result = proc(): T = op(a.now, b.now)
|
||||||
|
|
||||||
|
lift1 `not`
|
||||||
|
lift2 `&`
|
||||||
|
|
||||||
|
proc newReactive[T](x: T): Reactive[T] =
|
||||||
|
result = Reactive[T](value: x)
|
||||||
|
|
||||||
|
proc rstr(x: cstring): RString =
|
||||||
|
result = RString(value: x)
|
||||||
|
|
||||||
|
proc newRSeq*[T](len: int): RSeq[T] =
|
||||||
|
result = RSeq[T](s: newSeq[T](len), L: newReactive[int](0))
|
||||||
|
|
||||||
|
proc newRSeq*[T](data: seq[T]): RSeq[T] =
|
||||||
|
result = RSeq[T](s: newSeq[T](data.len), L: newReactive[int](0))
|
||||||
|
for i in 0..high(data):
|
||||||
|
result.s[i] = data[i]
|
||||||
|
|
||||||
|
proc `[]=`[T](x: RSeq[T]; index: int; v: T) =
|
||||||
|
x.s[index] = v
|
||||||
|
|
||||||
|
proc `[]`[T](x: RSeq[T]; index: int): T = x.s[index]
|
||||||
|
|
||||||
|
proc insert*[T](x: RSeq[T]; y: T; position = 0) =
|
||||||
|
x.s.insert(y, position)
|
||||||
|
x.broadcast(Mark)
|
||||||
|
x.broadcast(Inserted, position)
|
||||||
|
|
||||||
|
proc delete*[T](x: RSeq[T]; position = 0) =
|
||||||
|
x.s.delete(position)
|
||||||
|
x.broadcast(Mark)
|
||||||
|
x.broadcast(Deleted, position)
|
||||||
|
|
||||||
|
proc map[T, U](x: RSeq[T], f: proc(x: T): U): RSeq[U] =
|
||||||
|
let xl = x.L.value
|
||||||
|
let res = newRSeq[U](xl)
|
||||||
|
for i in 0..<xl:
|
||||||
|
res.s[i] := proc(): U = f(x[i].now)
|
||||||
|
|
||||||
|
let reactor = proc (msg: Message, pos: int) =
|
||||||
|
case msg:
|
||||||
|
of Mark, Changed, Unchanged: discard "nothing to do"
|
||||||
|
of Inserted:
|
||||||
|
res.insert(f(x[pos]), pos)
|
||||||
|
of Deleted:
|
||||||
|
res.delete(pos)
|
||||||
|
x.addSink reactor
|
||||||
|
result = res
|
||||||
|
|
||||||
|
type
|
||||||
|
User = ref object
|
||||||
|
firstname, lastname: cstring
|
||||||
|
|
||||||
|
var gu = newReactive(User(firstname: "Some", lastname: "Body"))
|
||||||
|
|
||||||
|
proc renderUser(u: Reactive[User]): Node =
|
||||||
|
result = document.createElement("button")
|
||||||
|
result.appendChild document.createTextNode(u.now.firstname & " " & u.now.lastname)
|
||||||
|
result.addEventListener "click", proc (ev: Event) =
|
||||||
|
gu <- User(firstname: "Another", lastname: "Guy")
|
||||||
|
|
||||||
|
proc replaceById(id: cstring; newTree: Node) =
|
||||||
|
let x = document.getElementById(id)
|
||||||
|
x.parentNode.replaceChild(newTree, x)
|
||||||
|
newTree.id = id
|
||||||
|
|
||||||
|
template track(r: ReactiveBase; a, b: Node) =
|
||||||
|
r.addSink proc(m: Message; pos: int) =
|
||||||
|
if m == Changed:
|
||||||
|
a.parentNode.replaceChild(b, a)
|
||||||
|
|
||||||
|
proc main(): Node =
|
||||||
|
result = renderUser(gu)
|
||||||
|
track gu, result, renderUser(gu)
|
||||||
|
|
||||||
|
proc init(ev: Event) =
|
||||||
|
replaceById("ROOT", main())
|
||||||
|
|
||||||
|
window.onload = init
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
--path: "../src"
|
|
||||||
|
|
@ -37,6 +37,7 @@ type
|
||||||
patchLenV: int
|
patchLenV: int
|
||||||
runCount: int
|
runCount: int
|
||||||
components: seq[ComponentPair]
|
components: seq[ComponentPair]
|
||||||
|
surpressRedraws*: bool
|
||||||
when defined(stats):
|
when defined(stats):
|
||||||
recursion: int
|
recursion: int
|
||||||
|
|
||||||
|
|
@ -117,7 +118,7 @@ proc applyEvents(n: VNode; kxi: KaraxInstance) =
|
||||||
for i in 0..<len(n.events):
|
for i in 0..<len(n.events):
|
||||||
n.events[i][2] = wrapEvent(dest, n, n.events[i][0], n.events[i][1])
|
n.events[i][2] = wrapEvent(dest, n, n.events[i][0], n.events[i][1])
|
||||||
|
|
||||||
proc vnodeToDom(n: VNode; kxi: KaraxInstance): Node =
|
proc vnodeToDom*(n: VNode; kxi: KaraxInstance): Node =
|
||||||
if n.kind == VNodeKind.text:
|
if n.kind == VNodeKind.text:
|
||||||
result = document.createTextNode(n.text)
|
result = document.createTextNode(n.text)
|
||||||
attach n
|
attach n
|
||||||
|
|
@ -134,6 +135,7 @@ proc vnodeToDom(n: VNode; kxi: KaraxInstance): Node =
|
||||||
return result
|
return result
|
||||||
elif n.kind == VNodeKind.component:
|
elif n.kind == VNodeKind.component:
|
||||||
let x = VComponent(n)
|
let x = VComponent(n)
|
||||||
|
if x.realDomImpl != nil: return x.realDomImpl(x)
|
||||||
if x.onAttachImpl != nil: x.onAttachImpl(x)
|
if x.onAttachImpl != nil: x.onAttachImpl(x)
|
||||||
assert x.renderImpl != nil
|
assert x.renderImpl != nil
|
||||||
if x.expanded == nil:
|
if x.expanded == nil:
|
||||||
|
|
@ -465,7 +467,11 @@ proc applyComponents(kxi: KaraxInstance) =
|
||||||
let newNode = kxi.components[i].newNode
|
let newNode = kxi.components[i].newNode
|
||||||
when defined(karaxDebug):
|
when defined(karaxDebug):
|
||||||
echo "Processing component ", newNode.text, " changed impl set ", x.changedImpl != nil
|
echo "Processing component ", newNode.text, " changed impl set ", x.changedImpl != nil
|
||||||
if x.changedImpl != nil and x.changedImpl(x, newNode):
|
if x.realDomImpl != nil:
|
||||||
|
let current = kxi.components[i].current
|
||||||
|
let parent = kxi.components[i].parent
|
||||||
|
kxi.addPatch(pkReplace, parent, current, x)
|
||||||
|
elif x.changedImpl != nil and x.changedImpl(x, newNode):
|
||||||
when defined(karaxDebug):
|
when defined(karaxDebug):
|
||||||
echo "Component ", newNode.text, " did change"
|
echo "Component ", newNode.text, " did change"
|
||||||
let current = kxi.components[i].current
|
let current = kxi.components[i].current
|
||||||
|
|
@ -507,6 +513,14 @@ when defined(stats):
|
||||||
result = m + 1
|
result = m + 1
|
||||||
inc total
|
inc total
|
||||||
|
|
||||||
|
proc runDiff*(kxi: KaraxInstance; oldNode, newNode: VNode) =
|
||||||
|
let olddom = oldNode.dom
|
||||||
|
discard diff(newNode, oldNode, nil, olddom, kxi)
|
||||||
|
applyComponents(kxi)
|
||||||
|
applyPatch(kxi)
|
||||||
|
if kxi.currentTree == oldNode:
|
||||||
|
kxi.currentTree = newNode
|
||||||
|
|
||||||
proc dodraw(kxi: KaraxInstance) =
|
proc dodraw(kxi: KaraxInstance) =
|
||||||
if kxi.renderer.isNil: return
|
if kxi.renderer.isNil: return
|
||||||
let newtree = kxi.renderer()
|
let newtree = kxi.renderer()
|
||||||
|
|
@ -576,6 +590,18 @@ proc setRenderer*(renderer: proc (): VNode, root: cstring = "ROOT",
|
||||||
kxi = result
|
kxi = result
|
||||||
window.onload = init
|
window.onload = init
|
||||||
|
|
||||||
|
proc setInitializer*(renderer: proc (): VNode, root: cstring = "ROOT",
|
||||||
|
clientPostRenderCallback: proc () = nil): KaraxInstance {.discardable.} =
|
||||||
|
## Setup Karax. Usually the return value can be ignored.
|
||||||
|
result = KaraxInstance(rootId: root, renderer: renderer,
|
||||||
|
postRenderCallback: clientPostRenderCallback,
|
||||||
|
patches: newSeq[Patch](60),
|
||||||
|
patchesV: newSeq[PatchV](30),
|
||||||
|
components: @[],
|
||||||
|
surpressRedraws: true)
|
||||||
|
kxi = result
|
||||||
|
window.onload = init
|
||||||
|
|
||||||
proc addEventHandler*(n: VNode; k: EventKind; action: EventHandler;
|
proc addEventHandler*(n: VNode; k: EventKind; action: EventHandler;
|
||||||
kxi: KaraxInstance = kxi) =
|
kxi: KaraxInstance = kxi) =
|
||||||
## Implements the foundation of Karax's event management.
|
## Implements the foundation of Karax's event management.
|
||||||
|
|
@ -585,7 +611,7 @@ proc addEventHandler*(n: VNode; k: EventKind; action: EventHandler;
|
||||||
## a ``redraw``.
|
## a ``redraw``.
|
||||||
proc wrapper(ev: Event; n: VNode) =
|
proc wrapper(ev: Event; n: VNode) =
|
||||||
action(ev, n)
|
action(ev, n)
|
||||||
redraw(kxi)
|
if not kxi.surpressRedraws: redraw(kxi)
|
||||||
addEventListener(n, k, wrapper)
|
addEventListener(n, k, wrapper)
|
||||||
|
|
||||||
proc setOnHashChange*(action: proc (hashPart: cstring)) =
|
proc setOnHashChange*(action: proc (hashPart: cstring)) =
|
||||||
|
|
|
||||||
|
|
@ -133,6 +133,7 @@ type
|
||||||
updatedImpl*: proc(self, newInstance: VComponent)
|
updatedImpl*: proc(self, newInstance: VComponent)
|
||||||
onAttachImpl*: proc(self: VComponent)
|
onAttachImpl*: proc(self: VComponent)
|
||||||
onDetachImpl*: proc(self: VComponent)
|
onDetachImpl*: proc(self: VComponent)
|
||||||
|
realDomImpl*: proc(self: VComponent): kdom.Node
|
||||||
version*: int ## Update this to trigger a redraw by karax. Usually you
|
version*: int ## Update this to trigger a redraw by karax. Usually you
|
||||||
## should call 'markDirty' instead which is an alias for
|
## should call 'markDirty' instead which is an alias for
|
||||||
## 'inc version'.
|
## 'inc version'.
|
||||||
|
|
@ -185,7 +186,7 @@ proc getDebugId(): int =
|
||||||
gid
|
gid
|
||||||
|
|
||||||
template newComponent*[T](t: typeDesc[T];
|
template newComponent*[T](t: typeDesc[T];
|
||||||
render: (proc(self: VComponent): VNode) not nil,
|
render: (proc(self: VComponent): VNode) = nil,
|
||||||
onAttach: proc(self: VComponent) = nil,
|
onAttach: proc(self: VComponent) = nil,
|
||||||
onDetach: proc(self: VComponent) = nil,
|
onDetach: proc(self: VComponent) = nil,
|
||||||
changed: (proc(self, newInstance: VComponent): bool) = defaultChangedImpl,
|
changed: (proc(self, newInstance: VComponent): bool) = defaultChangedImpl,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue