feat: bundle
This commit is contained in:
parent
22568bd999
commit
bbf11c0b63
58
packages/ecs/src/bundle.js
Normal file
58
packages/ecs/src/bundle.js
Normal file
|
@ -0,0 +1,58 @@
|
|||
/* eslint-disable guard-for-in, max-classes-per-file, no-restricted-syntax */
|
||||
export default class Bundle {
|
||||
|
||||
Components = {};
|
||||
|
||||
constructor(Components) {
|
||||
this.Components = this.constructor.Components.reduce(
|
||||
(r, Component, i) => {
|
||||
if (!Components[i]) {
|
||||
throw new TypeError(`Bundle(): no such component '${Component}'`);
|
||||
}
|
||||
return {...r, [Component]: Components[i]};
|
||||
},
|
||||
{},
|
||||
);
|
||||
}
|
||||
|
||||
configure(configuration) {
|
||||
return this.constructor.configure(configuration, this.Components);
|
||||
}
|
||||
|
||||
static configure(configuration, Components) {
|
||||
const result = {};
|
||||
const entries = Object.entries(Components);
|
||||
for (let i = 0; i < entries.length; i++) {
|
||||
const [component] = entries[i];
|
||||
result[component] = configuration[component];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static maybeNormalize(BundleLike) {
|
||||
if (Array.isArray(BundleLike)) {
|
||||
return class AdhocBundle extends Bundle {
|
||||
|
||||
static Components = BundleLike;
|
||||
|
||||
};
|
||||
}
|
||||
if (BundleLike.prototype instanceof Bundle) {
|
||||
return BundleLike;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
static select(entity, Components) {
|
||||
const bundle = {};
|
||||
for (const i in Components) {
|
||||
bundle[i] = Components[i].getUnsafe(entity);
|
||||
}
|
||||
return bundle;
|
||||
}
|
||||
|
||||
select(entity) {
|
||||
return this.constructor.select(entity, this.Components);
|
||||
}
|
||||
|
||||
}
|
|
@ -23,6 +23,9 @@ ComponentRouter.normalize = (ComponentLike) => {
|
|||
if (ComponentLike.prototype instanceof ComponentRouter) {
|
||||
return ComponentLike;
|
||||
}
|
||||
if ('object' !== typeof ComponentLike) {
|
||||
throw new TypeError(`Component.normalize(): couldn't normalize '${ComponentLike}'`);
|
||||
}
|
||||
return class AdhocComponent extends ComponentRouter {
|
||||
|
||||
static schema = ComponentLike;
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
/* eslint-disable guard-for-in, max-classes-per-file, no-continue, no-restricted-syntax */
|
||||
import Bundle from './bundle';
|
||||
import Component from './component';
|
||||
|
||||
export default class Ecs {
|
||||
|
||||
$$caret = 1;
|
||||
|
||||
Bundles = {};
|
||||
|
||||
Components = {};
|
||||
|
||||
dirty = new Set();
|
||||
|
@ -15,9 +18,21 @@ export default class Ecs {
|
|||
|
||||
$$systems = [];
|
||||
|
||||
constructor(Components) {
|
||||
for (const i in Components) {
|
||||
this.Components[i] = this.trackDirtyEntities(Component.normalize(Components[i]));
|
||||
constructor(ComponentLikesAndOrBundleLikes) {
|
||||
const Bundles = [];
|
||||
for (const i in ComponentLikesAndOrBundleLikes) {
|
||||
const MaybeBundle = Bundle.maybeNormalize(ComponentLikesAndOrBundleLikes[i]);
|
||||
if (MaybeBundle) {
|
||||
Bundles.push([i, MaybeBundle]);
|
||||
continue;
|
||||
}
|
||||
this.Components[i] = this.trackDirtyEntities(
|
||||
Component.normalize(ComponentLikesAndOrBundleLikes[i]),
|
||||
);
|
||||
}
|
||||
for (let i = 0; i < Bundles.length; i++) {
|
||||
const [j, Bundle] = Bundles[i];
|
||||
this.Bundles[j] = new Bundle(Bundle.Components.map((c) => this.Components[c]));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -55,7 +70,18 @@ export default class Ecs {
|
|||
const creating = {};
|
||||
for (let i = 0; i < componentsList.length; i++) {
|
||||
const components = componentsList[i];
|
||||
const componentKeys = Object.keys(components);
|
||||
const componentKeys = Object.keys(components)
|
||||
.reduce(
|
||||
(r, componentOrBundle) => {
|
||||
if (this.Components[componentOrBundle]) {
|
||||
return r.concat(componentOrBundle);
|
||||
}
|
||||
const Bundle = this.Bundles[componentOrBundle];
|
||||
Object.assign(components, Bundle.configure(components[componentOrBundle]));
|
||||
return r.concat(Object.keys(Bundle.Components));
|
||||
},
|
||||
[],
|
||||
);
|
||||
let entity;
|
||||
if (this.$$pool.length > 0) {
|
||||
entity = this.$$pool.pop();
|
||||
|
@ -70,7 +96,7 @@ export default class Ecs {
|
|||
if (!creating[component]) {
|
||||
creating[component] = [];
|
||||
}
|
||||
creating[component].push([entity, components[component]]);
|
||||
creating[component].push(components[component] ? [entity, components[component]] : entity);
|
||||
}
|
||||
}
|
||||
for (const i in creating) {
|
||||
|
|
|
@ -1,18 +1,21 @@
|
|||
/* eslint-disable no-restricted-syntax */
|
||||
import BaseComponent from './component/base';
|
||||
|
||||
export default class Query {
|
||||
|
||||
$$compiled = {with: [], without: []};
|
||||
|
||||
$$index = [];
|
||||
|
||||
constructor(parameters, Components) {
|
||||
constructor(parameters, ComponentsAndOrBundles) {
|
||||
for (let i = 0; i < parameters.length; ++i) {
|
||||
const parameter = parameters[i];
|
||||
switch (parameter.charCodeAt(0)) {
|
||||
case '!'.charCodeAt(0):
|
||||
this.$$compiled.without.push(Components[parameter.slice(1)]);
|
||||
this.$$compiled.without.push(ComponentsAndOrBundles[parameter.slice(1)]);
|
||||
break;
|
||||
default:
|
||||
this.$$compiled.with.push(Components[parameter]);
|
||||
this.$$compiled.with.push(ComponentsAndOrBundles[parameter]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -46,21 +49,49 @@ export default class Query {
|
|||
const entity = entities[i];
|
||||
const index = this.$$index.indexOf(entity);
|
||||
let should = true;
|
||||
for (let j = 0; j < this.$$compiled.with.length; ++j) {
|
||||
const C = this.$$compiled.with[j];
|
||||
if ('undefined' === typeof C.getUnsafe(entity)) {
|
||||
// eslint-disable-next-line no-restricted-syntax, no-labels
|
||||
withCheck: for (let j = 0; j < this.$$compiled.with.length; ++j) {
|
||||
const ComponentOrBundle = this.$$compiled.with[j];
|
||||
if (ComponentOrBundle instanceof BaseComponent) {
|
||||
if ('undefined' === typeof ComponentOrBundle.getUnsafe(entity)) {
|
||||
should = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (const k in ComponentOrBundle.Components) {
|
||||
if ('undefined' === typeof ComponentOrBundle.Components[k].getUnsafe(entity)) {
|
||||
should = false;
|
||||
// eslint-disable-next-line no-labels
|
||||
break withCheck;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (should) {
|
||||
// eslint-disable-next-line no-restricted-syntax, no-labels
|
||||
for (let j = 0; j < this.$$compiled.without.length; ++j) {
|
||||
const C = this.$$compiled.without[j];
|
||||
if ('undefined' !== typeof C.getUnsafe(entity)) {
|
||||
const ComponentOrBundle = this.$$compiled.without[j];
|
||||
if (ComponentOrBundle instanceof BaseComponent) {
|
||||
if ('undefined' !== typeof ComponentOrBundle.getUnsafe(entity)) {
|
||||
should = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
let shouldLocal = false;
|
||||
for (const k in ComponentOrBundle.Components) {
|
||||
if ('undefined' === typeof ComponentOrBundle.Components[k].getUnsafe(entity)) {
|
||||
shouldLocal = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!shouldLocal) {
|
||||
should = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (should && -1 === index) {
|
||||
this.$$index.push(entity);
|
||||
|
@ -84,8 +115,13 @@ export default class Query {
|
|||
const entity = entities.pop();
|
||||
const value = [];
|
||||
for (let i = 0; i < this.$$compiled.with.length; ++i) {
|
||||
if (this.$$compiled.with[i] instanceof BaseComponent) {
|
||||
value.push(this.$$compiled.with[i].getUnsafe(entity));
|
||||
}
|
||||
else {
|
||||
value.push(this.$$compiled.with[i].select(entity));
|
||||
}
|
||||
}
|
||||
value.push(entity);
|
||||
return {done: false, value};
|
||||
},
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
/* eslint-disable react/prefer-stateless-function */
|
||||
import {expect} from 'chai';
|
||||
|
||||
import Bundle from '../src/bundle';
|
||||
import Ecs from '../src/ecs';
|
||||
import System from '../src/system';
|
||||
|
||||
|
@ -115,3 +116,34 @@ it('can encode and decode an ecs', () => {
|
|||
expect(newEcs2.get(entity))
|
||||
.to.not.be.undefined;
|
||||
});
|
||||
|
||||
it('can add bundles', () => {
|
||||
const Height = {height: 'uint32'};
|
||||
const Width = {width: 'uint32'};
|
||||
const Area = ['Height', 'Width'];
|
||||
class ConfiguredArea extends Bundle {
|
||||
|
||||
static configure({height, width}) {
|
||||
return {Height: {height}, Width: {width}};
|
||||
}
|
||||
|
||||
static Components = ['Height', 'Width'];
|
||||
|
||||
}
|
||||
const ecs = new Ecs({
|
||||
Area,
|
||||
ConfiguredArea,
|
||||
Height,
|
||||
Width,
|
||||
});
|
||||
const entity = ecs.get(ecs.create({Area: {Width: {width: 420}}}));
|
||||
expect(entity.Height.height)
|
||||
.to.equal(0);
|
||||
expect(entity.Width.width)
|
||||
.to.equal(420);
|
||||
const configuredEntity = ecs.get(ecs.create({ConfiguredArea: {width: 420}}));
|
||||
expect(configuredEntity.Height.height)
|
||||
.to.equal(0);
|
||||
expect(configuredEntity.Width.width)
|
||||
.to.equal(420);
|
||||
});
|
||||
|
|
|
@ -1,29 +1,37 @@
|
|||
import {expect} from 'chai';
|
||||
|
||||
import Bundle from '../src/bundle';
|
||||
import Component from '../src/component';
|
||||
import Query from '../src/query';
|
||||
|
||||
class A extends Component {
|
||||
const A = Component.normalize({a: {type: 'int32', defaultValue: 420}});
|
||||
const B = Component.normalize({b: {type: 'int32', defaultValue: 69}});
|
||||
const C = Component.normalize({c: 'int32'});
|
||||
const D = Bundle.maybeNormalize(['B', 'C']);
|
||||
class E extends Bundle {
|
||||
|
||||
static schema = {
|
||||
a: 'int32',
|
||||
};
|
||||
static Components = ['A', 'B'];
|
||||
|
||||
static select(entity, Components) {
|
||||
const {A: {a}, B: {b}} = super.select(entity, Components);
|
||||
return {a, b};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class B extends Component {
|
||||
const ComponentsAndOrBundles = {
|
||||
A: new A(),
|
||||
B: new B(),
|
||||
C: new C(),
|
||||
};
|
||||
ComponentsAndOrBundles.A.createMany([2n, 3n]);
|
||||
ComponentsAndOrBundles.B.createMany([1n, 2n]);
|
||||
ComponentsAndOrBundles.C.createMany([2n, 4n]);
|
||||
ComponentsAndOrBundles.D = new D([ComponentsAndOrBundles.B, ComponentsAndOrBundles.C]);
|
||||
ComponentsAndOrBundles.E = new E([ComponentsAndOrBundles.A, ComponentsAndOrBundles.B]);
|
||||
|
||||
static schema = {
|
||||
b: 'int32',
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
const Components = {A: new A(), B: new B()};
|
||||
Components.A.createMany([2n, 3n]);
|
||||
Components.B.createMany([1n, 2n]);
|
||||
function testQuery(parameters, expected) {
|
||||
const query = new Query(parameters, Components);
|
||||
const query = new Query(parameters, ComponentsAndOrBundles);
|
||||
query.reindex([1n, 2n, 3n]);
|
||||
expect(query.count)
|
||||
.to.equal(expected.length);
|
||||
|
@ -50,8 +58,16 @@ it('can query excluding', () => {
|
|||
testQuery(['A', '!B'], [3n]);
|
||||
});
|
||||
|
||||
it('can query bundles', () => {
|
||||
testQuery(['D'], [2n]);
|
||||
});
|
||||
|
||||
it('can query excluding bundles', () => {
|
||||
testQuery(['!D'], [1n, 3n]);
|
||||
});
|
||||
|
||||
it('can deindex', () => {
|
||||
const query = new Query(['A'], Components);
|
||||
const query = new Query(['A'], ComponentsAndOrBundles);
|
||||
query.reindex([1n, 2n, 3n]);
|
||||
expect(query.count)
|
||||
.to.equal(2);
|
||||
|
@ -59,3 +75,30 @@ it('can deindex', () => {
|
|||
expect(query.count)
|
||||
.to.equal(1);
|
||||
});
|
||||
|
||||
it('can select', () => {
|
||||
const query = new Query(['A'], ComponentsAndOrBundles);
|
||||
query.reindex([1n, 2n, 3n]);
|
||||
const it = query.select();
|
||||
const result = it.next();
|
||||
expect(result.value[0].a)
|
||||
.to.equal(420);
|
||||
});
|
||||
|
||||
it('can select bundles', () => {
|
||||
const query = new Query(['D'], ComponentsAndOrBundles);
|
||||
query.reindex([1n, 2n, 3n]);
|
||||
const it = query.select();
|
||||
const result = it.next();
|
||||
expect(result.value[0].B.b)
|
||||
.to.equal(69);
|
||||
});
|
||||
|
||||
it('can select configured bundles', () => {
|
||||
const query = new Query(['E'], ComponentsAndOrBundles);
|
||||
query.reindex([1n, 2n, 3n]);
|
||||
const it = query.select();
|
||||
const result = it.next();
|
||||
expect(result.value[0])
|
||||
.to.deep.equal({a: 420, b: 69});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue
Block a user