refactor: component
This commit is contained in:
parent
6d6904bd22
commit
ee96c69a30
|
@ -1,4 +1,4 @@
|
|||
import Arbitrary from '@/ecs/arbitrary.js';
|
||||
import Component from '@/ecs/component.js';
|
||||
import Schema from '@/ecs/schema.js';
|
||||
import gather from '@/util/gather.js';
|
||||
|
||||
|
@ -15,7 +15,7 @@ for (const componentName in specificationsAndOrDecorators) {
|
|||
const specificationOrDecorator = specificationsAndOrDecorators[componentName];
|
||||
if ('function' === typeof specificationOrDecorator) {
|
||||
Components[componentName] = specificationOrDecorator(
|
||||
class Decorated extends Arbitrary {
|
||||
class Decorated extends Component {
|
||||
static componentName = componentName;
|
||||
}
|
||||
);
|
||||
|
@ -24,7 +24,7 @@ for (const componentName in specificationsAndOrDecorators) {
|
|||
}
|
||||
}
|
||||
else {
|
||||
Components[componentName] = class Component extends Arbitrary {
|
||||
Components[componentName] = class WrappedComponent extends Component {
|
||||
static componentName = componentName;
|
||||
static schema = new Schema({
|
||||
type: 'object',
|
||||
|
|
|
@ -1,56 +0,0 @@
|
|||
import {expect, test} from 'vitest';
|
||||
|
||||
import Schema from './schema.js';
|
||||
import Arbitrary from './arbitrary.js';
|
||||
|
||||
test('creates instances', () => {
|
||||
class CreatingArbitrary extends Arbitrary {
|
||||
static schema = new Schema({
|
||||
type: 'object',
|
||||
properties: {foo: {defaultValue: 'bar', type: 'string'}},
|
||||
});
|
||||
}
|
||||
const Component = new CreatingArbitrary();
|
||||
Component.create(1);
|
||||
expect(Component.get(1).entity)
|
||||
.to.equal(1);
|
||||
});
|
||||
|
||||
test('does not serialize default values', () => {
|
||||
class CreatingArbitrary extends Arbitrary {
|
||||
static schema = new Schema({
|
||||
type: 'object',
|
||||
properties: {foo: {defaultValue: 'bar', type: 'string'}, bar: {type: 'uint8'}},
|
||||
});
|
||||
}
|
||||
const fakeEcs = {markChange() {}};
|
||||
const Component = new CreatingArbitrary(fakeEcs);
|
||||
Component.create(1)
|
||||
expect(Component.get(1).toJSON())
|
||||
.to.deep.equal({});
|
||||
Component.get(1).bar = 1;
|
||||
expect(Component.get(1).toJSON())
|
||||
.to.deep.equal({bar: 1});
|
||||
});
|
||||
|
||||
test('reuses instances', () => {
|
||||
class ReusingArbitrary extends Arbitrary {
|
||||
static schema = new Schema({
|
||||
type: 'object',
|
||||
properties: {foo: {type: 'string'}},
|
||||
});
|
||||
}
|
||||
const Component = new ReusingArbitrary();
|
||||
Component.create(1);
|
||||
const instance = Component.get(1);
|
||||
Component.destroy(1);
|
||||
expect(Component.get(1))
|
||||
.to.be.undefined;
|
||||
expect(() => {
|
||||
Component.destroy(1);
|
||||
})
|
||||
.to.throw();
|
||||
Component.create(1);
|
||||
expect(Component.get(1))
|
||||
.to.equal(instance);
|
||||
});
|
101
app/ecs/base.js
101
app/ecs/base.js
|
@ -1,101 +0,0 @@
|
|||
import Schema from './schema.js';
|
||||
|
||||
export default class Base {
|
||||
|
||||
ecs;
|
||||
|
||||
map = {};
|
||||
|
||||
pool = [];
|
||||
|
||||
static schema = new Schema({
|
||||
type: 'object',
|
||||
properties: {},
|
||||
});
|
||||
|
||||
constructor(ecs) {
|
||||
this.ecs = ecs;
|
||||
}
|
||||
|
||||
allocateMany(count) {
|
||||
const results = [];
|
||||
while (count-- > 0 && this.pool.length > 0) {
|
||||
results.push(this.pool.pop());
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
create(entityId, values) {
|
||||
this.createMany([[entityId, values]]);
|
||||
}
|
||||
|
||||
destroy(entityId) {
|
||||
this.destroyMany([entityId]);
|
||||
}
|
||||
|
||||
destroyMany(entities) {
|
||||
this.freeMany(
|
||||
entities
|
||||
.map((entityId) => {
|
||||
if ('undefined' !== typeof this.map[entityId]) {
|
||||
return this.map[entityId];
|
||||
}
|
||||
throw new Error(`can't free for non-existent id ${entityId}`);
|
||||
}),
|
||||
);
|
||||
for (let i = 0; i < entities.length; i++) {
|
||||
this.map[entities[i]] = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
static filterDefaults(instance) {
|
||||
const json = {};
|
||||
for (const key in this.properties) {
|
||||
const {defaultValue} = this.properties[key];
|
||||
if (key in instance && instance[key] !== defaultValue) {
|
||||
json[key] = instance[key];
|
||||
}
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
freeMany(indices) {
|
||||
for (let i = 0; i < indices.length; ++i) {
|
||||
this.pool.push(indices[i]);
|
||||
}
|
||||
}
|
||||
|
||||
insertMany(entities) {
|
||||
const creating = [];
|
||||
for (let i = 0; i < entities.length; i++) {
|
||||
const [entityId, values] = entities[i];
|
||||
if (!this.get(entityId)) {
|
||||
creating.push([entityId, values]);
|
||||
}
|
||||
else {
|
||||
const instance = this.get(entityId);
|
||||
for (const i in values) {
|
||||
instance[i] = values[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
this.createMany(creating);
|
||||
}
|
||||
|
||||
markChange(entityId, key, value) {
|
||||
this.ecs.markChange(entityId, {[this.constructor.componentName]: {[key]: value}})
|
||||
}
|
||||
|
||||
mergeDiff(original, update) {
|
||||
return {...original, ...update};
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return this.schema.specification.properties;
|
||||
}
|
||||
|
||||
sizeOf(entityId) {
|
||||
return this.constructor.schema.sizeOf(this.get(entityId));
|
||||
}
|
||||
|
||||
}
|
|
@ -1,24 +1,43 @@
|
|||
import Base from './base.js';
|
||||
import Schema from './schema.js';
|
||||
|
||||
export default class Arbitrary extends Base {
|
||||
export default class Component {
|
||||
|
||||
data = [];
|
||||
|
||||
ecs;
|
||||
Instance;
|
||||
map = {};
|
||||
pool = [];
|
||||
serializer;
|
||||
|
||||
Instance;
|
||||
static schema = new Schema({
|
||||
type: 'object',
|
||||
properties: {},
|
||||
});
|
||||
|
||||
constructor(ecs) {
|
||||
this.ecs = ecs;
|
||||
}
|
||||
|
||||
allocateMany(count) {
|
||||
if (!this.Instance) {
|
||||
this.Instance = this.instanceFromSchema();
|
||||
}
|
||||
const results = super.allocateMany(count);
|
||||
count -= results.length; while (count--) {
|
||||
results.push(this.data.push(new this.Instance()) - 1);
|
||||
const results = [];
|
||||
while (count > 0) {
|
||||
results.push(
|
||||
this.pool.length > 0
|
||||
? this.pool.pop()
|
||||
: this.data.push(new this.Instance()) - 1,
|
||||
)
|
||||
count -= 1;
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
create(entityId, values) {
|
||||
this.createMany([[entityId, values]]);
|
||||
}
|
||||
|
||||
createMany(entries) {
|
||||
if (entries.length > 0) {
|
||||
const allocated = this.allocateMany(entries.length);
|
||||
|
@ -53,14 +72,63 @@ export default class Arbitrary extends Base {
|
|||
}
|
||||
}
|
||||
|
||||
serialize(entityId, view, offset) {
|
||||
this.constructor.schema.serialize(this.get(entityId), view, offset);
|
||||
destroy(entityId) {
|
||||
this.destroyMany([entityId]);
|
||||
}
|
||||
|
||||
destroyMany(entities) {
|
||||
this.freeMany(
|
||||
entities
|
||||
.map((entityId) => {
|
||||
if ('undefined' !== typeof this.map[entityId]) {
|
||||
return this.map[entityId];
|
||||
}
|
||||
throw new Error(`can't free for non-existent id ${entityId}`);
|
||||
}),
|
||||
);
|
||||
for (let i = 0; i < entities.length; i++) {
|
||||
this.map[entities[i]] = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
static filterDefaults(instance) {
|
||||
const json = {};
|
||||
for (const key in this.properties) {
|
||||
const {defaultValue} = this.properties[key];
|
||||
if (key in instance && instance[key] !== defaultValue) {
|
||||
json[key] = instance[key];
|
||||
}
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
freeMany(indices) {
|
||||
for (let i = 0; i < indices.length; ++i) {
|
||||
this.pool.push(indices[i]);
|
||||
}
|
||||
}
|
||||
|
||||
get(entityId) {
|
||||
return this.data[this.map[entityId]];
|
||||
}
|
||||
|
||||
insertMany(entities) {
|
||||
const creating = [];
|
||||
for (let i = 0; i < entities.length; i++) {
|
||||
const [entityId, values] = entities[i];
|
||||
if (!this.get(entityId)) {
|
||||
creating.push([entityId, values]);
|
||||
}
|
||||
else {
|
||||
const instance = this.get(entityId);
|
||||
for (const i in values) {
|
||||
instance[i] = values[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
this.createMany(creating);
|
||||
}
|
||||
|
||||
instanceFromSchema() {
|
||||
const Component = this;
|
||||
const Instance = class {
|
||||
|
@ -106,4 +174,24 @@ export default class Arbitrary extends Base {
|
|||
return Instance;
|
||||
}
|
||||
|
||||
markChange(entityId, key, value) {
|
||||
this.ecs.markChange(entityId, {[this.constructor.componentName]: {[key]: value}})
|
||||
}
|
||||
|
||||
mergeDiff(original, update) {
|
||||
return {...original, ...update};
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return this.schema.specification.properties;
|
||||
}
|
||||
|
||||
serialize(entityId, view, offset) {
|
||||
this.constructor.schema.serialize(this.get(entityId), view, offset);
|
||||
}
|
||||
|
||||
sizeOf(entityId) {
|
||||
return this.constructor.schema.sizeOf(this.get(entityId));
|
||||
}
|
||||
|
||||
}
|
56
app/ecs/component.test.js
Normal file
56
app/ecs/component.test.js
Normal file
|
@ -0,0 +1,56 @@
|
|||
import {expect, test} from 'vitest';
|
||||
|
||||
import Schema from './schema.js';
|
||||
import Component from './component.js';
|
||||
|
||||
test('creates instances', () => {
|
||||
class CreatingComponent extends Component {
|
||||
static schema = new Schema({
|
||||
type: 'object',
|
||||
properties: {foo: {defaultValue: 'bar', type: 'string'}},
|
||||
});
|
||||
}
|
||||
const ComponentInstance = new CreatingComponent();
|
||||
ComponentInstance.create(1);
|
||||
expect(ComponentInstance.get(1).entity)
|
||||
.to.equal(1);
|
||||
});
|
||||
|
||||
test('does not serialize default values', () => {
|
||||
class CreatingComponent extends Component {
|
||||
static schema = new Schema({
|
||||
type: 'object',
|
||||
properties: {foo: {defaultValue: 'bar', type: 'string'}, bar: {type: 'uint8'}},
|
||||
});
|
||||
}
|
||||
const fakeEcs = {markChange() {}};
|
||||
const ComponentInstance = new CreatingComponent(fakeEcs);
|
||||
ComponentInstance.create(1)
|
||||
expect(ComponentInstance.get(1).toJSON())
|
||||
.to.deep.equal({});
|
||||
ComponentInstance.get(1).bar = 1;
|
||||
expect(ComponentInstance.get(1).toJSON())
|
||||
.to.deep.equal({bar: 1});
|
||||
});
|
||||
|
||||
test('reuses instances', () => {
|
||||
class ReusingComponent extends Component {
|
||||
static schema = new Schema({
|
||||
type: 'object',
|
||||
properties: {foo: {type: 'string'}},
|
||||
});
|
||||
}
|
||||
const ComponentInstance = new ReusingComponent();
|
||||
ComponentInstance.create(1);
|
||||
const instance = ComponentInstance.get(1);
|
||||
ComponentInstance.destroy(1);
|
||||
expect(ComponentInstance.get(1))
|
||||
.to.be.undefined;
|
||||
expect(() => {
|
||||
ComponentInstance.destroy(1);
|
||||
})
|
||||
.to.throw();
|
||||
ComponentInstance.create(1);
|
||||
expect(ComponentInstance.get(1))
|
||||
.to.equal(instance);
|
||||
});
|
|
@ -1,12 +1,12 @@
|
|||
import {expect, test} from 'vitest';
|
||||
|
||||
import Arbitrary from './arbitrary.js';
|
||||
import Component from './component.js';
|
||||
import Ecs from './ecs.js';
|
||||
import Schema from './schema.js';
|
||||
import System from './system.js';
|
||||
|
||||
function wrapSpecification(name, specification) {
|
||||
return class Component extends Arbitrary {
|
||||
return class WrappedComponent extends Component {
|
||||
static componentName = name;
|
||||
static schema = new Schema({
|
||||
type: 'object',
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import {expect, test} from 'vitest';
|
||||
|
||||
import Arbitrary from './arbitrary.js';
|
||||
import Component from './component.js';
|
||||
import Query from './query.js';
|
||||
import Schema from './schema.js';
|
||||
|
||||
function wrapSpecification(name, specification) {
|
||||
return class Component extends Arbitrary {
|
||||
return class WrappedComponent extends Component {
|
||||
static name = name;
|
||||
static schema = new Schema({
|
||||
type: 'object',
|
||||
|
|
Loading…
Reference in New Issue
Block a user