nandi/vflutter_ffipublic Fork 0
main
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.

vflutter_ffi.dart · 230 lines · 7.2 KBDart Blame HistoryRaw
Initial commit: V -> Flutter FFI plugin ad0ccda nandi 5h ago1/// Idiomatic Dart surface over the V library.
2///
3/// Callers never see a Pointer, and never own V memory: every string that
4/// crosses the boundary is copied into Dart and the V allocation is released
5/// before the call returns.
6library vflutter_ffi;
7
8import 'dart:ffi';
9import 'dart:io';
10import 'dart:isolate';
Add a Mandelbrot example app, and a V kernel worth demonstrating 81540bb nandithebull 1h ago11import 'dart:typed_data';
Initial commit: V -> Flutter FFI plugin ad0ccda nandi 5h ago12import 'package:ffi/ffi.dart';
13
14const String _libName = 'vflutter';
15
16DynamicLibrary _open() {
17 if (Platform.isMacOS || Platform.isIOS) {
18 // Static archive linked into the app binary.
19 return DynamicLibrary.process();
20 }
21 if (Platform.isAndroid || Platform.isLinux) {
22 return DynamicLibrary.open('lib$_libName.so');
23 }
24 if (Platform.isWindows) {
25 return DynamicLibrary.open('$_libName.dll');
26 }
27 throw UnsupportedError('vflutter_ffi: unsupported platform');
28}
29
30final DynamicLibrary _lib = _open();
31
32final void Function() _vfInit =
33 _lib.lookupFunction<Void Function(), void Function()>('vf_init');
34
35final int Function(int, int) _vfAdd =
36 _lib.lookupFunction<Int32 Function(Int32, Int32), int Function(int, int)>(
37 'vf_add');
38
39final Pointer<Utf8> Function(Pointer<Utf8>) _vfGreet = _lib.lookupFunction<
40 Pointer<Utf8> Function(Pointer<Utf8>),
41 Pointer<Utf8> Function(Pointer<Utf8>)>('vf_greet');
42
43final void Function(Pointer<Void>) _vfFree =
44 _lib.lookupFunction<Void Function(Pointer<Void>), void Function(Pointer<Void>)>(
45 'vf_free');
46
47bool _ready = false;
48
49/// Initialises the V runtime. Idempotent and cheap; called automatically by
50/// every API below, so you rarely need it directly.
51void ensureInitialized() {
52 if (_ready) return;
53 _vfInit();
54 _ready = true;
55}
56
57/// Adds two integers in V. The trivial case, useful as a liveness check.
58int add(int a, int b) {
59 ensureInitialized();
60 return _vfAdd(a, b);
61}
62
63/// Round-trips a string through V.
64///
65/// V allocates the result; this function copies it into a Dart [String] and
66/// frees the V allocation before returning, so there is nothing to release.
67String greet(String name) {
68 ensureInitialized();
69 final arg = name.toNativeUtf8();
70 Pointer<Utf8> res = nullptr;
71 try {
72 res = _vfGreet(arg);
73 if (res == nullptr) {
74 throw StateError('vf_greet returned null');
75 }
76 return res.toDartString();
77 } finally {
78 calloc.free(arg);
79 if (res != nullptr) _vfFree(res.cast());
80 }
81}
82
83/// Runs [greet] on a helper isolate.
84///
85/// V compiled with `-gc none` has no stop-the-world phase and no thread-local
86/// runtime state, so calls are safe from any isolate. Use this for work long
87/// enough to jank a frame.
88Future<String> greetAsync(String name) =>
89 Isolate.run(() => greet(name));
Add a Mandelbrot example app, and a V kernel worth demonstrating 81540bb nandithebull 1h ago90
91// ---------------------------------------------------------------------------
92// Mandelbrot
93// ---------------------------------------------------------------------------
94
95final void Function(Pointer<Uint8>, int, int, double, double, double, int, int,
96 int) _vfMandelbrot = _lib.lookupFunction<
97 Void Function(Pointer<Uint8>, Int32, Int32, Double, Double, Double,
98 Int32, Int32, Int32),
99 void Function(Pointer<Uint8>, int, int, double, double, double, int,
100 int, int)>('vf_mandelbrot');
101
102/// Where to look in the complex plane, and how hard to look.
103class FractalView {
104 const FractalView({
105 required this.width,
106 required this.height,
107 this.centerX = -0.5,
108 this.centerY = 0.0,
109 this.scale = 3.0,
110 this.maxIter = 500,
111 });
112
113 final int width;
114 final int height;
115
116 /// Centre of the viewport in the complex plane.
117 final double centerX;
118 final double centerY;
119
120 /// Width of the viewport in the complex plane. Smaller = deeper zoom.
121 final double scale;
122
123 /// Escape-time iteration cap. The cost of a frame scales with this.
124 final int maxIter;
125
126 /// Returns this view zoomed by [factor] about a point given in *fractional*
127 /// viewport coordinates — (0,0) top-left, (1,1) bottom-right.
128 ///
129 /// The point under the cursor stays under the cursor: that is the whole
130 /// contract, and it is what makes click-to-zoom feel anchored rather than
131 /// drifting. `factor < 1` zooms in.
132 FractalView zoomedAt(double fx, double fy, double factor) {
133 final aspect = width / height;
134 final ox = fx - 0.5;
135 final oy = fy - 0.5;
136
137 // The complex-plane point currently under (fx, fy).
138 final targetX = centerX + ox * scale * aspect;
139 final targetY = centerY + oy * scale;
140
141 final newScale = scale * factor;
142 return copyWith(
143 scale: newScale,
144 centerX: targetX - ox * newScale * aspect,
145 centerY: targetY - oy * newScale,
146 );
147 }
148
Add drag-to-pan 0ed90f3 nandithebull 50m ago149 /// Returns this view dragged by a delta given as a *fraction* of the
150 /// viewport — dragging right by half the width is `fdx = 0.5`.
151 ///
152 /// The image follows the finger, so the centre moves the opposite way.
153 FractalView pannedBy(double fdx, double fdy) {
154 final aspect = width / height;
155 return copyWith(
156 centerX: centerX - fdx * scale * aspect,
157 centerY: centerY - fdy * scale,
158 );
159 }
160
Add a Mandelbrot example app, and a V kernel worth demonstrating 81540bb nandithebull 1h ago161 FractalView copyWith({
162 int? width,
163 int? height,
164 double? centerX,
165 double? centerY,
166 double? scale,
167 int? maxIter,
168 }) =>
169 FractalView(
170 width: width ?? this.width,
171 height: height ?? this.height,
172 centerX: centerX ?? this.centerX,
173 centerY: centerY ?? this.centerY,
174 scale: scale ?? this.scale,
175 maxIter: maxIter ?? this.maxIter,
176 );
177}
178
179/// Renders rows [y0, y1) of [view] into a fresh RGBA byte buffer.
180///
181/// The buffer is allocated on the Dart side and filled in place by V, so no V
182/// memory is created and nothing needs freeing on the V side. The native
183/// allocation is released before returning; callers get a plain [Uint8List].
184Uint8List renderBand(FractalView view, int y0, int y1) {
185 ensureInitialized();
186 final rows = y1 - y0;
187 if (rows <= 0) return Uint8List(0);
188
189 final bytes = rows * view.width * 4;
190 final buf = calloc<Uint8>(bytes);
191 try {
192 _vfMandelbrot(buf, view.width, view.height, view.centerX, view.centerY,
193 view.scale, view.maxIter, y0, y1);
194 // Copy out of native memory before it is freed.
195 return Uint8List.fromList(buf.asTypedList(bytes));
196 } finally {
197 calloc.free(buf);
198 }
199}
200
201/// Renders the whole of [view] on the calling isolate.
202Uint8List render(FractalView view) => renderBand(view, 0, view.height);
203
204/// Renders [view] as [tiles] horizontal bands computed in parallel.
205///
206/// Bands are independent by construction — each call writes only its own rows
207/// — so this is real parallelism across isolates, not interleaving. `-gc none`
208/// means there is no stop-the-world phase to serialise them.
209Future<Uint8List> renderParallel(FractalView view, {int tiles = 4}) async {
210 ensureInitialized();
211 final count = tiles.clamp(1, view.height);
212 final rowsPer = (view.height / count).ceil();
213
214 final futures = <Future<Uint8List>>[];
215 for (var t = 0; t < count; t++) {
216 final y0 = t * rowsPer;
217 final y1 = ((t + 1) * rowsPer).clamp(0, view.height);
218 if (y0 >= y1) break;
219 futures.add(Isolate.run(() => renderBand(view, y0, y1)));
220 }
221
222 final bands = await Future.wait(futures);
223 final out = Uint8List(view.width * view.height * 4);
224 var offset = 0;
225 for (final band in bands) {
226 out.setRange(offset, offset + band.length, band);
227 offset += band.length;
228 }
229 return out;
230}