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 {
frame: {type: 'uint16'},
};
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 {
x: {type: 'uint16'},
y: {type: 'uint16'},
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 {
x: {type: 'uint16'},
y: {type: 'uint16'},
import Component from '@/ecs/component.js';
export default class Camera extends Component {
static properties = {
x: {type: 'uint16'},
y: {type: 'uint16'},
};
}

View File

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

View File

@ -1,3 +1,7 @@
export default {
direction: {type: 'uint8'},
};
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 {
path: {type: 'string'},
import Component from '@/ecs/component.js';
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) {
return class Emitter extends Component {
mergeDiff(original, update) {
const merged = {};
if (update.emit) {
merged.emit = {
...original.emit,
...update.emit,
}
export default class Emitter extends Component {
mergeDiff(original, update) {
const merged = {};
if (update.emit) {
merged.emit = {
...original.emit,
...update.emit,
}
return merged;
}
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});
}
};
}
static schema = new Schema({
type: 'object',
properties: {},
});
return merged;
}
}
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 {
forceX: {type: 'float32'},
forceY: {type: 'float32'},
impulseX: {type: 'float32'},
impulseY: {type: 'float32'},
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 {
health: {type: 'uint32'},
};
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 {
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,
});
}
}
for (const componentName in Gathered) {
Components[componentName] = class Named extends Gathered[componentName] {
static componentName = componentName;
};
}
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) {
return class Inventory extends Component {
insertMany(entities) {
for (const [id, {slotChange}] of entities) {
if (slotChange) {
const {slots} = this.get(id);
for (const slotIndex in slotChange) {
if (false === slotChange[slotIndex]) {
delete slots[slotIndex];
}
else {
slots[slotIndex] = {
...slots[slotIndex],
...slotChange[slotIndex],
};
}
export default class Inventory extends Component {
insertMany(entities) {
for (const [id, {slotChange}] of entities) {
if (slotChange) {
const {slots} = this.get(id);
for (const slotIndex in slotChange) {
if (false === slotChange[slotIndex]) {
delete slots[slotIndex];
}
else {
slots[slotIndex] = {
...slots[slotIndex],
...slotChange[slotIndex],
};
}
}
}
return super.insertMany(entities);
}
mergeDiff(original, update) {
if (!update.slotChange) {
return super.mergeDiff(original, update);
}
const slotChange = {
...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};
return super.insertMany(entities);
}
mergeDiff(original, update) {
if (!update.slotChange) {
return super.mergeDiff(original, update);
}
instanceFromSchema() {
const Instance = super.instanceFromSchema();
const Component = this;
Instance.prototype.item = async function (slot) {
const {slots} = this;
if (!(slot in slots)) {
return undefined;
}
const json = await (
fetch([slots[slot].source, 'item.json'].join('/'))
.then((response) => (response.ok ? response.json() : {}))
);
const item = {
...slots[slot],
...json,
const slotChange = {
...original.slotChange,
};
for (const index in update.slotChange) {
if (false === update.slotChange[index]) {
slotChange[index] = false;
}
else {
slotChange[index] = {
...slotChange[index],
...update.slotChange[index],
};
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({
type: 'object',
properties: {
slots: {
type: 'map',
value: {
type: 'object',
properties: {
quantity: {type: 'uint16'},
source: {type: 'string'},
},
},
return {slotChange};
}
instanceFromSchema() {
const Instance = super.instanceFromSchema();
const Component = this;
Instance.prototype.item = async function (slot) {
const {slots} = this;
if (!(slot in slots)) {
return undefined;
}
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) {
return class Wielder extends Component {
instanceFromSchema() {
const Instance = super.instanceFromSchema();
const Component = this;
Object.defineProperty(Instance.prototype, 'tile', {
get: function () {
const {TileLayers} = Component.ecs.get(1);
const {Position: {x, y}} = Component.ecs.get(this.entity);
const {tileSize} = TileLayers.layers[0];
return {
x: (x - (x % tileSize.x)) / tileSize.x,
y: (y - (y % tileSize.y)) / tileSize.y,
}
},
});
return Instance;
}
static schema = new Schema({
type: 'object',
properties: {
x: {type: 'float32'},
y: {type: 'float32'},
export default class Position extends Component {
instanceFromSchema() {
const Instance = super.instanceFromSchema();
const Component = this;
Object.defineProperty(Instance.prototype, 'tile', {
get: function () {
const {TileLayers} = Component.ecs.get(1);
const {Position: {x, y}} = Component.ecs.get(this.entity);
const {tileSize} = TileLayers.layers[0];
return {
x: (x - (x % tileSize.x)) / tileSize.x,
y: (y - (y % tileSize.y)) / tileSize.y,
}
},
});
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) {
return class Sound extends Component {
mergeDiff(original, update) {
const merged = {};
if (update.play) {
merged.play = [
...(original.play ?? []),
...update.play,
];
export default class Sound extends Component {
mergeDiff(original, update) {
const merged = {};
if (update.play) {
merged.play = [
...(original.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 {
speed: {type: 'float32'},
};
import Component from '@/ecs/component.js';
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";
export default {
anchor: vector2d('float32', {x: 0.5, y: 0.5}),
animation: {type: 'string'},
elapsed: {type: 'float32'},
frame: {type: 'uint16'},
frames: {type: 'uint16'},
source: {type: 'string'},
speed: {type: 'float32'},
};
export default class Sprite extends Component {
static properties = {
anchor: vector2d('float32', {x: 0.5, y: 0.5}),
animation: {type: 'string'},
elapsed: {type: 'float32'},
frame: {type: 'uint16'},
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) {
return class Ticking extends Component {
instanceFromSchema() {
const Instance = super.instanceFromSchema();
export default class Ticking extends Component {
instanceFromSchema() {
const Instance = super.instanceFromSchema();
Instance.prototype.$$finished = [];
Instance.prototype.$$tickingPromises = [];
Instance.prototype.addTickingPromise = function(tickingPromise) {
this.$$tickingPromises.push(tickingPromise);
tickingPromise.then(() => {
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;
Instance.prototype.$$finished = [];
Instance.prototype.$$tickingPromises = [];
Instance.prototype.addTickingPromise = function(tickingPromise) {
this.$$tickingPromises.push(tickingPromise);
tickingPromise.then(() => {
this.$$finished.push(tickingPromise);
});
}
static schema = new Schema({
type: 'object',
properties: {
isTicking: {defaultValue: 1, type: 'uint8'},
},
});
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 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 Schema from '@/ecs/schema.js';
export default function(Component) {
return class TileLayers extends Component {
insertMany(entities) {
for (const [id, {layerChange}] of entities) {
if (layerChange) {
const component = this.get(id);
const {layers} = component;
for (const layerIndex in layerChange) {
for (const calculated in layerChange[layerIndex]) {
const tile = layerChange[layerIndex][calculated];
layers[layerIndex].data[calculated] = tile;
}
layers[layerIndex] = {...layers[layerIndex]};
export default class TileLayers extends Component {
insertMany(entities) {
for (const [id, {layerChange}] of entities) {
if (layerChange) {
const component = this.get(id);
const {layers} = component;
for (const layerIndex in layerChange) {
for (const calculated in layerChange[layerIndex]) {
const tile = layerChange[layerIndex][calculated];
layers[layerIndex].data[calculated] = tile;
}
layers[layerIndex] = {...layers[layerIndex]};
}
}
return super.insertMany(entities);
}
mergeDiff(original, update) {
if (!update.layerChange) {
return super.mergeDiff(original, update);
}
const layerChange = {
...original.layerChange,
return super.insertMany(entities);
}
mergeDiff(original, update) {
if (!update.layerChange) {
return super.mergeDiff(original, update);
}
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() {
const Instance = super.instanceFromSchema();
const Component = this;
Instance.prototype.layer = function (index) {
const {layers} = this;
if (!(index in layers)) {
return undefined;
return {layerChange};
}
instanceFromSchema() {
const Instance = super.instanceFromSchema();
const Component = this;
Instance.prototype.layer = function (index) {
const {layers} = this;
if (!(index in layers)) {
return undefined;
}
const instance = this;
class LayerProxy {
constructor(layer) {
this.layer = layer;
}
const instance = this;
class LayerProxy {
constructor(layer) {
this.layer = layer;
}
get area() {
return this.layer.area;
}
get source() {
return this.layer.source;
}
stamp(at, data) {
const changes = {};
for (const row in data) {
const columns = data[row];
for (const column in columns) {
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;
get area() {
return this.layer.area;
}
get source() {
return this.layer.source;
}
stamp(at, data) {
const changes = {};
for (const row in data) {
const columns = data[row];
for (const column in columns) {
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;
}
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]);
};
return Instance;
}
static schema = new Schema({
type: 'object',
properties: {
layers: {
type: 'array',
subtype: {
type: 'object',
properties: {
area: vector2d('float32'),
data: {
type: 'array',
subtype: {
type: 'uint16',
},
},
source: {type: 'string'},
tileSize: vector2d('float32'),
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;
}
}
return new LayerProxy(layers[index]);
};
return Instance;
}
static properties = {
layers: {
type: 'array',
subtype: {
type: 'object',
properties: {
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 {
x0: {type: 'float32'},
x1: {type: 'float32'},
y0: {type: 'float32'},
y1: {type: 'float32'},
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,76 +1,71 @@
import Schema from '@/ecs/schema.js';
import Component from '@/ecs/component.js';
export default function(Component) {
return class Wielder extends Component {
instanceFromSchema() {
const Instance = super.instanceFromSchema();
const Component = this;
Instance.prototype.activeItem = async function () {
const {Inventory, Wielder} = Component.ecs.get(this.entity);
return Inventory.item(Wielder.activeSlot + 1);
};
Instance.prototype.project = function(position, projection) {
const {TileLayers: {layers: [layer]}} = Component.ecs.get(1);
const {Direction: {direction}} = Component.ecs.get(this.entity);
let startX = position.x;
let startY = position.y;
switch (direction) {
case 0:
startX += projection.distance[1];
startY -= projection.distance[0];
break;
case 1:
startX += projection.distance[0];
startY += projection.distance[1];
break;
case 2:
startX -= projection.distance[1];
startY += projection.distance[0];
break;
case 3:
startX -= projection.distance[0];
startY -= projection.distance[1];
break;
}
const projected = [];
for (const row in projection.grid) {
const columns = projection.grid[row];
for (const column in columns) {
const targeted = projection.grid[row][column];
if (targeted) {
let axe;
switch (direction) {
case 0:
axe = [column, row];
break;
case 1:
axe = [-row, column];
break;
case 2:
axe = [-column, -row];
break;
case 3:
axe = [row, -column];
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});
export default class Wielder extends Component {
instanceFromSchema() {
const Instance = super.instanceFromSchema();
const Component = this;
Instance.prototype.activeItem = async function () {
const {Inventory, Wielder} = Component.ecs.get(this.entity);
return Inventory.item(Wielder.activeSlot + 1);
};
Instance.prototype.project = function(position, projection) {
const {TileLayers: {layers: [layer]}} = Component.ecs.get(1);
const {Direction: {direction}} = Component.ecs.get(this.entity);
let startX = position.x;
let startY = position.y;
switch (direction) {
case 0:
startX += projection.distance[1];
startY -= projection.distance[0];
break;
case 1:
startX += projection.distance[0];
startY += projection.distance[1];
break;
case 2:
startX -= projection.distance[1];
startY += projection.distance[0];
break;
case 3:
startX -= projection.distance[0];
startY -= projection.distance[1];
break;
}
const projected = [];
for (const row in projection.grid) {
const columns = projection.grid[row];
for (const column in columns) {
const targeted = projection.grid[row][column];
if (targeted) {
let axe;
switch (direction) {
case 0:
axe = [column, row];
break;
case 1:
axe = [-row, column];
break;
case 2:
axe = [-column, -row];
break;
case 3:
axe = [row, -column];
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});
}
}
return projected;
}
return Instance;
return projected;
}
static schema = new Schema({
type: 'object',
properties: {
activeSlot: {type: 'uint16'},
},
});
return Instance;
}
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]);