Skip to content

Commit 24a73f1

Browse files
committed
Swtich from dilation webservice to web assembly
* Initialized wasm submodule * Add npm scripts and webpack config to compile wasm and include to assets * Update express to fix mime type issue with wasm * Update react to fix async act (see facebook/react#14853) * Changed error message component props for less tight coupling
1 parent dae8f64 commit 24a73f1

21 files changed

+1085
-289
lines changed

.eslintignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
dist/
2+
wasm/

.github/workflows/nodejs.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ jobs:
2121
run: |
2222
npm ci
2323
npm run build
24-
npm run build-server
24+
npm run build:server
2525
npm test
2626
env:
2727
CI: true

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
coverage/
22
dist/
3+
main.wasm

.gitmodules

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[submodule "n-dilation-webassembly"]
2+
path = n-dilation-webassembly
3+
url = [email protected]:acra5y/n-dilation-webassembly.git

app/components/WasmLoader.jsx

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { useEffect } from "react";
2+
3+
import { useWindowContext } from "./WindowContext";
4+
5+
const WasmLoader = () => {
6+
const window = useWindowContext();
7+
8+
useEffect(() => {
9+
if (!window.Go) {
10+
const script = document.createElement("script");
11+
script.onload = async function() {
12+
const go = new window.Go();
13+
const webAssembly = await window.WebAssembly.instantiateStreaming(
14+
window.fetch("/public/wasm/main.wasm"),
15+
go.importObject
16+
);
17+
go.run(webAssembly.instance); // populates window.UnitaryNDilation
18+
};
19+
script.src = "/public/wasm/wasm_exec.js";
20+
script.type = "text/javascript";
21+
22+
document.head.appendChild(script);
23+
}
24+
}, []);
25+
26+
return null;
27+
};
28+
29+
WasmLoader.displayName = "WasmLoader";
30+
31+
export default WasmLoader;

app/components/nDilation/Calculator.jsx

Lines changed: 74 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,105 @@
1-
import React, { useReducer } from "react";
1+
import React, { useReducer, useEffect, useRef } from "react";
22

3-
import fetchNDilation from "../../lib/fetchNDilation";
43
import { useWindowContext } from "../WindowContext";
54
import DilationForm from "./DilationForm";
65
import Result from "./Result";
6+
import WasmLoader from "../WasmLoader";
77

8-
const initialState = { isLoading: false, dilation: null, error: null };
8+
const initialState = {
9+
isLoading: false,
10+
dilation: null,
11+
validationError: false,
12+
runtimeError: false,
13+
ready: false,
14+
};
915

1016
function reducer(state, action) {
1117
switch (action.type) {
12-
case "FETCH_START":
13-
return { isLoading: true, dilation: null, error: null };
14-
case "FETCH_OK":
15-
return { isLoading: false, dilation: action.payload, error: null };
16-
case "FETCH_NOT_OK":
17-
return { isLoading: false, dilation: null, error: action.payload };
18-
case "FETCH_ERROR":
19-
return { isLoading: false, dilation: null, error: action.payload };
18+
case "WASM_INITIALIZED":
19+
return { ...state, ready: true };
20+
case "CALCULATE_START":
21+
return {
22+
...state,
23+
isLoading: true,
24+
dilation: null,
25+
runtimeError: false,
26+
validationError: false,
27+
};
28+
case "CALCULATE_OK":
29+
return {
30+
...state,
31+
isLoading: false,
32+
dilation: action.payload,
33+
error: null,
34+
};
35+
case "VALIDATION_ERROR":
36+
return {
37+
...state,
38+
isLoading: false,
39+
dilation: null,
40+
runtimeError: false,
41+
validationError: true,
42+
};
43+
case "CALCULATE_ERROR":
44+
return {
45+
...state,
46+
ready: false,
47+
isLoading: false,
48+
dilation: null,
49+
runtimeError: true,
50+
validationError: false,
51+
};
2052
default:
2153
return state;
2254
}
2355
}
2456

25-
const createOnSubmitHandler = (fetch, dispatch) => async (matrix, degree) => {
57+
const createOnSubmitHandler = (unitaryNDilationAsync, dispatch) => async (
58+
matrix,
59+
degree
60+
) => {
2661
try {
27-
dispatch({ type: "FETCH_START" });
28-
const response = await fetchNDilation(fetch, matrix, degree);
29-
30-
const body = await response.json();
62+
dispatch({ type: "CALCULATE_START" });
63+
const { error, value } = await unitaryNDilationAsync(matrix, degree);
3164

32-
if (response.ok) {
33-
dispatch({ type: "FETCH_OK", payload: body.value });
65+
if (!error) {
66+
dispatch({ type: "CALCULATE_OK", payload: value });
3467
} else {
35-
dispatch({ type: "FETCH_NOT_OK", payload: body });
68+
dispatch({ type: "VALIDATION_ERROR", payload: error });
3669
}
3770
} catch (e) {
38-
dispatch({ type: "FETCH_ERROR", payload: e });
71+
dispatch({ type: "CALCULATE_ERROR", payload: e });
3972
}
4073
};
4174

4275
const Calculator = () => {
43-
const [{ isLoading, dilation, error }, dispatch] = useReducer(
44-
reducer,
45-
initialState
46-
);
76+
const [
77+
{ isLoading, dilation, validationError, runtimeError },
78+
dispatch,
79+
] = useReducer(reducer, initialState);
80+
const onSubmitHandler = useRef(null);
4781
const window = useWindowContext();
4882

83+
useEffect(() => {
84+
if (!onSubmitHandler.current) {
85+
const unitaryNDilationAsync = (matrix, degree) =>
86+
Promise.resolve(window.UnitaryNDilation(matrix, degree));
87+
onSubmitHandler.current = createOnSubmitHandler(
88+
unitaryNDilationAsync,
89+
dispatch
90+
);
91+
dispatch({ type: "WASM_INITIALIZED" });
92+
}
93+
}, [window && window.UnitaryNDilation]);
94+
4995
return (
5096
<div>
51-
<DilationForm
52-
onSubmit={
53-
window && createOnSubmitHandler(window.fetch, dispatch)
54-
}
55-
/>
97+
<WasmLoader />
98+
<DilationForm onSubmit={onSubmitHandler.current} />
5699
<Result
57100
isLoading={isLoading}
58-
errorDetails={error}
101+
validationError={validationError}
102+
runtimeError={runtimeError}
59103
dilation={dilation}
60104
/>
61105
</div>

app/components/nDilation/ErrorMessage.jsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
import React from "react";
22

3-
function ErrorMessage({ errorDetails }) {
4-
if (!errorDetails.validationError) {
5-
return <div>Ooops, something went terribly wrong 🤯</div>;
3+
function ErrorMessage({ validationError }) {
4+
if (!validationError) {
5+
return (
6+
<div>
7+
Ooops, something went terribly wrong 🤯. You will probably have
8+
to reload the page now.
9+
</div>
10+
);
611
}
712

813
return (

app/components/nDilation/Result.jsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ const AnimatedContentTransition = styled.div`
1515
export const Result = ({
1616
isLoading,
1717
dilation,
18-
errorDetails,
18+
validationError,
19+
runtimeError,
1920
opacity,
2021
animationTimeInSeconds,
2122
}) => {
@@ -29,9 +30,10 @@ export const Result = ({
2930
) : (
3031
<>
3132
{dilation && <Matrix matrixInRowMajorOrder={dilation} />}
32-
{errorDetails && (
33-
<ErrorMessage errorDetails={errorDetails} />
34-
)}
33+
{validationError ||
34+
(runtimeError && (
35+
<ErrorMessage validationError={validationError} />
36+
))}
3537
</>
3638
)}
3739
</AnimatedContentTransition>

0 commit comments

Comments
 (0)