@needle-tools/engine
Version:
Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development with great integrations into editors like Unity or Blender - and can be deployed onto any device! It is flexible, extensible and networking and XR are built-in.
216 lines • 10.5 kB
JavaScript
import { getParam } from "./engine_utils.js";
const showProgressLogs = getParam("debugprogress");
/** Gets the date formatted as 20240220-161993. When no Date is passed in, the current local date is used. */
export function getFormattedDate(date) {
date = date || new Date();
const month = date.getMonth() + 1;
const day = date.getDate();
const hour = date.getHours();
const min = date.getMinutes();
const sec = date.getSeconds();
const s_month = (month < 10 ? "0" : "") + month;
const s_day = (day < 10 ? "0" : "") + day;
const s_hour = (hour < 10 ? "0" : "") + hour;
const s_min = (min < 10 ? "0" : "") + min;
const s_sec = (sec < 10 ? "0" : "") + sec;
return date.getFullYear() + s_month + s_day + "-" + s_hour + s_min + s_sec;
}
/** Progress reporting utility.
* See `Progress.start` for usage examples.
*/
export class Progress {
/** Start a new progress reporting scope. Make sure to close it with Progress.end.
* @param scope The scope to start progress reporting for.
* @param options Parent scope, onProgress callback and logging. If only a string is provided, it's used as parentScope.
* @example
* // Manual usage:
* Progress.start("export-usdz", undefined, (progress) => console.log("Progress: " + progress));
* Progress.report("export-usdz", { message: "Exporting object 1", currentStep: 1, totalSteps: 3 });
* Progress.report("export-usdz", { message: "Exporting object 2", currentStep: 2, totalSteps: 3 });
* Progress.report("export-usdz", { message: "Exporting object 3", currentStep: 3, totalSteps: 3 });
*
* // Auto step usage:
* Progress.start("export-usdz", undefined, (progress) => console.log("Progress: " + progress));
* Progress.report("export-usdz", { message: "Exporting objects", autoStep: true, totalSteps: 3 });
* Progress.report("export-usdz", "Exporting object 1");
* Progress.report("export-usdz", "Exporting object 2");
* Progress.report("export-usdz", "Exporting object 3");
* Progress.end("export-usdz");
*
* // Auto step with weights:
* Progress.start("export-usdz", undefined, (progress) => console.log("Progress: " + progress));
* Progress.report("export-usdz", { message: "Exporting objects", autoStep: true, totalSteps: 10 });
* Progress.report("export-usdz", { message: "Exporting object 1", autoStep: 8 }); // will advance to 80% progress
* Progress.report("export-usdz", "Exporting object 2"); // 90%
* Progress.report("export-usdz", "Exporting object 3"); // 100%
*
* // Child scopes:
* Progress.start("export-usdz", undefined, (progress) => console.log("Progress: " + progress));
* Progress.report("export-usdz", { message: "Overall export", autoStep: true, totalSteps: 2 });
* Progress.start("export-usdz-objects", "export-usdz");
* Progress.report("export-usdz-objects", { message: "Exporting objects", autoStep: true, totalSteps: 3 });
* Progress.report("export-usdz-objects", "Exporting object 1");
* Progress.report("export-usdz-objects", "Exporting object 2");
* Progress.report("export-usdz-objects", "Exporting object 3");
* Progress.end("export-usdz-objects");
* Progress.report("export-usdz", "Exporting materials");
* Progress.end("export-usdz");
*
* // Enable console logging:
* Progress.start("export-usdz", { logTimings: true });
*/
static start(scope, options) {
if (typeof options === "string")
options = { parentScope: options };
const p = new ProgressEntry(scope, options);
progressCache.set(scope, p);
}
/** Report progress for a formerly started scope.
* @param scope The scope to report progress for.
* @param options Options for the progress report. If a string is passed, it will be used as the message.
* @example
* // auto step and show a message
* Progress.report("export-usdz", "Exporting object 1");
* // same as above
* Progress.report("export-usdz", { message: "Exporting object 1", autoStep: true });
* // show the current step and total steps and implicitly calculate progress as 10%
* Progress.report("export-usdz", { currentStep: 1, totalSteps: 10 });
* // enable auto step mode, following calls that have autoStep true will increase currentStep automatically.
* Progress.report("export-usdz", { totalSteps: 20, autoStep: true });
* // show the progress as 50%
* Progress.report("export-usdz", { progress: 0.5 });
* // give this step a weight of 20, which changes how progress is calculated. Useful for steps that take longer and/or have child scopes.
* Progress.report("export-usdz", { message. "Long process", autoStep: 20 });
* // show the current step and total steps and implicitly calculate progress as 10%
* Progress.report("export-usdz", { currentStep: 1, totalSteps: 10 });
*/
static report(scope, options) {
const p = progressCache.get(scope);
if (!p) {
console.warn("Reporting progress for non-existing scope", scope);
return;
}
if (typeof options === "string")
options = { message: options, autoStep: true };
p.report(options);
}
/** End a formerly started scope. This will also report the progress as 100%.
* @remarks Will warn if any child scope is still running (progress < 1).
*/
static end(scope) {
const p = progressCache.get(scope);
if (!p)
return;
p.end();
progressCache.delete(scope);
}
}
const progressCache = new Map();
/** Internal class that handles Progress instances and their parent/child relationship. */
class ProgressEntry {
scopeLabel;
parentScope;
childScopes = [];
parentDepth = 0;
lastStep = 0;
lastAutoStepWeight = 1;
lastTotalSteps = 0;
onProgress;
showLogs = false;
selfProgress = 0;
totalProgress = 0;
selfReports = 0;
totalReports = 0;
constructor(scope, options) {
this.parentScope = options?.parentScope ? progressCache.get(options.parentScope) : undefined;
if (this.parentScope) {
this.parentScope.childScopes.push(this);
this.parentDepth = this.parentScope.parentDepth + 1;
}
this.scopeLabel = " ".repeat(this.parentDepth * 2) + scope;
this.showLogs = options?.logTimings ?? !!showProgressLogs;
if (this.showLogs)
console.time(this.scopeLabel);
this.onProgress = options?.onProgress;
}
report(options, indirect = false) {
if (options) {
if (options.totalSteps !== undefined)
this.lastTotalSteps = options.totalSteps;
if (options.currentStep !== undefined)
this.lastStep = options.currentStep;
if (options.autoStep !== undefined) {
if (options.currentStep === undefined) {
if (this.lastStep === undefined)
this.lastStep = 0;
const stepIncrease = typeof options.autoStep === "number" ? options.autoStep : 1;
this.lastStep += this.lastAutoStepWeight;
this.lastAutoStepWeight = stepIncrease;
options.currentStep = this.lastStep;
}
options.totalSteps = this.lastTotalSteps;
}
if (options.progress !== undefined)
this.selfProgress = options.progress;
else if (options.currentStep !== undefined && options.totalSteps !== undefined) {
this.selfProgress = options.currentStep / options.totalSteps;
}
}
if (this.childScopes.length > 0) {
let avgChildProgress = 0;
let sumChildWeight = 0;
for (const c of this.childScopes) {
avgChildProgress += c.selfProgress;
sumChildWeight += 1;
}
if (sumChildWeight > 0)
avgChildProgress /= sumChildWeight;
const stepWeight = this.lastAutoStepWeight / (this.lastTotalSteps ?? 1);
// not entirely sure about this formula – idea is that a step should be weighted by the progress of the children
this.totalProgress = this.selfProgress + avgChildProgress * stepWeight;
}
else {
this.totalProgress = this.selfProgress;
}
// sanitize values
this.selfProgress = Math.min(1, this.selfProgress);
this.totalProgress = Math.min(1, this.totalProgress);
let msg = (this.totalProgress * 100).toFixed(3) + "%";
if (this.childScopes.length > 0)
msg += " (" + (this.selfProgress * 100).toFixed(3) + "% self)";
if (options?.message)
msg = options.message + " – " + msg;
if (this.lastStep !== undefined && this.lastTotalSteps !== undefined)
msg = "Step " + (this.lastStep + (this.lastAutoStepWeight != 1 ? "–" + (this.lastStep + this.lastAutoStepWeight) : "") + "/" + this.lastTotalSteps) + " " + msg;
if (indirect)
this.totalReports++;
else {
this.selfReports++;
this.totalReports++;
}
if (this.showLogs)
console.timeLog(this.scopeLabel, msg);
if (this.onProgress)
this.onProgress(this.totalProgress);
if (this.parentScope)
this.parentScope.report(undefined, true);
}
end() {
this.report({ progress: 1, autoStep: true }, true);
if (this.showLogs) {
console.timeLog(this.scopeLabel, "Total reports: " + this.totalReports, "Self reports: " + this.selfReports);
console.timeEnd(this.scopeLabel);
}
let anyRunningChildProgress = false;
for (const c of this.childScopes) {
if (c.selfProgress >= 1)
continue;
anyRunningChildProgress = true;
break;
}
if (anyRunningChildProgress)
console.warn("Progress end with child scopes that are still running", this);
this.onProgress = undefined;
}
}
//# sourceMappingURL=engine_time_utils.js.map