nandi/vflutter_ffipublic Fork 0
728acaab2594667f4998fc18812e7c1e2e4cc549
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
Add a WebAssembly backend for the example 728acaa nandithebull 19h ago1/// Web backend: the same V code compiled to WebAssembly by emscripten and
2/// driven over `dart:js_interop`.
3///
4/// Selected by the conditional import in `../vflutter_ffi.dart` when
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
11/// `createVFlutterModule`.
12library;
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
27@JS('createVFlutterModule')
28external 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
39 @JS('_vf_init')
40 external void vfInit();
41
42 @JS('_vf_add')
43 external int vfAdd(int a, int b);
44
45 @JS('_vf_greet')
46 external int vfGreet(int namePtr);
47
48 @JS('_vf_free')
49 external void vfFree(int ptr);
50
51 @JS('_vf_mandelbrot')
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
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(
84 'createVFlutterModule is not defined. The wasm glue is missing: run '
85 '`just wasm` and make sure web/index.html loads vflutter.js before '
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(
100 'vflutter_ffi is not initialised on web. Await initialize() before '
101 'calling into V — loading a wasm module cannot be synchronous.',
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.
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) {
123 throw StateError('vf_greet returned null');
124 }
125 return m.UTF8ToString(res);
126 } finally {
127 m.free(arg);
128 // V allocated the result, so V releases it — same rule as on native.
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.
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}