godot-kotlin-jvm

repository·master·Indexed 21 days ago

https://github.com/utopia-rise/godot-kotlin-jvm

A project providing the Godot Kotlin/JVM IntelliJ Plugin for specialized code analysis, inspections, and project scaffolding for Kotlin, Java, and Scala developers using the Godot engine. It includes a registration pipeline featuring the ClassGraph Symbol Processor for bytecode scanning and a Registrar Generator to transform registration models into source files, supporting Explicit, Inferred, and Automatic registration modes.

Tokens
33.6K
Snippets
85
Records
159
Agent score
75%

What's inside godot-kotlin-jvm

  1. Overview of the Godot Kotlin/JVM IntelliJ Plugin

    master

    The Godot Kotlin/JVM IntelliJ Plugin provides focused, K2-only code insight specifically for Godot Kotlin/JVM projects. It is designed to improve the developer experience by validating Godot-specific annotations and patterns directly within the IntelliJ IDEA editor.

    Key Capabilities:

    • Annotation Validation: Validates usage of @Script, @Visible, @Register, @Emit, @Export, and @Rpc.
    • Code Insight: Highlights Godot script declarations (ineligible, candidates, or registered) and validates callable-reference usage for signals, call, and rpc patterns.
    • Error Detection: Detects common pitfalls like nested mutation of core-type copies (e.g., transform.basis.x.x = 1.0).
    • Quick Fixes: Provides automated repairs for registration and mutability mistakes.
    • Project Wizard: A built-in wizard to create new Godot Kotlin/JVM projects and modules using optimized templates.
  2. Understand the BunnymarkV1DrawTexture benchmark

    master
    The BunnymarkV1DrawTexture benchmark is designed to measure compute and rendering performance. It attempts to draw the maximum number of sprites possible to the screen by adding Sprite nodes. To ensure the test focuses on rendering and compute overhead rather than engine overhead, it is specifically designed to avoid making Godot API calls during the performance-critical loop.
  3. How to handle Godot callbacks and overrides

    master

    Godot callbacks (e.g., _ready, _process, _physicsProcess) are handled differently than ordinary functions.

    When a function overrides a Godot method, simply use the override keyword. Do not use @Register on these methods; Godot recognizes compatible overrides automatically in the default Inferred mode.

    override fun _process(delta: Double) {
        // Update this node each frame.
    }
  4. Understand the role of godot-bootstrap

    master

    The godot-bootstrap is a shadow JAR (fat JAR) that includes godot-library and the runtime code required to reload user code within the Godot editor after a rebuild.

    Usage:

    • It is shipped alongside the Godot engine for editor use.
    • It is bundled with the game executable during the export process.
    • Note: You should never add this as a dependency or use it directly.
  5. How Notification Handlers are generated and registered

    master

    Notification handlers are treated differently than regular Godot methods:

    • Storage: They are stored as regular functions with specific notification metadata.
    • Selection: They are identified by RegisteredFunction.notification.
    • Generation: They are generated using repeated notification(id, Class::method) calls.
    • Visibility: They are not exposed as regular callable Godot methods.
    • Deduplication: They are not deduplicated by notification ID; multiple methods can handle the same notification.

    Lifecycle and Dispatch: Notification handlers are collected through the hierarchy and registered in child-to-parent order (matching runtime storage order).

    • Parent-to-child calls: Godot walks the list in reverse.
    • Child-to-parent calls: Godot walks the list forward.
  6. How class hierarchy and registration works

    master

    The registration model uses a hierarchy of ScriptFamily types to manage how members are inherited and exposed to Godot:

    • ScriptClass: A user-authored Kotlin, Java, or Scala class. It can be a registered class, an abstract registered class, or a non-registered intermediate parent. Members from intermediate parents are hoisted into the nearest registered descendant.
    • GodotBaseClass: Built-in Godot classes (e.g., Node, Resource). These carry no registered members but preserve the ancestry chain.
    • ScriptInterface: User-authored interfaces that can contribute registered functions.

    Inheritance and Overriding

    When generating the registrar, the system walks the parent classes and interfaces to create an effective inherited view.

    • Properties/Signals/Functions: The generator combines local and inherited members.
    • Deduplication: Local members are added first. If a child overrides a parent member, the child's version wins and the inherited duplicate is dropped.
    • Property Binding:
      • Kotlin properties use Class::property.
      • Java field-only properties use Class::property.
      • Java bean-style/Scala accessor properties use explicit accessor references.
  7. Understand the ClassGraph Symbol Processor flow

    master

    The ClassGraph Symbol Processor is the bytecode-reading front end of the registration pipeline. It scans compiled JVM classes and converts them into ScriptClass registration models. It does not generate registrar source code.

    The registration pipeline follows these steps:

    1. Scanning: ClassGraphProcessor scans the runtime classpath.
    2. Selection: RegistrationMapper identifies candidate Godot classes.
    3. Normalization: The matching JvmLanguage implementation (e.g., KotlinJvmLanguage) converts JVM bytecode into a LogicalClassShape (a language-normalized view).
    4. Mapping: RegistrationMapper applies the configured registration mode and maps the accepted members into the final registration model (ScriptClass, RegisteredProperty, RegisteredFunction, or RegisteredSignal).

    Data Representations:

    • Raw ClassGraph objects: ClassInfo, FieldInfo, MethodInfo, AnnotationInfo.
    • Logical shape: LogicalClassShape, LogicalSignal, LogicalProperty, LogicalMethod.
    • Final model: ScriptClass, RegisteredProperty, RegisteredFunction, RegisteredSignal.
  8. How registrar generation works

    master

    The registrar generation process is designed to be language-agnostic. It relies on a two-step process:

    1. Information Gathering: A tool gathers source code information and converts it into a set of model classes that the registrar generator understands. For JVM languages (Kotlin, Java, and Scala), this tool is godot-class-graph-symbol-processor.
    2. File Generation: The godot-class-graph-symbol-processor analyzes bytecode using ClassGraph (a bytecode processor) to collect the necessary data, then calls the registrar generator to produce the required registrar files.

    By using ClassGraph instead of a direct compiler plugin (like KSP or MpApt), the system works against a more stable bytecode API, making it easier to support additional JVM languages.

  9. Mutating Godot core types (Pass-by-value behavior)

    master

    Most Godot built-in types (like Vector3, Transform3D, etc.) are passed by value. This means modifying a property returned by a getter actually modifies a temporary copy, not the object itself.

    The Problem:

    node3D.rotation.y += 10f // This modifies a copy; the actual node rotation remains unchanged

    The Solution (Standard): You must retrieve the object, mutate it, and then re-assign it back to the property.

    val rotation = node3D.rotation
    rotation.y += 10f
    node3D.rotation = rotation

    The Solution (Kotlin-only optimization): Use the rotationMutate extension for a concise syntax:

    node3D.rotationMutate {
      y += 10f
    }
    // Kotlin concise mutation
    node3D.rotationMutate {
      y += 10f
    }
  10. Expose Callables from Java or Scala classes

    master

    When exposing a callable as a registered property in a Java or Scala class, you should use the base Callable type for the property signature rather than the typed CallableN variants.

    Even though the property surface seen by the registration layer uses Callable, the actual instance stored can be a typed LambdaCallableN or MethodCallableN to preserve type safety at runtime.