wasm/modules/modules.js

import { CUtils } from "./C/mods.js";
import { ASUtils } from "./assemblyScript/mods.js";

/**
 * @namespace WASMUtils
 */


/**
 * @memberof WASMUtils
 * @description object container of relative paths for the Web Assembly modules
 */
const availableScripts = {
  C: "../../wasm/modules/C",
  //assemblyScrpt utils
  AS: "../../wasm/modules/assemblyScript",
};

/**
 * @description Returns the location of a specified utility in a given script or module.
 * @memberof WASMUtils
 * @param {string} scName - The name of the script or module.
 * @param {string} utilName - The name of the utility.
 * @returns {string} - The location of the specified utility in the given script or module.
 */
const _location = (scriptName, utilName) => {
  const getModule = (utils, name) => {
    if (utils.hasOwnProperty(name)) {
      return utils[name];
    }
    throw new Error(
      `No utils found with the name '${name}' in the given module`
    );
  };

  switch (scriptName) {
    case "C":
      const cUtils = getModule(CUtils, utilName);
      return new URL(
        `${availableScripts.C}/${utilName}/${cUtils}`,
        import.meta.url
      );
    case "AS":
      const asUtils = getModule(ASUtils, utilName);
      return new URL(
        `${availableScripts.AS}/${utilName}/${asUtils}`,
        import.meta.url
      );
    default:
      throw new Error(`No script or module with the name '${scriptName}'`);
  }
};

/**
 * @description Load and instantiate a WebAssembly module from the ASUtils script directory.
 * @memberof WASMUtils
 * @async
 * @param {string} name - The name of the module to load.
 * @returns {Promise} - An object representing the exports of the instantiated module.
 * @throws {Error} - If the module cannot be loaded or instantiated.
 */
const ASModule = async (name) => {
  const memory = new WebAssembly.Memory({
    initial: 1,
    //maximum: 100,
    //shared: true,
  });
  try {
    // const module = await WebAssembly.instantiateStreaming(
    //   fetch(_location("AS", name)),
    //   {
    //     js: { mem: memory },
    //     env: {
    //       abort: (_msg, _file, line, column) =>
    //         console.error(`Abort at ${line}: ${column}`),
    //       memory: memory,
    //     },
    //   }
    // );

    const response = await fetch(_location("AS", name));
    const buffer = await response.arrayBuffer();
    const module = await WebAssembly.instantiate(buffer,     
        {
          js: { mem: memory },
          env: {
            abort: (_msg, _file, line, column) =>
              console.error(`Abort at ${line}: ${column}`),
            memory: memory,
          },
        })

    return module.instance.exports;
  } catch (error) {
    throw new Error(`Failed to load AS module '${name}'.`, error);
  }
};

/**
 * @description Asynchronously loads and creates a module from a C script.
 * @memberof WASMUtils
 * @param {string} moduleName - The name of the module to load.
 * @returns {Promise} A promise that resolves to the module.
 * @throws Will throw an error if there was an error loading the module.
 */
const CModule = async (modName) => {
  try {
    let { default: Module } = await import(_location("C", modName));
    return Module();
  } catch (error) {
    console.error(
      `There was an error pulling the following module: ${modName}`,
      error
    );
  }
};

/**
 * @description Utility class for dealing with AS compiled WASM.
 * @member AScriptUtils
 * @memberof WASMUtils
 * @property lifTypedArray
 */
class AScriptUtils {
  constructor() {
    this.dataview = undefined;
    this.memory = undefined;
    this.refCounts = new Map();
  }

  /**
   * Lifts a TypedArray from the module's memory.
   * @param {TypedArrayConstructor} constructor - The constructor of the TypedArray.
   * @param {number} pointer - The memory pointer to the TypedArray.
   * @param {object} module - The AScript module.
   * @returns {TypedArray|null} - The lifted TypedArray.
   */
  liftTypedArray(constructor, pointer, module) {
    if (!pointer) return null;
    return new constructor(
      module.memory.buffer,
      this.getU32(pointer + 4, module),
      this.dataview.getUint32(pointer + 8, true) / constructor.BYTES_PER_ELEMENT
    ).slice();
  }

  /**
   * Lowers a TypedArray to the module's memory.
   * @param {TypedArrayConstructor} constructor - The constructor of the TypedArray.
   * @param {number} id - The identifier of the TypedArray.
   * @param {number} align - The alignment of the TypedArray.
   * @param {TypedArray} values - The TypedArray to lower.
   * @param {object} module - The AScript module.
   * @returns {number} - The memory pointer to the lowered TypedArray.
   */
  lowerTypedArray(constructor, id, align, values, module) {
    if (values == null) return 0;

    const length = values.length,
      buffer = module.__pin(module.__new(length << align, 1)) >>> 0,
      header = module.__new(12, id) >>> 0;

    this.setU32(header + 0, buffer, module);
    this.dataview.setUint32(header + 4, buffer, true);
    this.dataview.setUint32(header + 8, length << align, true);
    new constructor(module.memory.buffer, buffer, length).set(values);
    module.__unpin(buffer);
    return header;
  }

  /**
   * Retains a memory pointer and increments its reference count.
   * @param {number} pointer - The memory pointer to retain.
   * @param {object} module - The AScript module.
   * @returns {number} - The retained memory pointer.
   */
  retainP(pointer, module) {
    if (pointer) {
      const refcount = this.refCounts.get(pointer);
      if (refcount) this.refCounts.set(pointer, refcount + 1);
      else this.refCounts.set(module.__pin(pointer), 1);
    }
    return pointer;
  }

  /**
   * Releases a memory pointer and decrements its reference count.
   * @param {number} pointer - The memory pointer to release.
   * @param {object} module - The AScript module.
   */
  releaseP(pointer, module) {
    if (pointer) {
      const refcount = this.refCounts.get(pointer);
      if (refcount === 1)
        module.__unpin(pointer), this.refCounts.delete(pointer);
      else if (refcount) this.refCounts.set(pointer, refcount - 1);
      else
        throw Error(
          `No refcounter "${refcount}" for the reference "${pointer}".`
        );
    }
  }

/**
 * Sets a 32-bit unsigned integer value at the specified memory pointer.
 * @param {number} pointer - The memory pointer.
 * @param {number} value - The value to set.
 * @param {object} module - The AScript module.
 */
  setU32(pointer, value, module) {
    try {
      this.dataview.setUint32(pointer, value, true);
    } catch {
      this.dataview = new DataView(module.memory.buffer);
      this.dataview.setInt32(pointer, value, true);
    }
  }

/**
 * Gets a 32-bit unsigned integer value from the specified memory pointer.
 * @param {number} pointer - The memory pointer.
 * @param {object} module - The AScript module.
 * @returns {number} - The 32-bit unsigned integer value.
 */
  getU32(pointer, module) {
    try {
      return this.dataview.getUint32(pointer, true);
    } catch {
      this.dataview = new DataView(module.memory.buffer);
      return this.dataview.getUint32(pointer, true);
    }
  }
}

/**
 * @description Loads a module based on the script name and module name.
 * @memberof WASMUtils
 * @param {string} scriptName - The script name.
 * @param {string} moduleName - The module name.
 * @returns {Promise<object>} - A promise that resolves to the loaded module.
 * @throws {NotFound} - If the module is not found in the available scripts.
 */
const loadModule = async (scriptName, moduleName) => {
  try {
    const myCurrentModule = await new Promise((resolve, reject) => {
      //Removes extension for each module
      resolve(
        scriptName === "AS"
          ? ASModule(moduleName.substring(0, moduleName.length - 5))
          : CModule(moduleName.substring(0, moduleName.length - 3))
      );
    });
    return myCurrentModule;
  } catch (e) {
    console.error(e);
    throw new NotFound(`Module not found in available scripts.`);
  }
};

/**
 * @description Retrieves all available modules.
 * @memberof WASMUtils
 * @returns {Promise<object>} - A promise that resolves to an object containing all the available modules.
 */
const getAllModules = async () => {
  let wasmMods = {};
  let availableMods;
  for (var sc of Object.keys(availableScripts)) {
    if (sc === "C") {
      availableMods = Object.values(CUtils).map((val) => val);
    } else if (sc === "AS") {
      availableMods = Object.values(ASUtils).map((val) => val);
    }
    wasmMods[sc] = {};
    for (var mod of availableMods) {
      let stgMod = await loadModule(sc, mod);
      wasmMods[sc][
        sc === "AS"
          ? mod.substring(0, mod.length - 5)
          : mod.substring(0, mod.length - 3)
      ] = stgMod;
    }
  }
  return wasmMods;
};

/**
 * @description Retrieves all available scripts and their modules.
 * @memberof WASMUtils
 * @returns {Promise<object>} - A promise that resolves to an object containing all the available scripts and their modules.
 */
const avScripts = async () => {
  const wasmMods = await getAllModules();
  const main = {};

  for (const scr of Object.keys(wasmMods)) {
    const modules = new Map();
    main[scr] = modules;
    for (const fn of Object.keys(wasmMods[scr])) {
      const fun = Array.from(filterFunctionKeys(wasmMods[scr][fn]));
      modules.set(fn, fun);
    }
  }

  return main;
};

/**
 * @description Filters the function keys of an object, excluding specific keys.
 * @memberof WASMUtils
 * @param {object} obj - The object to filter.
 * @returns {string[]} - An array of filtered function keys.
 */
function filterFunctionKeys(obj) {
  const excludeKeys = new Set([
    undefined,
    "memory",
    "__collect",
    "__new",
    "__pin",
    "__rtti_base",
    "__unpin",
    "__setArgumentsLength",
    "ready",
    "calledRun",
    "asm",
    "_createMem",
    "_destroy",
    "HEAP8",
    "HEAP16",
    "HEAP32",
    "HEAPU8",
    "HEAPU16",
    "HEAPU32",
    "HEAPF32",
    "HEAPF64",
  ]);
  return Object.keys(obj).filter((key) => !excludeKeys.has(key));
}

export {
  getAllModules,
  loadModule,
  AScriptUtils,
  ASModule,
  availableScripts,
  avScripts,
};