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 { TraceRow } from '../../component/trace/base/TraceRow';
17import {
18  BaseStruct,
19  computeUnitWidth,
20  drawLoadingFrame,
21  isSurroundingPoint,
22  ns2x,
23  Rect,
24  Render,
25} from './ProcedureWorkerCommon';
26import { type AnimationRanges } from '../../bean/FrameComponentBean';
27import { ColorUtils } from '../../component/trace/base/ColorUtils';
28import { SpSystemTrace } from '../../component/SpSystemTrace';
29
30export class FrameDynamicRender extends Render {
31  renderMainThread(
32    req: {
33      useCache: boolean;
34      context: CanvasRenderingContext2D;
35      type: string;
36      animationRanges: AnimationRanges[];
37    },
38    row: TraceRow<FrameDynamicStruct>
39  ): void {
40    let frameDynamicList: FrameDynamicStruct[] = row.dataList;
41    let frameDynamicFilter: FrameDynamicStruct[] = row.dataListCache;
42    this.frameDynamic(frameDynamicList, frameDynamicFilter, row, req.animationRanges, req.useCache);
43    drawLoadingFrame(req.context, row.dataListCache, row);
44    if (req.animationRanges.length > 0 && req.animationRanges[0] && frameDynamicList.length > 0) {
45      let modelType: string = row.getAttribute('model-type') || 'x';
46      let [minValue, maxValue] = this.getMinAndMaxData(frameDynamicList, modelType);
47      let preDynamic: FrameDynamicStruct = frameDynamicList[0];
48      let isDraw = false;
49      let selectUnitWidth: number = 0;
50      for (let index: number = 0; index < frameDynamicList.length; index++) {
51        let currDynamic: FrameDynamicStruct = frameDynamicList[index]; // @ts-ignore
52        selectUnitWidth = computeUnitWidth(preDynamic.ts, currDynamic.ts, row.frame.width, selectUnitWidth);
53        this.refreshPointY(currDynamic, row, modelType, minValue, maxValue);
54        if (currDynamic.groupId === 0) {
55          if (currDynamic.ts > TraceRow.range!.startNS && currDynamic.ts < TraceRow.range!.endNS) {
56            isDraw = true;
57            this.drawSinglePoint(req.context, currDynamic, row, modelType, minValue, maxValue);
58          }
59        } else if (currDynamic.groupId !== invalidGroupId && index > 0 && currDynamic.groupId === preDynamic!.groupId) {
60          isDraw = true;
61          FrameDynamicStruct.draw(req.context, preDynamic, currDynamic, row, modelType);
62        }
63        FrameDynamicStruct.drawSelect(req.context, currDynamic, row);
64        preDynamic = currDynamic;
65      }
66      if (isDraw) {
67        // @ts-ignore
68        this.drawDynamicPointYStr(req.context, frameDynamicList, row.frame, minValue, maxValue);
69      }
70      if (!this.setHoverFrameDynamic(row, frameDynamicList, selectUnitWidth) && row.isHover) {
71        FrameDynamicStruct.hoverFrameDynamicStruct = undefined;
72      }
73    }
74  }
75
76  private setHoverFrameDynamic(
77    row: TraceRow<FrameDynamicStruct>,
78    frameDynamicFilter: FrameDynamicStruct[],
79    selectUnitWidth: number
80  ): boolean {
81    let find: boolean = false;
82    let findStructList = frameDynamicFilter.filter(
83      (filter) => row.isHover && isSurroundingPoint(row.hoverX, filter.frame!, selectUnitWidth / multiple)
84    );
85    if (findStructList.length > 0) {
86      find = true;
87      let hoverIndex: number = 0;
88      if (findStructList.length > unitIndex) {
89        hoverIndex = Math.ceil(findStructList.length / multiple);
90      }
91      FrameDynamicStruct.hoverFrameDynamicStruct = findStructList[hoverIndex];
92    }
93    return find;
94  }
95
96  private frameDynamic(
97    dynamicList: FrameDynamicStruct[],
98    dynamicFilter: FrameDynamicStruct[],
99    row: TraceRow<FrameDynamicStruct>,
100    animationRanges: AnimationRanges[],
101    use: boolean
102  ): void {
103    let startNS: number = TraceRow.range!.startNS;
104    let endNS: number = TraceRow.range!.endNS;
105    let totalNS: number = TraceRow.range!.totalNS; // @ts-ignore
106    let frame: Rect = row.frame;
107    let modelName: string | undefined | null = row.getAttribute('model-name');
108    if ((use || !TraceRow.range!.refresh) && dynamicFilter.length > 0) {
109      dynamicList.length = 0;
110      let groupIdList: number[] = [];
111      for (let dataIndex: number = 0; dataIndex < dynamicFilter.length; dataIndex++) {
112        let currentDynamic: FrameDynamicStruct = dynamicFilter[dataIndex];
113        if (currentDynamic.appName === modelName) {
114          currentDynamic.groupId = invalidGroupId;
115          for (let rangeIndex = 0; rangeIndex < animationRanges.length; rangeIndex++) {
116            let currentRange = animationRanges[rangeIndex];
117            if (currentDynamic.ts >= currentRange.start && currentDynamic.ts <= currentRange.end) {
118              currentDynamic.groupId = currentRange.start;
119              break;
120            }
121          }
122          if (
123            currentDynamic.ts < startNS &&
124            dataIndex + unitIndex < dynamicFilter.length &&
125            dynamicFilter[dataIndex + unitIndex].ts >= startNS &&
126            currentDynamic.groupId !== invalidGroupId
127          ) {
128            this.refreshFilterDynamicFrame(dynamicList, currentDynamic, frame, startNS, endNS, totalNS, groupIdList);
129          }
130          if (currentDynamic.ts >= startNS && currentDynamic.ts <= endNS && currentDynamic.groupId !== invalidGroupId) {
131            this.refreshFilterDynamicFrame(dynamicList, currentDynamic, frame, startNS, endNS, totalNS, groupIdList);
132          }
133          if (currentDynamic.ts >= endNS && currentDynamic.groupId !== invalidGroupId) {
134            this.refreshFilterDynamicFrame(dynamicList, currentDynamic, frame, startNS, endNS, totalNS, groupIdList);
135            break;
136          }
137        }
138      }
139      this.setSimpleGroupId(groupIdList, dynamicList);
140    }
141  }
142
143  private setSimpleGroupId(groupIdList: number[], frameDynamicFilter: FrameDynamicStruct[]): void {
144    let simpleGroup = groupIdList.filter((groupId) => {
145      return groupId !== invalidGroupId && groupIdList.indexOf(groupId) === groupIdList.lastIndexOf(groupId);
146    });
147    frameDynamicFilter.forEach((dynamic) => {
148      if (simpleGroup.indexOf(dynamic.groupId!) > invalidGroupId) {
149        dynamic.groupId = 0;
150      }
151    });
152  }
153
154  private refreshDynamicFrame(
155    frameDynamicFilter: FrameDynamicStruct[],
156    frame: Rect,
157    startNS: number,
158    endNS: number,
159    totalNS: number,
160    modelName: string
161  ): void {
162    for (let index: number = 0; index < frameDynamicFilter.length; index++) {
163      let frameDynamicNode: FrameDynamicStruct = frameDynamicFilter[index];
164      frameDynamicNode.frame = undefined;
165      if (frameDynamicNode.appName === modelName) {
166        FrameDynamicStruct.setFrameDynamic(frameDynamicNode, startNS, endNS, totalNS, frame);
167      }
168    }
169  }
170
171  private drawSinglePoint(
172    ctx: CanvasRenderingContext2D,
173    currDynamic: FrameDynamicStruct,
174    row: TraceRow<FrameDynamicStruct>,
175    modelType: string,
176    minValue: number,
177    maxValue: number
178  ): void {
179    let smallArcRadius: number = 2;
180    // @ts-ignore
181    currDynamic.typeValue = currDynamic[modelType];
182    currDynamic.frame!.y = // @ts-ignore
183      row.frame.height -
184      padding - // @ts-ignore
185      ((row.frame.height - padding * multiple) * ((currDynamic.typeValue || 0) - minValue)) / (maxValue - minValue);
186    ctx.beginPath();
187    ctx.lineWidth = 1;
188    ctx.globalAlpha = 1;
189    ctx.arc(currDynamic.frame!.x, currDynamic.frame!.y, smallArcRadius, 0, multiple * Math.PI);
190    ctx.strokeStyle = ColorUtils.ANIMATION_COLOR[2];
191    ctx.fillStyle = ColorUtils.ANIMATION_COLOR[2];
192    ctx.stroke();
193    ctx.fill();
194    ctx.closePath();
195  }
196
197  private refreshPointY(
198    curDynamic: FrameDynamicStruct,
199    row: TraceRow<FrameDynamicStruct>,
200    modelType: string,
201    minValue: number,
202    maxValue: number
203  ): void {
204    // @ts-ignore
205    let currDynamicValue = curDynamic[modelType];
206    if (curDynamic.frame) {
207      // @ts-ignore
208      let pointY = (row.frame.height - padding * multiple) * ((currDynamicValue - minValue) / (maxValue - minValue)); // @ts-ignore
209      curDynamic.frame.y = row.frame.height - padding - pointY;
210    }
211  }
212
213  private drawDynamicPointYStr(
214    ctx: CanvasRenderingContext2D,
215    frameDynamicFilter: FrameDynamicStruct[],
216    frame: Rect,
217    minValue: number,
218    maxValue: number
219  ): void {
220    if (frameDynamicFilter.length > 0) {
221      ctx.globalAlpha = 1;
222      ctx.lineWidth = 1;
223      ctx.fillStyle = ColorUtils.ANIMATION_COLOR[3];
224      let minUnitValue: number = 10;
225      let fixedNumber = 2;
226      let totalValue = maxValue - minValue;
227      let pointYInterval = totalValue / (yScaleNumber - unitIndex);
228      for (let index = 0; index < yScaleNumber; index++) {
229        let pointYValue = minValue + pointYInterval * index;
230        let pointYHeight = ((frame.height - padding * multiple) * (pointYValue - minValue)) / totalValue;
231        let pointY = frame.height - multiple * padding - pointYHeight;
232        if (pointYValue !== 0) {
233          if (maxValue - minValue <= minUnitValue) {
234            ctx.fillText(`- ${pointYValue.toFixed(fixedNumber)}`, 0, pointY);
235          } else {
236            ctx.fillText(`- ${Math.ceil(pointYValue)}`, 0, pointY);
237          }
238        }
239      }
240    }
241  }
242
243  private getMinAndMaxData(frameDynamicFilter: FrameDynamicStruct[], modelType: string): [number, number] {
244    let min: number = Math.min.apply(
245      Math,
246      frameDynamicFilter.map((filterData) => {
247        // @ts-ignore
248        return filterData[modelType];
249      })
250    );
251    let max: number = Math.max.apply(
252      Math,
253      frameDynamicFilter.map((filterData) => {
254        // @ts-ignore
255        return filterData[modelType];
256      })
257    );
258
259    let yScaleMinValue: number = 1;
260    let yScaleMinSpacing: number = 10;
261    if (min === max) {
262      if (max <= yScaleMinValue) {
263        max = yScaleMinValue;
264        min = 0;
265      } else if (max <= yScaleMinSpacing) {
266        max = yScaleMinSpacing;
267        min = 0;
268      } else {
269        max += yScaleMinSpacing;
270        min -= yScaleMinSpacing;
271      }
272    }
273    return [min, max];
274  }
275
276  private refreshFilterDynamicFrame(
277    frameDynamicFilter: FrameDynamicStruct[],
278    currentFrameDynamic: FrameDynamicStruct,
279    frame: Rect,
280    startNS: number,
281    endNS: number,
282    totalNS: number,
283    groupIdList: number[]
284  ): void {
285    groupIdList.push(currentFrameDynamic.groupId!);
286    frameDynamicFilter.push(currentFrameDynamic);
287    FrameDynamicStruct.setFrameDynamic(currentFrameDynamic, startNS, endNS, totalNS, frame);
288  }
289}
290export function FrameDynamicStructOnClick(
291  clickRowType: string,
292  sp: SpSystemTrace,
293  //@ts-ignore
294  row: undefined | TraceRow<unknown>,
295  entry?: FrameDynamicStruct,
296): Promise<unknown> {
297  return new Promise((resolve, reject) => {
298    if (clickRowType === TraceRow.ROW_TYPE_FRAME_DYNAMIC) {
299      //@ts-ignore
300      FrameDynamicStruct.selectFrameDynamicStruct =
301        FrameDynamicStruct.hoverFrameDynamicStruct || row?.getHoverStruct(false, true);
302      if (FrameDynamicStruct.selectFrameDynamicStruct || entry) {
303        let data = entry || FrameDynamicStruct.selectFrameDynamicStruct;
304        //@ts-ignore
305        sp.traceSheetEL?.displayFrameDynamicData(row!, data!);
306        sp.timerShaftEL?.modifyFlagList(undefined);
307      }
308      reject(new Error());
309    } else {
310      resolve(null);
311    }
312  });
313}
314export class FrameDynamicStruct extends BaseStruct {
315  static hoverFrameDynamicStruct: FrameDynamicStruct | undefined;
316  static selectFrameDynamicStruct: FrameDynamicStruct | undefined;
317  ts: number = 0;
318  id: number | undefined;
319  x: number | undefined;
320  y: number | undefined;
321  width: number | undefined;
322  height: number | undefined;
323  alpha: number = 0;
324  appName: string | undefined;
325  groupId: number | undefined;
326  typeValue: number | undefined;
327
328  static setFrameDynamic(
329    dynamicNode: FrameDynamicStruct,
330    startNS: number,
331    endNS: number,
332    totalNS: number,
333    frame: Rect
334  ): void {
335    let pointX: number = ns2x(dynamicNode.ts || 0, startNS, endNS, totalNS, frame);
336    if (!dynamicNode.frame) {
337      dynamicNode.frame = new Rect(0, 0, 0, 0);
338    }
339    dynamicNode.frame.x = Math.floor(pointX);
340    dynamicNode.frame.height = frame.height;
341  }
342
343  static draw(
344    ctx: CanvasRenderingContext2D,
345    preDynamicStruct: FrameDynamicStruct,
346    currDynamicStruct: FrameDynamicStruct,
347    row: TraceRow<FrameDynamicStruct>,
348    modelType: string
349  ): void {
350    if (preDynamicStruct.frame && currDynamicStruct.frame) {
351      // @ts-ignore
352      preDynamicStruct.typeValue = preDynamicStruct[modelType];
353      // @ts-ignore
354      currDynamicStruct.typeValue = currDynamicStruct[modelType];
355      if (preDynamicStruct.typeValue === currDynamicStruct.typeValue) {
356        ctx.strokeStyle = ColorUtils.ANIMATION_COLOR[1];
357      } else {
358        ctx.strokeStyle = ColorUtils.ANIMATION_COLOR[2];
359      }
360      ctx.beginPath();
361      ctx.globalAlpha = 1;
362      ctx.lineWidth = 3;
363      ctx.lineJoin = 'round';
364      ctx.moveTo(preDynamicStruct.frame.x, preDynamicStruct.frame.y);
365      ctx.lineTo(currDynamicStruct.frame.x, currDynamicStruct.frame.y);
366      ctx.stroke();
367      ctx.closePath();
368      FrameDynamicStruct.drawSelect(ctx, preDynamicStruct, row);
369    }
370  }
371
372  static drawSelect(
373    ctx: CanvasRenderingContext2D,
374    currDynamicStruct: FrameDynamicStruct,
375    row: TraceRow<FrameDynamicStruct>
376  ): void {
377    if (
378      (currDynamicStruct === FrameDynamicStruct.hoverFrameDynamicStruct && row.isHover) ||
379      currDynamicStruct === FrameDynamicStruct.selectFrameDynamicStruct
380    ) {
381      FrameDynamicStruct.drawSelectOrHoverArc(ctx, currDynamicStruct);
382    }
383    if (row.getAttribute('check-type') === '2' && FrameDynamicStruct.isSelect(currDynamicStruct)) {
384      FrameDynamicStruct.drawSelectOrHoverArc(ctx, currDynamicStruct);
385    }
386  }
387
388  static drawSelectOrHoverArc(ctx: CanvasRenderingContext2D, currDynamicStruct: FrameDynamicStruct): void {
389    if (
390      currDynamicStruct.frame &&
391      currDynamicStruct.ts > TraceRow.range!.startNS &&
392      currDynamicStruct.ts < TraceRow.range!.endNS
393    ) {
394      let bigArcRadius: number = 3;
395      ctx.beginPath();
396      ctx.lineWidth = 3;
397      ctx.globalAlpha = 1;
398      ctx.arc(currDynamicStruct.frame.x, currDynamicStruct.frame.y, bigArcRadius, 0, multiple * Math.PI);
399      ctx.strokeStyle = ColorUtils.ANIMATION_COLOR[7];
400      ctx.fillStyle = ColorUtils.ANIMATION_COLOR[9];
401      ctx.stroke();
402      ctx.fill();
403    }
404  }
405
406  static isSelect(currDynamicStruct: FrameDynamicStruct): boolean | 0 | undefined {
407    return (
408      TraceRow.rangeSelectObject &&
409      TraceRow.rangeSelectObject.startNS &&
410      TraceRow.rangeSelectObject.endNS &&
411      currDynamicStruct.ts >= TraceRow.rangeSelectObject.startNS &&
412      currDynamicStruct.ts <= TraceRow.rangeSelectObject.endNS
413    );
414  }
415}
416
417const padding: number = 3;
418const invalidGroupId: number = -1;
419const multiple: number = 2;
420const unitIndex: number = 1;
421const yScaleNumber: number = 5;
422