silphius/app/ecs/component.js

241 lines
5.9 KiB
JavaScript
Raw Normal View History

2024-06-26 10:33:31 -05:00
import Schema from './schema.js';
2024-06-10 22:42:30 -05:00
2024-06-26 10:33:31 -05:00
export default class Component {
2024-06-10 22:42:30 -05:00
2024-06-26 10:33:31 -05:00
ecs;
Instance;
2024-08-01 21:24:54 -05:00
instances = {};
2024-06-26 10:33:31 -05:00
pool = [];
2024-06-26 21:08:09 -05:00
static properties = {};
static $$schema;
2024-06-26 10:33:31 -05:00
constructor(ecs) {
this.ecs = ecs;
2024-06-27 02:05:10 -05:00
this.Instance = this.instanceFromSchema();
2024-06-26 10:33:31 -05:00
}
2024-06-10 22:42:30 -05:00
allocateMany(count) {
const results = [];
while (count > 0) {
results.push(
this.pool.length > 0
? this.pool.pop()
2024-08-01 21:24:54 -05:00
: new this.Instance(),
)
count -= 1;
}
return results;
}
2024-06-27 06:28:00 -05:00
async create(entityId, values) {
2024-07-07 17:26:17 -05:00
const [created] = await this.createMany([[entityId, values]]);
return created;
2024-06-26 10:33:31 -05:00
}
2024-06-27 06:28:00 -05:00
async createMany(entries) {
2024-07-07 17:26:17 -05:00
if (0 === entries.length) {
return [];
}
const allocated = this.allocateMany(entries.length);
2024-08-04 21:56:06 -05:00
const {
constructor: Schema,
specification: {concrete: {properties}},
} = this.constructor.schema;
const defaults = {};
for (const key in properties) {
defaults[key] = ((key) => () => Schema.defaultValue(properties[key]))(key);
switch (properties[key].type) {
case 'float32':
case 'float64':
case 'int8':
case 'int16':
case 'int32':
case 'int64':
case 'string':
case 'uint8':
case 'uint16':
case 'uint32':
case 'uint64': {
defaults[key] = defaults[key]();
break;
}
}
}
2024-07-07 17:26:17 -05:00
const promises = [];
for (let i = 0; i < entries.length; ++i) {
2024-08-01 21:24:54 -05:00
const [entityId, values] = entries[i];
const instance = allocated[i];
instance.entity = entityId;
this.instances[entityId] = instance;
2024-08-07 14:24:17 -05:00
const keys = new Set(Object.keys(defaults));
2024-08-01 21:10:16 -05:00
for (const key in values) {
keys.delete(key);
}
2024-08-04 21:56:06 -05:00
const defaultValues = {};
2024-08-01 21:10:16 -05:00
for (const key of keys) {
2024-08-04 21:56:06 -05:00
defaultValues[key] = 'function' === typeof defaults[key]
? defaults[key]()
: defaults[key];
2024-06-10 22:42:30 -05:00
}
2024-08-04 21:56:06 -05:00
instance.initialize(values, defaultValues);
2024-08-01 21:24:54 -05:00
promises.push(this.load(instance));
2024-07-07 17:26:17 -05:00
}
await Promise.all(promises);
2024-08-01 21:24:54 -05:00
return allocated;
2024-06-10 22:42:30 -05:00
}
2024-06-11 19:10:57 -05:00
deserialize(entityId, view, offset) {
2024-07-23 10:10:32 -05:00
const {properties} = this.constructor.schema.specification.concrete;
2024-06-12 01:38:05 -05:00
const instance = this.get(entityId);
const deserialized = this.constructor.schema.deserialize(view, offset);
for (const key in properties) {
instance[key] = deserialized[key];
}
2024-06-10 22:42:30 -05:00
}
2024-06-26 10:33:31 -05:00
destroy(entityId) {
2024-08-03 15:25:30 -05:00
this.destroyMany(new Set([entityId]));
2024-06-26 10:33:31 -05:00
}
2024-08-01 21:24:54 -05:00
destroyMany(entityIds) {
for (const entityId of entityIds) {
const instance = this.instances[entityId];
if ('undefined' === typeof instance) {
throw new Error(`can't free for non-existent id ${entityId}`);
}
instance.destroy();
this.pool.push(instance);
delete this.instances[entityId];
2024-06-26 10:33:31 -05:00
}
}
static filterDefaults(instance) {
2024-07-23 10:10:32 -05:00
const {properties} = this.schema.specification.concrete;
const Schema = this.schema.constructor;
2024-06-26 10:33:31 -05:00
const json = {};
2024-06-26 21:08:09 -05:00
for (const key in properties) {
2024-07-23 10:10:32 -05:00
if (key in instance && instance[key] !== Schema.defaultValue(properties[key])) {
2024-06-26 10:33:31 -05:00
json[key] = instance[key];
}
}
return json;
}
2024-06-11 19:10:57 -05:00
get(entityId) {
2024-08-01 21:24:54 -05:00
return this.instances[entityId];
2024-06-10 22:42:30 -05:00
}
2024-06-27 06:28:00 -05:00
async insertMany(entities) {
2024-07-30 11:46:04 -05:00
await this.createMany(entities);
2024-06-26 10:33:31 -05:00
}
2024-06-10 22:42:30 -05:00
instanceFromSchema() {
const Component = this;
2024-07-23 10:10:32 -05:00
const {concrete} = Component.constructor.schema.specification;
2024-06-10 22:42:30 -05:00
const Instance = class {
$$entity = 0;
2024-07-04 09:06:50 -05:00
destroy() {}
2024-08-04 21:56:06 -05:00
initialize(values, defaults) {
for (const key in values) {
this[`$$${key}`] = values[key];
}
for (const key in defaults) {
this[`$$${key}`] = defaults[key];
}
Component.ecs.markChange(this.entity, {[Component.constructor.componentName]: values})
2024-08-04 21:56:06 -05:00
}
2024-09-05 07:15:55 -05:00
toFullJSON() {
const {properties} = concrete;
const json = {};
for (const key in properties) {
json[key] = this[key];
}
return json;
}
2024-07-28 18:42:28 -05:00
toNet(recipient, data) {
2024-09-05 07:15:55 -05:00
if (data) {
return data;
}
return this.toFullJSON();
2024-06-10 22:42:30 -05:00
}
2024-07-13 00:33:41 -05:00
toJSON() {
2024-09-05 07:15:55 -05:00
return this.toFullJSON();
2024-07-13 00:33:41 -05:00
}
2024-07-31 21:07:17 -05:00
async update(values) {
2024-07-30 11:46:04 -05:00
for (const key in values) {
if (concrete.properties[key]) {
this[`$$${key}`] = values[key];
}
else {
this[key] = values[key];
}
}
}
2024-06-10 22:42:30 -05:00
};
const properties = {};
properties.entity = {
get: function get() {
return this.$$entity;
},
set: function set(v) {
this.$$entity = v;
},
};
2024-07-23 10:10:32 -05:00
for (const key in concrete.properties) {
2024-06-12 01:38:05 -05:00
properties[key] = {
2024-06-10 22:42:30 -05:00
get: function get() {
2024-06-12 01:38:05 -05:00
return this[`$$${key}`];
2024-06-10 22:42:30 -05:00
},
2024-06-12 01:38:05 -05:00
set: function set(value) {
if (this[`$$${key}`] !== value) {
this[`$$${key}`] = value;
Component.markChange(this.entity, key, value);
2024-06-10 22:42:30 -05:00
}
},
};
}
Object.defineProperties(Instance.prototype, properties);
return Instance;
}
2024-06-27 06:28:00 -05:00
async load(instance) {
return instance;
}
2024-06-26 10:33:31 -05:00
markChange(entityId, key, value) {
this.ecs.markChange(entityId, {[this.constructor.componentName]: {[key]: value}})
}
mergeDiff(original, update) {
return {...original, ...update};
}
2024-06-26 21:08:09 -05:00
static get schema() {
if (!this.$$schema) {
this.$$schema = new Schema({
type: 'object',
properties: this.properties,
});
}
return this.$$schema;
2024-06-26 10:33:31 -05:00
}
serialize(entityId, view, offset) {
this.constructor.schema.serialize(this.get(entityId), view, offset);
}
sizeOf(entityId) {
return this.constructor.schema.sizeOf(this.get(entityId));
}
2024-07-30 11:46:04 -05:00
async updateMany(entities) {
2024-07-31 21:07:17 -05:00
const promises = [];
2024-07-30 11:46:04 -05:00
for (let i = 0; i < entities.length; i++) {
const [entityId, values] = entities[i];
2024-07-31 21:07:17 -05:00
promises.push(this.get(entityId).update(values));
2024-07-30 11:46:04 -05:00
}
2024-07-31 21:07:17 -05:00
return Promise.all(promises);
2024-07-30 11:46:04 -05:00
}
2024-06-10 22:42:30 -05:00
}