rscad_csg_traits/stack.rs
1//! Big-stack execution for deep CSG recursion.
2
3/// Stack size for [`on_csg_stack`] workers. Reserved lazily (virtual pages),
4/// so only pages actually touched cost physical memory.
5#[cfg(not(target_arch = "wasm32"))]
6const CSG_STACK_SIZE: usize = 64 << 20;
7
8/// Run `f` on a dedicated thread with a stack large enough for deep CSG
9/// recursion, blocking until it finishes.
10///
11/// BSP build/clip/invert (and the tree's `Drop`) recurse per node, so stack
12/// depth scales with polygon count. Callers can sit on small stacks — the
13/// iOS main thread gets 1 MiB and secondary Apple threads 512 KiB — where a
14/// full-scene CSG conversion overflows (SIGSEGV). Wrap the conversion in
15/// this helper whenever the caller's stack size isn't under our control.
16pub fn on_csg_stack<R: Send>(f: impl FnOnce() -> R + Send) -> R {
17 // wasm32 is single-threaded: no worker thread to spawn, so run inline on
18 // the caller's stack. Deep BSP recursion can overflow the default wasm
19 // stack on complex scenes; bump it at link time with
20 // `-C link-arg=-zstack-size=<bytes>` if that becomes a problem.
21 #[cfg(target_arch = "wasm32")]
22 {
23 f()
24 }
25 #[cfg(not(target_arch = "wasm32"))]
26 {
27 std::thread::scope(|scope| {
28 let handle = std::thread::Builder::new()
29 .name("csg-worker".into())
30 .stack_size(CSG_STACK_SIZE)
31 .spawn_scoped(scope, f)
32 .expect("BUG: failed to spawn csg-worker thread");
33 match handle.join() {
34 Ok(value) => value,
35 Err(panic) => std::panic::resume_unwind(panic),
36 }
37 })
38 }
39}