register static method

void register()

Register platform adapter with C++. Must be called FIRST during SDK init (before any C++ operations).

Implementation

static void register() {
  if (_isRegistered) {
    _logger.debug('Platform adapter already registered');
    return;
  }

  try {
    final lib = PlatformLoader.loadCommons();
    // Resolve the platform helper before giving commons any secure-storage
    // callback pointers. Missing helper symbols are an initialization error.
    DartBridgeSecureStorage.instance;

    // Allocate the platform adapter struct
    _adapterPtr = calloc<RacPlatformAdapterStruct>();
    final adapter = _adapterPtr!;

    // ABI guard (MUST be the first two fields). rac_init rejects the adapter
    // with RAC_ERROR_ABI_VERSION_MISMATCH unless these match the commons
    // build. 1 == RAC_PLATFORM_ADAPTER_ABI_VERSION.
    adapter.ref.abiVersion = 1;
    adapter.ref.structSize = sizeOf<RacPlatformAdapterStruct>();

    // Logging callback - MUST use NativeCallable.listener for thread safety
    // This allows C++ to call the logger from any thread (including background
    // threads used by LLM generation) without crashing with:
    // "Cannot invoke native callback from a different isolate"
    _loggerCallable = NativeCallable<RacLogCallbackNative>.listener(
      _platformLogCallback,
    );
    adapter.ref.log = _loggerCallable!.nativeFunction;

    // File operations
    adapter.ref.fileExists =
        Pointer.fromFunction<RacFileExistsCallbackNative>(
          _platformFileExistsCallback,
          _exceptionalReturnFalse,
        );
    adapter.ref.fileRead = Pointer.fromFunction<RacFileReadCallbackNative>(
      _platformFileReadCallback,
      _exceptionalReturnInt32,
    );
    adapter.ref.fileWrite = Pointer.fromFunction<RacFileWriteCallbackNative>(
      _platformFileWriteCallback,
      _exceptionalReturnInt32,
    );
    adapter.ref.fileDelete =
        Pointer.fromFunction<RacFileDeleteCallbackNative>(
          _platformFileDeleteCallback,
          _exceptionalReturnInt32,
        );

    // Synchronous platform-native secure storage.
    adapter.ref.secureGet = Pointer.fromFunction<RacSecureGetCallbackNative>(
      _platformSecureGetCallback,
      _exceptionalReturnSecureStorage,
    );
    adapter.ref.secureSet = Pointer.fromFunction<RacSecureSetCallbackNative>(
      _platformSecureSetCallback,
      _exceptionalReturnSecureStorage,
    );
    adapter.ref.secureDelete =
        Pointer.fromFunction<RacSecureDeleteCallbackNative>(
          _platformSecureDeleteCallback,
          _exceptionalReturnSecureStorage,
        );

    // Clock — intentionally null. C++ falls back to std::chrono::system_clock
    // in rac_time.cpp. This is the correct design for Dart FFI, not a
    // workaround:
    //   - Pointer.fromFunction trampolines are tied to the registering
    //     isolate and crash (SIGABRT) when invoked from non-Dart threads.
    //   - NativeCallable.listener is one-way/async and cannot return an
    //     Int64 synchronously.
    //   - rac_get_current_time_ms is called from C++ worker threads
    //     (download orchestrator std::thread, OkHttp transport pool) with
    //     no isolate affinity.
    // std::chrono::system_clock yields equivalent ms timestamps to Swift's
    // Foundation Date() callback, so no platform override is needed.
    adapter.ref.nowMs = nullptr;

    // Memory info callback
    adapter.ref.getMemoryInfo =
        Pointer.fromFunction<RacGetMemoryInfoCallbackNative>(
          _platformGetMemoryInfoCallback,
          _exceptionalReturnInt32,
        );

    // HTTP download callbacks — disabled because OkHttp transport vtable
    // handles all HTTP. Pointer.fromFunction trampolines are not safe to
    // call from the C++ worker thread spawned by the download orchestrator.
    adapter.ref.httpDownload = nullptr;
    adapter.ref.httpDownloadCancel = nullptr;
    adapter.ref.extractArchive = nullptr;

    // Directory enumeration — commons uses these from the model-registry
    // refresh path (rescan_local) and the canonical RAModelInfo factory
    // (is_downloaded gating for multi-file artifacts). Both are invoked
    // from the Dart-owned isolate that initiates the corresponding SDK
    // public API call, so Pointer.fromFunction trampolines are safe here
    // (same constraint as file_exists / file_read above). See
    // rac_platform_adapter.h doc-block for cross-SDK status.
    adapter.ref.fileListDirectory =
        Pointer.fromFunction<RacFileListDirectoryCallbackNative>(
          _platformFileListDirectoryCallback,
          _exceptionalReturnInt32,
        );
    adapter.ref.isNonEmptyDirectory =
        Pointer.fromFunction<RacIsNonEmptyDirectoryCallbackNative>(
          _platformIsNonEmptyDirectoryCallback,
          _exceptionalReturnFalse,
        );

    // Vendor ID — intentionally null. Apple-only slot per
    // rac_platform_adapter.h:get_vendor_id; commons calls it from
    // rac_device_get_or_create_persistent_id() only after secure_get
    // misses. Flutter pre-populates the device-id cache in
    // dart_bridge_device.dart::_getOrCreateDeviceId() using
    // device_info_plus (identifierForVendor on iOS, generated UUID on
    // Android) and writes it through secure_set, so the commons chain
    // resolves on the secure_get branch before reaching this slot. A
    // direct FFI trampoline cannot bridge UIDevice.identifierForVendor
    // anyway because Flutter exposes it only via the async
    // device_info_plus MethodChannel, which FFI's synchronous return
    // contract cannot await.
    adapter.ref.getVendorId = nullptr;

    adapter.ref.userData = nullptr;

    // Register with C++
    final setAdapter = lib
        .lookupFunction<
          Int32 Function(Pointer<RacPlatformAdapterStruct>),
          int Function(Pointer<RacPlatformAdapterStruct>)
        >('rac_set_platform_adapter');

    final result = setAdapter(adapter);
    if (result != RacResultCode.success) {
      _logger.error(
        'Failed to register platform adapter',
        metadata: {'error_code': result},
      );
      calloc.free(adapter);
      _adapterPtr = null;
      SDKException.throwIfError(result);
      throw StateError('Platform adapter registration failed (rc=$result)');
    }

    _isRegistered = true;
    _logger.debug('Platform adapter registered successfully');

    // Note: We don't free the adapter here as C++ holds a reference to it
    // It will be valid for the lifetime of the application
  } catch (_) {
    _logger.error('Platform adapter registration failed');
    rethrow;
  }
}