diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 97c9102..f4d2cbb 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -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: @@ -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 diff --git a/INSTALL.md b/INSTALL.md index 69a38cc..28d7175 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -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} diff --git a/README.md b/README.md index 62fbb19..d9f8da4 100644 --- a/README.md +++ b/README.md @@ -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} @@ -780,10 +780,11 @@ 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 <> createModule().then((module) => { - <> + <> + <> }); ``` @@ -791,8 +792,8 @@ The `module` variable contains the `NewtonRaphson` class we defined in the bindi 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 <> const epsilon = 0.001; const finder = new module.NewtonRaphson(epsilon); const guess = -20; @@ -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); @@ -817,7 +818,7 @@ To be able to use the `createModule` function, we will import the `newtonraphson ``` @@ -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 <> 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.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 <> +onmessage = function(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'); + +<> +``` + +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 <> +if (message.data.type === 'CALCULATE') { + createModule().then((module) => { + <> + <> + }); +} +``` + +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 <> +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 <> +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.onmessage = function(message) { + if (message.data.type === 'RESULT') { + const root = message.data.payload.root; + <> + } +} +``` + +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} + + + + + +``` + +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