Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,19 @@ mainthread.Call(func() {
```

However, be careful with `mainthread.CallNonBlock` when dealing with local variables.

## WebAssembly (`GOOS=js GOARCH=wasm`)

A parallel implementation ships under the `js && wasm` build tag. Go programs
compiled for the browser run as a single JavaScript event-loop thread, so
there is no separate main thread to queue onto — and blocking the event loop
deadlocks `requestAnimationFrame`. The WASM build therefore:

- runs `Run`, `Call`, `CallErr`, and `CallVal` inline on the calling goroutine,
- spawns a goroutine for `CallNonBlock` so the caller still returns without
waiting, and
- exports `CallQueueCap` only for source compatibility; it is unused.

Downstream libraries that need to differ for WASM (for example, to avoid using
finalizers that post to the queue) can gate on the same `js && wasm` build
tag.
2 changes: 2 additions & 0 deletions mainthread.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//go:build !js

package mainthread

import (
Expand Down
34 changes: 34 additions & 0 deletions mainthread_wasm.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
//go:build js && wasm

package mainthread

// Under WASM the entire program runs on the JS event-loop thread. Queuing onto
// a separate main thread would deadlock, so the blocking helpers (Run, Call,
// CallErr, CallVal) run the callback inline. CallNonBlock uses a goroutine to
// preserve its "return immediately" semantic.

// CallQueueCap exists for source compatibility with the desktop build; the
// WASM shim does not queue anything, so the value is unused.
var CallQueueCap = 16

// Run invokes the supplied function synchronously and returns when it exits.
// On WASM there is no separate main thread to pump, so Run is effectively a
// direct call.
func Run(run func()) { run() }

// Call runs f inline on the current goroutine. Equivalent to the desktop
// behavior of queueing onto the main thread and blocking until done, because
// under WASM the calling goroutine is already on the main (and only) thread.
func Call(f func()) { f() }

// CallNonBlock runs f on a new goroutine so the caller is not blocked. This
// matches the desktop semantic ("queue and return") as closely as possible
// under a single-threaded runtime; f will execute cooperatively once the
// caller yields to the scheduler.
func CallNonBlock(f func()) { go f() }

// CallErr runs f inline and returns its error.
func CallErr(f func() error) error { return f() }

// CallVal runs f inline and returns its value.
func CallVal[T any](f func() T) T { return f() }