about summary refs log tree commit diff stats
path: root/src/js
diff options
context:
space:
mode:
authorbptato <nincsnevem662@gmail.com>2024-09-23 19:51:20 +0200
committerbptato <nincsnevem662@gmail.com>2024-09-23 19:58:54 +0200
commitfcd9aa9f9c604ed5d104343542962a26b2acda62 (patch)
tree7b0eea9b63bacc27cdc6471e2b2409b7b5d15c9e /src/js
parent8e8c7f0911f4a20446a83090d722fecaf203f6f3 (diff)
downloadchawan-fcd9aa9f9c604ed5d104343542962a26b2acda62.tar.gz
Replace std/selectors with poll
std/selectors uses OS-specific selector APIs, which sounds good in
theory (faster than poll!), but sucks for portability in practice.
Sure, you can fix portability bugs, but who knows how many there are
on untested platforms... poll is standard, so if it works on one
computer it should work on all other ones. (I hope.)

As a bonus, I rewrote the timeout API for poll, which incidentally
fixes setTimeout across forks. Also, SIGWINCH should now work on all
platforms (as we self-pipe instead of signalfd/kqueue magic).
Diffstat (limited to 'src/js')
-rw-r--r--src/js/timeout.nim113
1 files changed, 73 insertions, 40 deletions
diff --git a/src/js/timeout.nim b/src/js/timeout.nim
index 0213156a..72a68dbc 100644
--- a/src/js/timeout.nim
+++ b/src/js/timeout.nim
@@ -1,5 +1,5 @@
-import std/selectors
-import std/tables
+import std/algorithm
+import std/times
 
 import io/dynstream
 import js/console
@@ -15,59 +15,76 @@ type
 
   TimeoutEntry = ref object
     t: TimeoutType
-    fd: int
+    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: Table[int32, TimeoutEntry]
-    timeoutFds: Table[int, int32]
-    selector: Selector[int] #TODO would be better with void...
+    timeouts: seq[TimeoutEntry]
     jsctx: JSContext
     err: DynStream #TODO shouldn't be needed
-    evalJSFree: proc(src, file: string) #TODO ew
+    evalJSFree: EvalJSFree
+    opaque: RootRef
+    sorted: bool
 
-func newTimeoutState*(selector: Selector[int]; jsctx: JSContext; err: DynStream;
-    evalJSFree: proc(src, file: string)): TimeoutState =
+func newTimeoutState*(jsctx: JSContext; err: DynStream;
+    evalJSFree: EvalJSFree; opaque: RootRef): TimeoutState =
   return TimeoutState(
-    selector: selector,
     jsctx: jsctx,
     err: err,
-    evalJSFree: evalJSFree
+    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) =
-  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)
+  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
 
-#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)
+    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[id] = entry
+  state.timeouts.add(entry)
+  state.sorted = false
   return id
 
-proc runEntry(state: var TimeoutState; entry: TimeoutEntry; name: string) =
+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())
@@ -77,23 +94,39 @@ proc runEntry(state: var TimeoutState; entry: TimeoutEntry; name: string) =
   else:
     var s: string
     if state.jsctx.fromJS(entry.val, s).isSome:
-      state.evalJSFree(s, name)
+      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 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 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.values:
-    state.selector.unregister(entry.fd)
+  for entry in state.timeouts:
     JS_FreeValue(state.jsctx, entry.val)
     for arg in entry.args:
       JS_FreeValue(state.jsctx, arg)
-  state.timeouts.clear()
-  state.timeoutFds.clear()
+  state.timeouts.setLen(0)