UNPKG

nitro-codegen

Version:

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

253 lines (230 loc) 8.5 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 { addJNINativeRegistration } from './JNINativeRegistrations.js'; import { KotlinCxxBridgedType } from './KotlinCxxBridgedType.js'; export function createKotlinFunction(functionType) { 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; let jniCallBody; 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 = []; 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; }