Skip to content

Commit

Permalink
feat(benchmark): provide sample PSD file w/ download link
Browse files Browse the repository at this point in the history
  • Loading branch information
pastelmind committed Mar 28, 2022
1 parent b15f89e commit d907f05
Show file tree
Hide file tree
Showing 5 changed files with 212 additions and 15 deletions.
34 changes: 29 additions & 5 deletions examples/benchmark/public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,35 @@ <h1>
</p>
</header>
<div class="app__section control-panel">
<div>
<label for="file-input">Select a PSD file: </label>
</div>
<div>
<input id="file-input" type="file" accept=".psd,.psb" />
<div>Run benchmark with PSD file:</div>
<div class="control-panel__psd-source-grid">
<div>
<input
type="radio"
name="choose-file-source"
id="use-sample-psd-checkbox"
checked
/>
</div>
<div>
<label for="use-sample-psd-checkbox">
Use <a id="sample-psd-download-link">sample PSD file</a>
</label>
<button type="button" id="use-sample-psd-button">
Run benchmark
</button>
</div>
<div>
<input
type="radio"
name="choose-file-source"
id="use-uploaded-psd-checkbox"
/>
</div>
<div>
<label for="use-uploaded-psd-checkbox">Open PSD:</label>
<input id="file-input" type="file" accept=".psd,.psb" disabled />
</div>
</div>
<div>
<label for="repeat-count-input"># of times to parse the file:</label>
Expand Down
54 changes: 47 additions & 7 deletions examples/benchmark/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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
Expand Down
60 changes: 58 additions & 2 deletions examples/benchmark/src/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -41,6 +43,8 @@ export class AppState {
psdFileData,
psdFileName,
error,
defaultPsdFileUrl,
defaultPsdFileData,
}: AppStateProperties) {
this.tasks = Object.freeze([...tasks]);
this.benchmarkResults = Object.freeze([...benchmarkResults]);
Expand All @@ -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);
}
Expand All @@ -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,
Expand All @@ -73,6 +81,8 @@ export class AppState {
psdFileData,
psdFileName,
error,
defaultPsdFileUrl,
defaultPsdFileData,
});
}

Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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,
}),
});
}
Expand All @@ -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;
Expand Down Expand Up @@ -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})`
Expand All @@ -375,6 +430,7 @@ export class BenchmarkOptions {

this.trialCount = trialCount;
this.shouldApplyOpacity = shouldApplyOpacity;
this.preferDefaultPsd = preferDefaultPsd;

Object.freeze(this);
}
Expand Down
14 changes: 14 additions & 0 deletions examples/benchmark/src/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
65 changes: 64 additions & 1 deletion examples/benchmark/src/views/control-panel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<typeof getFileInput>;
const file = fileInput.files?.[0] ?? null;
Expand All @@ -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<HTMLAnchorElement>(
"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;
Expand All @@ -51,6 +94,26 @@ export function renderControlPanel(appState: AppState) {
applyOpacityCheckbox.disabled = appState.isRunning();
}

function getPsdSourceCheckboxes() {
return {
useDefaultPsdCheckbox: findElement<HTMLInputElement>(
"input#use-sample-psd-checkbox",
"'Use sample PSD' checkbox"
),
useUploadedPsdCheckbox: findElement<HTMLInputElement>(
"input#use-uploaded-psd-checkbox",
"'Use uploaded PSD' checkbox"
),
};
}

function getRunWithDefaultPsdButton() {
return findElement<HTMLButtonElement>(
"button#use-sample-psd-button",
"'Run with sample PSD' button element"
);
}

function getFileInput() {
return findElement<HTMLInputElement>(
"input#file-input",
Expand Down

0 comments on commit d907f05

Please sign in to comment.