Remediate top level async calls. (#1375)

* Remediate top level async calls.

* Prefer null over undefined.
This commit is contained in:
Tony Thompson
2025-10-09 09:30:52 +13:00
committed by GitHub
parent 34da5f0629
commit 64c7d229bc
5 changed files with 124 additions and 34 deletions
+4 -1
View File
@@ -17,15 +17,18 @@ import {strict as assert} from 'assert';
import {afterEach, expect, suite, test} from 'vitest';
import {readMesh, setupIO} from '../lib/gltf-io';
import {getManifoldModule} from '../lib/wasm.js';
import type {ManifoldToplevel} from '../manifold.d.ts';
// @ts-ignore
import {examples} from './public/examples.js';
import {Mesh} from './public/manifold';
import {cleanup, evaluateCADToModel, module} from './worker';
import {cleanup, evaluateCADToModel} from './worker';
const io = setupIO(new WebIO());
async function runExample(name: string) {
const module: ManifoldToplevel = await getManifoldModule();
const code = examples.functionBodies.get(name);
const result = await evaluateCADToModel(code);
cleanup();
-1
View File
@@ -29,7 +29,6 @@ function log(...args: any[]) {
// Setup the evaluator and it's context.
const evaluator = new Evaluator();
export const module = evaluator.getModule(); // Used in tests.
// Exporters.
// The end user can download either.
+65 -31
View File
@@ -14,11 +14,8 @@
import {CrossSection, Manifold} from '../manifold-encapsulated-types';
import type {ManifoldToplevel} from '../manifold.d.ts';
import Module from '../manifold.js';
// Instantiate Manifold WASM
const manifoldwasm = await Module();
manifoldwasm.setup();
import {getManifoldModule} from './wasm.ts';
// manifold static methods (that return a new manifold)
const manifoldStaticFunctions = [
@@ -102,44 +99,34 @@ export class Evaluator {
afterScript: string =
'return typeof result === "undefined" ? undefined : result;';
protected module: ManifoldToplevel = manifoldwasm;
protected module: ManifoldToplevel|undefined;
protected memoryRegistry: Array<Manifold|CrossSection>;
protected moduleIntercepted: boolean = false;
/**
* Construct a new evaluator.
*
* @param module An optional Manifold module instance.
*/
constructor() {
constructor(module?: ManifoldToplevel) {
this.memoryRegistry = new Array<Manifold|CrossSection>();
this.addMembers('Manifold', manifoldMemberFunctions, false);
this.addMembers('Manifold', manifoldStaticFunctions, true);
this.addMembers('CrossSection', crossSectionMemberFunctions, false);
this.addMembers('CrossSection', crossSectionStaticFunctions, true);
if (module) {
this.module = module;
}
}
/**
* Intercept calls and add their results to our garbage collection
* list.
* Pass an existing, instantiated manifold module to this evaluator.
*
* @param className The class to intercept.
* @param methodNames An array of methods to intercept.
* @param areStatic Are these static methods? If so, intercept them at
* the prototype level.
* @param module The manifold module to use.
*/
protected addMembers(
className: string, methodNames: Array<string>, areStatic: boolean) {
//@ts-ignore
const cls = this.module[className];
const obj = areStatic ? cls : cls.prototype;
for (const name of methodNames) {
const originalFn = obj[name];
obj[name] = (...args: any) => {
//@ts-ignore
const result = originalFn(...args);
this.memoryRegistry.push(result);
return result;
};
setModule(module: ManifoldToplevel) {
if (this.memoryRegistry.length) {
throw new Error(
'Cannot replace manifold module while there are outstanding objects to clean up.');
}
this.module = module;
this.moduleIntercepted = false;
}
/**
@@ -191,6 +178,48 @@ export class Evaluator {
this.memoryRegistry.length = 0;
}
/**
* Set up garbage collection for a white listed set of methods belonging
* to the Manifold WASM module.
*/
protected setupGarbageCollection(): undefined {
if (this.moduleIntercepted) {
return;
}
this.addMembers('Manifold', manifoldMemberFunctions, false);
this.addMembers('Manifold', manifoldStaticFunctions, true);
this.addMembers('CrossSection', crossSectionMemberFunctions, false);
this.addMembers('CrossSection', crossSectionStaticFunctions, true);
this.moduleIntercepted = true;
}
/**
* Intercept calls and add their results to our garbage collection
* list.
*
* @param className The class to intercept.
* @param methodNames An array of methods to intercept.
* @param areStatic Are these static methods? If so, intercept them at
* the prototype level.
*/
protected addMembers(
className: string, methodNames: Array<string>, areStatic: boolean) {
//@ts-ignore
const cls = this.module[className];
const obj = areStatic ? cls : cls.prototype;
for (const name of methodNames) {
const originalFn = obj[name];
obj[name] = (...args: any) => {
//@ts-ignore
const result = originalFn(...args);
this.memoryRegistry.push(result);
return result;
};
}
}
/**
* Evaluate a string as javascript code creating a Manifold model.
*
@@ -204,6 +233,11 @@ export class Evaluator {
* behaviour.
*/
async evaluate(code: string): Promise<any> {
if (!this.module) {
this.module = await getManifoldModule();
}
this.setupGarbageCollection();
const exposedFunctions =
toplevel.map((name) => [name, (this.module as any)[name]]);
const context = {
@@ -226,6 +260,6 @@ export class Evaluator {
* collection will continue to be intercepted, even outside of the evaluator.
*/
getModule(): ManifoldToplevel {
return this.module;
return this.module!;
}
}
+52
View File
@@ -0,0 +1,52 @@
// Copyright 2025 The Manifold Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import type {ManifoldToplevel} from '../manifold.d.ts';
import Module from '../manifold.js';
// Instantiate Manifold WASM
let manifoldwasm: ManifoldToplevel|null = null;
let wasmUrl: string|null = null;
/**
* Tell us how to find `manifold.wasm`.
* This is important when using a bundler like WebPack.
*
* @param url Path to manifold.wasm
*/
export function setWasmUrl(url: string) {
wasmUrl = url;
}
/**
* Instantiate a new Manifold WASM instance.
*
* @returns The newly created instance.
*/
export async function instantiateManifold(): Promise<ManifoldToplevel> {
const options = wasmUrl ? {locateFile: () => wasmUrl!} : undefined;
const module = await Module(options);
module.setup();
return module;
}
/**
* Instantiate or get a global Manifold WASM instance.
*
* @returns A manifold instance.
*/
export async function getManifoldModule(): Promise<ManifoldToplevel> {
if (!manifoldwasm) manifoldwasm = await instantiateManifold();
return manifoldwasm;
}
+3 -1
View File
@@ -38,7 +38,9 @@
"lib/manifold-gltf.d.ts",
"lib/manifold-gltf.js",
"lib/material.d.ts",
"lib/material.js"
"lib/material.js",
"lib/wasm.js",
"lib/wasm.d.ts"
],
"typings": "manifold.d.ts",
"types": "manifold.d.ts",