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)