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';
frame: {type: 'uint16'},
}; 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';
x: {type: 'uint16'},
y: {type: 'uint16'}, 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';
x: {type: 'uint16'},
y: {type: 'uint16'}, export default class Camera extends Component {
static properties = {
x: {type: 'uint16'},
y: {type: 'uint16'},
};
} }

View File

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

View File

@ -1,3 +1,7 @@
export default { import Component from '@/ecs/component.js';
direction: {type: 'uint8'},
}; 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';
path: {type: 'string'},
export default class Ecs extends Component {
static properties = {
path: {type: 'string'},
};
} }

View File

@ -1,31 +1,25 @@
import Schema from '@/ecs/schema.js'; import Component from '@/ecs/component.js';
export default function(Component) { export default class Emitter extends Component {
return class Emitter extends Component { mergeDiff(original, update) {
mergeDiff(original, update) { const merged = {};
const merged = {}; if (update.emit) {
if (update.emit) { merged.emit = {
merged.emit = { ...original.emit,
...original.emit, ...update.emit,
...update.emit,
}
} }
return merged;
} }
instanceFromSchema() { return merged;
const Component = this;
const Instance = super.instanceFromSchema();
return class EmitterInstance extends Instance {
emitting = [];
id = 0;
emit(specification) {
Component.markChange(this.entity, 'emit', {[this.id++]: specification});
}
};
}
static schema = new Schema({
type: 'object',
properties: {},
});
} }
} instanceFromSchema() {
const Component = this;
const Instance = super.instanceFromSchema();
return class EmitterInstance extends Instance {
emitting = [];
id = 0;
emit(specification) {
Component.markChange(this.entity, 'emit', {[this.id++]: specification});
}
};
}
}

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';
forceX: {type: 'float32'},
forceY: {type: 'float32'}, export default class Forces extends Component {
impulseX: {type: 'float32'}, static properties = {
impulseY: {type: 'float32'}, 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';
health: {type: 'uint32'},
}; 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'; import gather from '@/util/gather.js';
const specificationsAndOrDecorators = gather( const Gathered = gather(
import.meta.glob('./*.js', {eager: true, import: 'default'}), import.meta.glob('./*.js', {eager: true, import: 'default'}),
); );
const Components = {}; const Components = {};
for (const componentName in specificationsAndOrDecorators) { for (const componentName in Gathered) {
// TODO: byKey, byId, ... Components[componentName] = class Named extends Gathered[componentName] {
if (Number.isInteger(+componentName)) { static componentName = componentName;
continue; };
}
const specificationOrDecorator = specificationsAndOrDecorators[componentName];
if ('function' === typeof specificationOrDecorator) {
Components[componentName] = specificationOrDecorator(
class Decorated extends Component {
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; export default Components;

View File

@ -1,114 +1,109 @@
import Schema from '@/ecs/schema.js'; import Component from '@/ecs/component.js';
export default function(Component) { export default class Inventory extends Component {
return class Inventory extends Component { insertMany(entities) {
insertMany(entities) { for (const [id, {slotChange}] of entities) {
for (const [id, {slotChange}] of entities) { if (slotChange) {
if (slotChange) { const {slots} = this.get(id);
const {slots} = this.get(id); for (const slotIndex in slotChange) {
for (const slotIndex in slotChange) { if (false === slotChange[slotIndex]) {
if (false === slotChange[slotIndex]) { delete slots[slotIndex];
delete slots[slotIndex]; }
} else {
else { slots[slotIndex] = {
slots[slotIndex] = { ...slots[slotIndex],
...slots[slotIndex], ...slotChange[slotIndex],
...slotChange[slotIndex], };
};
}
} }
} }
} }
return super.insertMany(entities);
} }
mergeDiff(original, update) { return super.insertMany(entities);
if (!update.slotChange) { }
return super.mergeDiff(original, update); mergeDiff(original, update) {
} if (!update.slotChange) {
const slotChange = { return super.mergeDiff(original, update);
...original.slotChange,
};
for (const index in update.slotChange) {
if (false === update.slotChange[index]) {
slotChange[index] = false;
}
else {
slotChange[index] = {
...slotChange[index],
...update.slotChange[index],
};
}
}
return {slotChange};
} }
instanceFromSchema() { const slotChange = {
const Instance = super.instanceFromSchema(); ...original.slotChange,
const Component = this; };
Instance.prototype.item = async function (slot) { for (const index in update.slotChange) {
const {slots} = this; if (false === update.slotChange[index]) {
if (!(slot in slots)) { slotChange[index] = false;
return undefined; }
} else {
const json = await ( slotChange[index] = {
fetch([slots[slot].source, 'item.json'].join('/')) ...slotChange[index],
.then((response) => (response.ok ? response.json() : {})) ...update.slotChange[index],
);
const item = {
...slots[slot],
...json,
}; };
const instance = this; }
const proxy = new Proxy(item, {
set(target, property, value) {
slots[slot][property] = value;
if ('qty' === property && value <= 0) {
Component.markChange(instance.entity, 'slotChange', {[slot]: false});
delete slots[slot];
}
else {
Component.markChange(instance.entity, 'slotChange', {[slot]: {[property]: value}});
}
return true;
},
});
return proxy;
};
Instance.prototype.swapSlots = function(l, r) {
const {slots} = this;
const tmp = slots[l];
const change = {};
if (slots[r]) {
change[l] = slots[l] = slots[r];
}
else {
change[l] = false;
delete slots[l];
}
if (tmp) {
change[r] = slots[r] = tmp;
}
else {
change[r] = false;
delete slots[r];
}
Component.markChange(this.entity, 'slotChange', change);
};
return Instance;
} }
static schema = new Schema({ return {slotChange};
type: 'object', }
properties: { instanceFromSchema() {
slots: { const Instance = super.instanceFromSchema();
type: 'map', const Component = this;
value: { Instance.prototype.item = async function (slot) {
type: 'object', const {slots} = this;
properties: { if (!(slot in slots)) {
quantity: {type: 'uint16'}, return undefined;
source: {type: 'string'}, }
}, const json = await (
}, fetch([slots[slot].source, 'item.json'].join('/'))
.then((response) => (response.ok ? response.json() : {}))
);
const item = {
...slots[slot],
...json,
};
const instance = this;
const proxy = new Proxy(item, {
set(target, property, value) {
slots[slot][property] = value;
if ('qty' === property && value <= 0) {
Component.markChange(instance.entity, 'slotChange', {[slot]: false});
delete slots[slot];
}
else {
Component.markChange(instance.entity, 'slotChange', {[slot]: {[property]: value}});
}
return true;
},
});
return proxy;
};
Instance.prototype.swapSlots = function(l, r) {
const {slots} = this;
const tmp = slots[l];
const change = {};
if (slots[r]) {
change[l] = slots[l] = slots[r];
}
else {
change[l] = false;
delete slots[l];
}
if (tmp) {
change[r] = slots[r] = tmp;
}
else {
change[r] = false;
delete slots[r];
}
Component.markChange(this.entity, 'slotChange', change);
};
return Instance;
}
static properties = {
slots: {
type: 'map',
value: {
type: 'object',
properties: {
quantity: {type: 'uint16'},
source: {type: 'string'},
}, },
}, },
}); },
} };
} }

View File

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

View File

@ -1,29 +1,24 @@
import Schema from '@/ecs/schema.js'; import Component from '@/ecs/component.js';
export default function(Component) { export default class Position extends Component {
return class Wielder extends Component { instanceFromSchema() {
instanceFromSchema() { const Instance = super.instanceFromSchema();
const Instance = super.instanceFromSchema(); const Component = this;
const Component = this; Object.defineProperty(Instance.prototype, 'tile', {
Object.defineProperty(Instance.prototype, 'tile', { get: function () {
get: function () { const {TileLayers} = Component.ecs.get(1);
const {TileLayers} = Component.ecs.get(1); const {Position: {x, y}} = Component.ecs.get(this.entity);
const {Position: {x, y}} = Component.ecs.get(this.entity); const {tileSize} = TileLayers.layers[0];
const {tileSize} = TileLayers.layers[0]; return {
return { x: (x - (x % tileSize.x)) / tileSize.x,
x: (x - (x % tileSize.x)) / tileSize.x, y: (y - (y % tileSize.y)) / tileSize.y,
y: (y - (y % tileSize.y)) / tileSize.y, }
}
},
});
return Instance;
}
static schema = new Schema({
type: 'object',
properties: {
x: {type: 'float32'},
y: {type: 'float32'},
}, },
}); });
return Instance;
} }
static properties = {
x: {type: 'float32'},
y: {type: 'float32'},
};
} }

View File

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

View File

@ -1,29 +1,23 @@
import Schema from '@/ecs/schema.js'; import Component from '@/ecs/component.js';
export default function(Component) { export default class Sound extends Component {
return class Sound extends Component { mergeDiff(original, update) {
mergeDiff(original, update) { const merged = {};
const merged = {}; if (update.play) {
if (update.play) { merged.play = [
merged.play = [ ...(original.play ?? []),
...(original.play ?? []), ...update.play,
...update.play, ];
]; }
return merged;
}
instanceFromSchema() {
const Component = this;
const Instance = super.instanceFromSchema();
return class SoundInstance extends Instance {
play(source) {
Component.markChange(this.entity, 'play', [source]);
} }
return merged; };
}
instanceFromSchema() {
const Component = this;
const Instance = super.instanceFromSchema();
return class SoundInstance extends Instance {
play(source) {
Component.markChange(this.entity, 'play', [source]);
}
};
}
static schema = new Schema({
type: 'object',
properties: {},
});
} }
} }

View File

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

View File

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

View File

@ -1,44 +1,39 @@
import Schema from '@/ecs/schema.js'; import Component from '@/ecs/component.js';
export default function(Component) { export default class Ticking extends Component {
return class Ticking extends Component { instanceFromSchema() {
instanceFromSchema() { const Instance = super.instanceFromSchema();
const Instance = super.instanceFromSchema();
Instance.prototype.$$finished = []; Instance.prototype.$$finished = [];
Instance.prototype.$$tickingPromises = []; Instance.prototype.$$tickingPromises = [];
Instance.prototype.addTickingPromise = function(tickingPromise) { Instance.prototype.addTickingPromise = function(tickingPromise) {
this.$$tickingPromises.push(tickingPromise); this.$$tickingPromises.push(tickingPromise);
tickingPromise.then(() => { tickingPromise.then(() => {
this.$$finished.push(tickingPromise); this.$$finished.push(tickingPromise);
}); });
}
Instance.prototype.tick = function(elapsed) {
for (const tickingPromise of this.$$finished) {
this.$$tickingPromises.splice(
this.$$tickingPromises.indexOf(tickingPromise),
1,
);
}
this.$$finished = [];
for (const tickingPromise of this.$$tickingPromises) {
tickingPromise.tick(elapsed);
}
for (const tickingPromise of this.$$finished) {
this.$$tickingPromises.splice(
this.$$tickingPromises.indexOf(tickingPromise),
1,
);
}
this.$$finished = [];
}
return Instance;
} }
static schema = new Schema({ Instance.prototype.tick = function(elapsed) {
type: 'object', for (const tickingPromise of this.$$finished) {
properties: { this.$$tickingPromises.splice(
isTicking: {defaultValue: 1, type: 'uint8'}, this.$$tickingPromises.indexOf(tickingPromise),
}, 1,
}); );
}
this.$$finished = [];
for (const tickingPromise of this.$$tickingPromises) {
tickingPromise.tick(elapsed);
}
for (const tickingPromise of this.$$finished) {
this.$$tickingPromises.splice(
this.$$tickingPromises.indexOf(tickingPromise),
1,
);
}
this.$$finished = [];
}
return Instance;
}
static properties = {
isTicking: {defaultValue: 1, type: 'uint8'},
}; };
} }

View File

@ -1,112 +1,107 @@
import Component from '@/ecs/component.js';
import vector2d from './helpers/vector-2d'; import vector2d from './helpers/vector-2d';
import Schema from '@/ecs/schema.js'; export default class TileLayers extends Component {
insertMany(entities) {
export default function(Component) { for (const [id, {layerChange}] of entities) {
return class TileLayers extends Component { if (layerChange) {
insertMany(entities) { const component = this.get(id);
for (const [id, {layerChange}] of entities) { const {layers} = component;
if (layerChange) { for (const layerIndex in layerChange) {
const component = this.get(id); for (const calculated in layerChange[layerIndex]) {
const {layers} = component; const tile = layerChange[layerIndex][calculated];
for (const layerIndex in layerChange) { layers[layerIndex].data[calculated] = tile;
for (const calculated in layerChange[layerIndex]) {
const tile = layerChange[layerIndex][calculated];
layers[layerIndex].data[calculated] = tile;
}
layers[layerIndex] = {...layers[layerIndex]};
} }
layers[layerIndex] = {...layers[layerIndex]};
} }
} }
return super.insertMany(entities);
} }
mergeDiff(original, update) { return super.insertMany(entities);
if (!update.layerChange) { }
return super.mergeDiff(original, update); mergeDiff(original, update) {
} if (!update.layerChange) {
const layerChange = { return super.mergeDiff(original, update);
...original.layerChange, }
const layerChange = {
...original.layerChange,
};
for (const index in update.layerChange) {
layerChange[index] = {
...layerChange[index],
...update.layerChange[index],
}; };
for (const index in update.layerChange) {
layerChange[index] = {
...layerChange[index],
...update.layerChange[index],
};
}
return {layerChange};
} }
instanceFromSchema() { return {layerChange};
const Instance = super.instanceFromSchema(); }
const Component = this; instanceFromSchema() {
Instance.prototype.layer = function (index) { const Instance = super.instanceFromSchema();
const {layers} = this; const Component = this;
if (!(index in layers)) { Instance.prototype.layer = function (index) {
return undefined; const {layers} = this;
if (!(index in layers)) {
return undefined;
}
const instance = this;
class LayerProxy {
constructor(layer) {
this.layer = layer;
} }
const instance = this; get area() {
class LayerProxy { return this.layer.area;
constructor(layer) { }
this.layer = layer; get source() {
} return this.layer.source;
get area() { }
return this.layer.area; stamp(at, data) {
} const changes = {};
get source() { for (const row in data) {
return this.layer.source; const columns = data[row];
} for (const column in columns) {
stamp(at, data) { const tile = columns[column];
const changes = {}; const x = at.x + parseInt(column);
for (const row in data) { const y = at.y + parseInt(row);
const columns = data[row]; if (x < 0 || y < 0 || x >= this.layer.area.x || y >= this.layer.area.y) {
for (const column in columns) { continue;
const tile = columns[column];
const x = at.x + parseInt(column);
const y = at.y + parseInt(row);
if (x < 0 || y < 0 || x >= this.layer.area.x || y >= this.layer.area.y) {
continue;
}
const calculated = y * this.layer.area.x + x;
this.layer.data[calculated] = tile;
changes[calculated] = tile;
} }
const calculated = y * this.layer.area.x + x;
this.layer.data[calculated] = tile;
changes[calculated] = tile;
} }
Component.markChange(instance.entity, 'layerChange', {[index]: changes});
}
tile({x, y}) {
if (x < 0 || y < 0 || x >= this.layer.area.x || y >= this.layer.area.y) {
return undefined;
}
return this.layer.data[y * this.layer.area.x + x];
}
get tileSize() {
return this.layer.tileSize;
} }
Component.markChange(instance.entity, 'layerChange', {[index]: changes});
} }
return new LayerProxy(layers[index]); tile({x, y}) {
}; if (x < 0 || y < 0 || x >= this.layer.area.x || y >= this.layer.area.y) {
return Instance; return undefined;
} }
static schema = new Schema({ return this.layer.data[y * this.layer.area.x + x];
type: 'object', }
properties: { get tileSize() {
layers: { return this.layer.tileSize;
type: 'array', }
subtype: { }
type: 'object', return new LayerProxy(layers[index]);
properties: { };
area: vector2d('float32'), return Instance;
data: { }
type: 'array', static properties = {
subtype: { layers: {
type: 'uint16', type: 'array',
}, subtype: {
}, type: 'object',
source: {type: 'string'}, properties: {
tileSize: vector2d('float32'), area: vector2d('float32'),
data: {
type: 'array',
subtype: {
type: 'uint16',
}, },
}, },
source: {type: 'string'},
tileSize: vector2d('float32'),
}, },
}, },
}); },
} };
} }

View File

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

View File

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

View File

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

View File

@ -1,76 +1,71 @@
import Schema from '@/ecs/schema.js'; import Component from '@/ecs/component.js';
export default function(Component) { export default class Wielder extends Component {
return class Wielder extends Component { instanceFromSchema() {
instanceFromSchema() { const Instance = super.instanceFromSchema();
const Instance = super.instanceFromSchema(); const Component = this;
const Component = this; Instance.prototype.activeItem = async function () {
Instance.prototype.activeItem = async function () { const {Inventory, Wielder} = Component.ecs.get(this.entity);
const {Inventory, Wielder} = Component.ecs.get(this.entity); return Inventory.item(Wielder.activeSlot + 1);
return Inventory.item(Wielder.activeSlot + 1); };
}; Instance.prototype.project = function(position, projection) {
Instance.prototype.project = function(position, projection) { const {TileLayers: {layers: [layer]}} = Component.ecs.get(1);
const {TileLayers: {layers: [layer]}} = Component.ecs.get(1); const {Direction: {direction}} = Component.ecs.get(this.entity);
const {Direction: {direction}} = Component.ecs.get(this.entity); let startX = position.x;
let startX = position.x; let startY = position.y;
let startY = position.y; switch (direction) {
switch (direction) { case 0:
case 0: startX += projection.distance[1];
startX += projection.distance[1]; startY -= projection.distance[0];
startY -= projection.distance[0]; break;
break; case 1:
case 1: startX += projection.distance[0];
startX += projection.distance[0]; startY += projection.distance[1];
startY += projection.distance[1]; break;
break; case 2:
case 2: startX -= projection.distance[1];
startX -= projection.distance[1]; startY += projection.distance[0];
startY += projection.distance[0]; break;
break; case 3:
case 3: startX -= projection.distance[0];
startX -= projection.distance[0]; startY -= projection.distance[1];
startY -= projection.distance[1]; break;
break; }
} const projected = [];
const projected = []; for (const row in projection.grid) {
for (const row in projection.grid) { const columns = projection.grid[row];
const columns = projection.grid[row]; for (const column in columns) {
for (const column in columns) { const targeted = projection.grid[row][column];
const targeted = projection.grid[row][column]; if (targeted) {
if (targeted) { let axe;
let axe; switch (direction) {
switch (direction) { case 0:
case 0: axe = [column, row];
axe = [column, row]; break;
break; case 1:
case 1: axe = [-row, column];
axe = [-row, column]; break;
break; case 2:
case 2: axe = [-column, -row];
axe = [-column, -row]; break;
break; case 3:
case 3: axe = [row, -column];
axe = [row, -column]; break;
break;
}
const x = startX + parseInt(axe[0]);
const y = startY + parseInt(axe[1]);
if (x < 0 || y < 0 || x >= layer.area.x || y >= layer.area.y) {
continue;
}
projected.push({x, y});
} }
const x = startX + parseInt(axe[0]);
const y = startY + parseInt(axe[1]);
if (x < 0 || y < 0 || x >= layer.area.x || y >= layer.area.y) {
continue;
}
projected.push({x, y});
} }
} }
return projected;
} }
return Instance; return projected;
} }
static schema = new Schema({ return Instance;
type: 'object',
properties: {
activeSlot: {type: 'uint16'},
},
});
} }
static properties = {
activeSlot: {type: 'uint16'},
};
} }

View File

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

View File

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

View File

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

View File

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