Skip to content

Commit

Permalink
feat(ecs): add version bits for VersionedIDGen, add/update tests
Browse files Browse the repository at this point in the history
  • Loading branch information
postspectacular committed Nov 11, 2019
1 parent 118405d commit cc06f0b
Show file tree
Hide file tree
Showing 2 changed files with 99 additions and 7 deletions.
24 changes: 17 additions & 7 deletions packages/ecs/src/id.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,16 +45,24 @@ export class VersionedIDGen {
nextID: number;
capacity: number;
mask: number;
vmask: number;
shift: number;
freeID: number;

constructor(bits: number, cap = (1 << bits) >>> 0, next = 0) {
constructor(
bits: number,
vbits = 32 - bits,
cap = (1 << bits) >>> 0,
next = 0
) {
assert(bits + vbits <= 32, "too many combined bits");
const maxCap = (1 << bits) >>> 0;
assert(cap <= maxCap, "capacity too large for given bit size");
this.ids = [];
this.nextID = next;
this.capacity = cap;
this.mask = maxCap - 1;
this.vmask = (1 << vbits) - 1;
this.shift = bits;
this.freeID = -1;
}
Expand All @@ -64,7 +72,7 @@ export class VersionedIDGen {
}

version(id: number) {
return id >>> this.shift;
return (id >>> this.shift) & this.vmask;
}

next() {
Expand All @@ -83,21 +91,23 @@ export class VersionedIDGen {
}

free(id: number) {
if (!this.isValid(id)) return false;
if (!this.has(id)) return false;
this.ids[this.id(id)] = this.freeID;
this.freeID = this.nextVersion(id);
return true;
}

isValid(id: number) {
has(id: number) {
const rawID = this.id(id);
if (id < 0 || rawID >= this.nextID) return false;
return this.version(this.ids[rawID]) === this.version(id);
const storedID = this.ids[rawID];
return id >= 0 && rawID < this.nextID && storedID === id;
}

protected nextVersion(id: number) {
return (
((id & this.mask) | ((id & ~this.mask) + (1 << this.shift))) >>> 0
((id & this.mask) |
(((this.version(id) + 1) & this.vmask) << this.shift)) >>>
0
);
}
}
82 changes: 82 additions & 0 deletions packages/ecs/test/id.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,86 @@ describe("VersionedIDGen", () => {
assert.equal((<any>g).freeID, 0x202);
assert.deepEqual((<any>g).ids, [-1, 0x103, 0x201, 0x200]);
});

it("has (unversioned)", () => {
const check = (expected: boolean[]) => {
for (let i = 0; i < 4; i++) {
i > 0 && assert(!g.has(-i), String(-i));
assert.equal(g.has(i), expected[i], String(i));
assert(!g.has(i + 4), String(i + 4));
}
};

const g = new VersionedIDGen(2, 0);
g.next();
g.next();
g.next();
g.next();
assert.throws(() => g.next(), "max cap");
check([true, true, true, true]);
g.free(2);
check([true, true, false, true]);
g.free(1);
check([true, false, false, true]);
g.free(0);
check([false, false, false, true]);
g.next();
check([true, false, false, true]);
g.next();
check([true, true, false, true]);
g.free(3);
check([true, true, false, false]);
g.next();
check([true, true, false, true]);
g.next();
check([true, true, true, true]);
assert.throws(() => g.next(), "max cap 2");
});

it("has (versioned)", () => {
const check = (ids: number[], expected: boolean[]) => {
for (let i = 0; i < 4; i++) {
assert.equal(g.has(ids[i]), expected[i], String(i));
assert.equal(g.has(ids[i]), expected[i], String(ids[i]));
}
};

const g = new VersionedIDGen(2, 1);
g.next();
g.next();
g.next();
g.next();
assert.throws(() => g.next(), "max cap");
check([0, 1, 2, 3], [true, true, true, true]);
check([0 + 4, 1 + 4, 2 + 4, 3 + 4], [false, false, false, false]);
g.free(2);
check([0, 1, 2, 3], [true, true, false, true]);
check([0, 1, 2 + 4, 3], [true, true, false, true]);
g.free(1);
check([0, 1, 2, 3], [true, false, false, true]);
check([0, 1 + 4, 2 + 4, 3], [true, false, false, true]);
g.free(0);
check([0, 1, 2, 3], [false, false, false, true]);
check([0 + 4, 1 + 4, 2 + 4, 3], [false, false, false, true]);
g.next();
check([0, 1 + 4, 2 + 4, 3], [false, false, false, true]);
check([0 + 4, 1 + 4, 2 + 4, 3], [true, false, false, true]);
g.free(0 + 4);
check([0 + 4, 1 + 4, 2 + 4, 3], [false, false, false, true]);
// 0 version wraparound
g.next();
check([0, 1 + 4, 2 + 4, 3], [true, false, false, true]);
check([0 + 4, 1 + 4, 2 + 4, 3], [false, false, false, true]);
g.next();
check([0, 1 + 4, 2 + 4, 3], [true, true, false, true]);
g.next();
check([0, 1 + 4, 2 + 4, 3], [true, true, true, true]);
g.free(0);
g.free(1 + 4);
g.free(2 + 4);
g.free(3);
check([0, 1, 2, 3], [false, false, false, false]);
check([0 + 4, 1 + 4, 2 + 4, 3 + 4], [false, false, false, false]);
assert.equal((<any>g).freeID, 3 + 4);
});
});

0 comments on commit cc06f0b

Please sign in to comment.