8745a73c4f
The assignment crashes with 3.1.25 as instance.ENV is now a read-only property. This used to be assigned to an empty object, so it was useless anyway. Change-Id: I47d4cd831052de514c372adb3f2202d8a0a6e06f Reviewed-by: Morten Johan Sørvig <morten.sorvig@qt.io>
224 lines
6.8 KiB
JavaScript
224 lines
6.8 KiB
JavaScript
// Copyright (C) 2022 The Qt Company Ltd.
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
|
|
|
|
// Exposes platform capabilities as static properties
|
|
|
|
export class AbortedError extends Error {
|
|
constructor(stdout) {
|
|
super(`The program has been aborted`)
|
|
|
|
this.stdout = stdout;
|
|
}
|
|
}
|
|
export class Platform {
|
|
static #webAssemblySupported = typeof WebAssembly !== 'undefined';
|
|
|
|
static #canCompileStreaming = WebAssembly.compileStreaming !== 'undefined';
|
|
|
|
static #webGLSupported = (() => {
|
|
// We expect that WebGL is supported if WebAssembly is; however
|
|
// the GPU may be blacklisted.
|
|
try {
|
|
const canvas = document.createElement('canvas');
|
|
return !!(
|
|
window.WebGLRenderingContext &&
|
|
(canvas.getContext('webgl') || canvas.getContext('experimental-webgl'))
|
|
);
|
|
} catch (e) {
|
|
return false;
|
|
}
|
|
})();
|
|
|
|
static #canLoadQt = Platform.#webAssemblySupported && Platform.#webGLSupported;
|
|
|
|
static get webAssemblySupported() {
|
|
return this.#webAssemblySupported;
|
|
}
|
|
static get canCompileStreaming() {
|
|
return this.#canCompileStreaming;
|
|
}
|
|
static get webGLSupported() {
|
|
return this.#webGLSupported;
|
|
}
|
|
static get canLoadQt() {
|
|
return this.#canLoadQt;
|
|
}
|
|
}
|
|
|
|
// Locates a resource, based on its relative path
|
|
export class ResourceLocator {
|
|
#rootPath;
|
|
|
|
constructor(rootPath) {
|
|
this.#rootPath = rootPath;
|
|
if (rootPath.length > 0 && !rootPath.endsWith('/')) rootPath += '/';
|
|
}
|
|
|
|
locate(relativePath) {
|
|
return this.#rootPath + relativePath;
|
|
}
|
|
}
|
|
|
|
// Allows fetching of resources, such as text resources or wasm modules.
|
|
export class ResourceFetcher {
|
|
#locator;
|
|
|
|
constructor(locator) {
|
|
this.#locator = locator;
|
|
}
|
|
|
|
async fetchText(filePath) {
|
|
return (await this.#fetchRawResource(filePath)).text();
|
|
}
|
|
|
|
async fetchCompileWasm(filePath, onFetched) {
|
|
const fetchResponse = await this.#fetchRawResource(filePath);
|
|
onFetched?.();
|
|
|
|
if (Platform.canCompileStreaming) {
|
|
try {
|
|
return await WebAssembly.compileStreaming(fetchResponse);
|
|
} catch {
|
|
// NOOP - fallback to sequential fetching below
|
|
}
|
|
}
|
|
return WebAssembly.compile(await fetchResponse.arrayBuffer());
|
|
}
|
|
|
|
async #fetchRawResource(filePath) {
|
|
const response = await fetch(this.#locator.locate(filePath));
|
|
if (!response.ok)
|
|
throw new Error(
|
|
`${response.status} ${response.statusText} ${response.url}`
|
|
);
|
|
return response;
|
|
}
|
|
}
|
|
|
|
// Represents a WASM module, wrapping the instantiation and execution thereof.
|
|
export class CompiledModule {
|
|
#createQtAppInstanceFn;
|
|
#js;
|
|
#wasm;
|
|
#resourceLocator;
|
|
|
|
constructor(createQtAppInstanceFn, js, wasm, resourceLocator) {
|
|
this.#createQtAppInstanceFn = createQtAppInstanceFn;
|
|
this.#js = js;
|
|
this.#wasm = wasm;
|
|
this.#resourceLocator = resourceLocator;
|
|
}
|
|
|
|
static make(js, wasm, resourceLocator
|
|
) {
|
|
const exports = {};
|
|
eval(js);
|
|
if (!exports.createQtAppInstance) {
|
|
throw new Error(
|
|
'createQtAppInstance has not been exported by the main script'
|
|
);
|
|
}
|
|
|
|
return new CompiledModule(
|
|
exports.createQtAppInstance, js, wasm, resourceLocator
|
|
);
|
|
}
|
|
|
|
async exec(parameters) {
|
|
return await new Promise(async (resolve, reject) => {
|
|
let instance = undefined;
|
|
let result = undefined;
|
|
const continuation = () => {
|
|
if (!(instance && result))
|
|
return;
|
|
resolve({
|
|
stdout: result.stdout,
|
|
exitCode: result.exitCode,
|
|
instance,
|
|
});
|
|
};
|
|
|
|
instance = await this.#createQtAppInstanceFn((() => {
|
|
const params = this.#makeDefaultExecParams({
|
|
onInstantiationError: (error) => { reject(error); },
|
|
});
|
|
params.arguments = parameters?.args;
|
|
let data = '';
|
|
params.print = (out) => {
|
|
parameters?.onStdout?.(out);
|
|
data += `${out}\n`;
|
|
};
|
|
params.printErr = () => { };
|
|
params.onAbort = () => reject(new AbortedError(data));
|
|
params.quit = (code, exception) => {
|
|
if (exception && exception.name !== 'ExitStatus')
|
|
reject(exception);
|
|
result = { stdout: data, exitCode: code };
|
|
continuation();
|
|
};
|
|
return params;
|
|
})());
|
|
continuation();
|
|
});
|
|
}
|
|
|
|
#makeDefaultExecParams(params) {
|
|
const instanceParams = {};
|
|
instanceParams.instantiateWasm = async (imports, onDone) => {
|
|
try {
|
|
onDone(await WebAssembly.instantiate(this.#wasm, imports), this.#wasm);
|
|
} catch (e) {
|
|
params?.onInstantiationError?.(e);
|
|
}
|
|
};
|
|
instanceParams.locateFile = (filename) =>
|
|
this.#resourceLocator.locate(filename);
|
|
instanceParams.monitorRunDependencies = (name) => { };
|
|
instanceParams.print = (text) => true && console.log(text);
|
|
instanceParams.printErr = (text) => true && console.warn(text);
|
|
|
|
instanceParams.mainScriptUrlOrBlob = new Blob([this.#js], {
|
|
type: 'text/javascript',
|
|
});
|
|
return instanceParams;
|
|
}
|
|
}
|
|
|
|
// Streamlines loading of WASM modules.
|
|
export class ModuleLoader {
|
|
#fetcher;
|
|
#resourceLocator;
|
|
|
|
constructor(
|
|
fetcher,
|
|
resourceLocator
|
|
) {
|
|
this.#fetcher = fetcher;
|
|
this.#resourceLocator = resourceLocator;
|
|
}
|
|
|
|
// Loads an emscripten module named |moduleName| from the main resource path. Provides
|
|
// progress of 'downloading' and 'compiling' to the caller using the |onProgress| callback.
|
|
async loadEmscriptenModule(
|
|
moduleName, onProgress
|
|
) {
|
|
if (!Platform.webAssemblySupported)
|
|
throw new Error('Web assembly not supported');
|
|
if (!Platform.webGLSupported)
|
|
throw new Error('WebGL is not supported');
|
|
|
|
onProgress('downloading');
|
|
|
|
const jsLoadPromise = this.#fetcher.fetchText(`${moduleName}.js`);
|
|
const wasmLoadPromise = this.#fetcher.fetchCompileWasm(
|
|
`${moduleName}.wasm`,
|
|
() => {
|
|
onProgress('compiling');
|
|
}
|
|
);
|
|
|
|
const [js, wasm] = await Promise.all([jsLoadPromise, wasmLoadPromise]);
|
|
return CompiledModule.make(js, wasm, this.#resourceLocator);
|
|
}
|
|
}
|