Skip to content

Commit

Permalink
fix: Fix downcasting WOOHOOO
Browse files Browse the repository at this point in the history
  • Loading branch information
mrousavy committed Jan 9, 2025
1 parent e9c89eb commit 7601724
Show file tree
Hide file tree
Showing 36 changed files with 902 additions and 280 deletions.
38 changes: 34 additions & 4 deletions packages/nitrogen/src/syntax/kotlin/KotlinCxxBridgedType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -440,9 +440,9 @@ export class KotlinCxxBridgedType implements BridgedType<'kotlin', 'c++'> {
switch (language) {
case 'c++':
const func = getTypeAs(this.type, FunctionType)
return `J${func.specializationName}::fromCpp(${parameterName})`
return `J${func.specializationName}_cxx::fromCpp(${parameterName})`
case 'kotlin':
return `${parameterName}.toLambda()`
return `${parameterName} /* TODO: Does this work? */`
default:
return parameterName
}
Expand Down Expand Up @@ -690,14 +690,44 @@ export class KotlinCxxBridgedType implements BridgedType<'kotlin', 'c++'> {
true
)
return `${parameterName} != nullptr ? std::make_optional(${parsed}) : std::nullopt`
case 'kotlin':
if (bridge.needsSpecialHandling) {
return `${parameterName}?.let { ${bridge.parseFromKotlinToCpp('it', language, isBoxed)} }`
} else {
return parameterName
}
default:
return parameterName
}
}
case 'function': {
const functionType = getTypeAs(this.type, FunctionType)
switch (language) {
case 'c++':
return `${parameterName}->cthis()->getFunction()`
case 'c++': {
const returnType = functionType.returnType.getCode('c++')
const params = functionType.parameters.map(
(p) => `${p.getCode('c++')} ${p.escapedName}`
)
const paramsForward = functionType.parameters.map(
(p) => p.escapedName
)
const jniType = `J${functionType.specializationName}_cxx`
return `
[&]() -> ${functionType.getCode('c++')} {
if (${parameterName}->isInstanceOf(${jniType}::javaClassStatic())) [[likely]] {
auto downcast = jni::static_ref_cast<${jniType}::javaobject>(${parameterName});
return downcast->cthis()->getFunction();
} else {
return [${parameterName}](${params.join(', ')}) -> ${returnType} {
return ${parameterName}->invoke(${paramsForward});
};
}
}()
`.trim()
}
case 'kotlin': {
return `${functionType.specializationName}_java(${parameterName})`
}
default:
return parameterName
}
Expand Down
1 change: 1 addition & 0 deletions packages/nitrogen/src/syntax/kotlin/KotlinEnum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ namespace ${cxxNamespace} {
* Convert this Java/Kotlin-based enum to the C++ enum ${enumType.enumName}.
*/
[[maybe_unused]]
[[nodiscard]]
${enumType.enumName} toCpp() const {
static const auto clazz = javaClassStatic();
static const auto fieldOrdinal = clazz->getField<int>("_ordinal");
Expand Down
145 changes: 115 additions & 30 deletions packages/nitrogen/src/syntax/kotlin/KotlinFunction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@ export function createKotlinFunction(functionType: FunctionType): SourceFile[] {
const kotlinParams = functionType.parameters.map(
(p) => `${p.escapedName}: ${p.getCode('kotlin')}`
)
const lambdaSignature = `(${kotlinParams.join(', ')}) -> ${kotlinReturnType}`
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`)}
Expand All @@ -27,14 +31,37 @@ 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("RedundantSuppression", "ConvertSecondaryConstructorToPrimary", "RedundantUnitReturnType", "KotlinJniMissingFunction", "ClassName", "unused", "LocalVariableName")
class ${name} {
@Suppress(
"KotlinJniMissingFunction", "unused",
"RedundantSuppression", "RedundantUnitReturnType",
"ConvertSecondaryConstructorToPrimary", "ClassName", "LocalVariableName",
)
class ${name}_cxx: ${name} {
@DoNotStrip
@Keep
private val mHybridData: HybridData
Expand All @@ -45,23 +72,37 @@ class ${name} {
mHybridData = hybridData
}
/**
* Converts this function to a Kotlin Lambda.
* This exists purely as syntactic sugar, and has minimal runtime overhead.
*/
fun toLambda(): ${lambdaSignature} = this::call
/**
* 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.
*/
@FastNative
external fun call(${kotlinParams.join(', ')}): ${kotlinReturnType}
external override fun invoke(${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')
Expand All @@ -71,19 +112,49 @@ class ${name} {
const bridge = new KotlinCxxBridgedType(p)
return bridge.parseFromKotlinToCpp(p.escapedName, 'c++', false)
})
const jniClassDescriptor = NitroConfig.getAndroidPackage('c++/jni', name)
const cxxNamespace = NitroConfig.getCxxNamespace('c++')
const typename = functionType.getCode('c++')
let callBody: string

// 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
callBody = `_func(${indent(paramsForward.join(', '), ' ')});`
cppCallBody = `_func(${indent(paramsForward.join(', '), ' ')});`
jniCallBody = `
static const auto method = getClass()->getMethod<${jniSignature}>("invoke");
method(${jniParamsForward.join(', ')});
`.trim()
} else {
// It returns a type!
callBody = `
cppCallBody = `
${functionType.returnType.getCode('c++')} __result = _func(${indent(paramsForward.join(', '), ' ')});
return ${bridgedReturn.parseFromCppToKotlin('__result', 'c++')};
`.trim()
jniCallBody = `
static const auto method = getClass()->getMethod<${jniSignature}>("invoke");
auto __result = method(${jniParamsForward.join(', ')});
return ${bridgedReturn.parseFromKotlinToCpp('__result', 'c++', false)};
`.trim()
}

const bridged = new KotlinCxxBridgedType(functionType)
Expand All @@ -107,33 +178,47 @@ namespace ${cxxNamespace} {
using namespace facebook;
/**
* C++ representation of the callback ${name}.
* This is a Kotlin \`${functionType.getCode('kotlin')}\`, backed by a \`std::function<...>\`.
* 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:
${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} final: public jni::HybridClass<J${name}> {
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}::newObjectCxxArgs(func);
static jni::local_ref<J${name}_cxx::javaobject> fromCpp(const ${typename}& func) {
return J${name}_cxx::newObjectCxxArgs(func);
}
public:
${bridgedReturn.asJniReferenceType('local')} call(${cppParams.join(', ')}) {
${indent(callBody, ' ')}
${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("call", J${name}::call)});
registerHybrid({makeNativeMethod("invoke", J${name}_cxx::invoke_cxx)});
}
private:
explicit J${name}(const ${typename}& func): _func(func) { }
explicit J${name}_cxx(const ${typename}& func): _func(func) { }
private:
friend HybridBase;
Expand All @@ -146,7 +231,7 @@ namespace ${cxxNamespace} {
// Make sure we register all native JNI methods on app startup
addJNINativeRegistration({
namespace: cxxNamespace,
className: `J${name}`,
className: `J${name}_cxx`,
import: {
name: `J${name}.hpp`,
space: 'user',
Expand Down
1 change: 1 addition & 0 deletions packages/nitrogen/src/syntax/kotlin/KotlinStruct.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ namespace ${cxxNamespace} {
* Convert this Java/Kotlin-based struct to the C++ struct ${structType.structName} by copying all values to C++.
*/
[[maybe_unused]]
[[nodiscard]]
${structType.structName} toCpp() const {
${indent(jniStructInitializerBody, ' ')}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,24 +43,24 @@ int initialize(JavaVM* vm) {
return facebook::jni::initialize(vm, [] {
// Register native JNI methods
margelo::nitro::image::JHybridImageSpec::registerNatives();
margelo::nitro::image::JFunc_void_std__string::registerNatives();
margelo::nitro::image::JFunc_void_std__string_cxx::registerNatives();
margelo::nitro::image::JHybridImageFactorySpec::registerNatives();
margelo::nitro::image::JHybridTestObjectSwiftKotlinSpec::registerNatives();
margelo::nitro::image::JFunc_void_double::registerNatives();
margelo::nitro::image::JFunc_void_double::registerNatives();
margelo::nitro::image::JFunc_void_std__vector_Powertrain_::registerNatives();
margelo::nitro::image::JFunc_void::registerNatives();
margelo::nitro::image::JFunc_void::registerNatives();
margelo::nitro::image::JFunc_void::registerNatives();
margelo::nitro::image::JFunc_void::registerNatives();
margelo::nitro::image::JFunc_void_std__optional_double_::registerNatives();
margelo::nitro::image::JFunc_std__shared_ptr_Promise_double__::registerNatives();
margelo::nitro::image::JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_double____::registerNatives();
margelo::nitro::image::JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_std__shared_ptr_ArrayBuffer_____::registerNatives();
margelo::nitro::image::JFunc_std__shared_ptr_Promise_double__::registerNatives();
margelo::nitro::image::JFunc_std__shared_ptr_Promise_std__string__::registerNatives();
margelo::nitro::image::JFunc_void_std__string::registerNatives();
margelo::nitro::image::JFunc_void_double::registerNatives();
margelo::nitro::image::JFunc_void_double_cxx::registerNatives();
margelo::nitro::image::JFunc_void_double_cxx::registerNatives();
margelo::nitro::image::JFunc_void_std__vector_Powertrain__cxx::registerNatives();
margelo::nitro::image::JFunc_void_cxx::registerNatives();
margelo::nitro::image::JFunc_void_cxx::registerNatives();
margelo::nitro::image::JFunc_void_cxx::registerNatives();
margelo::nitro::image::JFunc_void_cxx::registerNatives();
margelo::nitro::image::JFunc_void_std__optional_double__cxx::registerNatives();
margelo::nitro::image::JFunc_std__shared_ptr_Promise_double___cxx::registerNatives();
margelo::nitro::image::JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_double_____cxx::registerNatives();
margelo::nitro::image::JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_std__shared_ptr_ArrayBuffer______cxx::registerNatives();
margelo::nitro::image::JFunc_std__shared_ptr_Promise_double___cxx::registerNatives();
margelo::nitro::image::JFunc_std__shared_ptr_Promise_std__string___cxx::registerNatives();
margelo::nitro::image::JFunc_void_std__string_cxx::registerNatives();
margelo::nitro::image::JFunc_void_double_cxx::registerNatives();
margelo::nitro::image::JHybridBaseSpec::registerNatives();
margelo::nitro::image::JHybridChildSpec::registerNatives();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ namespace margelo::nitro::image {
* Convert this Java/Kotlin-based struct to the C++ struct Car by copying all values to C++.
*/
[[maybe_unused]]
[[nodiscard]]
Car toCpp() const {
static const auto clazz = javaClassStatic();
static const auto fieldYear = clazz->getField<double>("year");
Expand Down
Loading

0 comments on commit 7601724

Please sign in to comment.