| Initial commit: V -> Flutter FFI plugin ad0ccda nandi 6h ago | 1 | /// 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. |
| 6 | library vflutter_ffi; |
| 7 | |
| 8 | import 'dart:ffi'; |
| 9 | import 'dart:io'; |
| 10 | import 'dart:isolate'; |
| 11 | import 'package:ffi/ffi.dart'; |
| 12 | |
| 13 | const String _libName = 'vflutter'; |
| 14 | |
| 15 | DynamicLibrary _open() { |
| 16 | if (Platform.isMacOS || Platform.isIOS) { |
| 17 | // Static archive linked into the app binary. |
| 18 | return DynamicLibrary.process(); |
| 19 | } |
| 20 | if (Platform.isAndroid || Platform.isLinux) { |
| 21 | return DynamicLibrary.open('lib$_libName.so'); |
| 22 | } |
| 23 | if (Platform.isWindows) { |
| 24 | return DynamicLibrary.open('$_libName.dll'); |
| 25 | } |
| 26 | throw UnsupportedError('vflutter_ffi: unsupported platform'); |
| 27 | } |
| 28 | |
| 29 | final DynamicLibrary _lib = _open(); |
| 30 | |
| 31 | final void Function() _vfInit = |
| 32 | _lib.lookupFunction<Void Function(), void Function()>('vf_init'); |
| 33 | |
| 34 | final int Function(int, int) _vfAdd = |
| 35 | _lib.lookupFunction<Int32 Function(Int32, Int32), int Function(int, int)>( |
| 36 | 'vf_add'); |
| 37 | |
| 38 | final Pointer<Utf8> Function(Pointer<Utf8>) _vfGreet = _lib.lookupFunction< |
| 39 | Pointer<Utf8> Function(Pointer<Utf8>), |
| 40 | Pointer<Utf8> Function(Pointer<Utf8>)>('vf_greet'); |
| 41 | |
| 42 | final void Function(Pointer<Void>) _vfFree = |
| 43 | _lib.lookupFunction<Void Function(Pointer<Void>), void Function(Pointer<Void>)>( |
| 44 | 'vf_free'); |
| 45 | |
| 46 | bool _ready = false; |
| 47 | |
| 48 | /// Initialises the V runtime. Idempotent and cheap; called automatically by |
| 49 | /// every API below, so you rarely need it directly. |
| 50 | void ensureInitialized() { |
| 51 | if (_ready) return; |
| 52 | _vfInit(); |
| 53 | _ready = true; |
| 54 | } |
| 55 | |
| 56 | /// Adds two integers in V. The trivial case, useful as a liveness check. |
| 57 | int add(int a, int b) { |
| 58 | ensureInitialized(); |
| 59 | return _vfAdd(a, b); |
| 60 | } |
| 61 | |
| 62 | /// Round-trips a string through V. |
| 63 | /// |
| 64 | /// V allocates the result; this function copies it into a Dart [String] and |
| 65 | /// frees the V allocation before returning, so there is nothing to release. |
| 66 | String greet(String name) { |
| 67 | ensureInitialized(); |
| 68 | final arg = name.toNativeUtf8(); |
| 69 | Pointer<Utf8> res = nullptr; |
| 70 | try { |
| 71 | res = _vfGreet(arg); |
| 72 | if (res == nullptr) { |
| 73 | throw StateError('vf_greet returned null'); |
| 74 | } |
| 75 | return res.toDartString(); |
| 76 | } finally { |
| 77 | calloc.free(arg); |
| 78 | if (res != nullptr) _vfFree(res.cast()); |
| 79 | } |
| 80 | } |
| 81 | |
| 82 | /// Runs [greet] on a helper isolate. |
| 83 | /// |
| 84 | /// V compiled with `-gc none` has no stop-the-world phase and no thread-local |
| 85 | /// runtime state, so calls are safe from any isolate. Use this for work long |
| 86 | /// enough to jank a frame. |
| 87 | Future<String> greetAsync(String name) => |
| 88 | Isolate.run(() => greet(name)); |