1// Copyright (C) 2018 The Android Open Source Project 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15import {assertTrue, assertUnreachable} from '../base/logging'; 16import {Time, time, TimeSpan} from '../base/time'; 17import {HighPrecisionTimeSpan} from '../base/high_precision_time_span'; 18import {Area} from '../public/selection'; 19import {raf} from './raf_scheduler'; 20import {HighPrecisionTime} from '../base/high_precision_time'; 21import {DurationPrecision, Timeline, TimestampFormat} from '../public/timeline'; 22import { 23 durationPrecision, 24 setDurationPrecision, 25 setTimestampFormat, 26 timestampFormat, 27} from './timestamp_format'; 28import {TraceInfo} from '../public/trace_info'; 29 30const MIN_DURATION = 10; 31 32/** 33 * State that is shared between several frontend components, but not the 34 * controller. This state is updated at 60fps. 35 */ 36export class TimelineImpl implements Timeline { 37 private _visibleWindow: HighPrecisionTimeSpan; 38 private _hoverCursorTimestamp?: time; 39 private _highlightedSliceId?: number; 40 private _hoveredNoteTimestamp?: time; 41 42 // TODO(stevegolton): These are currently only referenced by the cpu slice 43 // tracks and the process summary tracks. We should just make this a local 44 // property of the cpu slice tracks and ignore them in the process tracks. 45 private _hoveredUtid?: number; 46 private _hoveredPid?: number; 47 48 get highlightedSliceId() { 49 return this._highlightedSliceId; 50 } 51 52 set highlightedSliceId(x) { 53 this._highlightedSliceId = x; 54 raf.scheduleCanvasRedraw(); 55 } 56 57 get hoveredNoteTimestamp() { 58 return this._hoveredNoteTimestamp; 59 } 60 61 set hoveredNoteTimestamp(x) { 62 this._hoveredNoteTimestamp = x; 63 raf.scheduleCanvasRedraw(); 64 } 65 66 get hoveredUtid() { 67 return this._hoveredUtid; 68 } 69 70 set hoveredUtid(x) { 71 this._hoveredUtid = x; 72 raf.scheduleCanvasRedraw(); 73 } 74 75 get hoveredPid() { 76 return this._hoveredPid; 77 } 78 79 set hoveredPid(x) { 80 this._hoveredPid = x; 81 raf.scheduleCanvasRedraw(); 82 } 83 84 // This is used to calculate the tracks within a Y range for area selection. 85 private _selectedArea?: Area; 86 87 constructor(private readonly traceInfo: TraceInfo) { 88 this._visibleWindow = HighPrecisionTimeSpan.fromTime( 89 traceInfo.start, 90 traceInfo.end, 91 ); 92 } 93 94 // TODO: there is some redundancy in the fact that both |visibleWindowTime| 95 // and a |timeScale| have a notion of time range. That should live in one 96 // place only. 97 98 zoomVisibleWindow(ratio: number, centerPoint: number) { 99 this._visibleWindow = this._visibleWindow 100 .scale(ratio, centerPoint, MIN_DURATION) 101 .fitWithin(this.traceInfo.start, this.traceInfo.end); 102 103 raf.scheduleCanvasRedraw(); 104 } 105 106 panVisibleWindow(delta: number) { 107 this._visibleWindow = this._visibleWindow 108 .translate(delta) 109 .fitWithin(this.traceInfo.start, this.traceInfo.end); 110 111 raf.scheduleCanvasRedraw(); 112 } 113 114 // Given a timestamp, if |ts| is not currently in view move the view to 115 // center |ts|, keeping the same zoom level. 116 panToTimestamp(ts: time) { 117 if (this._visibleWindow.contains(ts)) return; 118 // TODO(hjd): This is an ugly jump, we should do a smooth pan instead. 119 const halfDuration = this.visibleWindow.duration / 2; 120 const newStart = new HighPrecisionTime(ts).subNumber(halfDuration); 121 const newWindow = new HighPrecisionTimeSpan( 122 newStart, 123 this._visibleWindow.duration, 124 ); 125 this.updateVisibleTimeHP(newWindow); 126 } 127 128 // Set the highlight box to draw 129 selectArea( 130 start: time, 131 end: time, 132 tracks = this._selectedArea ? this._selectedArea.trackUris : [], 133 ) { 134 assertTrue( 135 end >= start, 136 `Impossible select area: start [${start}] >= end [${end}]`, 137 ); 138 this._selectedArea = {start, end, trackUris: tracks}; 139 raf.scheduleFullRedraw(); 140 } 141 142 deselectArea() { 143 this._selectedArea = undefined; 144 raf.scheduleCanvasRedraw(); 145 } 146 147 get selectedArea(): Area | undefined { 148 return this._selectedArea; 149 } 150 151 // Set visible window using an integer time span 152 updateVisibleTime(ts: TimeSpan) { 153 this.updateVisibleTimeHP(HighPrecisionTimeSpan.fromTime(ts.start, ts.end)); 154 } 155 156 // TODO(primiano): we ended up with two entry-points for the same function, 157 // unify them. 158 setViewportTime(start: time, end: time): void { 159 this.updateVisibleTime(new TimeSpan(start, end)); 160 } 161 162 // Set visible window using a high precision time span 163 updateVisibleTimeHP(ts: HighPrecisionTimeSpan) { 164 this._visibleWindow = ts 165 .clampDuration(MIN_DURATION) 166 .fitWithin(this.traceInfo.start, this.traceInfo.end); 167 168 raf.scheduleCanvasRedraw(); 169 } 170 171 // Get the bounds of the visible window as a high-precision time span 172 get visibleWindow(): HighPrecisionTimeSpan { 173 return this._visibleWindow; 174 } 175 176 get hoverCursorTimestamp(): time | undefined { 177 return this._hoverCursorTimestamp; 178 } 179 180 set hoverCursorTimestamp(t: time | undefined) { 181 this._hoverCursorTimestamp = t; 182 raf.scheduleCanvasRedraw(); 183 } 184 185 // Offset between t=0 and the configured time domain. 186 timestampOffset(): time { 187 const fmt = timestampFormat(); 188 switch (fmt) { 189 case TimestampFormat.Timecode: 190 case TimestampFormat.Seconds: 191 case TimestampFormat.Milliseconds: 192 case TimestampFormat.Microseconds: 193 return this.traceInfo.start; 194 case TimestampFormat.TraceNs: 195 case TimestampFormat.TraceNsLocale: 196 return Time.ZERO; 197 case TimestampFormat.UTC: 198 return this.traceInfo.utcOffset; 199 case TimestampFormat.TraceTz: 200 return this.traceInfo.traceTzOffset; 201 default: 202 assertUnreachable(fmt); 203 } 204 } 205 206 // Convert absolute time to domain time. 207 toDomainTime(ts: time): time { 208 return Time.sub(ts, this.timestampOffset()); 209 } 210 211 get timestampFormat() { 212 return timestampFormat(); 213 } 214 215 set timestampFormat(format: TimestampFormat) { 216 setTimestampFormat(format); 217 } 218 219 get durationPrecision() { 220 return durationPrecision(); 221 } 222 223 set durationPrecision(precision: DurationPrecision) { 224 setDurationPrecision(precision); 225 } 226} 227