Plotting Fractals in WebAssembly
Plotting Fractals in WebAssembly
Previous | Top | Next |
---|---|---|
6: Zooming In | 7: WebAssembly and Web Workers | |
7.3 Create the Web Worker | 7.4 Adapt the Main Thread Coding | |
7.4.2 Split WebAssembly Coding | 7.4.3 Create Web Workers | 7.4.4 Send/Receive Web Worker Messages |
7.4.3 Create Web Workers
The coding in the main thread is no longer responsible for directly calling the WebAssembly function to plot a fractal image. Instead, the main thread needs to implement coding that does the following:
- Creates as many Web Workers as required
- Attaches an
onmessage
event handler to each worker instance - Send a message to each worker instance whenever a fractal image needs to be plotted
- Listen for the worker’s completion message
- When all the workers have finished the current image calculation, the region of shared memory containing the image is transferred to the
canvas
element for display
Notice that the function below has been written as an event handler.
This means it accepts an argument evt
that, if present, is assumed to be an event object.
This is because not only is this function called directly from the start()
function (without being passed any arguments), but is it also attached to the input
event of the workers
slider UI element.
Therefore, when the slider is moved, this function will be called, but now an event object is passed.
Hence the test for if (evt)...
<GOTCHA>
You might think that if a slider range input
element can only have numeric values, then the evt.target.value
property ought to return a number…
Nope! evt.target.value
returns a string… 🤔
If you want the numeric representation of this value, then call evt.target.valueAsNumber
</GOTCHA>
The function below shows only the essential parts of the code. Other statements for adjusting the UI are present but have not been shown here:
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Build required number of worker threads
const rebuild_workers = evt => {
if (evt) {
WORKERS = evt.target.valueAsNumber
$id("workers_txt").innerHTML = WORKERS
}
// If the number of workers has changed, terminate any existing workers then creating new ones
if (worker_list.length !== WORKERS) {
worker_list.map(w => w.terminate() )
worker_list.length = 0
plot_time.start = window.performance.now()
// Create new set of workers
for (let i=0; i<WORKERS; i++) {
// snip UI stuff...
let w = new Worker('worker.js')
// Respond to messages received from the worker
w.onmessage = worker_msg_handler
// Initialise worker thread
w.postMessage(gen_worker_msg('init', i, 'mb', 0, 0, host_fns))
worker_list.push(w)
}
// snip more UI stuff...
}
}
After each worker instance is created, we do the following:
- Attach a message handler function in order to process inbound messages from that worker
- Send a message to the worker by calling its
postMessage
function. The actual message content is generated by thegen_worker_msg
helper function.
This helper function is responsible for creating a message object that will become the value of the data
property received by the worker thread.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Generate a worker message object
const gen_worker_msg = (p_action, p_worker_id, p_fractal, p_zx, p_zy, p_host_fns) =>
p_fractal === "mb"
? { action : p_action,
payload : {
host_fns : p_host_fns || {},
fractal : {
name : p_fractal,
width : CANVAS_WIDTH,
height : CANVAS_HEIGHT,
origin_x : X_ORIGIN,
origin_y : Y_ORIGIN,
zx : 0.0,
zy : 0.0,
ppu : PPU,
is_mandelbrot : true,
img_offset : mImageStart,
},
max_iters : MAX_ITERS,
worker_id : p_worker_id || 0,
},
}
: { action : p_action,
payload : {
host_fns : p_host_fns || {},
fractal : {
name : p_fractal,
width : CANVAS_WIDTH,
height : CANVAS_HEIGHT,
origin_x : 0.0,
origin_y : 0.0,
zx : p_zx,
zy : p_zy,
ppu : MIN_PPU,
is_mandelbrot : false,
img_offset : jImageStart,
},
max_iters : MAX_ITERS,
worker_id : p_worker_id || 0,
},
}