/** * Fixed-size bitset backed by `Uint32Array` storage for fast component-mask * arithmetic. Used by archetypes and queries for O(words) subset/superset * checks. */ export class BitSet { private readonly data: Uint32Array; public readonly size: number; public constructor(size: number) { if (!Number.isInteger(size) || size <= 0) { throw new RangeError( `BitSet size must be a positive integer, got ${size}` ); } this.size = size; this.data = new Uint32Array(Math.ceil(size / 32)); } /** Set the bit at `bit` to 1. */ public set(bit: number): void { this.checkBit(bit); const index = bit >>> 5; const offset = bit & 31; this.data[index] = (this.data[index] as number) | (1 << offset); } /** Clear the bit at `bit`. */ public unset(bit: number): void { this.checkBit(bit); const index = bit >>> 5; const offset = bit & 31; this.data[index] = (this.data[index] as number) & ~(1 << offset); } /** Test whether `bit` is set. */ public has(bit: number): boolean { if (bit < 0 || bit >= this.size) return false; const index = bit >>> 5; const offset = bit & 31; return ((this.data[index] as number) & (1 << offset)) !== 0; } /** True when this bitset has every bit set in `other`. */ public containsAll(other: BitSet): boolean { const shared = Math.min(this.data.length, other.data.length); for (let i = 0; i < shared; i++) { const a = this.data[i] as number; const b = other.data[i] as number; if ((a & b) >>> 0 !== b) return false; } for (let i = shared; i < other.data.length; i++) { if ((other.data[i] as number) !== 0) return false; } return true; } /** True when this bitset shares at least one set bit with `other`. */ public intersects(other: BitSet): boolean { const shared = Math.min(this.data.length, other.data.length); for (let i = 0; i < shared; i++) { if (((this.data[i] as number) & (other.data[i] as number)) !== 0) { return true; } } return false; } /** Comma-separated string of set bit indices, ascending. Stable across runs. */ public toKey(): string { const bits: number[] = []; for (let i = 0; i < this.size; i++) { if (this.has(i)) bits.push(i); } return bits.join(","); } /** Return a new BitSet with the same size and bits. */ public clone(): BitSet { const copy = new BitSet(this.size); copy.data.set(this.data); return copy; } /** Structural equality — true when every word matches. */ public equals(other: BitSet): boolean { const len = Math.max(this.data.length, other.data.length); for (let i = 0; i < len; i++) { const a = i < this.data.length ? (this.data[i] as number) : 0; const b = i < other.data.length ? (other.data[i] as number) : 0; if (a !== b) return false; } return true; } /** Population count — total number of set bits. */ public count(): number { let total = 0; for (let i = 0; i < this.data.length; i++) { let v = this.data[i] as number; v = v - ((v >>> 1) & 0x55555555); v = (v & 0x33333333) + ((v >>> 2) & 0x33333333); total += (((v + (v >>> 4)) & 0x0f0f0f0f) * 0x01010101) >>> 24; } return total; } /** Clear every bit. */ public clear(): void { this.data.fill(0); } /** Iterate set bit indices in ascending order. */ public *[Symbol.iterator](): Generator { for (let i = 0; i < this.size; i++) { if (this.has(i)) yield i; } } private checkBit(bit: number): void { if (!Number.isInteger(bit) || bit < 0 || bit >= this.size) { throw new RangeError( `bit index ${bit} out of range [0, ${this.size})` ); } } }