const binding = require('./binding')
const Heap = require('./lib/heap')
const TIMEOUT = 1
const IMMEDIATE = 2
const ACTIVE = 0x1
const CLEARED = 0x2
const REFED = 0x4
const REPEAT = 0x8
class Task {
static _id = 0
constructor(scheduler, callback, args) {
this._id = ++Task._id
this._state = ACTIVE | REFED
this._scheduler = scheduler
this._callback = callback
this._args = args
}
ref() {
this._scheduler._ref(this)
return this
}
unref() {
this._scheduler._unref(this)
return this
}
hasRef() {
return (this._state & REFED) !== 0
}
[Symbol.dispose]() {
this._scheduler._clear(this)
}
}
class Timeout extends Task {
constructor(scheduler, delay, expiry, repeat, callback, args) {
super(scheduler, callback, args)
this._index = -1
this._delay = delay
this._expiry = expiry
if (repeat) this._state |= REPEAT
}
get _type() {
return TIMEOUT
}
refresh() {
this._scheduler._refresh(this)
return this
}
}
class Immediate extends Task {
get _type() {
return IMMEDIATE
}
}
class Scheduler {
constructor() {
this._refs = 0
this._timeouts = new Heap()
this._immediates = []
this._handle = binding.init(this, this._ontimeout, this._onimmediate)
const stop = this._stop.bind(this)
const start = this._start.bind(this)
Bare.on('idle', stop).on('resume', start).on('wakeup', start)
}
_acquire() {
if (this._refs++ === 0) binding.ref(this._handle)
}
_release() {
if (--this._refs === 0) binding.unref(this._handle)
}
_timeout(delay, repeat, callback, args = []) {
delay = Math.floor(delay)
if (delay < 1 || delay > Number.MAX_SAFE_INTEGER || Number.isNaN(delay)) {
delay = 1
}
const now = Date.now()
const timeout = new Timeout(this, delay, now + delay, repeat, callback, args)
this._acquire()
const next = this._timeouts.peek()
if (next === undefined || next._expiry > timeout._expiry) {
binding.timeout(this._handle, Math.max(0, timeout._expiry - now))
}
this._timeouts.push(timeout)
return timeout
}
_refresh(timeout) {
timeout._expiry = Date.now() + timeout._delay
if ((timeout._state & ACTIVE) !== 0) {
if ((timeout._state & CLEARED) !== 0) {
timeout._state &= ~CLEARED
if ((timeout._state & REFED) !== 0) this._acquire()
}
this._timeouts.update(timeout)
} else {
timeout._state |= ACTIVE
if ((timeout._state & REFED) !== 0) this._acquire()
this._timeouts.push(timeout)
}
}
_immediate(callback, args = []) {
const immediate = new Immediate(this, callback, args)
this._acquire()
if (this._immediates.length === 0) binding.immediate(this._handle)
this._immediates.push(immediate)
return immediate
}
_clear(task) {
if (typeof task !== 'object' || task === null) return
if ((task._state & CLEARED) !== 0 || (task._state & ACTIVE) === 0) {
return
}
task._state |= CLEARED
if ((task._state & REFED) !== 0) this._release()
if (task._type === TIMEOUT) {
task._expiry = 0
binding.timeout(this._handle, 0)
this._timeouts.update(task)
}
}
_ref(task) {
if ((task._state & REFED) !== 0 || (task._state & ACTIVE) === 0) {
return
}
task._state |= REFED
this._acquire()
}
_unref(task) {
if ((task._state & REFED) === 0 || (task._state & ACTIVE) === 0) {
return
}
task._state &= ~REFED
this._release()
}
_stop() {
binding.stop(this._handle)
}
_start() {
if (this._timeouts.length > 0) binding.timeout(this._handle, 0)
if (this._immediates.length > 0) binding.immediate(this._handle)
}
_ontimeout() {
let caught = false
let err = null
while (this._timeouts.length > 0) {
const now = Date.now()
const timeout = this._timeouts.peek()
if ((timeout._state & CLEARED) !== 0) {
timeout._state &= ~ACTIVE & ~CLEARED
this._timeouts.shift()
continue
}
if (caught || timeout._expiry > now) {
binding.timeout(this._handle, Math.max(0, timeout._expiry - now))
break
}
if ((timeout._state & REPEAT) !== 0) {
timeout._expiry = now + timeout._delay
this._timeouts.update(timeout)
} else {
timeout._state &= ~ACTIVE
if ((timeout._state & REFED) !== 0) this._release()
this._timeouts.shift()
}
try {
Reflect.apply(timeout._callback, null, timeout._args)
} catch (e) {
caught = true
err = e
}
}
if (caught) throw err
}
_onimmediate() {
const immediates = this._immediates
this._immediates = []
let caught = false
let err = null
for (let i = 0, n = immediates.length; i < n; i++) {
const immediate = immediates[i]
if ((immediate._state & CLEARED) !== 0) {
immediate._state &= ~ACTIVE & ~CLEARED
continue
}
if (caught) {
const remaining = immediates.slice(i)
if (this._immediates.length === 0) {
this._immediates = remaining
} else {
this._immediates = remaining.concat(this._immediates)
}
binding.immediate(this._handle)
break
}
immediate._state &= ~ACTIVE
if ((immediate._state & REFED) !== 0) this._release()
try {
Reflect.apply(immediate._callback, null, immediate._args)
} catch (e) {
caught = true
err = e
}
}
if (caught) throw err
}
}
const scheduler = new Scheduler()
exports.setTimeout = function setTimeout(callback, delay, ...args) {
return scheduler._timeout(delay, false, callback, args)
}
exports.clearTimeout = function clearTimeout(timeout) {
scheduler._clear(timeout)
}
exports.setInterval = function setInterval(callback, delay, ...args) {
return scheduler._timeout(delay, true, callback, args)
}
exports.clearInterval = function clearInterval(timeout) {
scheduler._clear(timeout)
}
exports.setImmediate = function setImmediate(callback, ...args) {
return scheduler._immediate(callback, args)
}
exports.clearImmediate = function clearImmediate(immediate) {
scheduler._clear(immediate)
}
exports.promises = require('./promises')