Repository: playcanvas/engine
Pull request #5071 (2023-02-11 09:25:09)

BitPacking functionality
A BitPacking namespace with functionality useful for packing bits into a number.

I've done some performance testing as well. The winner were free functions, followed by functions in an object (name space) - this was 17% slower, but still great. This followed by a class with static functions, which was massively slower, and so I've decided to not use that.

<img width="620" alt="Screenshot 2023-02-10 at 11 20 09" src="https://user-images.githubusercontent.com/59932779/218250843-b359dfdc-37b9-46ca-a195-91650377c9ad.png">


mvaligursky: Yes, it's part of how I store render states - this will be a separate PR, just need to finish up. But this functionality looked a good one to be put into a separate PR.

Patch:
diff --git a/src/core/math/bit-packing.js b/src/core/math/bit-packing.js
new file mode 100644
index 000000000..11d768ec5
--- /dev/null
+++ b/src/core/math/bit-packing.js
@@ -0,0 +1,64 @@
+/**
+ * BitPacking API - functionality for operating on values stored as bits in a number.
+ *
+ * @namespace
+ * @ignore
+ */
+const BitPacking = {
+
+    /**
+     * Sets a value to specified bits of a number.
+     *
+     * @param {number} storage - Number to store the bits into.
+     * @param {number} value - Value to store.
+     * @param {number} shift - Number of bits to shift the value.
+     * @param {number} [mask] - Mask for the value to limit the number of storage bits. Defaults to 1.
+     * @returns {number} Returns the storage updated with the value.
+     */
+    set: function (storage, value, shift, mask = 1) {
+        // clear the space
+        const data = storage & ~(mask << shift);
+
+        // set the bits
+        return data | (value << shift);
+    },
+
+    /**
+     * Gets the value of specified bits from a number.
+     *
+     * @param {number} storage - Number to extract the bits from.
+     * @param {number} shift - Number of bits to shift the mask.
+     * @param {number} [mask] - Mask for the value to limit the number of storage bits. Defaults to 1.
+     * @returns {number} Returns the extracted value.
+     */
+    get: function (storage, shift, mask = 1) {
+        return (storage >> shift) & mask;
+    },
+
+    /**
+     * Tests if all specified bits are set.
+     *
+     * @param {number} storage - Number to test.
+     * @param {number} shift - Number of bits to shift the mask.
+     * @param {number} [mask] - Mask to limit the number of storage bits. Defaults to 1.
+     * @returns {boolean} Returns true if all bits in the mask are set in the storage.
+     */
+    all: function (storage, shift, mask = 1) {
+        const shifted = mask << shift;
+        return (storage & shifted) === shifted;
+    },
+
+    /**
+     * Tests if any specified bits are set.
+     *
+     * @param {number} storage - Number to test.
+     * @param {number} shift - Number of bits to shift the mask.
+     * @param {number} [mask] - Mask to limit the number of storage bits. Defaults to 1.
+     * @returns {boolean} Returns true if any bits in the mask are set in the storage.
+     */
+    any: function (storage, shift, mask = 1) {
+        return (storage & (mask << shift)) !== 0;
+    }
+};
+
+export { BitPacking };


Test patch:
diff --git a/test/core/math/bit-packing.test.mjs b/test/core/math/bit-packing.test.mjs
new file mode 100644
index 000000000..9a690d80f
--- /dev/null
+++ b/test/core/math/bit-packing.test.mjs
@@ -0,0 +1,51 @@
+import { BitPacking } from '../../../src/core/math/bit-packing.js';
+
+import { expect } from 'chai';
+
+describe('BitPacking', function () {
+
+    describe('#set', function () {
+
+        it('sets bits', function () {
+
+            let d = 0;
+            d = BitPacking.set(d, 0b11, 1, 0b11);
+            expect(d).to.equal(0b110);
+            d = BitPacking.set(d, 0, 1, 0b11);
+            expect(d).to.equal(0);
+            d = BitPacking.set(d, 1, 3);
+            expect(d).to.equal(0b1000);
+        });
+    });
+
+    describe('#get', function () {
+        it('gets bits', function () {
+
+            const d = 0b110011;
+            expect(BitPacking.get(d, 0, 0b111111)).to.equal(d);
+            expect(BitPacking.get(d, 4, 0b11)).to.equal(0b11);
+            expect(BitPacking.get(d, 3)).to.equal(0);
+            expect(BitPacking.get(d, 5)).to.equal(1);
+        });
+    });
+
+    describe('#any', function () {
+        it('any', function () {
+
+            const d = 0b110011;
+            expect(BitPacking.any(d, 0, 0b111111)).to.equal(true);
+            expect(BitPacking.any(d, 2, 0b11)).to.equal(false);
+            expect(BitPacking.any(d, 2, 0b111)).to.equal(true);
+        });
+    });
+
+    describe('#all', function () {
+        it('all', function () {
+
+            const d = 0b110011;
+            expect(BitPacking.all(d, 0, 0b111111)).to.equal(false);
+            expect(BitPacking.all(d, 2, 0b11)).to.equal(false);
+            expect(BitPacking.all(d, 4, 0b11)).to.equal(true);
+        });
+    });
+});