Creating Web Workers

Web Workers are a useful feature of the web platform that allows running a script in a separate thread. It can run in parallel to other code (making use of multi-core CPUs) and communicate via a messaging system. Web Workers are typically created using the Worker constructor, e.g. new Worker(url, opts).

This works normally when Construct is hosting the runtime in the DOM, which is the default when using JavaScript coding. However when hosting the runtime in a Web Worker (with the Use worker project property set to Yes), using web workers can become a bit more complicated. This is because some browsers have missing or buggy support for nested web workers (i.e. creating a web worker inside another web worker).

Construct provides the runtime createWorker(url, opts) method to help deal with creating a web worker when the runtime is also running in worker mode. Its parameters are the same as used by the Worker constructor: a URL of the script and options for the worker. However one difference is that the method is async, and so must be awaited with the await keyword.

This method works by doing the following:

  1. It creates a MessageChannel for directly communicating with the created worker.
  2. It sends a message to the DOM to create the Web Worker there. This avoids using a nested worker.
  3. It sends one MessageChannel port to the newly created worker, and returns the other port to from the call to createWorker.
  4. Then your code can directly communicate with the newly created worker via the MessageChannel ports.

However this means the Web Worker script must use a small amount of code to receive a MessageChannel port and switch to communicating with that instead of the usual messaging functions. Here is some sample code to demonstrate how it is used.

Runtime code

Use this code to create a worker in the runtime via the createWorker() method. The comments help explain how it is used.

async function CreateWorker(runtime)
{
	// Create the worker with the runtime.createWorker() method.
	// This must be awaited and resolves with a messagePort.
	const messagePort = await runtime.createWorker("myworker.js");
	
	// Add an onmessage handler to receive messages
	messagePort.onmessage = (e =>
	{
		// TODO: handle the message in e.data
	});
	
	// Now messages can be posted to the worker with:
	// messagePort.postMessage(...);
}

Worker code

Use this code in the worker script. It uses a small amount of code to receive a MessagePort from Construct and switch over to communicating with that.

// The MessagePort for communicating with the runtime
let messagePort = null;

// Receive the MessagePort from Construct
self.addEventListener("message", e =>
{
	if (e.data && e.data["type"] === "construct-worker-init")
	{
		messagePort = e.data["port2"];
		messagePort.onmessage = OnMessage;
		OnReady();
	}
});

function OnMessage(e)
{
	// TODO: handle the message in e.data
}

function OnReady()
{
	// Put any startup code in here.
	// Now messages can be posted to the worker with:
	// messagePort.postMessage(...);
}

Conclusion

Using the createWorker() method and a small amount of extra code in the worker script allows safely using web workers even when the runtime itself is hosted in a web worker, and the browser doesn't support nested workers.

Construct 3 Manual 2022-11-03