Source: InternalTimer.js

/**
 * @license GPL-3.0-only
 *
 * @author Mark Mayes / mm-dev
 *
 *
 * @module InternalTimer
 *
 * @description
 * ## Handles ticks/frames
 * - `requestAnimationFrame()` is used to call a function before the next browser repaint --- this function performs all updates to the state of the game (hopefully) inbetween repaints
 * - Every time an update happens, we record the timestamp in `InternalTimer.lastTick_ms`
 * - When we next update, we see how much time has elapsed since the last update
 * - From this, we can calculate how many of our ideal-world (`GAME.FRAME_MS`) frames have passed
 * - If we aren't quite keeping up with the ideal frame rate, eg 2 frames' worth of time has passed since our last update, we can try to keep things looking smooth by moving obstacles twice as far as during a single-frame update
 */

import { PD } from "./PD/CONST.js";
import * as GAME from "./PD/GAME.js";

import { Game } from "./Game.js";

import { __ } from "./utils.js";

class InternalTimer {}

/**
 * @function init
 * @static 
 *
 * @description
 * ##### Main init
 */
InternalTimer.init = function () {
  InternalTimer.frameCount = 0;

  InternalTimer.fpsRecent_ar = [];
  InternalTimer.currentFps = GAME.TARGET_FPS;
};

/**
 * @function startTicking
 * @static 
 *
 * @description
 * ##### Start the internal clock
 */
InternalTimer.startTicking = function () {
  __("InternalTimer.startTicking()::", PD.FMT_GAME);

  // set initial tick timestamp
  requestAnimationFrame(function (_timestamp_ms) {
    InternalTimer.lastTick_ms = _timestamp_ms;
  });
  // start ticking
  InternalTimer.nextTick();
};

/**
 * @function tick
 * @static 
 *
 * @description
 * ##### This function is given as a callback for `requestAnimationFrame()`
 * - Calculate how many frames have passed since the last tick
 * - Increment the internal frame count variable
 * - Call the main game update function `Game.updateByFrameCount()`, passing it the number of frames
 * - Start the next tick
 *
 * @param {DOMHighResTimeStamp} _timestamp_ms - The time of the previous frame's rendering (automatically passed in by `requestAnimationFrame()`)
 */
InternalTimer.tick = function (_timestamp_ms) {
  var framesSinceLastTick,
    sinceLastTick_ms = _timestamp_ms - InternalTimer.lastTick_ms;

  if (sinceLastTick_ms > GAME.FRAME_MS) {
    InternalTimer.lastTick_ms = _timestamp_ms;
    framesSinceLastTick = Math.floor(sinceLastTick_ms / GAME.FRAME_MS);
    //__('framesSinceLastTick: ' + framesSinceLastTick, PD.FMT_GAME);

    InternalTimer.frameCount += framesSinceLastTick;

    Game.updateByFrameCount(framesSinceLastTick);

    InternalTimer.updateFpsAverage(1000 / sinceLastTick_ms);
  }

  // TODO remove this check and handle inbetween levels properly while animation continues
  //if (Game.isInPlay) {
    InternalTimer.nextTick();
  //}
};

/**
 * @function nextTick
 * @static 
 *
 * @description
 * ##### Call `requestAnimationFrame()` for the next opportunity to run `InternalTimer.tick()`
 */
InternalTimer.nextTick = function () {
  cancelAnimationFrame(InternalTimer.tickAnimationFrameRef);
  InternalTimer.tickAnimationFrameRef = requestAnimationFrame(
    InternalTimer.tick
  );
};

/**
 * @function updateFpsAverage
 * @static 
 *
 * @description
 * ##### Keep a rolling average of how many frames per second we are achieving
 * - On each `InternalTimer.tick()` an estimate of FPS for the latest frame is calculated and passed in to this function
 * - The most recent few (`GAME.FPSDISPLAY_AVERAGE_FRAMES_SPAN`) fps timings are kept in an array
 * - The array is summed and the sum is divided by the array length to get an average (mean) which is stored as `InternalTimer.currentFps`
 *
 * @param {number} _latestFps - The most recent FPS reading
 */
InternalTimer.updateFpsAverage = function (_latestFps) {
  var i,
    sum = 0;

  // Add latest reading to front of the array. Adding at the end (with `pop`)
  // would mean we keep chopping off the most recent values when we fix
  // `length`.
  InternalTimer.fpsRecent_ar.unshift(_latestFps);

  // Chop old values off the end of the array
  InternalTimer.fpsRecent_ar.length = Math.min(
    InternalTimer.fpsRecent_ar.length,
    GAME.FPSDISPLAY_AVERAGE_FRAMES_SPAN
  );

  for (i = 0; i < InternalTimer.fpsRecent_ar.length; i++) {
    sum += InternalTimer.fpsRecent_ar[i];
  }
  // Get average (mean) value of array
  InternalTimer.currentFps = Math.floor(
    sum / InternalTimer.fpsRecent_ar.length
  );
};

/**
 * @function secondsToFrames
 * @static 
 *
 * @description
 * ##### Convert seconds to number of frames
 * Eg when an animation wants to run for 10secs, how many frames should it last for?
 *
 * @param {number} _secs - How many seconds to convert
 *
 * @returns {number} The **estimated** number of frames it will take for `_secs` seconds to expire
 */
InternalTimer.secondsToFrames = function(_secs) {
  return Math.ceil(_secs * InternalTimer.currentFps);
};

export { InternalTimer };