@bitbybit-dev/occt-worker
Version:
Bit By Bit Developers CAD algorithms using OpenCascade Technology kernel adapted for WebWorker
182 lines (181 loc) • 6.94 kB
JavaScript
import { ShapesHelperService, VectorHelperService, OccHelper, OCCTService } from "@bitbybit-dev/occt";
import { CacheHelper } from "./cache-helper";
import { WorkerMessages, NON_CACHEABLE_FUNCTIONS } from "./constants";
import { ShapeResolver, ResultSerializer, FunctionPathResolver } from "./shape-resolver";
import { getCommandHandler } from "./command-handlers";
// Module-level state
let openCascade;
let cacheHelper;
let shapeResolver;
let resultSerializer;
let functionPathResolver;
/**
* Pending dependencies that need to be added to plugins once OpenCascade is initialized.
* This handles the case where addOc is called before full initialization.
*/
const pendingDependencies = {};
/**
* Initializes the OpenCascade worker with the given module and plugins.
*
* @param occ - The BitbybitOcctModule instance
* @param plugins - Optional plugins to add to the OpenCascade service (e.g., AdvancedOCCT)
* @param doNotPost - If true, skip posting the initialization message (used for testing)
* @returns The CacheHelper instance for testing purposes
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const initializationComplete = (occ, plugins, doNotPost) => {
// Initialize cache helper
cacheHelper = new CacheHelper(occ);
// Initialize helper services
const vecService = new VectorHelperService();
const shapesService = new ShapesHelperService();
// Initialize OpenCascade service
openCascade = new OCCTService(occ, new OccHelper(vecService, shapesService, occ));
// Initialize resolver utilities
shapeResolver = new ShapeResolver(cacheHelper);
resultSerializer = new ResultSerializer(cacheHelper);
functionPathResolver = new FunctionPathResolver();
// Set up plugins if provided
if (plugins) {
openCascade.plugins = plugins;
// Add any pending dependencies that were registered before initialization
Object.entries(pendingDependencies).forEach(([key, value]) => {
openCascade.plugins.dependencies[key] = value;
});
}
// Notify that initialization is complete
if (!doNotPost) {
postMessage(WorkerMessages.INITIALIZED);
}
return cacheHelper;
};
/**
* Creates the command context for command handlers.
*/
function createCommandContext() {
return {
openCascade,
cacheHelper,
shapeResolver,
addPendingDependency: (key, value) => {
pendingDependencies[key] = value;
},
};
}
/**
* Executes a standard (cacheable) OCCT function.
*
* This handles the common flow:
* 1. Recursively resolve shape references in inputs
* 2. Execute the function with caching
* 3. Serialize the result for transmission
*/
function executeStandardFunction(action) {
// Recursively resolve all shape references in inputs
const resolvedInputs = shapeResolver.resolveShapeReferences(action.inputs);
// Execute with caching - the cache helper will return cached result if available
const res = cacheHelper.cacheOp(action, () => {
return functionPathResolver.callFunction(openCascade, action.functionName, resolvedInputs);
});
// Serialize the result for transmission back to main thread
return resultSerializer.serializeResult(res);
}
/**
* Formats error information for transmission.
*/
function formatError(error, action) {
// Extract meaningful error message
let errorMessage;
if (error instanceof Error) {
errorMessage = error.stack || `${error.name}: ${error.message}`;
}
else if (typeof error === "string") {
errorMessage = error;
}
else {
try {
errorMessage = JSON.stringify(error);
}
catch (_a) {
errorMessage = String(error);
}
}
let props = "";
if (action === null || action === void 0 ? void 0 : action.inputs) {
const inputDetails = Object.entries(action.inputs)
.map(([key, value]) => {
// Don't stringify large binary data — it can crash V8
if (ArrayBuffer.isView(value)) {
return `${key}: [${value.constructor.name} length=${value.byteLength}]`;
}
if (value instanceof ArrayBuffer) {
return `${key}: [ArrayBuffer byteLength=${value.byteLength}]`;
}
try {
const str = JSON.stringify(value);
// Truncate very long values to avoid bloating the error message
return str.length > 200
? `${key}: ${str.slice(0, 200)}…(truncated)`
: `${key}: ${str}`;
}
catch (_a) {
return `${key}: [unserializable]`;
}
})
.join(", ");
props = ` Input values were: {${inputDetails}}.`;
}
const funcName = (action === null || action === void 0 ? void 0 : action.functionName) ? ` while executing function '${action.functionName}'` : "";
return `OCCT computation failed${funcName}: ${errorMessage}.${props}`;
}
/**
* Main message handler for the OCCT worker.
*
* Processes incoming messages from the main thread, executes the requested
* OCCT operations, and sends results back.
*
* @param d - The data input containing the action to perform
* @param postMessage - Function to send messages back to the main thread
*/
export const onMessageInput = (d, postMessage) => {
// Notify that processing has started
postMessage(WorkerMessages.BUSY);
let result;
try {
const { functionName, inputs } = d.action;
// Check if this is a reserved function with special handling
const commandHandler = getCommandHandler(functionName);
if (commandHandler) {
// Execute special command handler
const commandResult = commandHandler(inputs, createCommandContext());
result = commandResult.result;
}
else if (!NON_CACHEABLE_FUNCTIONS.has(functionName)) {
// Execute standard cacheable function
result = executeStandardFunction(d.action);
}
// Send successful response
postMessage({
uid: d.uid,
result,
});
}
catch (e) {
// Send error response — wrapped in its own try/catch to guarantee we always reply
try {
postMessage({
uid: d.uid,
result: undefined,
error: formatError(e, d.action),
});
}
catch (fmtErr) {
// formatError itself failed (should not happen, but be defensive)
postMessage({
uid: d.uid,
result: undefined,
error: `OCCT computation failed: ${e instanceof Error ? e.message : String(e)} (additionally, error formatting failed: ${fmtErr instanceof Error ? fmtErr.message : String(fmtErr)})`,
});
}
}
};