Skip to content

Commit 5a448ec

Browse files
committed
feat: Implement KotlinCallableCustom to enable lambda callables
1 parent 5de4af5 commit 5a448ec

File tree

42 files changed

+5213
-1514
lines changed

Some content is hidden

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

42 files changed

+5213
-1514
lines changed

harness/tests/scripts/godot/tests/Invocation.gdj

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,20 @@ fqName = godot.tests.Invocation
66
relativeSourcePath = src/main/kotlin/godot/tests/Invocation.kt
77
baseType = Node3D
88
supertypes = [
9-
godot.Node3D,
9+
godot.Node3D,
1010
godot.Node,
1111
godot.Object,
1212
godot.core.KtObject,
1313
kotlin.Any
1414
]
1515
signals = [
16-
no_param,
16+
no_param,
1717
one_param,
1818
two_param,
1919
signal_with_multiple_targets
2020
]
2121
properties = [
22-
button,
22+
button,
2323
enum_list,
2424
vector_list,
2525
enum_list_mutable,
@@ -78,7 +78,7 @@ properties = [
7878
array
7979
]
8080
functions = [
81-
target_function_one,
81+
target_function_one,
8282
target_function_two,
8383
int_value,
8484
long_value,
@@ -171,4 +171,4 @@ functions = [
171171
nullable_string_is_null,
172172
nullable_return_type,
173173
create_variant_array_of_user_type
174-
]
174+
]
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// THIS FILE IS GENERATED! DO NOT EDIT OR DELETE IT. EDIT OR DELETE THE ASSOCIATED SOURCE CODE FILE INSTEAD
2+
// Note: You can however freely move this file inside your godot project if you want. Keep in mind however, that if you rename the originating source code file, this file will be deleted and regenerated as a new file instead of being updated! Other modifications to the source file however, will result in this file being updated.
3+
4+
registeredName = LambdaCallableTest
5+
fqName = godot.tests.LambdaCallableTest
6+
relativeSourcePath = src/main/kotlin/godot/tests/LambdaCallableTest.kt
7+
baseType = Node
8+
supertypes = [
9+
godot.Node,
10+
godot.Object,
11+
godot.core.KtObject,
12+
kotlin.Any
13+
]
14+
signals = [
15+
signal_no_param,
16+
signal_with_params
17+
]
18+
properties = [
19+
has_signal_no_param_been_triggered,
20+
signal_string,
21+
signal_long,
22+
signal_node,
23+
kt_callable,
24+
kt_callable_string
25+
]
26+
functions = [
27+
_ready,
28+
emit_signal_no_param,
29+
emit_signal_with_param
30+
]

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import godot.Button;
44
import godot.Node;
55
import godot.annotation.*;
6-
import godot.core.Callable;
6+
import godot.core.NativeCallable;
77
import godot.core.StringNameUtils;
88
import godot.signals.Signal;
99
import godot.signals.Signal2;
@@ -70,7 +70,7 @@ public String greeting() {
7070
public void connectAndTriggerSignal() {
7171
connect(
7272
StringNameUtils.asStringName("test_signal"),
73-
new Callable(this, StringNameUtils.asStringName("signal_callback")),
73+
new NativeCallable(this, StringNameUtils.asStringName("signal_callback")),
7474
(int) ConnectFlags.CONNECT_ONE_SHOT.getId()
7575
);
7676
emitSignal(StringNameUtils.asStringName("test_signal"));

harness/tests/src/main/kotlin/godot/tests/Invocation.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import godot.core.dictionaryOf
4242
import godot.core.variantArrayOf
4343
import godot.extensions.getNodeAs
4444
import godot.registration.Range
45+
import godot.signals.connect
4546
import godot.signals.signal
4647
import godot.tests.subpackage.OtherScript
4748
import godot.util.RealT
@@ -407,6 +408,10 @@ class Invocation : Node3D() {
407408
oneParam.connect(invocation, OtherScript::hookOneParam)
408409
twoParam.connect(invocation, OtherScript::hookTwoParam)
409410

411+
noParam.connect { println("noParam signal emitted") }
412+
oneParam.connect { b -> println("oneParam signal emitted with $b") }
413+
twoParam.connect { p0, p1 -> println("twoParam signal emitted with $p0 and $p1") }
414+
410415
signalWithMultipleTargets.connect(this, Invocation::targetFunctionOne)
411416
signalWithMultipleTargets.connect(this, Invocation::targetFunctionTwo)
412417

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package godot.tests
2+
3+
import godot.Node
4+
import godot.annotation.RegisterClass
5+
import godot.annotation.RegisterFunction
6+
import godot.annotation.RegisterProperty
7+
import godot.annotation.RegisterSignal
8+
import godot.core.callable.asCallable
9+
import godot.signals.connect
10+
import godot.signals.signal
11+
12+
@RegisterClass
13+
class LambdaCallableTest : Node() {
14+
15+
@RegisterSignal
16+
val signalNoParam by signal()
17+
18+
@RegisterProperty
19+
var hasSignalNoParamBeenTriggered = false
20+
21+
@RegisterSignal
22+
val signalWithParams by signal<String, Long, Node>("str", "long", "node")
23+
24+
@RegisterProperty
25+
lateinit var signalString: String
26+
27+
@RegisterProperty
28+
var signalLong: Long = Long.MIN_VALUE
29+
30+
@RegisterProperty
31+
lateinit var signalNode: Node
32+
33+
@RegisterProperty
34+
var ktCallable = { str: String -> ktCallableString = str }.asCallable()
35+
36+
@RegisterProperty
37+
lateinit var ktCallableString: String
38+
39+
@RegisterFunction
40+
override fun _ready() {
41+
signalNoParam.connect {
42+
hasSignalNoParamBeenTriggered = true
43+
}
44+
45+
signalWithParams.connect { p0, p1, p2 ->
46+
signalString = p0
47+
signalLong = p1
48+
signalNode = p2
49+
}
50+
}
51+
52+
@RegisterFunction
53+
fun emitSignalNoParam() {
54+
signalNoParam.emit()
55+
}
56+
57+
@RegisterFunction
58+
fun emitSignalWithParam(str: String, long: Long, node: Node) {
59+
signalWithParams.emit(str, long, node)
60+
}
61+
}

harness/tests/src/main/kotlin/godot/tests/callable/CallableMethodBindTest.kt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import godot.Node
44
import godot.annotation.RegisterClass
55
import godot.annotation.RegisterFunction
66
import godot.annotation.RegisterProperty
7-
import godot.core.Callable
7+
import godot.core.NativeCallable
88
import godot.core.VariantArray
99
import godot.core.variantArrayOf
1010
import godot.global.GD
@@ -16,22 +16,22 @@ class CallableMethodBindTest: Node() {
1616

1717
@RegisterFunction
1818
fun callWithMethodWithAllBinds() {
19-
Callable(this, CallableMethodBindTest::readySignalMethodBindTest).bind(1, 2, 3).call()
19+
NativeCallable(this, CallableMethodBindTest::readySignalMethodBindTest).bind(1, 2, 3).call()
2020
}
2121

2222
@RegisterFunction
2323
fun callWithMethodWithTwoBinds() {
24-
Callable(this, CallableMethodBindTest::readySignalMethodBindTest).bind(2, 3).call(0)
24+
NativeCallable(this, CallableMethodBindTest::readySignalMethodBindTest).bind(2, 3).call(0)
2525
}
2626

2727
@RegisterFunction
2828
fun callWithMethodWithOneBind() {
29-
Callable(this, CallableMethodBindTest::readySignalMethodBindTest).bind(3).call(0, 0)
29+
NativeCallable(this, CallableMethodBindTest::readySignalMethodBindTest).bind(3).call(0, 0)
3030
}
3131

3232
@RegisterFunction
3333
fun callWithMethodWithNoBind() {
34-
Callable(this, CallableMethodBindTest::readySignalMethodBindTest).bind().call(0, 0, 0)
34+
NativeCallable(this, CallableMethodBindTest::readySignalMethodBindTest).bind().call(0, 0, 0)
3535
}
3636

3737
@RegisterFunction
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
extends "res://addons/gut/test.gd"
2+
3+
4+
func test_signal_without_param():
5+
var lambda_callable_test_script = LambdaCallableTest.new()
6+
get_tree().root.add_child(lambda_callable_test_script)
7+
lambda_callable_test_script.emit_signal_no_param()
8+
assert_true(lambda_callable_test_script.has_signal_no_param_been_triggered)
9+
get_tree().root.remove_child(lambda_callable_test_script)
10+
lambda_callable_test_script.free()
11+
12+
func test_signal_with_param():
13+
var lambda_callable_test_script = LambdaCallableTest.new()
14+
get_tree().root.add_child(lambda_callable_test_script)
15+
16+
var expected_str = "expected"
17+
var expected_int = randi()
18+
var expected_node = lambda_callable_test_script
19+
20+
lambda_callable_test_script.emit_signal_with_param(expected_str, expected_int, expected_node)
21+
22+
assert_eq(lambda_callable_test_script.signal_string, expected_str)
23+
assert_eq(lambda_callable_test_script.signal_long, expected_int)
24+
assert_eq(lambda_callable_test_script.signal_node, expected_node)
25+
get_tree().root.remove_child(lambda_callable_test_script)
26+
lambda_callable_test_script.free()
27+
28+
func test_kotlin_lambda_call_from_gdscript():
29+
var lambda_callable_test_script = LambdaCallableTest.new()
30+
31+
var expected_str = "expected"
32+
33+
lambda_callable_test_script.kt_callable.call(expected_str)
34+
35+
assert_eq(lambda_callable_test_script.kt_callable_string, expected_str)
36+
lambda_callable_test_script.free()

kt/api-generator/src/main/kotlin/godot/codegen/extensions/TypedExtensions.kt

Lines changed: 16 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -15,34 +15,7 @@ import godot.codegen.traits.CastableTrait
1515
import godot.codegen.traits.NullableTrait
1616
import godot.codegen.traits.TypedTrait
1717
import godot.codegen.traits.WithDefaultValueTrait
18-
import godot.tools.common.constants.GODOT_ARRAY
19-
import godot.tools.common.constants.GODOT_DICTIONARY
20-
import godot.tools.common.constants.GODOT_ERROR
21-
import godot.tools.common.constants.GodotKotlinJvmTypes
22-
import godot.tools.common.constants.GodotTypes
23-
import godot.tools.common.constants.VARIANT_TYPE_ANY
24-
import godot.tools.common.constants.VARIANT_TYPE_ARRAY
25-
import godot.tools.common.constants.VARIANT_TYPE_BOOL
26-
import godot.tools.common.constants.VARIANT_TYPE_DOUBLE
27-
import godot.tools.common.constants.VARIANT_TYPE_LONG
28-
import godot.tools.common.constants.VARIANT_TYPE_NIL
29-
import godot.tools.common.constants.VARIANT_TYPE_NODE_PATH
30-
import godot.tools.common.constants.VARIANT_TYPE_OBJECT
31-
import godot.tools.common.constants.VARIANT_TYPE_PACKED_BYTE_ARRAY
32-
import godot.tools.common.constants.VARIANT_TYPE_PACKED_COLOR_ARRAY
33-
import godot.tools.common.constants.VARIANT_TYPE_PACKED_FLOAT_32_ARRAY
34-
import godot.tools.common.constants.VARIANT_TYPE_PACKED_FLOAT_64_ARRAY
35-
import godot.tools.common.constants.VARIANT_TYPE_PACKED_INT_32_ARRAY
36-
import godot.tools.common.constants.VARIANT_TYPE_PACKED_INT_64_ARRAY
37-
import godot.tools.common.constants.VARIANT_TYPE_PACKED_STRING_ARRAY
38-
import godot.tools.common.constants.VARIANT_TYPE_PACKED_VECTOR2_ARRAY
39-
import godot.tools.common.constants.VARIANT_TYPE_PACKED_VECTOR3_ARRAY
40-
import godot.tools.common.constants.VARIANT_TYPE_STRING_NAME
41-
import godot.tools.common.constants.VARIANT_TYPE__RID
42-
import godot.tools.common.constants.godotApiPackage
43-
import godot.tools.common.constants.godotCorePackage
44-
import godot.tools.common.constants.signalPackage
45-
import godot.tools.common.constants.variantTypePackage
18+
import godot.tools.common.constants.*
4619
import java.util.*
4720

4821
const val enumPrefix = "enum::"
@@ -126,6 +99,7 @@ fun TypedTrait.getTypeClassName(): ClassTypeNameWrapper {
12699
type == GodotTypes.dictionary -> ClassTypeNameWrapper(GODOT_DICTIONARY)
127100
.parameterizedBy(ANY.copy(nullable = true), ANY.copy(nullable = true))
128101
type == GodotTypes.variant -> ClassTypeNameWrapper(ANY)
102+
type == GodotTypes.callable -> ClassTypeNameWrapper(GODOT_CALLABLE_BASE)
129103
isCoreType() -> ClassTypeNameWrapper(ClassName(godotCorePackage, type!!))
130104
else -> ClassTypeNameWrapper(ClassName(godotApiPackage, type!!))
131105
}
@@ -167,20 +141,20 @@ val TypedTrait.jvmVariantTypeValue: ClassName
167141
}
168142
}
169143

170-
fun <T> T.getDefaultValueKotlinString(): String?
144+
fun <T> T.getDefaultValueKotlinString(): Pair<String, Array<Any?>>?
171145
where T : WithDefaultValueTrait,
172146
T : NullableTrait,
173147
T : CastableTrait {
174148
val defaultValueString = defaultValue ?: return null
175149
return when {
176-
nullable && defaultValue == "null" -> defaultValueString
177-
type == GodotTypes.color -> "${GodotKotlinJvmTypes.color}($defaultValueString)"
178-
type == GodotTypes.variant -> defaultValueString
179-
type == GodotTypes.bool -> defaultValueString.lowercase(Locale.US)
180-
type == GodotTypes.float && meta == GodotMeta.Float.float -> "${intToFloat(defaultValueString)}f"
181-
type == GodotTypes.float -> intToFloat(defaultValueString)
150+
nullable && defaultValue == "null" -> defaultValueString to arrayOf()
151+
type == GodotTypes.color -> "${GodotKotlinJvmTypes.color}($defaultValueString)" to arrayOf()
152+
type == GodotTypes.variant -> defaultValueString to arrayOf()
153+
type == GodotTypes.bool -> defaultValueString.lowercase(Locale.US) to arrayOf()
154+
type == GodotTypes.float && meta == GodotMeta.Float.float -> "${intToFloat(defaultValueString)}f" to arrayOf()
155+
type == GodotTypes.float -> intToFloat(defaultValueString) to arrayOf()
182156
type == GodotTypes.stringName -> "${GodotKotlinJvmTypes.stringName}(".plus(defaultValueString.replace("&", ""))
183-
.plus(")")
157+
.plus(")") to arrayOf()
184158

185159
type == GodotTypes.array || isTypedArray() ->
186160
if (defaultValueString.startsWith("Array")) {
@@ -192,14 +166,15 @@ fun <T> T.getDefaultValueKotlinString(): String?
192166
"$godotCorePackage.variantArrayOf("
193167
.plus(defaultValueString.removePrefix("[").removeSuffix("]"))
194168
.plus(")")
195-
}
169+
} to arrayOf()
196170

197171
type == GodotTypes.rect2 -> defaultValueString
198172
.replace(",", ".0,")
199-
.replace(")", ".0)")
173+
.replace(")", ".0)") to arrayOf()
174+
175+
type == GodotTypes.callable -> "%T()" to arrayOf(GODOT_CALLABLE)
200176

201177
type == GodotTypes.rid ||
202-
type == GodotTypes.callable ||
203178
type == GodotTypes.dictionary ||
204179
type == GodotTypes.transform2D ||
205180
type == GodotTypes.transform3D ||
@@ -212,9 +187,9 @@ fun <T> T.getDefaultValueKotlinString(): String?
212187
type == GodotTypes.packedInt64Array ||
213188
type == GodotTypes.packedVector2Array ||
214189
type == GodotTypes.packedVector3Array
215-
-> "$type()"
190+
-> "$type()" to arrayOf()
216191

217-
else -> defaultValueString
192+
else -> defaultValueString to arrayOf()
218193
}
219194
}
220195

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import godot.codegen.repositories.*
1111
import godot.codegen.repositories.impl.*
1212
import godot.codegen.services.*
1313
import godot.codegen.services.impl.*
14+
import godot.tools.common.constants.Constraints
1415
import godot.tools.common.constants.GENERATED_COMMENT
1516
import java.io.File
1617

@@ -78,4 +79,7 @@ fun File.generateApiFrom(jsonSource: File, docsDir: File? = null) {
7879
.build()
7980
.writeTo(this)
8081
}
82+
83+
KtCallableGenerationService().generate(Constraints.MAX_FUNCTION_ARG_COUNT).writeTo(this)
84+
SignalGenerationService().generate(Constraints.MAX_FUNCTION_ARG_COUNT).writeTo(this)
8185
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package godot.codegen.services
2+
3+
import com.squareup.kotlinpoet.FileSpec
4+
5+
interface IKtCallableGenerationService {
6+
fun generate(maxArgumentCount: Int): FileSpec
7+
}

0 commit comments

Comments
 (0)