Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: New Callback implementation that extends () -> T in Kotlin (faster!) #470

Merged
merged 21 commits into from
Jan 9, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1888,11 +1888,11 @@ EXTERNAL SOURCES:
:path: "../../node_modules/react-native/ReactCommon/yoga"

SPEC CHECKSUMS:
boost: 1dca942403ed9342f98334bf4c3621f011aa7946
DoubleConversion: f16ae600a246532c4020132d54af21d0ddb2a385
boost: 7e761d76ca2ce687f7cc98e698152abd03a18f90
DoubleConversion: cb417026b2400c8f53ae97020b2be961b59470cb
FBLazyVector: 1bf99bb46c6af9a2712592e707347315f23947aa
fmt: 10c6e61f4be25dc963c36bd73fc7b1705fe975be
glog: 08b301085f15bcbb6ff8632a8ebaf239aae04e6a
glog: eb93e2f488219332457c3c4eafd2738ddc7e80b8
hermes-engine: 06a9c6900587420b90accc394199527c64259db4
NitroImage: b49db0212f1b50aa976b8cdd97072df904b29102
NitroModules: ff8680342cb7008646d8160ff9b30ba74c681c11
Expand Down
19 changes: 19 additions & 0 deletions example/src/getTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,20 @@ export function getTests(
.didReturn(typeof OldEnum.SECOND)
.equals(OldEnum.SECOND)
),
createTest('set optionalCallback, then undefined', () =>
it(() => {
testObject.optionalCallback = () => {}
testObject.optionalCallback = undefined
}).didNotThrow()
),
createTest('get optionalCallback (== self)', () =>
it(() => {
testObject.optionalCallback = () => {}
return testObject.optionalCallback
})
.didNotThrow()
.didReturn('function')
),

// Test basic functions
createTest('addNumbers(5, 13) = 18', () =>
Expand Down Expand Up @@ -946,6 +960,11 @@ export function getTests(
})
).didThrow()
),
createTest('Getting complex callback from native returns a function', () =>
it(() => testObject.getComplexCallback())
.didNotThrow()
.didReturn('function')
),

// Objects
createTest('getCar()', () =>
Expand Down
11 changes: 9 additions & 2 deletions packages/nitrogen/src/syntax/kotlin/FbjniHybridObject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,13 @@ function getFbjniMethodForwardImplementation(
const name = getHybridObjectName(spec.name)

const returnJNI = new KotlinCxxBridgedType(method.returnType)
const requiresBridge =
returnJNI.needsSpecialHandling ||
method.parameters.some((p) => {
const bridged = new KotlinCxxBridgedType(p.type)
return bridged.needsSpecialHandling
})
const methodName = requiresBridge ? `${method.name}_cxx` : method.name

const returnType = returnJNI.asJniReferenceType('local')
const paramsTypes = method.parameters
Expand All @@ -237,14 +244,14 @@ function getFbjniMethodForwardImplementation(
if (returnJNI.hasType) {
// return something - we need to parse it
body = `
static const auto method = _javaPart->getClass()->getMethod<${cxxSignature}>("${method.name}");
static const auto method = _javaPart->getClass()->getMethod<${cxxSignature}>("${methodName}");
auto __result = method(${paramsForward.join(', ')});
return ${returnJNI.parse('__result', 'kotlin', 'c++', 'c++')};
`
} else {
// void method. no return
body = `
static const auto method = _javaPart->getClass()->getMethod<${cxxSignature}>("${method.name}");
static const auto method = _javaPart->getClass()->getMethod<${cxxSignature}>("${methodName}");
method(${paramsForward.join(', ')});
`
}
Expand Down
93 changes: 78 additions & 15 deletions packages/nitrogen/src/syntax/kotlin/KotlinCxxBridgedType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,24 @@ export class KotlinCxxBridgedType implements BridgedType<'kotlin', 'c++'> {

get needsSpecialHandling(): boolean {
switch (this.type.kind) {
case 'function':
// Function needs to be converted from JFunc_... to Lambda
return true
default:
return false
break
}
// check if any types this type references (e.g. underlying optional, array element, ...)
// needs special handling. if yes, we need it as well
const referencedTypes = getReferencedTypes(this.type)
.filter((t) => t !== this.type)
.map((t) => new KotlinCxxBridgedType(t))
for (const type of referencedTypes) {
if (type.needsSpecialHandling) {
return true
}
}
// no special handling needed
return false
}

getRequiredImports(): SourceImport[] {
Expand Down Expand Up @@ -204,9 +219,10 @@ export class KotlinCxxBridgedType implements BridgedType<'kotlin', 'c++'> {
return this.type.getCode(language)
}
case 'array':
const array = getTypeAs(this.type, ArrayType)
const bridgedItem = new KotlinCxxBridgedType(array.itemType)
switch (language) {
case 'c++':
const array = getTypeAs(this.type, ArrayType)
switch (array.itemType.kind) {
case 'number':
return 'jni::JArrayDouble'
Expand All @@ -215,9 +231,10 @@ export class KotlinCxxBridgedType implements BridgedType<'kotlin', 'c++'> {
case 'bigint':
return 'jni::JArrayLong'
default:
const bridged = new KotlinCxxBridgedType(array.itemType)
return `jni::JArrayClass<${bridged.getTypeCode(language)}>`
return `jni::JArrayClass<${bridgedItem.getTypeCode(language)}>`
}
case 'kotlin':
return `Array<${bridgedItem.getTypeCode(language)}>`
default:
return this.type.getCode(language)
}
Expand Down Expand Up @@ -313,9 +330,12 @@ export class KotlinCxxBridgedType implements BridgedType<'kotlin', 'c++'> {
return this.type.getCode(language)
}
case 'optional': {
const optional = getTypeAs(this.type, OptionalType)
const bridgedWrappingType = new KotlinCxxBridgedType(
optional.wrappingType
)
switch (language) {
case 'c++':
const optional = getTypeAs(this.type, OptionalType)
switch (optional.wrappingType.kind) {
// primitives need to be boxed to make them nullable
case 'number':
Expand All @@ -325,9 +345,10 @@ export class KotlinCxxBridgedType implements BridgedType<'kotlin', 'c++'> {
return boxed
default:
// all other types can be nullable as they are objects.
const bridge = new KotlinCxxBridgedType(optional.wrappingType)
return bridge.getTypeCode('c++')
return bridgedWrappingType.getTypeCode('c++')
}
case 'kotlin':
return `${bridgedWrappingType.getTypeCode(language)}?`
default:
return this.type.getCode(language)
}
Expand Down Expand Up @@ -419,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
default:
return parameterName
}
Expand All @@ -442,6 +463,12 @@ export class KotlinCxxBridgedType implements BridgedType<'kotlin', 'c++'> {
switch (language) {
case 'c++':
return `${parameterName}.has_value() ? ${bridge.parseFromCppToKotlin(`${parameterName}.value()`, 'c++', true)} : nullptr`
case 'kotlin':
if (bridge.needsSpecialHandling) {
return `${parameterName}?.let { ${bridge.parseFromCppToKotlin('it', language, isBoxed)} }`
} else {
return parameterName
}
default:
return parameterName
}
Expand Down Expand Up @@ -488,11 +515,11 @@ export class KotlinCxxBridgedType implements BridgedType<'kotlin', 'c++'> {
}
}
case 'array': {
const array = getTypeAs(this.type, ArrayType)
const arrayType = this.getTypeCode('c++')
const bridge = new KotlinCxxBridgedType(array.itemType)
switch (language) {
case 'c++': {
const array = getTypeAs(this.type, ArrayType)
const arrayType = this.getTypeCode('c++')
const bridge = new KotlinCxxBridgedType(array.itemType)
switch (array.itemType.kind) {
case 'number':
case 'boolean':
Expand All @@ -516,14 +543,20 @@ export class KotlinCxxBridgedType implements BridgedType<'kotlin', 'c++'> {
jni::local_ref<${arrayType}> __array = ${arrayType}::newArray(__size);
for (size_t __i = 0; __i < __size; __i++) {
const auto& __element = ${parameterName}[__i];
__array->setElement(__i, *${bridge.parseFromCppToKotlin('__element', 'c++')});
__array->setElement(__i, *${indent(bridge.parseFromCppToKotlin('__element', 'c++'), ' ')});
}
return __array;
}()
`.trim()
}
}
}
case 'kotlin':
if (bridge.needsSpecialHandling) {
return `${parameterName}.map { ${bridge.parseFromCppToKotlin('it', language, isBoxed)} }`
} else {
return parameterName
}
default:
return parameterName
}
Expand Down Expand Up @@ -657,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
Loading
Loading