@woosh/meep-engine
Version:
Pure JavaScript game engine. Fully featured and production ready.
293 lines (226 loc) • 8.35 kB
JavaScript
/**
* @readonly
* @enum {string}
*/
import { assert } from "../../assert.js";
import { DataType2DataViewReaders } from "../../binary/data_view/DataType2DataViewReaders.js";
import { DataType2DataViewWriters } from "../../binary/data_view/DataType2DataViewWriters.js";
import { EndianType } from "../../binary/EndianType.js";
import { BinaryDataType } from "../../binary/type/BinaryDataType.js";
import { DataTypeByteSizes } from "../../binary/type/DataTypeByteSizes.js";
import { Cache } from "../../cache/Cache.js";
import { FunctionCompiler } from "../../function/FunctionCompiler.js";
import { objectKeyByValue } from "../../model/object/objectKeyByValue.js";
import { computeStringHash } from "../../primitives/strings/computeStringHash.js";
import { computeHashArray } from "../array/computeHashArray.js";
import { isArrayEqualStrict } from "../array/isArrayEqualStrict.js";
/**
*
* @param {BinaryDataType[]} types
* @param {EndianType} [endianType]
* @returns {function(data_view:DataView, byte_offset:number, result:number[]):void}
*/
function genRowReader(types, endianType = EndianType.BigEndian) {
let offset = 0;
const lines = [];
const numTypes = types.length;
for (let i = 0; i < numTypes; i++) {
const type = types[i];
const reader_name = DataType2DataViewReaders[type];
assert.isString(reader_name, 'reader_name');
const littleEndianFlag = endianType === EndianType.BigEndian ? 'false' : 'true';
// See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView
lines.push(`result[${i}] = dataView.${reader_name}(${offset} + byteOffset, ${littleEndianFlag});`);
offset += DataTypeByteSizes[type];
}
const result = FunctionCompiler.INSTANCE.compile({
args: ['dataView, byteOffset, result'],
code: lines.join("\n")
});
return result;
}
/**
*
* @param {BinaryDataType[]} types
* @param {EndianType} [endianType]
* @returns {function(data_view:DataView, byte_offset:number, record:number[]):void}
*/
function genRowWriter(types, endianType = EndianType.BigEndian) {
let offset = 0;
const lines = [];
const numTypes = types.length;
for (let i = 0; i < numTypes; i++) {
const type = types[i];
const writer_name = DataType2DataViewWriters[type];
assert.isString(writer_name, 'writer_name');
const littleEndianFlag = endianType === EndianType.BigEndian ? 'false' : 'true';
// See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView
lines.push(`dataView.${writer_name}(${offset} + byteOffset, record[${i}], ${littleEndianFlag});`);
offset += DataTypeByteSizes[type];
}
const result = FunctionCompiler.INSTANCE.compile({
args: ['dataView, byteOffset, record'],
code: lines.join("\n")
}
);
return result;
}
/**
*
* @param {BinaryDataType} type
* @param {number} offset
* @param {EndianType} [endianType]
* @returns {function(data_view:DataView, byte_offset:number, value:number):void}
*/
function compileDataViewValueWriter(type, offset, endianType = EndianType.BigEndian) {
const writeMethod = DataType2DataViewWriters[type];
const littleEndianFlag = endianType === EndianType.BigEndian ? 'false' : 'true';
return FunctionCompiler.INSTANCE.compile({
args: ['dataView, byteOffset, value'],
code: `dataView.${writeMethod}(byteOffset+${offset}, value, ${littleEndianFlag});`
});
}
/**
*
* @param {BinaryDataType} type
* @param {number} offset
* @param {EndianType} [endianType]
* @returns {function(data_view:DataView, byte_offset:number):number}
*/
function compileDataViewValueReader(type, offset, endianType = EndianType.BigEndian) {
const readMethod = DataType2DataViewReaders[type];
const littleEndianFlag = endianType === EndianType.BigEndian ? 'false' : 'true';
return FunctionCompiler.INSTANCE.compile({
args: ['dataView, byteOffset'],
code: `return dataView.${readMethod}(byteOffset+${offset}, ${littleEndianFlag});`
});
}
/**
* This is a schema class for binary data tables of class {@link RowFirstTable}
* @class
* @copyright Company Named Limited (c) 2025
* @author Alex Goldring
*/
export class RowFirstTableSpec {
/**
*
* @param {BinaryDataType[]} types
* @param {EndianType} [endianness]
* @constructor
*/
constructor(
types,
endianness = EndianType.BigEndian
) {
assert.isArray(types, 'types');
assert.enum(endianness, EndianType, 'endianness');
const numTypes = types.length;
/**
* @readonly
* @type {BinaryDataType[]}
*/
this.types = types;
/**
* @readonly
* @type {EndianType}
*/
this.endianType = endianness;
/**
* @readonly
* @type {Uint32Array}
*/
this.columnOffsets = new Uint32Array(numTypes);
let byteOffset = 0;
for (let index = 0; index < numTypes; index++) {
const type = types[index];
assert.enum(type, BinaryDataType, `type[${index}]`);
this.columnOffsets[index] = byteOffset;
const columnByteSize = DataTypeByteSizes[type];
byteOffset += columnByteSize;
}
/**
* Number of bytes taken up by a single row of values
* @readonly
* @type {number}
*/
this.bytesPerRecord = byteOffset;
/**
* @readonly
* @type {function(DataView, number, number[]): void}
*/
this.readRowMethod = genRowReader(types, endianness);
/**
* @readonly
* @type {function(DataView, number, number[]): void}
*/
this.writeRowMethod = genRowWriter(types, endianness);
//generate cell readers/writers
this.cellWriters = new Array(numTypes);
this.cellReaders = new Array(numTypes);
for (let i = 0; i < numTypes; i++) {
this.cellReaders[i] = compileDataViewValueReader(types[i], this.columnOffsets[i], endianness);
this.cellWriters[i] = compileDataViewValueWriter(types[i], this.columnOffsets[i], endianness);
}
}
/**
*
* @return {number}
*/
getColumnCount() {
return this.types.length;
}
/**
*
* @return {number}
*/
hash() {
let hash = this.endianType === EndianType.BigEndian ? 1 : 0;
hash += computeHashArray(this.types, computeStringHash);
return hash;
}
/**
*
* @param {RowFirstTableSpec} other
* @returns {boolean}
*/
equals(other) {
if (this.endianType !== other.endianType) {
return false;
}
return isArrayEqualStrict(this.types, other.types);
}
toString() {
return `TableSpec{types=[${this.types.join(', ')}], endian=${objectKeyByValue(EndianType, this.endianType)}}`;
}
/**
* Produces an immutable spec, either creates a new spec or retrieves a cached version
* @param {BinaryDataType[]} types
* @param {EndianType} [endianType]
* @returns {RowFirstTableSpec}
*/
static get(types, endianType = EndianType.BigEndian) {
assert.isArray(types, 'types');
assert.enum(endianType, EndianType, 'endianType');
//compute hash
const hash = types.join('.') + ':' + endianType;
const cachedValue = SPEC_CACHE.get(hash);
if (cachedValue !== null) {
return cachedValue;
}
const newValue = Object.freeze(new RowFirstTableSpec(types));
SPEC_CACHE.put(hash, newValue);
return newValue;
}
}
/**
* @readonly
* @type {boolean}
*/
RowFirstTableSpec.prototype.isRowFirstTableSpec = true;
/**
* @readonly
* @type {Cache<string,RowFirstTableSpec>}
*/
const SPEC_CACHE = new Cache({
keyHashFunction: computeStringHash
});