refactor: liberalize deserializer
This commit is contained in:
parent
547cd5a9b9
commit
0685243b7b
|
@ -1,5 +1,6 @@
|
|||
import Component from './component.js';
|
||||
import EntityFactory from './entity-factory.js';
|
||||
import Schema from './schema.js';
|
||||
import System from './system.js';
|
||||
|
||||
export default class Ecs {
|
||||
|
@ -122,8 +123,15 @@ export default class Ecs {
|
|||
static deserialize(view) {
|
||||
const ecs = new this();
|
||||
let cursor = 0;
|
||||
const headerComponents = view.getUint16(cursor, true);
|
||||
cursor += 2;
|
||||
const keys = [];
|
||||
for (let i = 0; i < headerComponents; ++i) {
|
||||
const wrapped = {value: cursor};
|
||||
keys[i] = Schema.deserialize(view, wrapped, {type: 'string'});
|
||||
cursor = wrapped.value;
|
||||
}
|
||||
const count = view.getUint32(cursor, true);
|
||||
const keys = Object.keys(ecs.Types);
|
||||
cursor += 4;
|
||||
const creating = new Map();
|
||||
const updating = new Map();
|
||||
|
@ -142,8 +150,12 @@ export default class Ecs {
|
|||
const componentId = view.getUint16(cursor, true);
|
||||
cursor += 2;
|
||||
const component = keys[componentId];
|
||||
if (!component) {
|
||||
throw new Error(`can't decode component ${componentId}`);
|
||||
const componentSize = view.getUint32(cursor, true);
|
||||
cursor += 4;
|
||||
if (!this.Types[component]) {
|
||||
console.error(`can't deserialize nonexistent component ${component}`);
|
||||
cursor += componentSize;
|
||||
continue;
|
||||
}
|
||||
if (!ecs.$$entities[entityId]) {
|
||||
creating.get(entityId)[component] = false;
|
||||
|
@ -156,7 +168,7 @@ export default class Ecs {
|
|||
updating.get(component).push([entityId, false]);
|
||||
}
|
||||
cursors.get(entityId)[component] = cursor;
|
||||
cursor += ecs.Types[component].constructor.schema.readSize(view, cursor);
|
||||
cursor += componentSize;
|
||||
}
|
||||
if (addedComponents.length > 0 && ecs.$$entities[entityId]) {
|
||||
ecs.rebuild(entityId, (types) => types.concat(addedComponents));
|
||||
|
@ -333,9 +345,15 @@ export default class Ecs {
|
|||
view = new DataView(new ArrayBuffer(ecs.size()));
|
||||
}
|
||||
let cursor = 0;
|
||||
const keys = Object.keys(ecs.Types);
|
||||
view.setUint16(cursor, keys.length, true);
|
||||
cursor += 2;
|
||||
for (const i in keys) {
|
||||
cursor += Schema.serialize(keys[i], view, cursor, {type: 'string'});
|
||||
}
|
||||
const entitiesWrittenAt = cursor;
|
||||
let entitiesWritten = 0;
|
||||
cursor += 4;
|
||||
const keys = Object.keys(ecs.Types);
|
||||
for (const entityId of ecs.entities) {
|
||||
const entity = ecs.get(entityId);
|
||||
entitiesWritten += 1;
|
||||
|
@ -343,18 +361,21 @@ export default class Ecs {
|
|||
cursor += 4;
|
||||
const entityComponents = entity.constructor.types;
|
||||
view.setUint16(cursor, entityComponents.length, true);
|
||||
const componentsWrittenIndex = cursor;
|
||||
const componentsWrittenAt = cursor;
|
||||
cursor += 2;
|
||||
for (const component of entityComponents) {
|
||||
const instance = ecs.Types[component];
|
||||
view.setUint16(cursor, keys.indexOf(component), true);
|
||||
cursor += 2;
|
||||
const sizeOf = instance.sizeOf(entityId);
|
||||
view.setUint32(cursor, sizeOf, true);
|
||||
cursor += 4;
|
||||
instance.serialize(entityId, view, cursor);
|
||||
cursor += instance.sizeOf(entityId);
|
||||
cursor += sizeOf;
|
||||
}
|
||||
view.setUint16(componentsWrittenIndex, entityComponents.length, true);
|
||||
view.setUint16(componentsWrittenAt, entityComponents.length, true);
|
||||
}
|
||||
view.setUint32(0, entitiesWritten, true);
|
||||
view.setUint32(entitiesWrittenAt, entitiesWritten, true);
|
||||
return view;
|
||||
}
|
||||
|
||||
|
@ -363,8 +384,14 @@ export default class Ecs {
|
|||
}
|
||||
|
||||
size() {
|
||||
let size = 0;
|
||||
// # of components.
|
||||
size += 2;
|
||||
for (const type in this.Types) {
|
||||
size += Schema.sizeOf(type, {type: 'string'});
|
||||
}
|
||||
// # of entities.
|
||||
let size = 4;
|
||||
size += 4;
|
||||
for (const entityId of this.entities) {
|
||||
size += this.get(entityId).size();
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import {expect, test} from 'vitest';
|
||||
import {expect, test, vi} from 'vitest';
|
||||
|
||||
import Ecs from './ecs.js';
|
||||
import System from './system.js';
|
||||
|
@ -403,31 +403,33 @@ test('calculates entity size', () => {
|
|||
const ecs = new SizingEcs();
|
||||
ecs.createSpecific(1, {Empty: {}, Position: {}});
|
||||
// ID + # of components + Empty + Position + x + y + z
|
||||
// 4 + 2 + 2 + 2 + 4 + 4 + 4 = 22
|
||||
// 4 + 2 + 2 + 4 + 2 + 4 + 4 + 4 + 4 = 30
|
||||
expect(ecs.get(1).size())
|
||||
.to.equal(22);
|
||||
.to.equal(30);
|
||||
});
|
||||
|
||||
test('serializes and deserializes', () => {
|
||||
class SerializingEcs extends Ecs {
|
||||
static Types = {Empty, Name, Position};
|
||||
}
|
||||
// # of components + strings (Empty, Name, Position)
|
||||
// 2 + 4 + 5 + 4 + 4 + 4 + 8 = 31
|
||||
const ecs = new SerializingEcs();
|
||||
// ID + # of components + Empty + Position + x + y + z
|
||||
// 4 + 2 + 2 + 2 + 4 + 4 + 4 = 22
|
||||
// 4 + 2 + 2 + 4 + 2 + 4 + 4 + 4 + 4 = 30
|
||||
ecs.createSpecific(1, {Empty: {}, Position: {x: 64}});
|
||||
// ID + # of components + Name + 'foobar' + Position + x + y + z
|
||||
// 4 + 2 + 2 + 4 + 6 + 2 + 4 + 4 + 4 = 32
|
||||
// ID + # of components + Name + 'foobar' + Position + x + y + z
|
||||
// 4 + 2 + 2 + 4 + 4 + 6 + 2 + 4 + 4 + 4 + 4 = 40
|
||||
ecs.createSpecific(16, {Name: {name: 'foobar'}, Position: {x: 128}});
|
||||
const view = SerializingEcs.serialize(ecs);
|
||||
// # of entities + Entity(1) + Entity(16)
|
||||
// 4 + 22 + 32 = 58
|
||||
// # of entities + Header + Entity(1) + Entity(16)
|
||||
// 4 + 31 + 30 + 40 = 105
|
||||
expect(view.byteLength)
|
||||
.to.equal(58);
|
||||
.to.equal(105);
|
||||
// Entity values.
|
||||
expect(view.getUint32(4 + 22 - 12, true))
|
||||
expect(view.getUint32(4 + 31 + 30 - 12, true))
|
||||
.to.equal(64);
|
||||
expect(view.getUint32(4 + 22 + 32 - 12, true))
|
||||
expect(view.getUint32(4 + 31 + 30 + 40 - 12, true))
|
||||
.to.equal(128);
|
||||
const deserialized = SerializingEcs.deserialize(view);
|
||||
// # of entities.
|
||||
|
@ -449,11 +451,33 @@ test('serializes and deserializes', () => {
|
|||
.to.equal('foobar');
|
||||
});
|
||||
|
||||
test('deserializes from compatible ECS', () => {
|
||||
class DeserializingEcs extends Ecs {
|
||||
static Types = {Empty, Name};
|
||||
}
|
||||
class SerializingEcs extends Ecs {
|
||||
static Types = {Empty, Name, Position};
|
||||
}
|
||||
const ecs = new SerializingEcs();
|
||||
ecs.createSpecific(1, {Empty: {}, Position: {x: 64}});
|
||||
ecs.createSpecific(16, {Name: {name: 'foobar'}, Position: {x: 128}});
|
||||
const view = SerializingEcs.serialize(ecs);
|
||||
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
||||
const deserialized = DeserializingEcs.deserialize(view);
|
||||
expect(consoleErrorSpy.mock.calls.length)
|
||||
.to.equal(2);
|
||||
consoleErrorSpy.mockRestore();
|
||||
expect(deserialized.get(1).toJSON())
|
||||
.to.deep.equal({Empty: {}});
|
||||
expect(deserialized.get(16).toJSON())
|
||||
.to.deep.equal({Name: {name: 'foobar'}});
|
||||
});
|
||||
|
||||
test('deserializes empty', () => {
|
||||
class SerializingEcs extends Ecs {
|
||||
static Types = {Empty, Name, Position};
|
||||
}
|
||||
const ecs = SerializingEcs.deserialize(new DataView(new Uint32Array([0]).buffer));
|
||||
const ecs = SerializingEcs.deserialize(new DataView(new Uint16Array([0, 0, 0]).buffer));
|
||||
expect(ecs)
|
||||
.to.not.be.undefined;
|
||||
});
|
||||
|
|
|
@ -28,7 +28,7 @@ export default class EntityFactory {
|
|||
let size = 0;
|
||||
for (const component of this.constructor.types) {
|
||||
const instance = Types[component];
|
||||
size += 2 + instance.constructor.schema.sizeOf(instance.get(this.id));
|
||||
size += 2 + 4 + instance.constructor.schema.sizeOf(instance.get(this.id));
|
||||
}
|
||||
// ID + # of components.
|
||||
return size + 4 + 2;
|
||||
|
|
|
@ -145,41 +145,6 @@ export default class Schema {
|
|||
return {...normalized, defaultValue: this.defaultValue(normalized)};
|
||||
}
|
||||
|
||||
static readSize(view, offset, specification) {
|
||||
const size = this.size(specification);
|
||||
if (size > 0) {
|
||||
return size;
|
||||
}
|
||||
switch (specification.type) {
|
||||
case 'array': {
|
||||
const length = view.getUint32(offset.value, true);
|
||||
offset.value += 4;
|
||||
let arraySize = 0;
|
||||
for (let i = 0; i < length; ++i) {
|
||||
arraySize += this.readSize(view, offset, specification.subtype);
|
||||
}
|
||||
return 4 + arraySize;
|
||||
}
|
||||
case 'object': {
|
||||
let objectSize = 0;
|
||||
for (const key in specification.properties) {
|
||||
objectSize += this.readSize(view, offset, specification.properties[key]);
|
||||
}
|
||||
return objectSize;
|
||||
}
|
||||
case 'string': {
|
||||
const length = view.getUint32(offset.value, true);
|
||||
offset.value += 4 + length;
|
||||
return 4 + length;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
readSize(view, offset) {
|
||||
const wrapped = {value: offset};
|
||||
return this.constructor.readSize(view, wrapped, this.specification);
|
||||
}
|
||||
|
||||
static serialize(source, view, offset, specification) {
|
||||
const viewSetMethod = this.viewSetMethods[specification.type];
|
||||
if (viewSetMethod) {
|
||||
|
|
Loading…
Reference in New Issue
Block a user