Home | History | Annotate | Download | only in tracks
      1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 'use strict';
      6 
      7 base.requireStylesheet('tracing.tracks.object_instance_track');
      8 
      9 base.require('base.sorted_array_utils');
     10 base.require('tracing.tracks.heading_track');
     11 base.require('tracing.color_scheme');
     12 base.require('ui');
     13 
     14 base.exportTo('tracing.tracks', function() {
     15 
     16   var palette = tracing.getColorPalette();
     17   var highlightIdBoost = tracing.getColorPaletteHighlightIdBoost();
     18 
     19   /**
     20    * A track that displays an array of Slice objects.
     21    * @constructor
     22    * @extends {HeadingTrack}
     23    */
     24 
     25   var ObjectInstanceTrack = ui.define(
     26       'object-instance-track', tracing.tracks.HeadingTrack);
     27 
     28   ObjectInstanceTrack.prototype = {
     29     __proto__: tracing.tracks.HeadingTrack.prototype,
     30 
     31     decorate: function(viewport) {
     32       tracing.tracks.HeadingTrack.prototype.decorate.call(this, viewport);
     33       this.classList.add('object-instance-track');
     34       this.objectInstances_ = [];
     35       this.objectSnapshots_ = [];
     36     },
     37 
     38     get objectInstances() {
     39       return this.objectInstances_;
     40     },
     41 
     42     set objectInstances(objectInstances) {
     43       if (!objectInstances || objectInstances.length == 0) {
     44         this.heading = '';
     45         this.objectInstances_ = [];
     46         this.objectSnapshots_ = [];
     47         return;
     48       }
     49       this.heading = objectInstances[0].typeName;
     50       this.objectInstances_ = objectInstances;
     51       this.objectSnapshots_ = [];
     52       this.objectInstances_.forEach(function(instance) {
     53         this.objectSnapshots_.push.apply(
     54             this.objectSnapshots_, instance.snapshots);
     55       }, this);
     56     },
     57 
     58     get height() {
     59       return window.getComputedStyle(this).height;
     60     },
     61 
     62     set height(height) {
     63       this.style.height = height;
     64     },
     65 
     66     get snapshotRadiusView() {
     67       return 7 * (window.devicePixelRatio || 1);
     68     },
     69 
     70     draw: function(type, viewLWorld, viewRWorld) {
     71       switch (type) {
     72         case tracing.tracks.DrawType.SLICE:
     73           this.drawSlices_(viewLWorld, viewRWorld);
     74           break;
     75       }
     76     },
     77 
     78     drawSlices_: function(viewLWorld, viewRWorld) {
     79       var ctx = this.context();
     80       var pixelRatio = window.devicePixelRatio || 1;
     81 
     82       var bounds = this.getBoundingClientRect();
     83       var height = bounds.height * pixelRatio;
     84       var halfHeight = height * 0.5;
     85       var twoPi = Math.PI * 2;
     86 
     87       // Culling parameters.
     88       var vp = this.viewport;
     89       var snapshotRadiusView = this.snapshotRadiusView;
     90       var snapshotRadiusWorld = vp.xViewVectorToWorld(height);
     91       var loI;
     92 
     93       // Begin rendering in world space.
     94       ctx.save();
     95       vp.applyTransformToCanvas(ctx);
     96 
     97       // Instances
     98       var objectInstances = this.objectInstances_;
     99       var loI = base.findLowIndexInSortedArray(
    100           objectInstances,
    101           function(instance) {
    102             return instance.deletionTs;
    103           },
    104           viewLWorld);
    105       ctx.globalAlpha = 0.25;
    106       ctx.strokeStyle = 'rgb(0,0,0)';
    107       for (var i = loI; i < objectInstances.length; ++i) {
    108         var instance = objectInstances[i];
    109         var x = instance.creationTs;
    110         if (x > viewRWorld)
    111           break;
    112 
    113         var colorId = instance.selected ?
    114             instance.colorId + highlightIdBoost :
    115             instance.colorId;
    116 
    117         var right = instance.deletionTs == Number.MAX_VALUE ?
    118             viewRWorld : instance.deletionTs;
    119         ctx.fillStyle = palette[colorId];
    120         ctx.fillRect(x, pixelRatio, right - x, height - 2 * pixelRatio);
    121       }
    122       ctx.globalAlpha = 1;
    123       ctx.restore();
    124 
    125       // Snapshots. Has to run in worldspace because ctx.arc gets transformed.
    126       var objectSnapshots = this.objectSnapshots_;
    127       loI = base.findLowIndexInSortedArray(
    128           objectSnapshots,
    129           function(snapshot) {
    130             return snapshot.ts +
    131                 snapshotRadiusWorld;
    132           },
    133           viewLWorld);
    134       for (var i = loI; i < objectSnapshots.length; ++i) {
    135         var snapshot = objectSnapshots[i];
    136         var x = snapshot.ts;
    137         if (x - snapshotRadiusWorld > viewRWorld)
    138           break;
    139         var xView = vp.xWorldToView(x);
    140 
    141         var colorId = snapshot.selected ?
    142             snapshot.objectInstance.colorId + highlightIdBoost :
    143             snapshot.objectInstance.colorId;
    144 
    145         ctx.fillStyle = palette[colorId];
    146         ctx.beginPath();
    147         ctx.arc(xView, halfHeight, snapshotRadiusView, 0, twoPi);
    148         ctx.fill();
    149         if (snapshot.selected) {
    150           ctx.lineWidth = 5;
    151           ctx.strokeStyle = 'rgb(100,100,0)';
    152           ctx.stroke();
    153 
    154           ctx.beginPath();
    155           ctx.arc(xView, halfHeight, snapshotRadiusView - 1, 0, twoPi);
    156           ctx.lineWidth = 2;
    157           ctx.strokeStyle = 'rgb(255,255,0)';
    158           ctx.stroke();
    159         } else {
    160           ctx.lineWidth = 1;
    161           ctx.strokeStyle = 'rgb(0,0,0)';
    162           ctx.stroke();
    163         }
    164       }
    165       ctx.lineWidth = 1;
    166     },
    167 
    168     addIntersectingItemsInRangeToSelectionInWorldSpace: function(
    169         loWX, hiWX, viewPixWidthWorld, selection) {
    170       var that = this;
    171 
    172       // Pick snapshots first.
    173       var foundSnapshot = false;
    174       function onSnapshotHit(snapshot) {
    175         selection.addObjectSnapshot(that, snapshot);
    176         foundSnapshot = true;
    177       }
    178       var snapshotRadiusView = this.snapshotRadiusView;
    179       var snapshotRadiusWorld = viewPixWidthWorld * snapshotRadiusView;
    180       base.iterateOverIntersectingIntervals(
    181           this.objectSnapshots_,
    182           function(x) { return x.ts - snapshotRadiusWorld; },
    183           function(x) { return 2 * snapshotRadiusWorld; },
    184           loWX, hiWX,
    185           onSnapshotHit);
    186       if (foundSnapshot)
    187         return;
    188 
    189       // Try picking instances.
    190       function onInstanceHit(instance) {
    191         selection.addObjectInstance(that, instance);
    192       }
    193       base.iterateOverIntersectingIntervals(
    194           this.objectInstances_,
    195           function(x) { return x.creationTs; },
    196           function(x) { return x.deletionTs - x.creationTs; },
    197           loWX, hiWX,
    198           onInstanceHit);
    199     },
    200 
    201     /**
    202      * Add the item to the left or right of the provided hit, if any, to the
    203      * selection.
    204      * @param {slice} The current slice.
    205      * @param {Number} offset Number of slices away from the hit to look.
    206      * @param {Selection} selection The selection to add a hit to,
    207      * if found.
    208      * @return {boolean} Whether a hit was found.
    209      * @private
    210      */
    211     addItemNearToProvidedHitToSelection: function(hit, offset, selection) {
    212       if (hit instanceof tracing.SelectionObjectSnapshotHit) {
    213         var index = this.objectSnapshots_.indexOf(hit.objectSnapshot);
    214         var newIndex = index + offset;
    215         if (newIndex >= 0 && newIndex < this.objectSnapshots_.length) {
    216           selection.addObjectSnapshot(this, this.objectSnapshots_[newIndex]);
    217           return true;
    218         }
    219       } else if (hit instanceof tracing.SelectionObjectInstanceHit) {
    220         var index = this.objectInstances_.indexOf(hit.objectInstance);
    221         var newIndex = index + offset;
    222         if (newIndex >= 0 && newIndex < this.objectInstances_.length) {
    223           selection.addObjectInstance(this, this.objectInstances_[newIndex]);
    224           return true;
    225         }
    226       } else {
    227         throw new Error('Unrecognized hit');
    228       }
    229       return false;
    230     },
    231 
    232     addAllObjectsMatchingFilterToSelection: function(filter, selection) {
    233     }
    234   };
    235 
    236   ObjectInstanceTrack.typeNameToTrackConstructorMap = {};
    237   ObjectInstanceTrack.register = function(typeName, constructor) {
    238     if (ObjectInstanceTrack.typeNameToTrackConstructorMap[typeName])
    239       throw new Error('Handler already registered for ' + typeName);
    240     ObjectInstanceTrack.typeNameToTrackConstructorMap[typeName] =
    241         constructor;
    242   };
    243 
    244   ObjectInstanceTrack.getTrackConstructor = function(typeName) {
    245     return ObjectInstanceTrack.typeNameToTrackConstructorMap[typeName];
    246   };
    247 
    248   return {
    249     ObjectInstanceTrack: ObjectInstanceTrack
    250   };
    251 });
    252