| Render in parallel on web, via a pool of Web Workers 4506a58 nandithebull 7h ago | 1 | // A worker pool for band rendering, exposed to Dart as one promise-returning |
| 2 | // function. |
| 3 | // |
| 4 | // The pool lives here rather than in Dart because dart:js_interop has no |
| 5 | // ergonomic Worker binding, and because the interesting part — a work queue |
| 6 | // feeding N workers — is the same code in either language. |
| 7 | (function () { |
| 8 | const workers = []; |
| 9 | const idle = []; |
| 10 | let nextId = 1; |
| 11 | let poolSize = 0; |
| 12 | |
| 13 | // More bands than cores is deliberate: bands cost wildly different amounts |
| 14 | // (interior rows run the full iteration cap), so over-decomposing keeps a |
| 15 | // free worker busy instead of waiting on the slowest band. Same finding as |
| 16 | // `just sweep` on native. |
| 17 | const maxWorkers = Math.max(1, (self.navigator && self.navigator.hardwareConcurrency) || 4); |
| 18 | |
| 19 | function grow(n) { |
| 20 | const want = Math.min(n, maxWorkers); |
| 21 | while (workers.length < want) { |
| 22 | const w = new Worker('nimflutter_worker.js'); |
| 23 | w.pending = new Map(); |
| 24 | w.onmessage = (e) => { |
| 25 | const resolve = w.pending.get(e.data.id); |
| 26 | if (!resolve) return; |
| 27 | w.pending.delete(e.data.id); |
| 28 | resolve(e.data); |
| 29 | }; |
| 30 | workers.push(w); |
| 31 | idle.push(w); |
| 32 | } |
| 33 | poolSize = workers.length; |
| 34 | } |
| 35 | |
| 36 | function run(worker, job) { |
| 37 | return new Promise((resolve) => { |
| 38 | worker.pending.set(job.id, resolve); |
| 39 | worker.postMessage(job); |
| 40 | }); |
| 41 | } |
| 42 | |
| 43 | self.nimflutterPoolSize = () => poolSize; |
| 44 | |
| 45 | self.nimflutterRenderParallel = function (w, h, cx, cy, scale, iter, tiles) { |
| 46 | const count = Math.max(1, Math.min(tiles, h)); |
| 47 | grow(count); |
| 48 | |
| 49 | const rowsPer = Math.ceil(h / count); |
| 50 | const queue = []; |
| 51 | for (let t = 0; t < count; t++) { |
| 52 | const y0 = t * rowsPer; |
| 53 | const y1 = Math.min((t + 1) * rowsPer, h); |
| 54 | if (y0 >= y1) break; |
| 55 | queue.push({ id: nextId++, w, h, cx, cy, scale, iter, y0, y1 }); |
| 56 | } |
| 57 | |
| 58 | const out = new Uint8Array(w * h * 4); |
| 59 | |
| 60 | // Hand each worker the next band as it frees up, rather than dealing the |
| 61 | // whole deck up front — that is what turns uneven band costs into a |
| 62 | // balanced schedule. |
| 63 | const pump = (worker) => { |
| 64 | const job = queue.shift(); |
| 65 | if (!job) return Promise.resolve(); |
| 66 | return run(worker, job).then((res) => { |
| 67 | out.set(res.band, res.y0 * w * 4); |
| 68 | return pump(worker); |
| 69 | }); |
| 70 | }; |
| 71 | |
| 72 | return Promise.all(workers.map(pump)).then(() => out); |
| 73 | }; |
| 74 | })(); |