added .track macro
This commit is contained in:
parent
bfb9985754
commit
46b1259498
2 changed files with 193 additions and 159 deletions
|
|
@ -1,173 +1,25 @@
|
||||||
|
|
||||||
import vdom, kdom, vstyles, karax, karaxdsl, jdict, jstrutils
|
import vdom, kdom, vstyles, karax, karaxdsl, jdict, jstrutils, reactive
|
||||||
|
|
||||||
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
|
type
|
||||||
User = ref object
|
User = ref object
|
||||||
firstname, lastname: cstring
|
firstname, lastname: cstring
|
||||||
|
|
||||||
var gu = newReactive(User(firstname: "Some", lastname: "Body"))
|
var gu = @[newReactive(User(firstname: "Some", lastname: "Body")),
|
||||||
|
newReactive(User(firstname: "Some", lastname: "One")),
|
||||||
|
newReactive(User(firstname: "Some", lastname: "Two"))]
|
||||||
|
var clicks = 0
|
||||||
|
|
||||||
proc renderUser(u: Reactive[User]): VNode =
|
proc renderUser(u: Reactive[User]): VNode {.track.} =
|
||||||
result = buildHtml(button):
|
result = buildHtml(button):
|
||||||
text u.now.firstname & " " & u.now.lastname
|
text u.now.firstname & " " & u.now.lastname
|
||||||
proc onclick(ev: Event; n: VNode) =
|
proc onclick(ev: Event; n: VNode) =
|
||||||
gu <- User(firstname: "Another", lastname: "Guy")
|
inc clicks
|
||||||
|
u <- User(firstname: "Another", lastname: &clicks)
|
||||||
template track(r: ReactiveBase; a, b: VNode) =
|
|
||||||
r.addSink proc(m: Message; pos: int) =
|
|
||||||
if m == Changed:
|
|
||||||
runDiff(kxi, a, b)
|
|
||||||
|
|
||||||
proc main(): VNode =
|
proc main(): VNode =
|
||||||
result = renderUser(gu)
|
result = buildHtml(tdiv):
|
||||||
track gu, result, renderUser(gu)
|
for i in 0..high(gu):
|
||||||
|
renderUser(gu[i])
|
||||||
|
|
||||||
setInitializer(main)
|
setInitializer(main)
|
||||||
|
|
|
||||||
182
src/reactive.nim
Normal file
182
src/reactive.nim
Normal file
|
|
@ -0,0 +1,182 @@
|
||||||
|
|
||||||
|
import jdict
|
||||||
|
|
||||||
|
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)]
|
||||||
|
dups: JDict[cstring, bool] # prevent duplicate registrations
|
||||||
|
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 addSink(x: ReactiveBase; key: cstring; sink: proc(msg: Message; pos: int)) =
|
||||||
|
if x.dups == nil: x.dups = newJDict[cstring, bool]()
|
||||||
|
if not x.dups.contains(key):
|
||||||
|
x.dups[key] = true
|
||||||
|
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
|
||||||
|
|
||||||
|
template wrapObserver(f: untyped) =
|
||||||
|
var state: State
|
||||||
|
proc helper(msg: Message; pos: int) =
|
||||||
|
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
|
||||||
|
helper
|
||||||
|
|
||||||
|
proc `:=`[T](x: Reactive[T], f: proc(): T) =
|
||||||
|
toTrack = wrapObserver(f())
|
||||||
|
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
|
||||||
|
|
||||||
|
import macros
|
||||||
|
|
||||||
|
template trackImpl(r: ReactiveBase; key: cstring; a, b: untyped) =
|
||||||
|
when r is Reactive:
|
||||||
|
addSink r, key, proc(m: Message; pos: int) =
|
||||||
|
if m == Changed:
|
||||||
|
runDiff(kxi, a, b)
|
||||||
|
|
||||||
|
macro track*(procDef: untyped): untyped =
|
||||||
|
let params = params(procDef)
|
||||||
|
let key = lineInfo(procDef)
|
||||||
|
var call = newCall(procDef.name)
|
||||||
|
var trackings = newStmtList()
|
||||||
|
for b in procDef.body: trackings.add b
|
||||||
|
for j in 1..<params.len:
|
||||||
|
let x = params[j]
|
||||||
|
expectKind x, nnkIdentDefs
|
||||||
|
for i in 0..x.len-3:
|
||||||
|
let param = x[i]
|
||||||
|
call.add param
|
||||||
|
trackings.add getAst(trackImpl(param, key, ident"result", call))
|
||||||
|
|
||||||
|
result = copyNimTree(procDef)
|
||||||
|
result.body = trackings
|
||||||
Loading…
Add table
Add a link
Reference in a new issue