-
+
Run benchmark with PSD file:
+
diff --git a/examples/benchmark/src/main.ts b/examples/benchmark/src/main.ts
index ea47f33..dc4408d 100644
--- a/examples/benchmark/src/main.ts
+++ b/examples/benchmark/src/main.ts
@@ -27,22 +27,51 @@ const benchmarkSetup: BenchmarkTaskSetup[] = [
let appState = initialAppState;
+async function runBenchmark() {
+ await raf(() => render(appState));
+
+ while (appState.isRunning()) {
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
+ await raf(() => {});
+ appState = await appState.runNextSubtask();
+ await raf(() => render(appState));
+ }
+}
+
// Initial render
render(appState);
initialize({
+ onUseDefaultPsdCheckboxClick() {
+ appState = appState.updateOptions({preferDefaultPsd: true});
+ render(appState);
+ },
+ onUseUploadedPsdCheckboxClick() {
+ appState = appState.updateOptions({preferDefaultPsd: false});
+ render(appState);
+ },
+
async onFileInputChange(file) {
if (!file) return;
try {
appState = appState.start(benchmarkSetup, file);
- await raf(() => render(appState));
+ runBenchmark();
+ } catch (error) {
+ appState = appState.setError(error);
+ render(appState);
+ }
+ },
+
+ async onUseDefaultPsdFileButtonClick() {
+ if (!appState.defaultPsdFileData) {
+ appState = appState.setError("Default PSD file not loaded yet");
+ render(appState);
+ return;
+ }
- while (appState.isRunning()) {
- // eslint-disable-next-line @typescript-eslint/no-empty-function
- await raf(() => {});
- appState = await appState.runNextSubtask();
- await raf(() => render(appState));
- }
+ try {
+ appState = appState.startWithDefaultPsdFile(benchmarkSetup);
+ runBenchmark();
} catch (error) {
appState = appState.setError(error);
render(appState);
@@ -60,6 +89,17 @@ initialize({
},
});
+// Load default PSD file
+(async () => {
+ try {
+ appState = await appState.loadDefaultPsdFile();
+ render(appState);
+ } catch (error) {
+ appState = appState.setError(error);
+ render(appState);
+ }
+})();
+
/**
* `window.requestAnimationFrame` promisified
* @returns
diff --git a/examples/benchmark/src/model.ts b/examples/benchmark/src/model.ts
index 0d5c92e..2283fa3 100644
--- a/examples/benchmark/src/model.ts
+++ b/examples/benchmark/src/model.ts
@@ -32,6 +32,8 @@ export class AppState {
readonly psdFileName: string | null;
readonly psdFileData: ArrayBuffer | null;
readonly error: string | null;
+ readonly defaultPsdFileUrl: URL;
+ readonly defaultPsdFileData: ArrayBuffer | null;
constructor({
tasks,
@@ -41,6 +43,8 @@ export class AppState {
psdFileData,
psdFileName,
error,
+ defaultPsdFileUrl,
+ defaultPsdFileData,
}: AppStateProperties) {
this.tasks = Object.freeze([...tasks]);
this.benchmarkResults = Object.freeze([...benchmarkResults]);
@@ -51,6 +55,8 @@ export class AppState {
this.psdFileName = psdFileName;
this.psdFileData = psdFileData;
this.error = error;
+ this.defaultPsdFileUrl = defaultPsdFileUrl;
+ this.defaultPsdFileData = defaultPsdFileData;
Object.freeze(this);
}
@@ -64,6 +70,8 @@ export class AppState {
psdFileData = this.psdFileData,
psdFileName = this.psdFileName,
error = this.error,
+ defaultPsdFileUrl = this.defaultPsdFileUrl,
+ defaultPsdFileData = this.defaultPsdFileData,
} = properties;
return new AppState({
tasks,
@@ -73,6 +81,8 @@ export class AppState {
psdFileData,
psdFileName,
error,
+ defaultPsdFileUrl,
+ defaultPsdFileData,
});
}
@@ -140,6 +150,28 @@ export class AppState {
});
}
+ startWithDefaultPsdFile(setups: readonly BenchmarkTaskSetup[]) {
+ if (!this.defaultPsdFileData) {
+ throw new Error("Default PSD file has not been loaded yet");
+ }
+
+ return this.#update({
+ tasks: setups.map(
+ (setup) =>
+ new BenchmarkTask({
+ libraryName: setup.libraryName,
+ benchmarkCallback: setup.benchmarkCallback,
+ remainingTrialCount: this.options.trialCount,
+ })
+ ),
+ currentTaskTrialMeasurements: [],
+ benchmarkResults: [],
+ psdFileName: this.defaultPsdFileUrl.pathname,
+ psdFileData: this.defaultPsdFileData,
+ error: null,
+ });
+ }
+
async runNextSubtask() {
if (!this.isRunning()) {
throw new Error("Cannot run next trial because the app has halted");
@@ -198,12 +230,14 @@ export class AppState {
const {
trialCount = this.options.trialCount,
shouldApplyOpacity = this.options.shouldApplyOpacity,
+ preferDefaultPsd = this.options.preferDefaultPsd,
} = newOptions;
return this.#update({
options: new BenchmarkOptions({
trialCount,
shouldApplyOpacity,
+ preferDefaultPsd,
}),
});
}
@@ -213,16 +247,32 @@ export class AppState {
error: error === null ? error : String(error),
});
}
+
+ async loadDefaultPsdFile() {
+ if (this.defaultPsdFileData) {
+ throw new Error(`${this.defaultPsdFileUrl} has already been loaded`);
+ }
+
+ // eslint-disable-next-line compat/compat
+ const response = await fetch(String(this.defaultPsdFileUrl));
+ return this.#update({
+ defaultPsdFileData: await response.arrayBuffer(),
+ });
+ }
}
export const initialAppState = new AppState({
tasks: [],
benchmarkResults: [],
currentTaskTrialMeasurements: [],
- options: {trialCount: 3, shouldApplyOpacity: false},
+ options: {trialCount: 3, shouldApplyOpacity: false, preferDefaultPsd: true},
psdFileName: null,
psdFileData: null,
error: null,
+ // Webpack will resolve this as a resource asset
+ // eslint-disable-next-line compat/compat
+ defaultPsdFileUrl: new URL("../../node/example.psd", import.meta.url),
+ defaultPsdFileData: null,
});
export type Task = LoadFileTask | BenchmarkTask;
@@ -365,8 +415,13 @@ export class BenchmarkOptions {
readonly trialCount: number;
/** Whether to apply opacity when decoding image data */
readonly shouldApplyOpacity: boolean;
+ readonly preferDefaultPsd: boolean;
- constructor({trialCount, shouldApplyOpacity}: BenchmarkOptionsProperties) {
+ constructor({
+ trialCount,
+ shouldApplyOpacity,
+ preferDefaultPsd,
+ }: BenchmarkOptionsProperties) {
if (!(Number.isSafeInteger(trialCount) && trialCount > 0)) {
throw new Error(
`trialCount must be a positive safe integer (got ${trialCount})`
@@ -375,6 +430,7 @@ export class BenchmarkOptions {
this.trialCount = trialCount;
this.shouldApplyOpacity = shouldApplyOpacity;
+ this.preferDefaultPsd = preferDefaultPsd;
Object.freeze(this);
}
diff --git a/examples/benchmark/src/style.css b/examples/benchmark/src/style.css
index 3c08261..d459648 100644
--- a/examples/benchmark/src/style.css
+++ b/examples/benchmark/src/style.css
@@ -65,6 +65,20 @@
font-size: 14px;
}
+.control-panel__psd-source-grid {
+ display: grid;
+ grid-template-columns: max-content 1fr;
+ gap: 16px 4px;
+}
+
+#use-sample-psd-button {
+ margin-left: 4px;
+}
+
+#file-input {
+ width: 160px;
+}
+
.progress-panel {
display: flex;
flex-direction: column;
diff --git a/examples/benchmark/src/views/control-panel.ts b/examples/benchmark/src/views/control-panel.ts
index 3b3ceac..478508c 100644
--- a/examples/benchmark/src/views/control-panel.ts
+++ b/examples/benchmark/src/views/control-panel.ts
@@ -6,14 +6,33 @@ import type {AppState} from "../model";
import {findElement} from "./common";
export function initControlPanel({
+ onUseDefaultPsdCheckboxClick,
+ onUseUploadedPsdCheckboxClick,
onFileInputChange,
+ onUseDefaultPsdFileButtonClick,
onTrialCountChange,
onShouldApplyOpacityChange,
}: {
+ onUseDefaultPsdCheckboxClick?: () => void;
+ onUseUploadedPsdCheckboxClick?: () => void;
onFileInputChange?: (file: File | null) => void;
+ onUseDefaultPsdFileButtonClick?: () => void;
onTrialCountChange?: (value: number) => void;
onShouldApplyOpacityChange?: (value: boolean) => void;
}) {
+ const {useDefaultPsdCheckbox, useUploadedPsdCheckbox} =
+ getPsdSourceCheckboxes();
+ useDefaultPsdCheckbox.addEventListener("click", () => {
+ onUseDefaultPsdCheckboxClick?.();
+ });
+ useUploadedPsdCheckbox.addEventListener("click", () => {
+ onUseUploadedPsdCheckboxClick?.();
+ });
+
+ getRunWithDefaultPsdButton().addEventListener("click", () =>
+ onUseDefaultPsdFileButtonClick?.()
+ );
+
getFileInput().addEventListener("change", (event) => {
const fileInput = event.target as ReturnType;
const file = fileInput.files?.[0] ?? null;
@@ -39,8 +58,32 @@ export function initControlPanel({
}
export function renderControlPanel(appState: AppState) {
+ const {useDefaultPsdCheckbox, useUploadedPsdCheckbox} =
+ getPsdSourceCheckboxes();
+ if (appState.options.preferDefaultPsd) {
+ useDefaultPsdCheckbox.checked = true;
+ } else {
+ useUploadedPsdCheckbox.checked = true;
+ }
+
+ useDefaultPsdCheckbox.disabled = appState.isRunning();
+ useUploadedPsdCheckbox.disabled = appState.isRunning();
+
+ const samplePsdDownloadAnchor = findElement(
+ "a#sample-psd-download-link",
+ "'Download sample PSD' anchor"
+ );
+ samplePsdDownloadAnchor.href = String(appState.defaultPsdFileUrl);
+
+ const isLoadingDefaultPsdFile = appState.defaultPsdFileData === null;
+ getRunWithDefaultPsdButton().disabled =
+ !appState.options.preferDefaultPsd ||
+ isLoadingDefaultPsdFile ||
+ appState.isRunning();
+
const fileInput = getFileInput();
- fileInput.disabled = appState.isRunning();
+ fileInput.disabled =
+ appState.options.preferDefaultPsd || appState.isRunning();
const trialCountInput = getTrialCountInput();
trialCountInput.valueAsNumber = appState.options.trialCount;
@@ -51,6 +94,26 @@ export function renderControlPanel(appState: AppState) {
applyOpacityCheckbox.disabled = appState.isRunning();
}
+function getPsdSourceCheckboxes() {
+ return {
+ useDefaultPsdCheckbox: findElement(
+ "input#use-sample-psd-checkbox",
+ "'Use sample PSD' checkbox"
+ ),
+ useUploadedPsdCheckbox: findElement(
+ "input#use-uploaded-psd-checkbox",
+ "'Use uploaded PSD' checkbox"
+ ),
+ };
+}
+
+function getRunWithDefaultPsdButton() {
+ return findElement(
+ "button#use-sample-psd-button",
+ "'Run with sample PSD' button element"
+ );
+}
+
function getFileInput() {
return findElement(
"input#file-input",