Initial Commit
This commit is contained in:
127
packages/gaming/games/ecs/src/bitset/bitset.ts
Normal file
127
packages/gaming/games/ecs/src/bitset/bitset.ts
Normal file
@@ -0,0 +1,127 @@
|
||||
/**
|
||||
* 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<number> {
|
||||
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})`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user