Skip to content

Move from KSP to classgraph for registration code and initial Scala support #761

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 32 commits into from
Apr 23, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
ac70c66
feat: Add java and kotlin parser to get fqdn from source code
piiertho Jan 20, 2025
f5152c5
feat: remove script source file path from KtClass and registration.
piiertho Feb 2, 2025
66c0a3d
Remove lazy for named scripts
CedNaru Feb 25, 2025
1738c8b
feat: First draft of ClassGraph symbol processor
piiertho Dec 28, 2024
9bac0d0
enh(entry-gen): Disallow multiple constructors with same argument cou…
piiertho Jan 12, 2025
e5cf4e2
feat(entry-gen): Manage type parameters with classgraph
piiertho Jan 13, 2025
27c7d31
feat(classgraph): get signal parameter names from annotation
piiertho Feb 5, 2025
7471d90
feat(classgraph): generate registrars using classgraph
piiertho Feb 11, 2025
e5cf682
feat: configure gradle plugin to use classgraph instead of KSP and fi…
piiertho Feb 20, 2025
6e46220
fix: remove gdj generation for abstract classes
piiertho Feb 26, 2025
f0714dc
feat: Add update of gdj files
piiertho Feb 27, 2025
b264e81
feat: Add support of scala source script in engine
piiertho Mar 2, 2025
35b8792
fix: remove deprecated context receivers in classgraph generator
piiertho Mar 2, 2025
7b95ac8
feat: set scala version configurable from gradle plugin
piiertho Mar 2, 2025
6c2601e
chore: remove classes to test parser
piiertho Mar 2, 2025
314af4c
fix: Add export of scala source scripts in export plugin
piiertho Mar 2, 2025
061873e
fix: remove warning in source parser when no registered class found
piiertho Mar 2, 2025
4c7a97f
fix: Add initial compile java task for classgraph generation
piiertho Mar 4, 2025
ff794ee
fix: set right classgraph classpath for registration files generation
piiertho Mar 4, 2025
2bc1d68
fix: Adapt code to changes from fce31d9
piiertho Mar 5, 2025
d595e9b
fix: classgraph rpc enum values are now correctly set into registrati…
piiertho Mar 5, 2025
70beb04
Remove support for registering constructors with arguments
chippmann Mar 5, 2025
f786c38
feat: Remove multi args constructor constraints and simplify code acc…
piiertho Mar 5, 2025
903911a
chore: update scala implementation code to godot 4.4 and add scala ca…
piiertho Mar 12, 2025
b89e0d9
chore: rename fqdn to fqName
piiertho Mar 12, 2025
86f5ab7
fix: remove afterEvaluate in setupConfigurationsAndCompilations
piiertho Mar 12, 2025
4e207b7
enh: fail on errors at the end of classgraph process
piiertho Mar 12, 2025
7f3b0b5
fix: remove changes on JvmScript::get_base_script
piiertho Mar 12, 2025
fd6c3d0
doc: Add scala support to documentation
piiertho Mar 12, 2025
4ff8985
fix(doc): java mention in scala code blocks and update entry generati…
piiertho Apr 16, 2025
a905134
enh: Add context on classgraph errors
piiertho Apr 16, 2025
a5bbd42
chore: regenerate api with godot kotlin json
piiertho Apr 16, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
To make it more language agnostic, the entry generator provides a set of model classes which represent the needed information about the source code by the entry genenerator.
It expects this information to be gathered and assembled by the calling tool and to be provided in the entry point of the entry generator.

For Kotlin and Java, this tool is `godot-kotlin-symbol-processor` (a Kotlin compiler's plugin), which analyses the source code, and gathers the information needed by the entry generator.
For Kotlin, Java and Scala, this tool is `godot-class-graph-symbol-processor`, which analyses the byte code, and gathers the information needed by the entry generator.
It then calls the entry generator which in turn generates the needed entry files.

## The godot-kotlin-symbol-processor
Expand Down
6 changes: 3 additions & 3 deletions docs/src/doc/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ The items in this list are explicitly mentioned here as these will be implemente
Also consider the [API Differences](user-guide/api-differences.md) section for general differences
and limitations which will not be or cannot be adressed in the near forseable future or ever.

- Each registered constructor must have a unique number of arguments, constructor overloading is not yet supported.
- Only a default no arg constructor can be registered.
- No tool mode (you can set it already in the `@RegisterClass` annotation but it has no effect yet).
- No addon support, you cannot use Godot Kotlin/JVM to write plugins and addons yet (you can however [write libraries](develop-libraries/introduction.md) with godot specific code).
- Web is currently not supported. See [Supported platforms](#supported-platforms) to see what platforms we currently support
Expand All @@ -48,7 +48,7 @@ If you don't have Discord or you don't want to use it, please file an issue on G

## Supported languages

The main language supported is Kotlin. We do however support Java experimentally. It should be possible to support other JVM-based languages as well but this is not the focus of this project. If you want to have support for other languages, have a look at [support for other JVM-based languages](contribution/support-for-other-jvm-based-languages.md).
The main language supported is Kotlin. We do however support Java and Scala experimentally. It should be possible to support other JVM-based languages as well but this is not the focus of this project. If you want to have support for other languages, have a look at [support for other JVM-based languages](contribution/support-for-other-jvm-based-languages.md).

## Supported platforms

Expand Down Expand Up @@ -85,7 +85,7 @@ Contrary to the official binaries, there are two builds of the editor per Platfo
`release` editors are the editors you use normally. `debug` editors provide debug symbols and are intended to provide better stacktraces in case of crashes of the editor. Please use those when submitting bugreports.

!!! warning
This module will NOT work with the official Godot Editor and Export Templates! To be able to use Kotlin and Java scripts in Godot, you need our Editor and Export Templates builds.
This module will NOT work with the official Godot Editor and Export Templates! To be able to use Kotlin, Java and Scala scripts in Godot, you need our Editor and Export Templates builds.

## Developer discussion

Expand Down
2 changes: 1 addition & 1 deletion docs/src/doc/user-guide/advanced/cleanup-operations.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
When running Kotlin/Java code, the JVM is embedded and managed directly by Godot, which offer little control when the game is closing.
When running Kotlin/Java/Scala code, the JVM is embedded and managed directly by Godot, which offer little control when the game is closing.
If you use third-party library that needs resources to be freed/saved or threads to be closed.
To that end, we provide a simple method that allows you to register callbacks that will be called when the JVM is shutdown.

Expand Down
23 changes: 19 additions & 4 deletions docs/src/doc/user-guide/api-differences.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ Godot Kotlin/JVM offers two different ways to attach scripts:
- Source files
- Registration files.

## Source files .kt and .java
## Source files .kt, .java and .scala

Just like you would do with GDScript, you can directly attach your Kotlin/Java files to Nodes as scripts.
Just like you would do with GDScript, you can directly attach your Kotlin/Java/Scala files to Nodes as scripts.
This is the most straightforward method to use Kotlin scripts but not the most flexible.

The limitations are the following:
Expand All @@ -20,7 +20,7 @@ The limitations are the following:
var test_script: MyScript = load("res://pathToScript/MyScript.kt").new() // Wrong
var test_script: Node = load("res://pathToScript/MyScript.kt").new() // Correct
```
The same applies if you use a Godot object with a .kt/.java attached to it
The same applies if you use a Godot object with a .kt/.java/.scala attached to it

If those limitations don't apply to you, feel free to use Kotlin source files directly.

Expand All @@ -32,7 +32,7 @@ They have several benefits over source files:
- .gdj can be placed wherever you want in your Godot project, you are not limited to the source set.
- Each script get its own .gdj. This includes scripts in different modules and libraries.
- If a source file contains several scripts. A different .gdj will be generated for each.
- Registration files are language agnostic, they are generated for Kotlin and Java files with no difference.
- Registration files are language agnostic, they are generated for Kotlin, Java and Scala files with no difference.
- When creating a script from code using its registered name. The module is going to use the registration file as the script. Therefore, registration files are treated as the default way to use scripts inside the module.

By default, these files are generated into a folder called `gdj` in the root of your project.
Expand Down Expand Up @@ -186,6 +186,14 @@ This kind of operation can be costly so we provide extension functions which cac
```
///

/// tab | Scala
```scala
val stringName = StringNames.asCachedStringName("myString")
val nodePath = NodePaths.asCachedNodePath("myNode/myChildNode")
val snakeCaseStringName = StringNames.toGodotName("myString")
```
///

You can also use the non-cached version of them if you simply want ease of conversion:

/// tab | Kotlin
Expand All @@ -202,6 +210,13 @@ You can also use the non-cached version of them if you simply want ease of conve
```
///

/// tab | Scala
```scala
val stringName = StringNames.asStringName("myString")
val nodePath = NodePaths.asNodePath("myNode/myChildNode")
```
///


## Logging

Expand Down
20 changes: 5 additions & 15 deletions docs/src/doc/user-guide/classes.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,12 +139,9 @@ This also works for any type you define.

## Constructors

Godot requires you to have a default constructor on your classes.
You can define additional constructors but you have to register them by annothing them with `@RegisterConstructor`.
Default constructors, on the other hand, are always registered automatically.

Constructors can also have **a maximum of 8 arguments** and must have a unique argument count as constructor overloading is not yet supported.
This limitation is only for registered constructors.
Godot requires you to have a default constructor on your classes. These are automatically registered for you.
Registering constructor with arguments is currently not supported. But you can freely use them from Kotlin/Java/Scala
just not from GDScript or any other non Godot Kotlin/JVM language.

### Instantiate Kotlin script classes in GDScript

Expand All @@ -154,15 +151,8 @@ From GDScript it is possible to create an instance of a Kotlin class using the d
var instance := YourKotlinClass.new()
```

Additional constructors must use the `load` function:

```kt
var instance := load("res://gdj/YourClass.gdj").new(oneArg, anotherArg)
```

!!! info
The limitation of max 16 arguments for constructors is arbitrary. We decided to introduce this limitation to prevent performance bottlenecks for creating objects as each argument passed to a constructor needs to be unpacked by the binding. The more arguments, the more unpacking is needed which means more overhead.

Using other constructors is not possible. Only the default no arg constructor is registered.
But you can create the object and set the required properties after instantiation.

## Customization

Expand Down
56 changes: 56 additions & 0 deletions docs/src/doc/user-guide/signals_and_callables.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,16 @@ public MyScript extends Node {
```
///

/// tab | Scala
```scala
@RegisterClass
class MyScript extends Node {
@RegisterSignal("reverse")
val mySignal: Signal1[Boolean] = Signal1.create(this, "mySignal") // Only one way to do it in Scala.
}
```
///

!!! warning Signal parameter count
In GDScript, signals can have any number of arguments, this is not possible in Kotlin as it is a statically typed language.
At the moment, you can create signals and expose them to Godot up to 16 parameters.
Expand All @@ -55,6 +65,12 @@ reverseChanged.emit(false);
```
///

/// tab | Scala
```scala
reverseChanged.emit(false)
```
///

## Callables

You can use a classic Callable referencing a Godot Object and one of its method or conveniently use to avoid to creating a separate function.
Expand All @@ -80,6 +96,19 @@ You can use a classic Callable referencing a Godot Object and one of its method
```
///

/// tab | Scala
```scala
val regularCallable = Callable.create(this, "myMethod".toGodotName())
val customCallable = LambdaCallable1.create(
classOf[Void],
classOf[String],
(string: String) => {
println(string);
}
)
```
///

### Signals and Callables together

Kotlin and Java already have ways to pass functions around as References.
Expand Down Expand Up @@ -143,6 +172,33 @@ public class AnotherObject extends Object {
```
///

/// tab | Scala
```scala
@RegisterClass
class SomeObject extends Object {
@RegisterFunction
def onReverseChanged(boolean reverse): Unit = {
println(s"Value of reverse has changed: $reverse")
}
}

@RegisterClass
class AnotherObject extends Object {
@RegisterSignal("reverse")
val mySignal: Signal1[Boolean] = Signal1.create(this, "mySignal")

private val targetObject = new SomeObject()

public AnotherObject() {
// Here are 3 different ways to connect a signal to a registered method. The method reference syntax is not implemented for Scala.
mySignal.connect(Callable.create(targetObject, StringNames.toGodotName("onReverseChanged"))) // The recommanded way.
mySignal.connect(Callable.create(targetObject, "on_reverse_changed")) // Unsafe, try to use snake_case in your code as least as possible.
connect("my_signal", Callable.create(targetObject, "on_reverse_changed")) // Really, don't do that.
}
}
```
///

You can also use Kotlin lambdas directly to subscribe to signals

```kt
Expand Down
4 changes: 1 addition & 3 deletions harness/tests/FreeformRegistrationFileTestClass.gdj
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,13 @@

registeredName = FreeformRegistrationFileTestClass
fqName = godot.tests.freeform.FreeformRegistrationFileTestClass
relativeSourcePath = src/main/kotlin/godot/tests/freeform/FreeformRegistrationFileTestClass.kt
baseType = Node
supertypes = [
godot.api.Node,
godot.api.Object,
godot.core.KtObject,
godot.common.interop.NativeWrapper,
godot.common.interop.NativePointer,
kotlin.Any
godot.common.interop.NativePointer
]
signals = [

Expand Down
1 change: 1 addition & 0 deletions harness/tests/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ godot {

// uncomment to test ios
// isIOSExportEnabled.set(true)

}

dependencies {
Expand Down
2 changes: 1 addition & 1 deletion harness/tests/project.godot
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ config_version=5

config/name="Godot Kotlin Tests"
run/main_scene="res://test/GutTests.tscn"
config/features=PackedStringArray("4.4")
config/features=PackedStringArray("4.3")
config/icon="res://icon.png"

[debug]
Expand Down
10 changes: 10 additions & 0 deletions harness/tests/scala_test_scene.tscn
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[gd_scene load_steps=2 format=3 uid="uid://c27pq6lr7xver"]

[ext_resource type="Script" path="res://scripts/godot/tests/ScalaTestClass.gdj" id="1_ls5ji"]

[node name="ScalaTestScene" type="Node3D"]
script = ExtResource("1_ls5ji")

[node name="Button" type="Button" parent="."]
offset_right = 8.0
offset_bottom = 8.0
4 changes: 1 addition & 3 deletions harness/tests/scripts/CopyModificationCheckTestClass.gdj
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,14 @@

registeredName = CopyModificationCheckTestClass
fqName = CopyModificationCheckTestClass
relativeSourcePath = otherSourceDir/CopyModificationCheckTestClass.kt
baseType = Node3D
supertypes = [
godot.api.Node3D,
godot.api.Node,
godot.api.Object,
godot.core.KtObject,
godot.common.interop.NativeWrapper,
godot.common.interop.NativePointer,
kotlin.Any
godot.common.interop.NativePointer
]
signals = [

Expand Down
4 changes: 1 addition & 3 deletions harness/tests/scripts/CoreTypePropertyChecks.gdj
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,13 @@

registeredName = CoreTypePropertyChecks
fqName = CoreTypePropertyChecks
relativeSourcePath = otherSourceDir/CoreTypePropertyChecks.kt
baseType = Node
supertypes = [
godot.api.Node,
godot.api.Object,
godot.core.KtObject,
godot.common.interop.NativeWrapper,
godot.common.interop.NativePointer,
kotlin.Any
godot.common.interop.NativePointer
]
signals = [

Expand Down
4 changes: 1 addition & 3 deletions harness/tests/scripts/ScriptInOtherSourceDir.gdj
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,13 @@

registeredName = ScriptInOtherSourceDir
fqName = ScriptInOtherSourceDir
relativeSourcePath = otherSourceDir/ScriptInOtherSourceDir.kt
baseType = Node
supertypes = [
godot.api.Node,
godot.api.Object,
godot.core.KtObject,
godot.common.interop.NativeWrapper,
godot.common.interop.NativePointer,
kotlin.Any
godot.common.interop.NativePointer
]
signals = [

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,14 @@

registeredName = FLSimple
fqName = godot.tests.library.flattened.Simple
relativeSourcePath = src/main/kotlin/godot/tests/library/flattened/Simple.kt
baseType = Node3D
supertypes = [
godot.api.Node3D,
godot.api.Node,
godot.api.Object,
godot.core.KtObject,
godot.common.interop.NativeWrapper,
godot.common.interop.NativePointer,
kotlin.Any
godot.common.interop.NativePointer
]
signals = [

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,14 @@

registeredName = godot_tests_library_fqname_FQNLSimple
fqName = godot.tests.library.fqname.Simple
relativeSourcePath = src/main/kotlin/godot/tests/library/fqname/Simple.kt
baseType = Node3D
supertypes = [
godot.api.Node3D,
godot.api.Node,
godot.api.Object,
godot.core.KtObject,
godot.common.interop.NativeWrapper,
godot.common.interop.NativePointer,
kotlin.Any
godot.common.interop.NativePointer
]
signals = [

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

registeredName = godot_tests_library_fqname_FQNLSimpleChild
fqName = godot.tests.library.fqname.SimpleChild
relativeSourcePath = src/main/kotlin/godot/tests/library/fqname/SimpleChild.kt
baseType = Node3D
supertypes = [
godot.tests.library.fqname.Simple,
Expand All @@ -12,8 +11,7 @@ supertypes = [
godot.api.Object,
godot.core.KtObject,
godot.common.interop.NativeWrapper,
godot.common.interop.NativePointer,
kotlin.Any
godot.common.interop.NativePointer
]
signals = [

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,14 @@

registeredName = HLSimple
fqName = godot.tests.library.hierarchical.Simple
relativeSourcePath = src/main/kotlin/godot/tests/library/hierarchical/Simple.kt
baseType = Node3D
supertypes = [
godot.api.Node3D,
godot.api.Node,
godot.api.Object,
godot.core.KtObject,
godot.common.interop.NativeWrapper,
godot.common.interop.NativePointer,
kotlin.Any
godot.common.interop.NativePointer
]
signals = [

Expand Down
Loading
Loading