utopia-rise/godot-kotlin-native

[Master-Merge ToDo] Change function and property registration to get rid of `Variant.unwrap`

Closed this issue · 0 comments

Currently we make heavy use of Variant.unwrap inside the entry generation. This issue aims to remove the need of Variant.unwrap as it includes a JumpTable and an unsafe cast which is both dangerous and not good for performance (as it's called many times).

An example how the new system will work:

//Entry.kt
fun asDouble(argument: Variant) = argument.asDouble()
fun asString(argument: Variant) = argument.asString()

//Entry.kt
val typeConversionLambdas: Map<String, (Variant) -> Any?> = mapOf(
    "double" to ::asDouble,
    "string" to ::asString
)

//Entry.kt
inline fun <reified T> getTypeConversionLambda(): (Variant) -> T? {
    return when (T::class) {
        Double::class -> typeConversionLambdas["double"] as (Variant) -> T?
        String::class -> typeConversionLambdas["string"] as (Variant) -> T?
        else -> return { null }
    }
}

//Entry.kt
@CName("godot_nativescript_init")
fun NativeScriptInit(handle: COpaquePointer) {
    Godot.nativescriptInit(handle)
    with(ClassRegistry(handle)) {
        registerClass("example.TestingClass", "Node", ::TestingClass, false) {
            //function(TestingClass::_ready.name, RPCMode.DISABLED, TestingClass::_ready) <-OLD WAY
            function(TestingClass::_process.name, RPCMode.DISABLED, TestingClass::_process, listOf(getTypeConversionLambda<Double>())) //NEW WAY
        }
    }
}

//function.kt
class Function1<T : Object, P0, R>(
    val method: T.(P0) -> R,
    val list: List<(Variant) -> T?>
) : Function<T, R>(1) {
    override fun invoke(instance: T, args: List<Variant>): Variant {
        return Variant.wrap(
            method(
                instance,
                list[0].invoke(args[0]) as P0
            )
        )
    }
}

//ClassBuilder
fun <T : Object, P0, R> ClassBuilder<T>.function(methodName: String, rpcMode: RPCMode, body: T.(P0) -> R, argumentConverters: List<(Variant) -> T?>) {
    val methodRef = StableRef.create(Function1(body, argumentConverters)).asCPointer()
    disposables.add(methodRef)
    memScoped {
        val attribs = cValue<godot_method_attributes> {
            rpc_type = toGodotRpcMode(rpcMode)
        }

        val instanceMethod = cValue<godot_instance_method> {
            method_data = methodRef
            this.method = staticCFunction(::invokeMethod)
        }

        nullSafe(Godot.nativescript.godot_nativescript_register_method)(
            nativescriptHandle,
            className.cstr.ptr,
            methodName.cstr.ptr,
            attribs,
            instanceMethod
        )
    }
}

Same applies for property registration.