add ecs lib
This commit is contained in:
parent
8a7b206f0a
commit
0fc3fb3b2e
124
src/ecs.js
Normal file
124
src/ecs.js
Normal 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
59
tests/ecs.test.js
Normal 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);
|
||||
});
|
||||
});
|
Loading…
x
Reference in New Issue
Block a user