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() {} 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)); } }