silphius/app/ecs/schema.js
2024-06-10 22:45:09 -05:00

165 lines
3.6 KiB
JavaScript

const encoder = new TextEncoder();
export default class Schema {
$$size = 0;
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) {
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();
}
static defaultValueForType(type) {
switch (type) {
case 'uint8': case 'int8':
case 'uint16': case 'int16':
case 'uint32': case 'int32':
case 'uint64': case 'int64':
case 'float32': case 'float64': {
return 0;
}
case 'string': {
return '';
}
}
}
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;
break;
}
}
}
else {
cursor += size;
fullSize += size;
}
}
return fullSize;
}
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;
break;
}
}
else {
fullSize += size;
}
}
return fullSize;
}
static sizeOfType(type) {
switch (type) {
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;
}
}
static validateType(type) {
return [
'uint8', 'int8',
'uint16', 'int16',
'uint32', 'int32',
'uint64', 'int64',
'float32', 'float64',
'string',
]
.includes(type);
}
}