1/* 2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17import {FrameMap} from './frame_map'; 18import { 19 AbsoluteEntryIndex, 20 AbsoluteFrameIndex, 21 FramesRange, 22} from './index_types'; 23 24export class FrameMapBuilder { 25 private readonly lengthEntries: number; 26 private readonly lengthFrames: number; 27 28 // See comments in FrameMap about the semantics of these lookup tables 29 private readonly entryToStartFrame: Array<AbsoluteFrameIndex | undefined>; 30 private readonly entryToEndFrame: Array<AbsoluteFrameIndex | undefined>; 31 private readonly frameToStartEntry: Array<AbsoluteEntryIndex | undefined>; 32 private readonly frameToEndEntry: Array<AbsoluteEntryIndex | undefined>; 33 34 private isFinalized = false; 35 36 constructor(lengthEntries: number, lengthFrames: number) { 37 this.lengthEntries = lengthEntries; 38 this.lengthFrames = lengthFrames; 39 40 this.entryToStartFrame = new Array<AbsoluteFrameIndex | undefined>( 41 this.lengthEntries, 42 ).fill(undefined); 43 this.entryToEndFrame = new Array<AbsoluteFrameIndex | undefined>( 44 this.lengthEntries, 45 ).fill(undefined); 46 this.frameToStartEntry = new Array<AbsoluteEntryIndex | undefined>( 47 this.lengthFrames, 48 ).fill(undefined); 49 this.frameToEndEntry = new Array<AbsoluteEntryIndex | undefined>( 50 this.lengthFrames, 51 ).fill(undefined); 52 } 53 54 setFrames( 55 entry: AbsoluteEntryIndex, 56 range: FramesRange | undefined, 57 ): FrameMapBuilder { 58 this.checkIsNotFinalized(); 59 if (!range || range.start === range.end) { 60 return this; 61 } 62 63 this.setStartArrayValue(this.entryToStartFrame, entry, range.start); 64 this.setEndArrayValue(this.entryToEndFrame, entry, range.end); 65 66 for (let frame = range.start; frame < range.end; ++frame) { 67 this.setStartArrayValue(this.frameToStartEntry, frame, entry); 68 this.setEndArrayValue(this.frameToEndEntry, frame, entry + 1); 69 } 70 71 return this; 72 } 73 74 build(): FrameMap { 75 this.checkIsNotFinalized(); 76 this.finalizeStartArray(this.entryToStartFrame); 77 this.finalizeEndArray(this.entryToEndFrame); 78 this.finalizeStartArray(this.frameToStartEntry); 79 this.finalizeEndArray(this.frameToEndEntry); 80 this.isFinalized = true; 81 return new FrameMap( 82 this.lengthEntries, 83 this.lengthFrames, 84 this.entryToStartFrame, 85 this.entryToEndFrame, 86 this.frameToStartEntry, 87 this.frameToEndEntry, 88 ); 89 } 90 91 private setStartArrayValue( 92 array: Array<number | undefined>, 93 index: number, 94 value: number, 95 ) { 96 const currentValue = array[index]; 97 if (currentValue === undefined) { 98 array[index] = value; 99 } else { 100 array[index] = Math.min(currentValue, value); 101 } 102 } 103 104 private setEndArrayValue( 105 array: Array<number | undefined>, 106 index: number, 107 value: number, 108 ) { 109 const currentValue = array[index]; 110 if (currentValue === undefined) { 111 array[index] = value; 112 } else { 113 array[index] = Math.max(currentValue, value); 114 } 115 } 116 117 private finalizeStartArray(array: Array<number | undefined>) { 118 let firstValidStart: number | undefined = undefined; 119 for (let i = array.length - 1; i >= 0; --i) { 120 if (array[i] === undefined) { 121 array[i] = firstValidStart; 122 } else { 123 firstValidStart = array[i]; 124 } 125 } 126 } 127 128 private finalizeEndArray(array: Array<number | undefined>) { 129 let lastValidEnd: number | undefined = undefined; 130 for (let i = 0; i < array.length; ++i) { 131 if (array[i] === undefined) { 132 array[i] = lastValidEnd; 133 } else { 134 lastValidEnd = array[i]; 135 } 136 } 137 } 138 139 private checkIsNotFinalized() { 140 if (this.isFinalized) { 141 throw new Error('Attemped to modify already finalized frame map.'); 142 } 143 } 144} 145