import { SerialTimer } from 'virtjs/devices/timers/SerialTimer';
import { makeFastTick } from 'virtjs/devices/timers/utils';
export class AsyncTimer {
/**
* An AsyncTimer is an asynchronous timer device. You can use it to run your emulator without blocking your main thread. However, unless you really want to implement a new asynchronous device on top of a new API, you're probably looking for {@link AnimationFrameTimer} for browser environments, or {@link ImmediateTimer} for Node.js environments.
*
* @constructor
* @implements {Timer}
*
* @param {object} [options] - The timer options.
* @param {function} [options.prepare] - The callback that will schedule the next cycle
* @param {function} [options.cancel] - The callback that will abort the next cycle
*
* @see {@link AnimationFrameTimer}
* @see {@link ImmediateTimer}
*/
constructor({ prepare, cancel } = { }) {
if (prepare)
this.prepare = prepare;
if (cancel)
this.cancel = cancel;
this.running = false;
this.nested = false;
this.loopHandler = null;
this.fastLoop = null;
this.timer = new SerialTimer();
}
nextTick(callback) {
return this.timer.nextTick(callback);
}
cancelTick(handler) {
return this.timer.cancelTick(handler);
}
start(beginning, ending) {
if (this.running)
throw new Error(`You can't start a timer that is already running`);
if (this.nested)
throw new Error(`You can't start a timer from its callbacks - use resume instead`);
this.running = true;
let resolve;
let reject;
let promise = new Promise((resolveFn, rejectFn) => {
resolve = resolveFn;
reject = rejectFn;
});
let fastTick = makeFastTick(beginning, ending, () => {
this.timer.one();
});
let mainLoop = () => {
if (!this.running) {
resolve();
} else try {
this.prepare(mainLoop);
this.nested = true;
fastTick();
this.nested = false;
} catch (e) {
this.running = false;
this.nested = false;
reject(e);
}
};
this.prepare(mainLoop);
return promise;
}
resume() {
if (!this.nested)
throw new Error(`You can't resume a timer from anywhere else than its callbacks - use start instead`);
if (this.running)
return;
this.running = true;
}
stop() {
if (!this.running)
return;
this.running = false;
}
/**
* This method should be specialized, either via subclassing, or by passing the proper parameter when instanciating the timer.
*
* @protected
*
* @type {prepareCallback}
*/
prepare() {
throw new Error(`Unimplemented`);
}
/**
* This method should be specialized, either via subclassing, or by passing the proper parameter when instanciating the timer.
*
* @protected
*
* @type {cancelCallback}
*/
cancel() {
throw new Error(`Unimplemented`);
}
}