diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml deleted file mode 100644 index a1c9e4a..0000000 --- a/.github/FUNDING.yml +++ /dev/null @@ -1,12 +0,0 @@ -# These are supported funding model platforms - -github: mrousavy -patreon: # Replace with a single Patreon username -open_collective: # Replace with a single Open Collective username -ko_fi: mrousavy -tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel -community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry -liberapay: # Replace with a single Liberapay username -issuehunt: # Replace with a single IssueHunt username -otechie: # Replace with a single Otechie username -custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] diff --git a/.github/workflows/build-android.yml b/.github/workflows/build-android.yml deleted file mode 100644 index dfa97bc..0000000 --- a/.github/workflows/build-android.yml +++ /dev/null @@ -1,60 +0,0 @@ -name: Build Android App - -on: - push: - branches: - - main - paths: - - '.github/workflows/build-android.yml' - - 'android/**' - - 'example/android/**' - - 'yarn.lock' - - 'example/yarn.lock' - pull_request: - paths: - - '.github/workflows/build-android.yml' - - 'android/**' - - 'example/android/**' - - 'yarn.lock' - - 'example/yarn.lock' - -jobs: - build: - name: Build Android Example App - runs-on: ubuntu-latest - defaults: - run: - working-directory: example/android - steps: - - uses: actions/checkout@v2 - - - name: Setup JDK 1.8 - uses: actions/setup-java@v1 - with: - java-version: 1.8 - - - name: Get yarn cache directory path - id: yarn-cache-dir-path - run: echo "::set-output name=dir::$(yarn cache dir)" - - name: Restore node_modules from cache - uses: actions/cache@v2 - id: yarn-cache - with: - path: ${{ steps.yarn-cache-dir-path.outputs.dir }} - key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} - restore-keys: | - ${{ runner.os }}-yarn- - - name: Install node_modules for example/ - run: yarn install --frozen-lockfile --cwd .. - - - name: Restore Gradle cache - uses: actions/cache@v2 - with: - path: | - ~/.gradle/caches - ~/.gradle/wrapper - key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} - restore-keys: | - ${{ runner.os }}-gradle- - - name: Run Gradle Build - run: ./gradlew assembleDebug diff --git a/.github/workflows/build-ios.yml b/.github/workflows/build-ios.yml deleted file mode 100644 index 50e8dc1..0000000 --- a/.github/workflows/build-ios.yml +++ /dev/null @@ -1,70 +0,0 @@ -name: Build iOS App - -on: - push: - branches: - - main - paths: - - '.github/workflows/build-ios.yml' - - 'ios/**' - - '*.podspec' - - 'example/ios/**' - pull_request: - paths: - - '.github/workflows/build-ios.yml' - - 'ios/**' - - '*.podspec' - - 'example/ios/**' - -jobs: - build: - name: Build iOS Example App - runs-on: macOS-latest - defaults: - run: - working-directory: example/ios - steps: - - uses: actions/checkout@v2 - - - name: Get yarn cache directory path - id: yarn-cache-dir-path - run: echo "::set-output name=dir::$(yarn cache dir)" - - name: Restore node_modules from cache - uses: actions/cache@v2 - id: yarn-cache - with: - path: ${{ steps.yarn-cache-dir-path.outputs.dir }} - key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} - restore-keys: | - ${{ runner.os }}-yarn- - - name: Install node_modules for example/ - run: yarn install --frozen-lockfile --cwd .. - - - name: Setup Ruby (bundle) - uses: ruby/setup-ruby@v1 - with: - ruby-version: 2.6 - bundler-cache: true - working-directory: example/ios - - - name: Restore Pods cache - uses: actions/cache@v2 - with: - path: | - example/ios/Pods - ~/Library/Caches/CocoaPods - ~/.cocoapods - key: ${{ runner.os }}-pods-${{ hashFiles('**/Podfile.lock') }} - restore-keys: | - ${{ runner.os }}-pods- - - name: Install Pods - run: bundle exec pod check || bundle exec pod install - - name: Build App - run: "xcodebuild \ - -workspace MultithreadingExample.xcworkspace \ - -scheme MultithreadingExample \ - -sdk iphonesimulator \ - -configuration Debug \ - -destination \"generic/platform=iOS Simulator\" \ - build \ - CODE_SIGNING_ALLOWED=NO" diff --git a/.github/workflows/validate-android.yml b/.github/workflows/validate-android.yml deleted file mode 100644 index 7fde06c..0000000 --- a/.github/workflows/validate-android.yml +++ /dev/null @@ -1,60 +0,0 @@ -name: Validate Android - -on: - push: - branches: - - main - paths: - - '.github/workflows/validate-android.yml' - - 'android/**' - - '.editorconfig' - pull_request: - paths: - - '.github/workflows/validate-android.yml' - - 'android/**' - - '.editorconfig' - -jobs: - lint: - name: Gradle Lint - runs-on: ubuntu-latest - defaults: - run: - working-directory: ./android - steps: - - uses: actions/checkout@v2 - - name: Setup JDK 1.8 - uses: actions/setup-java@v1 - with: - java-version: 1.8 - - - name: Get yarn cache directory path - id: yarn-cache-dir-path - run: echo "::set-output name=dir::$(yarn cache dir)" - - name: Restore node_modules from cache - uses: actions/cache@v2 - id: yarn-cache - with: - path: ${{ steps.yarn-cache-dir-path.outputs.dir }} - key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} - restore-keys: | - ${{ runner.os }}-yarn- - - name: Install node_modules - run: yarn install --frozen-lockfile --cwd .. - - - name: Restore Gradle cache - uses: actions/cache@v2 - with: - path: | - ~/.gradle/caches - ~/.gradle/wrapper - key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} - restore-keys: | - ${{ runner.os }}-gradle- - - name: Run Gradle Lint - run: ./gradlew lint - - - name: Parse Gradle Lint Report - uses: yutailang0119/action-android-lint@v1.0.2 - with: - xml_path: android/build/reports/lint-results.xml diff --git a/.github/workflows/validate-cpp.yml b/.github/workflows/validate-cpp.yml deleted file mode 100644 index 5179e07..0000000 --- a/.github/workflows/validate-cpp.yml +++ /dev/null @@ -1,31 +0,0 @@ -name: Validate C++ - -on: - push: - branches: - - main - paths: - - '.github/workflows/validate-cpp.yml' - - 'cpp/**' - pull_request: - paths: - - '.github/workflows/validate-cpp.yml' - - 'cpp/**' - -jobs: - lint: - name: cpplint - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: reviewdog/action-cpplint@master - with: - github_token: ${{ secrets.github_token }} - reporter: github-pr-review - flags: --linelength=230 - targets: --recursive cpp android/src/main/cpp - filter: "-legal/copyright\ - ,-readability/todo\ - ,-build/namespaces\ - ,-whitespace/comments\ - " diff --git a/.github/workflows/validate-js.yml b/.github/workflows/validate-js.yml deleted file mode 100644 index 6f41bdc..0000000 --- a/.github/workflows/validate-js.yml +++ /dev/null @@ -1,92 +0,0 @@ -name: Validate JS - -on: - push: - branches: - - main - paths: - - '.github/workflows/validate-js.yml' - - 'src/**' - - '*.json' - - '*.js' - - '*.lock' - - 'example/src/**' - - 'example/*.json' - - 'example/*.js' - - 'example/*.lock' - - 'example/*.tsx' - pull_request: - paths: - - '.github/workflows/validate-js.yml' - - 'src/**' - - '*.json' - - '*.js' - - '*.lock' - - 'example/src/**' - - 'example/*.json' - - 'example/*.js' - - 'example/*.lock' - - 'example/*.tsx' - -jobs: - compile: - name: Compile JS (tsc) - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - - name: Install reviewdog - uses: reviewdog/action-setup@v1 - - - name: Get yarn cache directory path - id: yarn-cache-dir-path - run: echo "::set-output name=dir::$(yarn cache dir)" - - name: Restore node_modules from cache - uses: actions/cache@v2 - id: yarn-cache - with: - path: ${{ steps.yarn-cache-dir-path.outputs.dir }} - key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} - restore-keys: | - ${{ runner.os }}-yarn- - - name: Install node_modules - run: yarn install --frozen-lockfile - - name: Install node_modules (example/) - run: yarn install --frozen-lockfile --cwd example - - - name: Run TypeScript # Reviewdog tsc errorformat: %f:%l:%c - error TS%n: %m - run: | - yarn typescript | reviewdog -name="tsc" -efm="%f(%l,%c): error TS%n: %m" -reporter="github-pr-review" -filter-mode="nofilter" -fail-on-error -tee - env: - REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - lint: - name: Lint JS (eslint, prettier) - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - - name: Get yarn cache directory path - id: yarn-cache-dir-path - run: echo "::set-output name=dir::$(yarn cache dir)" - - name: Restore node_modules from cache - uses: actions/cache@v2 - id: yarn-cache - with: - path: ${{ steps.yarn-cache-dir-path.outputs.dir }} - key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} - restore-keys: | - ${{ runner.os }}-yarn- - - name: Install node_modules - run: yarn install --frozen-lockfile - - name: Install node_modules (example/) - run: yarn install --frozen-lockfile --cwd example - - - name: Run ESLint - run: yarn lint -f @jamesacarr/github-actions - - - name: Run ESLint with auto-fix - run: yarn lint --fix - - - name: Verify no files have changed after auto-fix - run: git diff --exit-code HEAD diff --git a/README.md b/README.md index 79f936a..cbb6ebc 100644 --- a/README.md +++ b/README.md @@ -31,9 +31,7 @@ npm install react-native-multithreading npx pod-install ``` -Since JSI is not officially released, installing **react-native-multithreading** requires you to edit a few native files. See [the setup guide (**SETUP.md**)](./SETUP.md) for more details. - -> Requires react-native-reanimated [**2.1.0**](https://github.com/software-mansion/react-native-reanimated/releases/tag/2.1.0) or higher. +> Requires react-native-reanimated [**2.12.0**](https://github.com/software-mansion/react-native-reanimated/releases/tag/2.1.0) or higher. > 🎉 [v1.0](https://github.com/mrousavy/react-native-multithreading/releases/tag/1.0.0) with Android support is here! 🎉 diff --git a/SETUP.md b/SETUP.md deleted file mode 100644 index 5d318ca..0000000 --- a/SETUP.md +++ /dev/null @@ -1,163 +0,0 @@ -# Setup Guide - -## iOS - -Run: - -```sh -cd ios -pod install -``` - -## Android - -Since pure JSI Modules cannot be autolinked yet, you have to manually initialize them. - -
- - Without react-native-mmkv (or other JSI libs) - -
- -1. Open your app's `MainApplication.java` -2. Add the following code: - ```diff - package com.example.reactnativemultithreading; - - import android.app.Application; - import android.content.Context; - import com.facebook.react.PackageList; - import com.facebook.react.ReactApplication; - import com.facebook.react.ReactNativeHost; - import com.facebook.react.ReactPackage; - import com.facebook.react.ReactInstanceManager; - import com.facebook.soloader.SoLoader; - import java.lang.reflect.InvocationTargetException; - import java.util.List; - - +import com.reactnativemultithreading.MultithreadingJSIModulePackage; - +import com.facebook.react.bridge.JSIModulePackage; - - public class MainApplication extends Application implements ReactApplication { - - private final ReactNativeHost mReactNativeHost = - new ReactNativeHost(this) { - @Override - public boolean getUseDeveloperSupport() { - return BuildConfig.DEBUG; - } - - @Override - protected List getPackages() { - @SuppressWarnings("UnnecessaryLocalVariable") - List packages = new PackageList(this).getPackages(); - // Packages that cannot be autolinked yet can be added manually here, for MultithreadingExample: - // packages.add(new MyReactNativePackage()); - packages.add(new MultithreadingPackage()); - return packages; - } - - @Override - protected String getJSMainModuleName() { - return "index"; - } - - + // TODO: Remove this when JSI Modules can be autoinstalled (maybe RN 0.65) - + @Override - + protected JSIModulePackage getJSIModulePackage() { - + return new MultithreadingJSIModulePackage(); - + } - }; - ``` - -
- - - -
- - With react-native-mmkv (or other JSI libs) - -
- -1. Open your project in Android Studio -2. Open the folder where `MainApplication.java` lives (`src/main/java/...`) -3. Right click the folder, **New** > **Java class** -4. Call it whatever you prefer, in my case it's `ExampleJSIPackage` because my app is called "Example" -5. Add the following code: - - ```java - package com.example; - - import com.facebook.react.bridge.JSIModuleSpec; - import com.facebook.react.bridge.JavaScriptContextHolder; - import com.facebook.react.bridge.ReactApplicationContext; - import com.swmansion.reanimated.ReanimatedJSIModulePackage; - import com.reactnativemmkv.MultithreadingModule; - - import java.util.Collections; - import java.util.List; - - // TODO: Remove all of this when JSI Modules can be autoinstalled (maybe RN 0.65) - public class ExampleJSIPackage extends ReanimatedJSIModulePackage { - @Override - public List getJSIModules(ReactApplicationContext reactApplicationContext, JavaScriptContextHolder jsContext) { - super.getJSIModules(reactApplicationContext, jsContext); - MultithreadingModule.install(reactApplicationContext, jsContext); - return Collections.emptyList(); - } - } - ``` -6. Replace `com.example` (first line) with the correct package name -7. Replace `ExampleJSIPackage` with the file name you chose in step 4. -8. Open `MainApplication.java` and find the location where the `ReactNativeHost` is initialized. You have to override it's `getJSIModulePackage` method: - ```diff - package com.example.reactnativemultithreading; - - import android.app.Application; - import android.content.Context; - import com.facebook.react.PackageList; - import com.facebook.react.ReactApplication; - import com.facebook.react.ReactNativeHost; - import com.facebook.react.ReactPackage; - import com.facebook.react.ReactInstanceManager; - import com.facebook.soloader.SoLoader; - import java.lang.reflect.InvocationTargetException; - import java.util.List; - - +import com.example.ExampleJSIPackage; - +import com.facebook.react.bridge.JSIModulePackage; - - public class MainApplication extends Application implements ReactApplication { - - private final ReactNativeHost mReactNativeHost = - new ReactNativeHost(this) { - @Override - public boolean getUseDeveloperSupport() { - return BuildConfig.DEBUG; - } - - @Override - protected List getPackages() { - @SuppressWarnings("UnnecessaryLocalVariable") - List packages = new PackageList(this).getPackages(); - // Packages that cannot be autolinked yet can be added manually here, for MultithreadingExample: - // packages.add(new MyReactNativePackage()); - packages.add(new MultithreadingPackage()); - return packages; - } - - @Override - protected String getJSMainModuleName() { - return "index"; - } - - + // TODO: Remove this when JSI Modules can be autoinstalled (maybe RN 0.65) - + @Override - + protected JSIModulePackage getJSIModulePackage() { - + return new ExampleJSIModulePackage(); - + } - }; - ``` - -
diff --git a/android/.settings/org.eclipse.buildship.core.prefs b/android/.settings/org.eclipse.buildship.core.prefs deleted file mode 100644 index 8c253d6..0000000 --- a/android/.settings/org.eclipse.buildship.core.prefs +++ /dev/null @@ -1,13 +0,0 @@ -arguments= -auto.sync=false -build.scans.enabled=false -connection.gradle.distribution=GRADLE_DISTRIBUTION(VERSION(6.0)) -connection.project.dir= -eclipse.preferences.version=1 -gradle.user.home= -java.home=/Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home -jvm.arguments= -offline.mode=false -override.workspace.settings=true -show.console.view=true -show.executions.view=true diff --git a/android/CMakeLists.txt b/android/CMakeLists.txt index a9d5d62..bdd02e4 100644 --- a/android/CMakeLists.txt +++ b/android/CMakeLists.txt @@ -1,7 +1,5 @@ -cmake_minimum_required(VERSION 3.4.1) - -set (CMAKE_VERBOSE_MAKEFILE ON) -set (CMAKE_CXX_STANDARD 14) +cmake_minimum_required(VERSION 3.9.0) +project(rnmultithreading) set (CMAKE_CXX_FLAGS "-DFOLLY_NO_CONFIG=1 -DFOLLY_HAVE_CLOCK_GETTIME=1 -DFOLLY_HAVE_MEMRCHR=1 -DFOLLY_USE_LIBCPP=1 -DFOLLY_MOBILE=1 -DON_ANDROID") set (PACKAGE_NAME "rnmultithreading") @@ -9,12 +7,26 @@ set (BUILD_DIR ${CMAKE_SOURCE_DIR}/build) # rnmultithreading shared +if(${RN_MINOR_VERSION} LESS 66) + set ( + INCLUDE_JSI_CPP + "${REACT_NATIVE_DIR}/ReactCommon/jsi/jsi/jsi.cpp" + ) + set ( + INCLUDE_JSIDYNAMIC_CPP + "${REACT_NATIVE_DIR}/ReactCommon/jsi/jsi/JSIDynamic.cpp" + ) +endif() + add_library( ${PACKAGE_NAME} SHARED src/main/cpp/cpp-adapter.cpp + ../cpp/MultithreadingScheduler.cpp ../cpp/RNMultithreadingInstaller.cpp ../cpp/ThreadPool.cpp + ${INCLUDE_JSI_CPP} + ${INCLUDE_JSIDYNAMIC_CPP} ) # includes @@ -25,22 +37,23 @@ target_include_directories( ${PACKAGE_NAME} PRIVATE ${LIBFBJNI_INCLUDE_DIR} - ${BUILD_DIR}/third-party-ndk/boost - ${BUILD_DIR}/third-party-ndk/double-conversion - ${BUILD_DIR}/third-party-ndk/folly - ${BUILD_DIR}/third-party-ndk/glog - "${NODE_MODULES_DIR}/react-native/React" - "${NODE_MODULES_DIR}/react-native/React/Base" - "${NODE_MODULES_DIR}/react-native/ReactAndroid/src/main/jni" - "${NODE_MODULES_DIR}/react-native/ReactAndroid/src/main/java/com/facebook/react/turbomodule/core/jni" - "${NODE_MODULES_DIR}/react-native/ReactCommon" - "${NODE_MODULES_DIR}/react-native/ReactCommon/callinvoker" - "${NODE_MODULES_DIR}/react-native/ReactCommon/jsi" - "${NODE_MODULES_DIR}/react-native/ReactCommon/jsi" - "${NODE_MODULES_DIR}/react-native-reanimated/Common/cpp/headers/Tools" - "${NODE_MODULES_DIR}/react-native-reanimated/Common/cpp/headers/SpecTools" - "${NODE_MODULES_DIR}/react-native-reanimated/Common/cpp/headers/SharedItems" - "${NODE_MODULES_DIR}/react-native-reanimated/Common/cpp/headers/Registries" + "${BUILD_DIR}/third-party-ndk/boost/boost_${BOOST_VERSION}" + "${BUILD_DIR}/third-party-ndk/double-conversion" + "${BUILD_DIR}/third-party-ndk/folly" + "${BUILD_DIR}/third-party-ndk/glog/exported" + "${REACT_NATIVE_DIR}/React" + "${REACT_NATIVE_DIR}/React/Base" + "${REACT_NATIVE_DIR}/ReactAndroid/src/main/jni" + "${REACT_NATIVE_DIR}/ReactAndroid/src/main/java/com/facebook/react/turbomodule/core/jni" + "${REACT_NATIVE_DIR}/ReactCommon" + "${REACT_NATIVE_DIR}/ReactCommon/callinvoker" + "${REACT_NATIVE_DIR}/ReactCommon/jsi" + "${NODE_MODULES_DIR}/react-native-reanimated/Common/cpp/AnimatedSensor" + "${NODE_MODULES_DIR}/react-native-reanimated/Common/cpp/LayoutAnimations" + "${NODE_MODULES_DIR}/react-native-reanimated/Common/cpp/Tools" + "${NODE_MODULES_DIR}/react-native-reanimated/Common/cpp/SpecTools" + "${NODE_MODULES_DIR}/react-native-reanimated/Common/cpp/SharedItems" + "${NODE_MODULES_DIR}/react-native-reanimated/Common/cpp/Registries" "${NODE_MODULES_DIR}/react-native-reanimated/Common/cpp/hidden_headers" "../cpp" ) @@ -76,13 +89,6 @@ find_library( NO_CMAKE_FIND_ROOT_PATH ) -find_library( - REACT_NATIVE_UTILS_LIB - reactnativeutilsjni - PATHS ${LIBRN_DIR} - NO_CMAKE_FIND_ROOT_PATH -) - # linking find_library( @@ -93,12 +99,32 @@ find_library( # build shared lib +set_target_properties( + ${PACKAGE_NAME} PROPERTIES + CXX_STANDARD 17 + CXX_EXTENSIONS OFF + POSITION_INDEPENDENT_CODE ON +) + +if(${RN_MINOR_VERSION} LESS 66) + set (JSI_LIB "") +else() + find_library( + JSI_LIB + jsi + PATHS ${LIBRN_DIR} + NO_CMAKE_FIND_ROOT_PATH + ) +endif() + + + target_link_libraries( ${PACKAGE_NAME} ${LOG_LIB} ${REANIMATED_LIB} ${REACT_NATIVE_JNI_LIB} - ${REACT_NATIVE_UTILS_LIB} ${FBJNI_LIB} + ${JSI_LIB} android ) diff --git a/android/build.gradle b/android/build.gradle index 3e7ca8c..bdf5bd5 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,6 +1,14 @@ import groovy.json.JsonSlurper import org.apache.tools.ant.filters.ReplaceTokens + +def resolveBuildType() { + Gradle gradle = getGradle() + String tskReqStr = gradle.getStartParameter().getTaskRequests()['args'].toString() + + return tskReqStr.contains('Release') ? 'release' : 'debug' +} + buildscript { // The Android Gradle plugin is only required when opening the android folder stand-alone. // This avoids unnecessary downloads and potential conflicts when the library is included as a @@ -8,7 +16,7 @@ buildscript { //if (project == rootProject) { repositories { google() - jcenter() + mavenCentral() maven { url "https://plugins.gradle.org/m2/" } @@ -23,13 +31,51 @@ buildscript { apply plugin: 'com.android.library' apply plugin: "de.undercouch.download" +def inputFile = new File(rootDir, '../node_modules/react-native/package.json') +def json = new JsonSlurper().parseText(inputFile.text) +def reactNativeVersion = json.version as String +def (major, minor, patchhh) = reactNativeVersion.tokenize('.') +def rnMinorVersion = minor.toInteger(); + +def resolveReactNativeDirectory() { + return new File(['node', '--print', "path.dirname(require.resolve('react-native/package.json'))"].execute(null, rootDir).text.trim()) +} + +// third-party-ndk deps headers +// mostly a copy of https://github.com/software-mansion/react-native-reanimated/blob/master/android/build.gradle#L115 + +// You need to have following folders in this directory: +// - boost_1_63_0 +// - double-conversion-1.1.6 +// - folly-deprecate-dynamic-initializer +// - glog-0.3.5 +def dependenciesPath = System.getenv("REACT_NATIVE_DEPENDENCIES") + +// The Boost library is a very large download (>100MB). +// If Boost is already present on your system, define the REACT_NATIVE_BOOST_PATH env variable +// and the build will use that. +def boostPath = dependenciesPath ?: System.getenv("REACT_NATIVE_BOOST_PATH") +def reactNativeRootDir = resolveReactNativeDirectory() +def reactNativeThirdParty = new File("$reactNativeRootDir/ReactAndroid/src/main/jni/third-party") +def reactNativeAndroidDownloadDir = new File("$reactNativeRootDir/ReactAndroid/build/downloads") + +def downloadsDir = new File("$buildDir/downloads") +def thirdPartyNdkDir = new File("$buildDir/third-party-ndk") +def thirdPartyVersionsFile = new File("${rootDir}/../node_modules/react-native/ReactAndroid/gradle.properties") +def thirdPartyVersions = new Properties() +thirdPartyVersions.load(new FileInputStream(thirdPartyVersionsFile)) + +def BOOST_VERSION = thirdPartyVersions["BOOST_VERSION"] +def DOUBLE_CONVERSION_VERSION = thirdPartyVersions["DOUBLE_CONVERSION_VERSION"] +def FOLLY_VERSION = thirdPartyVersions["FOLLY_VERSION"] +def GLOG_VERSION = thirdPartyVersions["GLOG_VERSION"] + def getExtOrDefault(name, defaultValue) { return rootProject.ext.has(name) ? rootProject.ext.get(name) : defaultValue } android { - compileSdkVersion getExtOrDefault('compileSdkVersion', 28) - + compileSdkVersion getExtOrDefault('compileSdkVersion', 28) defaultConfig { minSdkVersion getExtOrDefault('minSdkVersion', 16) targetSdkVersion getExtOrDefault('targetSdkVersion', 28) @@ -38,7 +84,11 @@ android { cmake { cppFlags "-fexceptions", "-frtti", "-std=c++1y", "-DONANDROID" abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a' - arguments '-DANDROID_STL=c++_shared', "-DNODE_MODULES_DIR=${rootDir}/../node_modules" + arguments '-DANDROID_STL=c++_shared', + "-DNODE_MODULES_DIR=${rootDir}/../node_modules", + "-DRN_MINOR_VERSION=${rnMinorVersion}", + "-DBOOST_VERSION=${BOOST_VERSION}", + "-DREACT_NATIVE_DIR=${reactNativeRootDir.path}" } } } @@ -54,11 +104,11 @@ android { } packagingOptions { - excludes = ["**/libc++_shared.so"] + excludes = ["**/libc++_shared.so","**/libfbjni.so","**/libreactnativejni.so","**/libreactnativeutilsjni.so","**/libreanimated.so","**/libjsi.so"] } buildFeatures { - prefab true + prefab false } configurations { @@ -73,7 +123,6 @@ repositories { // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm url "$rootDir/../node_modules/react-native/android" } - jcenter() } dependencies { @@ -85,15 +134,17 @@ dependencies { extractHeaders("com.facebook.fbjni:fbjni:+:headers") //noinspection GradleDynamicVersion extractJNI("com.facebook.fbjni:fbjni:+") - - def rnAAR = fileTree("${rootDir}/../node_modules/react-native/android").matching({ it.include "**/**/*.aar" }).singleFile + def buildType = resolveBuildType() + def rnAarMatcher = "**/react-native/**/*${buildType}.aar" + if (rnMinorVersion < 69) { + rnAarMatcher = "**/**/*.aar" + } + def rnAAR = fileTree("${rootDir}/../node_modules/react-native/android").matching({ it.include rnAarMatcher }).singleFile def jscAAR = fileTree("${rootDir}/../node_modules/jsc-android/dist/org/webkit/android-jsc").matching({ it.include "**/**/*.aar" }).singleFile - def inputFile = new File(rootDir, '../node_modules/react-native/package.json') - def json = new JsonSlurper().parseText(inputFile.text) - def reactNativeVersion = json.version as String - def (major, minor, patch) = reactNativeVersion.tokenize('.') - def raAAR = "${rootDir}/../node_modules/react-native-reanimated/android/react-native-reanimated-${minor}.aar" + + + def raAAR = "${rootDir}/../node_modules/react-native-reanimated/android/react-native-reanimated-${minor}-hermes.aar" extractJNI(files(rnAAR, jscAAR, raAAR)) } @@ -128,141 +179,168 @@ task extractJNIFiles { } } -// third-party-ndk deps headers -// mostly a copy of https://github.com/software-mansion/react-native-reanimated/blob/master/android/build.gradle#L115 - -def downloadsDir = new File("$buildDir/downloads") -def thirdPartyNdkDir = new File("$buildDir/third-party-ndk") -def thirdPartyVersionsFile = new File("${rootDir}/../node_modules/react-native/ReactAndroid/gradle.properties") -def thirdPartyVersions = new Properties() -thirdPartyVersions.load(new FileInputStream(thirdPartyVersionsFile)) - -def BOOST_VERSION = thirdPartyVersions["BOOST_VERSION"] -def boost_file = new File(downloadsDir, "boost_${BOOST_VERSION}.tar.gz") -def DOUBLE_CONVERSION_VERSION = thirdPartyVersions["DOUBLE_CONVERSION_VERSION"] -def double_conversion_file = new File(downloadsDir, "double-conversion-${DOUBLE_CONVERSION_VERSION}.tar.gz") -def FOLLY_VERSION = thirdPartyVersions["FOLLY_VERSION"] -def folly_file = new File(downloadsDir, "folly-${FOLLY_VERSION}.tar.gz") -def GLOG_VERSION = thirdPartyVersions["GLOG_VERSION"] -def glog_file = new File(downloadsDir, "glog-${GLOG_VERSION}.tar.gz") - -task createNativeDepsDirectories { - downloadsDir.mkdirs() - thirdPartyNdkDir.mkdirs() +task createNativeDepsDirectories() { + downloadsDir.mkdirs() + thirdPartyNdkDir.mkdirs() } -task downloadBoost(dependsOn: createNativeDepsDirectories, type: Download) { - src("https://github.com/react-native-community/boost-for-react-native/releases/download/v${BOOST_VERSION.replace("_", ".")}-0/boost_${BOOST_VERSION}.tar.gz") - onlyIfNewer(true) - overwrite(false) - dest(boost_file) +def resolveTaskFactory(String taskName, String artifactLocalName, File reactNativeAndroidDownloadDir, File reanimatedDownloadDir) { + return tasks.create(name: taskName, dependsOn: createNativeDepsDirectories, type: Copy) { + from reactNativeAndroidDownloadDir + include artifactLocalName + into reanimatedDownloadDir + + onlyIf { + // First we check whether the file is already in our download directory + if (file("$reanimatedDownloadDir/$artifactLocalName").isFile()) { + return false + } + + // If it is not the case we check whether it was downloaded by ReactAndroid project + if (file("$reactNativeAndroidDownloadDir/$artifactLocalName").isFile()) { + return true + } + + return false + } + } } -task prepareBoost(dependsOn: downloadBoost, type: Copy) { - from(tarTree(resources.gzip(downloadBoost.dest))) - from("src/main/jni/third-party/boost/Android.mk") - include("Android.mk", "boost_${BOOST_VERSION}/boost/**/*.hpp", "boost/boost/**/*.hpp") - includeEmptyDirs = false - into("$thirdPartyNdkDir") // /boost_X_XX_X - doLast { - file("$thirdPartyNdkDir/boost_${BOOST_VERSION}").renameTo("$thirdPartyNdkDir/boost") - } +Task resolveBoost = resolveTaskFactory("resolveBoost", "boost_${BOOST_VERSION}.tar.gz", reactNativeAndroidDownloadDir, downloadsDir) +Task resolveDoubleConversion = resolveTaskFactory( + "resolveDoubleConversion", + "double-conversion-${DOUBLE_CONVERSION_VERSION}.tar.gz", + reactNativeAndroidDownloadDir, + downloadsDir +) +Task resolveFolly = resolveTaskFactory("resolveFolly", "folly-${FOLLY_VERSION}.tar.gz", reactNativeAndroidDownloadDir, downloadsDir) +Task resolveGlog = resolveTaskFactory("resolveGlog", "glog-${GLOG_VERSION}.tar.gz", reactNativeAndroidDownloadDir, downloadsDir) + + +task downloadBoost(dependsOn: resolveBoost, type: Download) { + def transformedVersion = BOOST_VERSION.replace("_", ".") + def artifactLocalName = "boost_${BOOST_VERSION}.tar.gz" + def srcUrl = "https://boostorg.jfrog.io/artifactory/main/release/${transformedVersion}/source/boost_${BOOST_VERSION}.tar.gz" + if (rnMinorVersion < 69) { + srcUrl = "https://github.com/react-native-community/boost-for-react-native/releases/download/v${transformedVersion}-0/boost_${BOOST_VERSION}.tar.gz" + } + src(srcUrl) + onlyIfNewer(true) + overwrite(false) + dest(new File(downloadsDir, artifactLocalName)) } -task downloadDoubleConversion(dependsOn: createNativeDepsDirectories, type: Download) { - src("https://github.com/google/double-conversion/archive/v${DOUBLE_CONVERSION_VERSION}.tar.gz") - onlyIfNewer(true) - overwrite(false) - dest(double_conversion_file) +task prepareBoost(dependsOn: boostPath ? [] : [downloadBoost], type: Copy) { + from(boostPath ?: tarTree(resources.gzip(downloadBoost.dest))) + from("$reactNativeThirdParty/boost/Android.mk") + include("Android.mk", "boost_${BOOST_VERSION}/boost/**/*.hpp", "boost/boost/**/*.hpp") + includeEmptyDirs = false + into("$thirdPartyNdkDir/boost") + doLast { + file("$thirdPartyNdkDir/boost/boost").renameTo("$thirdPartyNdkDir/boost/boost_${BOOST_VERSION}") + } } -task prepareDoubleConversion(dependsOn: downloadDoubleConversion, type: Copy) { - from(tarTree(downloadDoubleConversion.dest)) - from("src/main/jni/third-party/double-conversion/Android.mk") - include("double-conversion-${DOUBLE_CONVERSION_VERSION}/src/**/*", "Android.mk") - filesMatching("*/src/**/*", { fname -> fname.path = "double-conversion/${fname.name}" }) - includeEmptyDirs = false - into("$thirdPartyNdkDir/double-conversion") +task downloadDoubleConversion(dependsOn: resolveDoubleConversion, type: Download) { + src("https://github.com/google/double-conversion/archive/v${DOUBLE_CONVERSION_VERSION}.tar.gz") + onlyIfNewer(true) + overwrite(false) + dest(new File(downloadsDir, "double-conversion-${DOUBLE_CONVERSION_VERSION}.tar.gz")) } -task downloadFolly(dependsOn: createNativeDepsDirectories, type: Download) { - src("https://github.com/facebook/folly/archive/v${FOLLY_VERSION}.tar.gz") - onlyIfNewer(true) - overwrite(false) - dest(folly_file) +task prepareDoubleConversion(dependsOn: dependenciesPath ? [] : [downloadDoubleConversion], type: Copy) { + from(dependenciesPath ?: tarTree(downloadDoubleConversion.dest)) + from("$reactNativeThirdParty/double-conversion/Android.mk") + include("double-conversion-${DOUBLE_CONVERSION_VERSION}/src/**/*", "Android.mk") + filesMatching("*/src/**/*", { fname -> fname.path = "double-conversion/${fname.name}" }) + includeEmptyDirs = false + into("$thirdPartyNdkDir/double-conversion") } -task prepareFolly(dependsOn: downloadFolly, type: Copy) { - from(tarTree(downloadFolly.dest)) - from("src/main/jni/third-party/folly/Android.mk") - include("folly-${FOLLY_VERSION}/folly/**/*", "Android.mk") - eachFile { fname -> fname.path = (fname.path - "folly-${FOLLY_VERSION}/") } - includeEmptyDirs = false - into("$thirdPartyNdkDir/folly") +task downloadFolly(dependsOn: resolveFolly, type: Download) { + src("https://github.com/facebook/folly/archive/v${FOLLY_VERSION}.tar.gz") + onlyIfNewer(true) + overwrite(false) + dest(new File(downloadsDir, "folly-${FOLLY_VERSION}.tar.gz")) } -task downloadGlog(dependsOn: createNativeDepsDirectories, type: Download) { - src("https://github.com/google/glog/archive/v${GLOG_VERSION}.tar.gz") - onlyIfNewer(true) - overwrite(false) - dest(glog_file) +def follyReplaceContent = ''' + ssize_t r; + do { + r = open(name, flags, mode); + } while (r == -1 && errno == EINTR); + return r; +''' + +task prepareFolly(dependsOn: dependenciesPath ? [] : [downloadFolly], type: Copy) { + from(dependenciesPath ?: tarTree(downloadFolly.dest)) + from("$reactNativeThirdParty/folly/Android.mk") + include("folly-${FOLLY_VERSION}/folly/**/*", "Android.mk") + eachFile { fname -> fname.path = (fname.path - "folly-${FOLLY_VERSION}/") } + // Fixes problem with Folly failing to build on certain systems. See + // https://github.com/software-mansion/react-native-reanimated/issues/1024 + filter { line -> line.replaceAll("return int\\(wrapNoInt\\(open, name, flags, mode\\)\\);", follyReplaceContent) } + includeEmptyDirs = false + into("$thirdPartyNdkDir/folly") } -task prepareGlog(dependsOn: downloadGlog, type: Copy) { - from(tarTree(downloadGlog.dest)) - from("src/main/jni/third-party/glog/") - include("glog-${GLOG_VERSION}/src/**/*", "Android.mk", "config.h") - includeEmptyDirs = false - filesMatching("**/*.h.in") { - filter(ReplaceTokens, tokens: [ - ac_cv_have_unistd_h : "1", - ac_cv_have_stdint_h : "1", - ac_cv_have_systypes_h : "1", - ac_cv_have_inttypes_h : "1", - ac_cv_have_libgflags : "0", - ac_google_start_namespace : "namespace google {", - ac_cv_have_uint16_t : "1", - ac_cv_have_u_int16_t : "1", - ac_cv_have___uint16 : "0", - ac_google_end_namespace : "}", - ac_cv_have___builtin_expect : "1", - ac_google_namespace : "google", - ac_cv___attribute___noinline : "__attribute__ ((noinline))", - ac_cv___attribute___noreturn : "__attribute__ ((noreturn))", - ac_cv___attribute___printf_4_5: "__attribute__((__format__ (__printf__, 4, 5)))" - ]) - it.path = (it.name - ".in") - } - into("$thirdPartyNdkDir/glog") +task downloadGlog(dependsOn: resolveGlog, type: Download) { + src("https://github.com/google/glog/archive/v${GLOG_VERSION}.tar.gz") + onlyIfNewer(true) + overwrite(false) + dest(new File(downloadsDir, "glog-${GLOG_VERSION}.tar.gz")) +} - doLast { - copy { - from(fileTree(dir: "$thirdPartyNdkDir/glog", includes: ["stl_logging.h", "logging.h", "raw_logging.h", "vlog_is_on.h", "**/src/glog/log_severity.h"]).files) - includeEmptyDirs = false - into("$thirdPartyNdkDir/glog/exported/glog") +// Prepare glog sources to be compiled, this task will perform steps that normally should've been +// executed by automake. This way we can avoid dependencies on make/automake +task prepareGlog(dependsOn: dependenciesPath ? [] : [downloadGlog], type: Copy) { + duplicatesStrategy = "include" + from(dependenciesPath ?: tarTree(downloadGlog.dest)) + from("$reactNativeThirdParty/glog/") + include("glog-${GLOG_VERSION}/src/**/*", "Android.mk", "config.h") + includeEmptyDirs = false + filesMatching("**/*.h.in") { + filter(ReplaceTokens, tokens: [ + ac_cv_have_unistd_h : "1", + ac_cv_have_stdint_h : "1", + ac_cv_have_systypes_h : "1", + ac_cv_have_inttypes_h : "1", + ac_cv_have_libgflags : "0", + ac_google_start_namespace : "namespace google {", + ac_cv_have_uint16_t : "1", + ac_cv_have_u_int16_t : "1", + ac_cv_have___uint16 : "0", + ac_google_end_namespace : "}", + ac_cv_have___builtin_expect : "1", + ac_google_namespace : "google", + ac_cv___attribute___noinline : "__attribute__ ((noinline))", + ac_cv___attribute___noreturn : "__attribute__ ((noreturn))", + ac_cv___attribute___printf_4_5: "__attribute__((__format__ (__printf__, 4, 5)))" + ]) + it.path = (it.name - ".in") + } + into("$thirdPartyNdkDir/glog") + + doLast { + copy { + from(fileTree(dir: "$thirdPartyNdkDir/glog", includes: ["stl_logging.h", "logging.h", "raw_logging.h", "vlog_is_on.h", "**/src/glog/log_severity.h"]).files) + includeEmptyDirs = false + into("$thirdPartyNdkDir/glog/exported/glog") + } } - } } task prepareThirdPartyNdkHeaders { - if (!boost_file.exists()) { dependsOn(prepareBoost) - } - if (!double_conversion_file.exists()) { dependsOn(prepareDoubleConversion) - } - if (!folly_file.exists()) { dependsOn(prepareFolly) - } - if (!glog_file.exists()) { dependsOn(prepareGlog) - } } // pre-native build pipeline tasks.whenTaskAdded { task -> - if (task.name.contains('externalNativeBuild')) { + if (task.name.contains('externalNativeBuild') || task.name.contains('configureCMakeDebug') || task.name.contains('buildCMakeDebug')) { task.dependsOn(extractAARHeaders) task.dependsOn(extractJNIFiles) task.dependsOn(prepareThirdPartyNdkHeaders) diff --git a/android/src/main/cpp/AndroidScheduler.h b/android/src/main/cpp/AndroidScheduler.h index 787f2bb..ca64dc3 100644 --- a/android/src/main/cpp/AndroidScheduler.h +++ b/android/src/main/cpp/AndroidScheduler.h @@ -11,27 +11,27 @@ namespace reanimated { - using namespace facebook; +using namespace facebook; - class AndroidScheduler : public jni::HybridClass { - public: - static auto constexpr kJavaDescriptor = "Lcom/swmansion/reanimated/Scheduler;"; - static jni::local_ref initHybrid(jni::alias_ref jThis); - static void registerNatives(); +class AndroidScheduler : public jni::HybridClass { +public: + static auto constexpr kJavaDescriptor = "Lcom/swmansion/reanimated/Scheduler;"; + static jni::local_ref initHybrid(jni::alias_ref jThis); + static void registerNatives(); - std::shared_ptr getScheduler() { return scheduler_; } + std::shared_ptr getScheduler() { return scheduler_; } - void scheduleOnUI(); + void scheduleOnUI(); - private: - friend HybridBase; +private: + friend HybridBase; - void triggerUI(); + void triggerUI(); - jni::global_ref javaPart_; - std::shared_ptr scheduler_; + jni::global_ref javaPart_; + std::shared_ptr scheduler_; - explicit AndroidScheduler(jni::alias_ref jThis); - }; + explicit AndroidScheduler(jni::alias_ref jThis); +}; } diff --git a/android/src/main/cpp/cpp-adapter.cpp b/android/src/main/cpp/cpp-adapter.cpp index 3070961..01c6db5 100644 --- a/android/src/main/cpp/cpp-adapter.cpp +++ b/android/src/main/cpp/cpp-adapter.cpp @@ -1,99 +1,107 @@ #include #include +#include #include #include #include #include #include - +#include #include "RNMultithreadingInstaller.h" #include "Scheduler.h" -#include "AndroidErrorHandler.h" #include "AndroidScheduler.h" +#include "AndroidErrorHandler.h" +#include "MultithreadingScheduler.h" using namespace facebook; using namespace reanimated; - +using namespace mrousavy; +using namespace multithreading; struct MultithreadingModule : jni::JavaClass { -public: - __unused static constexpr auto kJavaDescriptor = "Lcom/reactnativemultithreading/MultithreadingModule;"; - - static constexpr auto TAG = "RNMultithreading"; - - static void registerNatives() { - javaClassStatic()->registerNatives({ - makeNativeMethod("installNative", - MultithreadingModule::installNative) - }); - } - -private: - static std::shared_ptr makeJSExecutorFactory() { - __android_log_write(ANDROID_LOG_INFO, TAG, "Calling Java method MultithreadingModule.makeJSExecutor()..."); - static const auto cls = javaClassStatic(); - static const auto method = cls->getStaticMethod("makeJSExecutor"); - auto result = method(cls); - __android_log_write(ANDROID_LOG_INFO, TAG, "JavaScriptExecutor created! Getting factory..."); - return result->cthis()->getExecutorFactory(); - } - - static void installNative(jni::alias_ref, - jlong jsiRuntimePointer, - jni::alias_ref jsCallInvokerHolder, - jni::alias_ref androidScheduler) { - - auto runtime = reinterpret_cast(jsiRuntimePointer); - - auto jsCallInvoker = jsCallInvokerHolder->cthis()->getCallInvoker(); - auto scheduler = androidScheduler->cthis()->getScheduler(); - scheduler->setJSCallInvoker(jsCallInvoker); - - auto makeScheduler = [scheduler]() -> std::shared_ptr { - return scheduler; - }; - auto makeErrorHandler = [](const std::shared_ptr& scheduler_) -> std::shared_ptr { - return std::make_shared(scheduler_); - }; - auto makeJsExecutor = []() -> std::unique_ptr { - __android_log_write(ANDROID_LOG_INFO, TAG, "Creating JSExecutorFactory.."); - try { - std::shared_ptr delegate = std::shared_ptr(); - std::shared_ptr jsQueue = std::shared_ptr(); - - auto jsExecutorFactory = makeJSExecutorFactory(); - __android_log_write(ANDROID_LOG_INFO, TAG, "Creating JSExecutor.."); - auto executor = jsExecutorFactory->createJSExecutor(delegate, - jsQueue); - auto runtimePointer = static_cast(executor->getJavaScriptContext()); - __android_log_write(ANDROID_LOG_INFO, TAG, "JSExecutor created!"); - - // I need to release the local shared_ptr because otherwise the returned jsi::Runtime will be destroyed immediately. - auto _ = executor.release(); - - return std::unique_ptr(runtimePointer); - } catch (std::exception& exc) { - // Fatal error - the runtime can't be created at all. - __android_log_write(ANDROID_LOG_ERROR, TAG, "Failed to create JSExecutor!"); - __android_log_write(ANDROID_LOG_ERROR, TAG, exc.what()); - abort(); - } - }; - mrousavy::multithreading::install(*runtime, makeJsExecutor, makeScheduler, makeErrorHandler); - - } + public: + __unused static constexpr auto kJavaDescriptor = "Lcom/reactnativemultithreading/MultithreadingModule;"; + + static constexpr auto TAG = "RNMultithreading"; + + static void registerNatives() { + + javaClassStatic()->registerNatives({ + makeNativeMethod("installNative", + MultithreadingModule::installNative) + }); + } + + private: + static std::shared_ptr makeJSExecutorFactory() { + __android_log_write(ANDROID_LOG_INFO, TAG, "Calling Java method MultithreadingModule.makeJSExecutor()..."); + + static const auto cls = javaClassStatic(); + static const auto method = cls->getStaticMethod("makeJSExecutor"); + auto result = method(cls); + __android_log_write(ANDROID_LOG_INFO, TAG, "JavaScriptExecutor created! Getting factory..."); + return result->cthis()->getExecutorFactory(); + } + + static void installNative(jni::alias_ref clazz, + jlong jsiRuntimePointer, + jni::alias_ref jsCallInvokerHolder, + jni::alias_ref androidScheduler) { + + + auto runtimeT = reinterpret_cast(jsiRuntimePointer); + auto jsCallInvoker = jsCallInvokerHolder->cthis()->getCallInvoker(); + auto scheduler = std::shared_ptr(androidScheduler->cthis()); + scheduler->setJSCallInvoker(jsCallInvoker); + + auto makeScheduler = [scheduler]() -> std::shared_ptr { + return scheduler; + }; + + auto makeErrorHandler = [](const std::shared_ptr &scheduler_) -> std::shared_ptr { + return std::make_shared(scheduler_); + }; + + auto makeRuntime = []() -> std::unique_ptr { + __android_log_write(ANDROID_LOG_INFO, TAG, "Creating JSExecutorFactory.."); + try { + std::shared_ptr delegate = std::shared_ptr(); + std::shared_ptr jsQueue = std::shared_ptr(); + + auto jsExecutorFactory = makeJSExecutorFactory(); + __android_log_write(ANDROID_LOG_INFO, TAG, "Creating JSExecutor.."); + auto executor = jsExecutorFactory->createJSExecutor(delegate, + jsQueue); + + + + auto runtimePointer = static_cast(executor->getJavaScriptContext()); + __android_log_write(ANDROID_LOG_INFO, TAG, "JSExecutor created!"); + // I need to release the local shared_ptr because otherwise the returned jsi::Runtime will be destroyed immediately. + auto _ = executor.release(); + + return std::unique_ptr(runtimePointer); + } catch (std::exception &exc) { + // Fatal error - the runtime can't be created at all. + __android_log_write(ANDROID_LOG_ERROR, TAG, "Failed to create JSExecutor!"); + __android_log_write(ANDROID_LOG_ERROR, TAG, exc.what()); + abort(); + } + }; + + + + mrousavy::multithreading::install(*runtimeT, makeRuntime, makeScheduler, makeErrorHandler); + } }; JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *) { - return facebook::jni::initialize(vm, [] { - MultithreadingModule::registerNatives(); - }); + return facebook::jni::initialize(vm, [] { + MultithreadingScheduler::registerNatives(); + MultithreadingModule::registerNatives(); + }); } -/* -To create the Scheduler/AndroidErrorHandler: -1.: #include -2.: class AndroidScheduler : public jni::HybridClass; -3.: jni::alias_ref androidScheduler -4.: api project(":react-native-reanimated") -*/ \ No newline at end of file + + + diff --git a/android/src/main/java/com/reactnativemultithreading/MultithreadingJSIModulePackage.java b/android/src/main/java/com/reactnativemultithreading/MultithreadingJSIModulePackage.java index 158761f..87f0214 100644 --- a/android/src/main/java/com/reactnativemultithreading/MultithreadingJSIModulePackage.java +++ b/android/src/main/java/com/reactnativemultithreading/MultithreadingJSIModulePackage.java @@ -8,12 +8,12 @@ import java.util.List; public class MultithreadingJSIModulePackage extends ReanimatedJSIModulePackage { - @Override - public List getJSIModules(ReactApplicationContext reactApplicationContext, JavaScriptContextHolder jsContext) { - try { - return super.getJSIModules(reactApplicationContext, jsContext); - } finally { - MultithreadingModule.install(reactApplicationContext, jsContext); - } - } + // @Override + // public List getJSIModules(ReactApplicationContext reactApplicationContext, JavaScriptContextHolder jsContext) { + // // try { + // // return super.getJSIModules(reactApplicationContext, jsContext); + // // } finally { + // // MultithreadingModule.install(reactApplicationContext, jsContext); + // // } + // } } diff --git a/android/src/main/java/com/reactnativemultithreading/MultithreadingModule.java b/android/src/main/java/com/reactnativemultithreading/MultithreadingModule.java index 127464a..a1acc73 100644 --- a/android/src/main/java/com/reactnativemultithreading/MultithreadingModule.java +++ b/android/src/main/java/com/reactnativemultithreading/MultithreadingModule.java @@ -1,10 +1,14 @@ package com.reactnativemultithreading; +import android.content.res.AssetManager; import android.util.Log; import androidx.annotation.Keep; import androidx.annotation.NonNull; +import com.facebook.jni.HybridData; +import com.facebook.react.bridge.NativeModule; +import com.facebook.react.bridge.ReactMethod; import com.facebook.hermes.reactexecutor.HermesExecutorFactory; import com.facebook.proguard.annotations.DoNotStrip; import com.facebook.react.bridge.JavaScriptContextHolder; @@ -13,24 +17,38 @@ import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.jscexecutor.JSCExecutorFactory; +import com.facebook.react.module.annotations.ReactModule; import com.facebook.react.turbomodule.core.CallInvokerHolderImpl; import com.facebook.react.turbomodule.core.interfaces.CallInvokerHolder; import com.facebook.soloader.SoLoader; import com.swmansion.reanimated.Scheduler; +import java.util.HashMap; +import java.util.Set; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + @Keep @DoNotStrip +@ReactModule(name = MultithreadingModule.NAME) public class MultithreadingModule extends ReactContextBaseJavaModule { + public static HashMap Plugins = new HashMap(); + ExecutorService executorService = Executors.newSingleThreadExecutor(); + MultithreadingScheduler multithreadingScheduler; + static { System.loadLibrary("reanimated"); System.loadLibrary("rnmultithreading"); } + public static final String NAME = "RNMultithreading"; static String TAG = "RNMultithreading"; + private final ReactApplicationContext reactContext; // Dummy so react native adds it to the Gradle Module System public MultithreadingModule(ReactApplicationContext context) { super(context); + this.reactContext = context; } @NonNull @@ -39,16 +57,34 @@ public String getName() { return TAG; } + private native void setupAssetManager(AssetManager assetManager); + private static native void installNative(long jsiRuntimePointer, CallInvokerHolderImpl jsCallInvokerHolder, - Scheduler scheduler); + MultithreadingScheduler scheduler); - public static void install(ReactApplicationContext context, JavaScriptContextHolder jsContext) { - CallInvokerHolderImpl holder = (CallInvokerHolderImpl) context.getCatalystInstance().getJSCallInvokerHolder(); - SoLoader.init(context, false); // <-- required for makeJSExecutorFactory later on - installNative(jsContext.get(), holder, new Scheduler(context)); + + @ReactMethod(isBlockingSynchronousMethod = true) + public boolean install() { + multithreadingScheduler = new MultithreadingScheduler(executorService); + JavaScriptContextHolder jsContext = getReactApplicationContext().getJavaScriptContextHolder(); + CallInvokerHolderImpl holder = (CallInvokerHolderImpl) reactContext.getCatalystInstance().getJSCallInvokerHolder(); + SoLoader.init(reactContext, false); // <-- required for makeJSExecutorFactory later on + setupAssetManager(getReactApplicationContext().getAssets()); + installNative(jsContext.get(), holder, multithreadingScheduler); + return true; } + public boolean installPlugin(String name,long runtime) { + if (Plugins.containsKey(name)) { + MultithreadingPlugin plugin = Plugins.get(name); + return plugin.registerWithRuntime(runtime); + } + return false; + + } + + // Called from the C++ code @SuppressWarnings({"unused", "RedundantSuppression"}) diff --git a/android/src/main/java/com/reactnativemultithreading/MultithreadingPackage.java b/android/src/main/java/com/reactnativemultithreading/MultithreadingPackage.java index eaf47cb..6b4237d 100644 --- a/android/src/main/java/com/reactnativemultithreading/MultithreadingPackage.java +++ b/android/src/main/java/com/reactnativemultithreading/MultithreadingPackage.java @@ -2,11 +2,13 @@ import androidx.annotation.NonNull; + import com.facebook.react.ReactPackage; import com.facebook.react.bridge.NativeModule; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.uimanager.ViewManager; +import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -14,7 +16,7 @@ public class MultithreadingPackage implements ReactPackage { @NonNull @Override public List createNativeModules(@NonNull ReactApplicationContext reactContext) { - return Collections.singletonList((NativeModule)new MultithreadingModule(reactContext)); + return Arrays.asList(new MultithreadingModule(reactContext)); } @NonNull diff --git a/android/src/main/java/com/reactnativemultithreading/MultithreadingPlugin.java b/android/src/main/java/com/reactnativemultithreading/MultithreadingPlugin.java new file mode 100644 index 0000000..5e6e472 --- /dev/null +++ b/android/src/main/java/com/reactnativemultithreading/MultithreadingPlugin.java @@ -0,0 +1,15 @@ +package com.reactnativemultithreading; + +import com.facebook.react.bridge.JavaScriptContextHolder; + +public abstract class MultithreadingPlugin { + public abstract String getName(); + + protected MultithreadingPlugin() {} + + public static void register( MultithreadingPlugin plugin) { + MultithreadingModule.Plugins.put(plugin.getName(),plugin); + } + + public abstract boolean registerWithRuntime(long runtime); +} diff --git a/android/src/main/java/com/reactnativemultithreading/MultithreadingScheduler.java b/android/src/main/java/com/reactnativemultithreading/MultithreadingScheduler.java new file mode 100644 index 0000000..29a6481 --- /dev/null +++ b/android/src/main/java/com/reactnativemultithreading/MultithreadingScheduler.java @@ -0,0 +1,28 @@ +package com.reactnativemultithreading; + +import com.facebook.jni.HybridData; +import com.facebook.proguard.annotations.DoNotStrip; +import java.util.concurrent.ExecutorService; + +@SuppressWarnings("JavaJniMissingFunction") +public class MultithreadingScheduler { + @SuppressWarnings({"unused", "FieldCanBeLocal"}) + @DoNotStrip + private final HybridData mHybridData; + private final ExecutorService mExecutorThread; + + public MultithreadingScheduler(ExecutorService executorThread) { + this.mExecutorThread = executorThread; + mHybridData = initHybrid(); + } + + private native HybridData initHybrid(); + private native void triggerUI(); + + @SuppressWarnings("unused") + @DoNotStrip + private void scheduleTrigger() { + mExecutorThread.submit(this::triggerUI); + } +} + diff --git a/cpp/MultithreadingScheduler.cpp b/cpp/MultithreadingScheduler.cpp new file mode 100644 index 0000000..052b463 --- /dev/null +++ b/cpp/MultithreadingScheduler.cpp @@ -0,0 +1,46 @@ +// +// Created by Marc Rousavy on 25.07.21. +// + +#include "MultithreadingScheduler.h" +#include + +namespace mrousavy { + + +namespace multithreading { + +using namespace facebook; +using TSelf = jni::local_ref; + +TSelf MultithreadingScheduler::initHybrid(jni::alias_ref jThis) { + return makeCxxInstance(jThis); +} + +void MultithreadingScheduler::scheduleOnUI(std::function job) { + // 1. add job to queue + uiJobs.push(job); + scheduleTrigger(); +} + +void MultithreadingScheduler::scheduleTrigger() { + // 2. schedule `triggerUI` to be called on the java thread + static auto method = javaPart_->getClass()->getMethod("scheduleTrigger"); + method(javaPart_.get()); +} + +void MultithreadingScheduler::triggerUI() { + // 3. call job we enqueued in step 1. + auto job = uiJobs.pop(); + job(); +} + +void MultithreadingScheduler::registerNatives() { + registerHybrid({ + makeNativeMethod("initHybrid", MultithreadingScheduler::initHybrid), + makeNativeMethod("triggerUI", MultithreadingScheduler::triggerUI), + }); +} + +}// namespace multithreading +} // namespace mrousavy \ No newline at end of file diff --git a/cpp/MultithreadingScheduler.h b/cpp/MultithreadingScheduler.h new file mode 100644 index 0000000..5a81eda --- /dev/null +++ b/cpp/MultithreadingScheduler.h @@ -0,0 +1,46 @@ +// +// Created by Marc Rousavy on 25.07.21. +// + +#pragma once + +#include "Scheduler.h" +#include +#include +#include + +namespace mrousavy +{ + +namespace multithreading +{ + +using namespace facebook; + +class MultithreadingScheduler: public reanimated::Scheduler, public jni::HybridClass +{ +public: + static auto constexpr kJavaDescriptor = "Lcom/reactnativemultithreading/MultithreadingScheduler;"; + static jni::local_ref initHybrid(jni::alias_ref jThis); + static void registerNatives(); + + // schedules the given job to be run on the VisionCamera FP Thread at some future point in time + void scheduleOnUI(std::function job) override; + +private: + friend HybridBase; + jni::global_ref javaPart_; + + explicit MultithreadingScheduler(jni::alias_ref jThis) + : + javaPart_(jni::make_global(jThis)) + {} + + // Schedules a call to `triggerUI` on the VisionCamera FP Thread + void scheduleTrigger(); + // Calls the latest job in the job queue + void triggerUI() override; +}; + +} // namespace multithreading +} // namespace mrousavy \ No newline at end of file diff --git a/cpp/RNMultithreadingInstaller.cpp b/cpp/RNMultithreadingInstaller.cpp index 7a3e6e5..05673d6 100644 --- a/cpp/RNMultithreadingInstaller.cpp +++ b/cpp/RNMultithreadingInstaller.cpp @@ -5,110 +5,448 @@ #if __has_include() #include -#include #include +#include #include #include #else + #include "Scheduler.h" -#include "ShareableValue.h" #include "RuntimeManager.h" +#include "ShareableValue.h" #include "RuntimeDecorator.h" #include "ErrorHandler.h" + #endif #ifdef ON_ANDROID + +#include "android/asset_manager.h" #include +#include "MultithreadingScheduler.h" + +AAssetManager *assetManager; + +static JavaVM *vm; + +static jobject multithreading_object; + +static jclass multithreading_class; + +/** + * A simple callback function that allows us to detach current JNI Environment + * when the thread + * See https://stackoverflow.com/a/30026231 for detailed explanation + */ + +void DeferThreadDetach(JNIEnv *env) +{ + static pthread_key_t thread_key; + + // Set up a Thread Specific Data key, and a callback that + // will be executed when a thread is destroyed. + // This is only done once, across all threads, and the value + // associated with the key for any given thread will initially + // be NULL. + static auto run_once = [] + { + const auto err = pthread_key_create(&thread_key, [](void *ts_env) + { + if (ts_env) { + vm->DetachCurrentThread(); + } + }); + if (err) { + // Failed to create TSD key. Throw an exception if you want to. + } + return 0; + }(); + + // For the callback to actually be executed when a thread exits + // we need to associate a non-NULL value with the key on that thread. + // We can use the JNIEnv* as that value. + const auto ts_env = pthread_getspecific(thread_key); + if (!ts_env) { + if (pthread_setspecific(thread_key, env)) { + // Failed to set thread-specific value for key. Throw an exception if you want to. + } + } +} + +/** + * Get a JNIEnv* valid for this thread, regardless of whether + * we're on a native thread or a Java thread. + * If the calling thread is not currently attached to the JVM + * it will be attached, and then automatically detached when the + * thread is destroyed. + * + * See https://stackoverflow.com/a/30026231 for detailed explanation + */ +JNIEnv *GetJniEnv() +{ + JNIEnv *env = nullptr; + // We still call GetEnv first to detect if the thread already + // is attached. This is done to avoid setting up a DetachCurrentThread + // call on a Java thread. + + // g_vm is a global. + auto get_env_result = vm->GetEnv((void **) &env, JNI_VERSION_1_6); + if (get_env_result == JNI_EDETACHED) { + if (vm->AttachCurrentThread(&env, NULL) == JNI_OK) { + DeferThreadDetach(env); + } + else { + // Failed to attach thread. Throw an exception if you want to. + } + } + else if (get_env_result == JNI_EVERSION) { + // Unsupported JNI version. Throw an exception if you want to. + } + return env; +} + #endif -namespace mrousavy { -namespace multithreading { +jsi::Value eval(jsi::Runtime &rt, const char *code) +{ + return rt.global().getPropertyAsFunction(rt, "eval").call(rt, code); +} - std::unique_ptr manager; +namespace mrousavy +{ +namespace multithreading +{ - void install(jsi::Runtime& runtime, - const std::function()>& makeRuntime, - const std::function()>& makeScheduler, - const std::function(std::shared_ptr)>& makeErrorHandler) { - auto pool = std::make_shared(1); - - // Quickly setup the runtime - this is executed in parallel so we have to join this on the JS thread if spawnThread is called before this finishes. - auto setupFutureSingle = pool->enqueue([makeScheduler, makeRuntime, makeErrorHandler]() { - #ifdef ON_ANDROID - // We need to attach this Thread to JNI because the Runtime is a HybridClass. - jni::ThreadScope::WithClassLoader([makeRuntime, makeScheduler, makeErrorHandler]() { - __unused jni::ThreadScope scope; - #endif - auto runtime = makeRuntime(); - reanimated::RuntimeDecorator::decorateRuntime(*runtime, "CUSTOM_THREAD_1"); - auto scheduler = makeScheduler(); - auto errorHandler = makeErrorHandler(scheduler); - manager = std::make_unique(std::move(runtime), - errorHandler, - scheduler); - #ifdef ON_ANDROID - }); - #endif - }); - auto setupFuture = std::make_shared>(std::move(setupFutureSingle)); - - // spawnThread(run: () => T): Promise - auto spawnThread = jsi::Function::createFromHostFunction(runtime, - jsi::PropNameID::forAscii(runtime, "spawnThread"), - 1, // run - [setupFuture, pool](jsi::Runtime& runtime, const jsi::Value& thisValue, const jsi::Value* arguments, size_t count) -> jsi::Value { - if (!arguments[0].isObject()) - throw jsi::JSError(runtime, "spawnThread: First argument has to be a function!"); - - if (setupFuture->valid()) - setupFuture->get(); // clears future, makes invalid - - auto worklet = reanimated::ShareableValue::adapt(runtime, arguments[0], manager.get()); - - auto spawnThreadCallback = jsi::Function::createFromHostFunction(runtime, - jsi::PropNameID::forAscii(runtime, "spawnThreadCallback"), - 2, - [worklet, pool](jsi::Runtime& runtime, const jsi::Value& thisValue, const jsi::Value* arguments, size_t count) -> jsi::Value { - auto resolverValue = std::make_shared((arguments[0].asObject(runtime))); - auto rejecterValue = std::make_shared((arguments[1].asObject(runtime))); - - auto resolver = [&runtime, resolverValue](const std::shared_ptr& shareableValue) { - manager->scheduler->scheduleOnJS([&runtime, resolverValue, shareableValue] () { - resolverValue->asObject(runtime).asFunction(runtime).call(runtime, shareableValue->getValue(runtime)); - }); +static std::unique_ptr manager; + +static auto getResolver(jsi::Runtime &runtime, const jsi::Value &arg) +{ + auto resolverValue = std::make_shared( + (arg.asObject(runtime))); + + auto resolver = [&runtime, resolverValue]( + const std::shared_ptr &shareableValue) + { + manager->scheduler->scheduleOnJS( + [&runtime, resolverValue, shareableValue]() + { + resolverValue->asObject( + runtime).asFunction( + runtime).call( + runtime, + shareableValue->getValue( + runtime)); + }); + }; + + return resolver; +} + +static auto getRejecter(jsi::Runtime &runtime, const jsi::Value &arg) +{ + auto rejecterValue = std::make_shared( + (arg.asObject(runtime))); + auto rejecter = [&runtime, rejecterValue]( + const std::string &message) + { + manager->scheduler->scheduleOnJS( + [&runtime, rejecterValue, message]() + { + rejecterValue->asObject( + runtime).asFunction( + runtime).call( + runtime, + jsi::JSError( + runtime, + message).value()); + }); + }; + return rejecter; +} + +template +static jsi::Value registerPromiseFunc(jsi::Runtime &runtime, const std::shared_ptr &pool, int paramCount, Func &&func) +{ + auto promiseFunc = [func, pool]( + jsi::Runtime &runtime, + const jsi::Value &thisValue, + const jsi::Value *arguments, + size_t count) -> jsi::Value + { + + auto resolver = getResolver(runtime, arguments[0]); + + auto rejecter = getRejecter(runtime, arguments[1]); + + auto task = [func, resolver, rejecter] + { + try { + auto res = func(); + resolver(res); + + } + catch (const std::exception &e) { + rejecter(e.what()); + } + }; + + pool->enqueue(task); + + return jsi::Value::undefined(); + }; + + auto promiseCallback = jsi::Function::createFromHostFunction( + runtime, + jsi::PropNameID::forAscii( + runtime, + "promiseCallback"), + paramCount, + promiseFunc); + + auto newPromise = runtime.global().getProperty( + runtime, "Promise"); + auto promise = newPromise + .asObject(runtime) + .asFunction(runtime) + .callAsConstructor( + runtime, + promiseCallback); + + return promise; +} + +template +static void setGlobalRuntimeProperty(jsi::Runtime &runtime, const char *name, int paramCount, Func &&func) +{ + + __android_log_print(ANDROID_LOG_ERROR, "setGlobalRuntimeProperty", "RUNTIME"); + auto f = jsi::Function::createFromHostFunction(runtime, jsi::PropNameID::forAscii(runtime, name), paramCount, func); + runtime.global().setProperty(runtime, name, std::move(f)); +} + +static void registerSetLoadModulesFromAssets(jsi::Runtime &runtime, const std::shared_ptr> &setupFuture, const std::shared_ptr &pool) +{ + auto setLoadModuleFromAssets = [setupFuture, pool]( + jsi::Runtime &runtime, + const jsi::Value &thisValue, + const jsi::Value *arguments, + size_t count) -> jsi::Value + { + + if (setupFuture->valid()) + setupFuture->get(); // clears future, makes invalid + + auto moduleFile = reanimated::ShareableValue::adapt( + runtime, arguments[0], + manager.get()); + auto moduleName = reanimated::ShareableValue::adapt(runtime, arguments[1], manager.get()); + + auto task = [moduleName, moduleFile] + { + auto &runtime = *manager->runtime; + std::string _moduleName = moduleName->getValue(runtime).asString(runtime).utf8(runtime); + auto moduleExists = ("typeof globalThis." + _moduleName + " === 'object'"); + if (eval(runtime, moduleExists.c_str()).getBool()) { + return reanimated::ShareableValue::adapt(runtime, true, manager.get()); + } + else { + std::string _moduleFile = moduleFile->getValue(runtime).asString(runtime).utf8(runtime); + AAsset *file = AAssetManager_open(assetManager, _moduleFile.c_str(), AASSET_MODE_BUFFER); + size_t fileLength = AAsset_getLength(file); + char *fileContent = new char[fileLength + 1]; + AAsset_read(file, fileContent, fileLength); + fileContent[fileLength] = '\0'; + runtime.evaluateJavaScript(std::make_unique(fileContent), ""); + auto defineGlobal = ("global." + _moduleName + " = " + "globalThis." + _moduleName); + eval(runtime, defineGlobal.c_str()); + delete[] fileContent; + return reanimated::ShareableValue::adapt(runtime, true, manager.get()); + } }; - auto rejecter = [&runtime, rejecterValue](const std::string& message) { - manager->scheduler->scheduleOnJS([&runtime, rejecterValue, message] () { - rejecterValue->asObject(runtime).asFunction(runtime).call(runtime, jsi::JSError(runtime, message).value()); - }); + + return registerPromiseFunc(runtime, pool, 2, task); + }; + + setGlobalRuntimeProperty(runtime, "loadModuleFromAssets", 2, std::move(setLoadModuleFromAssets)); +} + +static void registerDoWork(jsi::Runtime &runtime, const std::shared_ptr> &setupFuture, const std::shared_ptr &pool) +{ + auto setDoWork = [setupFuture, pool]( + jsi::Runtime &runtime, + const jsi::Value &thisValue, + const jsi::Value *arguments, + size_t count) -> jsi::Value + { + + if (setupFuture->valid()) + setupFuture->get(); // clears future, makes invalid + + auto code = reanimated::ShareableValue::adapt( + runtime, arguments[0], + manager.get()); + + auto task = [code] + { + auto &runtime = *manager->runtime; + std::string c = code->getValue( + runtime).asString( + runtime).utf8( + runtime); + auto result = eval( + runtime, + ("(" + + c + + ")").c_str()).asObject( + runtime).asFunction( + runtime).call( + runtime); + return reanimated::ShareableValue::adapt( + runtime, + result, + manager.get()); }; - - pool->enqueue([resolver, rejecter, worklet]() { - try { - auto& runtime = *manager->runtime; - - auto function = worklet->getValue(runtime).asObject(runtime).asFunction(runtime); - auto result = function.getFunction(runtime).callWithThis(runtime, function); - - auto shareableResult = reanimated::ShareableValue::adapt(runtime, result, manager.get()); - resolver(shareableResult); - } catch (std::exception& exc) { - rejecter(exc.what()); - } + + return registerPromiseFunc(runtime, pool, 2, task); + }; + setGlobalRuntimeProperty(runtime, "doWork", 1, setDoWork); +} + +static void registerSpawnThread(jsi::Runtime &runtime, const std::shared_ptr> &setupFuture, const std::shared_ptr &pool) +{ + + auto setSpawnThread = [setupFuture, pool]( + jsi::Runtime &runtime, + const jsi::Value &thisValue, + const jsi::Value *arguments, + size_t count) -> jsi::Value + { + + + if (!arguments[0].isObject()) + throw jsi::JSError( + runtime, + "spawnThread: First argument has to be a function!"); + + if (setupFuture->valid()) + setupFuture->get(); // clears future, makes invalid + + auto worklet = reanimated::ShareableValue::adapt( + runtime, + arguments[0], + manager.get()); + + auto task = [worklet] + { + auto &runtime = *manager->runtime; + auto function = worklet->getValue( + runtime).asObject( + runtime).asFunction( + runtime); + auto result = function.getFunction( + runtime).callWithThis( + runtime, + function); + return reanimated::ShareableValue::adapt( + runtime, + result, + manager.get()); + }; + return registerPromiseFunc(runtime, pool, 2, task); + }; + + setGlobalRuntimeProperty(runtime, "spawnThread", 1, setSpawnThread); +} + +static void registerLoadPlugin(jsi::Runtime &runtime, const std::shared_ptr> &setupFuture, const std::shared_ptr &pool) +{ + + auto setLoadPlugin = [setupFuture, pool]( + jsi::Runtime &runtime, + const jsi::Value &thisValue, + const jsi::Value *arguments, + size_t count) -> jsi::Value + { + if (setupFuture->valid()) + setupFuture->get(); // clears future, makes invalid + + auto pluginName = reanimated::ShareableValue::adapt(runtime, arguments[0], manager.get()); + + pool->enqueue([pluginName]() + { + JNIEnv *jniEnv = GetJniEnv(); + auto &nextRuntime = *manager.get()->runtime; + auto _pluginName = pluginName->getValue(nextRuntime).asString(nextRuntime).utf8(nextRuntime); + multithreading_class = jniEnv->GetObjectClass(multithreading_object); + jmethodID installPlugin = jniEnv->GetMethodID(multithreading_class, "installPlugin", "(Ljava/lang/String;J)Z"); + long long runtimeAddr = reinterpret_cast(&nextRuntime); + bool result = jniEnv->CallBooleanMethod(multithreading_object, installPlugin, jniEnv->NewStringUTF(_pluginName.c_str()), runtimeAddr); + eval(nextRuntime, "console.log('second runtime is working')"); + }); + + return jsi::Value(true); + }; + + setGlobalRuntimeProperty(runtime, "loadPlugin", 1, setLoadPlugin); +} + +void install(jsi::Runtime &runtime, + const std::function()> &makeRuntime, + const std::function()> &makeScheduler, + const std::function( + std::shared_ptr)> &makeErrorHandler) +{ + auto pool = std::make_shared(1); + + // Quickly setup the runtime - this is executed in parallel so we have to join this on the JS thread if spawnThread is called before this finishes. + auto setupFutureSingle = pool->enqueue( + [makeScheduler, makeRuntime, makeErrorHandler]() + { +#ifdef ON_ANDROID + // We need to attach this Thread to JNI because the Runtime is a HybridClass. + jni::ThreadScope::WithClassLoader( + [makeRuntime, makeScheduler, makeErrorHandler]() + { + __unused jni::ThreadScope scope; +#endif + auto runtime = makeRuntime(); + reanimated::RuntimeDecorator::decorateRuntime(*runtime, + "CUSTOM_THREAD_1"); + auto scheduler = makeScheduler(); + auto errorHandler = makeErrorHandler(scheduler); + manager = std::make_unique( + std::move(runtime), + errorHandler, + scheduler); +#ifdef ON_ANDROID + }); +#endif }); - return jsi::Value::undefined(); - }); - - auto newPromise = runtime.global().getProperty(runtime, "Promise"); - auto promise = newPromise - .asObject(runtime) - .asFunction(runtime) - .callAsConstructor(runtime, spawnThreadCallback); - - return promise; - }); - runtime.global().setProperty(runtime, "spawnThread", std::move(spawnThread)); - } + auto setupFuture = std::make_shared>(std::move(setupFutureSingle)); + + registerLoadPlugin(runtime, setupFuture, pool); + registerSetLoadModulesFromAssets(runtime, setupFuture, pool); + registerDoWork(runtime, setupFuture, pool); + registerSpawnThread(runtime, setupFuture, pool); +} } // namespace multithreading } // namespace mrousavy + + +#ifdef ON_ANDROID +extern "C" +JNIEXPORT void JNICALL +Java_com_reactnativemultithreading_MultithreadingModule_setupAssetManager(JNIEnv *env, jobject thiz, jobject asset_manager) +{ + assetManager = AAssetManager_fromJava(env, asset_manager); + env->GetJavaVM(&vm); + multithreading_object = env->NewGlobalRef(thiz); + + if (assetManager == nullptr) { + __android_log_print(ANDROID_LOG_ERROR, "NDK_ASSET_MANAGER_EXAMPLE", "Could not load assetManager"); + return; + } + __android_log_print(ANDROID_LOG_INFO, "NDK_ASSET_MANAGER_EXAMPLE", "Successfully set asset manager in native module"); +} +#endif diff --git a/cpp/RNMultithreadingInstaller.h b/cpp/RNMultithreadingInstaller.h index 93cc7fd..1128926 100644 --- a/cpp/RNMultithreadingInstaller.h +++ b/cpp/RNMultithreadingInstaller.h @@ -11,6 +11,11 @@ #include "ErrorHandler.h" #endif + +#ifdef ON_ANDROID +#include "android/asset_manager_jni.h" +#include "MultithreadingScheduler.h" +#endif using namespace facebook; namespace mrousavy { @@ -18,8 +23,9 @@ namespace multithreading { void install(jsi::Runtime& runtime, const std::function()>& makeRuntime, - const std::function()>& makeScheduler, - const std::function(std::shared_ptr)>& makeErrorHandler); + const std::function()>& makeScheduler, + const std::function(std::shared_ptr)>& makeErrorHandler); } + } diff --git a/example/.buckconfig b/example/.buckconfig new file mode 100644 index 0000000..934256c --- /dev/null +++ b/example/.buckconfig @@ -0,0 +1,6 @@ + +[android] + target = Google Inc.:Google APIs:23 + +[maven_repositories] + central = https://repo1.maven.org/maven2 diff --git a/example/.bundle/config b/example/.bundle/config new file mode 100644 index 0000000..848943b --- /dev/null +++ b/example/.bundle/config @@ -0,0 +1,2 @@ +BUNDLE_PATH: "vendor/bundle" +BUNDLE_FORCE_RUBY_PLATFORM: 1 diff --git a/example/.eslintrc.js b/example/.eslintrc.js new file mode 100644 index 0000000..dcf0be0 --- /dev/null +++ b/example/.eslintrc.js @@ -0,0 +1,16 @@ +module.exports = { + root: true, + extends: '@react-native-community', + parser: '@typescript-eslint/parser', + plugins: ['@typescript-eslint'], + overrides: [ + { + files: ['*.ts', '*.tsx'], + rules: { + '@typescript-eslint/no-shadow': ['error'], + 'no-shadow': 'off', + 'no-undef': 'off', + }, + }, + ], +}; diff --git a/example/.gitignore b/example/.gitignore new file mode 100644 index 0000000..2423126 --- /dev/null +++ b/example/.gitignore @@ -0,0 +1,64 @@ +# OSX +# +.DS_Store + +# Xcode +# +build/ +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +xcuserdata +*.xccheckout +*.moved-aside +DerivedData +*.hmap +*.ipa +*.xcuserstate +ios/.xcode.env.local + +# Android/IntelliJ +# +build/ +.idea +.gradle +local.properties +*.iml +*.hprof +.cxx/ + +# node.js +# +node_modules/ +npm-debug.log +yarn-error.log + +# BUCK +buck-out/ +\.buckd/ +*.keystore +!debug.keystore + +# fastlane +# +# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the +# screenshots whenever they are needed. +# For more information about the recommended setup visit: +# https://docs.fastlane.tools/best-practices/source-control/ + +**/fastlane/report.xml +**/fastlane/Preview.html +**/fastlane/screenshots +**/fastlane/test_output + +# Bundle artifact +*.jsbundle + +# Ruby / CocoaPods +/ios/Pods/ +/vendor/bundle/ diff --git a/example/.node-version b/example/.node-version new file mode 100644 index 0000000..b6a7d89 --- /dev/null +++ b/example/.node-version @@ -0,0 +1 @@ +16 diff --git a/example/.prettierrc.js b/example/.prettierrc.js new file mode 100644 index 0000000..2b54074 --- /dev/null +++ b/example/.prettierrc.js @@ -0,0 +1,7 @@ +module.exports = { + arrowParens: 'avoid', + bracketSameLine: true, + bracketSpacing: false, + singleQuote: true, + trailingComma: 'all', +}; diff --git a/example/.ruby-version b/example/.ruby-version new file mode 100644 index 0000000..a603bb5 --- /dev/null +++ b/example/.ruby-version @@ -0,0 +1 @@ +2.7.5 diff --git a/example/.watchmanconfig b/example/.watchmanconfig new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/example/.watchmanconfig @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/example/App.tsx b/example/App.tsx new file mode 100644 index 0000000..4bc3ab2 --- /dev/null +++ b/example/App.tsx @@ -0,0 +1,32 @@ +import React from 'react'; +import {Button, SafeAreaView, useColorScheme, ViewStyle} from 'react-native'; +import {Colors} from 'react-native/Libraries/NewAppScreen'; +import RNMultiThreading from 'react-native-multithreading'; + +const App = () => { + const isDarkMode = useColorScheme() === 'dark'; + + const backgroundStyle: ViewStyle = { + backgroundColor: isDarkMode ? Colors.darker : Colors.lighter, + justifyContent: 'center', + alignItems: 'center', + flex: 1, + }; + + return ( + +