refactor: schema
This commit is contained in:
parent
263cf37e27
commit
88f0ec4715
|
@ -38,7 +38,8 @@ export default class Component {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
const allocated = this.allocateMany(entries.length);
|
const allocated = this.allocateMany(entries.length);
|
||||||
const {properties} = this.constructor.schema.specification;
|
const {properties} = this.constructor.schema.specification.concrete;
|
||||||
|
const Schema = this.constructor.schema.constructor;
|
||||||
const keys = Object.keys(properties);
|
const keys = Object.keys(properties);
|
||||||
const promises = [];
|
const promises = [];
|
||||||
for (let i = 0; i < entries.length; ++i) {
|
for (let i = 0; i < entries.length; ++i) {
|
||||||
|
@ -47,13 +48,15 @@ export default class Component {
|
||||||
this.data[allocated[i]].entity = entityId;
|
this.data[allocated[i]].entity = entityId;
|
||||||
for (let k = 0; k < keys.length; ++k) {
|
for (let k = 0; k < keys.length; ++k) {
|
||||||
const j = keys[k];
|
const j = keys[k];
|
||||||
const {defaultValue} = properties[j];
|
|
||||||
const instance = this.data[allocated[i]];
|
const instance = this.data[allocated[i]];
|
||||||
if (j in values) {
|
if (j in values) {
|
||||||
instance[j] = values[j];
|
instance[j] = values[j];
|
||||||
}
|
}
|
||||||
else if ('undefined' !== typeof defaultValue) {
|
else {
|
||||||
instance[j] = defaultValue;
|
const defaultValue = Schema.defaultValue(properties[j]);
|
||||||
|
if ('undefined' !== typeof defaultValue) {
|
||||||
|
instance[j] = defaultValue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
promises.push(this.load(this.data[allocated[i]]));
|
promises.push(this.load(this.data[allocated[i]]));
|
||||||
|
@ -67,7 +70,7 @@ export default class Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
deserialize(entityId, view, offset) {
|
deserialize(entityId, view, offset) {
|
||||||
const {properties} = this.constructor.schema.specification;
|
const {properties} = this.constructor.schema.specification.concrete;
|
||||||
const instance = this.get(entityId);
|
const instance = this.get(entityId);
|
||||||
const deserialized = this.constructor.schema.deserialize(view, offset);
|
const deserialized = this.constructor.schema.deserialize(view, offset);
|
||||||
for (const key in properties) {
|
for (const key in properties) {
|
||||||
|
@ -96,11 +99,11 @@ export default class Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
static filterDefaults(instance) {
|
static filterDefaults(instance) {
|
||||||
const {properties} = this.schema.specification;
|
const {properties} = this.schema.specification.concrete;
|
||||||
|
const Schema = this.schema.constructor;
|
||||||
const json = {};
|
const json = {};
|
||||||
for (const key in properties) {
|
for (const key in properties) {
|
||||||
const {defaultValue} = properties[key];
|
if (key in instance && instance[key] !== Schema.defaultValue(properties[key])) {
|
||||||
if (key in instance && instance[key] !== defaultValue) {
|
|
||||||
json[key] = instance[key];
|
json[key] = instance[key];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -136,16 +139,16 @@ export default class Component {
|
||||||
|
|
||||||
instanceFromSchema() {
|
instanceFromSchema() {
|
||||||
const Component = this;
|
const Component = this;
|
||||||
const {specification} = Component.constructor.schema;
|
const {concrete} = Component.constructor.schema.specification;
|
||||||
|
const Schema = Component.constructor.schema.constructor;
|
||||||
const Instance = class {
|
const Instance = class {
|
||||||
$$entity = 0;
|
$$entity = 0;
|
||||||
constructor() {
|
constructor() {
|
||||||
this.$$reset();
|
this.$$reset();
|
||||||
}
|
}
|
||||||
$$reset() {
|
$$reset() {
|
||||||
for (const key in specification.properties) {
|
for (const key in concrete.properties) {
|
||||||
const {defaultValue} = specification.properties[key];
|
this[`$$${key}`] = Schema.defaultValue(concrete.properties[key]);
|
||||||
this[`$$${key}`] = defaultValue;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
destroy() {}
|
destroy() {}
|
||||||
|
@ -166,7 +169,7 @@ export default class Component {
|
||||||
this.$$reset();
|
this.$$reset();
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
for (const key in specification.properties) {
|
for (const key in concrete.properties) {
|
||||||
properties[key] = {
|
properties[key] = {
|
||||||
get: function get() {
|
get: function get() {
|
||||||
return this[`$$${key}`];
|
return this[`$$${key}`];
|
||||||
|
|
40
app/ecs/schema-types/array.js
Normal file
40
app/ecs/schema-types/array.js
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
export default function (Schema) {
|
||||||
|
return {
|
||||||
|
defaultValue: () => [],
|
||||||
|
deserialize: (view, offset, {subtype}) => {
|
||||||
|
const length = view.getUint32(offset.value, true);
|
||||||
|
offset.value += 4;
|
||||||
|
const value = [];
|
||||||
|
for (let i = 0; i < length; ++i) {
|
||||||
|
value.push(Schema.deserialize(view, offset, subtype));
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
},
|
||||||
|
normalize: ({subtype}) => {
|
||||||
|
return {
|
||||||
|
subtype: Schema.normalize(subtype),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
serialize: (source, view, offset, {subtype}) => {
|
||||||
|
view.setUint32(offset, source.length, true);
|
||||||
|
let size = 4;
|
||||||
|
for (const element of source) {
|
||||||
|
size += Schema.serialize(
|
||||||
|
element,
|
||||||
|
view,
|
||||||
|
offset + size,
|
||||||
|
subtype,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return size;
|
||||||
|
},
|
||||||
|
sizeOf: (instance, {subtype}) => {
|
||||||
|
let size = 4;
|
||||||
|
for (const element of instance) {
|
||||||
|
size += Schema.sizeOf(element, subtype);
|
||||||
|
}
|
||||||
|
return size;
|
||||||
|
},
|
||||||
|
staticSizeOf: () => 0,
|
||||||
|
};
|
||||||
|
}
|
16
app/ecs/schema-types/float32.js
Normal file
16
app/ecs/schema-types/float32.js
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
export default function () {
|
||||||
|
return {
|
||||||
|
defaultValue: () => 0,
|
||||||
|
deserialize: (view, offset) => {
|
||||||
|
const value = view.getFloat32(offset.value, true);
|
||||||
|
offset.value += 4;
|
||||||
|
return value;
|
||||||
|
},
|
||||||
|
serialize: (source, view, offset) => {
|
||||||
|
view.setFloat32(offset, source, true);
|
||||||
|
return 4;
|
||||||
|
},
|
||||||
|
sizeOf: () => 4,
|
||||||
|
staticSizeOf: () => 4,
|
||||||
|
};
|
||||||
|
}
|
16
app/ecs/schema-types/float64.js
Normal file
16
app/ecs/schema-types/float64.js
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
export default function () {
|
||||||
|
return {
|
||||||
|
defaultValue: () => 0,
|
||||||
|
deserialize: (view, offset) => {
|
||||||
|
const value = view.getFloat64(offset.value, true);
|
||||||
|
offset.value += 8;
|
||||||
|
return value;
|
||||||
|
},
|
||||||
|
serialize: (source, view, offset) => {
|
||||||
|
view.setFloat64(offset, source, true);
|
||||||
|
return 8;
|
||||||
|
},
|
||||||
|
sizeOf: () => 8,
|
||||||
|
staticSizeOf: () => 8,
|
||||||
|
};
|
||||||
|
}
|
16
app/ecs/schema-types/int16.js
Normal file
16
app/ecs/schema-types/int16.js
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
export default function () {
|
||||||
|
return {
|
||||||
|
defaultValue: () => 0,
|
||||||
|
deserialize: (view, offset) => {
|
||||||
|
const value = view.getInt16(offset.value, true);
|
||||||
|
offset.value += 2;
|
||||||
|
return value;
|
||||||
|
},
|
||||||
|
serialize: (source, view, offset) => {
|
||||||
|
view.setInt16(offset, source, true);
|
||||||
|
return 2;
|
||||||
|
},
|
||||||
|
sizeOf: () => 2,
|
||||||
|
staticSizeOf: () => 2,
|
||||||
|
};
|
||||||
|
}
|
16
app/ecs/schema-types/int32.js
Normal file
16
app/ecs/schema-types/int32.js
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
export default function () {
|
||||||
|
return {
|
||||||
|
defaultValue: () => 0,
|
||||||
|
deserialize: (view, offset) => {
|
||||||
|
const value = view.getInt32(offset.value, true);
|
||||||
|
offset.value += 4;
|
||||||
|
return value;
|
||||||
|
},
|
||||||
|
serialize: (source, view, offset) => {
|
||||||
|
view.setInt32(offset, source, true);
|
||||||
|
return 4;
|
||||||
|
},
|
||||||
|
sizeOf: () => 4,
|
||||||
|
staticSizeOf: () => 4,
|
||||||
|
};
|
||||||
|
}
|
16
app/ecs/schema-types/int64.js
Normal file
16
app/ecs/schema-types/int64.js
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
export default function () {
|
||||||
|
return {
|
||||||
|
defaultValue: () => 0n,
|
||||||
|
deserialize: (view, offset) => {
|
||||||
|
const value = view.getBigInt64(offset.value, true);
|
||||||
|
offset.value += 8;
|
||||||
|
return value;
|
||||||
|
},
|
||||||
|
serialize: (source, view, offset) => {
|
||||||
|
view.setBigInt64(offset, source, true);
|
||||||
|
return 8;
|
||||||
|
},
|
||||||
|
sizeOf: () => 8,
|
||||||
|
staticSizeOf: () => 8,
|
||||||
|
};
|
||||||
|
}
|
16
app/ecs/schema-types/int8.js
Normal file
16
app/ecs/schema-types/int8.js
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
export default function () {
|
||||||
|
return {
|
||||||
|
defaultValue: () => 0,
|
||||||
|
deserialize: (view, offset) => {
|
||||||
|
const value = view.getInt8(offset.value, true);
|
||||||
|
offset.value += 1;
|
||||||
|
return value;
|
||||||
|
},
|
||||||
|
serialize: (source, view, offset) => {
|
||||||
|
view.setInt8(offset, source, true);
|
||||||
|
return 1;
|
||||||
|
},
|
||||||
|
sizeOf: () => 1,
|
||||||
|
staticSizeOf: () => 1,
|
||||||
|
};
|
||||||
|
}
|
50
app/ecs/schema-types/map.js
Normal file
50
app/ecs/schema-types/map.js
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
export default function (Schema) {
|
||||||
|
return {
|
||||||
|
defaultValue: () => ({}),
|
||||||
|
deserialize: (view, offset, concrete) => {
|
||||||
|
const length = view.getUint32(offset.value, true);
|
||||||
|
offset.value += 4;
|
||||||
|
const value = {};
|
||||||
|
for (let i = 0; i < length; ++i) {
|
||||||
|
const key = Schema.deserialize(view, offset, concrete.key);
|
||||||
|
value[key] = Schema.deserialize(view, offset, concrete.value);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
},
|
||||||
|
normalize: ({value}) => {
|
||||||
|
return {
|
||||||
|
key: Schema.normalize({type: 'string'}),
|
||||||
|
value: Schema.normalize(value),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
serialize: (source, view, offset, concrete) => {
|
||||||
|
const keys = Object.keys(source);
|
||||||
|
view.setUint32(offset, keys.length, true);
|
||||||
|
let size = 4;
|
||||||
|
for (const key of keys) {
|
||||||
|
size += Schema.serialize(
|
||||||
|
key,
|
||||||
|
view,
|
||||||
|
offset + size,
|
||||||
|
concrete.key,
|
||||||
|
);
|
||||||
|
size += Schema.serialize(
|
||||||
|
source[key],
|
||||||
|
view,
|
||||||
|
offset + size,
|
||||||
|
concrete.value,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return size;
|
||||||
|
},
|
||||||
|
sizeOf: (instance, concrete) => {
|
||||||
|
let size = 4;
|
||||||
|
for (const key in instance) {
|
||||||
|
size += Schema.sizeOf(key, concrete.key);
|
||||||
|
size += Schema.sizeOf(instance[key], concrete.value);
|
||||||
|
}
|
||||||
|
return size;
|
||||||
|
},
|
||||||
|
staticSizeOf: () => 0,
|
||||||
|
};
|
||||||
|
}
|
55
app/ecs/schema-types/object.js
Normal file
55
app/ecs/schema-types/object.js
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
export default function (Schema) {
|
||||||
|
return {
|
||||||
|
defaultValue: ({properties}) => {
|
||||||
|
const object = {};
|
||||||
|
for (const key in properties) {
|
||||||
|
object[key] = Schema.defaultValue(properties[key]);
|
||||||
|
}
|
||||||
|
return object;
|
||||||
|
},
|
||||||
|
deserialize: (view, offset, {properties}) => {
|
||||||
|
const value = {};
|
||||||
|
for (const key in properties) {
|
||||||
|
value[key] = Schema.deserialize(view, offset, properties[key]);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
},
|
||||||
|
normalize: ({properties}) => {
|
||||||
|
const normalized = {properties: {}};
|
||||||
|
for (const key in properties) {
|
||||||
|
normalized.properties[key] = Schema.normalize(properties[key])
|
||||||
|
}
|
||||||
|
return normalized;
|
||||||
|
},
|
||||||
|
serialize: (source, view, offset, {properties}) => {
|
||||||
|
let size = 0;
|
||||||
|
for (const key in properties) {
|
||||||
|
size += Schema.serialize(
|
||||||
|
source[key],
|
||||||
|
view,
|
||||||
|
offset + size,
|
||||||
|
properties[key],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return size;
|
||||||
|
},
|
||||||
|
sizeOf: (instance, {properties}) => {
|
||||||
|
let size = 0;
|
||||||
|
for (const key in properties) {
|
||||||
|
size += Schema.sizeOf(instance[key], properties[key]);
|
||||||
|
}
|
||||||
|
return size;
|
||||||
|
},
|
||||||
|
staticSizeOf: ({properties}) => {
|
||||||
|
let size = 0;
|
||||||
|
for (const key in properties) {
|
||||||
|
const propertySize = Schema.size(properties[key]);
|
||||||
|
if (0 === propertySize) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
size += propertySize;
|
||||||
|
}
|
||||||
|
return size;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
31
app/ecs/schema-types/string.js
Normal file
31
app/ecs/schema-types/string.js
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
const decoder = new TextDecoder();
|
||||||
|
const encoder = new TextEncoder();
|
||||||
|
|
||||||
|
export default function () {
|
||||||
|
return {
|
||||||
|
defaultValue: () => '',
|
||||||
|
deserialize: (view, offset) => {
|
||||||
|
const length = view.getUint32(offset.value, true);
|
||||||
|
offset.value += 4;
|
||||||
|
const {buffer, byteOffset} = view;
|
||||||
|
const value = decoder.decode(new DataView(buffer, byteOffset + offset.value, length));
|
||||||
|
offset.value += length;
|
||||||
|
return value;
|
||||||
|
},
|
||||||
|
serialize: (source, view, offset) => {
|
||||||
|
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;
|
||||||
|
},
|
||||||
|
sizeOf: (instance) => {
|
||||||
|
let size = 4;
|
||||||
|
size += (encoder.encode(instance)).length;
|
||||||
|
return size;
|
||||||
|
},
|
||||||
|
staticSizeOf: () => 0,
|
||||||
|
};
|
||||||
|
}
|
16
app/ecs/schema-types/uint16.js
Normal file
16
app/ecs/schema-types/uint16.js
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
export default function () {
|
||||||
|
return {
|
||||||
|
defaultValue: () => 0,
|
||||||
|
deserialize: (view, offset) => {
|
||||||
|
const value = view.getUint16(offset.value, true);
|
||||||
|
offset.value += 2;
|
||||||
|
return value;
|
||||||
|
},
|
||||||
|
serialize: (source, view, offset) => {
|
||||||
|
view.setUint16(offset, source, true);
|
||||||
|
return 2;
|
||||||
|
},
|
||||||
|
sizeOf: () => 2,
|
||||||
|
staticSizeOf: () => 2,
|
||||||
|
};
|
||||||
|
}
|
16
app/ecs/schema-types/uint32.js
Normal file
16
app/ecs/schema-types/uint32.js
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
export default function () {
|
||||||
|
return {
|
||||||
|
defaultValue: () => 0,
|
||||||
|
deserialize: (view, offset) => {
|
||||||
|
const value = view.getUint32(offset.value, true);
|
||||||
|
offset.value += 4;
|
||||||
|
return value;
|
||||||
|
},
|
||||||
|
serialize: (source, view, offset) => {
|
||||||
|
view.setUint32(offset, source, true);
|
||||||
|
return 4;
|
||||||
|
},
|
||||||
|
sizeOf: () => 4,
|
||||||
|
staticSizeOf: () => 4,
|
||||||
|
};
|
||||||
|
}
|
16
app/ecs/schema-types/uint64.js
Normal file
16
app/ecs/schema-types/uint64.js
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
export default function () {
|
||||||
|
return {
|
||||||
|
defaultValue: () => 0n,
|
||||||
|
deserialize: (view, offset) => {
|
||||||
|
const value = view.getBigUint64(offset.value, true);
|
||||||
|
offset.value += 8;
|
||||||
|
return value;
|
||||||
|
},
|
||||||
|
serialize: (source, view, offset) => {
|
||||||
|
view.setBigUint64(offset, source, true);
|
||||||
|
return 8;
|
||||||
|
},
|
||||||
|
sizeOf: () => 8,
|
||||||
|
staticSizeOf: () => 8,
|
||||||
|
};
|
||||||
|
}
|
16
app/ecs/schema-types/uint8.js
Normal file
16
app/ecs/schema-types/uint8.js
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
export default function () {
|
||||||
|
return {
|
||||||
|
defaultValue: () => 0,
|
||||||
|
deserialize: (view, offset) => {
|
||||||
|
const value = view.getUint8(offset.value, true);
|
||||||
|
offset.value += 1;
|
||||||
|
return value;
|
||||||
|
},
|
||||||
|
serialize: (source, view, offset) => {
|
||||||
|
view.setUint8(offset, source, true);
|
||||||
|
return 1;
|
||||||
|
},
|
||||||
|
sizeOf: () => 1,
|
||||||
|
staticSizeOf: () => 1,
|
||||||
|
};
|
||||||
|
}
|
|
@ -1,303 +1,66 @@
|
||||||
const encoder = new TextEncoder();
|
|
||||||
|
|
||||||
export default class Schema {
|
export default class Schema {
|
||||||
|
|
||||||
$$size = 0;
|
static $$types = {};
|
||||||
|
|
||||||
specification;
|
specification;
|
||||||
|
|
||||||
static viewGetMethods = {
|
|
||||||
uint8: 'getUint8',
|
|
||||||
int8: 'getInt8',
|
|
||||||
uint16: 'getUint16',
|
|
||||||
int16: 'getInt16',
|
|
||||||
uint32: 'getUint32',
|
|
||||||
int32: 'getInt32',
|
|
||||||
float32: 'getFloat32',
|
|
||||||
float64: 'getFloat64',
|
|
||||||
int64: 'getBigInt64',
|
|
||||||
uint64: 'getBigUint64',
|
|
||||||
};
|
|
||||||
|
|
||||||
static viewSetMethods = {
|
|
||||||
uint8: 'setUint8',
|
|
||||||
int8: 'setInt8',
|
|
||||||
uint16: 'setUint16',
|
|
||||||
int16: 'setInt16',
|
|
||||||
uint32: 'setUint32',
|
|
||||||
int32: 'setInt32',
|
|
||||||
float32: 'setFloat32',
|
|
||||||
float64: 'setFloat64',
|
|
||||||
int64: 'setBigInt64',
|
|
||||||
uint64: 'setBigUint64',
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor(specification) {
|
constructor(specification) {
|
||||||
this.specification = this.constructor.normalize(specification);
|
this.specification = this.constructor.normalize(specification);
|
||||||
}
|
}
|
||||||
|
|
||||||
static deserialize(view, offset, specification) {
|
static defaultValue({$, concrete}) {
|
||||||
const viewGetMethod = this.viewGetMethods[specification.type];
|
if (concrete.defaultValue) {
|
||||||
if (viewGetMethod) {
|
return concrete.defaultValue;
|
||||||
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 'map': {
|
|
||||||
const length = view.getUint32(offset.value, true);
|
|
||||||
offset.value += 4;
|
|
||||||
const value = {};
|
|
||||||
for (let i = 0; i < length; ++i) {
|
|
||||||
const key = this.deserialize(view, offset, {type: 'string'});
|
|
||||||
value[key] = this.deserialize(view, offset, specification.value);
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
case 'object': {
|
|
||||||
const value = {};
|
|
||||||
for (const key in specification.properties) {
|
|
||||||
value[key] = this.deserialize(view, offset, specification.properties[key]);
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
case 'string': {
|
|
||||||
const length = view.getUint32(offset.value, true);
|
|
||||||
offset.value += 4;
|
|
||||||
const {buffer, byteOffset} = view;
|
|
||||||
const decoder = new TextDecoder();
|
|
||||||
const value = decoder.decode(new DataView(buffer, byteOffset + offset.value, length));
|
|
||||||
offset.value += length;
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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':
|
|
||||||
case 'uint64': case 'int64':
|
|
||||||
case 'float32': case 'float64': {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
case 'array': {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
case 'map': {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
case 'object': {
|
|
||||||
const object = {};
|
|
||||||
for (const key in specification.properties) {
|
|
||||||
object[key] = this.defaultValue(specification.properties[key]);
|
|
||||||
}
|
|
||||||
return object;
|
|
||||||
}
|
|
||||||
case 'string': {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return $.defaultValue(concrete);
|
||||||
}
|
}
|
||||||
|
|
||||||
defaultValue() {
|
defaultValue() {
|
||||||
return this.constructor.defaultValue(this.specification);
|
return this.constructor.defaultValue(this.specification);
|
||||||
}
|
}
|
||||||
|
|
||||||
static normalize(specification) {
|
static deserialize(view, offset, {$, concrete}) {
|
||||||
let normalized = specification;
|
return $.deserialize(view, offset, concrete);
|
||||||
switch (specification.type) {
|
|
||||||
case 'array': {
|
|
||||||
normalized.subtype = this.normalize(specification.subtype);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'map': {
|
|
||||||
normalized.value = this.normalize(specification.value);
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
/* v8 ignore next 2 */
|
|
||||||
default:
|
|
||||||
throw new TypeError(`invalid specification: ${JSON.stringify(specification)}`);
|
|
||||||
}
|
|
||||||
return {...normalized, defaultValue: this.defaultValue(normalized)};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static serialize(source, view, offset, specification) {
|
deserialize(view, offset = 0) {
|
||||||
const viewSetMethod = this.viewSetMethods[specification.type];
|
return this.constructor.deserialize(view, {value: offset}, this.specification);
|
||||||
if (viewSetMethod) {
|
}
|
||||||
view[viewSetMethod](offset, source, true);
|
|
||||||
return this.size(specification);
|
static normalize({type, ...rest}) {
|
||||||
}
|
const $$type = this.$$types[type];
|
||||||
switch (specification.type) {
|
if (!$$type) {
|
||||||
case 'array': {
|
throw new TypeError(`unregistered schema type '${type}'`);
|
||||||
view.setUint32(offset, source.length, true);
|
|
||||||
let arraySize = 4;
|
|
||||||
for (const element of source) {
|
|
||||||
arraySize += this.serialize(
|
|
||||||
element,
|
|
||||||
view,
|
|
||||||
offset + arraySize,
|
|
||||||
specification.subtype,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return arraySize;
|
|
||||||
}
|
|
||||||
case 'map': {
|
|
||||||
const keys = Object.keys(source);
|
|
||||||
view.setUint32(offset, keys.length, true);
|
|
||||||
let mapSize = 4;
|
|
||||||
for (const key of keys) {
|
|
||||||
mapSize += this.serialize(
|
|
||||||
key,
|
|
||||||
view,
|
|
||||||
offset + mapSize,
|
|
||||||
{type: 'string'},
|
|
||||||
);
|
|
||||||
mapSize += this.serialize(
|
|
||||||
source[key],
|
|
||||||
view,
|
|
||||||
offset + mapSize,
|
|
||||||
specification.value,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return mapSize;
|
|
||||||
}
|
|
||||||
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 {
|
||||||
|
$: this.$$types[type],
|
||||||
|
concrete: $$type.normalize ? $$type.normalize(rest) : rest,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static serialize(source, view, offset, {$, concrete}) {
|
||||||
|
return $.serialize(source, view, offset, concrete);
|
||||||
}
|
}
|
||||||
|
|
||||||
serialize(source, view, offset = 0) {
|
serialize(source, view, offset = 0) {
|
||||||
this.constructor.serialize(source, view, offset, this.specification);
|
this.constructor.serialize(source, view, offset, this.specification);
|
||||||
}
|
}
|
||||||
|
|
||||||
static sizeOf(concrete, specification) {
|
static sizeOf(instance, {$, concrete}) {
|
||||||
const size = this.size(specification);
|
return $.sizeOf(instance, concrete);
|
||||||
if (size > 0) {
|
|
||||||
return size;
|
|
||||||
}
|
|
||||||
let fullSize = 0;
|
|
||||||
const {type} = specification;
|
|
||||||
switch (type) {
|
|
||||||
case 'array': {
|
|
||||||
fullSize += 4;
|
|
||||||
for (const element of concrete) {
|
|
||||||
fullSize += this.sizeOf(element, specification.subtype);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'map': {
|
|
||||||
fullSize += 4;
|
|
||||||
for (const key in concrete) {
|
|
||||||
fullSize += this.sizeOf(key, {type: 'string'});
|
|
||||||
fullSize += this.sizeOf(concrete[key], specification.value);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'object': {
|
|
||||||
for (const key in specification.properties) {
|
|
||||||
fullSize += this.sizeOf(concrete[key], specification.properties[key]);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'string':
|
|
||||||
fullSize += 4;
|
|
||||||
fullSize += (encoder.encode(concrete)).length;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return fullSize;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sizeOf(concrete) {
|
sizeOf(instance) {
|
||||||
return this.constructor.sizeOf(concrete, this.specification);
|
return this.constructor.sizeOf(instance, this.specification);
|
||||||
}
|
}
|
||||||
|
|
||||||
static size(specification) {
|
static size({$, concrete}) {
|
||||||
switch (specification.type) {
|
return $.staticSizeOf(concrete);
|
||||||
case 'array': return 0;
|
|
||||||
case 'map': return 0; // TODO could be fixed-size w/ fixed-size key
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
case 'uint16': case 'int16': {
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
case 'uint32': case 'int32': case 'float32': {
|
|
||||||
return 4;
|
|
||||||
}
|
|
||||||
case 'uint64': case 'int64': case 'float64': {
|
|
||||||
return 8;
|
|
||||||
}
|
|
||||||
default: return 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const imports = import.meta.glob('./schema-types/*.js', {eager: true, import: 'default'});
|
||||||
|
for (const path in imports) {
|
||||||
|
Schema.$$types[path.replace(/.\/schema-types\/(.*)\.js/, '$1')] = imports[path](Schema);
|
||||||
|
}
|
||||||
|
|
|
@ -4,10 +4,6 @@ import Schema from './schema.js';
|
||||||
|
|
||||||
test('defaults values', () => {
|
test('defaults values', () => {
|
||||||
const compare = (specification, value) => {
|
const compare = (specification, value) => {
|
||||||
expect(Schema.defaultValue(specification))
|
|
||||||
.to.deep.equal(value);
|
|
||||||
expect(new Schema(specification).specification.defaultValue)
|
|
||||||
.to.deep.equal(value);
|
|
||||||
expect(new Schema(specification).defaultValue())
|
expect(new Schema(specification).defaultValue())
|
||||||
.to.deep.equal(value);
|
.to.deep.equal(value);
|
||||||
};
|
};
|
||||||
|
@ -18,13 +14,17 @@ test('defaults values', () => {
|
||||||
'int16',
|
'int16',
|
||||||
'uint32',
|
'uint32',
|
||||||
'int32',
|
'int32',
|
||||||
'uint64',
|
|
||||||
'int64',
|
|
||||||
'float32',
|
'float32',
|
||||||
'float64',
|
'float64',
|
||||||
].forEach((type) => {
|
].forEach((type) => {
|
||||||
compare({type}, 0);
|
compare({type}, 0);
|
||||||
});
|
});
|
||||||
|
[
|
||||||
|
'uint64',
|
||||||
|
'int64',
|
||||||
|
].forEach((type) => {
|
||||||
|
compare({type}, 0n);
|
||||||
|
});
|
||||||
compare({type: 'string'}, '');
|
compare({type: 'string'}, '');
|
||||||
compare({type: 'array', subtype: {type: 'string'}}, []);
|
compare({type: 'array', subtype: {type: 'string'}}, []);
|
||||||
compare(
|
compare(
|
||||||
|
@ -167,11 +167,11 @@ test('encodes and decodes', () => {
|
||||||
[{type: 'object', properties: {foo: {type: 'uint8'}, bar: {type: 'string'}}}, {foo: 64, bar: 'baz'}],
|
[{type: 'object', properties: {foo: {type: 'uint8'}, bar: {type: 'string'}}}, {foo: 64, bar: 'baz'}],
|
||||||
[{type: 'object', properties: {foo: {type: 'uint8'}}}, {foo: 64}],
|
[{type: 'object', properties: {foo: {type: 'uint8'}}}, {foo: 64}],
|
||||||
];
|
];
|
||||||
entries.forEach(([specification, concrete]) => {
|
entries.forEach(([specification, instance]) => {
|
||||||
const schema = new Schema(specification);
|
const schema = new Schema(specification);
|
||||||
const view = new DataView(new ArrayBuffer(schema.sizeOf(concrete)));
|
const view = new DataView(new ArrayBuffer(schema.sizeOf(instance)));
|
||||||
schema.serialize(concrete, view);
|
schema.serialize(instance, view);
|
||||||
expect(concrete)
|
expect(instance)
|
||||||
.to.deep.equal(schema.deserialize(view));
|
.to.deep.equal(schema.deserialize(view));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue
Block a user