Home | History | Annotate | Download | only in spinning-balls
      1 // Copyright 2011 the V8 project authors. All rights reserved.
      2 // Redistribution and use in source and binary forms, with or without
      3 // modification, are permitted provided that the following conditions are
      4 // met:
      5 //
      6 //     * Redistributions of source code must retain the above copyright
      7 //       notice, this list of conditions and the following disclaimer.
      8 //     * Redistributions in binary form must reproduce the above
      9 //       copyright notice, this list of conditions and the following
     10 //       disclaimer in the documentation and/or other materials provided
     11 //       with the distribution.
     12 //     * Neither the name of Google Inc. nor the names of its
     13 //       contributors may be used to endorse or promote products derived
     14 //       from this software without specific prior written permission.
     15 //
     16 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     17 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     18 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     19 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     20 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     21 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     22 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     23 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     24 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     25 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     26 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     27 
     28 
     29 /**
     30  * This function provides requestAnimationFrame in a cross browser way.
     31  * http://paulirish.com/2011/requestanimationframe-for-smart-animating/
     32  */
     33 if ( !window.requestAnimationFrame ) {
     34   window.requestAnimationFrame = ( function() {
     35     return window.webkitRequestAnimationFrame ||
     36         window.mozRequestAnimationFrame ||
     37         window.oRequestAnimationFrame ||
     38         window.msRequestAnimationFrame ||
     39         function(callback, element) {
     40           window.setTimeout( callback, 1000 / 60 );
     41         };
     42   } )();
     43 }
     44 
     45 var kNPoints = 8000;
     46 var kNModifications = 20;
     47 var kNVisiblePoints = 200;
     48 var kDecaySpeed = 20;
     49 
     50 var kPointRadius = 4;
     51 var kInitialLifeForce = 100;
     52 
     53 var livePoints = void 0;
     54 var dyingPoints = void 0;
     55 var scene = void 0;
     56 var renderingStartTime = void 0;
     57 var scene = void 0;
     58 var pausePlot = void 0;
     59 var splayTree = void 0;
     60 var numberOfFrames = 0;
     61 var sumOfSquaredPauses = 0;
     62 var benchmarkStartTime = void 0;
     63 var benchmarkTimeLimit = void 0;
     64 var autoScale = void 0;
     65 var pauseDistribution = [];
     66 
     67 
     68 function Point(x, y, z, payload) {
     69   this.x = x;
     70   this.y = y;
     71   this.z = z;
     72 
     73   this.next = null;
     74   this.prev = null;
     75   this.payload = payload;
     76   this.lifeForce = kInitialLifeForce;
     77 }
     78 
     79 
     80 Point.prototype.color = function () {
     81   return "rgba(0, 0, 0, " + (this.lifeForce / kInitialLifeForce) + ")";
     82 };
     83 
     84 
     85 Point.prototype.decay = function () {
     86   this.lifeForce -= kDecaySpeed;
     87   return this.lifeForce <= 0;
     88 };
     89 
     90 
     91 function PointsList() {
     92   this.head = null;
     93   this.count = 0;
     94 }
     95 
     96 
     97 PointsList.prototype.add = function (point) {
     98   if (this.head !== null) this.head.prev = point;
     99   point.next = this.head;
    100   this.head = point;
    101   this.count++;
    102 }
    103 
    104 
    105 PointsList.prototype.remove = function (point) {
    106   if (point.next !== null) {
    107     point.next.prev = point.prev;
    108   }
    109   if (point.prev !== null) {
    110     point.prev.next = point.next;
    111   } else {
    112     this.head = point.next;
    113   }
    114   this.count--;
    115 }
    116 
    117 
    118 function GeneratePayloadTree(depth, tag) {
    119   if (depth == 0) {
    120     return {
    121       array  : [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ],
    122       string : 'String for key ' + tag + ' in leaf node'
    123     };
    124   } else {
    125     return {
    126       left:  GeneratePayloadTree(depth - 1, tag),
    127       right: GeneratePayloadTree(depth - 1, tag)
    128     };
    129   }
    130 }
    131 
    132 
    133 // To make the benchmark results predictable, we replace Math.random
    134 // with a 100% deterministic alternative.
    135 Math.random = (function() {
    136   var seed = 49734321;
    137   return function() {
    138     // Robert Jenkins' 32 bit integer hash function.
    139     seed = ((seed + 0x7ed55d16) + (seed << 12))  & 0xffffffff;
    140     seed = ((seed ^ 0xc761c23c) ^ (seed >>> 19)) & 0xffffffff;
    141     seed = ((seed + 0x165667b1) + (seed << 5))   & 0xffffffff;
    142     seed = ((seed + 0xd3a2646c) ^ (seed << 9))   & 0xffffffff;
    143     seed = ((seed + 0xfd7046c5) + (seed << 3))   & 0xffffffff;
    144     seed = ((seed ^ 0xb55a4f09) ^ (seed >>> 16)) & 0xffffffff;
    145     return (seed & 0xfffffff) / 0x10000000;
    146   };
    147 })();
    148 
    149 
    150 function GenerateKey() {
    151   // The benchmark framework guarantees that Math.random is
    152   // deterministic; see base.js.
    153   return Math.random();
    154 }
    155 
    156 function CreateNewPoint() {
    157   // Insert new node with a unique key.
    158   var key;
    159   do { key = GenerateKey(); } while (splayTree.find(key) != null);
    160 
    161   var point = new Point(Math.random() * 40 - 20,
    162                         Math.random() * 40 - 20,
    163                         Math.random() * 40 - 20,
    164                         GeneratePayloadTree(5, "" + key));
    165 
    166   livePoints.add(point);
    167 
    168   splayTree.insert(key, point);
    169   return key;
    170 }
    171 
    172 function ModifyPointsSet() {
    173   if (livePoints.count < kNPoints) {
    174     for (var i = 0; i < kNModifications; i++) {
    175       CreateNewPoint();
    176     }
    177   } else if (kNModifications === 20) {
    178     kNModifications = 80;
    179     kDecay = 30;
    180   }
    181 
    182   for (var i = 0; i < kNModifications; i++) {
    183     var key = CreateNewPoint();
    184     var greatest = splayTree.findGreatestLessThan(key);
    185     if (greatest == null) {
    186       var point = splayTree.remove(key).value;
    187     } else {
    188       var point = splayTree.remove(greatest.key).value;
    189     }
    190     livePoints.remove(point);
    191     point.payload = null;
    192     dyingPoints.add(point);
    193   }
    194 }
    195 
    196 
    197 function PausePlot(width, height, size, scale) {
    198   var canvas = document.createElement("canvas");
    199   canvas.width = this.width = width;
    200   canvas.height = this.height = height;
    201   document.body.appendChild(canvas);
    202 
    203   this.ctx = canvas.getContext('2d');
    204 
    205   if (typeof scale !== "number") {
    206     this.autoScale = true;
    207     this.maxPause = 0;
    208   } else {
    209     this.autoScale = false;
    210     this.maxPause = scale;
    211   }
    212 
    213   this.size = size;
    214 
    215   // Initialize cyclic buffer for pauses.
    216   this.pauses = new Array(this.size);
    217   this.start = this.size;
    218   this.idx = 0;
    219 }
    220 
    221 
    222 PausePlot.prototype.addPause = function (p) {
    223   if (this.idx === this.size) {
    224     this.idx = 0;
    225   }
    226 
    227   if (this.idx === this.start) {
    228     this.start++;
    229   }
    230 
    231   if (this.start === this.size) {
    232     this.start = 0;
    233   }
    234 
    235   this.pauses[this.idx++] = p;
    236 };
    237 
    238 
    239 PausePlot.prototype.iteratePauses = function (f) {
    240   if (this.start < this.idx) {
    241     for (var i = this.start; i < this.idx; i++) {
    242       f.call(this, i - this.start, this.pauses[i]);
    243     }
    244   } else {
    245     for (var i = this.start; i < this.size; i++) {
    246       f.call(this, i - this.start, this.pauses[i]);
    247     }
    248 
    249     var offs = this.size - this.start;
    250     for (var i = 0; i < this.idx; i++) {
    251       f.call(this, i + offs, this.pauses[i]);
    252     }
    253   }
    254 };
    255 
    256 
    257 PausePlot.prototype.draw = function () {
    258   var first = null;
    259 
    260   if (this.autoScale) {
    261     this.iteratePauses(function (i, v) {
    262       if (first === null) {
    263         first = v;
    264       }
    265       this.maxPause = Math.max(v, this.maxPause);
    266     });
    267   }
    268 
    269   var dx = this.width / this.size;
    270   var dy = this.height / this.maxPause;
    271 
    272   this.ctx.save();
    273   this.ctx.clearRect(0, 0, this.width, this.height);
    274   this.ctx.beginPath();
    275   this.ctx.moveTo(1, dy * this.pauses[this.start]);
    276   var p = first;
    277   this.iteratePauses(function (i, v) {
    278     var delta = v - p;
    279     var x = 1 + dx * i;
    280     var y = dy * v;
    281     this.ctx.lineTo(x, y);
    282     if (delta > 2 * (p / 3)) {
    283       this.ctx.font = "bold 12px sans-serif";
    284       this.ctx.textBaseline = "bottom";
    285       this.ctx.fillText(v + "ms", x + 2, y);
    286     }
    287     p = v;
    288   });
    289   this.ctx.strokeStyle = "black";
    290   this.ctx.stroke();
    291   this.ctx.restore();
    292 }
    293 
    294 
    295 function Scene(width, height) {
    296   var canvas = document.createElement("canvas");
    297   canvas.width = width;
    298   canvas.height = height;
    299   document.body.appendChild(canvas);
    300 
    301   this.ctx = canvas.getContext('2d');
    302   this.width = canvas.width;
    303   this.height = canvas.height;
    304 
    305   // Projection configuration.
    306   this.x0 = canvas.width / 2;
    307   this.y0 = canvas.height / 2;
    308   this.z0 = 100;
    309   this.f  = 1000;  // Focal length.
    310 
    311   // Camera is rotating around y-axis.
    312   this.angle = 0;
    313 }
    314 
    315 
    316 Scene.prototype.drawPoint = function (x, y, z, color) {
    317   // Rotate the camera around y-axis.
    318   var rx = x * Math.cos(this.angle) - z * Math.sin(this.angle);
    319   var ry = y;
    320   var rz = x * Math.sin(this.angle) + z * Math.cos(this.angle);
    321 
    322   // Perform perspective projection.
    323   var px = (this.f * rx) / (rz - this.z0) + this.x0;
    324   var py = (this.f * ry) / (rz - this.z0) + this.y0;
    325 
    326   this.ctx.save();
    327   this.ctx.fillStyle = color
    328   this.ctx.beginPath();
    329   this.ctx.arc(px, py, kPointRadius, 0, 2 * Math.PI, true);
    330   this.ctx.fill();
    331   this.ctx.restore();
    332 };
    333 
    334 
    335 Scene.prototype.drawDyingPoints = function () {
    336   var point_next = null;
    337   for (var point = dyingPoints.head; point !== null; point = point_next) {
    338     // Rotate the scene around y-axis.
    339     scene.drawPoint(point.x, point.y, point.z, point.color());
    340 
    341     point_next = point.next;
    342 
    343     // Decay the current point and remove it from the list
    344     // if it's life-force ran out.
    345     if (point.decay()) {
    346       dyingPoints.remove(point);
    347     }
    348   }
    349 };
    350 
    351 
    352 Scene.prototype.draw = function () {
    353   this.ctx.save();
    354   this.ctx.clearRect(0, 0, this.width, this.height);
    355   this.drawDyingPoints();
    356   this.ctx.restore();
    357 
    358   this.angle += Math.PI / 90.0;
    359 };
    360 
    361 
    362 function updateStats(pause) {
    363   numberOfFrames++;
    364   if (pause > 20) {
    365     sumOfSquaredPauses += (pause - 20) * (pause - 20);
    366   }
    367   pauseDistribution[Math.floor(pause / 10)] |= 0;
    368   pauseDistribution[Math.floor(pause / 10)]++;
    369 }
    370 
    371 
    372 function renderStats() {
    373   var msg = document.createElement("p");
    374   msg.innerHTML = "Score " +
    375     Math.round(numberOfFrames * 1000 / sumOfSquaredPauses);
    376   var table = document.createElement("table");
    377   table.align = "center";
    378   for (var i = 0; i < pauseDistribution.length; i++) {
    379     if (pauseDistribution[i] > 0) {
    380       var row = document.createElement("tr");
    381       var time = document.createElement("td");
    382       var count = document.createElement("td");
    383       time.innerHTML = i*10 + "-" + (i+1)*10 + "ms";
    384       count.innerHTML = " => " + pauseDistribution[i];
    385       row.appendChild(time);
    386       row.appendChild(count);
    387       table.appendChild(row);
    388     }
    389   }
    390   div.appendChild(msg);
    391   div.appendChild(table);
    392 }
    393 
    394 
    395 function render() {
    396   if (typeof renderingStartTime === 'undefined') {
    397     renderingStartTime = Date.now();
    398     benchmarkStartTime = renderingStartTime;
    399   }
    400 
    401   ModifyPointsSet();
    402 
    403   scene.draw();
    404 
    405   var renderingEndTime = Date.now();
    406   var pause = renderingEndTime - renderingStartTime;
    407   pausePlot.addPause(pause);
    408   renderingStartTime = renderingEndTime;
    409 
    410   pausePlot.draw();
    411 
    412   updateStats(pause);
    413 
    414   div.innerHTML =
    415       livePoints.count + "/" + dyingPoints.count + " " +
    416       pause + "(max = " + pausePlot.maxPause + ") ms " +
    417       numberOfFrames + " frames";
    418 
    419   if (renderingEndTime < benchmarkStartTime + benchmarkTimeLimit) {
    420     // Schedule next frame.
    421     requestAnimationFrame(render);
    422   } else {
    423     renderStats();
    424   }
    425 }
    426 
    427 
    428 function Form() {
    429   function create(tag) { return document.createElement(tag); }
    430   function text(value) { return document.createTextNode(value); }
    431 
    432   this.form = create("form");
    433   this.form.setAttribute("action", "javascript:start()");
    434 
    435   var table = create("table");
    436   table.setAttribute("style", "margin-left: auto; margin-right: auto;");
    437 
    438   function col(a) {
    439     var td = create("td");
    440     td.appendChild(a);
    441     return td;
    442   }
    443 
    444   function row(a, b) {
    445     var tr = create("tr");
    446     tr.appendChild(col(a));
    447     tr.appendChild(col(b));
    448     return tr;
    449   }
    450 
    451   this.timelimit = create("input");
    452   this.timelimit.setAttribute("value", "60");
    453 
    454   table.appendChild(row(text("Time limit in seconds"), this.timelimit));
    455 
    456   this.autoscale = create("input");
    457   this.autoscale.setAttribute("type", "checkbox");
    458   this.autoscale.setAttribute("checked", "true");
    459   table.appendChild(row(text("Autoscale pauses plot"), this.autoscale));
    460 
    461   var button = create("input");
    462   button.setAttribute("type", "submit");
    463   button.setAttribute("value", "Start");
    464   this.form.appendChild(table);
    465   this.form.appendChild(button);
    466 
    467   document.body.appendChild(this.form);
    468 }
    469 
    470 
    471 Form.prototype.remove = function () {
    472   document.body.removeChild(this.form);
    473 };
    474 
    475 
    476 function init() {
    477   livePoints = new PointsList;
    478   dyingPoints = new PointsList;
    479 
    480   splayTree = new SplayTree();
    481 
    482   scene = new Scene(640, 480);
    483 
    484   div = document.createElement("div");
    485   document.body.appendChild(div);
    486 
    487   pausePlot = new PausePlot(480, autoScale ? 240 : 500, 160, autoScale ? void 0 : 500);
    488 }
    489 
    490 function start() {
    491   benchmarkTimeLimit = form.timelimit.value * 1000;
    492   autoScale = form.autoscale.checked;
    493   form.remove();
    494   init();
    495   render();
    496 }
    497 
    498 var form = new Form();
    499