Added framework for progress bars

This commit is contained in:
Lucas Dower 2022-09-16 18:51:22 +01:00
parent 8fb1804571
commit 5e7f10bae6
9 changed files with 153 additions and 6 deletions

View File

@ -7,6 +7,10 @@
</head>
<body>
<div class="progress-bar-container">
<div id="progress-bar" class="progress-bar" style="width:0%"></div>
</div>
<!-- The UI is dynamically built at runtime, see ./src/ui/layout.ts for details -->
<div class="column-properties" id="properties">
</div>

View File

@ -73,6 +73,7 @@ export class AppContext {
default: {
this._ui.enableTo(action + 1);
ASSERT(payload.action !== 'Progress');
const { builder, style } = this._getActionMessageBuilder(action, payload.statusMessages);
uiOutput.setMessage(builder, style as OutputStyle);

View File

@ -3,6 +3,9 @@ import { LOG } from './util/log_util';
/* eslint-disable */
export enum EAppEvent {
onTaskStart,
onTaskProgress,
onTaskEnd,
}
/* eslint-enable */
@ -18,7 +21,7 @@ export class EventManager {
this._eventsToListeners = new Map();
}
public add(event: EAppEvent, delegate: () => void) {
public add(event: EAppEvent, delegate: (...args: any[]) => void) {
if (!this._eventsToListeners.has(event)) {
this._eventsToListeners.set(event, []);
}

52
src/progress.ts Normal file
View File

@ -0,0 +1,52 @@
import { EAppEvent, EventManager } from './event';
import { ASSERT } from './util/error_util';
export class ProgressManager {
/* Singleton */
private static _instance: ProgressManager;
public static get Get() {
return this._instance || (this._instance = new this());
}
private _tasks: string[];
private constructor() {
this._tasks = [];
}
/**
* Start tracking the progress of a task.
* @param taskId The id of the task (created here).
* @param max The maximum number the task progress index can reach.
* For example, if you are iterating over an array of 1000 elements, this will be 1000.
* @param divisions The number of updates the manager should track.
* For example, 4 means an event will be emitted for 20%, 40%, 60%, 80% progress.
*/
public start(taskId: string) {
ASSERT(!this._tasks.includes(taskId), 'Task with that Id already being tracked');
this._tasks.push(taskId);
EventManager.Get.broadcast(EAppEvent.onTaskStart, taskId);
}
/**
* Announce progress has been made on a task.
* @param taskId The id of the task (created in `start`).
* @param progressIndex The index of the progress made so far.
* For example, if you are iteratinve over an array of 1000 elements, and are on index 230, this should be 230.
*/
public progress(taskId: string, percentage: number) {
EventManager.Get.broadcast(EAppEvent.onTaskProgress, taskId, percentage);
}
/**
* Announce a task has completed.
* @param taskId The id of the task (created in `start`).
*/
public end(taskId: string) {
const taskIndex = this._tasks.findIndex((task) => { return task === taskId });
ASSERT(taskIndex !== -1, 'Task with that Id is not being tracked');
this._tasks.splice(taskIndex, 1);
EventManager.Get.broadcast(EAppEvent.onTaskEnd, taskId);
}
}

View File

@ -1,4 +1,5 @@
import { Mesh } from '../mesh';
import { ProgressManager } from '../progress';
import { Axes, axesToDirection, Ray } from '../ray';
import { ASSERT } from '../util/error_util';
import { LOG } from '../util/log_util';
@ -19,12 +20,12 @@ export class BVHRayVoxeliser extends IVoxeliser {
const scale = (voxeliseParams.desiredHeight - 1) / Mesh.desiredHeight;
const offset = (voxeliseParams.desiredHeight % 2 === 0) ? new Vector3(0.0, 0.5, 0.0) : new Vector3(0.0, 0.0, 0.0);
const useMesh = mesh.copy(); // TODO: Voxelise without copying mesh, too expensive for dense meshes
useMesh.scaleMesh(scale);
useMesh.translateMesh(offset);
// Build BVH
const triangles = Array<{x: Number, y: Number, z: Number}[]>(useMesh._tris.length);
const triangles = Array<{ x: Number, y: Number, z: Number }[]>(useMesh._tris.length);
for (let triIndex = 0; triIndex < useMesh.getTriangleCount(); ++triIndex) {
const positionData = useMesh.getVertices(triIndex);
triangles[triIndex] = [positionData.v0, positionData.v1, positionData.v2];
@ -77,7 +78,15 @@ export class BVHRayVoxeliser extends IVoxeliser {
LOG('Rays created...');
// Ray test BVH
let nextPercentage = 0.0;
ProgressManager.Get.start('Voxelising');
for (rayIndex = 0; rayIndex < rays.length; ++rayIndex) {
const percentage = rayIndex / rays.length;
if (rayIndex / rays.length >= nextPercentage) {
ProgressManager.Get.progress('Voxelising', percentage);
nextPercentage += 0.1;
}
const ray = rays[rayIndex];
const intersections = bvh.intersectRay(ray.origin, axesToDirection(ray.axis), false);
for (const intersection of intersections) {
@ -98,6 +107,7 @@ export class BVHRayVoxeliser extends IVoxeliser {
}
}
}
ProgressManager.Get.end('Voxelising');
return voxelMesh;
}

View File

@ -1,15 +1,17 @@
import { Atlas } from './atlas';
import { BlockMesh } from './block_mesh';
import { BufferGenerator } from './buffer';
import { EAppEvent, EventManager } from './event';
import { IExporter } from './exporters/base_exporter';
import { ExporterFactory } from './exporters/exporters';
import { ObjImporter } from './importers/obj_importer';
import { Mesh } from './mesh';
import { ASSERT } from './util/error_util';
import { Logger } from './util/log_util';
import { VoxelMesh } from './voxel_mesh';
import { IVoxeliser } from './voxelisers/base-voxeliser';
import { VoxeliserFactory } from './voxelisers/voxelisers';
import { AssignParams, ExportParams, ImportParams, RenderBlockMeshParams, RenderMeshParams, RenderVoxelMeshParams, VoxeliseParams } from './worker_types';
import { AssignParams, ExportParams, ImportParams, RenderBlockMeshParams, RenderMeshParams, RenderVoxelMeshParams, TFromWorkerMessage, VoxeliseParams } from './worker_types';
export class WorkerClient {
private static _instance: WorkerClient;
@ -18,6 +20,43 @@ export class WorkerClient {
}
private constructor() {
Logger.Get.enableLOG();
Logger.Get.enableLOGMAJOR();
Logger.Get.enableLOGWARN();
EventManager.Get.add(EAppEvent.onTaskStart, (e: any) => {
const message: TFromWorkerMessage = {
action: 'Progress',
payload: {
type: 'Started',
taskId: e[0],
},
};
postMessage(message);
});
EventManager.Get.add(EAppEvent.onTaskProgress, (e: any) => {
const message: TFromWorkerMessage = {
action: 'Progress',
payload: {
type: 'Progress',
taskId: e[0],
percentage: e[1],
},
};
postMessage(message);
});
EventManager.Get.add(EAppEvent.onTaskEnd, (e: any) => {
const message: TFromWorkerMessage = {
action: 'Progress',
payload: {
type: 'Finished',
taskId: e[0],
},
};
postMessage(message);
});
}
private _loadedMesh?: Mesh;

View File

@ -37,8 +37,27 @@ export class WorkerController {
return this._jobPending !== undefined;
}
private _onWorkerMessage(payload: any) {
private _onWorkerMessage(payload: MessageEvent<TFromWorkerMessage>) {
ASSERT(this._jobPending !== undefined, `Received worker message when no job is pending`);
if (payload.data.action === 'Progress') {
const element = document.getElementById('progress-bar') as HTMLDivElement;
if (element) {
switch (payload.data.payload.type) {
case 'Started':
element.style.width = `0%`;
break;
case 'Progress':
element.style.width = `${payload.data.payload.percentage * 100}%`;
break;
case 'Finished':
element.style.width = `100%`;
break;
}
}
return;
}
TIME_END(this._jobPending.id);
LOG(`[WorkerController]: Job '${this._jobPending.id}' finished:`);
@ -54,7 +73,7 @@ export class WorkerController {
if (this.isBusy()) {
return;
}
this._jobPending = this._jobQueue.shift();
if (this._jobPending === undefined) {
return;

View File

@ -104,6 +104,11 @@ export type TStatus = {
statusMessages: StatusMessage[],
}
export type TaskParams =
| { type: 'Started', taskId: string }
| { type: 'Progress', taskId: string, percentage: number }
| { type: 'Finished', taskId: string }
export type TToWorkerMessage =
| { action: 'Import', params: ImportParams.Input }
| { action: 'RenderMesh', params: RenderMeshParams.Input }
@ -116,6 +121,7 @@ export type TToWorkerMessage =
export type TFromWorkerMessage =
| { action: 'KnownError', error: AppError }
| { action: 'UnknownError', error: Error }
| { action: 'Progress', payload: TaskParams }
| (TStatus & (
| { action: 'Import', result: ImportParams.Output }
| { action: 'RenderMesh', result: RenderMeshParams.Output }

View File

@ -575,4 +575,17 @@ svg {
100% {
opacity: 100%;
}
}
.progress-bar-container {
width: 100%;
height: 2px;
}
.progress-bar {
background-color: var(--prop-accent-standard);
height: 100%;
transition: width 0.1s;
}