Skip to content

Commit ae036ed

Browse files
authored
Make API more friendly to Java (#712)
* Improve Callable syntax for Java * Make singletons jvm static * Rename KtCallable to LambdaCallable
1 parent 0a45375 commit ae036ed

File tree

59 files changed

+4946
-2476
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+4946
-2476
lines changed

harness/tests/src/main/java/godot/tests/JavaTestClass.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import godot.Button;
44
import godot.Node;
5+
import godot.RenderingServer;
56
import godot.annotation.*;
67
import godot.core.*;
78
import org.jetbrains.annotations.NotNull;
@@ -69,6 +70,25 @@ public String greeting() {
6970
@RegisterProperty
7071
public Dictionary<Float, String> dictionary = new Dictionary<>(Float.class, String.class);
7172

73+
public LambdaCallable<Void> lambdaCallable = LambdaCallable0.create(
74+
Void.class,
75+
() -> {
76+
System.out.println("Hello from Callable");
77+
return null;
78+
}
79+
);
80+
81+
public NativeCallable methodCallable = Callable.create(this, StringNames.asStringName("DummyName"));
82+
83+
@RegisterFunction
84+
@Override
85+
public void _ready() {
86+
// Check if Singletons have the correct syntax, without Single.INSTANCE
87+
long constant = RenderingServer.NO_INDEX_ARRAY;
88+
Signal signal = RenderingServer.getFramePreDraw();
89+
RenderingServer.getDefaultClearColor();
90+
}
91+
7292
@RegisterFunction
7393
public void connectAndTriggerSignal() {
7494
connect(

kt/api-generator/src/main/kotlin/godot/codegen/generationEntry.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import godot.codegen.services.impl.ClassGraphService
2424
import godot.codegen.services.impl.ClassService
2525
import godot.codegen.services.impl.EnumService
2626
import godot.codegen.services.impl.GenerationService
27-
import godot.codegen.services.impl.KtCallableGenerationService
27+
import godot.codegen.services.impl.LambdaCallableGenerationService
2828
import godot.codegen.services.impl.SignalGenerationService
2929
import godot.tools.common.constants.Constraints
3030
import godot.tools.common.constants.GENERATED_COMMENT
@@ -96,7 +96,7 @@ fun File.generateApiFrom(jsonSource: File) {
9696
.writeTo(this)
9797
}
9898

99-
KtCallableGenerationService().generate(Constraints.MAX_FUNCTION_ARG_COUNT).writeTo(this)
99+
LambdaCallableGenerationService().generate(Constraints.MAX_FUNCTION_ARG_COUNT).writeTo(this)
100100
SignalGenerationService().generate(Constraints.MAX_FUNCTION_ARG_COUNT).writeTo(this)
101101
}
102102

kt/api-generator/src/main/kotlin/godot/codegen/services/IKtCallableGenerationService.kt renamed to kt/api-generator/src/main/kotlin/godot/codegen/services/ILambdaCallableGenerationService.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@ package godot.codegen.services
22

33
import com.squareup.kotlinpoet.FileSpec
44

5-
interface IKtCallableGenerationService {
5+
interface ILambdaCallableGenerationService {
66
fun generate(maxArgumentCount: Int): FileSpec
7-
}
7+
}

kt/api-generator/src/main/kotlin/godot/codegen/services/impl/GenerationService.kt

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,9 @@ class GenerationService(
8282
return generateCommonsForClass(
8383
classTypeBuilder,
8484
singletonClass,
85+
true,
8586
classTypeBuilder
87+
8688
)
8789
}
8890

@@ -105,12 +107,13 @@ class GenerationService(
105107

106108
classTypeBuilder.generateClassConstructor(clazz.engineClassDBIndexName)
107109

108-
return generateCommonsForClass(classTypeBuilder, clazz)
110+
return generateCommonsForClass(classTypeBuilder, clazz, false)
109111
}
110112

111113
private fun generateCommonsForClass(
112114
classTypeBuilder: TypeSpec.Builder,
113115
enrichedClass: EnrichedClass,
116+
isSingleton: Boolean,
114117
constantsTypeReceiver: TypeSpec.Builder = TypeSpec.companionObjectBuilder()
115118
): FileSpec {
116119
val name = enrichedClass.name
@@ -151,22 +154,22 @@ class GenerationService(
151154
}
152155

153156
for (method in enrichedClass.methods.filter { it.internal.isStatic }) {
154-
constantsTypeReceiver.addFunction(generateMethod(enrichedClass, method, true))
157+
constantsTypeReceiver.addFunction(generateMethod(enrichedClass, method, true, isSingleton))
155158
}
156159

157160
if (constantsTypeReceiver != classTypeBuilder) {
158161
constantsTypeReceiver.build().let { classTypeBuilder.addType(it) }
159162
}
160163

161164
for (signal in enrichedClass.signals) {
162-
classTypeBuilder.addProperty(generateSignals(signal))
165+
classTypeBuilder.addProperty(generateSignals(signal, isSingleton))
163166
}
164167

165168
for (property in enrichedClass.properties) {
166-
val propertySpec = generateProperty(enrichedClass, property) ?: continue
169+
val propertySpec = generateProperty(enrichedClass, property, isSingleton) ?: continue
167170
classTypeBuilder.addProperty(propertySpec)
168171
if (property.hasValidSetterInClass && property.isLocalCopyCoreTypes()) {
169-
classTypeBuilder.addFunction(generateCoreTypeHelper(enrichedClass, property))
172+
classTypeBuilder.addFunction(generateCoreTypeHelper(enrichedClass, property, isSingleton))
170173
}
171174
}
172175

@@ -181,7 +184,7 @@ class GenerationService(
181184
}
182185
shouldGenerate = shouldGenerate && nativeStructureRepository.findMatchingType(method) == null
183186
if (shouldGenerate) {
184-
classTypeBuilder.addFunction(generateMethod(enrichedClass, method))
187+
classTypeBuilder.addFunction(generateMethod(enrichedClass, method, false, isSingleton))
185188
}
186189
}
187190

@@ -385,7 +388,7 @@ class GenerationService(
385388
}
386389
}
387390

388-
private fun generateSignals(signal: EnrichedSignal): PropertySpec {
391+
private fun generateSignals(signal: EnrichedSignal, isSingleton: Boolean): PropertySpec {
389392
val signalClass = signal.getTypeClassName()
390393
val arguments = signal.arguments
391394

@@ -400,10 +403,14 @@ class GenerationService(
400403
ClassName(godotCorePackage, "Signal" + arguments.size)
401404
)
402405

406+
if (isSingleton) {
407+
builder.addAnnotation(JvmStatic::class)
408+
}
409+
403410
return builder.build()
404411
}
405412

406-
private fun generateProperty(enrichedClass: EnrichedClass, property: EnrichedProperty): PropertySpec? {
413+
private fun generateProperty(enrichedClass: EnrichedClass, property: EnrichedProperty, isSingleton: Boolean): PropertySpec? {
407414
if (!property.hasValidGetterInClass && !property.hasValidSetterInClass) return null
408415

409416
// We can't trust the property alone because some of them don't have a getter so we have to check on the setter's first parameter as well.
@@ -413,6 +420,10 @@ class GenerationService(
413420
val propertyType = propertyTypeName.typeName
414421
val propertySpecBuilder = PropertySpec.builder(property.name, propertyType).addModifiers(KModifier.FINAL)
415422

423+
if (isSingleton) {
424+
propertySpecBuilder.addAnnotation(JvmStatic::class)
425+
}
426+
416427
if (property.hasValidGetterInClass) {
417428
val methodName = property.getter
418429

@@ -502,11 +513,15 @@ class GenerationService(
502513
return propertySpecBuilder.build()
503514
}
504515

505-
private fun generateCoreTypeHelper(enrichedClass: EnrichedClass, property: EnrichedProperty): FunSpec {
516+
private fun generateCoreTypeHelper(enrichedClass: EnrichedClass, property: EnrichedProperty, isSingleton: Boolean): FunSpec {
506517
val parameterTypeName = property.getCastedType()
507518
val parameterName = property.name
508519
val propertyFunSpec = FunSpec.builder("${parameterName}Mutate").addModifiers(KModifier.FINAL)
509520

521+
if (isSingleton) {
522+
propertyFunSpec.addAnnotation(JvmStatic::class)
523+
}
524+
510525
return propertyFunSpec
511526
.addParameter(
512527
ParameterSpec.builder(
@@ -555,7 +570,7 @@ class GenerationService(
555570
.build()
556571
}
557572

558-
private fun generateMethod(enrichedClass: EnrichedClass, method: EnrichedMethod, isStatic: Boolean = false): FunSpec {
573+
private fun generateMethod(enrichedClass: EnrichedClass, method: EnrichedMethod, isStatic: Boolean, isSingleton: Boolean): FunSpec {
559574
val modifiers = mutableListOf<KModifier>()
560575

561576
// This method already exist in the Kotlin class Any. We have to override it because Godot uses the same name in Object.
@@ -626,9 +641,14 @@ class GenerationService(
626641
)
627642
.build()
628643
)
644+
629645
}
630646
}
631647

648+
if (isSingleton) {
649+
generatedFunBuilder.addAnnotation(JvmStatic::class)
650+
}
651+
632652
return generatedFunBuilder.build()
633653
}
634654

@@ -938,7 +958,7 @@ class GenerationService(
938958
)
939959
}
940960

941-
fun TypeSpec.Builder.generateOperatorMethods(
961+
private fun TypeSpec.Builder.generateOperatorMethods(
942962
operations: Array<String>,
943963
enum: EnrichedEnum,
944964
isOperator: Boolean = false

kt/api-generator/src/main/kotlin/godot/codegen/services/impl/KtCallableGenerationService.kt renamed to kt/api-generator/src/main/kotlin/godot/codegen/services/impl/LambdaCallableGenerationService.kt

Lines changed: 85 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,25 +14,27 @@ import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
1414
import com.squareup.kotlinpoet.PropertySpec
1515
import com.squareup.kotlinpoet.TypeSpec
1616
import com.squareup.kotlinpoet.TypeVariableName
17-
import godot.codegen.services.IKtCallableGenerationService
17+
import godot.codegen.services.ILambdaCallableGenerationService
18+
import godot.codegen.utils.GenericClassNameInfo
1819
import godot.tools.common.constants.GodotFunctions
1920
import godot.tools.common.constants.GodotKotlinJvmTypes
2021
import godot.tools.common.constants.VARIANT_PARSER_NIL
2122
import godot.tools.common.constants.godotCorePackage
2223

23-
class KtCallableGenerationService : IKtCallableGenerationService {
24+
class LambdaCallableGenerationService : ILambdaCallableGenerationService {
2425
override fun generate(maxArgumentCount: Int): FileSpec {
25-
val callableFileSpec = FileSpec.builder(godotCorePackage, "KtCallables")
26+
val callableFileSpec = FileSpec.builder(godotCorePackage, "LambdaCallables")
2627

27-
for (argCount in 0 .. maxArgumentCount) {
28+
for (argCount in 0..maxArgumentCount) {
29+
val ktCallableClassName = ClassName(godotCorePackage, "$KT_CALLABLE_NAME$argCount")
2830
val classBuilder = TypeSpec
29-
.classBuilder(ClassName(godotCorePackage, "$KT_CALLABLE_NAME$argCount"))
31+
.classBuilder(ktCallableClassName)
3032
.superclass(
3133
KT_CALLABLE_CLASS_NAME
3234
.parameterizedBy(returnTypeParameter)
3335
)
3436

35-
val argumentRange = 0 ..< argCount
37+
val argumentRange = 0..<argCount
3638

3739
classBuilder
3840
.addSuperclassConstructorParameter(
@@ -129,6 +131,8 @@ class KtCallableGenerationService : IKtCallableGenerationService {
129131
)
130132
.build()
131133
)
134+
.addModifiers(KModifier.INTERNAL)
135+
.addAnnotation(PublishedApi::class)
132136

133137
classBuilder.primaryConstructor(primaryConstructor.build())
134138

@@ -231,13 +235,13 @@ class KtCallableGenerationService : IKtCallableGenerationService {
231235
buildString {
232236
append("return·%T($VARIANT_TYPE_ARGUMENT_NAME")
233237

234-
for (index in (0 ..< removedTypeVariables)) {
238+
for (index in (0..<removedTypeVariables)) {
235239
append(",·p${index}Type")
236240
}
237241

238242
append(")·{·")
239243

240-
for (index in (0 ..< removedTypeVariables)) {
244+
for (index in (0..<removedTypeVariables)) {
241245
if (index != 0) append("")
242246

243247
append("p${index}:·%T")
@@ -263,11 +267,14 @@ class KtCallableGenerationService : IKtCallableGenerationService {
263267
++removedTypeVariables
264268
}
265269

270+
val genericClassNameInfo = GenericClassNameInfo(ktCallableClassName, argCount)
271+
classBuilder.addType(generateKtCallableCompanion(argCount, genericClassNameInfo))
272+
266273
callableFileSpec.addType(classBuilder.build())
267274

268275
val variantMapperMember = MemberName(godotCorePackage, "variantMapper")
269276
callableFileSpec.addFunction(
270-
FunSpec.builder(CALLABLE_FUNCTION_NAME)
277+
FunSpec.builder(CALLABLE_FUNCTION_NAME + argCount)
271278
.addTypeVariables(typeVariableNames.map { it.copy(reified = true) })
272279
.addTypeVariable(returnTypeParameter.copy(reified = true))
273280
.addModifiers(KModifier.INLINE)
@@ -312,7 +319,7 @@ class KtCallableGenerationService : IKtCallableGenerationService {
312319
.addTypeVariable(returnTypeParameter.copy(reified = true))
313320
.addModifiers(KModifier.INLINE)
314321
.receiver(lambdaTypeName)
315-
.addCode("return·$CALLABLE_FUNCTION_NAME(this)")
322+
.addCode("return·$CALLABLE_FUNCTION_NAME$argCount(this)")
316323
.build()
317324
)
318325
}
@@ -326,12 +333,79 @@ class KtCallableGenerationService : IKtCallableGenerationService {
326333
return callableFileSpec.build()
327334
}
328335

336+
// JAVA BRIDGE FUNCTION
337+
private fun generateKtCallableCompanion(argCount: Int, genericClassNameInfo: GenericClassNameInfo): TypeSpec {
338+
val variantMapperMember = MemberName(godotCorePackage, "variantMapper")
339+
340+
return TypeSpec
341+
.companionObjectBuilder()
342+
.addFunction(
343+
FunSpec
344+
.builder(JAVA_CREATE_METHOD_NAME)
345+
.addTypeVariable(returnTypeParameter)
346+
.addTypeVariables(genericClassNameInfo.genericTypes)
347+
.addParameter(
348+
ParameterSpec
349+
.builder("returnClass", JAVA_CLASS_CLASS_NAME.parameterizedBy(returnTypeParameter))
350+
.build()
351+
)
352+
.addParameters(genericClassNameInfo.toParameterSpecList().map {
353+
ParameterSpec
354+
.builder(it.name + "Class", JAVA_CLASS_CLASS_NAME.parameterizedBy(it.type))
355+
.build()
356+
})
357+
.addParameter(
358+
ParameterSpec
359+
.builder(
360+
FUNCTION_PARAMETER_NAME,
361+
genericClassNameInfo.toLambdaTypeName(returnTypeParameter)
362+
)
363+
.build()
364+
)
365+
.addCode(
366+
CodeBlock.of(
367+
buildString {
368+
append("return·$KT_CALLABLE_NAME$argCount(")
369+
append("%M.getOrDefault(%T.getOrCreateKotlinClass(returnClass),·%T),·")
370+
genericClassNameInfo.toParameterSpecList().forEach {
371+
append("%M[%T.getOrCreateKotlinClass(${it.name}Class)]!!,·")
372+
}
373+
append(FUNCTION_PARAMETER_NAME)
374+
append(')')
375+
},
376+
variantMapperMember,
377+
REFLECTION_CLASS_NAME,
378+
VARIANT_PARSER_NIL,
379+
*genericClassNameInfo.genericTypes
380+
.flatMap {
381+
listOf(variantMapperMember, REFLECTION_CLASS_NAME)
382+
}
383+
.toTypedArray()
384+
)
385+
)
386+
.addAnnotation(JvmStatic::class)
387+
.addAnnotation(
388+
AnnotationSpec
389+
.builder(JvmName::class)
390+
.addMember("\"create\"")
391+
.build()
392+
)
393+
.build()
394+
)
395+
.build()
396+
}
397+
329398
private companion object {
330399
const val FUNCTION_PARAMETER_NAME = "function"
331-
const val KT_CALLABLE_NAME = "KtCallable"
400+
const val KT_CALLABLE_NAME = "LambdaCallable"
332401
const val CALLABLE_FUNCTION_NAME = "callable"
402+
const val JAVA_CREATE_METHOD_NAME = "javaCreate"
333403
const val VARIANT_TYPE_ARGUMENT_NAME = "variantConverter"
334404
val KT_CALLABLE_CLASS_NAME = ClassName(godotCorePackage, KT_CALLABLE_NAME)
335405
val returnTypeParameter = TypeVariableName("R", ANY.copy(nullable = true))
406+
407+
//Java
408+
val REFLECTION_CLASS_NAME = ClassName("kotlin.jvm.internal", "Reflection")
409+
val JAVA_CLASS_CLASS_NAME = ClassName("java.lang", "Class")
336410
}
337411
}

0 commit comments

Comments
 (0)