--- a/third-party/include/ThreadPool.h +++ b/third-party/include/ThreadPool.h @@ -110,9 +110,20 @@ : pool_size(threads) , in_flight(0) { +#ifndef __EMSCRIPTEN__ const std::unique_lock lock(this->queue_mutex); for (std::size_t i = 0; i != threads; ++i) start_worker(i, lock); +#else + // vpinball-wasm: a single-threaded Emscripten build (no -pthread / + // SharedArrayBuffer for v1) cannot construct real std::thread workers - + // std::thread's constructor throws at runtime otherwise ("thread + // constructor failed"). Run zero real workers; enqueue() below executes + // tasks synchronously and inline instead, matching the porting plan's + // "run ThreadPool work serially" decision for one-off parallel work + // (parallel .vpx item deserialization at load, env-map baking, etc.). + (void)pool_size; +#endif } // add new work item to the pool @@ -139,6 +150,15 @@ std::future res = task->get_future(); +#ifdef __EMSCRIPTEN__ + // vpinball-wasm: no real worker threads exist (see the constructor) - + // run the task synchronously inline so callers still get a valid, + // already-ready future, exactly as if it had run on a worker. + (void)std::atomic_fetch_add_explicit(&in_flight, std::size_t(1), std::memory_order_relaxed); + (*task)(); + (void)std::atomic_fetch_sub_explicit(&in_flight, std::size_t(1), std::memory_order_acq_rel); + return res; +#else std::unique_lock lock(queue_mutex); if (tasks.size () >= max_queue_size) // wait for the queue to empty or be stopped @@ -160,6 +180,7 @@ condition_consumers.notify_one(); return res; +#endif }