import TextConverter from "./TextConverter";
const DEBUG = false;
/**
* WASMJSAPI class.
* Creates a JS API from a WASM instance by demangling C++ function names.
*/
class WASMJSAPI {
/**
* WASMJSAPI constructor.
* @param {Object} wasmInstance An instantiated WASM.
* @param {Object} helperFunctions An object that provides these helper functions: malloc, free, memset, demangle.
*/
constructor(wasmInstance, helperFunctions) {
this.wasm = wasmInstance.exports;
this.memory = this.wasm.memory.buffer;
this.classes = {};
this.wasm_malloc = helperFunctions.malloc;
this.wasm_free = helperFunctions.free;
this.wasm_memset = helperFunctions.memset;
this.wasm_demangle = helperFunctions.demangle;
let demangledCppFunctions = this._demangleCppFunctions(this.wasm);
this._createJSClasses(demangledCppFunctions);
}
_createJSClasses(cppFunctions) {
for (const [key, value] of Object.entries(cppFunctions)) {
if (DEBUG) console.log("[SB] [WASMJSAPI] Processing " + key + "...");
let indexOfFirstOpeningBracket = key.indexOf('(');
if (indexOfFirstOpeningBracket == 0) continue;
var str = key.substring(0, indexOfFirstOpeningBracket);
var indexOfLastDoubleColon = str.lastIndexOf('::');
if (indexOfLastDoubleColon == 0) continue;
let methodName = str.substring(indexOfLastDoubleColon + 2);
str = str.substring(0, indexOfLastDoubleColon);
indexOfLastDoubleColon = str.lastIndexOf('::');
if (indexOfLastDoubleColon == 0) continue;
var className = str.substring(indexOfLastDoubleColon + 2);
var template = "";
var jsClassName = className;
if (className.includes("<")) {
let indexOfOpeningAngleBracket = className.indexOf('<');
let indexOfClosingAngleBracket = className.indexOf('>');
let classNameWoTemplate = className.substring(0, indexOfOpeningAngleBracket);
template = className.substring(indexOfOpeningAngleBracket + 1, indexOfClosingAngleBracket);
template = template[0].toUpperCase() + template.substring(1);
className = classNameWoTemplate;
jsClassName = classNameWoTemplate + template;
}
this._createJSClass(jsClassName);
this._createMethod(className, template, jsClassName, methodName, value);
}
}
_createJSClass(className) {
if (this.classes[className] != null) {
return;
}
if (DEBUG) console.log("[SB] [WASMJSAPI] Creating class " + className);
let module = this;
this.classes[className] = class {
constructor() {
if (DEBUG) console.log("[SB] [WASMJSAPI] " + className + " constructor called");
let defaultObjectSize = 1024;
var objectSize = 0;
if (this.getObjectSize === undefined) {
objectSize = defaultObjectSize;
if (DEBUG) console.warn("[SB] [WASMJSAPI] Could not determine object size for " + className + ". Using default size (" + defaultObjectSize + ")");
} else {
objectSize = this.getObjectSize();
}
let ptr = module.wasm_malloc(objectSize);
if (DEBUG) console.log("[SB] [WASMJSAPI] memory allocated at " + ptr + " (" + objectSize + " bytes)")
this.wasmMemAddress = ptr;
let args = Array.from(arguments);
this.init(args);
}
destruct() {
if (DEBUG) console.log("[SB] [WASMJSAPI] " + className + " destructor called");
if (this.deinit !== undefined) {
this.deinit();
}
module.wasm_free(this.wasmMemAddress);
}
static createWithPtr(memoryAddress) {
let obj = Object.create(this.prototype);
obj.wasmMemAddress = memoryAddress;
return obj;
}
};
}
_convertArguments(args) {
return args.map(arg => {
if (typeof arg === "object") {
if (arg.hasOwnProperty('wasmMemAddress')) {
return arg.wasmMemAddress;
}
}
if (typeof arg === "string") {
if (DEBUG) console.warn("[SB] [WASMJSAPI] string parameter needs to be converted to char*", arg);
}
return arg;
})
}
_createMethod(className, template, jsClassName, methodName, wasmFunction) {
let module = this;
// console.log("[SB] [WASMJSAPI] Creating method " + jsClassName + "::" + methodName + " to call " + wasmFunction);
var sbClass = this.classes[jsClassName];
if (methodName == className) {
if (sbClass.prototype.init !== undefined) {
if (DEBUG) console.warn("[SB] [WASMJSAPI] Init method is already set for " + className);
}
sbClass.prototype.init = function (args) {
if (DEBUG) console.log("[SB] [WASMJSAPI] Init called");
args.unshift(this.wasmMemAddress);
module.wasm[wasmFunction].apply(this, args);
};
} else if (methodName == "~" + className) {
sbClass.prototype.deinit = function () {
if (DEBUG) console.log("[SB] [WASMJSAPI] Deinit called");
let args = Array.from(arguments);
args.unshift(this.wasmMemAddress);
module.wasm[wasmFunction].apply(this, args);
}
} else {
if (sbClass.prototype[methodName] !== undefined) {
if (DEBUG) console.warn("[SB] [WASMJSAPI] " + methodName + " method is already set for " + className);
}
let jsMethod = function () {
// console.log("[SB] [WASMJSAPI] Called className " + className + " method " + jsClassName + " jsClassName" + methodName + " with arguments");
let args = Array.from(arguments);
let isStaticMethod = typeof this === "function";
if (!isStaticMethod) {
args.unshift(this.wasmMemAddress);
}
args = module._convertArguments(args);
let wasmResult = module.wasm[wasmFunction].apply(this, args);
// console.log("[SB] [WASMJSAPI] WASM returned: " + wasmResult);
return wasmResult;
}
sbClass.prototype[methodName] = jsMethod;
sbClass[methodName] = jsMethod;
}
}
_demangleCppFunctions(wasmInstanceExports) {
let maxBytes = 1024;
let inputBuffer = this.wasm_malloc(maxBytes);
let outputBuffer = this.wasm_malloc(maxBytes);
var demangledFunctions = {};
for (const [key, value] of Object.entries(wasmInstanceExports)) {
if (key.startsWith("_Z")) {
// console.log("[SB] [WASMJSAPI] Demangling " + key + "...");
this.wasm_memset(inputBuffer, 0, maxBytes);
this.wasm_memset(outputBuffer, 0, maxBytes);
TextConverter.toWASMString(this.memory, key, inputBuffer, maxBytes);
let length = this.wasm_demangle(inputBuffer, outputBuffer);
if (length == 0) {
if (DEBUG) console.warn("[SB] [WASMJSAPI] Could not demangle function: " + key);
continue;
}
let demangledFunction = TextConverter.toJSString(this.memory, outputBuffer, length);
demangledFunctions[demangledFunction] = key;
// console.log("[SB] [WASMJSAPI] Result: " + demangledFunction);
}
}
this.wasm_free(inputBuffer);
this.wasm_free(outputBuffer);
return demangledFunctions;
}
/**
* Converts a JS string to a WASM string.
* @param {String} str The JS string.
* @returns {Number} A pointer to the WASM string.
*/
allocString(str) {
let numberOfBytes = str.length + 1;
let ptr = this.wasm_malloc(numberOfBytes);
this.wasm_memset(ptr, 0, numberOfBytes);
let result = TextConverter.toWASMString(this.memory, str, ptr, numberOfBytes);
return ptr;
}
/**
* Writes bytes directly into the WASM memory and returns a pointer to its location.
* @param {Uint8Array} data The data to be written.
* @returns {Number} A pointer to the data.
*/
allocBytes(data, numberOfBytes) {
let ptr = this.wasm_malloc(numberOfBytes);
this.wasm_memset(ptr, 0, numberOfBytes);
let wasmMemory = new Uint8Array(this.memory, ptr, numberOfBytes);
wasmMemory.set(data);
return ptr;
}
/**
* Frees the memory at a given pointer.
* @param {Number} ptr The pointer to the WASM memory.
*/
freeMemory(ptr) {
this.wasm_free(ptr);
}
}
export default WASMJSAPI;