@woosh/meep-engine
Version:
Pure JavaScript game engine. Fully featured and production ready.
162 lines (134 loc) • 3.98 kB
JavaScript
import { assert } from "../assert.js";
import { Cache } from "../cache/Cache.js";
import { combine_hash } from "../collection/array/combine_hash.js";
import { computeHashArray } from "../collection/array/computeHashArray.js";
import { isArrayEqualStrict } from "../collection/array/isArrayEqualStrict.js";
import { invokeObjectEquals } from "../model/object/invokeObjectEquals.js";
import { invokeObjectHash } from "../model/object/invokeObjectHash.js";
import { computeStringHash } from "../primitives/strings/computeStringHash.js";
import { string_compute_byte_size } from "../primitives/strings/string_compute_byte_size.js";
/**
* Hash is an integer value, so this is an invalid value for a computed hash
* @type {number}
*/
const HASH_NOT_SET = 0.1;
class FunctionDefinition {
/**
*
* @param {string} [name]
* @param {string} body
* @param {string[]} [args]
*/
constructor({ name, body, args = [] }) {
assert.isString(body, 'body');
/**
*
* @type {string}
*/
this.name = name;
/**
*`
* @type {string[]}
*/
this.args = args;
/**
*
* @type {string}
*/
this.body = body;
/**
*
* @type {number}
* @private
*/
this.__hash = HASH_NOT_SET;
}
updateHash() {
this.__hash = combine_hash(
computeStringHash(this.name),
computeStringHash(this.body),
computeHashArray(this.args, computeStringHash)
);
}
/**
*
* @return {number}
*/
computeByteSize() {
let result = string_compute_byte_size(this.body);
if (this.name !== undefined) {
result += string_compute_byte_size(this.name);
}
const n = this.args.length;
for (let i = 0; i < n; i++) {
const arg = this.args[i];
result += string_compute_byte_size(arg);
}
return result;
}
/**
*
* @param {FunctionDefinition} other
* @returns {boolean}
*/
equals(other) {
return this.name === other.name
&& this.body === other.body
&& isArrayEqualStrict(this.args, other.args)
;
}
/**
* @returns {number}
*/
hash() {
if (this.__hash === HASH_NOT_SET) {
this.updateHash();
}
return this.__hash;
}
}
/**
* Optimization structure, caches functions internally to avoid creating multiple identical functions
*/
export class FunctionCompiler {
constructor() {
/**
*
* @type {Cache<FunctionDefinition, Function>}
* @private
*/
this.cache = new Cache({
maxWeight: 10383360,
keyWeigher(k) {
return k.computeByteSize();
},
keyHashFunction: invokeObjectHash,
keyEqualityFunction: invokeObjectEquals
});
}
/**
*
* @param {string} code
* @param {string[]} [args]
* @param {string} [name]
*/
compile({ code, args = [], name }) {
assert.isString(code, 'code');
assert.isArray(args, 'args');
const fd = new FunctionDefinition({ body: code, args, name });
const existing = this.cache.get(fd);
if (existing === null) {
const f = new Function(args.join(","), code);
if (name !== undefined) {
//give function a name
Object.defineProperty(f, "name", { value: name });
}
//cache
this.cache.put(fd, f);
return f;
} else {
return existing;
}
}
}
FunctionCompiler.INSTANCE = new FunctionCompiler();