1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
|
import std/algorithm
import std/times
import io/console
import monoucha/fromjs
import monoucha/javascript
import monoucha/jsutils
import types/opt
type
TimeoutType* = enum
ttTimeout = "setTimeout handler"
ttInterval = "setInterval handler"
TimeoutEntry = ref object
t: TimeoutType
id: int32
val: JSValue
args: seq[JSValue]
expires: int64
timeout: int32
dead: bool
EvalJSFree* = proc(opaque: RootRef; src, file: string) {.nimcall.}
TimeoutState* = ref object
timeoutid: int32
sorted: bool
timeouts: seq[TimeoutEntry]
jsctx: JSContext
evalJSFree: EvalJSFree
opaque: RootRef
func newTimeoutState*(jsctx: JSContext; evalJSFree: EvalJSFree;
opaque: RootRef): TimeoutState =
return TimeoutState(
jsctx: jsctx,
evalJSFree: evalJSFree,
opaque: opaque,
sorted: true
)
func empty*(state: TimeoutState): bool =
return state.timeouts.len == 0
proc clearTimeout0(state: var TimeoutState; i: int) =
let entry = state.timeouts[i]
JS_FreeValue(state.jsctx, entry.val)
for arg in entry.args:
JS_FreeValue(state.jsctx, arg)
state.timeouts.del(i)
if state.timeouts.len != i: # only set if we del'd in the middle
state.sorted = false
proc clearTimeout*(state: var TimeoutState; id: int32) =
for entry in state.timeouts:
if entry.id == id:
entry.dead = true
break
proc getUnixMillis*(): int64 =
let now = getTime()
return now.toUnix() * 1000 + now.nanosecond div 1_000_000
proc setTimeout*(state: var TimeoutState; t: TimeoutType; handler: JSValueConst;
timeout: int32; args: openArray[JSValueConst]): int32 =
let id = state.timeoutid
inc state.timeoutid
let entry = TimeoutEntry(
t: t,
id: id,
val: JS_DupValue(state.jsctx, handler),
expires: getUnixMillis() + int64(timeout),
timeout: timeout
)
for arg in args:
entry.args.add(JS_DupValue(state.jsctx, arg))
state.timeouts.add(entry)
state.sorted = false
return id
proc runEntry(state: var TimeoutState; entry: TimeoutEntry; console: Console) =
if JS_IsFunction(state.jsctx, entry.val):
let ret = JS_Call(state.jsctx, entry.val, JS_UNDEFINED,
cint(entry.args.len), entry.args.toJSValueArray())
if JS_IsException(ret):
console.writeException(state.jsctx)
JS_FreeValue(state.jsctx, ret)
else:
var s: string
if state.jsctx.fromJS(entry.val, s).isSome:
state.evalJSFree(state.opaque, s, $entry.t)
# for poll
proc sortAndGetTimeout*(state: var TimeoutState): cint =
if state.timeouts.len == 0:
return -1
if not state.sorted:
state.timeouts.sort(proc(a, b: TimeoutEntry): int =
cmp(a.expires, b.expires), order = Descending)
state.sorted = true
let now = getUnixMillis()
return cint(max(state.timeouts[^1].expires - now, -1))
proc run*(state: var TimeoutState; console: Console): bool =
let now = getUnixMillis()
var found = false
var H = state.timeouts.high
for i in countdown(H, 0):
if state.timeouts[i].expires > now:
break
let entry = state.timeouts[i]
if entry.dead:
continue
state.runEntry(entry, console)
found = true
case entry.t
of ttTimeout:
entry.dead = true
of ttInterval:
entry.expires = now + entry.timeout
state.sorted = false
# we can't just delete timeouts in the above loop, because the JS
# timeout handler may clear them in an arbitrary order
H = state.timeouts.high
for i in countdown(H, 0):
if state.timeouts[i].dead:
state.clearTimeout0(i)
return found
proc clearAll*(state: var TimeoutState) =
for entry in state.timeouts:
JS_FreeValue(state.jsctx, entry.val)
for arg in entry.args:
JS_FreeValue(state.jsctx, arg)
state.timeouts.setLen(0)
|