UNPKG

nitro-codegen

Version:

The code-generator for react-native-nitro-modules.

270 lines (237 loc) 8.37 kB
import { NitroConfig } from '../../config/NitroConfig.js' import { indent } from '../../utils.js' import { includeHeader } from '../c++/includeNitroHeader.js' import { createFileMetadataString, isNotDuplicate } from '../helpers.js' import type { SourceFile } from '../SourceFile.js' import type { FunctionType } from '../types/FunctionType.js' import { addJNINativeRegistration } from './JNINativeRegistrations.js' import { KotlinCxxBridgedType } from './KotlinCxxBridgedType.js' export function createKotlinFunction(functionType: FunctionType): SourceFile[] { const name = functionType.specializationName const packageName = NitroConfig.getAndroidPackage('java/kotlin') const kotlinReturnType = functionType.returnType.getCode('kotlin') const kotlinParams = functionType.parameters.map( (p) => `${p.escapedName}: ${p.getCode('kotlin')}` ) const kotlinParamTypes = functionType.parameters.map((p) => p.getCode('kotlin') ) const kotlinParamsForward = functionType.parameters.map((p) => p.escapedName) const lambdaSignature = `(${kotlinParamTypes.join(', ')}) -> ${kotlinReturnType}` const kotlinCode = ` ${createFileMetadataString(`${name}.kt`)} package ${packageName} import androidx.annotation.Keep import com.facebook.jni.HybridData import com.facebook.proguard.annotations.DoNotStrip import com.margelo.nitro.core.* import dalvik.annotation.optimization.FastNative /** * Represents the JavaScript callback \`${functionType.jsName}\`. * This can be either implemented in C++ (in which case it might be a callback coming from JS), * or in Kotlin/Java (in which case it is a native callback). */ @DoNotStrip @Keep @Suppress("ClassName", "RedundantUnitReturnType") fun interface ${name}: ${lambdaSignature} { /** * Call the given JS callback. * @throws Throwable if the JS function itself throws an error, or if the JS function/runtime has already been deleted. */ @DoNotStrip @Keep override fun invoke(${kotlinParams.join(', ')}): ${kotlinReturnType} } /** * Represents the JavaScript callback \`${functionType.jsName}\`. * This is implemented in C++, via a \`std::function<...>\`. * The callback might be coming from JS. */ @DoNotStrip @Keep @Suppress( "KotlinJniMissingFunction", "unused", "RedundantSuppression", "RedundantUnitReturnType", "FunctionName", "ConvertSecondaryConstructorToPrimary", "ClassName", "LocalVariableName", ) class ${name}_cxx: ${name} { @DoNotStrip @Keep private val mHybridData: HybridData @DoNotStrip @Keep private constructor(hybridData: HybridData) { mHybridData = hybridData } @DoNotStrip @Keep override fun invoke(${kotlinParams.join(', ')}): ${kotlinReturnType} = invoke_cxx(${kotlinParamsForward.join(',')}) @FastNative private external fun invoke_cxx(${kotlinParams.join(', ')}): ${kotlinReturnType} } /** * Represents the JavaScript callback \`${functionType.jsName}\`. * This is implemented in Java/Kotlin, via a \`${lambdaSignature}\`. * The callback is always coming from native. */ @DoNotStrip @Keep @Suppress("ClassName", "RedundantUnitReturnType", "unused") class ${name}_java(private val function: ${lambdaSignature}): ${name} { @DoNotStrip @Keep override fun invoke(${kotlinParams.join(', ')}): ${kotlinReturnType} { return this.function(${kotlinParamsForward.join(', ')}) } } `.trim() const jniInterfaceDescriptor = NitroConfig.getAndroidPackage('c++/jni', name) const jniClassDescriptor = NitroConfig.getAndroidPackage( 'c++/jni', `${name}_cxx` ) const bridgedReturn = new KotlinCxxBridgedType(functionType.returnType) const cxxNamespace = NitroConfig.getCxxNamespace('c++') const typename = functionType.getCode('c++') // call() Java -> C++ const cppParams = functionType.parameters.map((p) => { const bridge = new KotlinCxxBridgedType(p) const type = bridge.asJniReferenceType('alias') return `${type} ${p.escapedName}` }) const paramsForward = functionType.parameters.map((p) => { const bridge = new KotlinCxxBridgedType(p) return bridge.parseFromKotlinToCpp(p.escapedName, 'c++', false) }) // call() C++ -> Java const jniParams = functionType.parameters.map((p) => { if (p.canBePassedByReference) { return `const ${p.getCode('c++')}& ${p.escapedName}` } else { return `${p.getCode('c++')} ${p.escapedName}` } }) const jniParamsForward = [ 'self()', ...functionType.parameters.map((p) => { const bridge = new KotlinCxxBridgedType(p) return bridge.parseFromCppToKotlin(p.escapedName, 'c++', false) }), ] const jniSignature = `${bridgedReturn.asJniReferenceType('local')}(${functionType.parameters .map((p) => { const bridge = new KotlinCxxBridgedType(p) return `${bridge.asJniReferenceType('alias')} /* ${p.escapedName} */` }) .join(', ')})` let cppCallBody: string let jniCallBody: string if (functionType.returnType.kind === 'void') { // It returns void cppCallBody = `_func(${indent(paramsForward.join(', '), ' ')});` jniCallBody = ` static const auto method = javaClassStatic()->getMethod<${jniSignature}>("invoke"); method(${jniParamsForward.join(', ')}); `.trim() } else { // It returns a type! cppCallBody = ` ${functionType.returnType.getCode('c++')} __result = _func(${indent(paramsForward.join(', '), ' ')}); return ${bridgedReturn.parseFromCppToKotlin('__result', 'c++')}; `.trim() jniCallBody = ` static const auto method = javaClassStatic()->getMethod<${jniSignature}>("invoke"); auto __result = method(${jniParamsForward.join(', ')}); return ${bridgedReturn.parseFromKotlinToCpp('__result', 'c++', false)}; `.trim() } const bridged = new KotlinCxxBridgedType(functionType) const imports = bridged .getRequiredImports() .filter((i) => i.name !== `J${name}.hpp`) const includes = imports.map((i) => includeHeader(i)).filter(isNotDuplicate) const fbjniCode = ` ${createFileMetadataString(`J${name}.hpp`)} #pragma once #include <fbjni/fbjni.h> #include <functional> ${includes.join('\n')} namespace ${cxxNamespace} { using namespace facebook; /** * Represents the Java/Kotlin callback \`${functionType.getCode('kotlin')}\`. * This can be passed around between C++ and Java/Kotlin. */ struct J${name}: public jni::JavaClass<J${name}> { public: static auto constexpr kJavaDescriptor = "L${jniInterfaceDescriptor};"; public: /** * Invokes the function this \`J${name}\` instance holds through JNI. */ ${functionType.returnType.getCode('c++')} invoke(${jniParams.join(', ')}) const { ${indent(jniCallBody, ' ')} } }; /** * An implementation of ${name} that is backed by a C++ implementation (using \`std::function<...>\`) */ struct J${name}_cxx final: public jni::HybridClass<J${name}_cxx, J${name}> { public: static jni::local_ref<J${name}::javaobject> fromCpp(const ${typename}& func) { return J${name}_cxx::newObjectCxxArgs(func); } public: /** * Invokes the C++ \`std::function<...>\` this \`J${name}_cxx\` instance holds. */ ${bridgedReturn.asJniReferenceType('local')} invoke_cxx(${cppParams.join(', ')}) { ${indent(cppCallBody, ' ')} } public: [[nodiscard]] inline const ${typename}& getFunction() const { return _func; } public: static auto constexpr kJavaDescriptor = "L${jniClassDescriptor};"; static void registerNatives() { registerHybrid({makeNativeMethod("invoke_cxx", J${name}_cxx::invoke_cxx)}); } private: explicit J${name}_cxx(const ${typename}& func): _func(func) { } private: friend HybridBase; ${typename} _func; }; } // namespace ${cxxNamespace} `.trim() // Make sure we register all native JNI methods on app startup addJNINativeRegistration({ namespace: cxxNamespace, className: `J${name}_cxx`, import: { name: `J${name}.hpp`, space: 'user', language: 'c++', }, }) const files: SourceFile[] = [] files.push({ content: kotlinCode, language: 'kotlin', name: `${name}.kt`, subdirectory: NitroConfig.getAndroidPackageDirectory(), platform: 'android', }) files.push({ content: fbjniCode, language: 'c++', name: `J${name}.hpp`, subdirectory: [], platform: 'android', }) return files }