From b26a7695b88b44186aca11fc305c34552d958ad1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Strau=C3=9F?= Date: Wed, 24 Jan 2018 19:01:58 +0100 Subject: [PATCH] feat(animation): RenderLoop --- src/animation/rendering.js | 98 +++++++++++++++++++++++++++++++++++++ test/animation/rendering.js | 52 ++++++++++++++++++++ test/test_main.js | 2 + 3 files changed, 152 insertions(+) create mode 100644 src/animation/rendering.js create mode 100644 test/animation/rendering.js diff --git a/src/animation/rendering.js b/src/animation/rendering.js new file mode 100644 index 0000000..13631e2 --- /dev/null +++ b/src/animation/rendering.js @@ -0,0 +1,98 @@ +goog.module('clulib.animation.rendering'); + +const EventTarget = goog.require('goog.events.EventTarget'); +const Event = goog.require('goog.events.Event'); + +/** + * A render loop which runs on requestAnimationFrame. + * + * Dispatches RenderLoopEvents + */ +class RenderLoop extends EventTarget { + constructor () { + super(); + + /** + * @type {boolean} + * @private + */ + this.isRunning_ = false; + + /** + * @type {?number} + * @private + */ + this.id_ = null; + } + + /** + * @returns {boolean} + */ + get isRunning () { + return this.isRunning_; + } + + start () { + if (this.isRunning_) + return; + this.id_ = window.requestAnimationFrame(this.tick_.bind(this)); + this.isRunning_ = true; + this.dispatchEvent(new RenderLoopEvent(RenderLoopEventType.START, this)); + } + + /** + * @param {number} highResolutionTimestamp + * @private + */ + tick_ (highResolutionTimestamp) { + this.dispatchEvent(new RenderLoopEvent(RenderLoopEventType.TICK, this, highResolutionTimestamp)); + } + + stop () { + if (!this.isRunning_) + return; + window.cancelAnimationFrame(/** @type {number} */ (this.id_)); + this.isRunning_ = false; + this.dispatchEvent(new RenderLoopEvent(RenderLoopEventType.END, this)); + } +} + +/** + * The event for RenderLoop. Holds the elapsed time. + */ +class RenderLoopEvent extends Event { + /** + * @param {RenderLoopEventType} type + * @param {RenderLoop} target + * @param {number=} elapsedTime + */ + constructor (type, target, elapsedTime) { + super(type, target); + + /** + * @type {?number} + * @private + */ + this.elapsedTime_ = /** @type {?number} */ (elapsedTime); + } + + /** + * The elapsed time since the last tick in milliseconds. + * + * @returns {?number} + */ + get elapsedTime () { + return this.elapsedTime_; + } +} + +/** + * @enum {string} + */ +const RenderLoopEventType = { + START: 'start', + TICK: 'tick', + END: 'end' +}; + +exports = {RenderLoop, RenderLoopEvent, RenderLoopEventType}; diff --git a/test/animation/rendering.js b/test/animation/rendering.js new file mode 100644 index 0000000..c640434 --- /dev/null +++ b/test/animation/rendering.js @@ -0,0 +1,52 @@ +goog.module('test.clulib.animation.rendering'); + +const {RenderLoop, RenderLoopEventType} = goog.require('clulib.animation.rendering'); + +/** + * @param {number} ms + * @returns {Promise} + */ +const waitFor = function (ms) { + return new Promise(resolve => { + setTimeout(() => { + resolve(); + }, ms); + }); +}; + +exports = function () { + describe('clulib.animation.rendering', () => { + describe('RenderLoop', () => { + it('should dispatch tick events', async () => { + const loop = new RenderLoop(); + let started = false; + let ticked = false; + let ended = false; + let elapsedTime = 0; + + loop.addEventListener(RenderLoopEventType.START, () => { + started = true; + }); + + loop.addEventListener(RenderLoopEventType.TICK, event => { + ticked = true; + elapsedTime = event.elapsedTime; + loop.stop(); + }); + + loop.addEventListener(RenderLoopEventType.END, () => { + ended = true; + }); + + loop.start(); + + await waitFor(1000); + + expect(started).toBe(true); + expect(ticked).toBe(true); + expect(ended).toBe(true); + expect(elapsedTime > 0).toBe(true); + }); + }); + }); +}; diff --git a/test/test_main.js b/test/test_main.js index 31854b5..5444b7b 100644 --- a/test/test_main.js +++ b/test/test_main.js @@ -1,5 +1,6 @@ goog.module('test.main'); +const renderingMain = goog.require('test.clulib.animation.rendering'); const arrayMain = goog.require('test.clulib.array'); const asyncCompleterMain = goog.require('test.clulib.async.Completer'); const cmMain = goog.require('test.clulib.cm'); @@ -11,6 +12,7 @@ const resourceManagerMain = goog.require('test.clulib.l10n.ResourceManager'); const httpRequestMain = goog.require('test.clulib.net.http_request'); const mathMain = goog.require('test.clulib.math'); +renderingMain(); arrayMain(); asyncCompleterMain(); cmMain();