/** * A helper class that generates ranges of dummy data for buffer or texture operations * efficiently. Tries to minimize allocations and data updates. */ export class DataArrayGenerator { private dataBuffer = new Uint8Array(256); private lastOffset = 0; private lastStart = 0; private lastByteSize = 0; /** Find the nearest power of two greater than or equal to the input value. */ private nextPowerOfTwo(value: number) { return 1 << (32 - Math.clz32(value - 1)); } private generateData(byteSize: number, start: number = 0, offset: number = 0) { const prevSize = this.dataBuffer.length; if (prevSize < byteSize) { // If the requested data is larger than the allocated buffer, reallocate it to a buffer large // enough to handle the new request. const newData = new Uint8Array(this.nextPowerOfTwo(byteSize)); if (this.lastOffset === offset && this.lastStart === start && this.lastByteSize) { // Do a fast copy of any previous data that was generated. newData.set(this.dataBuffer); } this.dataBuffer = newData; } else if (this.lastOffset < offset) { // Ensure all values up to the offset are zeroed out. this.dataBuffer.fill(0, this.lastOffset, offset); } // If the offset or start values have changed, the whole data range needs to be regenerated. if (this.lastOffset !== offset || this.lastStart !== start) { this.lastByteSize = 0; } // Generate any new values that are required if (this.lastByteSize < byteSize) { for (let i = this.lastByteSize; i < byteSize - offset; ++i) { this.dataBuffer[i + offset] = ((i ** 3 + i + start) % 251) + 1; // Ensure data is always non-zero } this.lastOffset = offset; this.lastStart = start; this.lastByteSize = byteSize; } } /** * Returns a new view into the generated data that's the correct length. Because this is a view * previously returned views from the same generator will have their values overwritten as well. * @param {number} byteSize - Number of bytes the returned view should contain. * @param {number} [start] - The value of the first element generated in the view. * @param {number} [offset] - Offset of the generated data within the view. Preceeding values will be 0. * @returns {Uint8Array} A new Uint8Array view into the generated data. */ generateView(byteSize: number, start: number = 0, offset: number = 0): Uint8Array { this.generateData(byteSize, start, offset); if (this.dataBuffer.length === byteSize) { return this.dataBuffer; } return new Uint8Array(this.dataBuffer.buffer, 0, byteSize); } /** * Returns a copy of the generated data. Note that this still changes the underlying buffer, so * any previously generated views will still be overwritten, but the returned copy won't reflect * future generate* calls. * @param {number} byteSize - Number of bytes the returned array should contain. * @param {number} [start] - The value of the first element generated in the view. * @param {number} [offset] - Offset of the generated data within the view. Preceeding values will be 0. * @returns {Uint8Array} A new Uint8Array copy of the generated data. */ generateAndCopyView(byteSize: number, start: number = 0, offset: number = 0) { this.generateData(byteSize, start, offset); return this.dataBuffer.slice(0, byteSize); } }