Migrated exporting to worker thread

This commit is contained in:
Lucas Dower 2022-09-10 18:51:42 +01:00
parent 73f0c03c92
commit 3e559b3298
10 changed files with 91 additions and 67 deletions

View File

@ -14,6 +14,9 @@ import { AppConfig } from './config';
import { OutputStyle } from './ui/elements/output';
import { TextureFiltering } from './texture';
import { FallableBehaviour } from './block_mesh';
import { remote } from 'electron';
import { ExporterFactory, TExporters } from './exporters/exporters';
import { IExporter } from './exporters/base_exporter';
export class AppContext {
private _ui: UI;
@ -37,10 +40,15 @@ export class AppContext {
}
public do(action: EAction) {
this._ui.disable(action);
this._ui.cacheValues(action);
this._ui.disable(action);
const workerJob = this._getWorkerJob(action);
if (workerJob === undefined) {
this._ui.enable(action);
return;
}
const uiOutput = this._ui.getActionOutput(action);
const jobCallback = (payload: TFromWorkerMessage) => {
@ -92,14 +100,14 @@ export class AppContext {
builder.addItem('action', infoStatuses, 'success');
if (hasWarnings) {
builder.addHeading('action', 'There were some warnings:', 'warning');
builder.addHeading('action', 'There were some warnings', 'warning');
builder.addItem('action', warningStatuses, 'warning');
}
return { builder: builder, style: hasWarnings ? 'warning' : 'success' };
}
private _getWorkerJob(action: EAction): TWorkerJob {
private _getWorkerJob(action: EAction): (TWorkerJob | undefined) {
switch (action) {
case EAction.Import:
return this._import();
@ -107,6 +115,8 @@ export class AppContext {
return this._voxelise();
case EAction.Assign:
return this._assign();
case EAction.Export:
return this._export();
}
ASSERT(false);
}
@ -115,7 +125,7 @@ export class AppContext {
const uiElements = this._ui.layout.import.elements;
this._ui.getActionOutput(EAction.Import)
.setTaskInProgress('action', '[Voxel Mesh]: Loading...');
.setTaskInProgress('action', '[Importer]: Loading...');
const payload: TToWorkerMessage = {
action: 'Import',
@ -135,7 +145,7 @@ export class AppContext {
outputElement.setTaskInProgress('render', '[Renderer]: Processing...')
} else {
const message = `Will not render mesh as its over ${AppConfig.RENDER_TRIANGLE_THRESHOLD} triangles.`;
outputElement.setTaskComplete('render', '[Renderer]', [ message ], 'warning')
outputElement.setTaskComplete('render', '[Renderer]: Stopped.', [ message ], 'warning')
}
};
@ -324,52 +334,38 @@ export class AppContext {
return { id: 'RenderBlockMesh', payload: payload, callback: callback };
}
/*
private _assign() {
ASSERT(this._loadedVoxelMesh);
const uiElements = this._ui.layout.assign.elements;
const atlasId = uiElements.textureAtlas.getCachedValue();
const atlas = Atlas.load(atlasId);
ASSERT(atlas, 'Could not load atlas');
const paletteId = uiElements.blockPalette.getCachedValue();
const palette = Palette.load(paletteId);
ASSERT(palette);
const blockMeshParams: BlockMeshParams = {
textureAtlas: atlas,
blockPalette: palette,
blockAssigner: uiElements.dithering.getCachedValue(),
colourSpace: ColourSpace.RGB,
fallable: uiElements.fallable.getCachedValue() as FallableBehaviour,
};
this._loadedBlockMesh = BlockMesh.createFromVoxelMesh(this._loadedVoxelMesh, blockMeshParams);
Renderer.Get.useBlockMesh(this._loadedBlockMesh);
}
private _export() {
private _export(): (TWorkerJob | undefined) {
const exporterID: TExporters = this._ui.layout.export.elements.export.getCachedValue();
const exporter: IExporter = ExporterFactory.GetExporter(exporterID);
let filePath = remote.dialog.showSaveDialogSync({
let filepath = remote.dialog.showSaveDialogSync({
title: 'Save structure',
buttonLabel: 'Save',
filters: [exporter.getFormatFilter()],
});
ASSERT(this._loadedBlockMesh);
if (filePath) {
const fileExtension = '.' + exporter.getFileExtension();
if (!filePath.endsWith(fileExtension)) {
filePath += fileExtension;
}
exporter.export(this._loadedBlockMesh, filePath);
if (filepath === undefined) {
return undefined;
}
this._ui.getActionOutput(EAction.Export)
.setTaskInProgress('action', '[Exporter]: Saving...');
const payload: TToWorkerMessage = {
action: 'Export',
params: {
filepath: filepath,
exporter: exporterID,
}
};
const callback = (payload: TFromWorkerMessage) => {
// This callback is managed through `AppContext::do`, therefore
// this callback is only called if the job is successful.
};
return { id: 'Export', payload: payload, callback: callback };
}
*/
public draw() {
Renderer.Get.update();

View File

@ -1,6 +1,7 @@
import { Vector3 } from '../vector';
import { BlockMesh } from '../block_mesh';
import { TOptional } from '../util';
import { TBlockMeshBuffer } from '../buffer';
export abstract class IExporter {
protected _sizeVector!: Vector3;
@ -18,7 +19,7 @@ export abstract class IExporter {
return;
}
public abstract export(blockMesh: BlockMesh, filePath: string): boolean;
public abstract export(blockMesh: BlockMesh, filePath: string, blockMeshBuffer: TBlockMeshBuffer): boolean;
public getFormatFilter() {
return {

View File

@ -4,6 +4,7 @@ import { ASSERT } from '../util/error_util';
import fs from 'fs';
import path from 'path';
import { TBlockMeshBuffer } from '../buffer';
export class ObjExporter extends IExporter {
public override getFormatFilter(): Electron.FileFilter {
@ -21,7 +22,7 @@ export class ObjExporter extends IExporter {
return 'Wavefront OBJ';
}
public override export(blockMesh: BlockMesh, filepath: string) {
public override export(blockMesh: BlockMesh, filepath: string, blockMeshBuffer: TBlockMeshBuffer) {
ASSERT(path.isAbsolute(filepath));
const parsedPath = path.parse(filepath);
@ -29,15 +30,13 @@ export class ObjExporter extends IExporter {
const filepathMTL = path.join(parsedPath.dir, parsedPath.name + '.mtl');
const filepathTexture = path.join(parsedPath.dir, parsedPath.name + '.png');
this._exportOBJ(filepathOBJ, blockMesh, parsedPath.name + '.mtl');
this._exportOBJ(filepathOBJ, blockMesh, blockMeshBuffer, parsedPath.name + '.mtl');
this._exportMTL(filepathMTL, filepathTexture, blockMesh);
return true;
}
private _exportOBJ(filepath: string, blockMesh: BlockMesh, mtlRelativePath: string) {
/*
const buffer = blockMesh.createBuffer();
private _exportOBJ(filepath: string, blockMesh: BlockMesh, buffer: TBlockMeshBuffer, mtlRelativePath: string) {
const positionData = buffer.position.data as Float32Array;
const normalData = buffer.normal.data as Float32Array;
const texcoordData = buffer.texcoord.data as Float32Array;
@ -82,7 +81,6 @@ export class ObjExporter extends IExporter {
}
writeStream.end();
*/
}
private _exportMTL(filepathMTL: string, filepathTexture: string, blockMesh: BlockMesh) {

View File

@ -1,4 +1,5 @@
import { EAction } from './util';
import { ASSERT } from './util/error_util';
import { LOG, LOG_WARN } from './util/log_util';
export type StatusType = 'warning' | 'info';
@ -46,30 +47,30 @@ export class StatusHandler {
public getDefaultSuccessMessage(action: EAction): string {
switch (action) {
case EAction.Import:
return '[Mesh]: Loaded';
return '[Importer]: Loaded';
case EAction.Voxelise:
return 'Voxelised mesh';
return '[Voxeliser]: Succeeded';
case EAction.Assign:
return 'Assigned blocks';
return '[Assigner]: Succeeded';
case EAction.Export:
return 'Exported mesh';
return '[Exporter]: Saved';
default:
return 'Performed action';
ASSERT(false)
}
}
public getDefaultFailureMessage(action: EAction): string {
switch (action) {
case EAction.Import:
return 'Mesh: Failed';
return '[Importer]: Failed';
case EAction.Voxelise:
return 'Failed to voxelise mesh';
return '[Voxeliser]: Failed';
case EAction.Assign:
return 'Failed to assign blocks';
return '[Assigner]: Failed';
case EAction.Export:
return 'Failed to export mesh';
return '[Exporter]: Failed';
default:
return 'Failed to perform action';
ASSERT(false);
}
}
}

View File

@ -53,9 +53,6 @@ export class FileInputElement extends LabelledElement<string> {
const filePath = files[0];
this._loadedFilePath = filePath;
this._value = filePath;
} else {
this._loadedFilePath = '';
this._value = '';
}
const parsedPath = path.parse(this._loadedFilePath);
element.innerHTML = parsedPath.name + parsedPath.ext;

View File

@ -67,7 +67,7 @@ export class OutputElement {
const builder = this.getMessage().clear(taskId);
if (taskItems.length > 0) {
builder.addHeading(taskId, taskId + taskHeading, style);
builder.addHeading(taskId, taskHeading, style);
} else {
builder.addBold(taskId, [ taskHeading ], style);
}

View File

@ -44,6 +44,12 @@ export function doWork(message: TToWorkerMessage): TFromWorkerMessage {
result: WorkerClient.Get.renderBlockMesh(message.params),
statusMessages: StatusHandler.Get.getAllStatusMessages(),
};
case 'Export':
return {
action: 'Export',
result: WorkerClient.Get.export(message.params),
statusMessages: StatusHandler.Get.getAllStatusMessages(),
};
}
} catch (e: any) {
return { action: e instanceof AppError ? 'KnownError' : 'UnknownError', error: e as Error };

View File

@ -3,8 +3,8 @@ import { GeometryTemplates } from "./geometry";
import { ObjImporter } from "./importers/obj_importer";
import { MaterialType, Mesh, SolidMaterial, TexturedMaterial } from "./mesh";
import { ASSERT } from "./util/error_util";
import { AssignParams, ImportParams, RenderBlockMeshParams, RenderMeshParams, RenderVoxelMeshParams, VoxeliseParams } from "./worker_types";
import { BufferGenerator, TVoxelMeshBuffer } from "./buffer";
import { AssignParams, ExportParams, ImportParams, RenderBlockMeshParams, RenderMeshParams, RenderVoxelMeshParams, VoxeliseParams } from "./worker_types";
import { BufferGenerator, TBlockMeshBuffer, TVoxelMeshBuffer } from "./buffer";
import { TVoxelisers, VoxeliserFactory } from "./voxelisers/voxelisers";
import { param } from "jquery";
import { IVoxeliser } from "./voxelisers/base-voxeliser";
@ -12,6 +12,8 @@ import { TIME_END, TIME_START } from "./util/log_util";
import { VoxelMesh } from "./voxel_mesh";
import { BlockMesh } from "./block_mesh";
import { Atlas } from "./atlas";
import { ExporterFactory } from "./exporters/exporters";
import { IExporter } from "./exporters/base_exporter";
export class WorkerClient {
private static _instance: WorkerClient;
@ -24,6 +26,7 @@ export class WorkerClient {
private _loadedBlockMesh?: BlockMesh;
private _voxelMeshBuffer?: TVoxelMeshBuffer;
private _blockMeshBuffer?: TBlockMeshBuffer;
public import(params: ImportParams.Input): ImportParams.Output {
const importer = new ObjImporter();
@ -84,11 +87,29 @@ export class WorkerClient {
const atlas = Atlas.load(params.textureAtlas);
ASSERT(atlas !== undefined);
const buffer = BufferGenerator.fromBlockMesh(this._loadedBlockMesh, this._voxelMeshBuffer);
this._blockMeshBuffer = buffer.buffer;
return {
buffer: BufferGenerator.fromBlockMesh(this._loadedBlockMesh, this._voxelMeshBuffer),
buffer: buffer,
dimensions: this._loadedBlockMesh.getVoxelMesh().getBounds().getDimensions(),
atlasTexturePath: atlas.getAtlasTexturePath(),
atlasSize: atlas.getAtlasSize(),
};
}
public export(params: ExportParams.Input): ExportParams.Output {
ASSERT(this._loadedBlockMesh !== undefined);
ASSERT(this._blockMeshBuffer !== undefined);
const exporter: IExporter = ExporterFactory.GetExporter(params.exporter);
const fileExtension = '.' + exporter.getFileExtension();
if (!params.filepath.endsWith(fileExtension)) {
params.filepath += fileExtension;
}
exporter.export(this._loadedBlockMesh, params.filepath, this._blockMeshBuffer);
return {
}
}
}

View File

@ -1,5 +1,7 @@
const workerInstance = require('./worker');
addEventListener('message', (e) => {
postMessage(workerInstance.doWork(e.data));
setTimeout(() => {
postMessage(workerInstance.doWork(e.data));
}, 1000);
});

View File

@ -1,6 +1,7 @@
import { TBlockAssigners } from "./assigners/assigners"
import { FallableBehaviour } from "./block_mesh"
import { TBlockMeshBufferDescription, TMeshBufferDescription, TVoxelMeshBuffer, TVoxelMeshBufferDescription } from "./buffer"
import { TExporters } from "./exporters/exporters"
import { StatusMessage } from "./status"
import { TextureFiltering } from "./texture"
import { ColourSpace } from "./util"
@ -91,7 +92,8 @@ export namespace RenderBlockMeshParams {
export namespace ExportParams {
export type Input = {
filepath: string,
exporter: TExporters,
}
export type Output = {