Skip to main content

Module thread

Module thread 

Source
Expand description

Cooperative threads for games with their own loop (design/threads.md), the Rust side of the C SDK’s gasm_thread.h: the same scheduler, the same rules.

Every thread runs on the guest’s one wasm thread, until it blocks, yields or waits for the next frame; then the next ready thread runs, in creation order. A frame ends when no thread is ready. So there are no data races, and the schedule depends only on the input: runs stay reproducible on every runner.

Threads need threaded_main_loop! (and Binaryen’s wasm-opt --asyncify, as main_loop!); spawn panics elsewhere. The locks in crate::sync work everywhere.

ⓘ
fn run() -> i32 {
    let loader = gasm::thread::spawn(|| load_level("e1m1"));   // may wait for frames
    while !loader.is_finished() { draw_loading_screen(); gasm::main_loop::wait_frame(); }
    let level = loader.join();
    ...
}
gasm::threaded_main_loop!(run);

How it works: each thread is suspended with Asyncify into its own buffer and has its own shadow stack. Asyncify leaves the wasm stack pointer alone both ways (an unwind skips the functions’ epilogues, a rewind their prologues), so a suspended thread’s frames are everything above the stack pointer it had when it unwound: the scheduler records it then, sets it back before rewinding the thread, and runs its own calls on a stack of its own (gasm__get_sp/gasm__set_sp: sp/gasm_sp.s).

Differences from std::thread: closures needn’t be Send (it is one wasm thread), thread_local! values are shared by all threads, time is the frame’s (sleep ends at the first frame at or after its deadline), and a thread that spins without blocking or yielding keeps the frame from ending.

Structs§

Builder
Thread options (the stack size).
JoinHandle
The handle of a spawned thread: join waits for its result. Dropping it detaches the thread (it runs to the end on its own).
ThreadId
A thread’s number: main is 1, then in creation order.

Constants§

DEFAULT_STACK
A thread’s default stack (its locals, the shadow stack) and Asyncify buffer.
FOREVER
“No deadline” for block.

Functions§

available
Whether cooperative threads run (inside threaded_main_loop!).
block
Wait until wake is called on on or deadline_ms (crate::time_ms time, FOREVER: none) passes; true if it timed out. For building locks: on is any address that identifies what is waited for. Without the scheduler, a wait with a deadline times out at once and one without is a deadlock (trap).
current
The running thread’s id.
frames
Frames the threaded loop has run.
sleep
Wait at least d of frame time (other threads run meanwhile).
spawn
Start a thread running f (see Builder::spawn).
wait_frame
Wait for the next frame (the same as main_loop::wait_frame in a threaded loop).
wake
Make threads blocked on on ready (the first one in creation order, or all).
yield_now
Let the other ready threads run first.