nandi/vflutter_ffipublic Fork 0
472eb493bac94bdcb79a38829ccca0c541256fef
Commits
Clone
git clone https://git.rickub.com/nandi/vflutter_ffi.git
git clone ssh://git@rickub.com/nandi/vflutter_ffi.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

backend_web.dart · 175 lines · 5.3 KBDart Blame HistoryRaw
Replace V with Nim 472eb49 nandithebull 8h ago1/// Web backend: the same Nim code compiled to WebAssembly by emscripten and
Add a WebAssembly backend for the example 728acaa nandithebull 8h ago2/// driven over `dart:js_interop`.
3///
Replace V with Nim 472eb49 nandithebull 8h ago4/// Selected by the conditional import in `../nimflutter_ffi.dart` when
Add a WebAssembly backend for the example 728acaa nandithebull 8h ago5/// `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 ago11/// `createNimFlutterModule`.
Add a WebAssembly backend for the example 728acaa nandithebull 8h ago12library;
13
14import 'dart:async';
15import 'dart:js_interop';
16import 'dart:typed_data';
17
18import 'fractal_view.dart';
19
20const 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.
25const bool supportsIsolateParallelism = false;
26
Replace V with Nim 472eb49 nandithebull 8h ago27@JS('createNimFlutterModule')
Add a WebAssembly backend for the example 728acaa nandithebull 8h ago28external JSFunction? get _factory;
29
30/// The emscripten module object. Only the pieces this package uses are
31/// declared; `_`-prefixed members are the C exports.
32extension 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 ago39 @JS('_nf_init')
Add a WebAssembly backend for the example 728acaa nandithebull 8h ago40 external void vfInit();
41
Replace V with Nim 472eb49 nandithebull 8h ago42 @JS('_nf_add')
Add a WebAssembly backend for the example 728acaa nandithebull 8h ago43 external int vfAdd(int a, int b);
44
Replace V with Nim 472eb49 nandithebull 8h ago45 @JS('_nf_greet')
Add a WebAssembly backend for the example 728acaa nandithebull 8h ago46 external int vfGreet(int namePtr);
47
Replace V with Nim 472eb49 nandithebull 8h ago48 @JS('_nf_free')
Add a WebAssembly backend for the example 728acaa nandithebull 8h ago49 external void vfFree(int ptr);
50
Replace V with Nim 472eb49 nandithebull 8h ago51 @JS('_nf_mandelbrot')
Add a WebAssembly backend for the example 728acaa nandithebull 8h ago52 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
63extension type _U8Array._(JSObject _) implements JSObject {
64 external JSUint8Array subarray(int begin, int end);
65}
66
67_Module? _module;
68
69bool get isInitialized => _module != null;
70
71Future<void>? _pending;
72
73/// Instantiates the wasm module. Safe to call repeatedly and from several
74/// places at once; the work happens once.
75Future<void> initialize() {
76 if (_module != null) return Future<void>.value();
77 return _pending ??= _load();
78}
79
80Future<void> _load() async {
81 final factory = _factory;
82 if (factory == null) {
83 throw StateError(
Replace V with Nim 472eb49 nandithebull 8h ago84 '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 ago86 '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 ago100 '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 ago102 );
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.
109void ensureInitialized() => _m;
110
111int add(int a, int b) => _m.vfAdd(a, b);
112
113String 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 ago123 throw StateError('nf_greet returned null');
Add a WebAssembly backend for the example 728acaa nandithebull 8h ago124 }
125 return m.UTF8ToString(res);
126 } finally {
127 m.free(arg);
Replace V with Nim 472eb49 nandithebull 8h ago128 // Nim allocated the result, so Nim releases it — same rule as on native.
Add a WebAssembly backend for the example 728acaa nandithebull 8h ago129 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.
135Future<String> greetAsync(String name) async => greet(name);
136
137Uint8List 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].
160Future<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}