utopia-rise / godot-kotlin-jvm

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

Allow Godot enums to work as bitfields. #497

Closed CedNaru closed 10 months ago

CedNaru commented 11 months ago

In the godot Json API, every enum has a is_bitfield property.

If it's false, it means that the enum should be used as a unique value: node.process_node = PROCESS_MODE_INHERIT

If it's true, each enum represent one of several bits to be set in a mask. object.connect(callable, CONNECT_DEFERRED or CONNECT_PERSIST)

Right now, we can't do like the second case directly. Generated enum don't have any bitwise operator, so you would need to use their "value" directly: CONNECT_DEFERRED.id or CONNECT_PERSIST.id For convenience right now, we discarded the use of the enum type if it's set as a bitfield, so we can directly use a long. What we generate:

  public fun save(
    resource: Resource,
    path: String = "",
    flags: Long = 0,
  ): GodotError

What we have in the json:

                    "name": "save",
                    "is_const": false,
                    "is_vararg": false,
                    "is_static": false,
                    "is_virtual": false,
                    "hash": 2303056517,
                    "return_value": {
                        "type": "enum::Error"
                    },
                    "arguments": [
                        {
                            "name": "resource",
                            "type": "Resource"
                        },
                        {
                            "name": "path",
                            "type": "String",
                            "default_value": "\"\""
                        },
                        {
                            "name": "flags",
                            "type": "bitfield::ResourceSaver.SaverFlags",
                            "default_value": "0"
                        }
                    ]

I suggest we change the api gen to get the enum type back as a parameter and add basic boolean operation on them. Of course the result of using a OR operation on 2 enums won't be another existing enum, so the solution would be to change from enum to sealed class. It would have the following layout (simplified for sake of example, using ResourceSaver.SaverFlags as example):

sealed class SaverFlags(val id: Int){
    object FLAG_NONE: SaverFlags(0)
    object FLAG_RELATIVE_PATHS: SaverFlags(1)
    object FLAG_BUNDLE_RESOURCES: SaverFlags(2)
    object FLAG_CHANGE_PATH: SaverFlags(4)
    class CustomFlag private constructor (id: Int): SaverFlags(id)

    fun or(other: SaverFlags) = custom(id or other.id)
}

All original enum values are set as singleton object, but a custom regular class is added to allow the storing of other values. That way we can write:

saver.save(SaverFlags.FLAG_RELATIVE_PATHS or SaverFlags.FLAG_BUNDLE_RESOURCES, path) 
//instead of
saver.save(SaverFlags.FLAG_RELATIVE_PATHS.id or SaverFlags.FLAG_BUNDLE_RESOURCES.id, path) 
//Note that in the current case, we can actually use any int value or any other godot enum as no type safety is guaranteed so if someone wished they could write something like:
saver.save(ConnectFlags.PERSISTENT + 5, path) //We don't want that to be possible.
chippmann commented 11 months ago

Also see the commit message in: https://github.com/utopia-rise/godot-kotlin-jvm/pull/496/commits/1f1f568ece8775861ba1b8f1ce3cefbbfef78e9b for a perf related necessary change when retrieving the enum values in api calls

CedNaru commented 11 months ago

Bug discovered after 0.7.0 release: It seems that API methods using or returning an enum marked as bitfield are incorrectly sending marking the value as an OBJECT instead of LONG. Because of it, a part of the Godot API is just not useable, with no workaround to make them work until fix. Examples: https://github.com/utopia-rise/godot-kotlin-jvm/blob/b2af7bb654c13e18dcd917432eb7df9ccc470ac4/kt/godot-library/src/main/kotlin/godot/gen/godot/InputEventMouse.kt#L36 https://github.com/utopia-rise/godot-kotlin-jvm/blob/b2af7bb654c13e18dcd917432eb7df9ccc470ac4/kt/godot-library/src/main/kotlin/godot/gen/godot/Font.kt#L264