refactor!: schema
This commit is contained in:
parent
815d53d1e8
commit
405c2b3c6f
3
app/ecs-components/animation.js
Normal file
3
app/ecs-components/animation.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
export default {
|
||||
frame: {type: 'uint16'},
|
||||
};
|
|
@ -1,4 +1,4 @@
|
|||
export default {
|
||||
x: 'uint16',
|
||||
y: 'uint16',
|
||||
x: {type: 'uint16'},
|
||||
y: {type: 'uint16'},
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
export default {
|
||||
x: 'uint16',
|
||||
y: 'uint16',
|
||||
x: {type: 'uint16'},
|
||||
y: {type: 'uint16'},
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
export default {
|
||||
up: 'float32',
|
||||
right: 'float32',
|
||||
down: 'float32',
|
||||
left: 'float32',
|
||||
up: {type: 'float32'},
|
||||
right: {type: 'float32'},
|
||||
down: {type: 'float32'},
|
||||
left: {type: 'float32'},
|
||||
};
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
export default {
|
||||
x: 'float32',
|
||||
y: 'float32',
|
||||
x: {type: 'float32'},
|
||||
y: {type: 'float32'},
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
export default {
|
||||
x: 'float32',
|
||||
y: 'float32',
|
||||
x: {type: 'float32'},
|
||||
y: {type: 'float32'},
|
||||
};
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
export default {
|
||||
image: 'string',
|
||||
image: {type: 'string'},
|
||||
};
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
export default {
|
||||
x0: 'float32',
|
||||
x1: 'float32',
|
||||
y0: 'float32',
|
||||
y1: 'float32',
|
||||
x0: {type: 'float32'},
|
||||
x1: {type: 'float32'},
|
||||
y0: {type: 'float32'},
|
||||
y1: {type: 'float32'},
|
||||
}
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
export default {
|
||||
world: 'uint16',
|
||||
world: {type: 'uint16'},
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ export default class Arbitrary extends Base {
|
|||
createMany(entries) {
|
||||
if (entries.length > 0) {
|
||||
const allocated = this.allocateMany(entries.length);
|
||||
const keys = Object.keys(this.constructor.schema.specification);
|
||||
const keys = Object.keys(this.constructor.properties);
|
||||
for (let i = 0; i < entries.length; ++i) {
|
||||
const [entityId, values = {}] = entries[i];
|
||||
this.map[entityId] = allocated[i];
|
||||
|
@ -32,7 +32,7 @@ export default class Arbitrary extends Base {
|
|||
}
|
||||
for (let k = 0; k < keys.length; ++k) {
|
||||
const j = keys[k];
|
||||
const {defaultValue} = this.constructor.schema.specification[j];
|
||||
const {defaultValue} = this.constructor.properties[j];
|
||||
if (j in values) {
|
||||
this.data[allocated[i]][j] = values[j];
|
||||
}
|
||||
|
@ -45,7 +45,12 @@ export default class Arbitrary extends Base {
|
|||
}
|
||||
|
||||
deserialize(entityId, view, offset) {
|
||||
this.constructor.schema.deserialize(view, this.get(entityId), offset);
|
||||
const {properties} = this.constructor;
|
||||
const instance = this.get(entityId);
|
||||
const deserialized = this.constructor.schema.deserialize(view, offset);
|
||||
for (const key in properties) {
|
||||
instance[key] = deserialized[key];
|
||||
}
|
||||
}
|
||||
|
||||
serialize(entityId, view, offset) {
|
||||
|
@ -64,8 +69,10 @@ export default class Arbitrary extends Base {
|
|||
this.$$reset();
|
||||
}
|
||||
$$reset() {
|
||||
for (const [i, {defaultValue}] of Component.constructor.schema) {
|
||||
this[`$$${i}`] = defaultValue;
|
||||
const {properties} = Component.constructor;
|
||||
for (const key in properties) {
|
||||
const {defaultValue} = properties[key];
|
||||
this[`$$${key}`] = defaultValue;
|
||||
}
|
||||
}
|
||||
toJSON() {
|
||||
|
@ -82,15 +89,15 @@ export default class Arbitrary extends Base {
|
|||
this.$$reset();
|
||||
},
|
||||
};
|
||||
for (const [i] of Component.constructor.schema) {
|
||||
properties[i] = {
|
||||
for (const key in Component.constructor.properties) {
|
||||
properties[key] = {
|
||||
get: function get() {
|
||||
return this[`$$${i}`];
|
||||
return this[`$$${key}`];
|
||||
},
|
||||
set: function set(v) {
|
||||
if (this[`$$${i}`] !== v) {
|
||||
this[`$$${i}`] = v;
|
||||
Component.markChange(this.entity, i, v);
|
||||
set: function set(value) {
|
||||
if (this[`$$${key}`] !== value) {
|
||||
this[`$$${key}`] = value;
|
||||
Component.markChange(this.entity, key, value);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
|
|
@ -5,17 +5,23 @@ import Arbitrary from './arbitrary.js';
|
|||
|
||||
test('creates instances', () => {
|
||||
class CreatingArbitrary extends Arbitrary {
|
||||
static schema = new Schema({foo: {defaultValue: 'bar', type: 'string'}});
|
||||
static schema = new Schema({
|
||||
type: 'object',
|
||||
properties: {foo: {defaultValue: 'bar', type: 'string'}},
|
||||
});
|
||||
}
|
||||
const Component = new CreatingArbitrary();
|
||||
Component.create(1)
|
||||
Component.create(1);
|
||||
expect(Component.get(1).entity)
|
||||
.to.equal(1);
|
||||
});
|
||||
|
||||
test('does not serialize default values', () => {
|
||||
class CreatingArbitrary extends Arbitrary {
|
||||
static schema = new Schema({foo: {defaultValue: 'bar', type: 'string'}, bar: 'uint8'});
|
||||
static schema = new Schema({
|
||||
type: 'object',
|
||||
properties: {foo: {defaultValue: 'bar', type: 'string'}, bar: {type: 'uint8'}},
|
||||
});
|
||||
}
|
||||
const Component = new CreatingArbitrary();
|
||||
Component.create(1)
|
||||
|
@ -28,7 +34,10 @@ test('does not serialize default values', () => {
|
|||
|
||||
test('reuses instances', () => {
|
||||
class ReusingArbitrary extends Arbitrary {
|
||||
static schema = new Schema({foo: {type: 'string'}});
|
||||
static schema = new Schema({
|
||||
type: 'object',
|
||||
properties: {foo: {type: 'string'}},
|
||||
});
|
||||
}
|
||||
const Component = new ReusingArbitrary();
|
||||
Component.create(1);
|
||||
|
|
|
@ -39,9 +39,10 @@ export default class Base {
|
|||
|
||||
static filterDefaults(instance) {
|
||||
const json = {};
|
||||
for (const [i, {defaultValue}] of this.schema) {
|
||||
if (i in instance && instance[i] !== defaultValue) {
|
||||
json[i] = instance[i];
|
||||
for (const key in this.properties) {
|
||||
const {defaultValue} = this.properties[key];
|
||||
if (key in instance && instance[key] !== defaultValue) {
|
||||
json[key] = instance[key];
|
||||
}
|
||||
}
|
||||
return json;
|
||||
|
@ -77,6 +78,10 @@ export default class Base {
|
|||
return {...original, ...update};
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return this.schema.specification.properties;
|
||||
}
|
||||
|
||||
sizeOf(entityId) {
|
||||
return this.constructor.schema.sizeOf(this.get(entityId));
|
||||
}
|
||||
|
|
|
@ -9,6 +9,6 @@ export default function Component(specificationOrClass) {
|
|||
// Why the rigamarole? Maybe we'll implement a flat component for direct binary storage
|
||||
// eventually.
|
||||
return class AdhocComponent extends Arbitrary {
|
||||
static schema = new Schema(specificationOrClass);
|
||||
static schema = new Schema({type: 'object', properties: specificationOrClass});
|
||||
};
|
||||
}
|
||||
|
|
|
@ -6,13 +6,13 @@ import System from './system.js';
|
|||
const Empty = {};
|
||||
|
||||
const Name = {
|
||||
name: 'string',
|
||||
name: {type: 'string'},
|
||||
};
|
||||
|
||||
const Position = {
|
||||
x: {type: 'int32', defaultValue: 32},
|
||||
y: 'int32',
|
||||
z: 'int32',
|
||||
y: {type: 'int32'},
|
||||
z: {type: 'int32'},
|
||||
};
|
||||
|
||||
test('adds and remove systems at runtime', () => {
|
||||
|
@ -109,9 +109,9 @@ test('inserts components into entities', () => {
|
|||
|
||||
test('ticks systems', () => {
|
||||
const Momentum = {
|
||||
x: 'int32',
|
||||
y: 'int32',
|
||||
z: 'int32',
|
||||
x: {type: 'int32'},
|
||||
y: {type: 'int32'},
|
||||
z: {type: 'int32'},
|
||||
};
|
||||
class TickEcs extends Ecs {
|
||||
static Types = {Momentum, Position};
|
||||
|
@ -203,7 +203,7 @@ test('schedules entities to be deleted when ticking systems', () => {
|
|||
|
||||
test('adds components to and remove components from entities when ticking systems', () => {
|
||||
class TickingEcs extends Ecs {
|
||||
static Types = {Foo: {bar: 'uint8'}};
|
||||
static Types = {Foo: {bar: {type: 'uint8'}}};
|
||||
}
|
||||
const ecs = new TickingEcs();
|
||||
let addLength, removeLength;
|
||||
|
|
|
@ -5,7 +5,7 @@ import Query from './query.js';
|
|||
|
||||
const A = new (Component({a: {type: 'int32', defaultValue: 420}}));
|
||||
const B = new (Component({b: {type: 'int32', defaultValue: 69}}));
|
||||
const C = new (Component({c: 'int32'}));
|
||||
const C = new (Component({c: {type: 'int32'}}));
|
||||
|
||||
const Types = {A, B, C};
|
||||
Types.A.createMany([[2], [3]]);
|
||||
|
|
|
@ -34,51 +34,54 @@ export default class Schema {
|
|||
|
||||
constructor(specification) {
|
||||
this.specification = this.constructor.normalize(specification);
|
||||
// Try to calculate static size.
|
||||
for (const i in this.specification) {
|
||||
const {type} = this.specification[i];
|
||||
const size = this.constructor.sizeOfType(type);
|
||||
if (0 === size) {
|
||||
this.$$size = 0;
|
||||
break;
|
||||
}
|
||||
this.$$size += size;
|
||||
}
|
||||
}
|
||||
|
||||
[Symbol.iterator]() {
|
||||
return Object.entries(this.specification).values();
|
||||
}
|
||||
|
||||
deserialize(destination, view, offset = 0) {
|
||||
let cursor = offset;
|
||||
for (const key in this.specification) {
|
||||
const {type} = this.specification[key];
|
||||
const viewGetMethod = Schema.viewGetMethods[type];
|
||||
let value;
|
||||
static deserialize(view, offset, specification) {
|
||||
const viewGetMethod = this.viewGetMethods[specification.type];
|
||||
if (viewGetMethod) {
|
||||
value = view[viewGetMethod](cursor, true);
|
||||
cursor += Schema.sizeOfType(type);
|
||||
const value = view[viewGetMethod](offset.value, true);
|
||||
offset.value += this.size(specification);
|
||||
return value;
|
||||
}
|
||||
switch (specification.type) {
|
||||
case 'array': {
|
||||
const length = view.getUint32(offset.value, true);
|
||||
offset.value += 4;
|
||||
const value = [];
|
||||
for (let i = 0; i < length; ++i) {
|
||||
value.push(this.deserialize(view, offset, specification.subtype));
|
||||
}
|
||||
return value;
|
||||
}
|
||||
case 'object': {
|
||||
const value = {};
|
||||
for (const key in specification.properties) {
|
||||
value[key] = this.deserialize(view, offset, specification.properties[key]);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
else {
|
||||
switch (type) {
|
||||
case 'string': {
|
||||
const length = view.getUint32(cursor, true);
|
||||
cursor += 4;
|
||||
const length = view.getUint32(offset.value, true);
|
||||
offset.value += 4;
|
||||
const {buffer, byteOffset} = view;
|
||||
const decoder = new TextDecoder();
|
||||
value = decoder.decode(new DataView(buffer, byteOffset + cursor, length));
|
||||
cursor += length;
|
||||
break;
|
||||
const value = decoder.decode(new DataView(buffer, byteOffset + offset.value, length));
|
||||
offset.value += length;
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
destination[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
static defaultValueForType(type) {
|
||||
switch (type) {
|
||||
deserialize(view, offset = 0) {
|
||||
const wrapped = {value: offset};
|
||||
return this.constructor.deserialize(view, wrapped, this.specification);
|
||||
}
|
||||
|
||||
static defaultValue(specification) {
|
||||
if (specification.defaultValue) {
|
||||
return specification.defaultValue;
|
||||
}
|
||||
switch (specification.type) {
|
||||
case 'uint8': case 'int8':
|
||||
case 'uint16': case 'int16':
|
||||
case 'uint32': case 'int32':
|
||||
|
@ -86,107 +89,194 @@ export default class Schema {
|
|||
case 'float32': case 'float64': {
|
||||
return 0;
|
||||
}
|
||||
case 'array': {
|
||||
return [];
|
||||
}
|
||||
case 'object': {
|
||||
const object = {};
|
||||
for (const key in specification.properties) {
|
||||
object[key] = this.defaultValue(specification.properties[key]);
|
||||
}
|
||||
return object;
|
||||
}
|
||||
case 'string': {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
defaultValue() {
|
||||
return this.constructor.defaultValue(this.specification);
|
||||
}
|
||||
|
||||
has(key) {
|
||||
return key in this.specification;
|
||||
}
|
||||
|
||||
static normalize(specification) {
|
||||
const normalized = Object.create(null);
|
||||
for (const i in specification) {
|
||||
normalized[i] = 'string' === typeof specification[i]
|
||||
? {type: specification[i]}
|
||||
: specification[i];
|
||||
if (!this.validateType(normalized[i].type)) {
|
||||
throw new TypeError(`unknown schema type: ${normalized[i].type}`);
|
||||
}
|
||||
normalized[i].defaultValue = normalized[i].defaultValue || this.defaultValueForType(normalized[i].type);
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
readSize(view, cursor) {
|
||||
let fullSize = 0;
|
||||
for (const i in this.specification) {
|
||||
const {type} = this.specification[i];
|
||||
const size = this.constructor.sizeOfType(type);
|
||||
if (0 === size) {
|
||||
switch (type) {
|
||||
case 'string': {
|
||||
const length = view.getUint32(cursor, true);
|
||||
cursor += 4 + length;
|
||||
fullSize += 4;
|
||||
fullSize += length;
|
||||
let normalized = specification;
|
||||
switch (specification.type) {
|
||||
case 'array': {
|
||||
normalized.subtype = this.normalize(specification.subtype);
|
||||
break;
|
||||
}
|
||||
case 'object': {
|
||||
for (const key in specification.properties) {
|
||||
normalized.properties[key] = this.normalize(specification.properties[key])
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'uint8':
|
||||
case 'int8':
|
||||
case 'uint16':
|
||||
case 'int16':
|
||||
case 'uint32':
|
||||
case 'int32':
|
||||
case 'uint64':
|
||||
case 'int64':
|
||||
case 'float32':
|
||||
case 'float64':
|
||||
case 'string': {
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new TypeError(`invalid specification: ${JSON.stringify(specification)}`);
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
else {
|
||||
cursor += size;
|
||||
fullSize += size;
|
||||
}
|
||||
|
||||
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) {
|
||||
view[viewSetMethod](offset, source, true);
|
||||
return this.size(specification);
|
||||
}
|
||||
switch (specification.type) {
|
||||
case 'array': {
|
||||
view.setUint32(offset, source.length, true);
|
||||
offset += 4;
|
||||
let arraySize = 0;
|
||||
for (const element of source) {
|
||||
arraySize += this.serialize(
|
||||
element,
|
||||
view,
|
||||
offset + arraySize,
|
||||
specification.subtype,
|
||||
);
|
||||
}
|
||||
return 4 + arraySize;
|
||||
}
|
||||
case 'object': {
|
||||
let objectSize = 0;
|
||||
for (const key in specification.properties) {
|
||||
objectSize += this.serialize(
|
||||
source[key],
|
||||
view,
|
||||
offset + objectSize,
|
||||
specification.properties[key],
|
||||
);
|
||||
}
|
||||
return objectSize;
|
||||
}
|
||||
case 'string': {
|
||||
const encoder = new TextEncoder();
|
||||
const bytes = encoder.encode(source);
|
||||
view.setUint32(offset, bytes.length, true);
|
||||
offset += 4;
|
||||
for (let i = 0; i < bytes.length; ++i) {
|
||||
view.setUint8(offset++, bytes[i]);
|
||||
}
|
||||
return 4 + bytes.length;
|
||||
}
|
||||
}
|
||||
return fullSize;
|
||||
}
|
||||
|
||||
serialize(source, view, offset = 0) {
|
||||
let cursor = offset;
|
||||
for (const key in this.specification) {
|
||||
const {type} = this.specification[key];
|
||||
const viewSetMethod = Schema.viewSetMethods[type];
|
||||
if (viewSetMethod) {
|
||||
view[viewSetMethod](cursor, source[key], true);
|
||||
cursor += Schema.sizeOfType(type);
|
||||
this.constructor.serialize(source, view, offset, this.specification);
|
||||
}
|
||||
else {
|
||||
|
||||
static sizeOf(concrete, specification) {
|
||||
const size = this.size(specification);
|
||||
if (size > 0) {
|
||||
return size;
|
||||
}
|
||||
let fullSize = 0;
|
||||
const {type} = specification;
|
||||
switch (type) {
|
||||
case 'string': {
|
||||
const lengthOffset = cursor;
|
||||
cursor += 4;
|
||||
const encoder = new TextEncoder();
|
||||
const bytes = encoder.encode(source[key]);
|
||||
for (let i = 0; i < bytes.length; ++i) {
|
||||
view.setUint8(cursor++, bytes[i]);
|
||||
case 'array': {
|
||||
fullSize += 4;
|
||||
for (const element of concrete) {
|
||||
fullSize += this.sizeOf(element, specification.subtype);
|
||||
}
|
||||
view.setUint32(lengthOffset, bytes.length, true);
|
||||
break;
|
||||
}
|
||||
case 'object': {
|
||||
for (const key in specification.properties) {
|
||||
fullSize += this.sizeOf(concrete[key], specification.properties[key]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get size() {
|
||||
return this.$$size;
|
||||
}
|
||||
|
||||
sizeOf(concrete) {
|
||||
let fullSize = 0;
|
||||
for (const i in this.specification) {
|
||||
const {type} = this.specification[i];
|
||||
const size = this.constructor.sizeOfType(type);
|
||||
if (0 === size) {
|
||||
switch (type) {
|
||||
case 'string':
|
||||
fullSize += 4;
|
||||
fullSize += (encoder.encode(concrete[i])).length;
|
||||
fullSize += (encoder.encode(concrete)).length;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
fullSize += size;
|
||||
}
|
||||
}
|
||||
return fullSize;
|
||||
}
|
||||
|
||||
static sizeOfType(type) {
|
||||
switch (type) {
|
||||
sizeOf(concrete) {
|
||||
return this.constructor.sizeOf(concrete, this.specification);
|
||||
}
|
||||
|
||||
static size(specification) {
|
||||
switch (specification.type) {
|
||||
case 'array': return 0;
|
||||
case 'object': {
|
||||
let size = 0;
|
||||
for (const key in specification.properties) {
|
||||
const propertySize = this.size(specification.properties[key]);
|
||||
if (0 === propertySize) {
|
||||
return 0;
|
||||
}
|
||||
size += propertySize;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
case 'uint8': case 'int8': {
|
||||
return 1;
|
||||
}
|
||||
|
@ -203,16 +293,8 @@ export default class Schema {
|
|||
}
|
||||
}
|
||||
|
||||
static validateType(type) {
|
||||
return [
|
||||
'uint8', 'int8',
|
||||
'uint16', 'int16',
|
||||
'uint32', 'int32',
|
||||
'uint64', 'int64',
|
||||
'float32', 'float64',
|
||||
'string',
|
||||
]
|
||||
.includes(type);
|
||||
size() {
|
||||
return this.constructor.size(this.specification);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -2,57 +2,135 @@ import {expect, test} from 'vitest';
|
|||
|
||||
import Schema from './schema.js';
|
||||
|
||||
test('validates a schema', () => {
|
||||
expect(() => {
|
||||
new Schema({test: 'unknown'})
|
||||
})
|
||||
.to.throw();
|
||||
expect(() => {
|
||||
new Schema({test: 'unknown'})
|
||||
})
|
||||
.to.throw();
|
||||
|
||||
test('defaults values', () => {
|
||||
[
|
||||
'uint8',
|
||||
'int8',
|
||||
'uint16',
|
||||
'int16',
|
||||
'uint32',
|
||||
'int32',
|
||||
'uint64',
|
||||
'int64',
|
||||
'float32',
|
||||
'float64',
|
||||
].forEach((type) => {
|
||||
expect(Schema.defaultValue({type}))
|
||||
.to.equal(0);
|
||||
expect(new Schema({type}).specification.defaultValue)
|
||||
.to.equal(0);
|
||||
});
|
||||
expect(Schema.defaultValue({type: 'string'}))
|
||||
.to.equal('');
|
||||
expect(new Schema({type: 'string'}).specification.defaultValue)
|
||||
.to.equal('');
|
||||
expect(Schema.defaultValue({type: 'array', subtype: {type: 'string'}}))
|
||||
.to.deep.equal([]);
|
||||
expect(new Schema({type: 'array', subtype: {type: 'string'}}).specification.defaultValue)
|
||||
.to.deep.equal([]);
|
||||
expect(Schema.defaultValue({
|
||||
type: 'object',
|
||||
properties: {
|
||||
foo: {type: 'uint8'},
|
||||
bar: {type: 'string'},
|
||||
baz: {type: 'object', properties: {blah: {type: 'array', subtype: {type: 'string'}}}},
|
||||
},
|
||||
}))
|
||||
.to.deep.equal({foo: 0, bar: '', baz: {blah: []}});
|
||||
expect(new Schema({
|
||||
type: 'object',
|
||||
properties: {
|
||||
foo: {type: 'uint8'},
|
||||
bar: {type: 'string'},
|
||||
baz: {type: 'object', properties: {blah: {type: 'array', subtype: {type: 'string'}}}},
|
||||
},
|
||||
}).specification.defaultValue)
|
||||
.to.deep.equal({foo: 0, bar: '', baz: {blah: []}});
|
||||
});
|
||||
|
||||
test('calculates the size of an instance', () => {
|
||||
test('validates a schema', () => {
|
||||
[
|
||||
'uint8',
|
||||
'int8',
|
||||
'uint16',
|
||||
'int16',
|
||||
'uint32',
|
||||
'int32',
|
||||
'uint64',
|
||||
'int64',
|
||||
'float32',
|
||||
'float64',
|
||||
'string',
|
||||
].forEach((type) => {
|
||||
expect(() => {
|
||||
new Schema({type});
|
||||
new Schema({type: 'array', subtype: {type}});
|
||||
new Schema({type: 'object', properties: {foo: {type}}});
|
||||
})
|
||||
.to.not.throw();
|
||||
});
|
||||
});
|
||||
|
||||
test('calculates the size of concrete instances', () => {
|
||||
expect(
|
||||
(new Schema({foo: 'uint8', bar: 'uint32'}))
|
||||
.sizeOf({foo: 69, bar: 420})
|
||||
)
|
||||
.to.equal(5);
|
||||
expect(
|
||||
(new Schema({foo: 'string'}))
|
||||
.sizeOf({foo: 'hi'})
|
||||
(new Schema({type: 'string'}))
|
||||
.sizeOf('hi')
|
||||
)
|
||||
.to.equal(4 + (new TextEncoder().encode('hi')).length);
|
||||
|
||||
expect(
|
||||
(new Schema(
|
||||
{type: 'object', properties: {
|
||||
foo: {type: 'uint8'},
|
||||
bar: {type: 'uint32'},
|
||||
baz: {type: 'string'},
|
||||
}}
|
||||
))
|
||||
.sizeOf({foo: 69, bar: 420, baz: 'aα'})
|
||||
)
|
||||
.to.equal(
|
||||
1
|
||||
+ 4
|
||||
+ 4 + (new TextEncoder().encode('aα')).length
|
||||
);
|
||||
expect(
|
||||
(new Schema({type: 'array', subtype: {type: 'string'}}))
|
||||
.sizeOf(['hallo', 'hαllo'])
|
||||
)
|
||||
.to.equal(
|
||||
4
|
||||
+ 4 + (new TextEncoder().encode('hallo')).length
|
||||
+ 4 + (new TextEncoder().encode('hαllo')).length
|
||||
);
|
||||
});
|
||||
|
||||
test('can encode and decode', () => {
|
||||
test('encodes and decodes', () => {
|
||||
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'],
|
||||
['string', 'α'],
|
||||
[{type: 'uint8'}, 255],
|
||||
[{type: 'int8'}, -128],
|
||||
[{type: 'int8'}, 127],
|
||||
[{type: 'uint16'}, 65535],
|
||||
[{type: 'int16'}, -32768],
|
||||
[{type: 'int16'}, 32767],
|
||||
[{type: 'uint32'}, 4294967295],
|
||||
[{type: 'int32'}, -2147483648],
|
||||
[{type: 'int32'}, 2147483647],
|
||||
[{type: 'uint64'}, 18446744073709551615n],
|
||||
[{type: 'int64'}, -9223372036854775808n],
|
||||
[{type: 'int64'}, 9223372036854775807n],
|
||||
[{type: 'float32'}, 0.5],
|
||||
[{type: 'float64'}, 1.234],
|
||||
[{type: 'string'}, 'hello world'],
|
||||
[{type: 'string'}, 'α'],
|
||||
[{type: 'array', subtype: {type: 'uint8'}}, [1, 2, 3, 4]],
|
||||
[{type: 'array', subtype: {type: 'string'}}, ['one', 'two', 'three', 'four']],
|
||||
[{type: 'object', properties: {foo: {type: 'uint8'}, bar: {type: 'string'}}}, {foo: 64, bar: 'baz'}],
|
||||
];
|
||||
const specification = entries.reduce((r, [type]) => ({...r, [Object.keys(r).length]: type}), {});
|
||||
const data = entries.reduce((r, [, value]) => ({...r, [Object.keys(r).length]: value}), {});
|
||||
entries.forEach(([specification, concrete]) => {
|
||||
const schema = new Schema(specification);
|
||||
const view = new DataView(new ArrayBuffer(schema.sizeOf(data)));
|
||||
schema.serialize(data, view);
|
||||
const result = {};
|
||||
schema.deserialize(result, view);
|
||||
expect(data)
|
||||
.to.deep.equal(result);
|
||||
const view = new DataView(new ArrayBuffer(schema.sizeOf(concrete)));
|
||||
schema.serialize(concrete, view);
|
||||
expect(concrete)
|
||||
.to.deep.equal(schema.deserialize(view));
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue
Block a user