Shuffle files around

This commit is contained in:
ochafik 2023-03-26 15:57:09 +01:00
commit cfda844339
18 changed files with 704 additions and 738 deletions

115
src/runner/actions.ts Normal file
View file

@ -0,0 +1,115 @@
// Portions of this file are Copyright 2021 Google LLC, and licensed under GPL2+. See COPYING.
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api';
import { getParentDir } from '../fs/filesystem';
import { spawnOpenSCAD } from "./openscad-runner";
import { processMergedOutputs } from "./output-parser";
import { AbortablePromise, turnIntoDelayableExecution } from '../utils';
const syntaxDelay = 300;
type SyntaxCheckOutput = {logText: string, markers: monaco.editor.IMarkerData[]};
export const checkSyntax =
turnIntoDelayableExecution(syntaxDelay, (source: string, sourcePath: string) => {
// const timestamp = Date.now();
source = '$preview=true;\n' + source;
const job = spawnOpenSCAD({
workingDir: getParentDir(sourcePath),
inputs: [[sourcePath, source + '\n']],
args: [sourcePath, "-o", "out.ast"],
});
return AbortablePromise<SyntaxCheckOutput>((res, rej) => {
(async () => {
try {
const result = await job;
// console.log(result);
res(processMergedOutputs(result.mergedOutputs, {shiftSourceLines: {
[sourcePath]: 1,
[sourcePath.split('/').slice(-1)[0]]: 1
}}));
} catch (e) {
console.error(e);
rej(e);
}
})()
return () => job.kill();
});
});
var renderDelay = 1000;
export type RenderOutput = {stlFile: File, logText: string, markers: monaco.editor.IMarkerData[], elapsedMillis: number}
export type RenderArgs = {
source: string,
sourcePath: string,
features?: string[],
extraArgs?: string[],
isPreview: boolean
}
export const render =
turnIntoDelayableExecution(renderDelay, (params: RenderArgs) => {
const args = [
params.sourcePath,
"-o", "/home/out.stl",
"--export-format=binstl",
...(params.features ?? []).map(f => `--enable=${f}`),
...(params.extraArgs ?? [])
]
const prefixLines: string[] = [];
if (params.isPreview) {
prefixLines.push('$preview=true;');
}
const source = [...prefixLines, params.source].join('\n');
const job = spawnOpenSCAD({
// wasmMemory,
workingDir: getParentDir(params.sourcePath),
inputs: [[params.sourcePath, source]],
args,
outputPaths: ['/home/out.stl']
});
return AbortablePromise<RenderOutput>((resolve, reject) => {
(async () => {
try {
const result = await job;
console.log(result);
const {logText, markers} = processMergedOutputs(result.mergedOutputs, {
shiftSourceLines: {
[params.sourcePath]: prefixLines.length,
[params.sourcePath.split('/').slice(-1)[0]]: prefixLines.length
}
});
if (result.error) {
reject(result.error);
}
const [output] = result.outputs ?? [];
if (!output) {
reject(new Error('No output from runner!'));
return;
}
const [filePath, content] = output;
const filePathFragments = filePath.split('/');
const fileName = filePathFragments[filePathFragments.length - 1];
// TODO: have the runner accept and return files.
const blob = new Blob([content], { type: "application/octet-stream" });
// console.log(new TextDecoder().decode(content));
const stlFile = new File([blob], fileName);
resolve({stlFile, logText, markers, elapsedMillis: result.elapsedMillis});
} catch (e) {
console.error(e);
reject(e);
}
})();
return () => job.kill()
});
});

View file

@ -0,0 +1,65 @@
// Portions of this file are Copyright 2021 Google LLC, and licensed under GPL2+. See COPYING.
import { MergedOutputs } from "./openscad-worker";
import { AbortablePromise } from "../utils";
export function createWasmMemory({maximumMegabytes, maximumBytes}: {maximumMegabytes: number, maximumBytes: number}) {
const pageSize = 64 * 1024; // 64KB
if (!maximumBytes) {
maximumBytes = maximumMegabytes * 1024 * 1024;
}
return new WebAssembly.Memory({
initial: Math.floor(maximumBytes / 2 / pageSize),
maximum: Math.floor(maximumBytes / pageSize),
shared: true,
});
}
// Output is {outputs: [name, content][], mergedOutputs: [{(stderr|stdout|error)?: string}], exitCode: number}
export type OpenSCADInvocation = {
wasmMemory?: WebAssembly.Memory,
workingDir: string,
inputs?: [string, string][],
args: string[],
outputPaths?: string[],
}
export type OpenSCADInvocationResults = {
exitCode: number,
error?: string,
outputs?: [string, string][],
mergedOutputs: MergedOutputs,
elapsedMillis: number,
}
export function spawnOpenSCAD({inputs, args, outputPaths}: OpenSCADInvocation): AbortablePromise<OpenSCADInvocationResults> {
var worker: Worker | null;
var rejection: (err: any) => void;
function terminate() {
if (!worker) {
return;
}
worker.terminate();
worker = null;
}
return AbortablePromise<OpenSCADInvocationResults>((resolve, reject) => {
worker = new Worker('./openscad-worker.js');//, {type: "module"})
// if (navigator.userAgent.indexOf(' Chrome/') < 0) {
// worker = new Worker('./openscad-worker-firefox.js'); // {'type': 'module'}
// } else {
// worker = new Worker('./openscad-worker.js', {'type': 'module'});
// }
rejection = reject;
worker.onmessage = (e: {data: OpenSCADInvocationResults}) => {
resolve(e.data);
terminate();
}
worker.postMessage({inputs, args, outputPaths})
return () => {
// rejection({error: 'Terminated'});
terminate();
};
});
}

View file

@ -0,0 +1,101 @@
// Portions of this file are Copyright 2021 Google LLC, and licensed under GPL2+. See COPYING.
import OpenSCAD from "../wasm/openscad.js";
import { createEditorFS, getBrowserFSLibrariesMounts, getParentDir, symlinkLibraries } from "../fs/filesystem";
import { OpenSCADInvocation, OpenSCADInvocationResults } from "./openscad-runner";
import { zipArchives } from "../fs/zip-archives";
declare var BrowserFS: BrowserFSInterface
importScripts("browserfs.min.js");
// importScripts("https://cdnjs.cloudflare.com/ajax/libs/BrowserFS/2.0.0/browserfs.min.js");
const allArchiveNames = Object.keys(zipArchives)
const allZipMountsPromise = getBrowserFSLibrariesMounts(allArchiveNames);
export type MergedOutputs = {stdout?: string, stderr?: string, error?: string}[];
addEventListener('message', async (e) => {
const { inputs, args, outputPaths, wasmMemory, workingDir } = e.data as OpenSCADInvocation;
const mergedOutputs: MergedOutputs = [];
try {
const instance = await OpenSCAD({
wasmMemory,
buffer: wasmMemory && wasmMemory.buffer,
noInitialRun: true,
'print': (text: string) => {
console.debug('stdout: ' + text);
mergedOutputs.push({ stdout: text })
},
'printErr': (text: string) => {
console.debug('stderr: ' + text);
mergedOutputs.push({ stderr: text })
},
ENV: {
OPENSCADPATH: '/home'
}
});
const fs = await createEditorFS('/home');
// https://github.com/emscripten-core/emscripten/issues/10061
const BFS = new BrowserFS.EmscriptenFS(
instance.FS,
instance.PATH ?? {
join2: (a: string, b: string) => `${a}/${b}`,
join: (...args: string[]) => args.join('/'),
}, instance.ERRNO_CODES ?? {});
instance.FS.mount(BFS, {root: '/home'}, '/home');
// await symlinkLibraries(allArchiveNames, instance.FS, '/home/libraries', '/home');
instance.FS.chdir('/home');
if (inputs) {
for (const [path, content] of inputs) {
try {
// const parent = getParentDir(path);
// instance.FS.writeFile(path, content);
fs.writeFile(path, content);
} catch (e) {
console.error(`Error while trying to write ${path}`, e);
}
}
}
console.log('Calling main ', args)
const start = performance.now();
const exitCode = instance.callMain(args);
const end = performance.now();
const outputs: [string, string][] = [];
for (const path of (outputPaths ?? [])) {
try {
const content = instance.FS.readFile(path);
outputs.push([path, content]);
} catch (e) {
console.trace(`Failed to read output file ${path}`, e);
}
}
const result: OpenSCADInvocationResults = {
outputs,
mergedOutputs,
exitCode,
elapsedMillis: end - start
}
console.debug(result);
postMessage(result);
} catch (e) {
console.trace(e);//, e instanceof Error ? e.stack : '');
const error = `${e}`;
mergedOutputs.push({ error });
postMessage({
error,
mergedOutputs,
} as OpenSCADInvocationResults);
}
});

View file

@ -0,0 +1,88 @@
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api';
import { MergedOutputs } from "./openscad-worker";
const ignoredLogs = new Set([
'Could not initialize localization.'
]);
type MergedOutputsOptions = {
shiftSourceLines: {[path: string]: number}
}
export const processMergedOutputs = (outputs: MergedOutputs, opts: MergedOutputsOptions) => ({
logText: joinMergedOutputs(outputs, opts),
markers: parseMergedOutputs(outputs, opts)
});
export function joinMergedOutputs(mergedOutputs: MergedOutputs, opts: MergedOutputsOptions) {
let allLines = [];
for (const {stderr, stdout, error} of mergedOutputs){
const line = stderr ?? stdout ?? `EXCEPTION: ${error}`;
if (ignoredLogs.has(line)) {
continue;
}
allLines.push(line);
}
return allLines.join("\n");
}
export function parseMergedOutputs(mergedOutputs: MergedOutputs, opts: MergedOutputsOptions): monaco.editor.IMarkerData[] {
let unmatchedLines = [];
const markers = [];
let warningCount = 0, errorCount = 0;
const addError = (error: string, file: string, line: number) => {
markers.push({
startLineNumber: Number(line),
startColumn: 1,
endLineNumber: Number(line),
endColumn: 1000,
message: error,
severity: monaco.MarkerSeverity.Error
})
}
const getLine = (path: string, lineStr: string) => {
const shift = opts.shiftSourceLines[path] ?? 0;
return Number(lineStr) - shift;
}
for (const {stderr, stdout, error} of mergedOutputs){
if (stderr) {
if (stderr.startsWith('ERROR:')) errorCount++;
if (stderr.startsWith('WARNING:')) warningCount++;
let m = /^ERROR: Parser error in file "([^"]+)", line (\d+): (.*)$/.exec(stderr)
if (m) {
const [_, file, line, error] = m
addError(error, file, getLine(file, line));
continue;
}
m = /^ERROR: Parser error: (.*?) in file ([^",]+), line (\d+)$/.exec(stderr)
if (m) {
const [_, error, file, line] = m
addError(error, file, getLine(file, line));
continue;
}
m = /^WARNING: (.*?),? in file ([^,]+), line (\d+)\.?/.exec(stderr);
if (m) {
const [_, warning, file, line] = m
markers.push({
startLineNumber: getLine(file, line),
startColumn: 1,
endLineNumber: getLine(file, line),
endColumn: 1000,
message: warning,
severity: monaco.MarkerSeverity.Warning
})
continue;
}
}
unmatchedLines.push(stderr ?? stdout ?? `EXCEPTION: ${error}`);
}
if (errorCount || warningCount) unmatchedLines = [`${errorCount} errors, ${warningCount} warnings!`, '', ...unmatchedLines];
// logsElement.innerText = unmatchedLines.join("\n")
return markers;
}