javascriptbooleanarraybuffertyped-arrays

Javascript - Typed Arrays to store booleans - ArrayBuffer: what's the point with byteOffset?


I would like to store boolean values into a Javascript Typed Array. Originally I wanted to have a bidimensional array (making a small Game of Life actually) but it is not possible according to this post : javascript multidimensional Typed array (Int8Array) example

I'm filling the array like so :

const cols = 16;
const rows = 16;

const buffer = new ArrayBuffer(cols * rows);
const cells = new Uint8Array(buffer);

for (let i = 0; i < cols * rows; i++) {
    cells[i] =  Math.random() < 0.5;
}

Which returns:

enter image description here

Nevertheless, I've been doing some test, and I was wondering what is the point of byteOffset accessor in the ArrayBuffer ? Mozilla shows an example but I don't get the use case : enter image description here

When I add a byteOffset of 2 for example : const cells = new Uint8Array(buffer, 2); the only difference I've noticed is that my Array has lost 2 elements :

enter image description here

Beside all of this, if you have any suggestions on how to store booleans efficiently, I'd be glad to have your advices !


Solution

  • (just came across this, in case this helps folks finding this question) The byteOffset can be useful when creating multiple views on the same byteArray. E.g. you might have one byteArray with a section of 5 Uint32s followed by 5 Uint8s, e.g.:

    let buffer = new ArrayBuffer(45);
    let uint32Ary = new Uint32Array(buffer, 0, 5);
    let uint8Ary = new Uint8Array(buffer, 20, 5);
    let uint8AryAll = new Uint8Array(buffer, 0);
    // setting some values to show how the whole buffer is affected
    uint32Ary[1] = 2000;
    uint32Ary[2] = 2001;
    uint8Ary[4] = 55;
    console.log(uint32Ary);
    console.log(uint8Ary);
    console.log(uint8AryAll);
    

    results in:

    > Uint32Array [0, 2000, 2001, 0, 0]
    > Uint8Array [0, 0, 0, 0, 55]
    > Uint8Array [0, 0, 0, 0, 208, 7, 0, 0, 209, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 55]
    

    setting a single byte in a Uint32Array through the Uint8Array view:

    uint8AryAll[4] = 210;
    console.log(uint32Ary);
    
    > Uint32Array [0, 2002, 2001, 0, 0]
    

    This example is contrived, but illustrates how one could use an array buffer that contains the backing data for several byteArrays of different byteSize without needing to copy the data.

    To use an example a little closer to what you are doing, you could set up an array of typed arrays representing rows for your data matrix:

    const cols = 16;
    const rows = 16;
    const buffer = new ArrayBuffer(cols * rows);
    const cells = new Uint8Array(buffer);
    const rowArys = new Array(cols);
    
    for (let i = 0; i < cols; i++) {
        rowArys[i] =  new Uint8Array(buffer, i * rows, rows);
    }
    let x = 5;
    let y = 1;
    rowArys[y][x] = 55;
    console.log(rowArys[y][x]);
    console.log(cells[y * rows + x]);
    
    cells[y * rows + x] = 33;
    console.log(rowArys[y][x]);
    console.log(cells[y * rows + x]);
    

    results in:

    > 55
    > 55
    > 33
    > 33
    

    As you can see, the row arrays point to the same data as the cells array

    As for storing booleans, you could use bitwise operators to store and retrieve 8 booleans per byte, but since JS is not very efficient when doing bit operations, it might not be worth the in-memory savings due to the poorer performance and more complex code, but could be worthwhile for data loading and storing depending on the situation.

    That said, here is a code snippet illustration of using bitwise operators for storing booleans compactly in a simulated 2D array based on your game example:

    const cols = 16;
    const rows = 16;
    
    class BooleanArray {
      constructor(height, width) {
        this.height = height;
        this.width = width;
        this.byteWidth = (width >> 3) + ((width & 0b111) > 0 ? 1 : 0);
        this.dataArray = new Uint8Array(height * this.byteWidth);
      }
      getAt(x, y) {
        const xBytes = x >>> 3;
        const bitMask = 1 << (x & 0b111);
        return (this.dataArray[y * this.byteWidth + xBytes] & bitMask) > 0;
      }
      setAt(x, y, value) {
        const xBytes = x >>> 3;
        const xBits = x & 0b111;
        const i = y * this.byteWidth + xBytes;
        if (value) {
          this.dataArray[i] |= 1 << xBits;
        } else {
          this.dataArray[i] &= ~(1 << xBits);
        }
      }
      toString() {
        let result = "";
        for (let y = 0; y < this.height; y++) {
          result += "\n";
          for (let x = 0; x < this.width; x++) {
            result += this.getAt(x, y) ? "1" : "0";
          }
        }
        return result;
      }
    }
    bools = new BooleanArray(cols, rows);
    console.log(bools.dataArray.length);
    bools.setAt(13, 1, true);
    bools.setAt(3, 0, true);
    console.log(bools.getAt(13, 1));
    console.log(bools.toString());
    bools.setAt(13, 1, false);
    console.log(bools.getAt(13, 1));
    console.log(bools.toString());
    

    results in:

    > 32
    > true
    >
    0001000000000000
    0000000000000100
    0000000000000000
    0000000000000000
    0000000000000000
    0000000000000000
    0000000000000000
    0000000000000000
    0000000000000000
    0000000000000000
    0000000000000000
    0000000000000000
    0000000000000000
    0000000000000000
    0000000000000000
    0000000000000000
    > false
    >
    0001000000000000
    0000000000000000
    0000000000000000
    0000000000000000
    0000000000000000
    0000000000000000
    0000000000000000
    0000000000000000
    0000000000000000
    0000000000000000
    0000000000000000
    0000000000000000
    0000000000000000
    0000000000000000
    0000000000000000
    0000000000000000