Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 0 additions & 8 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,6 @@ jobs:
with:
args: --preserve-tabs README.md INSTALL.md

# Should not be needed anymore when https://github.com/entangled/filters/issues/2 is fixed
- name: Correct Makefile
run: perl -pi -e 's/ /\t/' Makefile

- name: Run C++ examples
run: make test-cli test-cgi
python:
Expand All @@ -48,10 +44,6 @@ jobs:
with:
args: --preserve-tabs README.md INSTALL.md

# Should not be needed anymore when https://github.com/entangled/filters/issues/2 is fixed
- name: Correct Makefile
run: perl -pi -e 's/ /\t/' Makefile

- uses: actions/setup-python@v1
with:
python-version: '3.x' # Version range or exact version of a Python version to use, using SemVer's version range syntax
Expand Down
10 changes: 10 additions & 0 deletions INSTALL.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,16 @@ describe('src/js/example.html', () => {
});
```

```{.js file=cypress/integration/example-web-worker_spec.js}
// this JavaScript snippet is run by cypress and is stored as cypress/integration/example-web-worker_spec.js
describe('src/js/example-web-worker.html', () => {
it('should render -1.00', () => {
cy.visit('http://localhost:8000/src/js/example-web-worker.html');
cy.get('#answer').contains('-1.00');
});
});
```

The test can be run with

```{.awk #test-wasm}
Expand Down
121 changes: 103 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -769,7 +769,7 @@ EMSCRIPTEN_BINDINGS(newtonraphsonwasm) {
}
```

The algorithm and binding can be compiled into a Web Assembly module with the Emscripten compiler called `emcc`.
The algorithm and binding can be compiled into a WebAssembly module with the Emscripten compiler called `emcc`.
To make live easier we configure the compile command to generate a `src/js/newtonraphsonwasm.js` file which exports the `createModule` function.

```{.awk #build-wasm}
Expand All @@ -780,19 +780,20 @@ The compilation also generates a `src/js/newtonraphsonwasm.wasm` file which will

The WebAssembly module must be loaded and initialized by calling the `createModule` function and waiting for the JavaScript promise to resolve.

```{.js #wasmpromise}
// this Javascript snippet is later referred to as wasmpromise
```{.js #wasm-promise}
// this JavaScript snippet is later referred to as <<wasm-promise>>
createModule().then((module) => {
<<wasmcalc>>
<<wasm-calculate>>
<<render-answer>>
});
```

The `module` variable contains the `NewtonRaphson` class we defined in the binding above.

The root finder can be called with.

```{.js #wasmcalc}
// this Javascript snippet is later referred to as wasmcalc
```{.js #wasm-calculate}
// this JavaScript snippet is later referred to as <<wasm-calculate>>
const epsilon = 0.001;
const finder = new module.NewtonRaphson(epsilon);
const guess = -20;
Expand All @@ -801,7 +802,7 @@ const root = finder.find(guess);

Append the root answer to the html page using [document manipulation functions](https://developer.mozilla.org/en-US/docs/Web/API/ParentNode/append).

```{.js #wasmcalc}
```{.js #render-answer}
const answer = document.createElement('span');
answer.id = 'answer';
answer.append(root);
Expand All @@ -817,7 +818,7 @@ To be able to use the `createModule` function, we will import the `newtonraphson
<html>
<script type="text/javascript" src="newtonraphsonwasm.js"></script>
<script>
<<wasmpromise>>
<<wasm-promise>>
</script>
</html>
```
Expand All @@ -836,23 +837,107 @@ The result of root finding was calculated using the C++ algorithm compiled to a

Executing a long running C++ method will block the browser from running any other code like updating the user interface. In order to avoid this, the method can be run in the background using [web workers](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers). A web worker runs in its own thread and can be interacted with from JavaScript using messages.

Example of starting and interacting with a web worker
We need to instantiate a web worker which we will implement later in `src/js/worker.js`.

```js
```{.js #worker-consumer}
// this JavaScript snippet is later referred to as <<worker-consumer>>
const worker = new Worker('worker.js');
// Listen for messages from worker
worker.onmessage = (event) => {
console.log('Message received from worker');
console.log(event.data);
}
// Send message to worker
```

We need to send the worker a message with description for the work it should do.

```{.js #worker-consumer}
// this JavaScript snippet is appended to <<worker-consumer>>
worker.postMessage({
type: 'CALCULATE',
data: [{b: 3e-10, e: 1e+5}]
payload: { epsilon: 0.001, guess: -20 }
});
```

In the web worker we need to listen for incoming messages.

```{.js #worker-provider-onmessage}
// this JavaScript snippet is later referred to as <<worker-provider-onmessage>>
onmessage = function(message) {
<<handle-message>>
};
```

Before we can handle the message we need to import the WebAssembly module.

```{.js file=src/js/worker.js}
// this JavaScript snippet is stored as src/js/worker.js
importScripts('newtonraphsonwasm.js');

<<worker-provider-onmessage>>
```

We can handle the `CALCULATE` message only after the WebAssembly module is loaded and initialized.

```{.js #handle-message}
// this JavaScript snippet is before referred to as <<handle-message>>
if (message.data.type === 'CALCULATE') {
createModule().then((module) => {
<<perform-calc-in-worker>>
<<post-result>>
});
}
```

Let's calculate the result (root) based on the payload parameters in the incoming message.

```{.js #perform-calc-in-worker}
// this JavaScript snippet is before referred to as <<perform-calc-in-worker>>
const epsilon = message.data.payload.epsilon;
const finder = new module.NewtonRaphson(epsilon);
const guess = message.data.payload.guess;
const root = finder.find(guess);
```

And send the result back to the web worker consumer as a outgoing message.

```{.js #post-result}
// this JavaScript snippet is before referred to as <<post-result>>
postMessage({
type: 'RESULT',
payload: {
root: root
}
});
```

> TODO add worker.js content
Listen for messages from worker and when a result message is received put the result in the HTML page like we did before.

```{.js #worker-consumer}
// this JavaScript snippet is appended to <<worker-consumer>>
worker.onmessage = function(message) {
if (message.data.type === 'RESULT') {
const root = message.data.payload.root;
<<render-answer>>
}
}
```

Like before we need a HTML page to run the JavaScript, but now we don't need to import the `newtonraphsonwasm.js` file here as it is imported in the `worker.js` file.

```{.html file=src/js/example-web-worker.html}
<!doctype html>
<!-- this HTML page is stored as src/js/example-web-worker.html -->
<html>
<script>
<<worker-consumer>>
</script>
</html>
```

Like before we also need to host the files in a web server with

```shell
python3 -m http.server 8000
```

Visit [http://localhost:8000/src/js/example-web-worker.html](http://localhost:8000/src/js/example-web-worker.html) to see the result of the calculation.
The result of root finding was calculated using the C++ algorithm compiled to a WebAssembly module, imported in a web worker (separate thread), executed by JavaScript with messages to/from the web worker and rendered on a HTML page.

### Single page application

Expand Down