• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (C) 2022 Huawei Device Co., Ltd.
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 */
15
16import { BaseElement, element } from '../../../base-ui/BaseElement';
17import { TimeRuler } from './timer-shaft/TimeRuler';
18import { Rect } from './timer-shaft/Rect';
19import { RangeRuler, TimeRange } from './timer-shaft/RangeRuler';
20import { SlicesTime, SportRuler } from './timer-shaft/SportRuler';
21import { procedurePool } from '../../database/Procedure';
22import { Flag } from './timer-shaft/Flag';
23import { info } from '../../../log/Log';
24import { TraceSheet } from './base/TraceSheet';
25import { SelectionParam } from '../../bean/BoxSelection';
26import { type SpSystemTrace, CurrentSlicesTime } from '../SpSystemTrace';
27import './timer-shaft/CollapseButton';
28import { TimerShaftElementHtml } from './TimerShaftElement.html';
29//随机生成十六位进制颜色
30export function randomRgbColor() {
31  let r = Math.floor(Math.random() * 255);
32  let g = Math.floor(Math.random() * 255);
33  let b = Math.floor(Math.random() * 255);
34  if (r * 0.299 + g * 0.587 + b * 0.114 < 192) {
35    let r16 = r.toString(16).length === 1 && r.toString(16) <= 'f' ? 0 + r.toString(16) : r.toString(16);
36    let g16 = g.toString(16).length === 1 && g.toString(16) <= 'f' ? 0 + g.toString(16) : g.toString(16);
37    let b16 = b.toString(16).length === 1 && b.toString(16) <= 'f' ? 0 + b.toString(16) : b.toString(16);
38    let color = '#' + r16 + g16 + b16;
39    return color;
40  } else {
41    randomRgbColor();
42  }
43}
44
45export function ns2s(ns: number): string {
46  let one_second = 1_000_000_000; // 1 second
47  let one_millisecond = 1_000_000; // 1 millisecond
48  let one_microsecond = 1_000; // 1 microsecond
49  let nanosecond1 = 1000.0;
50  let result;
51  if (ns >= one_second) {
52    result = (ns / 1000 / 1000 / 1000).toFixed(1) + ' s';
53  } else if (ns >= one_millisecond) {
54    result = (ns / 1000 / 1000).toFixed(1) + ' ms';
55  } else if (ns >= one_microsecond) {
56    result = (ns / 1000).toFixed(1) + ' μs';
57  } else if (ns > 0) {
58    result = ns.toFixed(1) + ' ns';
59  } else {
60    result = ns.toFixed(1) + ' s';
61  }
62  return result;
63}
64
65export function ns2UnitS(ns: number, scale: number): string {
66  let one_second = 1_000_000_000; // 1 second
67  let result;
68  if (scale >= 10_000_000_000) {
69    result = (ns / one_second).toFixed(0) + ' s';
70  } else if (scale >= 1_000_000_000) {
71    result = (ns / one_second).toFixed(1) + ' s';
72  } else if (scale >= 100_000_000) {
73    result = (ns / one_second).toFixed(2) + ' s';
74  } else if (scale >= 10_000_000) {
75    result = (ns / one_second).toFixed(3) + ' s';
76  } else if (scale >= 1_000_000) {
77    result = (ns / one_second).toFixed(4) + ' s';
78  } else if (scale >= 100_000) {
79    result = (ns / one_second).toFixed(5) + ' s';
80  } else {
81    result = (ns / one_second).toFixed(6) + ' s';
82  }
83  return result;
84}
85
86export function ns2x(ns: number, startNS: number, endNS: number, duration: number, rect: Rect): number {
87  if (endNS == 0) {
88    endNS = duration;
89  }
90  let xSize: number = ((ns - startNS) * rect.width) / (endNS - startNS);
91  if (xSize < 0) {
92    xSize = 0;
93  }
94  if (xSize > rect.width) {
95    xSize = rect.width;
96  }
97  return xSize;
98}
99
100@element('timer-shaft-element')
101export class TimerShaftElement extends BaseElement {
102  // @ts-ignore
103  offscreen: OffscreenCanvas | undefined;
104  isOffScreen: boolean = false;
105  public ctx: CanvasRenderingContext2D | undefined | null;
106  public canvas: HTMLCanvasElement | null | undefined;
107  public totalEL: HTMLDivElement | null | undefined;
108  public timeTotalEL: HTMLSpanElement | null | undefined;
109  public timeOffsetEL: HTMLSpanElement | null | undefined;
110  public collectGroup: HTMLDivElement | null | undefined;
111  public collect1: HTMLInputElement | null | undefined;
112  public loadComplete: boolean = false;
113  public collecBtn: HTMLElement | null | undefined;
114  rangeChangeHandler: ((timeRange: TimeRange) => void) | undefined = undefined;
115  rangeClickHandler: ((sliceTime: SlicesTime | undefined | null) => void) | undefined = undefined;
116  flagChangeHandler: ((hoverFlag: Flag | undefined | null, selectFlag: Flag | undefined | null) => void) | undefined =
117    undefined;
118  flagClickHandler: ((flag: Flag | undefined | null) => void) | undefined = undefined;
119  /**
120   * 离线渲染需要的变量
121   */
122  dpr = window.devicePixelRatio || 1;
123  frame: Rect = new Rect(0, 0, 0, 0);
124  must: boolean = true;
125  hoverX: number = 0;
126  hoverY: number = 0;
127  canvasWidth: number = 0;
128  canvasHeight: number = 0;
129  _cpuUsage: Array<{ cpu: number; ro: number; rate: number }> = [];
130  protected timeRuler: TimeRuler | undefined;
131  protected _rangeRuler: RangeRuler | undefined;
132  protected _sportRuler: SportRuler | undefined;
133  private root: HTMLDivElement | undefined | null;
134  private _totalNS: number = 10_000_000_000;
135  private _startNS: number = 0;
136  private _endNS: number = 10_000_000_000;
137  private traceSheetEL: TraceSheet | undefined | null;
138  private sliceTime: SlicesTime | undefined | null;
139  public selectionList: Array<SelectionParam> = [];
140  public selectionMap: Map<string, SelectionParam> = new Map<string, SelectionParam>();
141
142  get sportRuler(): SportRuler | undefined {
143    return this._sportRuler;
144  }
145
146  get rangeRuler(): RangeRuler | undefined {
147    return this._rangeRuler;
148  }
149
150  set cpuUsage(value: Array<{ cpu: number; ro: number; rate: number }>) {
151    info('set cpuUsage values :', value);
152    this._cpuUsage = value;
153    if (this._rangeRuler) {
154      this._rangeRuler.cpuUsage = this._cpuUsage;
155    }
156  }
157
158  get totalNS(): number {
159    return this._totalNS;
160  }
161
162  set totalNS(value: number) {
163    info('set totalNS values :', value);
164    this._totalNS = value;
165    if (this.timeRuler) this.timeRuler.totalNS = value;
166    if (this._rangeRuler) this._rangeRuler.range.totalNS = value;
167    if (this.timeTotalEL) this.timeTotalEL.textContent = `${ns2s(value)}`;
168    requestAnimationFrame(() => this.render());
169  }
170
171  get startNS(): number {
172    return this._startNS;
173  }
174
175  set startNS(value: number) {
176    this._startNS = value;
177  }
178
179  get endNS(): number {
180    return this._endNS;
181  }
182
183  set endNS(value: number) {
184    this._endNS = value;
185  }
186
187  isScaling(): boolean {
188    return this._rangeRuler?.isPress || false;
189  }
190
191  reset(): void {
192    this.loadComplete = false;
193    this.totalNS = 10_000_000_000;
194    this.startNS = 0;
195    this.endNS = 10_000_000_000;
196    if (this._rangeRuler) {
197      this._rangeRuler.drawMark = false;
198      this._rangeRuler.range.totalNS = this.totalNS;
199      this._rangeRuler.markAObj.frame.x = 0;
200      this._rangeRuler.markBObj.frame.x = this._rangeRuler.frame.width;
201      this._rangeRuler.cpuUsage = [];
202      this.sportRuler!.flagList.length = 0;
203      this.sportRuler!.slicesTimeList.length = 0;
204      this.selectionList.length = 0;
205      this.selectionMap.clear();
206      this._rangeRuler.rangeRect = new Rect(0, 25, this.canvas?.clientWidth || 0, 75);
207      this.sportRuler!.isRangeSelect = false;
208      this.setSlicesMark();
209    }
210    this.removeTriangle('inverted');
211    this.setRangeNS(0, this.endNS);
212  }
213
214  initElements(): void {
215    this.root = this.shadowRoot?.querySelector('.root');
216    this.canvas = this.shadowRoot?.querySelector('.panel');
217    this.totalEL = this.shadowRoot?.querySelector('.total');
218    this.collect1 = this.shadowRoot?.querySelector('#collect1');
219    this.timeTotalEL = this.shadowRoot?.querySelector('.time-total');
220    this.timeOffsetEL = this.shadowRoot?.querySelector('.time-offset');
221    this.collecBtn = this.shadowRoot?.querySelector('.time-collect');
222    this.collectGroup = this.shadowRoot?.querySelector('.collect_group');
223    this.collectGroup?.addEventListener('click', (e) => {
224      // @ts-ignore
225      if (e.target && e.target.tagName === 'INPUT') {
226        // @ts-ignore
227        window.publish(window.SmartEvent.UI.CollectGroupChange, e.target.value);
228      }
229    });
230    procedurePool.timelineChange = (a: any) => this.rangeChangeHandler?.(a);
231    window.subscribe(window.SmartEvent.UI.TimeRange, (b) => this.setRangeNS(b.startNS, b.endNS));
232  }
233
234  getRangeRuler() {
235    return this._rangeRuler;
236  }
237
238  connectedCallback(): RangeRuler | undefined {
239    if (this.canvas) {
240      if (this.isOffScreen) {
241        // @ts-ignore
242        this.offscreen = this.canvas.transferControlToOffscreen();
243        return;
244      } else {
245        this.ctx = this.canvas?.getContext('2d', { alpha: true });
246      }
247    }
248    if (this.timeTotalEL) this.timeTotalEL.textContent = ns2s(this._totalNS);
249    if (this.timeOffsetEL && this._rangeRuler)
250      this.timeOffsetEL.textContent = ns2UnitS(this._startNS, this._rangeRuler.getScale());
251    const width = this.canvas?.clientWidth || 0;
252    const height = this.canvas?.clientHeight || 0;
253    this.setTimeRuler(width);
254    this.setSportRuler(width, height);
255    this.setRangeRuler(width);
256  }
257
258  private setTimeRuler(width: number): void {
259    if (!this.timeRuler) {
260      this.timeRuler = new TimeRuler(this, new Rect(0, 0, width, 20), this._totalNS);
261    }
262    this.timeRuler.frame.width = width;
263  }
264
265  private setSportRuler(width: number, height: number): void {
266    if (!this._sportRuler) {
267      this._sportRuler = new SportRuler(
268        this,
269        new Rect(0, 100, width, height - 100),
270        (hoverFlag, selectFlag) => {
271          this.flagChangeHandler?.(hoverFlag, selectFlag);
272        },
273        (flag) => {
274          this.flagClickHandler?.(flag);
275        },
276        (slicetime) => {
277          this.rangeClickHandler?.(slicetime);
278        }
279      );
280    }
281    this._sportRuler.frame.width = width;
282  }
283
284  private setRangeRuler(width: number): void {
285    if (!this._rangeRuler) {
286      this._rangeRuler = new RangeRuler(
287        this,
288        new Rect(0, 25, width, 75),
289        {
290          slicesTime: {
291            startTime: null,
292            endTime: null,
293            color: null,
294          },
295          scale: 0,
296          startX: 0,
297          endX: this.canvas?.clientWidth || 0,
298          startNS: 0,
299          endNS: this.totalNS,
300          totalNS: this.totalNS,
301          refresh: true,
302          xs: [],
303          xsTxt: [],
304        },
305        (a) => {
306          if (this._sportRuler) {
307            this._sportRuler.range = a;
308          }
309          if (this.timeOffsetEL && this._rangeRuler) {
310            this.timeOffsetEL.textContent = ns2UnitS(a.startNS, this._rangeRuler.getScale());
311          }
312          if (this.loadComplete) {
313            this.rangeChangeHandler?.(a);
314          }
315        }
316      );
317    }
318    this._rangeRuler.frame.width = width;
319  }
320
321  setRangeNS(startNS: number, endNS: number): void {
322    info('set startNS values :' + startNS + 'endNS values : ' + endNS);
323    this._rangeRuler?.setRangeNS(startNS, endNS);
324  }
325
326  getRange(): TimeRange | undefined {
327    return this._rangeRuler?.getRange();
328  }
329
330  updateWidth(width: number): void {
331    this.dpr = window.devicePixelRatio || 1;
332    this.canvas!.width = width - (this.totalEL?.clientWidth || 0);
333    this.canvas!.height = this.shadowRoot!.host.clientHeight || 0;
334    let oldWidth = this.canvas!.width;
335    let oldHeight = this.canvas!.height;
336    this.canvas!.width = Math.ceil(oldWidth * this.dpr);
337    this.canvas!.height = Math.ceil(oldHeight * this.dpr);
338    this.canvas!.style.width = oldWidth + 'px';
339    this.canvas!.style.height = oldHeight + 'px';
340    this.ctx?.scale(this.dpr, this.dpr);
341    this.ctx?.translate(0, 0);
342    this._rangeRuler!.frame.width = oldWidth;
343    this._sportRuler!.frame.width = oldWidth;
344    this.timeRuler!.frame.width = oldWidth;
345    this._rangeRuler?.fillX();
346    this.render();
347  }
348
349  documentOnMouseDown = (ev: MouseEvent): void => {
350    if ((window as any).isSheetMove) return;
351    this._rangeRuler?.mouseDown(ev);
352  };
353
354  documentOnMouseUp = (ev: MouseEvent): void => {
355    if ((window as any).isSheetMove) return;
356    this._rangeRuler?.mouseUp(ev);
357    this.sportRuler?.mouseUp(ev);
358  };
359
360  documentOnMouseMove = (ev: MouseEvent, trace: SpSystemTrace): void => {
361    trace.style.cursor = 'default';
362    let x = ev.offsetX - (this.canvas?.offsetLeft || 0); // 鼠标的x轴坐标
363    let y = ev.offsetY; // 鼠标的y轴坐标
364    let findSlicestime = this.sportRuler?.findSlicesTime(x, y); // 查找帽子
365    if (!findSlicestime) {
366      // 如果在该位置没有找到一个“帽子”,则可以显示一个旗子。
367      this.sportRuler?.showHoverFlag();
368      this._rangeRuler?.mouseMove(ev, trace);
369      if (this.sportRuler?.edgeDetection(ev)) {
370        this.sportRuler?.mouseMove(ev);
371      } else {
372        this.sportRuler?.mouseOut(ev);
373      }
374    } else {
375      this.sportRuler?.clearHoverFlag();
376      this.sportRuler?.modifyFlagList(null); //重新绘制旗子,清除hover flag
377    }
378  };
379
380  documentOnMouseOut = (ev: MouseEvent): void => {
381    this._rangeRuler?.mouseOut(ev);
382    this.sportRuler?.mouseOut(ev);
383  };
384
385  documentOnKeyPress = (ev: KeyboardEvent, currentSlicesTime?: CurrentSlicesTime): void => {
386    if ((window as any).isSheetMove) return;
387    if ((window as any).flagInputFocus) return;
388    this._rangeRuler?.keyPress(ev, currentSlicesTime);
389    this.sportRuler?.clearHoverFlag();
390  };
391
392  documentOnKeyUp = (ev: KeyboardEvent): void => {
393    if ((window as any).isSheetMove) return;
394    if ((window as any).flagInputFocus) return;
395    this._rangeRuler?.keyUp(ev);
396  };
397
398  disconnectedCallback(): void {}
399
400  firstRender = true;
401
402  lineColor(): string {
403    return window.getComputedStyle(this.canvas!, null).getPropertyValue('color');
404  }
405
406  render(): void {
407    this.dpr = window.devicePixelRatio || 1;
408    if (this.ctx) {
409      this.ctx.fillStyle = 'transparent';
410      this.ctx?.fillRect(0, 0, this.canvas?.width || 0, this.canvas?.height || 0);
411      this.timeRuler?.draw();
412      this._rangeRuler?.draw();
413      this._sportRuler?.draw();
414    } else {
415      procedurePool.submitWithName(
416        `timeline`,
417        `timeline`,
418        {
419          offscreen: this.must ? this.offscreen : undefined, //是否离屏
420          dpr: this.dpr, //屏幕dpr值
421          hoverX: this.hoverX,
422          hoverY: this.hoverY,
423          canvasWidth: this.canvasWidth,
424          canvasHeight: this.canvasHeight,
425          keyPressCode: null,
426          keyUpCode: null,
427          lineColor: '#dadada',
428          startNS: this.startNS,
429          endNS: this.endNS,
430          totalNS: this.totalNS,
431          frame: this.frame,
432        },
433        this.must ? this.offscreen : undefined,
434        (res: any) => {
435          this.must = false;
436        }
437      );
438    }
439  }
440
441  modifyFlagList(flag: Flag | null | undefined): void {
442    this._sportRuler?.modifyFlagList(flag);
443  }
444
445  modifySlicesList(slicestime: SlicesTime | null | undefined): void {
446    this._sportRuler?.modifySicesTimeList(slicestime);
447  }
448  cancelPressFrame(): void {
449    this._rangeRuler?.cancelPressFrame();
450  }
451
452  cancelUpFrame(): void {
453    this._rangeRuler?.cancelUpFrame();
454  }
455
456  stopWASD(ev: any): void {
457    this._rangeRuler?.keyUp(ev);
458  }
459
460  drawTriangle(time: number, type: string): number | undefined {
461    return this._sportRuler?.drawTriangle(time, type);
462  }
463
464  removeTriangle(type: string): void {
465    this._sportRuler?.removeTriangle(type);
466  }
467
468  setSlicesMark(
469    startTime: null | number = null,
470    endTime: null | number = null,
471    shiftKey: null | boolean = false
472  ): SlicesTime | null | undefined {
473    let sliceTime = this._sportRuler?.setSlicesMark(startTime, endTime, shiftKey);
474    if (sliceTime && sliceTime != undefined) {
475      this.traceSheetEL?.displayCurrent(sliceTime); // 给当前pane准备数据
476
477      // 取最新创建的那个selection对象
478      let selection = this.selectionList[this.selectionList.length - 1];
479      if (selection) {
480        selection.isCurrentPane = true; // 设置当前面板为可以显示的状态
481        //把刚刚创建的slicetime和selection对象关联起来,以便后面再次选中“跑道”的时候显示对应的面板。
482        this.selectionMap.set(sliceTime.id, selection);
483        this.traceSheetEL?.rangeSelect(selection); // 显示选中区域对应的面板
484      }
485    }
486    return sliceTime;
487  }
488
489  displayCollect(showCollect: boolean): void {
490    if (showCollect) {
491      this.collecBtn!.style.display = 'flex';
492    } else {
493      this.collecBtn!.style.display = 'none';
494    }
495  }
496
497  initHtml(): string {
498    return TimerShaftElementHtml;
499  }
500}
501