# nimflutter_ffi example A Mandelbrot explorer whose pixels are computed in Nim. Drag to pan, scroll to zoom smoothly, tap to zoom in, right-click to zoom out — zoom is anchored at the cursor, and a drag covers less ground the deeper you are. Sliders control the iteration cap and how many isolates the frame is split across; "Reset view" restores the framing without disturbing either. It exists to exercise the parts of the bridge the string demo cannot: * **Bulk data across the boundary.** Each frame is a full RGBA buffer, not a string. Dart allocates it and `nf_mandelbrot` fills it in place, so no Nim memory crosses the boundary and there is nothing to `nf_free`. * **Band parallelism.** The image splits into horizontal strips rendered on separate isolates. Bands are independent by construction, and `-gc none` means no stop-the-world phase to serialise them. * **An honest benchmark.** The same algorithm is written twice — in Nim (`src/nimflutter.v`) and in Dart (`lib/mandelbrot_dart.dart`) — and the Benchmark button times both. The tests assert the two agree pixel-for-pixel, which is what makes the comparison meaningful. ## What the numbers actually say Measured here (800x600, maxIter 500, release build, 8-core Linux): | | time | |---|---| | Nim, one isolate | ~152 ms | | Nim, 4 isolates | ~65 ms | | Dart AOT, one isolate | ~165 ms | **Nim is not meaningfully faster than Dart at this.** For a scalar floating-point loop, Nim-through-C and Dart AOT land within ~10% of each other. The 2.5x comes from the parallel split, which Dart could do on its own without any FFI. Treat this app as a demonstration of the *bridge* — bulk buffers, caller-owned memory, safe multi-isolate calls — not as a speed claim for Nim. Reach for Nim here because you want to write the logic in Nim, or already have it in Nim, not because C-via-Nim is expected to outrun Dart AOT on arithmetic. ## How many isolates? The slider goes to 32, and past the core count is the right place to be. Bands cost very different amounts — rows crossing the set's interior run the full iteration cap, rows in open space escape almost immediately — and a frame is not done until its slowest band is. Over-decomposing lets a free core start the next small band instead of idling. Measured here (800x600 / 500 iterations, 8 logical cores): | tiles | best ms | speedup | |---|---|---| | serial | 153 | 1.00x | | 4 | 64 | 2.39x | | 8 | 43 | 3.56x | | 16 | 34 | 4.50x | | 32 | 29 | 5.28x | | 64 | 27 | 5.67x | Even at 32 bands the slowest is still 2.7x the mean, which is the remaining inefficiency — not isolate overhead. Spawning 24 isolates costs ~0.8 ms and moving the whole 1.83 MB frame back across them ~1.4 ms, against a ~29 ms frame. Run `just sweep` to get these numbers for your own machine. ## Run it ```bash just mandelbrot ``` or, driving Flutter yourself: ```bash cd example flutter run -d linux ``` Only the Linux runner is generated. For the other platforms, generate their runner directories once first: ```bash flutter create --platforms=android,ios,macos,windows . ``` `flutter create` leaves `lib/`, `test/` and `pubspec.yaml` alone. The native library is built by the plugin itself: CMake invokes `v` on Linux, Windows and Android; iOS and macOS compile the pre-generated C in `ios/Classes/`. ## Tests `flutter test` runs on the host VM rather than in the app bundle, so it needs the native library on the loader path: ```bash ./tool/build.sh cd example && LD_LIBRARY_PATH=../build flutter test ``` The suite covers the zoom maths as a property (the point under the cursor stays put), the buffer contract, the band split matching a single-shot render, and the Nim and Dart kernels agreeing exactly. ## Headless scripts No Flutter SDK needed — only `dart` and the host library. Run from the repository root, which resolves without Flutter: ```bash ./tool/build.sh LD_LIBRARY_PATH=build dart example/lib/fractal_bench.dart # Nim vs Dart timings LD_LIBRARY_PATH=build dart example/lib/main_test.dart # string round-trips LD_LIBRARY_PATH=build dart example/lib/memory_check.dart # RSS regression guard ``` For a like-for-like comparison with a release app, compile the benchmark AOT first — `dart run` uses the JIT: ```bash dart compile exe example/lib/fractal_bench.dart -o /tmp/bench LD_LIBRARY_PATH=build /tmp/bench ```