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 {AbsoluteEntryIndex, AbsoluteFrameIndex, FramesRange} from './index_types'; 19 20export class FrameMapBuilder { 21 private readonly lengthEntries: number; 22 private readonly lengthFrames: number; 23 24 // See comments in FrameMap about the semantics of these lookup tables 25 private readonly entryToStartFrame: Array<AbsoluteFrameIndex | undefined>; 26 private readonly entryToEndFrame: Array<AbsoluteFrameIndex | undefined>; 27 private readonly frameToStartEntry: Array<AbsoluteEntryIndex | undefined>; 28 private readonly frameToEndEntry: Array<AbsoluteEntryIndex | undefined>; 29 30 private isFinalized = false; 31 32 constructor(lengthEntries: number, lengthFrames: number) { 33 this.lengthEntries = lengthEntries; 34 this.lengthFrames = lengthFrames; 35 36 this.entryToStartFrame = new Array<AbsoluteFrameIndex | undefined>(this.lengthEntries).fill( 37 undefined 38 ); 39 this.entryToEndFrame = new Array<AbsoluteFrameIndex | undefined>(this.lengthEntries).fill( 40 undefined 41 ); 42 this.frameToStartEntry = new Array<AbsoluteEntryIndex | undefined>(this.lengthFrames).fill( 43 undefined 44 ); 45 this.frameToEndEntry = new Array<AbsoluteEntryIndex | undefined>(this.lengthFrames).fill( 46 undefined 47 ); 48 } 49 50 setFrames(entry: AbsoluteEntryIndex, range: FramesRange | undefined): FrameMapBuilder { 51 this.checkIsNotFinalized(); 52 if (!range || range.start === range.end) { 53 return this; 54 } 55 56 this.setStartArrayValue(this.entryToStartFrame, entry, range.start); 57 this.setEndArrayValue(this.entryToEndFrame, entry, range.end); 58 59 for (let frame = range.start; frame < range.end; ++frame) { 60 this.setStartArrayValue(this.frameToStartEntry, frame, entry); 61 this.setEndArrayValue(this.frameToEndEntry, frame, entry + 1); 62 } 63 64 return this; 65 } 66 67 build(): FrameMap { 68 this.checkIsNotFinalized(); 69 this.finalizeStartArray(this.entryToStartFrame); 70 this.finalizeEndArray(this.entryToEndFrame); 71 this.finalizeStartArray(this.frameToStartEntry); 72 this.finalizeEndArray(this.frameToEndEntry); 73 this.isFinalized = true; 74 return new FrameMap( 75 this.lengthEntries, 76 this.lengthFrames, 77 this.entryToStartFrame, 78 this.entryToEndFrame, 79 this.frameToStartEntry, 80 this.frameToEndEntry 81 ); 82 } 83 84 private setStartArrayValue(array: Array<number | undefined>, index: number, value: number) { 85 const currentValue = array[index]; 86 if (currentValue === undefined) { 87 array[index] = value; 88 } else { 89 array[index] = Math.min(currentValue, value); 90 } 91 } 92 93 private setEndArrayValue(array: Array<number | undefined>, index: number, value: number) { 94 const currentValue = array[index]; 95 if (currentValue === undefined) { 96 array[index] = value; 97 } else { 98 array[index] = Math.max(currentValue, value); 99 } 100 } 101 102 private finalizeStartArray(array: Array<number | undefined>) { 103 let firstValidStart: number | undefined = undefined; 104 for (let i = array.length - 1; i >= 0; --i) { 105 if (array[i] === undefined) { 106 array[i] = firstValidStart; 107 } else { 108 firstValidStart = array[i]; 109 } 110 } 111 } 112 113 private finalizeEndArray(array: Array<number | undefined>) { 114 let lastValidEnd: number | undefined = undefined; 115 for (let i = 0; i < array.length; ++i) { 116 if (array[i] === undefined) { 117 array[i] = lastValidEnd; 118 } else { 119 lastValidEnd = array[i]; 120 } 121 } 122 } 123 124 private checkIsNotFinalized() { 125 if (this.isFinalized) { 126 throw new Error('Attemped to modify already finalized frame map.'); 127 } 128 } 129} 130