Journey to Vortex Shedding in the Browser: Part 1
What is this project and why am I starting it? An introduction to this blog series and a proof of concept for Golang and WebAssembly.
What is vortex shedding?
Vortex shedding looks like this:
In brief it's when a fluid travels at a specific speed around some body that causes vortices (the swirly bits) in this specific alternating pattern. It's a very pretty phenomena and tends to feature on "Welcome to Fluid Mechanics" lectures and other introductory material as it's a classic bit of fluid mechanics imagery.
And so what are you trying to accomplish?
I'd love to create a live fluid simulation displaying the vortex shedding phenomenon that runs live in the browser. I'm sure there are other cool phenemona to aim towards but this one is pretty and I think it'd be cool! It's also a great excuse to revive an old computational fluid dynamics (CFD) project I started a while back and to learn more about various algorithms and techniques. You can read here about the start of my interest in homemade CFD if you'd like.
On the technical side, my goal is to render and simulate everything fully on the browser. This is because I'm increasingly convinced that the internet is becoming a first-class platform for sharing and running applications and so building a web-first CFD code (albeit rather basic) would be a great proof of concept and an excuse to learn! What I mean by this is that generally, if one wanted to run some Serious Software TM (think Solidworks or Abaqus), one would have to install it onto their computer and run it there. However, now that computers are getting more powerful and with the advent of WebAssembly, the remit of web-based apps are expanding beyond just basic Create, Read, Update and Destroy (CRUD) apps that just move data around on a database. The first example that comes to mind is Onshape, which is a full-featured CAD program that runs entirely on the browser: This is what I mean by first-class app platform.
A proof of concept
I'm not sure who said it first but a common bit of advice one hears for software project is "Do the scary part first" and I find that so helpful. So much so I'll give it its own blockquote:
Do the scary part first.
What this means is it's helpful to tackle the unknown bit of the project first
before doing all the work that you already know how to do. For me I'm pretty
happy with Golang and I have a vague understanding of where I want to go for
the fluid mechanics parts, but rendering things in the browser with Golang was
fully unknown to me, and so my first job was to get something rendered.
Using my previous python project
(see here)
I quickly ported the code to Go and then set to work figuring out the rendering bit.
It turns out that it's really not so bad - there's a nice library for calling
javascript from Go and you have to learn how the html <canvas> element works
and that's about it.
Diffusion Simulation
Future work
My rough plan for getting to vortex shedding is as follows:
- Work out a proper architecture for the codebase that promotes extensibility
- Add treament of convection terms
- Implement a solving algorithm to replace Gauss-Seidel
- Design a robust geometry handling layer for different grids (eg unstructured)
- Implement the SIMPLE algorithm for momentum conservation and velocity solutions
- Add in geometries with boundaries inside the simulation
- Anything else needed for vortex shedding - eg. boundary layer considerations
- Vortex shedding!
One point I'd love to prove is that you can run sophisticated application code in a performant way in the browser using WASM and so throughout the project I'm keen to take the time to incrementally optimise things and ensure the simulation stays speedy. Obviously premature optimisation is a classic way to kneecap any progress but I think a wise cycle of getting a feature to work before thinking about low-hanging optimisations will be achievable. I'm particularly keen on this as in traditional web development the problems are never complex enough to have to think hard about data structures and algorithms so this is a good excuse to do some more sophisticated programming.
How can I do something similar myself?
To use Go with WASM it's really as simple as:
//go:build wasm
package main
import "syscall/js"
func main() {
canvasID := "canvas-id"
canvas := js.Global().Get("document").Call("getElementById", canvasID)
ctx := canvas.Call("getContext", "2d")
r.ctx.Set("fillStyle", "rgb(0,255,0)")
r.ctx.Call("fillRect", 0, 0, 20, 20)
// do anything you want really with the canvas
}
Use TinyGo to compile it into a really small binary (with a restriction on what libraries you can use) and then put the resulting files (a .wasm and .js) into your website assets. I made a little Makefile to do this automatically for me in development which works really well.
# Set this when manually building
VERSION ?= dev
TARGET_DIR = ../../assets/wasm/cfd-sim-$(VERSION)
.PHONY: build dev clean clean-all
build:
mkdir -p $(TARGET_DIR)
tinygo build -o $(TARGET_DIR)/main.wasm -target wasm .
cp $$(tinygo env TINYGOROOT)/targets/wasm_exec.js $(TARGET_DIR)/
dev:
$(MAKE) build VERSION=dev
find . -name "*.go" | entr -r $(MAKE) build VERSION=dev
clean:
rm -rf $(TARGET_DIR)
clean-all:
rm -rf ../../assets/wasm/cfd-sim-*
And then to import this into an html file I made the following little partial for my Jekyll site:
{% assign timestamp = site.time | date: '%s' %}
<script src="/assets/wasm/{{ include.project }}/wasm_exec.js?v={{ timestamp }}"></script>
<script>
const go = new Go();
WebAssembly.instantiateStreaming(fetch("/assets/wasm/{{ include.project }}/main.wasm"), go.importObject)
.then((result) => go.run(result.instance));
</script>
The timestamp variable is for cache busting, as it means the script src attribute will change every time I deploy the jekyll site, forcing the browser to fetch the latest version of the script. To use this on any page, I can just put:
<canvas id="canvas-id" width="800" height="600"><canvas>
{% include wasm-loader.html project="cfd-sim-dev" %}
That's a brief overview of how I'm doing WASM development for this project. So far with the jekyll livereloading and my file watching in the make dev command it's been really easy to develop and iterate!
References
-
Van Dyke, Milton. "Vortices", in An Album of Fluid Motion. Stanford, CA: Parabolic Press, 1982. pp 96. ↩