// Due to the existence of features such as interpolation and "0 FPS" being treated as "screen refresh rate", // The VM loop logic has become much more complex // Use setTimeout to polyfill requestAnimationFrame in Node.js environments const _requestAnimationFrame = typeof requestAnimationFrame === 'function' ? requestAnimationFrame : (f => setTimeout(f, 1000 / 60)); const _cancelAnimationFrame = typeof requestAnimationFrame === 'function' ? cancelAnimationFrame : clearTimeout; const animationFrameWrapper = callback => { let id; const handle = () => { id = _requestAnimationFrame(handle); callback(); }; const cancel = () => _cancelAnimationFrame(id); id = _requestAnimationFrame(handle); return { cancel }; }; class FrameLoop { constructor (runtime) { this.runtime = runtime; this.running = false; this.setFramerate(30); this.setInterpolation(false); this.stepCallback = this.stepCallback.bind(this); this.interpolationCallback = this.interpolationCallback.bind(this); this._stepInterval = null; this._interpolationAnimation = null; this._stepAnimation = null; this._stepCounter = 0; } setFramerate (fps) { this.framerate = fps; this._restart(); } setInterpolation (interpolation) { this.interpolation = interpolation; this._restart(); } stepCallback () { this.runtime._step(); } interpolationCallback () { this.runtime._renderInterpolatedPositions(); } _restart () { if (this.running) { this.stop(); this.start(); } } start () { this.running = true; if (this.framerate === 0) { this._stepAnimation = animationFrameWrapper(this.stepCallback); this.runtime.currentStepTime = 1000 / 60; } else { // Interpolation should never be enabled when framerate === 0 as that's just redundant if (this.interpolation) { this._interpolationAnimation = animationFrameWrapper(this.interpolationCallback); } this._stepInterval = setInterval(this.stepCallback, 1000 / this.framerate); this.runtime.currentStepTime = 1000 / this.framerate; } } stop () { this.running = false; clearInterval(this._stepInterval); if (this._interpolationAnimation) { this._interpolationAnimation.cancel(); } if (this._stepAnimation) { this._stepAnimation.cancel(); } this._interpolationAnimation = null; this._stepAnimation = null; } } module.exports = FrameLoop;