-
-
Notifications
You must be signed in to change notification settings - Fork 263
Allow PhantomVar to work with engine enums
#1438
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
base: master
Are you sure you want to change the base?
Allow PhantomVar to work with engine enums
#1438
Conversation
|
Thanks a lot for the contribution! 👍
Yep, I think that's nice to have.
It's fine, but can you not use the same logic as used in the codegen for introspection methods on I don't think there's a need to reimplement the enum-to-string logic. |
Bromeon
left a comment
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.
Please also squash commits to 1 eventually, see Contributing guidelines.
| // Bounds for T are somewhat un-idiomatically directly on the type, rather than impls. | ||
| // This improves error messages in IDEs when using the type as a field. | ||
| pub struct PhantomVar<T: GodotType + Var>(PhantomData<T>); | ||
| pub struct PhantomVar<T: GodotConvert + Var>(PhantomData<T>); |
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.
Why these bound changes everywhere?
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.
Some are mandatory, such as Var, Export, and Default. Debug, Clone, and Copy might be needed, but they're not in my use case.
As for the rest... I think GodotConvert is more permissive than GodotType, and it only adds cost when actually called, so I modified it accordingly.
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.
The changes might be finalized tomorrow; it's too late here. Sorry~
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 think
GodotConvertis more permissive thanGodotType
Good point, makes sense 👍
| /// Returns the hint string for the given enum. | ||
| /// | ||
| /// Separate with commas, and remove the `<ENUM_NAME>_` prefix (if possible). | ||
| /// e.g.: "Left,Center,Right,Fill" | ||
| fn make_enum_hint_string(enum_: &Enum) -> String { | ||
| let upper_cast_enum_name = enum_.godot_name.to_shouty_snake_case() + "_"; | ||
| enum_.enumerators | ||
| .iter() | ||
| .map(|enumerator| { | ||
| enumerator.godot_name | ||
| .clone() | ||
| .trim_start_matches(upper_cast_enum_name.as_str()) | ||
| .to_pascal_case() | ||
| }) | ||
| .collect::<Vec<String>>() | ||
| .join(",") | ||
| } |
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.
As mentioned, this logic should already exist in some form.
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.
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 see. Can you reuse one of these though?
gdext/godot-codegen/src/conv/name_conversions.rs
Lines 57 to 105 in 0b6b792
| /// Used for `PascalCase` identifiers: classes and enums. | |
| pub fn to_pascal_case(ty_name: &str) -> String { | |
| use heck::ToPascalCase; | |
| assert!( | |
| is_valid_ident(ty_name), | |
| "invalid identifier for PascalCase conversion: {ty_name}" | |
| ); | |
| // Special cases: reuse snake_case impl to ensure at least consistency between those 2. | |
| if let Some(snake_special) = to_snake_special_case(ty_name) { | |
| return snake_special.to_pascal_case(); | |
| } | |
| ty_name | |
| .to_pascal_case() | |
| .replace("GdExtension", "GDExtension") | |
| .replace("GdNative", "GDNative") | |
| .replace("GdScript", "GDScript") | |
| .replace("Vsync", "VSync") | |
| .replace("Sdfgiy", "SdfgiY") | |
| } | |
| #[allow(dead_code)] // Keep around in case we need it later. | |
| pub fn shout_to_pascal(shout_case: &str) -> String { | |
| // TODO use heck? | |
| assert!( | |
| is_valid_shout_ident(shout_case), | |
| "invalid identifier for SHOUT_CASE -> PascalCase conversion: {shout_case}" | |
| ); | |
| let mut result = String::with_capacity(shout_case.len()); | |
| let mut next_upper = true; | |
| for ch in shout_case.chars() { | |
| if next_upper { | |
| assert_ne!(ch, '_'); // no double underscore | |
| result.push(ch); // unchanged | |
| next_upper = false; | |
| } else if ch == '_' { | |
| next_upper = true; | |
| } else { | |
| result.push(ch.to_ascii_lowercase()); | |
| } | |
| } | |
| result | |
| } |
If not, it should at least become a dedicated conversion function in that file (ideally with a comment on how it differs from the others). Thanks! 🙂
godot-codegen/src/generator/enums.rs
Outdated
|
|
||
| let var_trait_set_property = if enum_.is_exhaustive { | ||
| quote! { | ||
| fn set_property(&mut self, value: Self::Via){ |
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.
| fn set_property(&mut self, value: Self::Via){ | |
| fn set_property(&mut self, value: Self::Via) { |
Please fix this same formatting error everywhere 🙂
|
API docs are being generated and will be shortly available at: https://godot-rust.github.io/docs/gdext/pr-1438 |
94eb91e to
f76040c
Compare
| // Bounds for T are somewhat un-idiomatically directly on the type, rather than impls. | ||
| // This improves error messages in IDEs when using the type as a field. | ||
| pub struct PhantomVar<T: GodotType + Var>(PhantomData<T>); | ||
| pub struct PhantomVar<T: GodotConvert + Var>(PhantomData<T>); |
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 think
GodotConvertis more permissive thanGodotType
Good point, makes sense 👍
| /// Returns the hint string for the given enum. | ||
| /// | ||
| /// Separate with commas, and remove the `<ENUM_NAME>_` prefix (if possible). | ||
| /// e.g.: "Left,Center,Right,Fill" | ||
| fn make_enum_hint_string(enum_: &Enum) -> String { | ||
| let upper_cast_enum_name = enum_.godot_name.to_shouty_snake_case() + "_"; | ||
| enum_.enumerators | ||
| .iter() | ||
| .map(|enumerator| { | ||
| enumerator.godot_name | ||
| .clone() | ||
| .trim_start_matches(upper_cast_enum_name.as_str()) | ||
| .to_pascal_case() | ||
| }) | ||
| .collect::<Vec<String>>() | ||
| .join(",") | ||
| } |
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 see. Can you reuse one of these though?
gdext/godot-codegen/src/conv/name_conversions.rs
Lines 57 to 105 in 0b6b792
| /// Used for `PascalCase` identifiers: classes and enums. | |
| pub fn to_pascal_case(ty_name: &str) -> String { | |
| use heck::ToPascalCase; | |
| assert!( | |
| is_valid_ident(ty_name), | |
| "invalid identifier for PascalCase conversion: {ty_name}" | |
| ); | |
| // Special cases: reuse snake_case impl to ensure at least consistency between those 2. | |
| if let Some(snake_special) = to_snake_special_case(ty_name) { | |
| return snake_special.to_pascal_case(); | |
| } | |
| ty_name | |
| .to_pascal_case() | |
| .replace("GdExtension", "GDExtension") | |
| .replace("GdNative", "GDNative") | |
| .replace("GdScript", "GDScript") | |
| .replace("Vsync", "VSync") | |
| .replace("Sdfgiy", "SdfgiY") | |
| } | |
| #[allow(dead_code)] // Keep around in case we need it later. | |
| pub fn shout_to_pascal(shout_case: &str) -> String { | |
| // TODO use heck? | |
| assert!( | |
| is_valid_shout_ident(shout_case), | |
| "invalid identifier for SHOUT_CASE -> PascalCase conversion: {shout_case}" | |
| ); | |
| let mut result = String::with_capacity(shout_case.len()); | |
| let mut next_upper = true; | |
| for ch in shout_case.chars() { | |
| if next_upper { | |
| assert_ne!(ch, '_'); // no double underscore | |
| result.push(ch); // unchanged | |
| next_upper = false; | |
| } else if ch == '_' { | |
| next_upper = true; | |
| } else { | |
| result.push(ch.to_ascii_lowercase()); | |
| } | |
| } | |
| result | |
| } |
If not, it should at least become a dedicated conversion function in that file (ideally with a comment on how it differs from the others). Thanks! 🙂
godot-codegen/src/generator/enums.rs
Outdated
| fn var_hint() -> crate::meta::PropertyHintInfo{ | ||
| crate::meta::PropertyHintInfo{ | ||
| hint: #property_hint, | ||
| hint_string: #enum_hint_string.into(), |
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.
Please use explicit GString::from() here.
| hint_string: #enum_hint_string.into(), | |
| hint_string: GString::from(#enum_hint_string), |
4b2f968 to
e537bc7
Compare
|
Thank you! What would be nice now is to add 1-2 small integration tests, to make sure enums work. You could add
For inspiration how |
|
No problem. But I haven't used the gdext testing framework before; I'll look into how to do this later. |
|
We also have a guide: |
e537bc7 to
3416265
Compare

Background
See #1437
Feature
This enables use cases like:
Key Changes
Modify:
In
godot-core/src/registry/property/phantom_var.rsThis makes the requirements for PhantomVar more lenient, because
GodotTypeis a metaphor forGodotConvert. For existing supported typeswhere T: GodotType and Via = T, behavior remains the same.Add
In
godot-codegen/src/generator/enums.rsConcern
Clearly, not all enumerations will be edited as fields in the inspector. However, this PR extensively implements
VarandExportfor godot enums. Is it acceptable to treat all engine enums as "exportable as properties" by default, given that it's purely additive and only used when the user opts into exporting such fields?Additionally, I've provided a
make_enum_hint_stringfunction:It works fairly well, with one exception: for EularOrder, users might prefer to see values like
XYZrather thanXyz. Unfortunately, I cannot distinguish this throughextension_api.json.During my research of the Godot source code, I found this line:
This means that Godot itself does not have a method to convert enum values to hint strings, it's entirely "manually" maintained in every component that uses enum types.
This PR can be interpreted as an "overstepping" of authority, adding extra behavior to gdext that doesn't exist in Godot, but the user experience might be more comfortable than developing components on native Godot.
But is this allowed?
Thanks for considering this!