From 238a7ba8408056ba3326a3435aa858ad029f32da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Kopci=C5=84ski?= Date: Fri, 27 Mar 2026 13:35:17 +0100 Subject: [PATCH 1/7] added emulator detection --- .../src/main/cpp/ETInstallerModule.cpp | 13 +- .../rnexecutorch/RnExecutorchInstaller.cpp | 5 +- .../rnexecutorch/RnExecutorchInstaller.h | 113 +++++++++--------- .../ios/RnExecutorch/ETInstaller.mm | 4 +- packages/react-native-executorch/src/index.ts | 4 +- .../src/utils/ResourceFetcherUtils.ts | 7 ++ 6 files changed, 83 insertions(+), 63 deletions(-) diff --git a/packages/react-native-executorch/android/src/main/cpp/ETInstallerModule.cpp b/packages/react-native-executorch/android/src/main/cpp/ETInstallerModule.cpp index d69af8634b..0f67d057ef 100644 --- a/packages/react-native-executorch/android/src/main/cpp/ETInstallerModule.cpp +++ b/packages/react-native-executorch/android/src/main/cpp/ETInstallerModule.cpp @@ -4,6 +4,7 @@ #include #include +#include namespace rnexecutorch { JavaVM *java_machine; @@ -64,8 +65,18 @@ void ETInstallerModule::injectJSIBindings() { return std::vector(dataBytePtr, dataBytePtr + size); }; + auto isEmulator = []() { + char fingerprint[PROP_VALUE_MAX] = {0}; + char hardware[PROP_VALUE_MAX] = {0}; + __system_property_get("ro.build.fingerprint", fingerprint); + __system_property_get("ro.hardware", hardware); + std::string fp(fingerprint); + std::string hw(hardware); + return fp.find("generic") == 0 || hw == "goldfish" || hw == "ranchu"; + }(); + RnExecutorchInstaller::injectJSIBindings(jsiRuntime_, jsCallInvoker_, - fetchDataByUrl); + fetchDataByUrl, isEmulator); } } // namespace rnexecutorch diff --git a/packages/react-native-executorch/common/rnexecutorch/RnExecutorchInstaller.cpp b/packages/react-native-executorch/common/rnexecutorch/RnExecutorchInstaller.cpp index 1239642c5e..da75ab951c 100644 --- a/packages/react-native-executorch/common/rnexecutorch/RnExecutorchInstaller.cpp +++ b/packages/react-native-executorch/common/rnexecutorch/RnExecutorchInstaller.cpp @@ -34,9 +34,12 @@ FetchUrlFunc_t fetchUrlFunc; void RnExecutorchInstaller::injectJSIBindings( jsi::Runtime *jsiRuntime, std::shared_ptr jsCallInvoker, - FetchUrlFunc_t fetchDataFromUrl) { + FetchUrlFunc_t fetchDataFromUrl, bool isEmulator) { fetchUrlFunc = fetchDataFromUrl; + jsiRuntime->global().setProperty(*jsiRuntime, "__rne_isEmulator", + jsi::Value(isEmulator)); + jsiRuntime->global().setProperty( *jsiRuntime, "loadStyleTransfer", RnExecutorchInstaller::loadModel( diff --git a/packages/react-native-executorch/common/rnexecutorch/RnExecutorchInstaller.h b/packages/react-native-executorch/common/rnexecutorch/RnExecutorchInstaller.h index bd01abf256..0f94036f64 100644 --- a/packages/react-native-executorch/common/rnexecutorch/RnExecutorchInstaller.h +++ b/packages/react-native-executorch/common/rnexecutorch/RnExecutorchInstaller.h @@ -24,7 +24,7 @@ class RnExecutorchInstaller { static void injectJSIBindings(jsi::Runtime *jsiRuntime, std::shared_ptr jsCallInvoker, - FetchUrlFunc_t fetchDataFromUrl); + FetchUrlFunc_t fetchDataFromUrl, bool isEmulator); private: template @@ -56,72 +56,67 @@ class RnExecutorchInstaller { // access), then dispatch the heavy model construction to a background // thread and return a Promise. auto constructorArgs = - meta::createConstructorArgsWithCallInvoker( - args, runtime, jsCallInvoker); + meta::createConstructorArgsWithCallInvoker(args, runtime, + jsCallInvoker); return Promise::createPromise( runtime, jsCallInvoker, - [jsCallInvoker, - constructorArgs = - std::move(constructorArgs)](std::shared_ptr promise) { - threads::GlobalThreadPool::detach( - [jsCallInvoker, promise, - constructorArgs = std::move(constructorArgs)]() { - try { - auto modelImplementationPtr = std::apply( - [](auto &&...unpackedArgs) { - return std::make_shared( - std::forward( - unpackedArgs)...); - }, - std::move(constructorArgs)); + [jsCallInvoker, constructorArgs = std::move(constructorArgs)]( + std::shared_ptr promise) { + threads::GlobalThreadPool::detach([jsCallInvoker, promise, + constructorArgs = std::move( + constructorArgs)]() { + try { + auto modelImplementationPtr = std::apply( + [](auto &&...unpackedArgs) { + return std::make_shared( + std::forward( + unpackedArgs)...); + }, + std::move(constructorArgs)); - auto modelHostObject = - std::make_shared>( - modelImplementationPtr, jsCallInvoker); + auto modelHostObject = + std::make_shared>( + modelImplementationPtr, jsCallInvoker); - auto memoryLowerBound = - modelImplementationPtr->getMemoryLowerBound(); + auto memoryLowerBound = + modelImplementationPtr->getMemoryLowerBound(); - jsCallInvoker->invokeAsync( - [promise, modelHostObject, - memoryLowerBound](jsi::Runtime &rt) { - auto jsiObject = - jsi::Object::createFromHostObject( - rt, modelHostObject); - jsiObject.setExternalMemoryPressure( - rt, memoryLowerBound); - promise->resolve(std::move(jsiObject)); - }); - } catch (const rnexecutorch::RnExecutorchError &e) { - auto code = e.getNumericCode(); - auto msg = std::string(e.what()); - jsCallInvoker->invokeAsync( - [promise, code, msg](jsi::Runtime &rt) { - jsi::Object errorData(rt); - errorData.setProperty(rt, "code", code); - errorData.setProperty( - rt, "message", - jsi::String::createFromUtf8(rt, msg)); - promise->reject( - jsi::Value(rt, std::move(errorData))); - }); - } catch (const std::runtime_error &e) { - jsCallInvoker->invokeAsync( - [promise, msg = std::string(e.what())]() { - promise->reject(msg); - }); - } catch (const std::exception &e) { - jsCallInvoker->invokeAsync( - [promise, msg = std::string(e.what())]() { - promise->reject(msg); - }); - } catch (...) { - jsCallInvoker->invokeAsync([promise]() { - promise->reject(std::string("Unknown error")); + jsCallInvoker->invokeAsync([promise, modelHostObject, + memoryLowerBound]( + jsi::Runtime &rt) { + auto jsiObject = jsi::Object::createFromHostObject( + rt, modelHostObject); + jsiObject.setExternalMemoryPressure(rt, memoryLowerBound); + promise->resolve(std::move(jsiObject)); + }); + } catch (const rnexecutorch::RnExecutorchError &e) { + auto code = e.getNumericCode(); + auto msg = std::string(e.what()); + jsCallInvoker->invokeAsync([promise, code, + msg](jsi::Runtime &rt) { + jsi::Object errorData(rt); + errorData.setProperty(rt, "code", code); + errorData.setProperty( + rt, "message", jsi::String::createFromUtf8(rt, msg)); + promise->reject(jsi::Value(rt, std::move(errorData))); + }); + } catch (const std::runtime_error &e) { + jsCallInvoker->invokeAsync( + [promise, msg = std::string(e.what())]() { + promise->reject(msg); + }); + } catch (const std::exception &e) { + jsCallInvoker->invokeAsync( + [promise, msg = std::string(e.what())]() { + promise->reject(msg); }); - } + } catch (...) { + jsCallInvoker->invokeAsync([promise]() { + promise->reject(std::string("Unknown error")); }); + } + }); }); }); } diff --git a/packages/react-native-executorch/ios/RnExecutorch/ETInstaller.mm b/packages/react-native-executorch/ios/RnExecutorch/ETInstaller.mm index dcb40b29d8..2a8bc519ba 100644 --- a/packages/react-native-executorch/ios/RnExecutorch/ETInstaller.mm +++ b/packages/react-native-executorch/ios/RnExecutorch/ETInstaller.mm @@ -4,6 +4,7 @@ #import #import +#import #include #include @@ -41,8 +42,9 @@ @implementation ETInstaller throw std::runtime_error("Error fetching data from a url"); } }; + bool isEmulator = TARGET_OS_SIMULATOR; rnexecutorch::RnExecutorchInstaller::injectJSIBindings( - jsiRuntime, jsCallInvoker, fetchUrl); + jsiRuntime, jsCallInvoker, fetchUrl, isEmulator); NSLog(@"Successfully installed JSI bindings for react-native-executorch!"); return @true; diff --git a/packages/react-native-executorch/src/index.ts b/packages/react-native-executorch/src/index.ts index f9d9e32be9..87c6836e16 100644 --- a/packages/react-native-executorch/src/index.ts +++ b/packages/react-native-executorch/src/index.ts @@ -105,6 +105,7 @@ declare global { symbols: string, independentCharacters?: boolean ) => Promise; + var __rne_isEmulator: () => boolean; } // eslint-disable no-var @@ -124,7 +125,8 @@ if ( global.loadSpeechToText == null || global.loadTextToSpeechKokoro == null || global.loadOCR == null || - global.loadVerticalOCR == null + global.loadVerticalOCR == null || + global.__rne_isEmulator == null ) { if (!ETInstallerNativeModule) { throw new Error( diff --git a/packages/react-native-executorch/src/utils/ResourceFetcherUtils.ts b/packages/react-native-executorch/src/utils/ResourceFetcherUtils.ts index 689775f93d..52b17c736c 100644 --- a/packages/react-native-executorch/src/utils/ResourceFetcherUtils.ts +++ b/packages/react-native-executorch/src/utils/ResourceFetcherUtils.ts @@ -206,6 +206,11 @@ export namespace ResourceFetcherUtils { return 'UNKNOWN'; } + export function isEmulator(): boolean { + // eslint-disable-next-line camelcase + return !!(global as any).__rne_isEmulator; + } + function getModelNameFromUri(uri: string): string { const knownName = getModelNameForUrl(uri); if (knownName) { @@ -228,6 +233,8 @@ export namespace ResourceFetcherUtils { body: JSON.stringify({ modelName: getModelNameFromUri(uri), countryCode: getCountryCode(), + isEmulator: isEmulator(), + libVersion: LIB_VERSION, }), }); } catch (e) {} From a82516fa08d606e10d7547c45d1ae82aeebe2fad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Kopci=C5=84ski?= Date: Fri, 27 Mar 2026 13:45:00 +0100 Subject: [PATCH 2/7] Missing const, eslint --- .../src/main/cpp/ETInstallerModule.cpp | 15 +++-------- .../android/src/main/cpp/EmulatorDetection.h | 27 +++++++++++++++++++ .../src/constants/resourceFetcher.ts | 3 +++ packages/react-native-executorch/src/index.ts | 2 ++ .../src/utils/ResourceFetcherUtils.ts | 6 +++-- 5 files changed, 40 insertions(+), 13 deletions(-) create mode 100644 packages/react-native-executorch/android/src/main/cpp/EmulatorDetection.h diff --git a/packages/react-native-executorch/android/src/main/cpp/ETInstallerModule.cpp b/packages/react-native-executorch/android/src/main/cpp/ETInstallerModule.cpp index 0f67d057ef..1a637b15d1 100644 --- a/packages/react-native-executorch/android/src/main/cpp/ETInstallerModule.cpp +++ b/packages/react-native-executorch/android/src/main/cpp/ETInstallerModule.cpp @@ -2,9 +2,10 @@ #include +#include "EmulatorDetection.h" + #include #include -#include namespace rnexecutorch { JavaVM *java_machine; @@ -65,18 +66,10 @@ void ETInstallerModule::injectJSIBindings() { return std::vector(dataBytePtr, dataBytePtr + size); }; - auto isEmulator = []() { - char fingerprint[PROP_VALUE_MAX] = {0}; - char hardware[PROP_VALUE_MAX] = {0}; - __system_property_get("ro.build.fingerprint", fingerprint); - __system_property_get("ro.hardware", hardware); - std::string fp(fingerprint); - std::string hw(hardware); - return fp.find("generic") == 0 || hw == "goldfish" || hw == "ranchu"; - }(); + auto _isEmulator = isEmulator(); RnExecutorchInstaller::injectJSIBindings(jsiRuntime_, jsCallInvoker_, - fetchDataByUrl, isEmulator); + fetchDataByUrl, _isEmulator); } } // namespace rnexecutorch diff --git a/packages/react-native-executorch/android/src/main/cpp/EmulatorDetection.h b/packages/react-native-executorch/android/src/main/cpp/EmulatorDetection.h new file mode 100644 index 0000000000..affafdc1d3 --- /dev/null +++ b/packages/react-native-executorch/android/src/main/cpp/EmulatorDetection.h @@ -0,0 +1,27 @@ +#pragma once + +#include +#include + +namespace rnexecutorch { + +inline bool isEmulator() { + auto readProp = [](const char *key) -> std::string { + const prop_info *pi = __system_property_find(key); + if (pi == nullptr) return ""; + std::string result; + __system_property_read_callback( + pi, + [](void *cookie, const char * /*__name*/, const char *value, uint32_t /*__serial*/) { + *static_cast(cookie) = value; + }, + &result); + return result; + }; + + std::string fp = readProp("ro.build.fingerprint"); + std::string hw = readProp("ro.hardware"); + return fp.find("generic") == 0 || hw == "goldfish" || hw == "ranchu"; +} + +} // namespace rnexecutorch diff --git a/packages/react-native-executorch/src/constants/resourceFetcher.ts b/packages/react-native-executorch/src/constants/resourceFetcher.ts index 58a57fdd93..f52bcd95ad 100644 --- a/packages/react-native-executorch/src/constants/resourceFetcher.ts +++ b/packages/react-native-executorch/src/constants/resourceFetcher.ts @@ -1,2 +1,5 @@ export const DOWNLOAD_EVENT_ENDPOINT = 'https://ai.swmansion.com/telemetry/downloads/api/downloads'; + +// eslint-disable-next-line @typescript-eslint/no-var-requires +export const LIB_VERSION: string = require('../../package.json').version; diff --git a/packages/react-native-executorch/src/index.ts b/packages/react-native-executorch/src/index.ts index 87c6836e16..9db69771d5 100644 --- a/packages/react-native-executorch/src/index.ts +++ b/packages/react-native-executorch/src/index.ts @@ -105,6 +105,7 @@ declare global { symbols: string, independentCharacters?: boolean ) => Promise; + // eslint-disable-next-line camelcase var __rne_isEmulator: () => boolean; } // eslint-disable no-var @@ -126,6 +127,7 @@ if ( global.loadTextToSpeechKokoro == null || global.loadOCR == null || global.loadVerticalOCR == null || + // eslint-disable-next-line camelcase global.__rne_isEmulator == null ) { if (!ETInstallerNativeModule) { diff --git a/packages/react-native-executorch/src/utils/ResourceFetcherUtils.ts b/packages/react-native-executorch/src/utils/ResourceFetcherUtils.ts index 52b17c736c..d38ba0c903 100644 --- a/packages/react-native-executorch/src/utils/ResourceFetcherUtils.ts +++ b/packages/react-native-executorch/src/utils/ResourceFetcherUtils.ts @@ -1,6 +1,9 @@ import { ResourceSource } from '..'; import { getModelNameForUrl } from '../constants/modelUrls'; -import { DOWNLOAD_EVENT_ENDPOINT } from '../constants/resourceFetcher'; +import { + DOWNLOAD_EVENT_ENDPOINT, + LIB_VERSION, +} from '../constants/resourceFetcher'; /** * Http status codes @@ -207,7 +210,6 @@ export namespace ResourceFetcherUtils { } export function isEmulator(): boolean { - // eslint-disable-next-line camelcase return !!(global as any).__rne_isEmulator; } From 34ee77da3ef26ba90b648f05071b29d2f534041a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Kopci=C5=84ski?= Date: Fri, 27 Mar 2026 14:55:13 +0100 Subject: [PATCH 3/7] Changed to be complaint with API levels below 26 --- .../android/src/main/cpp/EmulatorDetection.h | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/packages/react-native-executorch/android/src/main/cpp/EmulatorDetection.h b/packages/react-native-executorch/android/src/main/cpp/EmulatorDetection.h index affafdc1d3..b25bebf219 100644 --- a/packages/react-native-executorch/android/src/main/cpp/EmulatorDetection.h +++ b/packages/react-native-executorch/android/src/main/cpp/EmulatorDetection.h @@ -1,22 +1,30 @@ #pragma once -#include #include +#include namespace rnexecutorch { inline bool isEmulator() { auto readProp = [](const char *key) -> std::string { +#if __ANDROID_API__ >= 26 const prop_info *pi = __system_property_find(key); - if (pi == nullptr) return ""; + if (pi == nullptr) + return ""; std::string result; __system_property_read_callback( pi, - [](void *cookie, const char * /*__name*/, const char *value, uint32_t /*__serial*/) { + [](void *cookie, const char * /*__name*/, const char *value, + uint32_t /*__serial*/) { *static_cast(cookie) = value; }, &result); return result; +#else + char value[PROP_VALUE_MAX] = {0}; + __system_property_get(key, value); + return std::string(value); +#endif }; std::string fp = readProp("ro.build.fingerprint"); From f483fc588f0f2a1821e1139138bf9872e41fb658 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Kopci=C5=84ski?= Date: Fri, 27 Mar 2026 14:58:22 +0100 Subject: [PATCH 4/7] ts types fix --- packages/react-native-executorch/src/index.ts | 2 +- .../react-native-executorch/src/utils/ResourceFetcherUtils.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react-native-executorch/src/index.ts b/packages/react-native-executorch/src/index.ts index 9db69771d5..76146dc079 100644 --- a/packages/react-native-executorch/src/index.ts +++ b/packages/react-native-executorch/src/index.ts @@ -106,7 +106,7 @@ declare global { independentCharacters?: boolean ) => Promise; // eslint-disable-next-line camelcase - var __rne_isEmulator: () => boolean; + var __rne_isEmulator: boolean; } // eslint-disable no-var diff --git a/packages/react-native-executorch/src/utils/ResourceFetcherUtils.ts b/packages/react-native-executorch/src/utils/ResourceFetcherUtils.ts index d38ba0c903..ced27a0b8e 100644 --- a/packages/react-native-executorch/src/utils/ResourceFetcherUtils.ts +++ b/packages/react-native-executorch/src/utils/ResourceFetcherUtils.ts @@ -210,7 +210,7 @@ export namespace ResourceFetcherUtils { } export function isEmulator(): boolean { - return !!(global as any).__rne_isEmulator; + return global.__rne_isEmulator; } function getModelNameFromUri(uri: string): string { From 6d35c3ae9b0b9230d13b30e7b9d8cd39f4b3e54f Mon Sep 17 00:00:00 2001 From: Mateusz Kopcinski <120639731+mkopcins@users.noreply.github.com> Date: Fri, 27 Mar 2026 15:01:46 +0100 Subject: [PATCH 5/7] Apply suggestion from @msluszniak Co-authored-by: Mateusz Sluszniak <56299341+msluszniak@users.noreply.github.com> --- .../android/src/main/cpp/EmulatorDetection.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-native-executorch/android/src/main/cpp/EmulatorDetection.h b/packages/react-native-executorch/android/src/main/cpp/EmulatorDetection.h index b25bebf219..d3a3870f05 100644 --- a/packages/react-native-executorch/android/src/main/cpp/EmulatorDetection.h +++ b/packages/react-native-executorch/android/src/main/cpp/EmulatorDetection.h @@ -23,7 +23,7 @@ inline bool isEmulator() { #else char value[PROP_VALUE_MAX] = {0}; __system_property_get(key, value); - return std::string(value); + return {value}; #endif }; From 7ede4038c63f256b524cdf353ccb93c0e782de41 Mon Sep 17 00:00:00 2001 From: Mateusz Kopcinski <120639731+mkopcins@users.noreply.github.com> Date: Fri, 27 Mar 2026 15:02:14 +0100 Subject: [PATCH 6/7] Apply suggestion from @msluszniak Co-authored-by: Mateusz Sluszniak <56299341+msluszniak@users.noreply.github.com> --- .../android/src/main/cpp/EmulatorDetection.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/react-native-executorch/android/src/main/cpp/EmulatorDetection.h b/packages/react-native-executorch/android/src/main/cpp/EmulatorDetection.h index d3a3870f05..63cc17e398 100644 --- a/packages/react-native-executorch/android/src/main/cpp/EmulatorDetection.h +++ b/packages/react-native-executorch/android/src/main/cpp/EmulatorDetection.h @@ -9,8 +9,9 @@ inline bool isEmulator() { auto readProp = [](const char *key) -> std::string { #if __ANDROID_API__ >= 26 const prop_info *pi = __system_property_find(key); - if (pi == nullptr) + if (pi == nullptr) { return ""; + } std::string result; __system_property_read_callback( pi, From d19b1d48ec56032de9aee8719730d2a2642a73cd Mon Sep 17 00:00:00 2001 From: Mateusz Sluszniak <56299341+msluszniak@users.noreply.github.com> Date: Fri, 27 Mar 2026 15:15:04 +0100 Subject: [PATCH 7/7] Apply suggestions from code review Co-authored-by: Mateusz Sluszniak <56299341+msluszniak@users.noreply.github.com> --- .../react-native-executorch/src/constants/resourceFetcher.ts | 1 - packages/react-native-executorch/src/index.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/packages/react-native-executorch/src/constants/resourceFetcher.ts b/packages/react-native-executorch/src/constants/resourceFetcher.ts index f52bcd95ad..ae6c5f1a3b 100644 --- a/packages/react-native-executorch/src/constants/resourceFetcher.ts +++ b/packages/react-native-executorch/src/constants/resourceFetcher.ts @@ -1,5 +1,4 @@ export const DOWNLOAD_EVENT_ENDPOINT = 'https://ai.swmansion.com/telemetry/downloads/api/downloads'; -// eslint-disable-next-line @typescript-eslint/no-var-requires export const LIB_VERSION: string = require('../../package.json').version; diff --git a/packages/react-native-executorch/src/index.ts b/packages/react-native-executorch/src/index.ts index 76146dc079..3f6b9fc4ce 100644 --- a/packages/react-native-executorch/src/index.ts +++ b/packages/react-native-executorch/src/index.ts @@ -127,7 +127,6 @@ if ( global.loadTextToSpeechKokoro == null || global.loadOCR == null || global.loadVerticalOCR == null || - // eslint-disable-next-line camelcase global.__rne_isEmulator == null ) { if (!ETInstallerNativeModule) {