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