I am also working with long running computations that can block the UI thread, although in my case I am doing it purely in Elm.
The way I handle the long running process is to split it into smaller parts. So instead of running all 100000 as in your example, I might run 1000 batches of 100 steps each. At the end of each batch, I return control to Elm runtime (by returning from my update function). I think you could use a similar approach?
There are sopme things I have found with this approach which are worth sharing with you:
- Splitting into small batches causes a loss of efficiency. I don’t really want to run the Elm runtime loop during my calculation loop - I’d much rather have a CPU core run my calculation loop undisturbed.
- Making the batches bigger is more efficient, but makes the UI much less responsive. You would expect the UI to become more unresponsive as more CPU calculation work is done, however the responsiveness changes quite dramatically. My current thinking is that garbage collection pressure is really impacting the UI performance beyond some threshold.
- This is explicit time-slicing multi-tasking by the application code. Its complicated and not really code that you want to be writing.
I am thinking of trying to move the calculatiopn code into a separate Elm process running under a web worker thread, and having the main UI thread and the web worker communicate over ports. I have not tried this web worker thread approach yet, but I am hoping it will perform a lot better.
I am interested in what you write above. If you have code like this, what actually happens?
for (i = 0; i < 100000; i++) {
status = some_calculation(status);
if (debug_condition_met(status)){
elmApp.ports.calculationProgress.send(i);
}
}
Does the Elm runtime enqueue a message on the progress port, but the loop continues to run? And only once the calculation is complete does the Elm runtime become unblocked and able to process the messages?