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