1 /**
  2  * The Render Engine
  3  *
  4  * An extension to the engine for metrics processing and display.
  5  *
  6  * @author: Brett Fattori (brettf@renderengine.com)
  7  *
  8  * @author: $Author: bfattori $
  9  * @version: $Revision: 1555 $
 10  *
 11  * Copyright (c) 2011 Brett Fattori (brettf@renderengine.com)
 12  *
 13  * Permission is hereby granted, free of charge, to any person obtaining a copy
 14  * of this software and associated documentation files (the "Software"), to deal
 15  * in the Software without restriction, including without limitation the rights
 16  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 17  * copies of the Software, and to permit persons to whom the Software is
 18  * furnished to do so, subject to the following conditions:
 19  *
 20  * The above copyright notice and this permission notice shall be included in
 21  * all copies or substantial portions of the Software.
 22  *
 23  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 24  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 25  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 26  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 27  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 28  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 29  * THE SOFTWARE.
 30  *
 31  */
 32 
 33 //====================================================================================================
 34 //====================================================================================================
 35 //                                     METRICS MANAGEMENT
 36 //====================================================================================================
 37 //====================================================================================================
 38 /**
 39  * @class A static class to be used to measure metrics of engine and game performance.  A
 40  *          visual profiler is provided which graphs runtime values of the engine, such as
 41  *          load and visible objects.  Additionally a metrics window is provided to show
 42  *          sampled metric data from parts of the engine, as well as user-defined metrics.
 43  * @static
 44  */
 45 R.debug.Metrics = Base.extend(/** @scope R.debug.Metrics.prototype */{
 46     constructor:null,
 47 
 48     /*
 49      * Metrics tracking/display
 50      */
 51     metrics:{}, // Tracked metrics
 52     metricDisplay:null, // The metric display object
 53     profileDisplay:null, // The profile display object
 54     metricSampleRate:10, // Frames between samples
 55     lastMetricSample:10, // Last sample frame
 56     showMetricsWindow:false, // Metrics display flag
 57     showMetricsProfile:false, // Metrics profile graph display flag
 58     vObj:0, // Visible objects
 59     droppedFrames:0, // Non-rendered frames/frames dropped
 60     profilePos:0,
 61     profiles:{},
 62 
 63 
 64     /**
 65      * Toggle the display of the metrics window.  Any metrics
 66      * that are being tracked will be reported in this window.
 67      * @memberOf R.debug.Metrics
 68      */
 69     toggleMetrics:function () {
 70         R.debug.Metrics.showMetricsWindow = !R.debug.Metrics.showMetricsWindow;
 71     },
 72 
 73     /**
 74      * Show the metrics window
 75      * @memberOf R.debug.Metrics
 76      */
 77     showMetrics:function () {
 78         R.debug.Metrics.showMetricsWindow = true;
 79     },
 80 
 81     /**
 82      * Show a graph of the engine profile
 83      * @memberOf R.debug.Metrics
 84      */
 85     showProfile:function () {
 86         R.debug.Metrics.showMetricsProfile = true;
 87     },
 88 
 89     /**
 90      * Hide the metrics window
 91      * @memberOf R.debug.Metrics
 92      */
 93     hideMetrics:function () {
 94         R.debug.Metrics.showMetricsWindow = false;
 95     },
 96 
 97     manMetrics:function () {
 98         if ($("div.metric-button.minimize").length > 0) {
 99             $("div.metric-button.minimize").removeClass("minimize").addClass("maximize").attr("title", "maximize");
100             $("div.metrics").css("height", 17);
101             $("div.metrics .items").hide();
102         } else {
103             $("div.metric-button.maximize").removeClass("maximize").addClass("minimize").attr("title", "minimize");
104             $("div.metrics .items").show();
105             $("div.metrics").css("height", "auto");
106         }
107     },
108 
109     /**
110      * Creates a button for the metrics window
111      * @private
112      */
113     metricButton:function (cssClass, fn) {
114         return $("<div class='metric-button " + cssClass + "' title='" + cssClass + "'><!-- --></div>").click(fn);
115     },
116 
117     /**
118      * Render the metrics window
119      * @private
120      */
121     render:function () {
122 
123         if (R.debug.Metrics.showMetricsWindow && !R.debug.Metrics.metricDisplay) {
124             R.debug.Metrics.metricDisplay = $("<div/>").addClass("metrics");
125             R.debug.Metrics.metricDisplay.append(R.debug.Metrics.metricButton("run", function () {
126                 R.Engine.run();
127             }));
128             R.debug.Metrics.metricDisplay.append(R.debug.Metrics.metricButton("step", function () {
129                 R.Engine.step();
130             }));
131             R.debug.Metrics.metricDisplay.append(R.debug.Metrics.metricButton("pause", function () {
132                 R.Engine.pause();
133             }));
134             R.debug.Metrics.metricDisplay.append(R.debug.Metrics.metricButton("shutdown", function () {
135                 R.Engine.shutdown();
136             }));
137 
138             R.debug.Metrics.metricDisplay.append(R.debug.Metrics.metricButton("close", function () {
139                 R.debug.Metrics.hideMetrics();
140             }));
141             R.debug.Metrics.metricDisplay.append(R.debug.Metrics.metricButton("minimize", function () {
142                 R.debug.Metrics.manMetrics();
143             }));
144 
145             R.debug.Metrics.metricDisplay.append($("<div class='items'/>"));
146             R.debug.Metrics.metricDisplay.appendTo($("body"));
147         }
148 
149         if ((this.showMetricsWindow || this.showMetricsProfile) && this.lastMetricSample-- == 0) {
150             // Basic engine metrics
151             R.debug.Metrics.add("FPS", R.Engine.getFPS(), false, "#");
152             R.debug.Metrics.add("aFPS", R.Engine.getActualFPS(), true, "#");
153             R.debug.Metrics.add("availTime", R.Engine.fpsClock, false, "#ms");
154             R.debug.Metrics.add("frames", R.Engine.totalFrames, false, "#");
155             R.debug.Metrics.add("frameGenTime", R.Engine.frameTime, true, "#ms");
156             R.debug.Metrics.add("engineLoad", Math.floor(R.Engine.getEngineLoad() * 100), true, "#%");
157             R.debug.Metrics.add("vObj", R.Engine.vObj, false, "#");
158             R.debug.Metrics.add("rObj", R.Engine.rObjs, false, "#");
159             R.debug.Metrics.add("droppedFrames", R.Engine.droppedFrames, false, "#");
160             R.debug.Metrics.add("upTime", Math.floor((R.Engine.worldTime - R.Engine.upTime) / 1000), false, "# sec");
161             R.debug.Metrics.add("pclRebuilds", R.Engine.pclRebuilds, false, "#");
162 
163             R.debug.Metrics.update();
164             R.debug.Metrics.lastMetricSample = R.debug.Metrics.metricSampleRate;
165         }
166 
167         if (R.debug.Metrics.showMetricsProfile && R.engine.Support.sysInfo().browser == "msie" &&
168             parseFloat(R.engine.Support.sysInfo().version) < 9) {
169             // Profiler not supported in IE
170             R.debug.Metrics.showMetricsProfile = false;
171         }
172 
173         if (R.debug.Metrics.showMetricsProfile && !R.debug.Metrics.profileDisplay) {
174             R.debug.Metrics.profileDisplay = $("<canvas width='150' height='100'/>").addClass("engine-profile");
175             R.debug.Metrics.profileDisplay.appendTo($("body"));
176             R.debug.Metrics.profileDisplay[0].getContext('2d').save();
177         }
178     },
179 
180     /**
181      * Set the interval at which metrics are sampled by the system.
182      * The default is for metrics to be calculated every 10 engine frames.
183      *
184      * @param sampleRate {Number} The number of ticks between samples
185      * @memberOf R.debug.Metrics
186      */
187     setSampleRate:function (sampleRate) {
188         R.debug.Metrics.lastMetricSample = 1;
189         R.debug.Metrics.metricSampleRate = sampleRate;
190     },
191 
192     /**
193      * Add a metric to the game engine that can be displayed
194      * while it is running.  If smoothing is selected, a 3 point
195      * running average will be used to smooth out jitters in the
196      * value that is shown.  For the <tt>fmt</tt> argument,
197      * you can provide a string which contains the pound sign "#"
198      * that will be used to determine where the calculated value will
199      * occur in the formatted string.
200      *
201      * @param metricName {String} The name of the metric to track
202      * @param value {String/Number} The value of the metric.
203      * @param smoothing {Boolean} <tt>true</tt> to use 3 point average smoothing
204      * @param fmt {String} The way the value should be formatted in the display (e.g. "#ms")
205      * @memberOf R.debug.Metrics
206      */
207     add:function (metricName, value, smoothing, fmt) {
208         if (smoothing) {
209             var vals = R.debug.Metrics.metrics[metricName] ? R.debug.Metrics.metrics[metricName].values : [];
210             if (vals.length == 0) {
211                 // Init
212                 vals.push(value);
213                 vals.push(value);
214                 vals.push(value);
215             }
216             vals.shift();
217             vals.push(value);
218             var v = Math.floor((vals[0] + vals[1] + vals[2]) * 0.33);
219             R.debug.Metrics.metrics[metricName] = { val:(fmt ? fmt.replace("#", v) : v), values:vals, act:v };
220         } else {
221             R.debug.Metrics.metrics[metricName] = { val:(fmt ? fmt.replace("#", value) : value), act:value };
222         }
223     },
224 
225     /**
226      * Remove a metric from the display
227      *
228      * @param metricName {String} The name of the metric to remove
229      * @memberOf R.debug.Metrics
230      */
231     remove:function (metricName) {
232         R.debug.Metrics.metrics[metricName] = null;
233         delete R.debug.Metrics.metrics[metricName];
234     },
235 
236     /**
237      * Updates the display of the metrics window.
238      * @private
239      * @memberOf R.debug.Metrics
240      */
241     update:function () {
242         var h = "", ctx;
243         if (R.debug.Metrics.showMetricsProfile) {
244             ctx = R.debug.Metrics.profileDisplay[0].getContext('2d');
245             ctx.save();
246             ctx.translate(147, 0);
247         }
248 
249         for (var m in R.debug.Metrics.metrics) {
250             if (R.debug.Metrics.showMetricsWindow) {
251                 h += m + ": " + R.debug.Metrics.metrics[m].val + "<br/>";
252             }
253             if (R.debug.Metrics.showMetricsProfile) {
254                 switch (m) {
255                     case "engineLoad":
256                         this.drawProfilePoint("#ffff00", R.debug.Metrics.metrics[m].act);
257                         break;
258                     case "vObj":
259                         this.drawProfilePoint("#339933", R.debug.Metrics.metrics[m].act);
260                         break;
261                     case "rObj":
262                         this.drawProfilePoint("#ff00ff", R.debug.Metrics.metrics[m].act);
263                         break;
264                     case "poolLoad" :
265                         this.drawProfilePoint("#a0a0ff", R.debug.Metrics.metrics[m].act);
266                         break;
267                 }
268             }
269         }
270         if (R.debug.Metrics.showMetricsWindow) {
271             $(".items", R.debug.Metrics.metricDisplay).html(h);
272         }
273         if (R.debug.Metrics.showMetricsProfile) {
274             ctx.restore();
275             R.debug.Metrics.moveProfiler();
276         }
277     },
278 
279     /**
280      * @private
281      */
282     drawProfilePoint:function (color, val) {
283         var ctx = R.debug.Metrics.profileDisplay[0].getContext('2d');
284         ctx.strokeStyle = color;
285         try {
286             if (!isNaN(val)) {
287                 ctx.beginPath();
288                 ctx.moveTo(0, R.debug.Metrics.profiles[color] || 100);
289                 ctx.lineTo(1, (100 - val < 1 ? 1 : 100 - val));
290                 ctx.closePath();
291                 ctx.stroke();
292                 R.debug.Metrics.profiles[color] = (100 - val < 1 ? 1 : 100 - val);
293             }
294         } catch (ex) {
295 
296         }
297     },
298 
299     /**
300      * @private
301      */
302     moveProfiler:function () {
303         var ctx = R.debug.Metrics.profileDisplay[0].getContext('2d');
304         var imgData = ctx.getImageData(1, 0, 149, 100);
305         ctx.save();
306         ctx.translate(-1, 0);
307         ctx.putImageData(imgData, 0, 0);
308         ctx.restore();
309     },
310 
311     /**
312      * Run the metrics display.
313      * @private
314      * @memberOf R.debug.Metrics
315      */
316     doMetrics:function () {
317         // Output any metrics
318         if (R.debug.Metrics.showMetricsWindow || R.debug.Metrics.showMetricsProfile) {
319             R.debug.Metrics.render();
320         } else if (!R.debug.Metrics.showMetricsWindow && R.debug.Metrics.metricDisplay) {
321             R.debug.Metrics.metricDisplay.remove();
322             R.debug.Metrics.metricDisplay = null;
323         }
324     }
325 
326 });
327 
328 if (R.engine.Support.checkBooleanParam("metrics")) {
329     R.debug.Metrics.showMetrics();
330 }
331 
332 if (R.engine.Support.checkBooleanParam("profile")) {
333     R.debug.Metrics.showProfile();
334 }
335 
336