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