// Copyright 2011 the V8 project authors. All rights reserved. // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following // disclaimer in the documentation and/or other materials provided // with the distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. /** * This function provides requestAnimationFrame in a cross browser way. * http://paulirish.com/2011/requestanimationframe-for-smart-animating/ */ if ( !window.requestAnimationFrame ) { window.requestAnimationFrame = ( function() { return window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function(callback, element) { window.setTimeout( callback, 1000 / 60 ); }; } )(); } var kNPoints = 8000; var kNModifications = 20; var kNVisiblePoints = 200; var kDecaySpeed = 20; var kPointRadius = 4; var kInitialLifeForce = 100; var livePoints = void 0; var dyingPoints = void 0; var scene = void 0; var renderingStartTime = void 0; var scene = void 0; var pausePlot = void 0; var splayTree = void 0; var numberOfFrames = 0; var sumOfSquaredPauses = 0; var benchmarkStartTime = void 0; var benchmarkTimeLimit = void 0; var autoScale = void 0; var pauseDistribution = []; function Point(x, y, z, payload) { this.x = x; this.y = y; this.z = z; this.next = null; this.prev = null; this.payload = payload; this.lifeForce = kInitialLifeForce; } Point.prototype.color = function () { return "rgba(0, 0, 0, " + (this.lifeForce / kInitialLifeForce) + ")"; }; Point.prototype.decay = function () { this.lifeForce -= kDecaySpeed; return this.lifeForce <= 0; }; function PointsList() { this.head = null; this.count = 0; } PointsList.prototype.add = function (point) { if (this.head !== null) this.head.prev = point; point.next = this.head; this.head = point; this.count++; } PointsList.prototype.remove = function (point) { if (point.next !== null) { point.next.prev = point.prev; } if (point.prev !== null) { point.prev.next = point.next; } else { this.head = point.next; } this.count--; } function GeneratePayloadTree(depth, tag) { if (depth == 0) { return { array : [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ], string : 'String for key ' + tag + ' in leaf node' }; } else { return { left: GeneratePayloadTree(depth - 1, tag), right: GeneratePayloadTree(depth - 1, tag) }; } } // To make the benchmark results predictable, we replace Math.random // with a 100% deterministic alternative. Math.random = (function() { var seed = 49734321; return function() { // Robert Jenkins' 32 bit integer hash function. seed = seed & 0xffffffff; seed = ((seed + 0x7ed55d16) + (seed << 12)) & 0xffffffff; seed = ((seed ^ 0xc761c23c) ^ (seed >>> 19)) & 0xffffffff; seed = ((seed + 0x165667b1) + (seed << 5)) & 0xffffffff; seed = ((seed + 0xd3a2646c) ^ (seed << 9)) & 0xffffffff; seed = ((seed + 0xfd7046c5) + (seed << 3)) & 0xffffffff; seed = ((seed ^ 0xb55a4f09) ^ (seed >>> 16)) & 0xffffffff; return (seed & 0xfffffff) / 0x10000000; }; })(); function GenerateKey() { // The benchmark framework guarantees that Math.random is // deterministic; see base.js. return Math.random(); } function CreateNewPoint() { // Insert new node with a unique key. var key; do { key = GenerateKey(); } while (splayTree.find(key) != null); var point = new Point(Math.random() * 40 - 20, Math.random() * 40 - 20, Math.random() * 40 - 20, GeneratePayloadTree(5, "" + key)); livePoints.add(point); splayTree.insert(key, point); return key; } function ModifyPointsSet() { if (livePoints.count < kNPoints) { for (var i = 0; i < kNModifications; i++) { CreateNewPoint(); } } else if (kNModifications === 20) { kNModifications = 80; kDecay = 30; } for (var i = 0; i < kNModifications; i++) { var key = CreateNewPoint(); var greatest = splayTree.findGreatestLessThan(key); if (greatest == null) { var point = splayTree.remove(key).value; } else { var point = splayTree.remove(greatest.key).value; } livePoints.remove(point); point.payload = null; dyingPoints.add(point); } } function PausePlot(width, height, size, scale) { var canvas = document.createElement("canvas"); canvas.width = this.width = width; canvas.height = this.height = height; document.body.appendChild(canvas); this.ctx = canvas.getContext('2d'); if (typeof scale !== "number") { this.autoScale = true; this.maxPause = 0; } else { this.autoScale = false; this.maxPause = scale; } this.size = size; // Initialize cyclic buffer for pauses. this.pauses = new Array(this.size); this.start = this.size; this.idx = 0; } PausePlot.prototype.addPause = function (p) { if (this.idx === this.size) { this.idx = 0; } if (this.idx === this.start) { this.start++; } if (this.start === this.size) { this.start = 0; } this.pauses[this.idx++] = p; }; PausePlot.prototype.iteratePauses = function (f) { if (this.start < this.idx) { for (var i = this.start; i < this.idx; i++) { f.call(this, i - this.start, this.pauses[i]); } } else { for (var i = this.start; i < this.size; i++) { f.call(this, i - this.start, this.pauses[i]); } var offs = this.size - this.start; for (var i = 0; i < this.idx; i++) { f.call(this, i + offs, this.pauses[i]); } } }; PausePlot.prototype.draw = function () { var first = null; if (this.autoScale) { this.iteratePauses(function (i, v) { if (first === null) { first = v; } this.maxPause = Math.max(v, this.maxPause); }); } var dx = this.width / this.size; var dy = this.height / this.maxPause; this.ctx.save(); this.ctx.clearRect(0, 0, this.width, this.height); this.ctx.beginPath(); this.ctx.moveTo(1, dy * this.pauses[this.start]); var p = first; this.iteratePauses(function (i, v) { var delta = v - p; var x = 1 + dx * i; var y = dy * v; this.ctx.lineTo(x, y); if (delta > 2 * (p / 3)) { this.ctx.font = "bold 12px sans-serif"; this.ctx.textBaseline = "bottom"; this.ctx.fillText(v + "ms", x + 2, y); } p = v; }); this.ctx.strokeStyle = "black"; this.ctx.stroke(); this.ctx.restore(); } function Scene(width, height) { var canvas = document.createElement("canvas"); canvas.width = width; canvas.height = height; document.body.appendChild(canvas); this.ctx = canvas.getContext('2d'); this.width = canvas.width; this.height = canvas.height; // Projection configuration. this.x0 = canvas.width / 2; this.y0 = canvas.height / 2; this.z0 = 100; this.f = 1000; // Focal length. // Camera is rotating around y-axis. this.angle = 0; } Scene.prototype.drawPoint = function (x, y, z, color) { // Rotate the camera around y-axis. var rx = x * Math.cos(this.angle) - z * Math.sin(this.angle); var ry = y; var rz = x * Math.sin(this.angle) + z * Math.cos(this.angle); // Perform perspective projection. var px = (this.f * rx) / (rz - this.z0) + this.x0; var py = (this.f * ry) / (rz - this.z0) + this.y0; this.ctx.save(); this.ctx.fillStyle = color this.ctx.beginPath(); this.ctx.arc(px, py, kPointRadius, 0, 2 * Math.PI, true); this.ctx.fill(); this.ctx.restore(); }; Scene.prototype.drawDyingPoints = function () { var point_next = null; for (var point = dyingPoints.head; point !== null; point = point_next) { // Rotate the scene around y-axis. scene.drawPoint(point.x, point.y, point.z, point.color()); point_next = point.next; // Decay the current point and remove it from the list // if it's life-force ran out. if (point.decay()) { dyingPoints.remove(point); } } }; Scene.prototype.draw = function () { this.ctx.save(); this.ctx.clearRect(0, 0, this.width, this.height); this.drawDyingPoints(); this.ctx.restore(); this.angle += Math.PI / 90.0; }; function updateStats(pause) { numberOfFrames++; if (pause > 20) { sumOfSquaredPauses += (pause - 20) * (pause - 20); } pauseDistribution[Math.floor(pause / 10)] |= 0; pauseDistribution[Math.floor(pause / 10)]++; } function renderStats() { var msg = document.createElement("p"); msg.innerHTML = "Score " + Math.round(numberOfFrames * 1000 / sumOfSquaredPauses); var table = document.createElement("table"); table.align = "center"; for (var i = 0; i < pauseDistribution.length; i++) { if (pauseDistribution[i] > 0) { var row = document.createElement("tr"); var time = document.createElement("td"); var count = document.createElement("td"); time.innerHTML = i*10 + "-" + (i+1)*10 + "ms"; count.innerHTML = " => " + pauseDistribution[i]; row.appendChild(time); row.appendChild(count); table.appendChild(row); } } div.appendChild(msg); div.appendChild(table); } function render() { if (typeof renderingStartTime === 'undefined') { renderingStartTime = Date.now(); benchmarkStartTime = renderingStartTime; } ModifyPointsSet(); scene.draw(); var renderingEndTime = Date.now(); var pause = renderingEndTime - renderingStartTime; pausePlot.addPause(pause); renderingStartTime = renderingEndTime; pausePlot.draw(); updateStats(pause); div.innerHTML = livePoints.count + "/" + dyingPoints.count + " " + pause + "(max = " + pausePlot.maxPause + ") ms " + numberOfFrames + " frames"; if (renderingEndTime < benchmarkStartTime + benchmarkTimeLimit) { // Schedule next frame. requestAnimationFrame(render); } else { renderStats(); } } function Form() { function create(tag) { return document.createElement(tag); } function text(value) { return document.createTextNode(value); } this.form = create("form"); this.form.setAttribute("action", "javascript:start()"); var table = create("table"); table.setAttribute("style", "margin-left: auto; margin-right: auto;"); function col(a) { var td = create("td"); td.appendChild(a); return td; } function row(a, b) { var tr = create("tr"); tr.appendChild(col(a)); tr.appendChild(col(b)); return tr; } this.timelimit = create("input"); this.timelimit.setAttribute("value", "60"); table.appendChild(row(text("Time limit in seconds"), this.timelimit)); this.autoscale = create("input"); this.autoscale.setAttribute("type", "checkbox"); this.autoscale.setAttribute("checked", "true"); table.appendChild(row(text("Autoscale pauses plot"), this.autoscale)); var button = create("input"); button.setAttribute("type", "submit"); button.setAttribute("value", "Start"); this.form.appendChild(table); this.form.appendChild(button); document.body.appendChild(this.form); } Form.prototype.remove = function () { document.body.removeChild(this.form); }; function init() { livePoints = new PointsList; dyingPoints = new PointsList; splayTree = new SplayTree(); scene = new Scene(640, 480); div = document.createElement("div"); document.body.appendChild(div); pausePlot = new PausePlot(480, autoScale ? 240 : 500, 160, autoScale ? void 0 : 500); } function start() { benchmarkTimeLimit = form.timelimit.value * 1000; autoScale = form.autoscale.checked; form.remove(); init(); render(); } var form = new Form();