The topic for this post relates to a project I’ve been working on for a few weeks, now. We’ve taken a complex set of spatial analyses - that use our favourite voxel-based architectural space analysis Dynamo package, VASA - and have translated them to work directly in the browser.
(This translation process isn’t really the subject of this series, but it’s pretty interesting, nonetheless, and it’s mostly what I was working on during the recent trip to Munich for the DevCon. Most of the Dynamo code was fairly easy to translate, although things like the automatic lacing of nodes meant throwing in some loops (or maps/reduces) from time to time. The trickiest area related to some hardcore Python scripts that were embedded in the graph: I know very little about Numpy, so had to get my head around some of those operations with the help of copious Print() statements - the easiest way to find out what’s happening in Python running inside Dynamo, as far as I can tell. Rhys Goldstein thankfully translated the first of the complex scripts from Python to JavaScript: once I’d tidied that up I could pass the original Python and the translated JavaScript to our corporate instance of ChatGPT to be used as a template for auto-translating the rest. With the exception of one (the most involved, admittedly) it did a very good job and certainly saved me loads of time. Without having a template to work with it had failed miserably, so this was quite an interesting takeaway for me.)
Anyway, so we now have this complex set of analyses translated from Dynamo (and Python) to work inside JavaScript with the VASA WebAssembly package. So far so good. The primary target for these analyses is Autodesk Forma, where we want to take building geometry as input to our analyses and display meaningful information to the user to help with their design work. (The feature to perform detailed design work isn’t yet part of Forma, so we’ve been working with geometry we’ve imported into Forma using Dynamo, which is enough for us to be able to test whether our analyses can be made to work there or not.)
When first implemented, the analyses would execute on the main thread. When the geometry was less complex, this wouldn’t be a problem, but for the larger/more intricate buildings this would lead to the dreaded “Page Unresponsive” warning:
JavaScript code runs by default on the browser’s main thread. Long operations will block it, leading to situations like the one shown above. The recommended way of addressing this is by using a web worker to execute the code in a background thread, freeing up the main thread to respond to UI events, etc.
Even asynchronous programming - using callbacks, Promises or the syntactic sugar of async/await - doesn’t use additional threads to execute code. This video - that was recently shared internally by Jørgen Aars Braseth from the Forma team - does a really good job of explaining how various async methods work. It’s well worth the watch.
So web workers are the way to address this situation, for sure. It wasn’t super complicated to set up. In our main JavaScript file, we instantiate the worker:
In our “compute-metrics-worker.js” file, a little more work is needed. As we’re loading a WASM package via its Emscripten-generated JS loader (this could also be loaded manually and passed as an argument to the web worker, but this seemed like too much effort) we need to wait until the VASA library is fully loaded before running our long-running operation and marshaling back the results.
The data that is sent across by the calling function is just the set of triangles extracted from the Forma geometry, which can then be voxelised and worked on in the worker. The results will be a set of numeric values that indicate how different locations in the building scores against our metrics.
I was initially concerned about the overhead of marshaling the data - the web worker approach just seemed like it was running quite a bit more slowly. I decided to run the two versions of the code - with the only difference being the use of the web worker, everything else was the same - and compare the results. I ran the code 3 times on the main thread and 3 times using a web worker for each of 20 different apartment models we’re using to test. Here are the results:
The average overhead was under 5%, which in most cases meant less than a second of additional time. Definitely acceptable considering the removal of the “Page Unresponsive” warning.
I’ve had some good news since the DevCon: I’ll be talking about topics such as this along with my Forma colleague Håvard Høiby at AU 2024 in San Diego. We’ll be talking through the basics of building extensions for Forma, as well as a more in-depth look at how Autodesk Research is using VASA to implement our own Forma extensions. It should be fun!