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
|
import std/algorithm
import std/times
import io/dynstream
import js/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
EvalJSFree* = proc(opaque: RootRef; src, file: string) {.nimcall.}
TimeoutState* = ref object
timeoutid: int32
timeouts: seq[TimeoutEntry]
jsctx: JSContext
err: DynStream #TODO shouldn't be needed
evalJSFree: EvalJSFree
opaque: RootRef
sorted: bool
func newTimeoutState*(jsctx: JSContext; err: DynStream;
evalJSFree: EvalJSFree; opaque: RootRef): TimeoutState =
return TimeoutState(
jsctx: jsctx,
err: err,
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) =
var j = -1
for i in 0 ..< state.timeouts.len:
if state.timeouts[i].id == id:
j = i
break
if j != -1:
state.clearTimeout0(j)
proc getUnixMillis(): int64 =
let now = getTime()
return now.toUnix() * 1000 + now.nanosecond div 1_000_000
proc setTimeout*(state: var TimeoutState; t: TimeoutType; handler: JSValue;
timeout: int32; args: openArray[JSValue]): 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) =
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):
state.jsctx.writeException(state.err)
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): bool =
let H = state.timeouts.high
let now = getUnixMillis()
var found = false
for i in countdown(H, 0):
if state.timeouts[i].expires > now:
break
let entry = state.timeouts[i]
state.runEntry(entry)
found = true
case entry.t
of ttTimeout: state.clearTimeout0(i)
of ttInterval:
entry.expires = now + entry.timeout
state.sorted = false
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)
|