-
-
Notifications
You must be signed in to change notification settings - Fork 21.6k
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
Add Trait System to GDScript #97657
base: master
Are you sure you want to change the base?
Add Trait System to GDScript #97657
Conversation
Is there a specific reason you specified a If not, might I recommend a different return type for clarity? |
(Edited because I missed a part in the OP description) Fantastic start on this feature. Thank you! One comment: please use |
I'm not sure your reasoning lines up with your conclusion there, but I can't say I have much of a preference, what with it being a strictly cosmetic affair. |
This system seems very similar to the |
Abstract classes are still beholden to the class hierarchy: No class can inherit from two classes at a time. There is some value in having both of these, I suppose, but traits are far more powerful. |
See: Also, as DaloLorn said, these are independent features that can coexist together. Traits offer capabilities that classic inheritance cannot provide. |
This looks great, will traits be able to constrain typed collections (ie. Array[Punchable], Dictionary[String, Kickable]) ? |
Amazing that somebody cared to make this,but since the original proposal is shut down,here is some feedback
|
Considering work has already been made for signals, we should get to keep them too. (unless massive performance issues appear) |
I am a bit concerned on the performance of this in general, but that would be something that can be solved over time. I am really, really ecstatic about this. I agree. There's no reason to exclude signals from traits if the work has been done. |
36d7605
to
4088f53
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just some nitpicks, mostly you should rename uses
with impl
everywhere.
As various others have suggested to use
The 6 characters
|
what would you abbreviate
Pretty sure non-native speakers are able to understand abbreviations, by your logic
traits aren't exactly beginner stuff, when someone starts with a language they learn they might learn traits ,but for gamedev you don't learn certain stuff until you get the basics/become a casual programmer , when i was a unity developer, i didn't learn about interfaces (which are extremely similar to traits) until i had advanced enough and realised i need some other solution to inheritance
this makes no sense? let's take a look at some example code: class_name Door
extends AnimatableBody3D
impl Interactable what would "implies" mean in a progammer context?, "impels" isn't even abbreviated correctly, "implant"? seriously?, "implode" would be a function for gameplay
previous points still matter, also rust uses the |
Is using a separate file extension necessary? And if not, would it be better to stick to .gd? From a UX perspective it seems a lot simpler and easier not to. |
Extensions can be useful for quick search, filter, etc., without the need to check the content of the file nor adding extra prefix/suffix to file names (so it's better in UX terms), also can help other tools like the filesystem to implement custom icons. |
Put me down as another vote in favor of "implements", for what it's worth. I'm indifferent on "implements" versus "uses", but I'm not nearly so indifferent on "impl" versus "implements": The majority of Adriaan's concerns have not been addressed to my satisfaction. |
I think |
I agree on "impl" been a very confusing keyword. |
To resolve the class reference error: Compile godot, then run |
f27a5d9
to
8d2b91c
Compare
We would check to see if |
I don't think it makes sense to have unnamed traits. Unlike regular scripts, you always have to extend them; they can't be attached to nodes and can't be built-in. Since they are that much different, they can follow a different rules and you'd have to always name them. There are always internal traits (i.e. |
Hello! Sorry for asking this here, the original discussion is locked. I've read through this thread and the original proposal's thread and haven't noticed much reason for the separation between
If there's anything i'm missing, please point it out! |
This is nice improvement, but, perhaps it's just me, a "Script" selector paired with "Language" selector looks quite confusing, especially given that the "class" flavor of GDScript in the "Script" selector is called just "GDScript", exactly like in the "Language" selector. I think it would be better to:
|
so for the confusion rn, i have a somewhat idea for a solution example for explict class script_type class
extends Node
type_name Door
uses Interactable example for trait script_type trait
extends Node
type_name Interactable this can also be done for enums and structs, but those aren't important rn |
It's an interesting idea, but the need for a separate extension is due to the fact that Godot relies on them to determine the resource type. A GDScript file ( The difference between a script/class and a trait for Godot is that a class can be attached as a script, while a trait cannot. So not only GDScript, but also Note that file extension, resource type and resource loader are different things, not necessarily in a one-to-one relationship. So perhaps we could apply your suggestion and use the same The key point is that |
@@ -0,0 +1,2 @@ | |||
GDTEST_ANALYZER_ERROR | |||
>> ERROR at line 1: Could not inheritance from trait, "TraitA". |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
GDScript analyzer and runtime tests now support tracking multiple errors in a single file. Please group similar errors into single files instead of creating many small files. Same goes for feature tests. Yes, you shouldn't put everything in one file, but I think there are too many of them now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will try to refactor tests
And having a "main" class in every script is essential for Godot, yeah, understandable.
👀
On a second thought, do you think it will be possible/feasible to allow |
Since trait typed variables store classes using the trait type, when |
Is there some reason we couldn't attach a trait-typed resource? 😕 |
Though I haven't looked through all posts in this pr, I can simply imagine that if you assign an exported resource with a trait type, e.g., Traitable, all resources using the trait will be available in the inspector, regardless of the difference of their base types. For example, it could be required to use a Texture-based resource, in which case if you use a trait for this, it's hard to guarantee whether the resource you assign in the inspector is derived from Texture. |
resources files stored in files such as ".res", ".tres". I thought there no way to identify the constructing gdscript class but after taking a second look at the format it is possible therefore I can check if the class uses the trait type when editor is assigning... I will fix that issue |
Hmm. If you have a trait Your original concern is valid, though, and makes me wonder if it could make sense to define variables as belonging to multiple compatible types at once, without defining an intermediary type for it. For instance, using TypeScript syntax: |
Also, is it too early to make a final decision on the |
I feel this is usefull but would be really similar to making it abstract. The only difference I can see between the two would be that this implementation wouldn't let abstract class not implement the functions, which imo isn't usefull and doesn't warrant the redudndancy. |
I think this has been brought up already—a couple of replies seem to agree that abstract classes are a distinct feature as they are based on an single-inheritance hierarchy. Also, in my opinion, the whole point of having traits is to statically guarantee the availability of required properties/methods, so allowing them to be unimplemented would defeat the purpose. |
Well, abstract classes can't be instantiated anyway, can they? So it's fine if an abstract class doesn't satisfy the contract specified by an abstract trait. |
Here I was not saying " because abstract class exist, traits shouldn't exist" just that there is no reason in my opinion to give the possibility to create body-less functions in traits in addition to abstract functions. Abstract functions could be put inside traits which would have the same effect and wouldn't be tied to the single-inheritance hierarchy, hence why this a different subject to concern which had been previously brought. For the part of leaving functions unimplemented, personally I agree with DaloLorn but would gladly change my mind if someone points out some good reason to have both of them. |
I've been testing this briefly and the bugs I mentioned before are fixed. Traits work without problems as types, as export variables and support debugging. One minor problem is that I had to reload scene after adding another trait to script, because it wasn't picked as supported Node type; it's rather minor though, I'm surprised that node exporting even works with multiple traits in a script. There might be some bugs left here and there, but overall the feature works fine right now. |
Actually there is one rather obvious and major problem - you can't export trait Resources. Export only works with nodes, for Resources you get
Judging from the error it looks intended? Or is it oversight? |
Initially intended, however I will rework it |
There's also a bug with exported Node traits. You can assign them by clicking the Assign the button and selecting, but not by drag and drop. |
In the recent push, exported trait-typed variables now align with the expected export behavior. Additionally, traits extending Nodes and Resources have been removed from various selection and creation windows. |
Made an interaction system with this, very pleased have other issues tho IssuesVariablestrait variables override the class variables and the only way to access the shadowed variables is to cast this also works even if the trait variable's type is different from the class variable also untested, but i think the overriding variables with the same name and type count as 2 different variables, not sure so check Functionstrait's functions work very weird with existing methods when the trait extends the trait and the class will fight over the function definition and if you somehow settle it, the engine will give you a warning for overriding a class method that the trait demands (in this case the Possible solutionsVariableswhile the best solution is to simply not have variables in traits, i doubt it will happen so here is another possible solution if the variable has the same name and type as a class's property, it will refer to that property (basically an alias/ptr if you will) for the different type, i was going to suggest an alt to btw did anyone check what happens if traits share a variable names? Functionsif the function definition matches an existing function in a base class, you won't need to override it, this also solves my case if a function's name but return_type/parameters don't match, there is a hacky solution i found func MyTrait.my_class_func(same: Parameter) -> AndReturnType:
pass
func other():
#`tbase` stands for trait base
tbase[MyTrait].my_class_func(blah_blah)
source: |
GDScript Trait System
Based on the discussion opened in:
The GDScript trait system allows traits to be declared in separate
.gdt
files or within atrait 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 usingtrait_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:
Using Traits in Classes
Use the
uses
keyword after theextends
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:
Creating Trait files.
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. Also note traits are pass down by inheritance, If a class is for instance "SomeTrait" also it here classes will be so.
Example:
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.
Note that unnamed enum are just copied over if not shadowing.
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:
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:
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.
However, cyclic use of traits (TraitA uses TraitB and TraitB uses TraitA) is not permitted and will result in error.
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
as
)is
).gdt
files unattachable to objects/nodesBugsquad edit: