/**
* WASM web worker for executing QubecTalk simulations using Java backend.
*
* This worker provides the same interface as legacy.worker.js but uses
* the Java-based QubecTalk interpreter compiled to WASM with JS fallback
* for legacy browser support.
*
* @license BSD, see LICENSE.md.
*/
let wasmLayer = null;
let isInitialized = false;
// Context variables for progress reporting
let currentRequestId = null;
let currentScenarioName = null;
/**
* Global reportProgress function called by WASM to report simulation progress.
* This function is available globally for the Java code to call via @JSBody.
*
* @param {number} progress - Progress value between 0.0 and 1.0
*/
function reportProgress(progress) {
self.postMessage({
resultType: "progress",
id: currentRequestId,
scenarioName: currentScenarioName,
progress: progress,
});
}
// Load WASM files with fallback to JS
async function initializeWasm() {
if (isInitialized) {
return;
}
try {
// Import TeaVM-compiled JavaScript version first (for fallback)
importScripts("/wasm/KigaliSim.js?v=EPOCH");
importScripts("/wasm/KigaliSim.wasm-runtime.js?v=EPOCH");
// Try to load WASM
wasmLayer = await TeaVM.wasmGC.load("/wasm/KigaliSim.wasm?v=EPOCH");
console.log("WASM backend initialized successfully");
} catch (error) {
console.log("Failed to load WASM, falling back to TeaVM JavaScript:", error);
// Fallback to TeaVM-compiled JavaScript implementation
wasmLayer = {
exports: {
execute: execute, // Use the TeaVM-compiled execute function
getVersion: getVersion, // Also available if needed
},
};
console.log("TeaVM JavaScript fallback initialized successfully");
}
isInitialized = true;
}
/**
* Execute QubecTalk code using WASM backend.
*
* @param {string} code - The QubecTalk code to execute.
* @param {string} scenarioName - Scenario name to execute, or empty string for all scenarios.
* @returns {Promise<string>} Promise resolving to status + CSV results.
*/
async function executeCode(code, scenarioName) {
try {
// Ensure WASM is initialized
await initializeWasm();
if (!wasmLayer) {
throw new Error("WASM layer not initialized");
}
// Set scenario name for progress reporting
currentScenarioName = scenarioName;
// Determine execution mode
const scenarioValid = scenarioName !== null && scenarioName !== undefined;
const executeSingleScenario = scenarioValid && scenarioName !== "";
let result;
if (executeSingleScenario) {
const execScenAvailLocal = typeof executeScenario === "function";
const execScenExported = wasmLayer.exports && wasmLayer.exports.executeScenario;
if (execScenAvailLocal) {
result = executeScenario(code, scenarioName);
} else if (execScenExported) {
result = wasmLayer.exports.executeScenario(code, scenarioName);
} else {
throw new Error("executeScenario function not found in WASM");
}
} else {
const execAvailLocal = typeof execute === "function";
const execExported = wasmLayer.exports && wasmLayer.exports.execute;
if (execAvailLocal) {
result = execute(code);
} else if (execExported) {
result = wasmLayer.exports.execute(code);
} else {
throw new Error("Execute function not found in WASM");
}
}
return result;
} catch (error) {
console.error("WASM execution error:", error);
return `Execution Error: ${error.message}\n\n`;
}
}
/**
* Handle messages from the main thread.
*/
self.onmessage = async function (event) {
const {id, command, code, scenarioName} = event.data;
// Set request ID for progress reporting
currentRequestId = id;
try {
if (command === "execute") {
const result = await executeCode(code, scenarioName || "");
self.postMessage({
resultType: "dataset",
id: id,
scenarioName: scenarioName, // Include scenario name in response
success: true,
result: result,
});
} else {
self.postMessage({
resultType: "dataset",
id: id,
success: false,
error: `Unknown command: ${command}`,
});
}
} catch (error) {
self.postMessage({
resultType: "dataset",
id: id,
success: false,
error: error.message,
});
}
};