Skip to content

Commit

Permalink
feat: Implement KotlinCallableCustom to enable lambda callables
Browse files Browse the repository at this point in the history
  • Loading branch information
piiertho committed Aug 20, 2024
1 parent 5de4af5 commit 5a448ec
Show file tree
Hide file tree
Showing 42 changed files with 5,213 additions and 1,514 deletions.
10 changes: 5 additions & 5 deletions harness/tests/scripts/godot/tests/Invocation.gdj
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,20 @@ fqName = godot.tests.Invocation
relativeSourcePath = src/main/kotlin/godot/tests/Invocation.kt
baseType = Node3D
supertypes = [
godot.Node3D,
godot.Node3D,
godot.Node,
godot.Object,
godot.core.KtObject,
kotlin.Any
]
signals = [
no_param,
no_param,
one_param,
two_param,
signal_with_multiple_targets
]
properties = [
button,
button,
enum_list,
vector_list,
enum_list_mutable,
Expand Down Expand Up @@ -78,7 +78,7 @@ properties = [
array
]
functions = [
target_function_one,
target_function_one,
target_function_two,
int_value,
long_value,
Expand Down Expand Up @@ -171,4 +171,4 @@ functions = [
nullable_string_is_null,
nullable_return_type,
create_variant_array_of_user_type
]
]
30 changes: 30 additions & 0 deletions harness/tests/scripts/godot/tests/LambdaCallableTest.gdj
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// THIS FILE IS GENERATED! DO NOT EDIT OR DELETE IT. EDIT OR DELETE THE ASSOCIATED SOURCE CODE FILE INSTEAD
// 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.

registeredName = LambdaCallableTest
fqName = godot.tests.LambdaCallableTest
relativeSourcePath = src/main/kotlin/godot/tests/LambdaCallableTest.kt
baseType = Node
supertypes = [
godot.Node,
godot.Object,
godot.core.KtObject,
kotlin.Any
]
signals = [
signal_no_param,
signal_with_params
]
properties = [
has_signal_no_param_been_triggered,
signal_string,
signal_long,
signal_node,
kt_callable,
kt_callable_string
]
functions = [
_ready,
emit_signal_no_param,
emit_signal_with_param
]
4 changes: 2 additions & 2 deletions harness/tests/src/main/java/godot/tests/JavaTestClass.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import godot.Button;
import godot.Node;
import godot.annotation.*;
import godot.core.Callable;
import godot.core.NativeCallable;
import godot.core.StringNameUtils;
import godot.signals.Signal;
import godot.signals.Signal2;
Expand Down Expand Up @@ -70,7 +70,7 @@ public String greeting() {
public void connectAndTriggerSignal() {
connect(
StringNameUtils.asStringName("test_signal"),
new Callable(this, StringNameUtils.asStringName("signal_callback")),
new NativeCallable(this, StringNameUtils.asStringName("signal_callback")),
(int) ConnectFlags.CONNECT_ONE_SHOT.getId()
);
emitSignal(StringNameUtils.asStringName("test_signal"));
Expand Down
5 changes: 5 additions & 0 deletions harness/tests/src/main/kotlin/godot/tests/Invocation.kt
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import godot.core.dictionaryOf
import godot.core.variantArrayOf
import godot.extensions.getNodeAs
import godot.registration.Range
import godot.signals.connect
import godot.signals.signal
import godot.tests.subpackage.OtherScript
import godot.util.RealT
Expand Down Expand Up @@ -407,6 +408,10 @@ class Invocation : Node3D() {
oneParam.connect(invocation, OtherScript::hookOneParam)
twoParam.connect(invocation, OtherScript::hookTwoParam)

noParam.connect { println("noParam signal emitted") }
oneParam.connect { b -> println("oneParam signal emitted with $b") }
twoParam.connect { p0, p1 -> println("twoParam signal emitted with $p0 and $p1") }

signalWithMultipleTargets.connect(this, Invocation::targetFunctionOne)
signalWithMultipleTargets.connect(this, Invocation::targetFunctionTwo)

Expand Down
61 changes: 61 additions & 0 deletions harness/tests/src/main/kotlin/godot/tests/LambdaCallableTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package godot.tests

import godot.Node
import godot.annotation.RegisterClass
import godot.annotation.RegisterFunction
import godot.annotation.RegisterProperty
import godot.annotation.RegisterSignal
import godot.core.callable.asCallable
import godot.signals.connect
import godot.signals.signal

@RegisterClass
class LambdaCallableTest : Node() {

@RegisterSignal
val signalNoParam by signal()

@RegisterProperty
var hasSignalNoParamBeenTriggered = false

@RegisterSignal
val signalWithParams by signal<String, Long, Node>("str", "long", "node")

@RegisterProperty
lateinit var signalString: String

@RegisterProperty
var signalLong: Long = Long.MIN_VALUE

@RegisterProperty
lateinit var signalNode: Node

@RegisterProperty
var ktCallable = { str: String -> ktCallableString = str }.asCallable()

@RegisterProperty
lateinit var ktCallableString: String

@RegisterFunction
override fun _ready() {
signalNoParam.connect {
hasSignalNoParamBeenTriggered = true
}

signalWithParams.connect { p0, p1, p2 ->
signalString = p0
signalLong = p1
signalNode = p2
}
}

@RegisterFunction
fun emitSignalNoParam() {
signalNoParam.emit()
}

@RegisterFunction
fun emitSignalWithParam(str: String, long: Long, node: Node) {
signalWithParams.emit(str, long, node)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import godot.Node
import godot.annotation.RegisterClass
import godot.annotation.RegisterFunction
import godot.annotation.RegisterProperty
import godot.core.Callable
import godot.core.NativeCallable
import godot.core.VariantArray
import godot.core.variantArrayOf
import godot.global.GD
Expand All @@ -16,22 +16,22 @@ class CallableMethodBindTest: Node() {

@RegisterFunction
fun callWithMethodWithAllBinds() {
Callable(this, CallableMethodBindTest::readySignalMethodBindTest).bind(1, 2, 3).call()
NativeCallable(this, CallableMethodBindTest::readySignalMethodBindTest).bind(1, 2, 3).call()
}

@RegisterFunction
fun callWithMethodWithTwoBinds() {
Callable(this, CallableMethodBindTest::readySignalMethodBindTest).bind(2, 3).call(0)
NativeCallable(this, CallableMethodBindTest::readySignalMethodBindTest).bind(2, 3).call(0)
}

@RegisterFunction
fun callWithMethodWithOneBind() {
Callable(this, CallableMethodBindTest::readySignalMethodBindTest).bind(3).call(0, 0)
NativeCallable(this, CallableMethodBindTest::readySignalMethodBindTest).bind(3).call(0, 0)
}

@RegisterFunction
fun callWithMethodWithNoBind() {
Callable(this, CallableMethodBindTest::readySignalMethodBindTest).bind().call(0, 0, 0)
NativeCallable(this, CallableMethodBindTest::readySignalMethodBindTest).bind().call(0, 0, 0)
}

@RegisterFunction
Expand Down
36 changes: 36 additions & 0 deletions harness/tests/test/unit/test_lambda_callable.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
extends "res://addons/gut/test.gd"


func test_signal_without_param():
var lambda_callable_test_script = LambdaCallableTest.new()
get_tree().root.add_child(lambda_callable_test_script)
lambda_callable_test_script.emit_signal_no_param()
assert_true(lambda_callable_test_script.has_signal_no_param_been_triggered)
get_tree().root.remove_child(lambda_callable_test_script)
lambda_callable_test_script.free()

func test_signal_with_param():
var lambda_callable_test_script = LambdaCallableTest.new()
get_tree().root.add_child(lambda_callable_test_script)

var expected_str = "expected"
var expected_int = randi()
var expected_node = lambda_callable_test_script

lambda_callable_test_script.emit_signal_with_param(expected_str, expected_int, expected_node)

assert_eq(lambda_callable_test_script.signal_string, expected_str)
assert_eq(lambda_callable_test_script.signal_long, expected_int)
assert_eq(lambda_callable_test_script.signal_node, expected_node)
get_tree().root.remove_child(lambda_callable_test_script)
lambda_callable_test_script.free()

func test_kotlin_lambda_call_from_gdscript():
var lambda_callable_test_script = LambdaCallableTest.new()

var expected_str = "expected"

lambda_callable_test_script.kt_callable.call(expected_str)

assert_eq(lambda_callable_test_script.kt_callable_string, expected_str)
lambda_callable_test_script.free()
Original file line number Diff line number Diff line change
Expand Up @@ -15,34 +15,7 @@ import godot.codegen.traits.CastableTrait
import godot.codegen.traits.NullableTrait
import godot.codegen.traits.TypedTrait
import godot.codegen.traits.WithDefaultValueTrait
import godot.tools.common.constants.GODOT_ARRAY
import godot.tools.common.constants.GODOT_DICTIONARY
import godot.tools.common.constants.GODOT_ERROR
import godot.tools.common.constants.GodotKotlinJvmTypes
import godot.tools.common.constants.GodotTypes
import godot.tools.common.constants.VARIANT_TYPE_ANY
import godot.tools.common.constants.VARIANT_TYPE_ARRAY
import godot.tools.common.constants.VARIANT_TYPE_BOOL
import godot.tools.common.constants.VARIANT_TYPE_DOUBLE
import godot.tools.common.constants.VARIANT_TYPE_LONG
import godot.tools.common.constants.VARIANT_TYPE_NIL
import godot.tools.common.constants.VARIANT_TYPE_NODE_PATH
import godot.tools.common.constants.VARIANT_TYPE_OBJECT
import godot.tools.common.constants.VARIANT_TYPE_PACKED_BYTE_ARRAY
import godot.tools.common.constants.VARIANT_TYPE_PACKED_COLOR_ARRAY
import godot.tools.common.constants.VARIANT_TYPE_PACKED_FLOAT_32_ARRAY
import godot.tools.common.constants.VARIANT_TYPE_PACKED_FLOAT_64_ARRAY
import godot.tools.common.constants.VARIANT_TYPE_PACKED_INT_32_ARRAY
import godot.tools.common.constants.VARIANT_TYPE_PACKED_INT_64_ARRAY
import godot.tools.common.constants.VARIANT_TYPE_PACKED_STRING_ARRAY
import godot.tools.common.constants.VARIANT_TYPE_PACKED_VECTOR2_ARRAY
import godot.tools.common.constants.VARIANT_TYPE_PACKED_VECTOR3_ARRAY
import godot.tools.common.constants.VARIANT_TYPE_STRING_NAME
import godot.tools.common.constants.VARIANT_TYPE__RID
import godot.tools.common.constants.godotApiPackage
import godot.tools.common.constants.godotCorePackage
import godot.tools.common.constants.signalPackage
import godot.tools.common.constants.variantTypePackage
import godot.tools.common.constants.*
import java.util.*

const val enumPrefix = "enum::"
Expand Down Expand Up @@ -126,6 +99,7 @@ fun TypedTrait.getTypeClassName(): ClassTypeNameWrapper {
type == GodotTypes.dictionary -> ClassTypeNameWrapper(GODOT_DICTIONARY)
.parameterizedBy(ANY.copy(nullable = true), ANY.copy(nullable = true))
type == GodotTypes.variant -> ClassTypeNameWrapper(ANY)
type == GodotTypes.callable -> ClassTypeNameWrapper(GODOT_CALLABLE_BASE)
isCoreType() -> ClassTypeNameWrapper(ClassName(godotCorePackage, type!!))
else -> ClassTypeNameWrapper(ClassName(godotApiPackage, type!!))
}
Expand Down Expand Up @@ -167,20 +141,20 @@ val TypedTrait.jvmVariantTypeValue: ClassName
}
}

fun <T> T.getDefaultValueKotlinString(): String?
fun <T> T.getDefaultValueKotlinString(): Pair<String, Array<Any?>>?
where T : WithDefaultValueTrait,
T : NullableTrait,
T : CastableTrait {
val defaultValueString = defaultValue ?: return null
return when {
nullable && defaultValue == "null" -> defaultValueString
type == GodotTypes.color -> "${GodotKotlinJvmTypes.color}($defaultValueString)"
type == GodotTypes.variant -> defaultValueString
type == GodotTypes.bool -> defaultValueString.lowercase(Locale.US)
type == GodotTypes.float && meta == GodotMeta.Float.float -> "${intToFloat(defaultValueString)}f"
type == GodotTypes.float -> intToFloat(defaultValueString)
nullable && defaultValue == "null" -> defaultValueString to arrayOf()
type == GodotTypes.color -> "${GodotKotlinJvmTypes.color}($defaultValueString)" to arrayOf()
type == GodotTypes.variant -> defaultValueString to arrayOf()
type == GodotTypes.bool -> defaultValueString.lowercase(Locale.US) to arrayOf()
type == GodotTypes.float && meta == GodotMeta.Float.float -> "${intToFloat(defaultValueString)}f" to arrayOf()
type == GodotTypes.float -> intToFloat(defaultValueString) to arrayOf()
type == GodotTypes.stringName -> "${GodotKotlinJvmTypes.stringName}(".plus(defaultValueString.replace("&", ""))
.plus(")")
.plus(")") to arrayOf()

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

type == GodotTypes.rect2 -> defaultValueString
.replace(",", ".0,")
.replace(")", ".0)")
.replace(")", ".0)") to arrayOf()

type == GodotTypes.callable -> "%T()" to arrayOf(GODOT_CALLABLE)

type == GodotTypes.rid ||
type == GodotTypes.callable ||
type == GodotTypes.dictionary ||
type == GodotTypes.transform2D ||
type == GodotTypes.transform3D ||
Expand All @@ -212,9 +187,9 @@ fun <T> T.getDefaultValueKotlinString(): String?
type == GodotTypes.packedInt64Array ||
type == GodotTypes.packedVector2Array ||
type == GodotTypes.packedVector3Array
-> "$type()"
-> "$type()" to arrayOf()

else -> defaultValueString
else -> defaultValueString to arrayOf()
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import godot.codegen.repositories.*
import godot.codegen.repositories.impl.*
import godot.codegen.services.*
import godot.codegen.services.impl.*
import godot.tools.common.constants.Constraints
import godot.tools.common.constants.GENERATED_COMMENT
import java.io.File

Expand Down Expand Up @@ -78,4 +79,7 @@ fun File.generateApiFrom(jsonSource: File, docsDir: File? = null) {
.build()
.writeTo(this)
}

KtCallableGenerationService().generate(Constraints.MAX_FUNCTION_ARG_COUNT).writeTo(this)
SignalGenerationService().generate(Constraints.MAX_FUNCTION_ARG_COUNT).writeTo(this)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package godot.codegen.services

import com.squareup.kotlinpoet.FileSpec

interface IKtCallableGenerationService {
fun generate(maxArgumentCount: Int): FileSpec
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package godot.codegen.services

import com.squareup.kotlinpoet.FileSpec

interface ISignalGenerationService {
fun generate(maxArgumentCount: Int): FileSpec
}
Loading

0 comments on commit 5a448ec

Please sign in to comment.