about summary refs log tree commit diff stats
path: root/src/js/timeout.nim
blob: 0213156a02c9cd3007402fb73ac6dcf01753ec15 (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
import std/selectors
import std/tables

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
    fd: int
    val: JSValue
    args: seq[JSValue]

  TimeoutState* = ref object
    timeoutid: int32
    timeouts: Table[int32, TimeoutEntry]
    timeoutFds: Table[int, int32]
    selector: Selector[int] #TODO would be better with void...
    jsctx: JSContext
    err: DynStream #TODO shouldn't be needed
    evalJSFree: proc(src, file: string) #TODO ew

func newTimeoutState*(selector: Selector[int]; jsctx: JSContext; err: DynStream;
    evalJSFree: proc(src, file: string)): TimeoutState =
  return TimeoutState(
    selector: selector,
    jsctx: jsctx,
    err: err,
    evalJSFree: evalJSFree
  )

func empty*(state: TimeoutState): bool =
  return state.timeouts.len == 0

proc clearTimeout*(state: var TimeoutState; id: int32) =
  if id in state.timeouts:
    let entry = state.timeouts[id]
    state.selector.unregister(entry.fd)
    JS_FreeValue(state.jsctx, entry.val)
    for arg in entry.args:
      JS_FreeValue(state.jsctx, arg)
    state.timeoutFds.del(entry.fd)
    state.timeouts.del(id)

#TODO varargs
proc setTimeout*(state: var TimeoutState; t: TimeoutType; handler: JSValue;
    timeout: int32; args: openArray[JSValue]): int32 =
  let id = state.timeoutid
  inc state.timeoutid
  let fd = state.selector.registerTimer(max(timeout, 1), t == ttTimeout, 0)
  state.timeoutFds[fd] = id
  let entry = TimeoutEntry(
    t: t,
    fd: fd,
    val: JS_DupValue(state.jsctx, handler)
  )
  for arg in args:
    entry.args.add(JS_DupValue(state.jsctx, arg))
  state.timeouts[id] = entry
  return id

proc runEntry(state: var TimeoutState; entry: TimeoutEntry; name: string) =
  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(s, name)

proc runTimeoutFd*(state: var TimeoutState; fd: int): bool =
  if fd notin state.timeoutFds:
    return false
  let id = state.timeoutFds[fd]
  let entry = state.timeouts[id]
  state.runEntry(entry, $entry.t)
  if entry.t == ttTimeout:
    state.clearTimeout(id)
  return true

proc clearAll*(state: var TimeoutState) =
  for entry in state.timeouts.values:
    state.selector.unregister(entry.fd)
    JS_FreeValue(state.jsctx, entry.val)
    for arg in entry.args:
      JS_FreeValue(state.jsctx, arg)
  state.timeouts.clear()
  state.timeoutFds.clear()