// Browser transport only; the simulation owns the schedule and shared state. let threadStarting = null; async function startThreads() { const count = Math.max(1, Math.min(16, Math.floor(SET.workerCap) || 16, (navigator.hardwareConcurrency || 3) - 2)); if (simPool && simPool.world === mat && simPool.workers.length === count) return; if (threadStarting) await threadStarting; if (simPool && simPool.world === mat && simPool.workers.length === count) return; threadStarting = (async () => { if (simPool) for (const worker of simPool.workers) worker.terminate(); simPool = null; if (!(mat.buffer instanceof SharedArrayBuffer)) throw Error('Shared memory is unavailable'); const url = URL.createObjectURL(new Blob([SIM_WORKER_SOURCE, '\nonmessage=e=>threadMessage(e.data);'], {type:'text/javascript'})); const pool = { world: mat, workers: [], slots: [] }; try { const ready = []; for (let k = 0; k < count; k++) { const worker = new Worker(url), slot = threadSlot(); pool.workers.push(worker); pool.slots.push(slot); ready.push(new Promise((resolve,reject) => { const deadline = performance.now() + 10000; worker.onerror = e => { const error = Error(e.message || 'Simulation worker failed'); threadFailure(slot, error); reject(error); }; const poll = () => { if (Atomics.load(slot.control,0)) resolve(); else if (performance.now() > deadline) reject(Error('Simulation worker startup timed out')); else setTimeout(poll, 1); }; poll(); })); worker.postMessage({world:threadWorld(),slot}); } await Promise.all(ready); simPool = pool; } catch (e) { for (const worker of pool.workers) worker.terminate(); throw e; } finally { URL.revokeObjectURL(url); } })(); try { await threadStarting; } finally { threadStarting = null; } }