refactor: less magic

This commit is contained in:
cha0s 2024-06-26 21:08:09 -05:00
parent 9bae378ac7
commit 45cb158f2a
28 changed files with 489 additions and 511 deletions

View File

@ -1,3 +1,7 @@
export default {
import Component from '@/ecs/component.js';
export default class Animation extends Component {
static properties = {
frame: {type: 'uint16'},
};
}

View File

@ -1,4 +1,8 @@
export default {
import Component from '@/ecs/component.js';
export default class AreaSize extends Component {
static properties = {
x: {type: 'uint16'},
y: {type: 'uint16'},
};
}

View File

@ -1,4 +1,8 @@
export default {
import Component from '@/ecs/component.js';
export default class Camera extends Component {
static properties = {
x: {type: 'uint16'},
y: {type: 'uint16'},
};
}

View File

@ -1,4 +1,7 @@
export default {
import Component from '@/ecs/component.js';
export default class Controlled extends Component {
static properties = {
locked: {type: 'uint8'},
moveUp: {type: 'float32'},
moveRight: {type: 'float32'},
@ -6,3 +9,4 @@ export default {
moveLeft: {type: 'float32'},
changeSlot: {type: 'int8'},
};
}

View File

@ -1,3 +1,7 @@
export default {
import Component from '@/ecs/component.js';
export default class Direction extends Component {
static properties = {
direction: {type: 'uint8'},
};
}

View File

@ -1,3 +1,7 @@
export default {
import Component from '@/ecs/component.js';
export default class Ecs extends Component {
static properties = {
path: {type: 'string'},
};
}

View File

@ -1,7 +1,6 @@
import Schema from '@/ecs/schema.js';
import Component from '@/ecs/component.js';
export default function(Component) {
return class Emitter extends Component {
export default class Emitter extends Component {
mergeDiff(original, update) {
const merged = {};
if (update.emit) {
@ -23,9 +22,4 @@ export default function(Component) {
}
};
}
static schema = new Schema({
type: 'object',
properties: {},
});
}
}

View File

@ -1 +1,3 @@
export default {};
import Component from '@/ecs/component.js';
export default class Engine extends Component {}

View File

@ -1,6 +1,10 @@
export default {
import Component from '@/ecs/component.js';
export default class Forces extends Component {
static properties = {
forceX: {type: 'float32'},
forceY: {type: 'float32'},
impulseX: {type: 'float32'},
impulseY: {type: 'float32'},
};
}

View File

@ -1,3 +1,7 @@
export default {
import Component from '@/ecs/component.js';
export default class Health extends Component {
static properties = {
health: {type: 'uint32'},
};
}

View File

@ -1,37 +1,14 @@
import Component from '@/ecs/component.js';
import Schema from '@/ecs/schema.js';
import gather from '@/util/gather.js';
const specificationsAndOrDecorators = gather(
const Gathered = gather(
import.meta.glob('./*.js', {eager: true, import: 'default'}),
);
const Components = {};
for (const componentName in specificationsAndOrDecorators) {
// TODO: byKey, byId, ...
if (Number.isInteger(+componentName)) {
continue;
}
const specificationOrDecorator = specificationsAndOrDecorators[componentName];
if ('function' === typeof specificationOrDecorator) {
Components[componentName] = specificationOrDecorator(
class Decorated extends Component {
for (const componentName in Gathered) {
Components[componentName] = class Named extends Gathered[componentName] {
static componentName = componentName;
}
);
if (!Components[componentName]) {
throw new Error(`Component ${componentName} decorator returned nothing`);
}
}
else {
Components[componentName] = class WrappedComponent extends Component {
static componentName = componentName;
static schema = new Schema({
type: 'object',
properties: specificationOrDecorator,
});
}
}
};
}
export default Components;

View File

@ -1,7 +1,6 @@
import Schema from '@/ecs/schema.js';
import Component from '@/ecs/component.js';
export default function(Component) {
return class Inventory extends Component {
export default class Inventory extends Component {
insertMany(entities) {
for (const [id, {slotChange}] of entities) {
if (slotChange) {
@ -95,9 +94,7 @@ export default function(Component) {
};
return Instance;
}
static schema = new Schema({
type: 'object',
properties: {
static properties = {
slots: {
type: 'map',
value: {
@ -108,7 +105,5 @@ export default function(Component) {
},
},
},
},
});
}
};
}

View File

@ -1 +1,3 @@
export default {};
import Component from '@/ecs/component.js';
export default class MainEntity extends Component {}

View File

@ -1,7 +1,6 @@
import Schema from '@/ecs/schema.js';
import Component from '@/ecs/component.js';
export default function(Component) {
return class Wielder extends Component {
export default class Position extends Component {
instanceFromSchema() {
const Instance = super.instanceFromSchema();
const Component = this;
@ -18,12 +17,8 @@ export default function(Component) {
});
return Instance;
}
static schema = new Schema({
type: 'object',
properties: {
static properties = {
x: {type: 'float32'},
y: {type: 'float32'},
},
});
}
};
}

View File

@ -1 +0,0 @@
export default {};

View File

@ -1,7 +1,6 @@
import Schema from '@/ecs/schema.js';
import Component from '@/ecs/component.js';
export default function(Component) {
return class Sound extends Component {
export default class Sound extends Component {
mergeDiff(original, update) {
const merged = {};
if (update.play) {
@ -21,9 +20,4 @@ export default function(Component) {
}
};
}
static schema = new Schema({
type: 'object',
properties: {},
});
}
}

View File

@ -1,3 +1,7 @@
export default {
import Component from '@/ecs/component.js';
export default class Speed extends Component {
static properties = {
speed: {type: 'float32'},
};
}

View File

@ -1,5 +1,9 @@
import Component from '@/ecs/component.js';
import vector2d from "./helpers/vector-2d";
export default {
export default class Sprite extends Component {
static properties = {
anchor: vector2d('float32', {x: 0.5, y: 0.5}),
animation: {type: 'string'},
elapsed: {type: 'float32'},
@ -8,3 +12,4 @@ export default {
source: {type: 'string'},
speed: {type: 'float32'},
};
}

View File

@ -1,7 +1,6 @@
import Schema from '@/ecs/schema.js';
import Component from '@/ecs/component.js';
export default function(Component) {
return class Ticking extends Component {
export default class Ticking extends Component {
instanceFromSchema() {
const Instance = super.instanceFromSchema();
@ -34,11 +33,7 @@ export default function(Component) {
}
return Instance;
}
static schema = new Schema({
type: 'object',
properties: {
static properties = {
isTicking: {defaultValue: 1, type: 'uint8'},
},
});
};
}

View File

@ -1,9 +1,8 @@
import Component from '@/ecs/component.js';
import vector2d from './helpers/vector-2d';
import Schema from '@/ecs/schema.js';
export default function(Component) {
return class TileLayers extends Component {
export default class TileLayers extends Component {
insertMany(entities) {
for (const [id, {layerChange}] of entities) {
if (layerChange) {
@ -86,9 +85,7 @@ export default function(Component) {
};
return Instance;
}
static schema = new Schema({
type: 'object',
properties: {
static properties = {
layers: {
type: 'array',
subtype: {
@ -106,7 +103,5 @@ export default function(Component) {
},
},
},
},
});
}
};
}

View File

@ -1 +0,0 @@
export default {};

View File

@ -1,6 +1,10 @@
export default {
import Component from '@/ecs/component.js';
export default class VisibleAabb extends Component {
static properties = {
x0: {type: 'float32'},
x1: {type: 'float32'},
y0: {type: 'float32'},
y1: {type: 'float32'},
};
}

View File

@ -1 +0,0 @@
export default {};

View File

@ -1,7 +1,6 @@
import Schema from '@/ecs/schema.js';
import Component from '@/ecs/component.js';
export default function(Component) {
return class Wielder extends Component {
export default class Wielder extends Component {
instanceFromSchema() {
const Instance = super.instanceFromSchema();
const Component = this;
@ -66,11 +65,7 @@ export default function(Component) {
}
return Instance;
}
static schema = new Schema({
type: 'object',
properties: {
static properties = {
activeSlot: {type: 'uint16'},
},
});
}
};
}

View File

@ -7,12 +7,8 @@ export default class Component {
Instance;
map = {};
pool = [];
serializer;
static schema = new Schema({
type: 'object',
properties: {},
});
static properties = {};
static $$schema;
constructor(ecs) {
this.ecs = ecs;
@ -41,7 +37,8 @@ export default class Component {
createMany(entries) {
if (entries.length > 0) {
const allocated = this.allocateMany(entries.length);
const keys = Object.keys(this.constructor.properties);
const {properties} = this.constructor.schema.specification;
const keys = Object.keys(properties);
for (let i = 0; i < entries.length; ++i) {
const [entityId, values = {}] = entries[i];
this.map[entityId] = allocated[i];
@ -51,7 +48,7 @@ export default class Component {
}
for (let k = 0; k < keys.length; ++k) {
const j = keys[k];
const {defaultValue} = this.constructor.properties[j];
const {defaultValue} = properties[j];
if (j in values) {
this.data[allocated[i]][j] = values[j];
}
@ -64,7 +61,7 @@ export default class Component {
}
deserialize(entityId, view, offset) {
const {properties} = this.constructor;
const {properties} = this.constructor.schema.specification;
const instance = this.get(entityId);
const deserialized = this.constructor.schema.deserialize(view, offset);
for (const key in properties) {
@ -92,9 +89,10 @@ export default class Component {
}
static filterDefaults(instance) {
const {properties} = this.schema.specification;
const json = {};
for (const key in this.properties) {
const {defaultValue} = this.properties[key];
for (const key in properties) {
const {defaultValue} = properties[key];
if (key in instance && instance[key] !== defaultValue) {
json[key] = instance[key];
}
@ -131,15 +129,15 @@ export default class Component {
instanceFromSchema() {
const Component = this;
const {specification} = Component.constructor.schema;
const Instance = class {
$$entity = 0;
constructor() {
this.$$reset();
}
$$reset() {
const {properties} = Component.constructor;
for (const key in properties) {
const {defaultValue} = properties[key];
for (const key in specification.properties) {
const {defaultValue} = specification.properties[key];
this[`$$${key}`] = defaultValue;
}
}
@ -157,7 +155,7 @@ export default class Component {
this.$$reset();
},
};
for (const key in Component.constructor.properties) {
for (const key in specification.properties) {
properties[key] = {
get: function get() {
return this[`$$${key}`];
@ -182,8 +180,14 @@ export default class Component {
return {...original, ...update};
}
static get properties() {
return this.schema.specification.properties;
static get schema() {
if (!this.$$schema) {
this.$$schema = new Schema({
type: 'object',
properties: this.properties,
});
}
return this.$$schema;
}
serialize(entityId, view, offset) {

View File

@ -1,14 +1,12 @@
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'}},
});
static properties = {
foo: {defaultValue: 'bar', type: 'string'},
};
}
const ComponentInstance = new CreatingComponent();
ComponentInstance.create(1);
@ -18,10 +16,9 @@ test('creates instances', () => {
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'}},
});
static properties = {
foo: {defaultValue: 'bar', type: 'string'}, bar: {type: 'uint8'},
};
}
const fakeEcs = {markChange() {}};
const ComponentInstance = new CreatingComponent(fakeEcs);
@ -35,10 +32,9 @@ test('does not serialize default values', () => {
test('reuses instances', () => {
class ReusingComponent extends Component {
static schema = new Schema({
type: 'object',
properties: {foo: {type: 'string'}},
});
static properties = {
foo: {type: 'string'},
};
}
const ComponentInstance = new ReusingComponent();
ComponentInstance.create(1);

View File

@ -2,26 +2,22 @@ import {expect, test} from 'vitest';
import Component from './component.js';
import Ecs from './ecs.js';
import Schema from './schema.js';
import System from './system.js';
function wrapSpecification(name, specification) {
function wrapProperties(name, properties) {
return class WrappedComponent extends Component {
static componentName = name;
static schema = new Schema({
type: 'object',
properties: specification,
});
static properties = properties;
};
}
const Empty = wrapSpecification('Empty', {});
const Empty = wrapProperties('Empty', {});
const Name = wrapSpecification('Name', {
const Name = wrapProperties('Name', {
name: {type: 'string'},
});
const Position = wrapSpecification('Position', {
const Position = wrapProperties('Position', {
x: {type: 'int32', defaultValue: 32},
y: {type: 'int32'},
z: {type: 'int32'},
@ -117,7 +113,7 @@ test('inserts components into entities', () => {
});
test('ticks systems', () => {
const Momentum = wrapSpecification('Momentum', {
const Momentum = wrapProperties('Momentum', {
x: {type: 'int32'},
y: {type: 'int32'},
z: {type: 'int32'},
@ -220,7 +216,7 @@ test('schedules entities to be deleted when ticking systems', () => {
test('adds components to and remove components from entities when ticking systems', () => {
let addLength, removeLength;
const ecs = new Ecs({
Components: {Foo: wrapSpecification('Foo', {bar: {type: 'uint8'}})},
Components: {Foo: wrapProperties('Foo', {bar: {type: 'uint8'}})},
Systems: {
AddComponent: class extends System {
static queries() {

View File

@ -2,21 +2,17 @@ import {expect, test} from 'vitest';
import Component from './component.js';
import Query from './query.js';
import Schema from './schema.js';
function wrapSpecification(name, specification) {
function wrapProperties(name, properties) {
return class WrappedComponent extends Component {
static name = name;
static schema = new Schema({
type: 'object',
properties: specification,
});
static properties = properties;
};
}
const A = new (wrapSpecification('A', {a: {type: 'int32', defaultValue: 420}}));
const B = new (wrapSpecification('B', {b: {type: 'int32', defaultValue: 69}}));
const C = new (wrapSpecification('C', {c: {type: 'int32'}}));
const A = new (wrapProperties('A', {a: {type: 'int32', defaultValue: 420}}));
const B = new (wrapProperties('B', {b: {type: 'int32', defaultValue: 69}}));
const C = new (wrapProperties('C', {c: {type: 'int32'}}));
const Components = {A, B, C};
Components.A.createMany([[2], [3]]);
@ -70,7 +66,7 @@ test('can deindex', () => {
});
test('can reindex', () => {
const Test = new (wrapSpecification('Test', {a: {type: 'int32', defaultValue: 420}}));
const Test = new (wrapProperties('Test', {a: {type: 'int32', defaultValue: 420}}));
Test.createMany([[2], [3]]);
const query = new Query(['Test'], fakeEcs({Test}));
query.reindex([2, 3]);