godot icon indicating copy to clipboard operation
godot copied to clipboard

Add Trait System to GDScript

Open SeremTitus opened this issue 4 months ago • 24 comments


GDScript Trait System

Based on the discussion opened in:

  • https://github.com/godotengine/godot-proposals/issues/6416

The GDScript trait system allows traits to be declared in separate .gdt files or within a trait SomeTrait block.

Traits facilitate efficient code reuse by enabling classes to share and implement common behaviors, reducing duplication and promoting maintainable design.

Syntax Breakdown

Declaring Traits

  • Traits can be defined globally or within classes.

  • In a .gdt file, declare a global trait using trait_name GlobalTrait at the top. trait used for inner traits.

  • Traits can contain all class members: enums, signals, variables, constants, functions and inner classes.

    Example:

    # SomeTrait.gdt
    trait_name SomeTrait
    
    # Trait types used for typing containers.
    var player: TraitPlayable
    var scene_props: Array[TraitMoveable]
    var collected: Dictionary[String, TraitCollectable]
    
    func _ready():
        pass  # Method with implementation, but can be overridden.
    
    # Method to be implemented by the class using the trait.
    # The return type must be `void`.
    func foo() -> void # Bodyless need to be implemented in class using trait.
    
    # The return type can be overridden, but it's not required to specify one.
    func some_method() # Bodyless need to be implemented in class using trait.
    
    # The return type can be overridden but must be a class that inherits from `Node`.
    func some_other_method() -> Node  # Bodyless need to be implemented in class using trait.
    
    

Using Traits in Classes

  • Use the uses keyword after the extends block, followed by the path or global name of the trait.

  • Traits can include other traits but do not need to implement their unimplemented functions. The implementation burden falls on the class using the trait.

    Example:

    # SomeClass.gd
    extends Node
    
    uses Shapes, Topology  # Global traits
    uses "res://someOtherTrait.gdt"  # File-based trait
    
    func _ready():
        var my_animals : Array = []
        my_animals.append(FluffyCow.new())
        my_animals.append(FluffyBull.new())
        my_animals.append(Bird.new())
        var count = 1
        for animal in my_animals:
            print("Animal ", count)
            if animal is Shearable:
                animal.shear()
            if animal is Milkable:
                animal.milk()
            count += 1
    
    trait Shearable:
        func shear() -> void:
            print("Shearable ok")
    
    trait Milkable:
        func milk() -> void:
            print("Milkable ok")
    
    class FluffyCow:
        uses Shearable, Milkable
    
    class FluffyBull:
        uses Shearable
    
    class Bird:
        pass
    

Creating Trait files.

  • In Editor go to FileSystem, left click and select "New Script ...". In the pop up select GDTrait as the preferred Language.
  • Alternatively in script creation pop up instead of selecting GDTrait from 'Language' dropdown menu change 'path' extention to '.gdt' and GDTrait will automatic change to GDTrait image

How Traits Are Handled

Cases

When a class uses a trait, its handled as follows:

1. Trait and Class Inheritance Compatibility:

The trait's inheritance must be a parent of the class's inheritance (compatible), but not the other way around, else an error occurs.

Example:
    # TraitA.gdt
    trait_name TraitA extends Node

    # ClassA.gd
    extends Control
    uses TraitA  # Allowed si nce Control inherits from Node

2. Used Traits Cohesion:

When a class uses various traits, some traits' members might shadow other traits members ,hence, an error should occur when on the trait relative on the order it is declared.

3. Enums, Constants, Variables, Signals, Functions and Inner Classes:

These are copied over, or an error occurs if they are shadowed.

4. Extending Named Enums:

Named enums can be redeclared in class and have new enum values.

5. Overriding Variables:

This is allowed if the type is compatible and the value is changed. Or only the type further specified. Export, Onready, Static state of trait variables are maintained. Setter and getter is maintained else overridden (setters parameters same and the ).

6. Overriding Signal:

This is allowed if parameter count are maintained and the parameter types is compatible by further specified from parent class type.

Example:

    # TraitA.gdt
    trait_name TraitA
    signal someSignal(out: Node)

    # ClassA.gd
    uses TraitA
    signal someSignal(out: Node2D) # Overridden signal

7. Overriding Functions:

Allowed if parameter count are maintained, return types and parameter types are compatible, but the function body can be changed. Static and rpc state of trait functions are maintained.

8. Unimplemented (Bodyless) Functions:

The class must provide an implementation. If a bodyless function remains unimplemented, an error occurs. Static and rpc state of trait functions are maintained.

9. Extending Inner Classes:

Inner classes defined in used trait can be redeclared in class and have new members provide not shadow members declared inner class declared in trait. Allow Member overrides for variables, Signals and function while extending Enum and its' Inner Classes.

Example:

    # Shapes.gdt
    trait_name Shapes
    class triangle: # Declared
        var edges:int = 3
        var face:int = 1
        func print_faces():
            print(face)


    # Draw.gd
    uses Shapes
    class triangle: # Redeclared
        var verticies:int = 3 # Add a new member
        var face:int =  2 # Overriding Variable
        func print_faces(): # Overriding Function
            print(face-1)

Special Trait Features

10. Trait can use other Traits:

A trait is allows to use another trait except it does not alter members of the trait it is using by overriding or extending.

11. Tool Trait:

if one trait containing the @tool keyword is used it converts classes (except inner classes) and traits using it into tool scripts.

12. File-Level Documentation:

Member documentation is copied over from trait else overridden.


System Implementation Progress

  • [x] Implement and verify How Traits Are Handled
  • [ ] Debugger Integration
  • [ ] Trait typed Assignable (variable, array, dictionary) - variable is done
  • [ ] Trait type as method & signal parameters' type
  • [x] Trait type as method return type
  • [x] Trait type casting (as)
  • [x] Class is Trait type compatibility check (is)
  • [x] Make .gdt files unattachable to objects/nodes
  • [x] Hot reloadable Classes using traits when trait Changes (for Editor and script documentation)
  • [ ] Write Tests
  • [ ] Write Documentation

SeremTitus avatar Sep 30 '24 18:09 SeremTitus