/// Native backend: the Nim library as a shared object / static archive, reached /// through `dart:ffi`. /// /// Selected by the conditional import in `../nimflutter_ffi.dart` on every /// target except web. The web twin is `backend_web.dart`; the two files must /// keep the same top-level signatures. library; import 'dart:ffi'; import 'dart:io'; import 'dart:isolate'; import 'dart:typed_data'; import 'package:ffi/ffi.dart'; import 'fractal_view.dart'; const String backendName = 'dart:ffi'; /// Isolates are real threads here, and `-gc none` means there is no /// stop-the-world phase to serialise them. const bool supportsIsolateParallelism = true; const String _libName = 'nimflutter'; DynamicLibrary _open() { if (Platform.isMacOS || Platform.isIOS) { // Static archive linked into the app binary. return DynamicLibrary.process(); } if (Platform.isAndroid || Platform.isLinux) { return DynamicLibrary.open('lib$_libName.so'); } if (Platform.isWindows) { return DynamicLibrary.open('$_libName.dll'); } throw UnsupportedError('nimflutter_ffi: unsupported platform'); } final DynamicLibrary _lib = _open(); final void Function() _nfInit = _lib.lookupFunction('nf_init'); final int Function(int, int) _nfAdd = _lib.lookupFunction( 'nf_add'); final Pointer Function(Pointer) _nfGreet = _lib.lookupFunction< Pointer Function(Pointer), Pointer Function(Pointer)>('nf_greet'); final void Function(Pointer) _nfFree = _lib.lookupFunction< Void Function(Pointer), void Function(Pointer)>('nf_free'); final void Function(Pointer, int, int, int, double, double, double, int, int, int) _nfMandelbrot = _lib.lookupFunction< Void Function(Pointer, Size, Int32, Int32, Double, Double, Double, Int32, Int32, Int32), void Function(Pointer, int, int, int, double, double, double, int, int, int)>('nf_mandelbrot'); bool _ready = false; bool get isInitialized => _ready; /// Loading a shared object is synchronous, so this is only here to match the /// web backend's contract. Nothing awaits anything. Future initialize() async => ensureInitialized(); void ensureInitialized() { if (_ready) return; _nfInit(); _ready = true; } int add(int a, int b) { ensureInitialized(); return _nfAdd(a, b); } String greet(String name) { ensureInitialized(); final arg = name.toNativeUtf8(); Pointer res = nullptr; try { res = _nfGreet(arg); if (res == nullptr) { throw StateError('nf_greet returned null'); } return res.toDartString(); } finally { calloc.free(arg); if (res != nullptr) _nfFree(res.cast()); } } Future greetAsync(String name) => Isolate.run(() => greet(name)); Uint8List renderBand(FractalView view, int y0, int y1) { ensureInitialized(); final rows = y1 - y0; if (rows <= 0) return Uint8List(0); final bytes = rows * view.width * 4; final buf = calloc(bytes); try { // `bytes` is the real allocation size, not a restatement of the band: Nim // checks it before writing, so an arithmetic slip here is caught there // rather than becoming a heap overflow. _nfMandelbrot(buf, bytes, view.width, view.height, view.centerX, view.centerY, view.scale, view.maxIter, y0, y1); // Copy out of native memory before it is freed. return Uint8List.fromList(buf.asTypedList(bytes)); } finally { calloc.free(buf); } } Future renderParallel(FractalView view, int tiles) async { ensureInitialized(); final count = tiles.clamp(1, view.height); final rowsPer = (view.height / count).ceil(); final futures = >[]; for (var t = 0; t < count; t++) { final y0 = t * rowsPer; final y1 = ((t + 1) * rowsPer).clamp(0, view.height); if (y0 >= y1) break; futures.add(Isolate.run(() => renderBand(view, y0, y1))); } final bands = await Future.wait(futures); final out = Uint8List(view.width * view.height * 4); var offset = 0; for (final band in bands) { out.setRange(offset, offset + band.length, band); offset += band.length; } return out; }