refactor: merge serializer into schema

This commit is contained in:
cha0s 2024-06-11 21:00:03 -05:00
parent 8cd242871a
commit 815d53d1e8
5 changed files with 94 additions and 105 deletions

View File

@ -1,5 +1,3 @@
import Serializer from './serializer.js';
import Base from './base.js';
export default class Arbitrary extends Base {
@ -10,11 +8,6 @@ export default class Arbitrary extends Base {
Instance;
constructor() {
super();
this.serializer = new Serializer(this.constructor.schema);
}
allocateMany(count) {
if (!this.Instance) {
this.Instance = this.instanceFromSchema();
@ -52,11 +45,11 @@ export default class Arbitrary extends Base {
}
deserialize(entityId, view, offset) {
this.serializer.decode(view, this.get(entityId), offset);
this.constructor.schema.deserialize(view, this.get(entityId), offset);
}
serialize(entityId, view, offset) {
this.serializer.encode(this.get(entityId), view, offset);
this.constructor.schema.serialize(this.get(entityId), view, offset);
}
get(entityId) {

View File

@ -50,6 +50,33 @@ export default class Schema {
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;
if (viewGetMethod) {
value = view[viewGetMethod](cursor, true);
cursor += Schema.sizeOfType(type);
}
else {
switch (type) {
case 'string': {
const length = view.getUint32(cursor, true);
cursor += 4;
const {buffer, byteOffset} = view;
const decoder = new TextDecoder();
value = decoder.decode(new DataView(buffer, byteOffset + cursor, length));
cursor += length;
break;
}
}
}
destination[key] = value;
}
}
static defaultValueForType(type) {
switch (type) {
case 'uint8': case 'int8':
@ -107,6 +134,33 @@ export default class Schema {
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);
}
else {
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]);
}
view.setUint32(lengthOffset, bytes.length, true);
break;
}
}
}
}
}
get size() {
return this.$$size;
}

View File

@ -15,8 +15,44 @@ test('validates a schema', () => {
});
test('calculates the size of an instance', () => {
expect((new Schema({foo: 'uint8', bar: 'uint32'})).sizeOf({foo: 69, bar: 420}))
expect(
(new Schema({foo: 'uint8', bar: 'uint32'}))
.sizeOf({foo: 69, bar: 420})
)
.to.equal(5);
expect((new Schema({foo: 'string'})).sizeOf({foo: 'hi'}))
expect(
(new Schema({foo: 'string'}))
.sizeOf({foo: 'hi'})
)
.to.equal(4 + (new TextEncoder().encode('hi')).length);
});
test('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'],
['string', 'α'],
];
const specification = entries.reduce((r, [type]) => ({...r, [Object.keys(r).length]: type}), {});
const data = entries.reduce((r, [, value]) => ({...r, [Object.keys(r).length]: value}), {});
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);
});

View File

@ -1,61 +0,0 @@
import Schema from './schema.js';
export default class Serializer {
constructor(schema) {
this.schema = schema instanceof Schema ? schema : new Schema(schema);
}
decode(view, destination, offset = 0) {
let cursor = offset;
for (const [key, {type}] of this.schema) {
const viewGetMethod = Schema.viewGetMethods[type];
let value;
if (viewGetMethod) {
value = view[viewGetMethod](cursor, true);
cursor += Schema.sizeOfType(type);
}
else {
switch (type) {
case 'string': {
const length = view.getUint32(cursor, true);
cursor += 4;
const {buffer, byteOffset} = view;
const decoder = new TextDecoder();
value = decoder.decode(new DataView(buffer, byteOffset + cursor, length));
cursor += length;
break;
}
}
}
destination[key] = value;
}
}
encode(source, view, offset = 0) {
let cursor = offset;
for (const [key, {type}] of this.schema) {
const viewSetMethod = Schema.viewSetMethods[type];
if (viewSetMethod) {
view[viewSetMethod](cursor, source[key], true);
cursor += Schema.sizeOfType(type);
}
else {
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]);
}
view.setUint32(lengthOffset, bytes.length, true);
break;
}
}
}
}
}
}

View File

@ -1,33 +0,0 @@
import {expect, test} from 'vitest';
import Serializer from './serializer.js';
test('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'],
['string', 'α'],
];
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);
});