about summary refs log tree commit diff stats
path: root/src/js/timeout.nim
blob: 72a68dbcb6b3c5bfc55455dee4f5b06e1312596a (plain) (blame)
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)