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 {assertUnreachable} from '../base/logging'; 16import {Time, time, TimeSpan} from '../base/time'; 17import {HighPrecisionTimeSpan} from '../base/high_precision_time_span'; 18import {raf} from './raf_scheduler'; 19import {HighPrecisionTime} from '../base/high_precision_time'; 20import {DurationPrecision, Timeline, TimestampFormat} from '../public/timeline'; 21import { 22 durationPrecision, 23 setDurationPrecision, 24 setTimestampFormat, 25 timestampFormat, 26} from './timestamp_format'; 27import {TraceInfo} from '../public/trace_info'; 28 29const MIN_DURATION = 10; 30 31/** 32 * State that is shared between several frontend components, but not the 33 * controller. This state is updated at 60fps. 34 */ 35export class TimelineImpl implements Timeline { 36 private _visibleWindow: HighPrecisionTimeSpan; 37 private _hoverCursorTimestamp?: time; 38 private _highlightedSliceId?: number; 39 private _hoveredNoteTimestamp?: time; 40 41 // TODO(stevegolton): These are currently only referenced by the cpu slice 42 // tracks and the process summary tracks. We should just make this a local 43 // property of the cpu slice tracks and ignore them in the process tracks. 44 private _hoveredUtid?: number; 45 private _hoveredPid?: number; 46 47 // This is used to mark the timeline of the area that is currently being 48 // selected. 49 // 50 // TODO(stevegolton): This shouldn't really be in the global timeline state, 51 // it's really only a concept of the viewer page and should be moved there 52 // instead. 53 selectedSpan?: {start: time; end: time}; 54 55 get highlightedSliceId() { 56 return this._highlightedSliceId; 57 } 58 59 set highlightedSliceId(x) { 60 this._highlightedSliceId = x; 61 raf.scheduleCanvasRedraw(); 62 } 63 64 get hoveredNoteTimestamp() { 65 return this._hoveredNoteTimestamp; 66 } 67 68 set hoveredNoteTimestamp(x) { 69 this._hoveredNoteTimestamp = x; 70 raf.scheduleCanvasRedraw(); 71 } 72 73 get hoveredUtid() { 74 return this._hoveredUtid; 75 } 76 77 set hoveredUtid(x) { 78 this._hoveredUtid = x; 79 raf.scheduleCanvasRedraw(); 80 } 81 82 get hoveredPid() { 83 return this._hoveredPid; 84 } 85 86 set hoveredPid(x) { 87 this._hoveredPid = x; 88 raf.scheduleCanvasRedraw(); 89 } 90 91 constructor(private readonly traceInfo: TraceInfo) { 92 this._visibleWindow = HighPrecisionTimeSpan.fromTime( 93 traceInfo.start, 94 traceInfo.end, 95 ); 96 } 97 98 // TODO: there is some redundancy in the fact that both |visibleWindowTime| 99 // and a |timeScale| have a notion of time range. That should live in one 100 // place only. 101 102 zoomVisibleWindow(ratio: number, centerPoint: number) { 103 this._visibleWindow = this._visibleWindow 104 .scale(ratio, centerPoint, MIN_DURATION) 105 .fitWithin(this.traceInfo.start, this.traceInfo.end); 106 107 raf.scheduleCanvasRedraw(); 108 } 109 110 panVisibleWindow(delta: number) { 111 this._visibleWindow = this._visibleWindow 112 .translate(delta) 113 .fitWithin(this.traceInfo.start, this.traceInfo.end); 114 115 raf.scheduleCanvasRedraw(); 116 } 117 118 // Given a timestamp, if |ts| is not currently in view move the view to 119 // center |ts|, keeping the same zoom level. 120 panToTimestamp(ts: time) { 121 if (this._visibleWindow.contains(ts)) return; 122 // TODO(hjd): This is an ugly jump, we should do a smooth pan instead. 123 const halfDuration = this.visibleWindow.duration / 2; 124 const newStart = new HighPrecisionTime(ts).subNumber(halfDuration); 125 const newWindow = new HighPrecisionTimeSpan( 126 newStart, 127 this._visibleWindow.duration, 128 ); 129 this.updateVisibleTimeHP(newWindow); 130 } 131 132 // Set visible window using an integer time span 133 updateVisibleTime(ts: TimeSpan) { 134 this.updateVisibleTimeHP(HighPrecisionTimeSpan.fromTime(ts.start, ts.end)); 135 } 136 137 // TODO(primiano): we ended up with two entry-points for the same function, 138 // unify them. 139 setViewportTime(start: time, end: time): void { 140 this.updateVisibleTime(new TimeSpan(start, end)); 141 } 142 143 moveStart(delta: number) { 144 this.updateVisibleTimeHP( 145 new HighPrecisionTimeSpan( 146 this._visibleWindow.start.addNumber(delta), 147 this.visibleWindow.duration - delta, 148 ), 149 ); 150 } 151 152 moveEnd(delta: number) { 153 this.updateVisibleTimeHP( 154 new HighPrecisionTimeSpan( 155 this._visibleWindow.start, 156 this.visibleWindow.duration + delta, 157 ), 158 ); 159 } 160 161 // Set visible window using a high precision time span 162 updateVisibleTimeHP(ts: HighPrecisionTimeSpan) { 163 this._visibleWindow = ts 164 .clampDuration(MIN_DURATION) 165 .fitWithin(this.traceInfo.start, this.traceInfo.end); 166 167 raf.scheduleCanvasRedraw(); 168 } 169 170 // Get the bounds of the visible window as a high-precision time span 171 get visibleWindow(): HighPrecisionTimeSpan { 172 return this._visibleWindow; 173 } 174 175 get hoverCursorTimestamp(): time | undefined { 176 return this._hoverCursorTimestamp; 177 } 178 179 set hoverCursorTimestamp(t: time | undefined) { 180 this._hoverCursorTimestamp = t; 181 raf.scheduleCanvasRedraw(); 182 } 183 184 // Offset between t=0 and the configured time domain. 185 timestampOffset(): time { 186 const fmt = timestampFormat(); 187 switch (fmt) { 188 case TimestampFormat.Timecode: 189 case TimestampFormat.Seconds: 190 case TimestampFormat.Milliseconds: 191 case TimestampFormat.Microseconds: 192 return this.traceInfo.start; 193 case TimestampFormat.TraceNs: 194 case TimestampFormat.TraceNsLocale: 195 return Time.ZERO; 196 case TimestampFormat.UTC: 197 return this.traceInfo.utcOffset; 198 case TimestampFormat.TraceTz: 199 return this.traceInfo.traceTzOffset; 200 default: 201 assertUnreachable(fmt); 202 } 203 } 204 205 // Convert absolute time to domain time. 206 toDomainTime(ts: time): time { 207 return Time.sub(ts, this.timestampOffset()); 208 } 209 210 get timestampFormat() { 211 return timestampFormat(); 212 } 213 214 set timestampFormat(format: TimestampFormat) { 215 setTimestampFormat(format); 216 } 217 218 get durationPrecision() { 219 return durationPrecision(); 220 } 221 222 set durationPrecision(precision: DurationPrecision) { 223 setDurationPrecision(precision); 224 } 225} 226