123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384 |
- import defaultValue from "../Core/defaultValue.js";
- import defined from "../Core/defined.js";
- import destroyObject from "../Core/destroyObject.js";
- import DeveloperError from "../Core/DeveloperError.js";
- import Event from "../Core/Event.js";
- import getTimestamp from "../Core/getTimestamp.js";
- import TimeConstants from "../Core/TimeConstants.js";
- /**
- * Monitors the frame rate (frames per second) in a {@link Scene} and raises an event if the frame rate is
- * lower than a threshold. Later, if the frame rate returns to the required level, a separate event is raised.
- * To avoid creating multiple FrameRateMonitors for a single {@link Scene}, use {@link FrameRateMonitor.fromScene}
- * instead of constructing an instance explicitly.
- *
- * @alias FrameRateMonitor
- * @constructor
- *
- * @param {Object} [options] Object with the following properties:
- * @param {Scene} options.scene The Scene instance for which to monitor performance.
- * @param {Number} [options.samplingWindow=5.0] The length of the sliding window over which to compute the average frame rate, in seconds.
- * @param {Number} [options.quietPeriod=2.0] The length of time to wait at startup and each time the page becomes visible (i.e. when the user
- * switches back to the tab) before starting to measure performance, in seconds.
- * @param {Number} [options.warmupPeriod=5.0] The length of the warmup period, in seconds. During the warmup period, a separate
- * (usually lower) frame rate is required.
- * @param {Number} [options.minimumFrameRateDuringWarmup=4] The minimum frames-per-second that are required for acceptable performance during
- * the warmup period. If the frame rate averages less than this during any samplingWindow during the warmupPeriod, the
- * lowFrameRate event will be raised and the page will redirect to the redirectOnLowFrameRateUrl, if any.
- * @param {Number} [options.minimumFrameRateAfterWarmup=8] The minimum frames-per-second that are required for acceptable performance after
- * the end of the warmup period. If the frame rate averages less than this during any samplingWindow after the warmupPeriod, the
- * lowFrameRate event will be raised and the page will redirect to the redirectOnLowFrameRateUrl, if any.
- */
- function FrameRateMonitor(options) {
- //>>includeStart('debug', pragmas.debug);
- if (!defined(options) || !defined(options.scene)) {
- throw new DeveloperError("options.scene is required.");
- }
- //>>includeEnd('debug');
- this._scene = options.scene;
- /**
- * Gets or sets the length of the sliding window over which to compute the average frame rate, in seconds.
- * @type {Number}
- */
- this.samplingWindow = defaultValue(
- options.samplingWindow,
- FrameRateMonitor.defaultSettings.samplingWindow
- );
- /**
- * Gets or sets the length of time to wait at startup and each time the page becomes visible (i.e. when the user
- * switches back to the tab) before starting to measure performance, in seconds.
- * @type {Number}
- */
- this.quietPeriod = defaultValue(
- options.quietPeriod,
- FrameRateMonitor.defaultSettings.quietPeriod
- );
- /**
- * Gets or sets the length of the warmup period, in seconds. During the warmup period, a separate
- * (usually lower) frame rate is required.
- * @type {Number}
- */
- this.warmupPeriod = defaultValue(
- options.warmupPeriod,
- FrameRateMonitor.defaultSettings.warmupPeriod
- );
- /**
- * Gets or sets the minimum frames-per-second that are required for acceptable performance during
- * the warmup period. If the frame rate averages less than this during any <code>samplingWindow</code> during the <code>warmupPeriod</code>, the
- * <code>lowFrameRate</code> event will be raised and the page will redirect to the <code>redirectOnLowFrameRateUrl</code>, if any.
- * @type {Number}
- */
- this.minimumFrameRateDuringWarmup = defaultValue(
- options.minimumFrameRateDuringWarmup,
- FrameRateMonitor.defaultSettings.minimumFrameRateDuringWarmup
- );
- /**
- * Gets or sets the minimum frames-per-second that are required for acceptable performance after
- * the end of the warmup period. If the frame rate averages less than this during any <code>samplingWindow</code> after the <code>warmupPeriod</code>, the
- * <code>lowFrameRate</code> event will be raised and the page will redirect to the <code>redirectOnLowFrameRateUrl</code>, if any.
- * @type {Number}
- */
- this.minimumFrameRateAfterWarmup = defaultValue(
- options.minimumFrameRateAfterWarmup,
- FrameRateMonitor.defaultSettings.minimumFrameRateAfterWarmup
- );
- this._lowFrameRate = new Event();
- this._nominalFrameRate = new Event();
- this._frameTimes = [];
- this._needsQuietPeriod = true;
- this._quietPeriodEndTime = 0.0;
- this._warmupPeriodEndTime = 0.0;
- this._frameRateIsLow = false;
- this._lastFramesPerSecond = undefined;
- this._pauseCount = 0;
- const that = this;
- this._preUpdateRemoveListener = this._scene.preUpdate.addEventListener(
- function (scene, time) {
- update(that, time);
- }
- );
- this._hiddenPropertyName =
- document.hidden !== undefined
- ? "hidden"
- : document.mozHidden !== undefined
- ? "mozHidden"
- : document.msHidden !== undefined
- ? "msHidden"
- : document.webkitHidden !== undefined
- ? "webkitHidden"
- : undefined;
- const visibilityChangeEventName =
- document.hidden !== undefined
- ? "visibilitychange"
- : document.mozHidden !== undefined
- ? "mozvisibilitychange"
- : document.msHidden !== undefined
- ? "msvisibilitychange"
- : document.webkitHidden !== undefined
- ? "webkitvisibilitychange"
- : undefined;
- function visibilityChangeListener() {
- visibilityChanged(that);
- }
- this._visibilityChangeRemoveListener = undefined;
- if (defined(visibilityChangeEventName)) {
- document.addEventListener(
- visibilityChangeEventName,
- visibilityChangeListener,
- false
- );
- this._visibilityChangeRemoveListener = function () {
- document.removeEventListener(
- visibilityChangeEventName,
- visibilityChangeListener,
- false
- );
- };
- }
- }
- /**
- * The default frame rate monitoring settings. These settings are used when {@link FrameRateMonitor.fromScene}
- * needs to create a new frame rate monitor, and for any settings that are not passed to the
- * {@link FrameRateMonitor} constructor.
- *
- * @memberof FrameRateMonitor
- * @type {Object}
- */
- FrameRateMonitor.defaultSettings = {
- samplingWindow: 5.0,
- quietPeriod: 2.0,
- warmupPeriod: 5.0,
- minimumFrameRateDuringWarmup: 4,
- minimumFrameRateAfterWarmup: 8,
- };
- /**
- * Gets the {@link FrameRateMonitor} for a given scene. If the scene does not yet have
- * a {@link FrameRateMonitor}, one is created with the {@link FrameRateMonitor.defaultSettings}.
- *
- * @param {Scene} scene The scene for which to get the {@link FrameRateMonitor}.
- * @returns {FrameRateMonitor} The scene's {@link FrameRateMonitor}.
- */
- FrameRateMonitor.fromScene = function (scene) {
- //>>includeStart('debug', pragmas.debug);
- if (!defined(scene)) {
- throw new DeveloperError("scene is required.");
- }
- //>>includeEnd('debug');
- if (
- !defined(scene._frameRateMonitor) ||
- scene._frameRateMonitor.isDestroyed()
- ) {
- scene._frameRateMonitor = new FrameRateMonitor({
- scene: scene,
- });
- }
- return scene._frameRateMonitor;
- };
- Object.defineProperties(FrameRateMonitor.prototype, {
- /**
- * Gets the {@link Scene} instance for which to monitor performance.
- * @memberof FrameRateMonitor.prototype
- * @type {Scene}
- */
- scene: {
- get: function () {
- return this._scene;
- },
- },
- /**
- * Gets the event that is raised when a low frame rate is detected. The function will be passed
- * the {@link Scene} instance as its first parameter and the average number of frames per second
- * over the sampling window as its second parameter.
- * @memberof FrameRateMonitor.prototype
- * @type {Event}
- */
- lowFrameRate: {
- get: function () {
- return this._lowFrameRate;
- },
- },
- /**
- * Gets the event that is raised when the frame rate returns to a normal level after having been low.
- * The function will be passed the {@link Scene} instance as its first parameter and the average
- * number of frames per second over the sampling window as its second parameter.
- * @memberof FrameRateMonitor.prototype
- * @type {Event}
- */
- nominalFrameRate: {
- get: function () {
- return this._nominalFrameRate;
- },
- },
- /**
- * Gets the most recently computed average frames-per-second over the last <code>samplingWindow</code>.
- * This property may be undefined if the frame rate has not been computed.
- * @memberof FrameRateMonitor.prototype
- * @type {Number}
- */
- lastFramesPerSecond: {
- get: function () {
- return this._lastFramesPerSecond;
- },
- },
- });
- /**
- * Pauses monitoring of the frame rate. To resume monitoring, {@link FrameRateMonitor#unpause}
- * must be called once for each time this function is called.
- * @memberof FrameRateMonitor
- */
- FrameRateMonitor.prototype.pause = function () {
- ++this._pauseCount;
- if (this._pauseCount === 1) {
- this._frameTimes.length = 0;
- this._lastFramesPerSecond = undefined;
- }
- };
- /**
- * Resumes monitoring of the frame rate. If {@link FrameRateMonitor#pause} was called
- * multiple times, this function must be called the same number of times in order to
- * actually resume monitoring.
- * @memberof FrameRateMonitor
- */
- FrameRateMonitor.prototype.unpause = function () {
- --this._pauseCount;
- if (this._pauseCount <= 0) {
- this._pauseCount = 0;
- this._needsQuietPeriod = true;
- }
- };
- /**
- * Returns true if this object was destroyed; otherwise, false.
- * <br /><br />
- * If this object was destroyed, it should not be used; calling any function other than
- * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.
- *
- * @memberof FrameRateMonitor
- *
- * @returns {Boolean} True if this object was destroyed; otherwise, false.
- *
- * @see FrameRateMonitor#destroy
- */
- FrameRateMonitor.prototype.isDestroyed = function () {
- return false;
- };
- /**
- * Unsubscribes this instance from all events it is listening to.
- * Once an object is destroyed, it should not be used; calling any function other than
- * <code>isDestroyed</code> will result in a {@link DeveloperError} exception. Therefore,
- * assign the return value (<code>undefined</code>) to the object as done in the example.
- *
- * @memberof FrameRateMonitor
- *
- * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
- *
- * @see FrameRateMonitor#isDestroyed
- */
- FrameRateMonitor.prototype.destroy = function () {
- this._preUpdateRemoveListener();
- if (defined(this._visibilityChangeRemoveListener)) {
- this._visibilityChangeRemoveListener();
- }
- return destroyObject(this);
- };
- function update(monitor, time) {
- if (monitor._pauseCount > 0) {
- return;
- }
- const timeStamp = getTimestamp();
- if (monitor._needsQuietPeriod) {
- monitor._needsQuietPeriod = false;
- monitor._frameTimes.length = 0;
- monitor._quietPeriodEndTime =
- timeStamp + monitor.quietPeriod / TimeConstants.SECONDS_PER_MILLISECOND;
- monitor._warmupPeriodEndTime =
- monitor._quietPeriodEndTime +
- (monitor.warmupPeriod + monitor.samplingWindow) /
- TimeConstants.SECONDS_PER_MILLISECOND;
- } else if (timeStamp >= monitor._quietPeriodEndTime) {
- monitor._frameTimes.push(timeStamp);
- const beginningOfWindow =
- timeStamp -
- monitor.samplingWindow / TimeConstants.SECONDS_PER_MILLISECOND;
- if (
- monitor._frameTimes.length >= 2 &&
- monitor._frameTimes[0] <= beginningOfWindow
- ) {
- while (
- monitor._frameTimes.length >= 2 &&
- monitor._frameTimes[1] < beginningOfWindow
- ) {
- monitor._frameTimes.shift();
- }
- const averageTimeBetweenFrames =
- (timeStamp - monitor._frameTimes[0]) / (monitor._frameTimes.length - 1);
- monitor._lastFramesPerSecond = 1000.0 / averageTimeBetweenFrames;
- const maximumFrameTime =
- 1000.0 /
- (timeStamp > monitor._warmupPeriodEndTime
- ? monitor.minimumFrameRateAfterWarmup
- : monitor.minimumFrameRateDuringWarmup);
- if (averageTimeBetweenFrames > maximumFrameTime) {
- if (!monitor._frameRateIsLow) {
- monitor._frameRateIsLow = true;
- monitor._needsQuietPeriod = true;
- monitor.lowFrameRate.raiseEvent(
- monitor.scene,
- monitor._lastFramesPerSecond
- );
- }
- } else if (monitor._frameRateIsLow) {
- monitor._frameRateIsLow = false;
- monitor._needsQuietPeriod = true;
- monitor.nominalFrameRate.raiseEvent(
- monitor.scene,
- monitor._lastFramesPerSecond
- );
- }
- }
- }
- }
- function visibilityChanged(monitor) {
- if (document[monitor._hiddenPropertyName]) {
- monitor.pause();
- } else {
- monitor.unpause();
- }
- }
- export default FrameRateMonitor;
|