refactor: test and clean
This commit is contained in:
parent
a70b8c2d76
commit
da6d15e910
|
@ -1,375 +1,17 @@
|
||||||
/* eslint-disable guard-for-in, max-classes-per-file, no-restricted-syntax */
|
/* eslint-disable guard-for-in, max-classes-per-file, no-restricted-syntax */
|
||||||
|
import ArbitraryComponent from './component/arbitrary';
|
||||||
|
import FlatComponent from './component/flat';
|
||||||
import Schema from './schema';
|
import Schema from './schema';
|
||||||
import Serializer from './serializer';
|
|
||||||
|
|
||||||
class BaseComponent {
|
|
||||||
|
|
||||||
$$dirty = true;
|
|
||||||
|
|
||||||
map = [];
|
|
||||||
|
|
||||||
pool = [];
|
|
||||||
|
|
||||||
schema;
|
|
||||||
|
|
||||||
serializer;
|
|
||||||
|
|
||||||
constructor(schema) {
|
|
||||||
this.schema = schema;
|
|
||||||
this.serializer = new Serializer(schema);
|
|
||||||
}
|
|
||||||
|
|
||||||
allocate() {
|
|
||||||
const [index] = this.allocateMany(1);
|
|
||||||
return index;
|
|
||||||
}
|
|
||||||
|
|
||||||
allocateMany(count) {
|
|
||||||
const results = [];
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
|
||||||
while (count-- > 0 && this.pool.length > 0) {
|
|
||||||
results.push(this.pool.pop());
|
|
||||||
}
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
create(entity, values) {
|
|
||||||
const [result] = this.createMany([entity, values]);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
destroy(entity) {
|
|
||||||
this.destroyMany([entity]);
|
|
||||||
}
|
|
||||||
|
|
||||||
destroyMany(entities) {
|
|
||||||
this.freeMany(entities.map((entity) => this.map[entity]).filter((index) => !!index));
|
|
||||||
for (let i = 0; i < entities.length; i++) {
|
|
||||||
this.map[entities[i]] = undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get dirty() {
|
|
||||||
return this.$$dirty;
|
|
||||||
}
|
|
||||||
|
|
||||||
setDirty(dirty) {
|
|
||||||
this.$$dirty = dirty;
|
|
||||||
}
|
|
||||||
|
|
||||||
free(index) {
|
|
||||||
this.freeMany([index]);
|
|
||||||
}
|
|
||||||
|
|
||||||
freeMany(indices) {
|
|
||||||
for (let i = 0; i < indices.length; ++i) {
|
|
||||||
this.pool.push(indices[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
set(entity, values) {
|
|
||||||
const instance = this.getUnsafe(entity);
|
|
||||||
for (const i in values) {
|
|
||||||
instance[i] = values[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
class FlatComponent extends BaseComponent {
|
|
||||||
|
|
||||||
chunkSize = 64;
|
|
||||||
|
|
||||||
caret = 0;
|
|
||||||
|
|
||||||
data = new ArrayBuffer(0);
|
|
||||||
|
|
||||||
Window;
|
|
||||||
|
|
||||||
window;
|
|
||||||
|
|
||||||
allocateMany(count) {
|
|
||||||
const results = super.allocateMany(count);
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
|
||||||
count -= results.length;
|
|
||||||
if (count > 0) {
|
|
||||||
const required = (this.caret + count) * this.constructor.width;
|
|
||||||
if (required > this.data.byteLength) {
|
|
||||||
const chunkWidth = this.chunkSize * this.constructor.width;
|
|
||||||
const remainder = required % chunkWidth;
|
|
||||||
const extra = 0 === remainder ? 0 : chunkWidth - remainder;
|
|
||||||
const size = required + extra;
|
|
||||||
const data = new ArrayBuffer(size);
|
|
||||||
(new Uint8Array(data)).set(this.data);
|
|
||||||
this.data = data;
|
|
||||||
}
|
|
||||||
for (let i = 0; i < count; ++i) {
|
|
||||||
results.push(this.caret++);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
clean() {
|
|
||||||
if (!this.dirty) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!this.Window) {
|
|
||||||
this.Window = this.makeWindowClass();
|
|
||||||
}
|
|
||||||
const window = new this.Window(this.data, this);
|
|
||||||
for (let i = 0; i < this.caret; ++i) {
|
|
||||||
window.dirty = false;
|
|
||||||
window.cursor += this.constructor.width;
|
|
||||||
}
|
|
||||||
this.setDirty(0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
createMany(entries) {
|
|
||||||
if (entries.length > 0) {
|
|
||||||
const allocated = this.allocateMany(entries.length);
|
|
||||||
if (!this.Window) {
|
|
||||||
this.Window = this.makeWindowClass();
|
|
||||||
}
|
|
||||||
const window = new this.Window(this.data, this);
|
|
||||||
const {defaultValues} = this.schema;
|
|
||||||
for (let i = 0; i < entries.length; ++i) {
|
|
||||||
let entity;
|
|
||||||
let values = {};
|
|
||||||
if (Array.isArray(entries[i])) {
|
|
||||||
[entity, values] = entries[i];
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
entity = entries[i];
|
|
||||||
}
|
|
||||||
this.map[entity] = allocated[i];
|
|
||||||
window.cursor = allocated[i] * this.constructor.width;
|
|
||||||
window.entity = entity;
|
|
||||||
for (const [i] of this.schema) {
|
|
||||||
if (i in values) {
|
|
||||||
window[i] = values[i];
|
|
||||||
}
|
|
||||||
else if (i in defaultValues) {
|
|
||||||
window[i] = defaultValues[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get(entity) {
|
|
||||||
if ('undefined' === typeof this.map[entity]) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
if (!this.Window) {
|
|
||||||
this.Window = this.makeWindowClass();
|
|
||||||
}
|
|
||||||
const window = new this.Window(this.data, this);
|
|
||||||
window.cursor = this.map[entity] * this.constructor.width;
|
|
||||||
return window;
|
|
||||||
}
|
|
||||||
|
|
||||||
getUnsafe(entity) {
|
|
||||||
if ('undefined' === typeof this.map[entity]) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
if (!this.Window) {
|
|
||||||
this.Window = this.makeWindowClass();
|
|
||||||
}
|
|
||||||
if (!this.window) {
|
|
||||||
this.window = new this.Window(this.data, this);
|
|
||||||
}
|
|
||||||
this.window.cursor = this.map[entity] * this.constructor.width;
|
|
||||||
return this.window;
|
|
||||||
}
|
|
||||||
|
|
||||||
makeWindowClass() {
|
|
||||||
const Component = this;
|
|
||||||
class Window {
|
|
||||||
|
|
||||||
cursor = 0;
|
|
||||||
|
|
||||||
parent;
|
|
||||||
|
|
||||||
view;
|
|
||||||
|
|
||||||
constructor(data, parent) {
|
|
||||||
if (data) {
|
|
||||||
this.view = new DataView(data);
|
|
||||||
}
|
|
||||||
if (parent) {
|
|
||||||
this.parent = parent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
toJSON() {
|
|
||||||
const json = {};
|
|
||||||
for (const [i] of Component.schema) {
|
|
||||||
json[i] = this[i];
|
|
||||||
}
|
|
||||||
return json;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
let offset = 0;
|
|
||||||
const properties = {};
|
|
||||||
const {width} = this.constructor;
|
|
||||||
const get = (type) => (
|
|
||||||
`return this.view.get${Schema.viewMethodFromType(type)}(this.cursor + ${offset}, true);`
|
|
||||||
);
|
|
||||||
const set = (type) => [
|
|
||||||
`this.parent.setDirty(1, Number(this.view.getBigUint64(this.cursor + ${width - 9}, true)));`,
|
|
||||||
`this.view.set${Schema.viewMethodFromType(type)}(this.cursor + ${offset}, v, true);`,
|
|
||||||
`this.view.setUint8(this.cursor + ${width - 1}, 1, true);`,
|
|
||||||
].join('');
|
|
||||||
/* eslint-disable no-new-func */
|
|
||||||
properties.dirty = {
|
|
||||||
get: new Function('', `return !!this.view.getUint8(this.cursor + ${width - 1}, true);`),
|
|
||||||
set: new Function('v', `this.view.setUint8(this.cursor + ${width - 1}, v ? 1 : 0, true);`),
|
|
||||||
};
|
|
||||||
properties.entity = {
|
|
||||||
get: new Function('', `return Number(this.view.getBigUint64(this.cursor + ${width - 9}, true));`),
|
|
||||||
set: new Function('v', `this.view.setBigUint64(this.cursor + ${width - 9}, BigInt(v), true);`),
|
|
||||||
};
|
|
||||||
for (const [i, spec] of this.schema) {
|
|
||||||
const {type} = spec;
|
|
||||||
properties[i] = {};
|
|
||||||
properties[i].get = new Function('', get(type));
|
|
||||||
properties[i].set = new Function('v', set(type));
|
|
||||||
offset += Schema.sizeOfType(type);
|
|
||||||
}
|
|
||||||
/* eslint-enable no-new-func */
|
|
||||||
Object.defineProperties(Window.prototype, properties);
|
|
||||||
return Window;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
class ArbitraryComponent extends BaseComponent {
|
|
||||||
|
|
||||||
data = [];
|
|
||||||
|
|
||||||
Instance;
|
|
||||||
|
|
||||||
allocateMany(count) {
|
|
||||||
if (!this.Instance) {
|
|
||||||
this.Instance = this.instanceFromSchema();
|
|
||||||
}
|
|
||||||
const results = super.allocateMany(count);
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
|
||||||
count -= results.length;
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
|
||||||
while (count--) {
|
|
||||||
results.push(this.data.push(new this.Instance()) - 1);
|
|
||||||
}
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
clean() {
|
|
||||||
if (!this.dirty) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for (const i in this.map) {
|
|
||||||
this.data[this.map[i]].dirty = false;
|
|
||||||
}
|
|
||||||
this.setDirty(false, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
createMany(entries) {
|
|
||||||
if (entries.length > 0) {
|
|
||||||
const allocated = this.allocateMany(entries.length);
|
|
||||||
for (let i = 0; i < entries.length; ++i) {
|
|
||||||
let entity;
|
|
||||||
let values = {};
|
|
||||||
if (Array.isArray(entries[i])) {
|
|
||||||
[entity, values] = entries[i];
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
entity = entries[i];
|
|
||||||
}
|
|
||||||
this.map[entity] = allocated[i];
|
|
||||||
this.data[allocated[i]].entity = entity;
|
|
||||||
for (const j in values) {
|
|
||||||
if (this.schema.has(j)) {
|
|
||||||
this.data[allocated[i]][j] = values[j];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get(entity) {
|
|
||||||
return this.data[this.map[entity]];
|
|
||||||
}
|
|
||||||
|
|
||||||
getUnsafe(entity) {
|
|
||||||
return this.get(entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
instanceFromSchema() {
|
|
||||||
const Component = this;
|
|
||||||
const Instance = class {
|
|
||||||
|
|
||||||
$$dirty = 1;
|
|
||||||
|
|
||||||
$$entity = 0;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
for (const [i, {defaultValue}] of Component.schema) {
|
|
||||||
this[i] = defaultValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
toJSON() {
|
|
||||||
const json = {};
|
|
||||||
for (const [i] of Component.schema) {
|
|
||||||
json[i] = this[i];
|
|
||||||
}
|
|
||||||
return json;
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
const properties = {};
|
|
||||||
properties.dirty = {
|
|
||||||
get: function get() {
|
|
||||||
return !!this.$$dirty;
|
|
||||||
},
|
|
||||||
set: function set(v) {
|
|
||||||
this.$$dirty = v;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
properties.entity = {
|
|
||||||
get: function get() {
|
|
||||||
return this.$$entity;
|
|
||||||
},
|
|
||||||
set: function set(v) {
|
|
||||||
this.$$entity = v;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
for (const [i] of Component.schema) {
|
|
||||||
properties[i] = {
|
|
||||||
get: function get() {
|
|
||||||
return this[`$$${i}`];
|
|
||||||
},
|
|
||||||
set: function set(v) {
|
|
||||||
this[`$$${i}`] = v;
|
|
||||||
this.$$dirty = 1;
|
|
||||||
Component.setDirty(1, this.entity);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
Object.defineProperties(Instance.prototype, properties);
|
|
||||||
return Instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function ComponentRouter() {
|
export default function ComponentRouter() {
|
||||||
const schema = new Schema(this.constructor.schema);
|
const schema = new Schema(this.constructor.schema);
|
||||||
let RealClass;
|
let RealClass;
|
||||||
if (schema.width > 0) {
|
if (schema.width > 0) {
|
||||||
RealClass = class extends FlatComponent {};
|
RealClass = class extends FlatComponent {
|
||||||
RealClass.width = schema.width + 9; // 1 for dirty, 8 for entity
|
|
||||||
|
static width = schema.width + 9; // 1 for dirty, 8 for entity
|
||||||
|
|
||||||
|
};
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
RealClass = class extends ArbitraryComponent {};
|
RealClass = class extends ArbitraryComponent {};
|
||||||
|
|
121
packages/ecs/src/component/arbitrary.js
Normal file
121
packages/ecs/src/component/arbitrary.js
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
/* eslint-disable guard-for-in, max-classes-per-file, no-restricted-syntax */
|
||||||
|
import BaseComponent from './base';
|
||||||
|
|
||||||
|
export default class ArbitraryComponent extends BaseComponent {
|
||||||
|
|
||||||
|
data = [];
|
||||||
|
|
||||||
|
Instance;
|
||||||
|
|
||||||
|
allocateMany(count) {
|
||||||
|
if (!this.Instance) {
|
||||||
|
this.Instance = this.instanceFromSchema();
|
||||||
|
}
|
||||||
|
const results = super.allocateMany(count);
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
count -= results.length;
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
while (count--) {
|
||||||
|
results.push(this.data.push(new this.Instance()) - 1);
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
createMany(entries) {
|
||||||
|
if (entries.length > 0) {
|
||||||
|
const allocated = this.allocateMany(entries.length);
|
||||||
|
for (let i = 0; i < entries.length; ++i) {
|
||||||
|
let entity;
|
||||||
|
let values = {};
|
||||||
|
if (Array.isArray(entries[i])) {
|
||||||
|
[entity, values] = entries[i];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
entity = entries[i];
|
||||||
|
}
|
||||||
|
this.map[entity] = allocated[i];
|
||||||
|
this.data[allocated[i]].entity = entity;
|
||||||
|
for (const j in values) {
|
||||||
|
if (this.schema.has(j)) {
|
||||||
|
this.data[allocated[i]][j] = values[j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get(entity) {
|
||||||
|
return this.data[this.map[entity]];
|
||||||
|
}
|
||||||
|
|
||||||
|
getUnsafe(entity) {
|
||||||
|
return this.get(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
instanceFromSchema() {
|
||||||
|
const Component = this;
|
||||||
|
const Instance = class {
|
||||||
|
|
||||||
|
$$dirty = 1;
|
||||||
|
|
||||||
|
$$entity = 0;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
for (const [i, {defaultValue}] of Component.schema) {
|
||||||
|
this[i] = defaultValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
toJSON() {
|
||||||
|
const json = {};
|
||||||
|
for (const [i] of Component.schema) {
|
||||||
|
json[i] = this[i];
|
||||||
|
}
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
const properties = {};
|
||||||
|
properties.dirty = {
|
||||||
|
get: function get() {
|
||||||
|
return !!this.$$dirty;
|
||||||
|
},
|
||||||
|
set: function set(v) {
|
||||||
|
this.$$dirty = v;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
properties.entity = {
|
||||||
|
get: function get() {
|
||||||
|
return this.$$entity;
|
||||||
|
},
|
||||||
|
set: function set(v) {
|
||||||
|
this.$$entity = v;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
for (const [i] of Component.schema) {
|
||||||
|
properties[i] = {
|
||||||
|
get: function get() {
|
||||||
|
return this[`$$${i}`];
|
||||||
|
},
|
||||||
|
set: function set(v) {
|
||||||
|
this[`$$${i}`] = v;
|
||||||
|
this.$$dirty = 1;
|
||||||
|
Component.setDirty(this.entity);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
Object.defineProperties(Instance.prototype, properties);
|
||||||
|
return Instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
setClean() {
|
||||||
|
super.setClean();
|
||||||
|
if (!this.dirty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (let i = 0; i < this.data.length; i++) {
|
||||||
|
this.data[i].dirty = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
79
packages/ecs/src/component/base.js
Normal file
79
packages/ecs/src/component/base.js
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
/* eslint-disable no-restricted-syntax, guard-for-in */
|
||||||
|
import Serializer from '../serializer';
|
||||||
|
|
||||||
|
export default class BaseComponent {
|
||||||
|
|
||||||
|
$$dirty = true;
|
||||||
|
|
||||||
|
map = [];
|
||||||
|
|
||||||
|
pool = [];
|
||||||
|
|
||||||
|
schema;
|
||||||
|
|
||||||
|
serializer;
|
||||||
|
|
||||||
|
constructor(schema) {
|
||||||
|
this.schema = schema;
|
||||||
|
this.serializer = new Serializer(schema);
|
||||||
|
}
|
||||||
|
|
||||||
|
allocate() {
|
||||||
|
const [index] = this.allocateMany(1);
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
allocateMany(count) {
|
||||||
|
const results = [];
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
while (count-- > 0 && this.pool.length > 0) {
|
||||||
|
results.push(this.pool.pop());
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
create(entity, values) {
|
||||||
|
this.createMany([entity, values]);
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy(entity) {
|
||||||
|
this.destroyMany([entity]);
|
||||||
|
}
|
||||||
|
|
||||||
|
destroyMany(entities) {
|
||||||
|
this.freeMany(entities.map((entity) => this.map[entity]).filter((index) => !!index));
|
||||||
|
for (let i = 0; i < entities.length; i++) {
|
||||||
|
this.map[entities[i]] = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get dirty() {
|
||||||
|
return this.$$dirty;
|
||||||
|
}
|
||||||
|
|
||||||
|
free(index) {
|
||||||
|
this.freeMany([index]);
|
||||||
|
}
|
||||||
|
|
||||||
|
freeMany(indices) {
|
||||||
|
for (let i = 0; i < indices.length; ++i) {
|
||||||
|
this.pool.push(indices[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
set(entity, values) {
|
||||||
|
const instance = this.getUnsafe(entity);
|
||||||
|
for (const i in values) {
|
||||||
|
instance[i] = values[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setClean() {
|
||||||
|
this.$$dirty = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
setDirty() {
|
||||||
|
this.$$dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
172
packages/ecs/src/component/flat.js
Normal file
172
packages/ecs/src/component/flat.js
Normal file
|
@ -0,0 +1,172 @@
|
||||||
|
/* eslint-disable guard-for-in, max-classes-per-file, no-restricted-syntax */
|
||||||
|
import BaseComponent from './base';
|
||||||
|
import Schema from '../schema';
|
||||||
|
|
||||||
|
export default class FlatComponent extends BaseComponent {
|
||||||
|
|
||||||
|
chunkSize = 64;
|
||||||
|
|
||||||
|
caret = 0;
|
||||||
|
|
||||||
|
data = new ArrayBuffer(0);
|
||||||
|
|
||||||
|
Window;
|
||||||
|
|
||||||
|
window;
|
||||||
|
|
||||||
|
allocateMany(count) {
|
||||||
|
const results = super.allocateMany(count);
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
count -= results.length;
|
||||||
|
if (count > 0) {
|
||||||
|
const required = (this.caret + count) * this.constructor.width;
|
||||||
|
if (required > this.data.byteLength) {
|
||||||
|
const chunkWidth = this.chunkSize * this.constructor.width;
|
||||||
|
const remainder = required % chunkWidth;
|
||||||
|
const extra = 0 === remainder ? 0 : chunkWidth - remainder;
|
||||||
|
const size = required + extra;
|
||||||
|
const data = new ArrayBuffer(size);
|
||||||
|
(new Uint8Array(data)).set(this.data);
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
for (let i = 0; i < count; ++i) {
|
||||||
|
results.push(this.caret++);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
createMany(entries) {
|
||||||
|
if (entries.length > 0) {
|
||||||
|
const allocated = this.allocateMany(entries.length);
|
||||||
|
if (!this.Window) {
|
||||||
|
this.Window = this.makeWindowClass();
|
||||||
|
}
|
||||||
|
const window = new this.Window(this.data, this);
|
||||||
|
const {defaultValues} = this.schema;
|
||||||
|
for (let i = 0; i < entries.length; ++i) {
|
||||||
|
let entity;
|
||||||
|
let values = {};
|
||||||
|
if (Array.isArray(entries[i])) {
|
||||||
|
[entity, values] = entries[i];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
entity = entries[i];
|
||||||
|
}
|
||||||
|
this.map[entity] = allocated[i];
|
||||||
|
window.cursor = allocated[i] * this.constructor.width;
|
||||||
|
window.entity = entity;
|
||||||
|
for (const [i] of this.schema) {
|
||||||
|
if (i in values) {
|
||||||
|
window[i] = values[i];
|
||||||
|
}
|
||||||
|
else if ('undefined' !== typeof defaultValues[i]) {
|
||||||
|
window[i] = defaultValues[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get(entity) {
|
||||||
|
if ('undefined' === typeof this.map[entity]) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
if (!this.Window) {
|
||||||
|
this.Window = this.makeWindowClass();
|
||||||
|
}
|
||||||
|
const window = new this.Window(this.data, this);
|
||||||
|
window.cursor = this.map[entity] * this.constructor.width;
|
||||||
|
return window;
|
||||||
|
}
|
||||||
|
|
||||||
|
getUnsafe(entity) {
|
||||||
|
if ('undefined' === typeof this.map[entity]) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
if (!this.Window) {
|
||||||
|
this.Window = this.makeWindowClass();
|
||||||
|
}
|
||||||
|
if (!this.window) {
|
||||||
|
this.window = new this.Window(this.data, this);
|
||||||
|
}
|
||||||
|
this.window.cursor = this.map[entity] * this.constructor.width;
|
||||||
|
return this.window;
|
||||||
|
}
|
||||||
|
|
||||||
|
makeWindowClass() {
|
||||||
|
const Component = this;
|
||||||
|
class Window {
|
||||||
|
|
||||||
|
cursor = 0;
|
||||||
|
|
||||||
|
parent;
|
||||||
|
|
||||||
|
view;
|
||||||
|
|
||||||
|
constructor(data, parent) {
|
||||||
|
if (data) {
|
||||||
|
this.view = new DataView(data);
|
||||||
|
}
|
||||||
|
if (parent) {
|
||||||
|
this.parent = parent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
toJSON() {
|
||||||
|
const json = {};
|
||||||
|
for (const [i] of Component.schema) {
|
||||||
|
json[i] = this[i];
|
||||||
|
}
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
let offset = 0;
|
||||||
|
const properties = {};
|
||||||
|
const {width} = this.constructor;
|
||||||
|
const get = (type) => (
|
||||||
|
`return this.view.get${Schema.viewMethodFromType(type)}(this.cursor + ${offset}, true);`
|
||||||
|
);
|
||||||
|
const set = (type) => [
|
||||||
|
`this.parent.setDirty(Number(this.view.getBigUint64(this.cursor + ${width - 9}, true)));`,
|
||||||
|
`this.view.set${Schema.viewMethodFromType(type)}(this.cursor + ${offset}, v, true);`,
|
||||||
|
`this.view.setUint8(this.cursor + ${width - 1}, 1, true);`,
|
||||||
|
].join('');
|
||||||
|
/* eslint-disable no-new-func */
|
||||||
|
properties.dirty = {
|
||||||
|
get: new Function('', `return !!this.view.getUint8(this.cursor + ${width - 1}, true);`),
|
||||||
|
set: new Function('v', `this.view.setUint8(this.cursor + ${width - 1}, v ? 1 : 0, true);`),
|
||||||
|
};
|
||||||
|
properties.entity = {
|
||||||
|
get: new Function('', `return Number(this.view.getBigUint64(this.cursor + ${width - 9}, true));`),
|
||||||
|
set: new Function('v', `this.view.setBigUint64(this.cursor + ${width - 9}, BigInt(v), true);`),
|
||||||
|
};
|
||||||
|
for (const [i, spec] of this.schema) {
|
||||||
|
const {type} = spec;
|
||||||
|
properties[i] = {};
|
||||||
|
properties[i].get = new Function('', get(type));
|
||||||
|
properties[i].set = new Function('v', set(type));
|
||||||
|
offset += Schema.sizeOfType(type);
|
||||||
|
}
|
||||||
|
/* eslint-enable no-new-func */
|
||||||
|
Object.defineProperties(Window.prototype, properties);
|
||||||
|
return Window;
|
||||||
|
}
|
||||||
|
|
||||||
|
setClean() {
|
||||||
|
super.setClean();
|
||||||
|
if (!this.dirty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this.Window) {
|
||||||
|
this.Window = this.makeWindowClass();
|
||||||
|
}
|
||||||
|
const window = new this.Window(this.data, this);
|
||||||
|
for (let i = 0; i < this.caret; ++i) {
|
||||||
|
window.dirty = false;
|
||||||
|
window.cursor += this.constructor.width;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -19,12 +19,9 @@ export default class Ecs {
|
||||||
constructor(Components) {
|
constructor(Components) {
|
||||||
for (const i in Components) {
|
for (const i in Components) {
|
||||||
const comp = new Components[i]();
|
const comp = new Components[i]();
|
||||||
const {setDirty} = comp;
|
comp.setDirty = (entity) => {
|
||||||
comp.setDirty = (dirty, entity) => {
|
comp.$$dirty = true;
|
||||||
setDirty.call(comp, dirty);
|
this.dirty.add(entity);
|
||||||
if (entity) {
|
|
||||||
this.dirty.add(entity);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
this.Components[i] = comp;
|
this.Components[i] = comp;
|
||||||
}
|
}
|
||||||
|
@ -299,7 +296,7 @@ export default class Ecs {
|
||||||
|
|
||||||
tickClean() {
|
tickClean() {
|
||||||
for (const i in this.Components) {
|
for (const i in this.Components) {
|
||||||
this.Components[i].clean();
|
this.Components[i].setClean();
|
||||||
}
|
}
|
||||||
this.dirty.clear();
|
this.dirty.clear();
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import Schema from './schema';
|
||||||
export default class Serializer {
|
export default class Serializer {
|
||||||
|
|
||||||
constructor(schema) {
|
constructor(schema) {
|
||||||
this.schema = schema;
|
this.schema = schema instanceof Schema ? schema : new Schema(schema);
|
||||||
}
|
}
|
||||||
|
|
||||||
decode(view, destination, offset = 0) {
|
decode(view, destination, offset = 0) {
|
||||||
|
|
15
packages/ecs/test/schema.js
Normal file
15
packages/ecs/test/schema.js
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import {expect} from 'chai';
|
||||||
|
|
||||||
|
import Schema from '../src/schema';
|
||||||
|
|
||||||
|
it('can validate a schema', () => {
|
||||||
|
expect(() => new Schema({test: 'unknown'}))
|
||||||
|
.to.throw();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can calculate the size of an instance', () => {
|
||||||
|
expect((new Schema({foo: 'uint8', bar: 'uint32'})).sizeOf({foo: 69, bar: 420}))
|
||||||
|
.to.equal(5);
|
||||||
|
expect((new Schema({foo: 'string'})).sizeOf({foo: 'hi'}))
|
||||||
|
.to.equal(4 + 2);
|
||||||
|
});
|
32
packages/ecs/test/serializer.js
Normal file
32
packages/ecs/test/serializer.js
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
import {expect} from 'chai';
|
||||||
|
|
||||||
|
import Serializer from '../src/serializer';
|
||||||
|
|
||||||
|
it('can encode and decode', () => {
|
||||||
|
const entries = [
|
||||||
|
['uint8', 255],
|
||||||
|
['int8', -128],
|
||||||
|
['int8', 127],
|
||||||
|
['uint16', 65535],
|
||||||
|
['int16', -32768],
|
||||||
|
['int16', 32767],
|
||||||
|
['uint32', 4294967295],
|
||||||
|
['int32', -2147483648],
|
||||||
|
['int32', 2147483647],
|
||||||
|
['uint64', 18446744073709551615n],
|
||||||
|
['int64', -9223372036854775808n],
|
||||||
|
['int64', 9223372036854775807n],
|
||||||
|
['float32', 0.5],
|
||||||
|
['float64', 1.234],
|
||||||
|
['string', 'hello world'],
|
||||||
|
];
|
||||||
|
const schema = entries.reduce((r, [type]) => ({...r, [Object.keys(r).length]: type}), {});
|
||||||
|
const data = entries.reduce((r, [, value]) => ({...r, [Object.keys(r).length]: value}), {});
|
||||||
|
const serializer = new Serializer(schema);
|
||||||
|
const view = new DataView(new ArrayBuffer(serializer.schema.sizeOf(data)));
|
||||||
|
serializer.encode(data, view);
|
||||||
|
const result = {};
|
||||||
|
serializer.decode(view, result);
|
||||||
|
expect(data)
|
||||||
|
.to.deep.equal(result);
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user