nitro 0.7.3
nitro: ^0.7.3 copied to clipboard
High-performance Native Modules for Flutter (Nitro Modules equivalent). Runtime support for .native.dart spec generated bridges.
0.7.3 #
- Ecosystem sync for
nitrogen_cli0.7.3 (web-specific C++ impl, bridge-checksum stamp inbuild_web.sh). Re-runnitrogen generate. No changes to this package.
0.7.2 #
- Ecosystem sync for
nitro_generator0.7.2 (web named-parameter fix). Re-runnitrogen generate. No changes to this package.
0.7.1 #
- Ecosystem sync for
nitro_generator0.7.1. Re-runnitrogen generate— the web struct layout changed.
Fixed
IsolatePool: a worker whose reply could not be sent died, hanging its in-flight calls forever. It now always replies, and the pool fails a dead worker's calls.loadWebModule: failed loads stayed cached (retries replayed the rejection) and concurrent callers shared one refcount.RecordReadertrusted the length prefix and could read past the allocation.RecordWriterBase(0)hung; growth doubled from zero.readCStringspun forever on an unterminated pointer.NitroCoalescer.submitleaked its pending slot when the call threw.NitroInstanceRegistry: the GC finalizer evicted a live entry on id reuse.
0.7.0 #
Web support — compiles and runs under flutter build web (dart2js) and
--wasm (dart2wasm).
Regenerate after upgrading: nitrogen generate && nitrogen link.
See migration/0.7.0.md.
Added
- Web runtime: WASM module loading, port registry,
NitroErrorslot in linear memory, streams,@nitroNativeAsync. NitroWireCodec<T>for@NitroCustomTypeon web.NitroRuntime.retainLib— per-instance web module refcount.
Changed
- Record/variant wire codecs share a platform-neutral core between FFI and web.
@nitroAsyncruns inline on web (no isolates);@zeroCopyis one bulk copy.- API moves:
NitroAnyMap.fromNative→nitroAnyMapFromNative,NitroNullable*.fromNative→ top-level functions, default log sink is now zone-awareprint. NitroWasmModule.removeFunctionno-ops when the module lacks the export.
Fixed
jsI64/dartI64rounded above 2^53;Int64.maxreturnedInt64.min. Now exact over the full int64 range on dart2wasm (dart2js stays 53-bit).- Disposing one hybrid instance evicted the shared WASM module for the others.
0.6.1 #
- Fixed:
NitroCoalescer.dispose()discarded results that were already posted, so theFuturehung forever (#47). A native post only enqueues on the port — delivery needs an event-loop turn — anddispose()is normally called in that same turn, so results that already existed were thrown away.dispose()now drains already-posted batches (bounded, exits as soon as nothing is pending) and completes anything genuinely lost with aStateErrorinstead of dropping it. Stopping the native side first no longer has to be enough on its own. dispose()is idempotent, andsubmit()after disposal throws instead of returning a future that could never complete.- Android
@zeroCopyTypedData returns no longer fragment the JVM heap under sustained load — the fix is generator-side, so regenerate to pick it up, and seenitro_generator0.6.1 (#48). - Behavior note: because pending calls now fail rather than vanish, a
submit()result that is never awaited surfaces as an unhandled error. Await the futures (or.ignore()deliberately dropped ones).
0.6.0 #
Sync bridge returns no longer allocate: native lends Dart a reusable per-thread
buffer instead of a fresh malloc/strdup, and Dart no longer frees it.
@nitroAsync / @nitroNativeAsync are unchanged.
Regenerate after upgrading — nitrogen generate && nitrogen link. Generated
code and the runtime changed together. See migration/0.6.0.md.
- Nullable primitives (
int?/double?/bool?/DateTime?/uint64?): 15.0 ns → 0.2 ns per call. @HybridStructreturns: 16.1 ns → 1.0 ns. Inner fields are still owned and freed.Stringreturns: 27.8 ns → 9.9 ns (32 B). NewtoDartStringBorrowed().- Multi-instance dispatch: the instance cache holds 8 entries instead of 1, so loops across several live instances stay lock-free — 7–8× faster, 48× under thread contention. Single-instance is unchanged.
- New
NitroCoalescer: opt-in batching of concurrent@nitroNativeAsynccompletions over one port. A 64-in-flight burst drops 559 µs → ~99 µs (#39). - A busy-spin completion path was tried and rejected: +35 % latency, because the spinning isolate starves the thread producing the result.
Verified on macOS, iOS and Android (679 integration tests per platform, 4342 generator tests).
0.5.17 #
- Memory-leak & robustness fixes for instance lifecycle and native-async. Verified on real devices (Android emulator, iOS simulator, macOS): under create → drop → GC churn, 499/500 keyed instances are collected and RSS stays bounded across single-instance, multi-instance, stream, and batch-stream soaks.
NitroRuntime.releaseLibno longer throws on iOS/macOS.DynamicLibrary.process()/.executable()(static linking) cannot be closed —close()throwsBad state: ... can't be closed. The close is now skipped on iOS/macOS (only the ref-counted cache entry is dropped). This was a latent crash on the instance-teardown path — the GC finalizer anddispose().@nitroNativeAsyncgained an opt-in timeout:NitroConfig.nativeAsyncTimeoutMs(default0= wait forever, unchanged behavior). When> 0, a native impl that crashes or never posts a result now completes theFuturewith aTimeoutExceptionand releases theReceivePort+ per-call error slot, instead of hanging and leaking both.openNativeAsyncnow guarantees teardown (port close + error-slot free via acleanupcallback) on every terminal path — success, native error, or timeout.- New tests:
native_async_leak_test.dart(viaffi_leak_tracker) andnative_async_timeout_test.dart.
- Released alongside
nitro_generator0.5.17 (generated multi-instance registries switched to a weak cache + GC finalizer that frees native memory on drop — regenerate to pick it up). See its changelog.
0.5.16 #
- Runtime hot-path performance — allocation and copy reductions across the FFI marshalling layer. All changes are internal (no API, no wire-format changes); regenerate is not required for the runtime wins. Measured on the macOS C++ bridge, 5 independent benchmark runs averaged (non-overlapping ranges):
List<@HybridRecord>encode: −55.9% (3.78 → 1.67 µs, 16-item list).RecordWriter.encodeIndexedListno longer builds one 256-byteRecordWriterper item and copies each item twice — a single writer reserves the offset table, writes each item once, and backpatches the offsets. Paired withRecordWriter.toNativenow copying the payload once over a single typed-list view instead of taking a sublist and copying twice (#34).- FFI string decode: −18.4% (0.64 → 0.53 µs).
_decodeUtf8NoBomStripuses the VM-nativeUtf8Decoder(withallowMalformed: true) after stripping any leading BOM, instead of a per-byte code-point loop; the NUL scan uses an indexed load rather than allocating aPointerper byte (#31). - Fewer per-call allocations (GC-pressure relief; not separately timeable in a tight loop):
ZeroCopy*Buffercache their typed-list view instead of rebuilding it on every.bytes/.valuesaccess (#32);callAsyncandopenNativeAsyncgained the error-level fast path that skips theStopwatchand tag-string allocation, mirroringcallSync(#33); the error handlers cache theerrPtr.refstruct view once instead of rebuilding it 12+ times on the throw path (#37);_log's level check is an O(1) enum-index compare instead of twoindexOfscans;NitroPromiseallocates its listener lists lazily (#36).
- Two proposed micro-optimizations were deliberately NOT adopted, each because it changed observable behavior (new regression tests lock both):
- A strict
utf8.decode()fast path would have thrownFormatExceptionon malformed bytes that bridge strings (raw data, not text) may contain; the lenientallowMalformed: truedecoder is kept. Completer.sync()inNitroPromise(#36) andError.throwWithStackTraceinIsolatePool(#35) both deliver a rejection synchronously before its handler is attached — surfacing un-awaited rejections as unhandled errors, and throwing intoIsolatePool.dispose(). The async-completer /Future.errorforms are kept.
- A strict
- Credit to the community performance report (#31–#37) and PR #38 for surfacing these hot paths. New tests:
indexed_list_codec_test.dart,string_decode_test.dart,nitro_promise_test.dart(21 cases; the package's first NitroPromise coverage).
0.5.15 #
- Ecosystem sync — Released alongside
nitro_generator0.5.15 (Swift record initializers no longer use Swift 6.1+-only trailing commas — Xcode ≤ 16.2 compatible, #22) andnitrogen_cli0.5.15 (hand-added desktoppluginClassentries preserved, #23). No functional changes to this package — regenerate and re-link to pick them up.
0.5.14 #
0.5.13 #
- Ecosystem sync — Released alongside the nitro_webgpu feedback batch (issues #13–#20):
@NitroOwned(release:),Future<NativeHandle>native-async,@mainThread, nullable native-async kNull support, self-contained all-C++ Swift bridges, per-module SPM targets, user-owned Plugin.kt, and build_runner symlink-cycle guards. No functional changes to this package — see thenitro_generatorandnitrogen_clichangelogs, and regenerate your plugin to pick them up.
0.5.12 #
- Ecosystem sync — Aligned with
nitro_generator0.5.12's zero-copy TypedData fixes (missingrelease_typed_data_returndefinition on the pure-C++ path; Swift struct conversions dropping the synthesized length for@zeroCopyfields). No functional changes to this package — seenitro_generator's changelog, and regenerate your plugin to pick them up.
0.5.11 #
- Ecosystem sync — Aligned with
nitrogen_cli0.5.11's desktop developer-experience fixes (#10: pubspecpluginClasson FFI-only desktop platforms, #11: example app-runner CMakeLists portability, #12: per-platform separation transition) andnitro_generator0.5.11's platform-matrix/no-duplicate-definition test lock. No functional changes to this package — runnitrogen link(with the updated CLI) to pick up the project-file repairs.
0.5.10 #
- Windows heap-corruption fix (runtime side): native-owned memory is now freed by the native allocator, never by package:ffi's
malloc.free— package:ffi'smalloc/freebind toCoTaskMemAlloc/CoTaskMemFreeon Windows, but every pointer the native bridge hands to Dart (strdup'd strings, record blobs, struct copies, posted async results, stream items, the S8 error-slot's string fields) is allocated with C-runtimemalloc— freeing those withCoTaskMemFreeis undefined behavior and crashed the very first string-returning call on Windows.nitro_generator0.5.10's regenerated bridges now export a<lib>_nitro_freesymbol and route all such frees through it; this package adds the runtime halves:Pointer<Utf8>.toDartStringFreedBy(nativeFree)— liketoDartStringWithFree()(which remains, unchanged, for package:ffi-allocated strings) but releases via the caller-supplied free function.NitroRuntime.throwIfOutParamError/throwIfOutParamErrorAndFreegained an optionalnativeFree:parameter for the error struct's native strdup'd string fields (the struct itself stayscalloc-allocated/freed by Dart, which is correct on every platform). Omitting it preserves the old behavior.LazyRecordList.decodegained an optionalnativeFree:finalizer parameter (aPointer<NativeFinalizerFunction>) so lazily-decoded record-list buffers are also released by the native allocator when the list is GC'd; oneNativeFinalizeris cached per module. All additions are backward-compatible optional parameters — previously generated code keeps compiling and behaving as before (on POSIX, where the old behavior was already correct).
- Added:
NitroNativeAllocator— an [Allocator] backed by a module's exported<lib>_nitro_alloc/<lib>_nitro_free(plain C-runtimemalloc/free). The reverse direction of the same Windows rule: values Dart produces that NATIVE code frees (String/record/variant callback returns, which the native wrapper releases withfree()) must not come from package:ffi's CoTaskMem-backed allocators. Regenerated bridges pass it totoNativeUtf8(allocator:)/toNative(...)in callback trampolines; on Windows the old code froze the app at the first String-returning callback. - Dependency floor:
ffi: ^2.2.0. - Ecosystem sync — Aligned with
nitro_generator0.5.10's desktop C-bridge fixes: #9 (@NitroResult<record>compile error, nullable record/variant param segfault on@nitroNativeAsync), plus a further cluster found via a real Windows/Linux CI build —@nitroNativeAsyncdesktop dispatch mishandlingList<T>/Map<K,V>/callback params, a@NitroCustomTypeparam declaration mismatch between the generated header and the dispatch body, the Windows allocator mismatch above, and a desktop record/variant stream-emit wire-format fix (double length prefix + leak). Also aligned withnitrogen_cli0.5.10's new opt-in per-platform (Windows/Linux) native-implementation separation and its Androidconsumer-rules.progeneration (R8includedescriptorclasseskeep rules for the JNI bridge, so release-mode builds no longer risk stripping/renaming types referenced only from native code). Seenitro_generator's andnitrogen_cli's changelogs, and regenerate/re-link your plugin to pick these up.
0.5.9 #
- Added:
NitroRuntime.throwIfOutParamErrorAndFree— checks and frees a fresh-per-callNitroErrorFfiout-param slot, throwing aHybridExceptionif it carries an error. Used internally bynitro_generator's regenerated@nitroNativeAsynccall sites to propagate a thrown native exception back to Dart, which previously was silently discarded (aFuture<void>native-async method's thrown exception was completely invisible — the call always "succeeded"). Differs from the existingthrowIfOutParamError(used by sync calls, which reuse one instance-owned slot safe only because sync calls on an isolate are serialized): native-async calls aren't serialized, so each call gets its owncalloc'd struct, and this variant also frees the struct itself either way (the sync variant doesn't, since the instance-owned slot outlives every call). Not typically called directly by plugin authors. - Ecosystem sync — Aligned with
nitro_generator0.5.9's@nitroNativeAsyncerror-propagation fix. Seenitro_generator's changelog, and regenerate your plugin to pick it up.
0.5.8 #
- Ecosystem sync — Aligned with
nitro_generator0.5.8's@nitroNativeAsyncfixes (Map<String,V>/NitroAnyMapparams on Kotlin and Swift, bare@HybridStructreturns on Kotlin, andNitroAnyMapsupport on Swift). No functional changes to this package — seenitro_generator's changelog, and regenerate your plugin to pick it up.
0.5.7 #
- Added:
NitroRuntime.deferredClose— closes a replaced callbackNativeCallableon the next microtask turn, after native has synchronously switched over to its replacement. Used internally bynitro_generator's regenerated callback-setter helpers to fix a leak where every re-registration of a callback-typed parameter (e.g. a listener setter called with a fresh closure) allocated a newNativeCallablethat was never released. Not typically called directly by plugin authors. IsolatePoolworker: cachegetError/clearError.asFunction()bindings —_workerMainwas rebinding a fresh Dart closure around the same unchangedPointer<NativeFunction<...>>on every single@nitroAsyncdispatch. Now cached by pointer address inside each worker. Low-risk internal change; no API impact.- Corrected long-stale async performance figures across READMEs and
doc/advanced/async.md— the oft-repeated "@nitroAsync~930 µs,@nitroNativeAsync~146 µs" numbers predated the "Isolate Pool 2.0" persistent-reply-port optimization (0.3.1) and were never updated afterward. Measured current numbers (macOS,benchmarkpackage):@nitroAsync~28 µs,@nitroNativeAsync~27 µs — both roughly at parity with a Flutter method channel round-trip (~27 µs).doc/advanced/async.md's claim thatIsolatePooldefaults toPlatform.numberOfProcessorsworkers was also wrong — the real default is1; a bigger pool only helps concurrent throughput, not single-call latency, since the least-busy-worker scheduler is O(1) regardless of pool size. Thebenchmarkpackage now has a dedicatednitro_native_async_recordcase (there was previously no benchmark coverage for@nitroNativeAsyncat all) and a CI regression gate comparing both async paths against the method-channel baseline. - Ecosystem sync — Also aligned with
nitro_generator0.5.7's callbackNativeCallableleak fix (entirely in its generated Dart/Kotlin/C++ output — see its changelog, and regenerate your plugin to pick up both fixes).
0.5.6 #
- Ecosystem sync — Aligned with the 0.5.6 release. No changes to this package; the 0.5.6 fix (a JNI global-reference leak on Android zero-copy stream events that aborted the process after ~25 minutes of continuous streaming) is entirely in
nitro_generator's generated C++ bridge — see its changelog, and regenerate your plugin to pick it up.
0.5.5 #
- Ecosystem sync — Aligned with
nitro_annotations,nitro_generator, andnitrogen_cli0.5.5. No changes to this package's runtime code; the 0.5.5 fixes are entirely in the desktop C++ (NativeImpl.cppon Windows/Linux) generator path and thenitrogen link/nitrogen doctorCLI — seenitro_generator's andnitrogen_cli's changelogs for details.
0.5.4 #
- Ecosystem sync — Aligned with
nitro_annotations,nitro_generator, andnitrogen_cli0.5.4.
0.5.3 #
- Ecosystem sync — Aligned with
nitro_annotations,nitro_generator, andnitrogen_cli0.5.3.
0.5.2 #
- Ecosystem sync — Aligned with
nitro_annotations,nitro_generator, andnitrogen_cli0.5.2.
0.5.1 #
- Ecosystem sync — Aligned with
nitro_annotations,nitro_generator, andnitrogen_cli0.5.1.
0.5.0 #
- Fixed:
ReceivePortavailable in generatedpartfiles without extra imports —nitro.dartnow re-exportsReceivePortandSendPortfromdart:isolate(conditionally, with a web stub). Generated.g.dartfiles arepart ofthe user's spec file and cannot have their ownimportdirectives; they useReceivePortfor the callback-release port. Previously, specs that used callbacks required an explicitimport 'dart:isolate'in the spec file. - New:
lib/src/isolate_stub.dart— Web stub forReceivePort/SendPortused by the conditionaldart:isolatere-export.
0.4.6 #
- Ecosystem sync — Updated annotations and generator support.
0.4.5 #
- Ecosystem sync — Aligned with
nitro_annotations,nitro_generator, andnitrogen_cli0.4.5.
0.4.4 #
- Ecosystem sync — Aligned with
nitro_annotations,nitro_generator, andnitrogen_cli0.4.4.
0.4.3 #
- Ecosystem sync — Aligned with
nitro_annotations,nitro_generator, andnitrogen_cli0.4.3.
0.4.2 #
- Ecosystem sync — Aligned with
nitro_annotations,nitro_generator, andnitrogen_cli0.4.2.
0.4.1 #
- Ecosystem sync — Aligned with
nitro_annotations,nitro_generator, andnitrogen_cli0.4.1. - Improved:
build_runnerconstraint — Updated dev dependency to^2.15.0for compatibility with the upgradedanalyzerandsource_genused bynitro_generator0.4.1.
0.4.0 #
- New:
NitroRuntime.callSyncobservability —callSyncnow has the same developer experience ascallAsync: verbose call/completion logs, slow-call warnings, error logs with stack traces, and a zero-allocation fast path when logging is disabled. - SPM and CocoaPods support — The runtime library is compatible with both Swift Package Manager and CocoaPods. Plugins built with
nitrogen linkwork in either build system with no code changes. - Ecosystem sync — Aligned with
nitro_annotations,nitro_generator, andnitrogen_cli0.4.0.
0.3.3 #
- Improved: Ecosystem Sync — Synchronized to version 0.3.3.
0.3.2 #
- Improved: Ecosystem Sync — Synchronized to version 0.3.2.
- Improved: Nested
@HybridStructintegration — Works seamlessly withnitro_generator0.3.2, which now generates correctPointer<NestedFfi>types, recursivefreeFields(), and typedtoNative()/toDart()for nested struct fields. - Improved: Struct constructor styles — Generated FFI extensions respect positional and named constructor parameters as declared in your
.native.dartspec, sotoDart()calls always match the actual constructor signature.
0.3.1 #
-
Improved:
IsolatePool— persistent reply port — replaced per-callReceivePortallocation with a single pool-level port kept alive for the pool's lifetime. Each call is tagged with a monotonically-increasingcallId; aMap<int, Completer>demuxes responses without any OS port operation per call. -
Improved:
IsolatePool— least-busy scheduling — replaced round-robin with a per-worker in-flight counter; the dispatcher always picks the worker with the fewest pending calls, preventing a slow JNI/FFI call from blocking the next task. -
Improved:
IsolatePool—Completer.sync()— reply completers useCompleter.sync()to deliver values in the same microtask as the port message, removing one extra microtask hop per async call. -
Improved:
IsolatePool.dispose()— now idempotent; in-flight calls are completed withStateErrorso awaiting code never hangs; the reply port is closed and worker shutdown is signalled gracefully. -
New:
IsolatePooltests — 21 tests covering pool creation, return values, error propagation, callId uniqueness, least-busy scheduling, dispose idempotency, in-flight cancellation, and stress scenarios. -
New:
LazyRecordList<T>—record_codec.dartgains aListBase<T>implementation backed by a rawPointer<Uint8>and a pre-parsed offset table. Items are decoded on first access and cached; aNativeFinalizerbacked bymalloc.nativeFreefrees the buffer on GC. -
New:
RecordWriter.encodeIndexedList<T>— serialises a list of records into the indexed wire format:[int32 count | int64[count] byte_offsets | item_blobs...], enabling O(1) random access by the Dart reader. -
New:
RecordWriter.encodeIndexedPrimitiveList<T>— same indexed format for primitive-typed lists. -
New:
RecordReader.fromPayloadOffset(Pointer<Uint8>, int)— constructs a reader at an arbitrary byte offset within an existing payload, used byLazyRecordListto decode individual items on demand.
0.3.0 #
- Breaking: C++ Interface Pointer Generation — The C++ bridge generator now generates
void*interface pointers instead of concrete class pointers forHybridObjecttypes.- Impact: Existing C++ code that directly casts these pointers to concrete types will break and require updates.
- Benefit: This change ensures compatibility with the new C++ build system and allows for more flexible native module integration.
- Improved: Memory Safety: FFI generated code now uses
try-finallyblocks for all async and sync record/struct return paths, ensuringmalloc.freeis called even if decoding fails. - Improved: Thread Safety: The
HybridObjectimplementation now enforcescheckDisposed()guards on all native methods, includingFastvariants, to prevent use-after-dispose crashes. - Fixed: Fail-Fast Initialization:
NitroRuntimenow explicitly validates return codes from native initialization (e.g.,Dart_InitializeApiDL). If initialization fails, aStateErroris thrown immediately instead of failing silently later.
0.2.3 #
- Improved: Native Visibility Visibility: Updated
nitro.hto includeNITRO_EXPORTmacros by default, ensuring all native symbols are correctly exported for FFI across iOS, Android, macOS, and Windows. - Improved: Dependency Sync: Synchronized the Nitro ecosystem to version 0.2.3.
0.2.2 #
- Improved: annotation compatibility — verified full compatibility with Nitrogen 0.2.2's stable annotation resolution system, ensuring re-exported
@NitroModule,@HybridStruct, and@HybridEnumannotations are correctly identified by the code generator. - Added explicit
voidsupport in return types for allHybridObjectmethods.
0.2.1 #
- Moved all annotations to the separate
nitro_annotationspackage to improve generator platform compatibility. - Re-exported
nitro_annotationsfor backward compatibility. - Added explicit support for
macos,windows, andlinuxto the plugin configuration to resolvepub.devplatform detection warnings.
0.2.0 #
- New: Binary
RecordWriterandRecordReaderCodec — Compact little-endian protocol for@HybridRecordtypes, replacing JSON text serialization with direct binary field access over rawuint8_t*buffers.- Wire format:
int64(8B),float64(8B),bool(1B),String(4-byte length + UTF-8), nullable (1-byte tag), andlist(4-byte count). - High-performance
encodeList/decodeListfor collections of records or primitives. - Retains
dart:convertre-exports forMap<String, T>which still uses the JSON path.
- Wire format:
- New:
IsolatePool&NitroRuntime.init()— Fixed-size pool of persistent worker isolates with round-robin dispatch. Pre-warmed byinit()to eliminate the ~1–5 msIsolate.spawnoverhead on everycallAsync. - New:
NitroConfigRuntime Singleton — Configurable runtime behavior:debugMode: Enables verbose logging of bridge calls, streams, isolates, and lifecycles.logLevel: Granular control (none,error,warning,verbose).logHandler: Custom sink for logs (e.g., Firebase, Sentry, Crashlytics).slowCallThresholdUs: Configurable warning threshold for long-running async calls (default 16ms).
- Improved:
NitroRuntimeRobustness — Stream unpack errors are now always logged aterrorlevel with stack traces, ensuring they are never silently swallowed. AddeddebugLabelto streams for easier debugging. - Fix: Style & Linting — Renamed internal state variables (e.g.,
_released→released) to follow Dart conventions for local variables.
0.1.0 #
- Initial release of Nitro runtime.
- Support for
HybridObject,HybridStruct, andHybridEnum. - Support for synchronous and asynchronous bridge calls.
- Unified FFI bridge support for Android and iOS.