Web Workers in Kotlin

Something I’ve known is useful for a web game is using Web Workers – the reason for this is that we need something to run in the background when the page isn’t active, and a page’s timer runs much more slowly when the page isn’t in focus (eg if you go to another tab)

Kotlin does contain a Web Worker wrapper so I looked into this next.

Using the Worker

This is sort of a bit backwards, but this is the easy bit really. The worker can simply be created with the Worker class, and then we attach an event listener for events from the worker.

val Worker = Worker("WitchWorker.js")
Worker.addEventListener("message", { println((it as MessageEvent).data) })
Worker.postMessage("send message " + CurrentPlayer.ticks)

This is pretty simple really – we should expect to load the worker file from the right place and we can send and receive messages from it.

There’s a real question still to be answered as to how we are going to use these messages, but the fundamentals are there.

Implementing the Worker

Now all we need is a WitchWorker javascript file. This I want to implement in Kotlin too. The theory here is that we’d have the actual game updating in the background on the worker, and the game will fetch the updates from it for the rendering.

The worker needs two things fundamentally which are the same as the other interface; a listener and some way to post the message. This took me a while to work out but this post helped. We need to use the DedicatedWorkerGlobalScope to get at the event listener and postMessage functions.

From there we get to a fairly simple Kotlin program:

external val self: DedicatedWorkerGlobalScope

fun main() {
    self.addEventListener("message", { onMessage(it as MessageEvent) })
}

fun onMessage(event: MessageEvent) {
    println(event.data)
    self.postMessage("received")
}

This isn’t the end though…and here was where the trouble started

Modules

The Worker class has a constructor that takes a url for a script, so we need Kotlin to compile the program into a separate javascript file. To do this we can put it in a separate module (there are other ways, but this was the simplest I could find at this point). This worked out to some extent, but there was a bit of a problem here. If we load the module using the worker, it immediately fails as the Kotlin module isn’t loaded.

I haven’t found a good solution to this yet. I don’t seem to be able to get the module file to load separately, and Javascript doesn’t appear to have a decent way to import other javascript files. The best solution I came up with was to manually merge the files (well merge them with a script). If we append the resulting Worker.js file to the lib/kotlin.js file we end up with a single js file that can work as the worker (note the kotlin has to be first).

Next Steps

I don’t think I’m 100% there with this yet – I’m not happy with the merging of the files and I’m sure there’s a better solution. But I do have a worker that runs in the background, and I ran a quick test to check that it does indeed run at a full rate while the browser is focused elsewhere.

The next steps here are to move most of the game update steps into the worker, and then make the main program focused only on the graphical interface. This will make the message passing a bit more complex than just simple strings, but it also solves some of the concurrency problems. In the main thread, we will request part of the game state from the worker thread to update the interface as needed; when a user clicks a button we’ll pass a message to the update thread to request the interaction (and get the results).

Usefully this also leads quite nicely into saving the game. If we need to be able to serialise the parts of the game state into a message, then we will also be able to use that same serialisation to be able to make a save game format, so hopefully that stage will be easier after I’ve finished writing the Web Worker.


Leave a Reply

Your email address will not be published. Required fields are marked *