-
-
Notifications
You must be signed in to change notification settings - Fork 193
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
Derive GodotClass
for enums
#334
Comments
This is quite a cool idea! Apart from the things you mentioned, one problem I see is that the "sub-classes" are not proper types in Rust. You cannot have People typically use this idiom to work around this: pub enum Creature {
Enemy(Enemy),
...
}
#[..??..]
struct Enemy {
#[base]
base: Base<Resource>,
#[export]
damage: f64,
} |
I dont think these being different types should be an issue for us, but we could consider something like that yes. I'm not entirely sure about the syntax for it but i think it shouldn't be included in an initial implementation. Since it'd border on being actual inheritance, and i'm a bit unclear on whether we should include actual inheritance to any extent. and if we should then to what extent. The initial idea is very controlled, but with this we could create hierarchies of base classes w/ inherited concrete classes. This might be fine tho, since they're all abstract classes. And we can limit how much you can use inheritance-like features, such as overriding code and stuff like that. So it could end up being a useful way to group together related classes in the class-explorer too. my first idea for the syntax is something like: #[derive(GodotClass)]
#[class(init, base = Resource)]
pub enum Creature {
#[transparent] // similar to for instance #[serde(transparent)]
Enemy(Enemy),
..
}
#[derive(GodotClass)]
#[class(bikeshed_parent_path = Creature)]
struct Enemy {
#[base]
base: Base<Resource>,
#[export]
damage: f64,
} |
Just did a bit of a hacky manual job of this, and it seems like it is doable. But there are some issues: We have a single We can make the init function take an extra argument representing the variant (like a string maybe?). In that case, where do we get that value from? We usually get values from What should happen if we do: impl Creature {
#[func]
fn foo(&mut self) {
*self = Enemy { .. };
}
} In general what should happen if the wrong data is contained in something godot believes to contain some specific type? Can we prevent this from happening somehow? I think we kind of have to do something like what @Bromeon suggested, requiring a newtype for each variant. Otherwise we'll have issues like the above ones. And probably somehow prevent users from getting a mutable reference to the enum, while still allowing mutable references to each variant-newtype. It is a bit unfortunate if we can't allow the initial syntax i proposed. Though i guess we could generate the types automatically, since we just dont allow you to use the normal struct syntax. So enum Creature {
Enemy {
#[export]
damage: f64
},
}
// becomes
enum Creature {
Enemy(Enemy),
}
struct Enemy {
#[export]
damage: f64,
} We should probably only include one syntax in an initial implementation though. |
Maybe taking a step back, what is the motivation behind enum? I guess code brevity and having exclusive variants representing the same type? But as you mentioned, those are not exclusive on GDScript side (someone else can inherit the base class). Or what is the original motivation of not using #[derive(GodotClass)]
pub struct Creature {
...
}
#[derive(GodotClass)]
#[class(base=Creature)]
pub struct Enemy {
#[base]
base: Base<Resource>,
#[export]
damage: f64,
};
#[derive(GodotClass)]
#[class(base=Creature)]
pub struct Villager {
#[export]
happiness: f64,
} If it's declaration verbosity, it could still use enum syntax that the proc-macro would split apart. fn do_sth<T>(creature: Gd<T>)
where T: Inherits<Creature> {
...
} |
A big reason to avoid adding inheritance of custom classes is that rust doesn't support it. We've managed to make a system that works for godot's classes, but those classes are effectively static. And adding full-on inheritance like this would add so many new cases to consider (and we might turn #23 into straight up UB, whereas it is currently probably sound). This idea could be a much more controlled kind of inheritance that plays better with rust's type system. |
I think the main problem is But representing inheritance as enum also has some issues:
But it may still be useful for a subset of inheritance cases like the pattern you mentioned. Or the other way around: we have a Rust enum and want to map it to something strongly typed in GDScript. Approached from this angle, it might be a nice addition, if we find a way to handle above cases. |
Another way to approach the problem :We've been thinking about mapping rust enums to godot by using an existing godot core functionality: inheritance. DrawbacksWell, if the exported class doesn't have subclasses for each of it's variant, we can't rely on the neat syntax From gdscript we'd still have something like this possibly which is not awful pub enum OptionFloat{
Some(f32),
None
} match value.enum_variant:
value.VARIANTS.SOME:
[var id] = value.get_variant_some() // I'm not sure this syntax works outside a match stmt and I really don't like it either way
value.VARIANTS.NONE:
assert(null == value.get_variant_none() Of course the problem with this is what happens if Why map rust enums to godot in the first place?Bindings are useful for bringing rust power/expressiveness to godot, rust enums are one of the reasons I fell in love with the language, they allow to think about problems in a typed manner which is very neat. AlternativesIf godot-egui is ported to godot4, we could instead create another tool, depending on gdext using https://github.com/matthewjberger/enum2egui (proc macros to generate egui ui for rust structs and enums) and integrate that with a tool script or editor plugin, it would be the responsibility of the user to initiate it (probably could be an addon). This would be more cumbersome and also, separate from godot gui but I would try to make it happen anyway cause I really want to game design by editing rust enums. This would also loose the advantage of autocomplete and help with enum from godot. |
It should be possible to derive
GodotClass
for rust enums by having it create an abstract base class, and make each enum variant a subclass of that abstract class.This would be very useful to model things like. You want to export a resource to the editor, and the user can pick between 3 different variants that export should be, which each have different exports.
One example of what this may look like:
I've found myself wanting something like this several times before. There isn't really a good workaround for it, especially in the case where we want this to inherit from
Resource
. Since we'd want some way to specify that an exported field should be one of a limited set of types.Some questions
What would happen if someone makes a new subclass of that abstract base class, is that possible? and if so how should we handle it?
Should we add some way to add exported fields/variants to the base class that will be shared among all variants?
Should there be a way to automatically add a base field to all variants, or should that be explicit?
In GDScript, we could specify that a function can only take/returns one of the variants of the enum, does this cause any issues?
The text was updated successfully, but these errors were encountered: