Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- // ES6 module transpilable via babel
- // References to requestAnimationFrame and cancelAnimationFrame
- let raf, caf;
- // Support: requestAnimationFrame and cancelAnimation
- if (typeof window === 'object' && window != null && window.requestAnimationFrame != null) {
- raf = window.requestAnimationFrame;
- caf = window.cancelAnimationFrame;
- // Polyfill requestAnimationFrame and cancelAnimationFrame
- } else {
- raf = callback => setTimeout(() => callback(Date.now()), 0);
- caf = clearInterval;
- }
- // List of game loop event names
- const keys = ['start', 'update', 'draw', 'end'];
- /** Calls registered event handlers
- * @private
- * @param {GameLoop} self The game loop instance that the event is to be emitted on
- * @param {String} event The event name to emit
- * @param {Array} args List of arguments to pass to the handler
- */
- function emit(self, event, ...args) {
- // retrieve a list of callbacks for the event
- let handlers = self.state.handlers[event],
- keep = [];
- // loop over the list of registered event handlers
- for (
- let index = 0, length = handlers.length;
- index < length;
- index++
- ) {
- // call the callback
- handlers[index].callback.apply(self, args);
- // remove non one-time emitters from handler list
- if (handlers[index].once !== true) {
- keep[keep.length] = handlers[index];
- }
- }
- self.state.handlers[event] = keep;
- }
- /** @param {Number} timestamp The current timestamp*/
- function tick(timestamp) {
- // quick references
- let self = this,
- state = self.state;
- // request a new animation frame
- state.id = raf(state.fnc);
- // get the number of milliseconds that elapsed since last call that have not been included as part of a simulation step
- // that is: the number of ms to simulate
- state.delta += timestamp - state.ts;
- // emit tick-start event
- emit(self, 'start', state.delta);
- let updates = 0, panic = false;
- // loop while there's a full 'time-step' to simulate
- while (state.delta >= state.step) {
- // emit update event
- emit(self, 'update');
- // decrease the number of seconds to simulate by the 'time-step'
- state.delta -= state.step;
- // increase the update counter
- // if it goes above 240 we've entered a death spiral where updates are taking longer than they can render
- if (++updates >= 240) {
- panic = true;
- break;
- }
- }
- // emit the draw event
- emit(self, 'draw', state.delta / state.step, !!updates, panic);
- // done processing, emit the end event
- emit(self, 'end', panic);
- // update timestamp
- state.ts = timestamp;
- }
- export default class GameLoop {
- /** @param {Number} [tickrate] The target number of ticks per second*/
- constructor(tickrate) {
- let self = this;
- self.state = {
- step: 1000 / (tickrate || 60), // number of milliseconds that must elapse for an update to be triggered
- delta: 0,
- ts: 0,
- running: false,
- id: null,
- handlers: {start: [], update: [], draw: [], end:[]},
- fnc: tick.bind(self)
- };
- }
- /** Registers an event handler; handlers are called in the order inwhich they were added
- * @param {String} event The event name inwhich to hook
- * @param {Function} callback The handling function to call when the event is emitted
- * @returns {GameLoop} The game loop instance the event handler was applied to
- */
- on(event, callback, once) {
- if (keys.indexOf(event = event.toLowerCase()) === -1) {
- throw new Error(`Invalid onGameLoop event`);
- }
- if (typeof callback !== 'function') {
- throw new Error(`Invalid onGameLoop event handler`);
- }
- // add the event handler
- this.state.handlers[event].push({callback, once: !!once});
- return this;
- }
- /** Removes the first matching registered event handler
- * @param {String} event The name of the event from which to remove the handler
- * @param {Function} callback the handling function to remove
- * @returns {GameLoop} the game loop instance the event handler was removed from.
- */
- off(event, callback, once) {
- once = !!once;
- // if the event is applicable and the callback is a function
- if (keys.indexOf(event = event.toLowerCase()) > -1 && typeof callback === 'function') {
- // Loop over all registered handlers until a matching handler is found
- for (
- let handlers = this.state.handlers[event],
- index = 0,
- length = handlers.length,
- handler;
- index < length;
- index++
- ) {
- handler = handlers[index];
- // matching handler found, remove it from the handlers array and exit the loop
- if (handler.callback === callback && handler.once === once) {
- handlers.splice(index, 1);
- break;
- }
- }
- }
- return this;
- }
- // Starts the game loop if it is not running
- start() {
- let self = this,
- state = self.state;
- if (!state.running) {
- state.running = true;
- state.delta = 0;
- state.ts = Date.now();
- state.id = raf(state.fnc);
- }
- return self;
- }
- // Stop the game loop
- stop() {
- let self = this,
- state = self.state;
- caf(state.id);
- state.running = false;
- state.id = null;
- return self;
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement