1/* 2 * Copyright (c) 2024 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 16interface ToleranceRange { 17 leftToleranceEdge: number; 18 rightToleranceEdge: number; 19 prefetchCountMaxRatioLeft: number; 20 prefetchCountMaxRatioRight: number; 21} 22 23type UpdateResult = 'ratio-changed' | 'ratio-not-changed'; 24 25class PrefetchRangeRatio { 26 private readonly itemsOnScreen: ItemsOnScreenProvider; 27 private readonly fetchedRegistry: FetchedRegistry; 28 private readonly TOLERANCE_RANGES: [ToleranceRange, ToleranceRange] = [ 29 { 30 leftToleranceEdge: 180, 31 rightToleranceEdge: 250, 32 prefetchCountMaxRatioLeft: 0.5, 33 prefetchCountMaxRatioRight: 1, 34 }, 35 { 36 leftToleranceEdge: 3000, 37 rightToleranceEdge: 4000, 38 prefetchCountMaxRatioLeft: 1, 39 prefetchCountMaxRatioRight: 0.25, 40 }, 41 ]; 42 private readonly ACTIVE_DEGREE = 0.5; 43 private readonly VISIBLE_DEGREE = 2.5; 44 private meanPrefetchTime = 0; 45 private leftToleranceEdge = Number.MIN_VALUE; 46 private rightToleranceEdge = 250; 47 private oldRatio = 0; 48 49 constructor(itemsOnScreen: ItemsOnScreenProvider, fetchedRegistry: FetchedRegistry) { 50 this.itemsOnScreen = itemsOnScreen; 51 this.fetchedRegistry = fetchedRegistry; 52 } 53 54 private _range = RatioRange.newEmpty(); 55 56 get range(): RatioRange { 57 return this._range; 58 } 59 60 private _maxRatio = 0.5; 61 62 get maxRatio(): number { 63 return this._maxRatio; 64 } 65 66 private updateTiming(prefetchDuration: number): void { 67 // Check if not from file storage 68 if (prefetchDuration > 20) { 69 const weight = 0.95; 70 this.meanPrefetchTime = this.meanPrefetchTime * weight + (1 - weight) * prefetchDuration; 71 } 72 Logger.log(`prefetchDifference prefetchDur=${prefetchDuration}, meanPrefetchDur=${this.meanPrefetchTime}`); 73 } 74 75 update(prefetchDuration: number): UpdateResult { 76 this.updateTiming(prefetchDuration); 77 78 if (this.meanPrefetchTime >= this.leftToleranceEdge && this.meanPrefetchTime <= this.rightToleranceEdge) { 79 return 'ratio-not-changed'; 80 } 81 82 let ratioChanged = false; 83 84 if (this.meanPrefetchTime > this.rightToleranceEdge) { 85 for (let i = 0; i < this.TOLERANCE_RANGES.length; i++) { 86 const limit = this.TOLERANCE_RANGES[i]; 87 if (this.meanPrefetchTime > limit.rightToleranceEdge) { 88 ratioChanged = true; 89 this._maxRatio = limit.prefetchCountMaxRatioRight; 90 this.leftToleranceEdge = limit.leftToleranceEdge; 91 if (i + 1 !== this.TOLERANCE_RANGES.length) { 92 this.rightToleranceEdge = this.TOLERANCE_RANGES[i + 1].rightToleranceEdge; 93 } else { 94 this.rightToleranceEdge = Number.MAX_VALUE; 95 } 96 } 97 } 98 } else if (this.meanPrefetchTime < this.leftToleranceEdge) { 99 for (let i = this.TOLERANCE_RANGES.length - 1; i >= 0; i--) { 100 const limit = this.TOLERANCE_RANGES[i]; 101 if (this.meanPrefetchTime < limit.leftToleranceEdge) { 102 ratioChanged = true; 103 this._maxRatio = limit.prefetchCountMaxRatioLeft; 104 this.rightToleranceEdge = limit.rightToleranceEdge; 105 if (i !== 0) { 106 this.leftToleranceEdge = this.TOLERANCE_RANGES[i - 1].leftToleranceEdge; 107 } else { 108 this.leftToleranceEdge = Number.MIN_VALUE; 109 } 110 } 111 } 112 } 113 return ratioChanged ? 'ratio-changed' : 'ratio-not-changed'; 114 } 115 116 calculateRatio(prefetchCount: number, totalCount: number): number { 117 const visibleRange = this.itemsOnScreen.visibleRange; 118 119 const start = Math.max(0, visibleRange.start - prefetchCount); 120 const end = Math.min(totalCount, visibleRange.end + prefetchCount); 121 const evaluatedPrefetchRange = new IndexRange(start, end); 122 123 const completedActive = this.fetchedRegistry.getFetchedInRange(evaluatedPrefetchRange); 124 const completedVisible = this.fetchedRegistry.getFetchedInRange(visibleRange); 125 126 return Math.min( 127 1, 128 Math.pow(completedActive / evaluatedPrefetchRange.length, this.ACTIVE_DEGREE) * 129 Math.pow(completedVisible / visibleRange.length, this.VISIBLE_DEGREE), 130 ); 131 } 132 133 updateRatioRange(ratio: number): void { 134 if (ratio > this.oldRatio) { 135 this._range = new RatioRange(new RangeEdge(this.oldRatio, false), new RangeEdge(ratio, true)); 136 } else { 137 this._range = new RatioRange(new RangeEdge(ratio, true), new RangeEdge(this.oldRatio, false)); 138 } 139 this.oldRatio = ratio; 140 } 141} 142