add ecs lib

This commit is contained in:
方而静 2023-05-31 14:53:06 +08:00
parent 8a7b206f0a
commit 0fc3fb3b2e
2 changed files with 183 additions and 0 deletions

124
src/ecs.js Normal file
View File

@ -0,0 +1,124 @@
import { TreapSet } from "./misc/treap.js";
export class EntityRegistry {
constructor() {
this.free_ids = new TreapSet();
this.signatures = [];
this.components = [];
this.component_map = new Map();
}
requireComponentList(cname) {
if (!this.component_map.has(cname)) {
this.component_map.set(cname, this.components.length);
this.components.push([]);
}
return this.components[this.component_map.get(cname)];
}
componentListLowerBound(cl, entity) {
if (cl.length === 0) {
return 0;
} else if (cl[cl.length - 1].__owner_entity < entity) {
return cl.length;
}
let l = 0, r = cl.length - 1, p = 0;
while (l <= r) {
let mid = (l + r) >>> 1;
if (cl[mid].__owner_entity < entity) {
l = mid + 1;
} else {
p = mid;
r = mid - 1;
}
}
return p;
}
assign(entity, cv) {
const cname = cv.constructor.name;
const cl = this.requireComponentList(cname);
cv.__owner_entity = entity;
const p = this.componentListLowerBound(cl, entity);
if (cl[p]?.__owner_entity === entity) {
cl[p] = cv;
return this;
}
cl.splice(p, 0, cv);
this.signatures[entity][this.component_map.get(cname)] = p;
return this;
}
create() {
let id = this.free_ids.takeInstance();
if (id == null) {
id = this.signatures.length;
} else {
this.free_ids.erase(id);
}
this.signatures[id] = [];
return id;
}
destory(entity) {
for (let i in this.signatures[entity]) {
const p = this.signatures[entity][i];
this.components[i].splice(p, 1);
}
this.signatures[entity] = undefined;
this.free_ids.insertRaw(entity);
return this;
}
getRaw(entity, cid) {
return this.components[cid][this.signatures[entity][cid]];
}
get(entity, ctype) {
if (!this.component_map.has(ctype.name)) {
return null;
}
const cid = this.component_map.get(ctype.name);
if (this.signatures[entity][cid] == null) {
return null;
}
return this.getRaw(entity, cid);
}
forEach(rc, func) {
if (rc.length === 0) {
for (let i = 0; i < this.signatures.length; ++i) {
if (this.signatures[i]) {
func.call(this, i);
}
}
return this;
}
const rcid = rc.map(ctype => this.component_map.get(ctype.name));
let p, v = Infinity;
for (let cid of rcid) {
const cl = this.components[cid];
if (cl.length < v) {
v = cl.length;
p = cid;
}
}
for (let c of this.components[p]) {
const e = c.__owner_entity;
if (rcid.every(cid => this.signatures[e][cid] != null)) {
func.apply(this, [e].concat(rcid.map(cid => this.getRaw(e, cid))));
}
}
return this;
}
};

59
tests/ecs.test.js Normal file
View File

@ -0,0 +1,59 @@
import assert from 'node:assert';
import { EntityRegistry } from '../src/ecs.js';
class Position {
constructor(x, y) {
this.x = x;
this.y = y;
}
}
describe('ECS', function () {
it('create', function () {
let registry = new EntityRegistry();
let id = registry.create();
});
it('destory', function () {
let registry = new EntityRegistry();
let id0 = registry.create();
let id1 = registry.destory(id0).create();
assert.equal(id0, id1);
});
it('component', function () {
let registry = new EntityRegistry();
let id3;
for (let i = 0; i < 5; i += 1) {
let e = registry.create();
registry.assign(e, new Position(i, i));
if (i == 3) {
id3 = e;
}
}
assert.equal(registry.get(id3, Position).x, 3);
});
it('forEach', function () {
let registry = new EntityRegistry();
let id3;
registry.create();
registry.create();
for (let i = 0; i < 5; i += 1) {
let e = registry.create();
registry.assign(e, new Position(i, i));
if (i == 3) {
id3 = e;
}
}
let count = 0;
registry.forEach([], () => {
count += 1;
});
assert.equal(count, 7);
registry.forEach([Position], (id, pos) => {
pos.x += 1;
});
assert.equal(registry.get(id3, Position).x, 4);
});
});