flow(ecs): lots
This commit is contained in:
parent
4834b5b294
commit
3373cb7135
|
@ -21,7 +21,21 @@ export default class Ecs {
|
|||
}
|
||||
|
||||
addSystem(System) {
|
||||
const system = new System(this.Components);
|
||||
const ecs = this;
|
||||
class WrappedSystem extends System {
|
||||
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
createEntity(components) {
|
||||
return ecs.create(components);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
createManyEntities(count, components) {
|
||||
return ecs.createMany(count, components);
|
||||
}
|
||||
|
||||
}
|
||||
const system = new WrappedSystem(this.Components);
|
||||
this.$$systems.push(system);
|
||||
return system;
|
||||
}
|
||||
|
@ -164,13 +178,14 @@ export default class Ecs {
|
|||
}
|
||||
|
||||
encode(entities, view) {
|
||||
let cursor = 0;
|
||||
let entitiesWritten = 0;
|
||||
view.setUint32(cursor, entities.length, true);
|
||||
cursor += 4;
|
||||
if (0 === entities.length) {
|
||||
return;
|
||||
}
|
||||
const keys = Object.keys(this.Components);
|
||||
let cursor = 0;
|
||||
view.setUint32(cursor, entities.length, true);
|
||||
cursor += 4;
|
||||
for (let i = 0; i < entities.length; i++) {
|
||||
let entity;
|
||||
let onlyDirty = false;
|
||||
|
@ -183,6 +198,7 @@ export default class Ecs {
|
|||
if (onlyDirty && !this.dirty.has(entity)) {
|
||||
continue;
|
||||
}
|
||||
entitiesWritten += 1;
|
||||
view.setBigUint64(cursor, BigInt(entity), true);
|
||||
cursor += 8;
|
||||
const components = this.$$entities[entity];
|
||||
|
@ -208,9 +224,13 @@ export default class Ecs {
|
|||
}
|
||||
view.setUint16(componentsWrittenIndex, componentsWritten, true);
|
||||
}
|
||||
view.setUint32(0, entitiesWritten, true);
|
||||
}
|
||||
|
||||
get(entity, Components = Object.keys(this.Components)) {
|
||||
if (!this.$$entities[entity]) {
|
||||
return undefined;
|
||||
}
|
||||
const result = {};
|
||||
for (let i = 0; i < Components.length; i++) {
|
||||
const component = this.Components[Components[i]].get(entity);
|
||||
|
|
|
@ -1,113 +1,3 @@
|
|||
/* eslint-disable */
|
||||
|
||||
import Component from './component';
|
||||
import Ecs from './ecs';
|
||||
import Schema from './schema';
|
||||
import Serializer from './serializer';
|
||||
import System from './system';
|
||||
|
||||
const N = 1000;
|
||||
const warm = 500;
|
||||
|
||||
const marks = [];
|
||||
|
||||
function mark(label, fn) {
|
||||
marks.push(label);
|
||||
performance.mark(`${label}-before`);
|
||||
fn();
|
||||
performance.mark(`${label}-after`);
|
||||
}
|
||||
|
||||
function measure() {
|
||||
for (let i = 0; i < marks.length; i++) {
|
||||
const label = marks[i];
|
||||
performance.measure(label, `${label}-before`, `${label}-after`);
|
||||
}
|
||||
console.log(performance.getEntriesByType('measure').map(({duration, name}) => ({duration, name})));
|
||||
}
|
||||
|
||||
class Position extends Component {
|
||||
|
||||
static schema = {
|
||||
x: {type: 'int32', defaultValue: 32},
|
||||
y: 'int32',
|
||||
z: 'int32',
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
class Direction extends Component {
|
||||
|
||||
static schema = {
|
||||
direction: 'uint8',
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
class ZSystem extends System {
|
||||
|
||||
static queries() {
|
||||
return {
|
||||
default: ['Position', 'Direction'],
|
||||
};
|
||||
}
|
||||
|
||||
tick() {
|
||||
for (let [position, {direction}, entity] of this.select('default')) {
|
||||
position.z = entity * direction * 2;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const ecs = new Ecs({Direction, Position});
|
||||
ecs.addSystem(ZSystem);
|
||||
|
||||
const createMany = () => {
|
||||
const entities = ecs.createMany(N, {Position: (entity) => ({y: entity})});
|
||||
ecs.insertMany({Direction: entities.map((entity) => [entity, {direction: 1 + entity % 4}])});
|
||||
return entities;
|
||||
};
|
||||
|
||||
for (let i = 0; i < warm; ++i) {
|
||||
ecs.destroyMany(createMany());
|
||||
}
|
||||
mark('create', createMany);
|
||||
|
||||
let buffer, view;
|
||||
|
||||
ecs.tick(0.01);
|
||||
ecs.tick(0.01);
|
||||
if (!view) {
|
||||
buffer = new ArrayBuffer(ecs.sizeOf(Array.from(ecs.dirty.values()), false));
|
||||
view = new DataView(buffer);
|
||||
}
|
||||
console.log('bytes:', buffer.byteLength);
|
||||
const encoding = Array.from(ecs.dirty.values());
|
||||
ecs.tickFinalize();
|
||||
|
||||
console.log('encoding', encoding.length);
|
||||
|
||||
for (let i = 0; i < warm; ++i) {
|
||||
ecs.tick(0.01);
|
||||
ecs.encode(encoding, view);
|
||||
ecs.tickFinalize();
|
||||
}
|
||||
|
||||
mark('tick', () => ecs.tick(0.01));
|
||||
mark('encode', () => ecs.encode(encoding, view));
|
||||
mark('finalize', () => ecs.tickFinalize());
|
||||
|
||||
ecs.destroyAll();
|
||||
|
||||
for (let i = 0; i < warm; ++i) {
|
||||
ecs.decode(view);
|
||||
ecs.destroyAll();
|
||||
}
|
||||
|
||||
mark('decode', () => ecs.decode(view));
|
||||
|
||||
console.log(JSON.stringify(ecs.get(1)));
|
||||
|
||||
console.log(N, 'iterations');
|
||||
measure();
|
||||
export {default as Component} from './component';
|
||||
export {default as Ecs} from './ecs';
|
||||
export {default as System} from './system';
|
||||
|
|
|
@ -31,6 +31,10 @@ export default class System {
|
|||
}
|
||||
}
|
||||
|
||||
static queries() {
|
||||
return {};
|
||||
}
|
||||
|
||||
reindex(entities) {
|
||||
for (const i in this.queries) {
|
||||
this.queries[i].reindex(entities);
|
||||
|
|
126
packages/ecs/test/ecs.js
Normal file
126
packages/ecs/test/ecs.js
Normal file
|
@ -0,0 +1,126 @@
|
|||
import {expect} from 'chai';
|
||||
|
||||
import Component from '../src/component';
|
||||
import Ecs from '../src/ecs';
|
||||
import System from '../src/system';
|
||||
|
||||
// eslint-disable-next-line
|
||||
class Empty extends Component {}
|
||||
|
||||
class Position extends Component {
|
||||
|
||||
static schema = {
|
||||
x: {type: 'int32', defaultValue: 32},
|
||||
y: 'int32',
|
||||
z: 'int32',
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
it('can create entities with components', () => {
|
||||
const ecs = new Ecs({Empty, Position});
|
||||
const entity = ecs.create({Empty: () => {}, Position: () => ({y: 420})});
|
||||
expect(JSON.stringify(ecs.get(entity)))
|
||||
.to.deep.equal(JSON.stringify({Empty: {}, Position: {x: 32, y: 420, z: 0}}));
|
||||
});
|
||||
|
||||
it('can tick systems', () => {
|
||||
class Momentum extends Component {
|
||||
|
||||
static schema = {
|
||||
x: 'int32',
|
||||
y: 'int32',
|
||||
z: 'int32',
|
||||
};
|
||||
|
||||
}
|
||||
const ecs = new Ecs({Momentum, Position});
|
||||
class Physics extends System {
|
||||
|
||||
static queries() {
|
||||
return {
|
||||
default: ['Position', 'Momentum'],
|
||||
};
|
||||
}
|
||||
|
||||
tick(elapsed) {
|
||||
expect(elapsed)
|
||||
.to.equal(1);
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const [position, momentum] of this.select('default')) {
|
||||
position.x += momentum.x * elapsed;
|
||||
position.y += momentum.y * elapsed;
|
||||
position.z += momentum.z * elapsed;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
ecs.addSystem(Physics);
|
||||
const entity = ecs.create({Momentum: () => ({}), Position: () => ({y: 420})});
|
||||
const position = JSON.stringify(ecs.get(entity).Position);
|
||||
ecs.tick(1);
|
||||
expect(JSON.stringify(ecs.get(entity).Position))
|
||||
.to.deep.equal(position);
|
||||
ecs.get(1).Momentum.y = 30;
|
||||
ecs.tick(1);
|
||||
expect(JSON.stringify(ecs.get(entity).Position))
|
||||
.to.deep.equal(JSON.stringify({x: 32, y: 450, z: 0}));
|
||||
});
|
||||
|
||||
it('can create entities when ticking systems', () => {
|
||||
const ecs = new Ecs();
|
||||
class Spawn extends System {
|
||||
|
||||
tick() {
|
||||
this.createEntity();
|
||||
}
|
||||
|
||||
}
|
||||
ecs.addSystem(Spawn);
|
||||
ecs.create();
|
||||
expect(ecs.get(2))
|
||||
.to.be.undefined;
|
||||
ecs.tick(1);
|
||||
expect(ecs.get(2))
|
||||
.to.not.be.undefined;
|
||||
});
|
||||
|
||||
it('can schedule entities to be deleted when ticking systems', () => {
|
||||
const ecs = new Ecs();
|
||||
class Despawn extends System {
|
||||
|
||||
tick() {
|
||||
this.destroyEntity(1);
|
||||
}
|
||||
|
||||
}
|
||||
ecs.addSystem(Despawn);
|
||||
ecs.createExact(1);
|
||||
ecs.tick(1);
|
||||
expect(ecs.get(1))
|
||||
.to.not.be.undefined;
|
||||
ecs.tickFinalize();
|
||||
expect(ecs.get(1))
|
||||
.to.be.undefined;
|
||||
});
|
||||
|
||||
it('can encode and decode an ecs', () => {
|
||||
const ecs = new Ecs({Empty, Position});
|
||||
const entity = ecs.create({Empty: () => {}, Position: () => ({y: 420})});
|
||||
const view = new DataView(new ArrayBuffer(1024));
|
||||
ecs.encode([[entity, true]], view);
|
||||
const newEcs = new Ecs({Empty, Position});
|
||||
newEcs.decode(view);
|
||||
expect(JSON.stringify(newEcs.get(entity)))
|
||||
.to.deep.equal(JSON.stringify(ecs.get(entity)));
|
||||
ecs.setClean();
|
||||
ecs.encode([[entity, true]], view);
|
||||
const newEcs2 = new Ecs({Empty, Position});
|
||||
newEcs2.decode(view);
|
||||
expect(newEcs2.get(entity))
|
||||
.to.be.undefined;
|
||||
ecs.encode([entity], view);
|
||||
newEcs2.decode(view);
|
||||
expect(newEcs2.get(entity))
|
||||
.to.not.be.undefined;
|
||||
});
|
61
packages/ecs/test/query.js
Normal file
61
packages/ecs/test/query.js
Normal file
|
@ -0,0 +1,61 @@
|
|||
import {expect} from 'chai';
|
||||
|
||||
import Component from '../src/component';
|
||||
import Query from '../src/query';
|
||||
|
||||
class A extends Component {
|
||||
|
||||
static schema = {
|
||||
a: 'int32',
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
class B extends Component {
|
||||
|
||||
static schema = {
|
||||
b: 'int32',
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
const Components = {A: new A(), B: new B()};
|
||||
Components.A.createMany([2, 3]);
|
||||
Components.B.createMany([1, 2]);
|
||||
function testQuery(parameters, expected) {
|
||||
const query = new Query(parameters, Components);
|
||||
query.reindex([1, 2, 3]);
|
||||
expect(query.count)
|
||||
.to.equal(expected.length);
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const _ of query.select()) {
|
||||
expect(_.length)
|
||||
.to.equal(parameters.filter((spec) => '!'.charCodeAt(0) !== spec.charCodeAt(0)).length + 1);
|
||||
expect(expected.includes(_.pop()))
|
||||
.to.equal(true);
|
||||
}
|
||||
}
|
||||
|
||||
it('can query all', () => {
|
||||
testQuery([], [1, 2, 3]);
|
||||
});
|
||||
|
||||
it('can query some', () => {
|
||||
testQuery(['A'], [2, 3]);
|
||||
testQuery(['A', 'B'], [2]);
|
||||
});
|
||||
|
||||
it('can query excluding', () => {
|
||||
testQuery(['!A'], [1]);
|
||||
testQuery(['A', '!B'], [3]);
|
||||
});
|
||||
|
||||
it('can deindex', () => {
|
||||
const query = new Query(['A'], Components);
|
||||
query.reindex([1, 2, 3]);
|
||||
expect(query.count)
|
||||
.to.equal(2);
|
||||
query.deindex([2]);
|
||||
expect(query.count)
|
||||
.to.equal(1);
|
||||
});
|
Loading…
Reference in New Issue
Block a user