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