# Block based Float32Array builder

**URL:** <https://forum.babylonjs.com/t/block-based-float32array-builder/60411>\
**Category:** Off topic\
**Tags:** performance, memory\
**Created:** [September 8, 2025, 1:22am UTC](https://forum.babylonjs.com/t/block-based-float32array-builder/60411 "2025-09-08T01:22:16Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

**Author:** ![kzhsw](https://avatars.discourse-cdn.com/v4/letter/k/848f3c/32.png) [@kzhsw](https://forum.babylonjs.com/u/kzhsw)\
**Post date:** [September 8, 2025, 1:22am UTC](https://forum.babylonjs.com/t/block-based-float32array-builder/60411/1 "2025-09-08T01:22:16Z")

</div>

The performance is expected to be **worse** than preallocated buffers with correct size.  
The core idea is to manage data in fixed-size blocks (`Float32Array`s) and only combine them into a single, contiguous `Float32Array` when explicitly requested. This avoids the overhead of dynamic resizing that standard JavaScript arrays incur.  
This can be used to replace `Array.push` when final size is not known at creation.  
Memory blocks not pooled yet (and unsure if really needed).  
Code is created with the help of AI.

```js
/**
 * A block-based builder for Float32Array, optimized for performance.
 * It allocates memory in fixed-size blocks and consolidates them into a single
 * Float32Array upon request.
 */
class BlockBasedFloat32ArrayBuilder {
    /**
     * @param {number} blockSize - The size of each block in number of elements.
     * A power of 2 is often a good choice for performance.
     * @param {number} [initialCapacity=blockSize] - The initial total capacity in elements.
     */
    constructor(blockSize, initialCapacity = blockSize) {
        if (typeof blockSize !== 'number' || blockSize <= 0 || !Number.isInteger(blockSize)) {
            throw new Error("blockSize must be a positive integer.");
        }
        this.blockSize = blockSize;
        this.blocks = [];
        this.totalElements = 0;
        this.currentBlockIndex = 0;
        this.currentOffset = 0;

        // Pre-allocate initial blocks up to initialCapacity
        const initialBlocksNeeded = Math.ceil(initialCapacity / blockSize);
        for (let i = 0; i < initialBlocksNeeded; i++) {
            this.allocateNewBlock();
        }
    }

    /**
     * Allocates a new block of memory and adds it to the internal blocks list.
     * @private
     */
    allocateNewBlock() {
        const newBlock = new Float32Array(this.blockSize);
        this.blocks.push(newBlock);
        // If this is the first block, it's the current one.
        // Otherwise, the currentOffset should be reset when switching to a new block.
        if (this.blocks.length === 1) {
            this.currentBlockIndex = 0;
            this.currentOffset = 0;
        }
    }

    /**
     * Ensures there is enough capacity for at least one more element.
     * Allocates a new block if the current one is full.
     * @private
     */
    ensureCapacity() {
        if (this.currentOffset >= this.blockSize) {
            this.currentBlockIndex++;
            this.currentOffset = 0;
            // If we've run out of pre-allocated blocks, create a new one.
            if (this.currentBlockIndex >= this.blocks.length) {
                this.allocateNewBlock();
            }
        }
    }

    /**
     * Adds a single number to the builder.
     * @param {number} value - The number to add.
     */
    push(value) {
        this.ensureCapacity();
        this.blocks[this.currentBlockIndex][this.currentOffset] = value;
        this.currentOffset++;
        this.totalElements++;
    }

    /**
     * Adds all elements from a Float32Array to the builder.
     * Optimized to copy chunks efficiently using `set()`.
     * @param {Float32Array} float32Array - The array to add.
     */
    addArray(float32Array) {
        if (!(float32Array instanceof Float32Array)) {
            throw new Error("Input must be a Float32Array.");
        }

        let elementsToAdd = float32Array.length;
        let sourceOffset = 0;

        while (elementsToAdd > 0) {
            this.ensureCapacity(); // Ensure current block has space

            const currentBlock = this.blocks[this.currentBlockIndex];
            const remainingInBlock = this.blockSize - this.currentOffset;
            const elementsToCopy = Math.min(elementsToAdd, remainingInBlock);

            if (elementsToCopy > 0) {
                // Use set() for efficient bulk copying
                currentBlock.set(float32Array.subarray(sourceOffset, sourceOffset + elementsToCopy), this.currentOffset);
                this.currentOffset += elementsToCopy;
                sourceOffset += elementsToCopy;
                elementsToAdd -= elementsToCopy;
                this.totalElements += elementsToCopy;
            }
            // If elementsToCopy was 0, it means remainingInBlock was 0,
            // so ensureCapacity() would have already moved to the next block.
        }
    }

    /**
     * Returns the total number of elements currently stored.
     * @returns {number}
     */
    get length() {
        return this.totalElements;
    }

    /**
     * Consolidates all internal blocks into a single Float32Array.
     * This is an O(N) operation where N is the total number of elements.
     * @returns {Float32Array} A new Float32Array containing all added elements.
     */
    toArray() {
        if (this.totalElements === 0) {
            return new Float32Array(0);
        }

        // Calculate the exact size needed for the final array
        const finalArray = new Float32Array(this.totalElements);
        let currentFinalOffset = 0;

        // Copy elements from each block to the final array
        for (const block of this.blocks) {
            // Determine how many elements from this block are actually used
            // This is important if the last block is not completely full.
            const elementsInBlock = Math.min(block.length, this.totalElements - currentFinalOffset);
            if (elementsInBlock > 0) {
                finalArray.set(block.subarray(0, elementsInBlock), currentFinalOffset);
                currentFinalOffset += elementsInBlock;
            }
            if (currentFinalOffset >= this.totalElements) {
                break; // Stop if we've copied all elements
            }
        }
        return finalArray;
    }

    /**
     * Clears the builder, freeing up memory and resetting its state.
     */
    clear() {
        this.blocks = [];
        this.totalElements = 0;
        this.currentBlockIndex = 0;
        this.currentOffset = 0;
        // Optionally, re-allocate the first block to reset capacity
        this.allocateNewBlock();
    }
}

// --- Example Usage ---

// Create a builder with a block size of 1024 elements
const builder = new BlockBasedFloat32ArrayBuilder(1024);

// Add individual numbers
for (let i = 0; i < 500; i++) {
    builder.push(i * 0.1);
}

// Add another array
const largeArray = new Float32Array(2000);
for (let i = 0; i < largeArray.length; i++) {
    largeArray[i] = Math.sin(i * 0.01);
}
builder.addArray(largeArray);

// Add more individual numbers
for (let i = 0; i < 700; i++) {
    builder.push(Math.cos(i * 0.02));
}

// Get the final consolidated Float32Array
const finalArray = builder.toArray();

console.log(`Builder length: ${builder.length}`); // Expected: 500 + 2000 + 700 = 3200
console.log(`Final array length: ${finalArray.length}`); // Expected: 3200
console.log(`First 10 elements: ${finalArray.slice(0, 10)}`);
console.log(`Elements around the largeArray boundary (index 500-510): ${finalArray.slice(500, 511)}`);
console.log(`Last 10 elements: ${finalArray.slice(-10)}`);

// Example of clearing and reusing
builder.clear();
console.log(`Builder length after clear: ${builder.length}`); // Expected: 0
builder.push(1.1);
builder.push(2.2);
console.log(`Builder length after adding 2 elements: ${builder.length}`); // Expected: 2
console.log(`Final array after clear and re-add: ${builder.toArray()}`); // Expected: Float32Array(2) [1.1, 2.2]

```
