utopia-rise / godot-kotlin-jvm

Godot Kotlin JVM Module
MIT License
585 stars 38 forks source link

Add notification ordering to Kotlin scripts. #495

Closed CedNaru closed 4 months ago

CedNaru commented 12 months ago

Godot notification function is a special case in the Object API. At first glance it looks like a regular virtual function but behind the hood it's using some macro black magic to call all the _notification implementations in the class hierarchy. It also have the option to make calls in reverse order (so from children's notifications to parent's notifications). Previously, Scripts and Extensions were not part of the ordering but it changed in a recent PR (from an issue I posted myself :p): https://github.com/godotengine/godot/pull/78634

In that PR, the "reversed" parameter is added to the notification method for scripts and extensions. The expected behaviour is the same as native Godot classes, the hierarchy of scripts' _notification should be called in the correct order. We are not supposed to rely on the user making a super call everytime the previous notification is overridden. We have to investigate if the current KotlinScript structure allows for that as we need to be able to access the parent script notification method as well.

If I remember correctly, we do things differently from the rest of Godot. We simply store all methods a script has, even the ones inherited from the parents if not overridden. Godot doesn't make any "copies" and relies on recursive calls to parents for everything, nut just notifications.

CedNaru commented 9 months ago

Proposal for the implementation:

@JvmInline
value class GodotNotification internal constructor(val block: Any.(Int) -> Unit)

open class KtObject{
      open fun _notification(): GodotNotification = godotNotification{}
    protected fun <T: KtObject> T.godotNotification(block: T.(Int) -> Unit ): GodotNotification = GodotNotification(block as Any.(Int) -> Unit)
}

open class A(): KtObject(){
    protected var counter: Int = 0
    override fun _notification() = godotNotification{ notification ->
        counter += 1
        println("A: $notification / Counter: $counter")
    }
}

open class B(): A(){
    override fun _notification() = godotNotification{ notification ->
        counter += 2
        println("B: $notification / Counter: $counter")
    }
}
class C(): B(){
    override fun _notification() = godotNotification{ notification ->
        counter += 3
        println("C: $notification / Counter: $counter")
    }
}

object KtClass {
    val stack = arrayOf(
        A()._notification().block,
        B()._notification().block,
        C()._notification().block
    )

    fun notification(c: C, notification: Int, reverse: Boolean){
        if(reverse){
            for (func in stack.reversed()){
                c.func(notification)
            } 
        } else {
            for (func in stack){
                c.func(notification)
            }
        }
    }
}

fun main() {
    KtClass.notification(C(), 13, false)
    println("")
    KtClass.notification(C(), 13, true)
}
chippmann commented 7 months ago

@piiertho wasn't this resolved with https://github.com/utopia-rise/godot-kotlin-jvm/pull/544?