217 lines
5.3 KiB
JavaScript
217 lines
5.3 KiB
JavaScript
import Schema from './schema.js';
|
|
|
|
export default class Component {
|
|
|
|
data = [];
|
|
ecs;
|
|
Instance;
|
|
map = {};
|
|
pool = [];
|
|
static properties = {};
|
|
static $$schema;
|
|
|
|
constructor(ecs) {
|
|
this.ecs = ecs;
|
|
this.Instance = this.instanceFromSchema();
|
|
}
|
|
|
|
allocateMany(count) {
|
|
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;
|
|
}
|
|
|
|
async create(entityId, values) {
|
|
const [created] = await this.createMany([[entityId, values]]);
|
|
return created;
|
|
}
|
|
|
|
async createMany(entries) {
|
|
if (0 === entries.length) {
|
|
return [];
|
|
}
|
|
const allocated = this.allocateMany(entries.length);
|
|
const {properties} = this.constructor.schema.specification;
|
|
const keys = Object.keys(properties);
|
|
const promises = [];
|
|
for (let i = 0; i < entries.length; ++i) {
|
|
const [entityId, values = {}] = entries[i];
|
|
this.map[entityId] = allocated[i];
|
|
this.data[allocated[i]].entity = entityId;
|
|
for (let k = 0; k < keys.length; ++k) {
|
|
const j = keys[k];
|
|
const {defaultValue} = properties[j];
|
|
const instance = this.data[allocated[i]];
|
|
if (j in values) {
|
|
instance[j] = values[j];
|
|
}
|
|
else if ('undefined' !== typeof defaultValue) {
|
|
instance[j] = defaultValue;
|
|
}
|
|
}
|
|
promises.push(this.load(this.data[allocated[i]]));
|
|
}
|
|
await Promise.all(promises);
|
|
const created = [];
|
|
for (let i = 0; i < allocated.length; ++i) {
|
|
created.push(this.data[allocated[i]]);
|
|
}
|
|
return created;
|
|
}
|
|
|
|
deserialize(entityId, view, offset) {
|
|
const {properties} = this.constructor.schema.specification;
|
|
const instance = this.get(entityId);
|
|
const deserialized = this.constructor.schema.deserialize(view, offset);
|
|
for (const key in properties) {
|
|
instance[key] = deserialized[key];
|
|
}
|
|
}
|
|
|
|
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.data[this.map[entities[i]]].destroy();
|
|
this.map[entities[i]] = undefined;
|
|
}
|
|
}
|
|
|
|
static filterDefaults(instance) {
|
|
const {properties} = this.schema.specification;
|
|
const json = {};
|
|
for (const key in properties) {
|
|
const {defaultValue} = 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]];
|
|
}
|
|
|
|
async 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];
|
|
}
|
|
}
|
|
}
|
|
await this.createMany(creating);
|
|
}
|
|
|
|
instanceFromSchema() {
|
|
const Component = this;
|
|
const {specification} = Component.constructor.schema;
|
|
const Instance = class {
|
|
$$entity = 0;
|
|
constructor() {
|
|
this.$$reset();
|
|
}
|
|
$$reset() {
|
|
for (const key in specification.properties) {
|
|
const {defaultValue} = specification.properties[key];
|
|
this[`$$${key}`] = defaultValue;
|
|
}
|
|
}
|
|
destroy() {}
|
|
toNet() {
|
|
return Component.constructor.filterDefaults(this);
|
|
}
|
|
toJSON() {
|
|
return Component.constructor.filterDefaults(this);
|
|
}
|
|
};
|
|
const properties = {};
|
|
properties.entity = {
|
|
get: function get() {
|
|
return this.$$entity;
|
|
},
|
|
set: function set(v) {
|
|
this.$$entity = v;
|
|
this.$$reset();
|
|
},
|
|
};
|
|
for (const key in specification.properties) {
|
|
properties[key] = {
|
|
get: function get() {
|
|
return this[`$$${key}`];
|
|
},
|
|
set: function set(value) {
|
|
if (this[`$$${key}`] !== value) {
|
|
this[`$$${key}`] = value;
|
|
Component.markChange(this.entity, key, value);
|
|
}
|
|
},
|
|
};
|
|
}
|
|
Object.defineProperties(Instance.prototype, properties);
|
|
return Instance;
|
|
}
|
|
|
|
async load(instance) {
|
|
return instance;
|
|
}
|
|
|
|
markChange(entityId, key, value) {
|
|
this.ecs.markChange(entityId, {[this.constructor.componentName]: {[key]: value}})
|
|
}
|
|
|
|
mergeDiff(original, update) {
|
|
return {...original, ...update};
|
|
}
|
|
|
|
static get schema() {
|
|
if (!this.$$schema) {
|
|
this.$$schema = new Schema({
|
|
type: 'object',
|
|
properties: this.properties,
|
|
});
|
|
}
|
|
return this.$$schema;
|
|
}
|
|
|
|
serialize(entityId, view, offset) {
|
|
this.constructor.schema.serialize(this.get(entityId), view, offset);
|
|
}
|
|
|
|
sizeOf(entityId) {
|
|
return this.constructor.schema.sizeOf(this.get(entityId));
|
|
}
|
|
|
|
}
|