diff --git a/bun.lockb b/bun.lockb index f4675b4df..5705a1730 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/example/android/app/src/main/java/com/nitroexample/MainActivity.kt b/example/android/app/src/main/java/com/nitroexample/MainActivity.kt index ed1693933..c647184fa 100644 --- a/example/android/app/src/main/java/com/nitroexample/MainActivity.kt +++ b/example/android/app/src/main/java/com/nitroexample/MainActivity.kt @@ -1,5 +1,6 @@ package com.margelo.nitroexample +import android.os.Bundle import com.facebook.react.ReactActivity import com.facebook.react.ReactActivityDelegate import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled @@ -19,4 +20,8 @@ class MainActivity : ReactActivity() { */ override fun createReactActivityDelegate(): ReactActivityDelegate = DefaultReactActivityDelegate(this, mainComponentName, fabricEnabled) + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(null) + } } diff --git a/example/android/app/src/main/java/com/nitroexample/MainApplication.kt b/example/android/app/src/main/java/com/nitroexample/MainApplication.kt index 6300d85f7..336ab7615 100644 --- a/example/android/app/src/main/java/com/nitroexample/MainApplication.kt +++ b/example/android/app/src/main/java/com/nitroexample/MainApplication.kt @@ -11,6 +11,7 @@ import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost import com.facebook.react.defaults.DefaultReactNativeHost import com.facebook.soloader.SoLoader +import com.nitroexample.exampleturbomodule.ExampleTurboModulePackage class MainApplication : Application(), ReactApplication { @@ -19,7 +20,7 @@ class MainApplication : Application(), ReactApplication { override fun getPackages(): List = PackageList(this).packages.apply { // Packages that cannot be autolinked yet can be added manually here, for example: - // add(MyReactNativePackage()) + add(ExampleTurboModulePackage()) } override fun getJSMainModuleName(): String = "index" diff --git a/example/android/app/src/main/java/com/nitroexample/exampleturbomodule/ExampleTurboModule.kt b/example/android/app/src/main/java/com/nitroexample/exampleturbomodule/ExampleTurboModule.kt new file mode 100644 index 000000000..6c5a94351 --- /dev/null +++ b/example/android/app/src/main/java/com/nitroexample/exampleturbomodule/ExampleTurboModule.kt @@ -0,0 +1,15 @@ +package com.nitroexample.exampleturbomodule + +import com.facebook.react.bridge.ReactApplicationContext + +class ExampleTurboModuleModule(reactContext: ReactApplicationContext) : NativeExampleTurboModuleSpec(reactContext) { + override fun getName() = NAME + + override fun addNumbers(a: Double, b: Double): Double { + return a + b + } + + companion object { + const val NAME = "ExampleTurboModule" + } +} \ No newline at end of file diff --git a/example/android/app/src/main/java/com/nitroexample/exampleturbomodule/ExampleTurboModulePackage.kt b/example/android/app/src/main/java/com/nitroexample/exampleturbomodule/ExampleTurboModulePackage.kt new file mode 100644 index 000000000..dc53e92be --- /dev/null +++ b/example/android/app/src/main/java/com/nitroexample/exampleturbomodule/ExampleTurboModulePackage.kt @@ -0,0 +1,30 @@ +package com.nitroexample.exampleturbomodule + +import com.facebook.react.TurboReactPackage +import com.facebook.react.bridge.NativeModule +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.module.model.ReactModuleInfo +import com.facebook.react.module.model.ReactModuleInfoProvider + +class ExampleTurboModulePackage: TurboReactPackage() { + override fun getModule(name: String, context: ReactApplicationContext): NativeModule? { + if (name == ExampleTurboModuleModule.NAME) { + return ExampleTurboModuleModule(context) + } else { + return null + } + } + override fun getReactModuleInfoProvider() = ReactModuleInfoProvider { + mapOf( + ExampleTurboModuleModule.NAME to ReactModuleInfo( + ExampleTurboModuleModule.NAME, + ExampleTurboModuleModule.NAME, + false, // canOverrideExistingModule + false, // needsEagerInit + true, // hasConstants + false, // isCxxModule + true // isTurboModule + ) + ) + } +} \ No newline at end of file diff --git a/example/ios/Example Turbo Module/MGLExampleTurboModule.h b/example/ios/Example Turbo Module/MGLExampleTurboModule.h new file mode 100644 index 000000000..2df9682a6 --- /dev/null +++ b/example/ios/Example Turbo Module/MGLExampleTurboModule.h @@ -0,0 +1,17 @@ +// +// MGLExampleTurboModule.h +// NitroExample +// +// Created by Marc Rousavy on 13.11.24. +// + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface ExampleTurboModule : NSObject + +@end + +NS_ASSUME_NONNULL_END diff --git a/example/ios/Example Turbo Module/MGLExampleTurboModule.mm b/example/ios/Example Turbo Module/MGLExampleTurboModule.mm new file mode 100644 index 000000000..69b343ece --- /dev/null +++ b/example/ios/Example Turbo Module/MGLExampleTurboModule.mm @@ -0,0 +1,25 @@ +// +// MGLExampleTurboModule.mm +// NitroExample +// +// Created by Marc Rousavy on 13.11.24. +// + +#import "MGLExampleTurboModule.h" + +@implementation ExampleTurboModule + +RCT_EXPORT_MODULE() + +- (NSNumber*)addNumbers:(double)a b:(double)b { + NSNumber* result = [[NSNumber alloc] initWithDouble:a + b]; + return result; +} + +- (std::shared_ptr)getTurboModule: + (const facebook::react::ObjCTurboModule::InitParams &)params +{ + return std::make_shared(params); +} + +@end diff --git a/example/ios/NitroExample.xcodeproj/project.pbxproj b/example/ios/NitroExample.xcodeproj/project.pbxproj index 5b0e73a3b..2776e4993 100644 --- a/example/ios/NitroExample.xcodeproj/project.pbxproj +++ b/example/ios/NitroExample.xcodeproj/project.pbxproj @@ -15,6 +15,7 @@ 7699B88040F8A987B510C191 /* libPods-NitroExample-NitroExampleTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 19F6CBCC0A4E27FBF8BF4A61 /* libPods-NitroExample-NitroExampleTests.a */; }; 81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; }; B5425A16F500085048F53261 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = A98DCE138B16911E2CF8DB85 /* PrivacyInfo.xcprivacy */; }; + B88F64252CE4CF530079E862 /* MGLExampleTurboModule.mm in Sources */ = {isa = PBXBuildFile; fileRef = B88F64242CE4CF4F0079E862 /* MGLExampleTurboModule.mm */; }; B8D0C54B2C2357CE0037DD32 /* DummyFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8D0C54A2C2357CE0037DD32 /* DummyFile.swift */; }; /* End PBXBuildFile section */ @@ -47,6 +48,8 @@ 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = NitroExample/LaunchScreen.storyboard; sourceTree = ""; }; 89C6BE57DB24E9ADA2F236DE /* Pods-NitroExample-NitroExampleTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NitroExample-NitroExampleTests.release.xcconfig"; path = "Target Support Files/Pods-NitroExample-NitroExampleTests/Pods-NitroExample-NitroExampleTests.release.xcconfig"; sourceTree = ""; }; A98DCE138B16911E2CF8DB85 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xml; name = PrivacyInfo.xcprivacy; path = NitroExample/PrivacyInfo.xcprivacy; sourceTree = ""; }; + B88F64232CE4CF480079E862 /* MGLExampleTurboModule.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MGLExampleTurboModule.h; sourceTree = ""; }; + B88F64242CE4CF4F0079E862 /* MGLExampleTurboModule.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLExampleTurboModule.mm; sourceTree = ""; }; B8D0C5492C2357CE0037DD32 /* NitroExample-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NitroExample-Bridging-Header.h"; sourceTree = ""; }; B8D0C54A2C2357CE0037DD32 /* DummyFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DummyFile.swift; sourceTree = ""; }; ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; @@ -92,6 +95,7 @@ 13B07FAE1A68108700A75B9A /* NitroExample */ = { isa = PBXGroup; children = ( + B88F64222CE4CF3F0079E862 /* Example Turbo Module */, 13B07FAF1A68108700A75B9A /* AppDelegate.h */, 13B07FB01A68108700A75B9A /* AppDelegate.mm */, 13B07FB51A68108700A75B9A /* Images.xcassets */, @@ -147,6 +151,15 @@ name = Products; sourceTree = ""; }; + B88F64222CE4CF3F0079E862 /* Example Turbo Module */ = { + isa = PBXGroup; + children = ( + B88F64242CE4CF4F0079E862 /* MGLExampleTurboModule.mm */, + B88F64232CE4CF480079E862 /* MGLExampleTurboModule.h */, + ); + path = "Example Turbo Module"; + sourceTree = ""; + }; BBD78D7AC51CEA395F1C20DB /* Pods */ = { isa = PBXGroup; children = ( @@ -406,6 +419,7 @@ 13B07FBC1A68108700A75B9A /* AppDelegate.mm in Sources */, B8D0C54B2C2357CE0037DD32 /* DummyFile.swift in Sources */, 13B07FC11A68108700A75B9A /* main.m in Sources */, + B88F64252CE4CF530079E862 /* MGLExampleTurboModule.mm in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/example/ios/NitroExample/Images.xcassets/nos.imageset/Contents.json b/example/ios/NitroExample/Images.xcassets/nos.imageset/Contents.json new file mode 100644 index 000000000..ba260cff3 --- /dev/null +++ b/example/ios/NitroExample/Images.xcassets/nos.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "android-chrome-192x192 2.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "android-chrome-192x192 1.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "android-chrome-192x192.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/example/ios/NitroExample/Images.xcassets/nos.imageset/android-chrome-192x192 1.png b/example/ios/NitroExample/Images.xcassets/nos.imageset/android-chrome-192x192 1.png new file mode 100644 index 000000000..e65d411b1 Binary files /dev/null and b/example/ios/NitroExample/Images.xcassets/nos.imageset/android-chrome-192x192 1.png differ diff --git a/example/ios/NitroExample/Images.xcassets/nos.imageset/android-chrome-192x192 2.png b/example/ios/NitroExample/Images.xcassets/nos.imageset/android-chrome-192x192 2.png new file mode 100644 index 000000000..e65d411b1 Binary files /dev/null and b/example/ios/NitroExample/Images.xcassets/nos.imageset/android-chrome-192x192 2.png differ diff --git a/example/ios/NitroExample/Images.xcassets/nos.imageset/android-chrome-192x192.png b/example/ios/NitroExample/Images.xcassets/nos.imageset/android-chrome-192x192.png new file mode 100644 index 000000000..e65d411b1 Binary files /dev/null and b/example/ios/NitroExample/Images.xcassets/nos.imageset/android-chrome-192x192.png differ diff --git a/example/ios/NitroExample/LaunchScreen.storyboard b/example/ios/NitroExample/LaunchScreen.storyboard index 4eda241ef..52ad1c362 100644 --- a/example/ios/NitroExample/LaunchScreen.storyboard +++ b/example/ios/NitroExample/LaunchScreen.storyboard @@ -1,9 +1,9 @@ - + - + @@ -16,32 +16,26 @@ - - + + + - + + - - - - - - + + + + - - + + + + diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index cd714ebd5..c94aabd60 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -1620,6 +1620,51 @@ PODS: - React-logger (= 0.76.1) - React-perflogger (= 0.76.1) - React-utils (= 0.76.1) + - RNScreens (4.0.0): + - DoubleConversion + - glog + - hermes-engine + - RCT-Folly (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-NativeModulesApple + - React-RCTFabric + - React-RCTImage + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - RNScreens/common (= 4.0.0) + - Yoga + - RNScreens/common (4.0.0): + - DoubleConversion + - glog + - hermes-engine + - RCT-Folly (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-NativeModulesApple + - React-RCTFabric + - React-RCTImage + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - Yoga - SocketRocket (0.7.1) - Yoga (0.0.0) @@ -1692,6 +1737,7 @@ DEPENDENCIES: - React-utils (from `../../node_modules/react-native/ReactCommon/react/utils`) - ReactCodegen (from `build/generated/ios`) - ReactCommon/turbomodule/core (from `../../node_modules/react-native/ReactCommon`) + - RNScreens (from `../../node_modules/react-native-screens`) - Yoga (from `../../node_modules/react-native/ReactCommon/yoga`) SPEC REPOS: @@ -1832,6 +1878,8 @@ EXTERNAL SOURCES: :path: build/generated/ios ReactCommon: :path: "../../node_modules/react-native/ReactCommon" + RNScreens: + :path: "../../node_modules/react-native-screens" Yoga: :path: "../../node_modules/react-native/ReactCommon/yoga" @@ -1900,8 +1948,9 @@ SPEC CHECKSUMS: React-runtimescheduler: e7df538274de0c65736068e40efc0d2228f42d0d React-timing: b3b233fe819d9e5b6ca32b605aa732621bdfa5aa React-utils: 5362bd16a9563f9916e7a56c011ddc533507650f - ReactCodegen: 4e26d365313307cc7c95e693529e539acfb5c64c + ReactCodegen: 0102cbeaaa6a147c48738529da06d9b5b8f69128 ReactCommon: 422e364463f33e336fc4db196aeb50fd801d90d6 + RNScreens: 2fe13c8d610ef2d9d5ace2e7d85b716ec0f5217c SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748 Yoga: 157bed1c62656587df4639d4dc29714898f8fb10 diff --git a/example/package.json b/example/package.json index 1ec8995e0..e853710ee 100644 --- a/example/package.json +++ b/example/package.json @@ -18,10 +18,13 @@ }, "dependencies": { "@react-native-segmented-control/segmented-control": "^2.5.2", + "@react-navigation/bottom-tabs": "^7.0.1", + "@react-navigation/native": "^7.0.0", "deep-equal": "^2.2.3", "react": "*", "react-native": "^0.76.1", - "react-native-safe-area-context": "^4.14.0" + "react-native-safe-area-context": "^4.14.0", + "react-native-screens": "^4.0.0" }, "devDependencies": { "@babel/core": "^7.25.2", @@ -42,5 +45,13 @@ }, "engines": { "node": ">=18" + }, + "codegenConfig": { + "name": "ExampleTurboModule", + "type": "modules", + "jsSrcsDir": "src", + "android": { + "javaPackageName": "com.nitroexample.exampleturbomodule" + } } } diff --git a/example/src/App.tsx b/example/src/App.tsx index 7eca733fa..f43d4c30c 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -1,11 +1,57 @@ +/* eslint-disable react/no-unstable-nested-components */ import * as React from 'react' -import { SafeAreaProvider } from 'react-native-safe-area-context' import { HybridObjectTestsScreen } from './screens/HybridObjectTestsScreen' +import { NavigationContainer } from '@react-navigation/native' +import { createBottomTabNavigator } from '@react-navigation/bottom-tabs' +import { useColors } from './useColors' +import { Image } from 'react-native' +import { BenchmarksScreen } from './screens/BenchmarksScreen' + +const dna = require('./img/dna.png') +const rocket = require('./img/rocket.png') + +const Tabs = createBottomTabNavigator() export default function App() { + const colors = useColors() return ( - - - + + + ( + + ), + }} + /> + ( + + ), + }} + /> + + ) } diff --git a/example/src/img/dna.png b/example/src/img/dna.png new file mode 100644 index 000000000..e9cf87c79 Binary files /dev/null and b/example/src/img/dna.png differ diff --git a/example/src/img/rocket.png b/example/src/img/rocket.png new file mode 100644 index 000000000..8df0b15e8 Binary files /dev/null and b/example/src/img/rocket.png differ diff --git a/example/src/screens/BenchmarksScreen.tsx b/example/src/screens/BenchmarksScreen.tsx new file mode 100644 index 000000000..a17b04654 --- /dev/null +++ b/example/src/screens/BenchmarksScreen.tsx @@ -0,0 +1,321 @@ +/* eslint-disable react-native/no-inline-styles */ +/* eslint-disable no-lone-blocks */ +import * as React from 'react' + +import { + StyleSheet, + View, + Text, + Button, + Platform, + InteractionManager, + Animated, + useWindowDimensions, +} from 'react-native' +import { NitroModules } from 'react-native-nitro-modules' +import { useSafeAreaInsets } from 'react-native-safe-area-context' +import { useColors } from '../useColors' +import { HybridTestObjectCpp } from 'react-native-nitro-image' +import { ExampleTurboModule } from '../turbo-module/ExampleTurboModule' + +declare global { + var performance: { + now: () => number + } +} + +interface BenchmarksResult { + numberOfIterations: number + nitroExecutionTimeMs: number + turboExecutionTimeMs: number + nitroResult: number + turboResult: number +} + +function delay(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)) +} + +async function waitForGc(): Promise { + await delay(500) + return new Promise((resolve) => { + requestAnimationFrame(() => { + InteractionManager.runAfterInteractions(() => { + resolve() + }) + }) + }) +} + +const ITERATIONS = 100_000 +async function runBenchmarks(): Promise { + console.log(`Running benchmarks ${ITERATIONS}x...`) + + await waitForGc() + let nitroResult = 0, + nitroStart = 0, + nitroEnd = 0 + { + nitroStart = performance.now() + for (let i = 0; i < ITERATIONS; i++) { + nitroResult = HybridTestObjectCpp.addNumbers(3, 5) + } + nitroEnd = performance.now() + } + + await waitForGc() + let turboResult = 0, + turboStart = 0, + turboEnd = 0 + { + turboStart = performance.now() + for (let i = 0; i < ITERATIONS; i++) { + turboResult = ExampleTurboModule.addNumbers(3, 5) + } + turboEnd = performance.now() + } + + console.log( + `Benchmarks finished! Nitro: ${(nitroEnd - nitroStart).toFixed(2)}ms | Turbo: ${(turboEnd - turboStart).toFixed(2)}ms` + ) + return { + nitroExecutionTimeMs: nitroEnd - nitroStart, + turboExecutionTimeMs: turboEnd - turboStart, + numberOfIterations: ITERATIONS, + turboResult: turboResult, + nitroResult: nitroResult, + } +} + +export function BenchmarksScreen() { + const safeArea = useSafeAreaInsets() + const colors = useColors() + const dimensions = useWindowDimensions() + const [status, setStatus] = React.useState('📱 Idle') + const [results, setResults] = React.useState() + const nitroWidth = React.useRef(new Animated.Value(0)).current + const turboWidth = React.useRef(new Animated.Value(0)).current + + const run = async () => { + nitroWidth.setValue(0) + turboWidth.setValue(0) + setStatus(`⏳ Running Benchmarks`) + const r = await runBenchmarks() + setResults(r) + + const maxWidth = dimensions.width * 0.7 + const smallerScale = + Math.min(r.nitroExecutionTimeMs, r.turboExecutionTimeMs) / + Math.max(r.nitroExecutionTimeMs, r.turboExecutionTimeMs) + Animated.spring(nitroWidth, { + toValue: maxWidth, + friction: 10, + tension: 40, + useNativeDriver: false, + }).start() + Animated.spring(turboWidth, { + toValue: smallerScale * maxWidth, + friction: 10, + tension: 40, + useNativeDriver: false, + }).start() + setStatus(`📱 Idle`) + } + + return ( + + Benchmarks + + + {NitroModules.buildType} + + + + {results != null ? ( + + + Calling addNumbers(...){' '} + {ITERATIONS}x: + + + + + Turbo Modules + + + + + Time:{' '} + + {results.turboExecutionTimeMs.toFixed(2)}ms + + + + + + + + Nitro Modules + + + + + Time:{' '} + + {results.nitroExecutionTimeMs.toFixed(2)}ms + + {' '}( + + {Math.round( + (results.turboExecutionTimeMs / + results.nitroExecutionTimeMs) * + 10 + ) / 10} + x + {' '} + faster!) + + + + ) : ( + + Press Run to call{' '} + addNumbers(...) {ITERATIONS} times. + + )} + + + + {status} + +