| Replace V with Nim 472eb49 nandithebull 8h ago | 1 | /// Web backend: the same Nim code compiled to WebAssembly by emscripten and |
| Add a WebAssembly backend for the example 728acaa nandithebull 8h ago | 2 | /// driven over `dart:js_interop`. |
| 3 | /// |
| Replace V with Nim 472eb49 nandithebull 8h ago | 4 | /// Selected by the conditional import in `../nimflutter_ffi.dart` when |
| Add a WebAssembly backend for the example 728acaa nandithebull 8h ago | 5 | /// `dart.library.js_interop` is available. The native twin is |
| 6 | /// `backend_native.dart`; the two files must keep the same top-level |
| 7 | /// signatures. |
| 8 | /// |
| 9 | /// The glue is emitted by tool/build_wasm.sh into example/web/ and loaded by |
| 10 | /// a <script> tag in example/web/index.html, which defines the global |
| Replace V with Nim 472eb49 nandithebull 8h ago | 11 | /// `createNimFlutterModule`. |
| Add a WebAssembly backend for the example 728acaa nandithebull 8h ago | 12 | library; |
| 13 | |
| 14 | import 'dart:async'; |
| 15 | import 'dart:js_interop'; |
| 16 | import 'dart:typed_data'; |
| 17 | |
| 18 | import 'fractal_view.dart'; |
| 19 | |
| 20 | const String backendName = 'wasm (emscripten)'; |
| 21 | |
| 22 | /// The module is single-threaded. Splitting into bands still works and still |
| 23 | /// produces the identical frame, but the bands run one after another, so the |
| 24 | /// "parallel" timing on web is a sequential one. |
| 25 | const bool supportsIsolateParallelism = false; |
| 26 | |
| Replace V with Nim 472eb49 nandithebull 8h ago | 27 | @JS('createNimFlutterModule') |
| Add a WebAssembly backend for the example 728acaa nandithebull 8h ago | 28 | external JSFunction? get _factory; |
| 29 | |
| 30 | /// The emscripten module object. Only the pieces this package uses are |
| 31 | /// declared; `_`-prefixed members are the C exports. |
| 32 | extension type _Module._(JSObject _) implements JSObject { |
| 33 | @JS('_malloc') |
| 34 | external int malloc(int bytes); |
| 35 | |
| 36 | @JS('_free') |
| 37 | external void free(int ptr); |
| 38 | |
| Replace V with Nim 472eb49 nandithebull 8h ago | 39 | @JS('_nf_init') |
| Add a WebAssembly backend for the example 728acaa nandithebull 8h ago | 40 | external void vfInit(); |
| 41 | |
| Replace V with Nim 472eb49 nandithebull 8h ago | 42 | @JS('_nf_add') |
| Add a WebAssembly backend for the example 728acaa nandithebull 8h ago | 43 | external int vfAdd(int a, int b); |
| 44 | |
| Replace V with Nim 472eb49 nandithebull 8h ago | 45 | @JS('_nf_greet') |
| Add a WebAssembly backend for the example 728acaa nandithebull 8h ago | 46 | external int vfGreet(int namePtr); |
| 47 | |
| Replace V with Nim 472eb49 nandithebull 8h ago | 48 | @JS('_nf_free') |
| Add a WebAssembly backend for the example 728acaa nandithebull 8h ago | 49 | external void vfFree(int ptr); |
| 50 | |
| Replace V with Nim 472eb49 nandithebull 8h ago | 51 | @JS('_nf_mandelbrot') |
| Add a WebAssembly backend for the example 728acaa nandithebull 8h ago | 52 | external void vfMandelbrot(int buf, int bufLen, int w, int h, double cx, |
| 53 | double cy, double scale, int maxIter, int y0, int y1); |
| 54 | |
| 55 | /// Re-read on every use: emscripten replaces the view when the heap grows. |
| 56 | external JSUint8Array get HEAPU8; |
| 57 | |
| 58 | external String UTF8ToString(int ptr); |
| 59 | external void stringToUTF8(String s, int ptr, int maxBytesToWrite); |
| 60 | external int lengthBytesUTF8(String s); |
| 61 | } |
| 62 | |
| 63 | extension type _U8Array._(JSObject _) implements JSObject { |
| 64 | external JSUint8Array subarray(int begin, int end); |
| 65 | } |
| 66 | |
| 67 | _Module? _module; |
| 68 | |
| 69 | bool get isInitialized => _module != null; |
| 70 | |
| 71 | Future<void>? _pending; |
| 72 | |
| 73 | /// Instantiates the wasm module. Safe to call repeatedly and from several |
| 74 | /// places at once; the work happens once. |
| 75 | Future<void> initialize() { |
| 76 | if (_module != null) return Future<void>.value(); |
| 77 | return _pending ??= _load(); |
| 78 | } |
| 79 | |
| 80 | Future<void> _load() async { |
| 81 | final factory = _factory; |
| 82 | if (factory == null) { |
| 83 | throw StateError( |
| Replace V with Nim 472eb49 nandithebull 8h ago | 84 | 'createNimFlutterModule is not defined. The wasm glue is missing: run ' |
| 85 | '`just wasm` and make sure web/index.html loads nimflutter.js before ' |
| Add a WebAssembly backend for the example 728acaa nandithebull 8h ago | 86 | 'flutter_bootstrap.js.', |
| 87 | ); |
| 88 | } |
| 89 | final module = await (factory.callAsFunction() as JSPromise<JSObject>).toDart; |
| 90 | final m = _Module._(module); |
| 91 | m.vfInit(); |
| 92 | _module = m; |
| 93 | _pending = null; |
| 94 | } |
| 95 | |
| 96 | _Module get _m { |
| 97 | final m = _module; |
| 98 | if (m == null) { |
| 99 | throw StateError( |
| Replace V with Nim 472eb49 nandithebull 8h ago | 100 | 'nimflutter_ffi is not initialised on web. Await initialize() before ' |
| 101 | 'calling into Nim — loading a wasm module cannot be synchronous.', |
| Add a WebAssembly backend for the example 728acaa nandithebull 8h ago | 102 | ); |
| 103 | } |
| 104 | return m; |
| 105 | } |
| 106 | |
| 107 | /// Present so callers written against the native backend keep compiling. On |
| 108 | /// web it only asserts; it cannot do the loading, because that is async. |
| 109 | void ensureInitialized() => _m; |
| 110 | |
| 111 | int add(int a, int b) => _m.vfAdd(a, b); |
| 112 | |
| 113 | String greet(String name) { |
| 114 | final m = _m; |
| 115 | // stringToUTF8 needs room for the trailing NUL. |
| 116 | final size = m.lengthBytesUTF8(name) + 1; |
| 117 | final arg = m.malloc(size); |
| 118 | var res = 0; |
| 119 | try { |
| 120 | m.stringToUTF8(name, arg, size); |
| 121 | res = m.vfGreet(arg); |
| 122 | if (res == 0) { |
| Replace V with Nim 472eb49 nandithebull 8h ago | 123 | throw StateError('nf_greet returned null'); |
| Add a WebAssembly backend for the example 728acaa nandithebull 8h ago | 124 | } |
| 125 | return m.UTF8ToString(res); |
| 126 | } finally { |
| 127 | m.free(arg); |
| Replace V with Nim 472eb49 nandithebull 8h ago | 128 | // Nim allocated the result, so Nim releases it — same rule as on native. |
| Add a WebAssembly backend for the example 728acaa nandithebull 8h ago | 129 | if (res != 0) m.vfFree(res); |
| 130 | } |
| 131 | } |
| 132 | |
| 133 | /// No isolates on web, so this is [greet] behind a `Future`. It does not move |
| 134 | /// the work off the UI thread. |
| 135 | Future<String> greetAsync(String name) async => greet(name); |
| 136 | |
| 137 | Uint8List renderBand(FractalView view, int y0, int y1) { |
| 138 | final m = _m; |
| 139 | final rows = y1 - y0; |
| 140 | if (rows <= 0) return Uint8List(0); |
| 141 | |
| 142 | final bytes = rows * view.width * 4; |
| 143 | final buf = m.malloc(bytes); |
| 144 | try { |
| 145 | m.vfMandelbrot(buf, bytes, view.width, view.height, view.centerX, |
| 146 | view.centerY, view.scale, view.maxIter, y0, y1); |
| 147 | // Take a view of just this band and copy it into Dart before the wasm |
| 148 | // allocation is released. Reading m.HEAPU8 here rather than earlier |
| 149 | // matters: malloc may have grown the heap and replaced the view. |
| 150 | final band = _U8Array._(m.HEAPU8).subarray(buf, buf + bytes).toDart; |
| 151 | return Uint8List.fromList(band); |
| 152 | } finally { |
| 153 | m.free(buf); |
| 154 | } |
| 155 | } |
| 156 | |
| 157 | /// Splits the frame the same way the native backend does, so the output is |
| 158 | /// identical — but the bands are computed one after another. See |
| 159 | /// [supportsIsolateParallelism]. |
| 160 | Future<Uint8List> renderParallel(FractalView view, int tiles) async { |
| 161 | final count = tiles.clamp(1, view.height); |
| 162 | final rowsPer = (view.height / count).ceil(); |
| 163 | |
| 164 | final out = Uint8List(view.width * view.height * 4); |
| 165 | var offset = 0; |
| 166 | for (var t = 0; t < count; t++) { |
| 167 | final y0 = t * rowsPer; |
| 168 | final y1 = ((t + 1) * rowsPer).clamp(0, view.height); |
| 169 | if (y0 >= y1) break; |
| 170 | final band = renderBand(view, y0, y1); |
| 171 | out.setRange(offset, offset + band.length, band); |
| 172 | offset += band.length; |
| 173 | } |
| 174 | return out; |
| 175 | } |