• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright (C) 2023 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 {BigintMath} from '../base/bigint_math';
16import {Disposable} from '../base/disposable';
17import {duration, Time, time, TimeSpan} from '../base/time';
18export {Store} from '../base/store';
19import {raf} from '../core/raf_scheduler';
20import {globals} from '../frontend/globals';
21
22type FetchTimeline<Data> = (
23  start: time,
24  end: time,
25  resolution: duration,
26) => Promise<Data>;
27
28// This helper provides the logic to call |doFetch()| only when more
29// data is needed as the visible window is panned and zoomed about, and
30// includes an FSM to ensure doFetch is not re-entered.
31export class TimelineFetcher<Data> implements Disposable {
32  private doFetch: FetchTimeline<Data>;
33
34  private data_?: Data;
35
36  // Timespan and resolution of the latest *request*. data_ may cover
37  // a different time window.
38  private latestTimespan: TimeSpan;
39  private latestResolution: duration;
40
41  constructor(doFetch: FetchTimeline<Data>) {
42    this.doFetch = doFetch;
43    this.latestTimespan = TimeSpan.ZERO;
44    this.latestResolution = 0n;
45  }
46
47  async requestDataForCurrentTime(): Promise<void> {
48    const currentTimeSpan = globals.timeline.visibleTimeSpan;
49    const currentResolution = globals.getCurResolution();
50    await this.requestData(currentTimeSpan, currentResolution);
51  }
52
53  async requestData(timespan: TimeSpan, resolution: duration): Promise<void> {
54    if (this.shouldLoadNewData(timespan, resolution)) {
55      // Over request data, one page worth to the left and right.
56      const start = timespan.start - timespan.duration;
57      const end = timespan.end + timespan.duration;
58
59      // Quantize up and down to the bounds of |resolution|.
60      const startQ = Time.fromRaw(BigintMath.quantFloor(start, resolution));
61      const endQ = Time.fromRaw(BigintMath.quantCeil(end, resolution));
62
63      this.latestTimespan = new TimeSpan(startQ, endQ);
64      this.latestResolution = resolution;
65      await this.loadData();
66    }
67  }
68
69  get data(): Data | undefined {
70    return this.data_;
71  }
72
73  invalidate() {
74    this.data_ = undefined;
75  }
76
77  dispose() {
78    this.data_ = undefined;
79  }
80
81  private shouldLoadNewData(timespan: TimeSpan, resolution: duration): boolean {
82    if (this.data_ === undefined) {
83      return true;
84    }
85
86    if (timespan.start < this.latestTimespan.start) {
87      return true;
88    }
89
90    if (timespan.end > this.latestTimespan.end) {
91      return true;
92    }
93
94    if (resolution !== this.latestResolution) {
95      return true;
96    }
97
98    return false;
99  }
100
101  private async loadData(): Promise<void> {
102    const {start, end} = this.latestTimespan;
103    const resolution = this.latestResolution;
104    this.data_ = await this.doFetch(start, end, resolution);
105    raf.scheduleRedraw();
106  }
107}
108