Source: common/FPSTimer.js

import { CircularQueue } from '@uino/base-thing';
import { Utils } from './Utils';

const __ = {
	private: Symbol('private'),
}

/**
 * @class FPSTimer
 * The FPS timer.
 * @memberof THING
 */
class FPSTimer {

	/**
	 * Control render's FPS(frame pre second), we can limited max FPS also.
	 */
	constructor() {
		this[__.private] = {};
		let _private = this[__.private];

		const maxFPS = 120;

		// Initialize core
		_private.fixedDeltaTime = null;
		_private.deltaTime = 0;
		_private.elapsedTime = 0;
		_private.currentFrameCount = 0;
		_private.enableFpsCounter = true;
		_private.fpsTimes = new CircularQueue(maxFPS);

		// FPS limited
		_private.maxFPS = null;
		_private.fpsLimitedDelta = 0;
		_private.fpsLimitedPreviousDelta = 0;
	}

	// #region Private Functions

	_updateFPSCounter() {
		let _private = this[__.private];

		let fpsTimes = _private.fpsTimes;

		// Thanks for sharing code: https://www.growingwiththeweb.com/2017/12/fast-simple-js-fps-counter.html
		const now = Utils.getCurrentTimeMilliseconds();
		while (fpsTimes.size > 0 && fpsTimes.front <= now - 1000) {
			fpsTimes.dequeue();
		}

		fpsTimes.enqueue(now);
	}

	/**
	 * Check whether it's in FPS limited state.
	 * @param {Number} deltaTime The delta time in milliseconds.
	 * @returns {Boolean}
	 * @private
	 */
	_isInLimitedState(deltaTime) {
		let _private = this[__.private];

		let maxFPS = _private.maxFPS;
		if (!maxFPS) {
			return false;
		}

		_private.fpsLimitedDelta += deltaTime;
		let delta = _private.fpsLimitedDelta - _private.fpsLimitedPreviousDelta;

		let interval = 1 / maxFPS;
		let needsUpdate = maxFPS && delta >= interval;
		if (needsUpdate) {
			_private.fpsLimitedPreviousDelta = _private.fpsLimitedDelta - (delta % interval);
		}

		return !needsUpdate;
	}

	// #endregion

	/**
	 * Update timer.
	 * @param {Number} deltaTime The delta time in seconds.
	 * @private
	 */
	update(deltaTime) {
		let _private = this[__.private];

		// Check whether use fixed delta time
		if (_private.fixedDeltaTime) {
			deltaTime = _private.fixedDeltaTime;
		}

		// Check whether it's in limited state
		if (this._isInLimitedState(deltaTime)) {
			return 0;
		}

		// Get the clock time
		_private.deltaTime = deltaTime;
		_private.elapsedTime += deltaTime;

		// Update FPS
		_private.currentFrameCount++;

		// Update FPS counter
		if (_private.enableFpsCounter) {
			this._updateFPSCounter();
		}

		return deltaTime;
	}

	// #region Accessor

	/**
	 * Get the elapsed time(seconds) since started.
	 * @type {Number}
	 * @example
	 * // Print elapsed time(seconds) since started.
	 * console.log(fpsTimer.elapsedTime);
	 */
	get elapsedTime() {
		return this[__.private].elapsedTime;
	}

	/**
	 * Get the delta time(seconds) from previous frame.
	 * @type {Number}
	 * @example
	 * // Print delta time(seconds) from previous frame.
	 * console.log(fpsTimer.deltaTime);
	 */
	get deltaTime() {
		return this[__.private].deltaTime;
	}

	/**
	 * Get the elapsed time(milliseconds) when started.
	 * @type {Number}
	 * @private
	 */
	get elapsedTimeMilliseconds() {
		return Math.floor(this.elapsedTime * 1000);
	}

	/**
	 * Get the delta time(milliseconds) of previous frame.
	 * @type {Number}
	 * @private
	 */
	get deltaTimeMilliseconds() {
		return Math.floor(this.deltaTime * 1000);
	}

	/**
	 * Get the current total frame count since started.
	 * @type {Number}
	 * @example
	 * // Print current frame count since started.
	 * console.log(fpsTimer.currentFrameCount);
	 */
	get currentFrameCount() {
		return this[__.private].currentFrameCount;
	}

	/**
	 * Get/Set fixed delta time in seconds.
	 * @type {Number}
	 */
	get fixedDeltaTime() {
		return this[__.private].fixedDeltaTime;
	}
	set fixedDeltaTime(value) {
		this[__.private].fixedDeltaTime = value;
	}

	/**
	 * Get/Set the max FPS number, null indicates unlimited.
	 * @type {Number}
	 * @example
	 * // Limited the max FPS to 30 fps
	 * fpsTimer.maxFPS = 30;
	 * // Unlimited the FPS
	 * fpsTimer.maxFPS = null;
	 */
	get maxFPS() {
		return this[__.private].maxFPS;
	}
	set maxFPS(value) {
		this[__.private].maxFPS = value;
	}

	/**
	 * Enable/Disable fps counter.
	 * @type {Boolean}
	 * @example
	 * // Disable the FPS counter, if we disable then we can not get the FPS Counter info
	 * fpsTimer.enableFpsCounter = false;
	 */
	get enableFpsCounter() {
		return this[__.private].enableFpsCounter;
	}
	set enableFpsCounter(value) {
		let _private = this[__.private];

		_private.enableFpsCounter = value;
	}

	/**
	 * Get the FPS Counter.
	 * @type {Number}
	 * @example
	 * // Print the current FPS counter
	 * console.log(fpsTimer.fpsCounter);
	 */
	get fpsCounter() {
		let _private = this[__.private];

		return _private.fpsTimes.size;
	}

	// #endregion

}

export { FPSTimer }