Skip to content

Commit

Permalink
Allow different class names in rust and Godot
Browse files Browse the repository at this point in the history
There's an issue that two structs with the same name in different
modules will conflict with each other when sent to Godot since only the
struct name is considered.  This resolves that by allowing the API user
to manually change the name of the class as Godot understands it.  This
also improves the error message that occurs when theres are aliased
classes.

Future work may seek to catch this issue at compile time rather than at
runtime.

Relevant to #332
  • Loading branch information
fpdotmonkey committed Sep 21, 2023
1 parent 0d4670f commit 2ab8845
Show file tree
Hide file tree
Showing 5 changed files with 75 additions and 4 deletions.
16 changes: 13 additions & 3 deletions godot-core/src/registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,13 @@ fn fill_class_info(component: PluginComponent, c: &mut ClassRegistrationInfo) {
fill_into(
&mut c.godot_params.create_instance_func,
generated_create_fn,
)
.unwrap_or_else(|_|
panic!(
"the definition for `{}` is overloaded; you may have multiple `GodotClass`es named `{}` which you should #[class(rename=...)]",
c.class_name,
c.class_name
)
);
c.godot_params.free_instance_func = Some(free_fn);
}
Expand All @@ -222,7 +229,9 @@ fn fill_class_info(component: PluginComponent, c: &mut ClassRegistrationInfo) {
get_virtual_fn,
} => {
c.user_register_fn = user_register_fn;
fill_into(&mut c.godot_params.create_instance_func, user_create_fn);
// this shouldn't panic since rustc will error if there's
// multiple `impl {Class}Virtual for Thing` definitions
fill_into(&mut c.godot_params.create_instance_func, user_create_fn).unwrap();
c.godot_params.to_string_func = user_to_string_fn;
c.godot_params.notification_func = user_on_notification_fn;
c.godot_params.get_virtual_func = Some(get_virtual_fn);
Expand All @@ -233,12 +242,13 @@ fn fill_class_info(component: PluginComponent, c: &mut ClassRegistrationInfo) {
}

/// If `src` is occupied, it moves the value into `dst`, while ensuring that no previous value is present in `dst`.
fn fill_into<T>(dst: &mut Option<T>, src: Option<T>) {
fn fill_into<T>(dst: &mut Option<T>, src: Option<T>) -> Result<(), ()> {
match (dst, src) {
(dst @ None, src) => *dst = src,
(Some(_), Some(_)) => panic!("option already filled"),
(Some(_), Some(_)) => return Err(()),
(Some(_), None) => { /* do nothing */ }
}
Ok(())
}

/// Registers a class with given the dynamic type information `info`.
Expand Down
7 changes: 6 additions & 1 deletion godot-macros/src/class/derive_godot_class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ pub fn derive_godot_class(decl: Declaration) -> ParseResult<TokenStream> {
let fields = parse_fields(class)?;

let class_name = &class.name;
let class_name_str = class.name.to_string();
let class_name_str = struct_cfg.rename.unwrap_or(class.name.clone()).to_string();
let class_name_cstr = util::cstr_u8_slice(&class_name_str);
let class_name_obj = util::class_name_obj(class_name);

Expand Down Expand Up @@ -89,6 +89,7 @@ fn parse_struct_attributes(class: &Struct) -> ParseResult<ClassAttributes> {
let mut base_ty = ident("RefCounted");
let mut has_generated_init = false;
let mut is_tool = false;
let mut rename: Option<Ident> = None;

// #[class] attribute on struct
if let Some(mut parser) = KvParser::parse(&class.attributes, "class")? {
Expand All @@ -104,13 +105,16 @@ fn parse_struct_attributes(class: &Struct) -> ParseResult<ClassAttributes> {
is_tool = true;
}

rename = parser.handle_ident("rename")?;

parser.finish()?;
}

Ok(ClassAttributes {
base_ty,
has_generated_init,
is_tool,
rename,
})
}

Expand Down Expand Up @@ -190,6 +194,7 @@ struct ClassAttributes {
base_ty: Ident,
has_generated_init: bool,
is_tool: bool,
rename: Option<Ident>,
}

fn make_godot_init_impl(class_name: &Ident, fields: Fields) -> TokenStream {
Expand Down
28 changes: 28 additions & 0 deletions godot-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,34 @@ use crate::util::ident;
/// for more information and further customization.
///
/// This is very similar to [GDScript's `@tool` feature](https://docs.godotengine.org/en/stable/tutorials/plugins/running_code_in_the_editor.html).
///
///
/// # Class Name Aliasing
///
/// You may want to have structs with the same name. With rust, this is
/// allowed using `mod`. However in GDScript, there are no modules,
/// namespaces, or any such diambiguation. Therefore, you need to
/// change the names before they can get to Godot. You can use the
/// `rename` key while defining your `GodotClass` for this.
///
/// ```
/// mod animal {
/// # use godot::prelude::*;
/// #[derive(GodotClass)]
/// #[class(init, rename=AnimalToad)]
/// pub struct Toad {}
/// }
///
/// mod npc {
/// # use godot::prelude::*;
/// #[derive(GodotClass)]
/// #[class(init, rename=NpcToad)]
/// pub struct Toad {}
/// }
/// ```
///
/// These classes will appear in the Godot editor and GDScript as
/// "AnimalToad" or "NpcToad".
#[proc_macro_derive(GodotClass, attributes(class, base, var, export, init, signal))]
pub fn derive_godot_class(input: TokenStream) -> TokenStream {
translate(input, class::derive_godot_class)
Expand Down
27 changes: 27 additions & 0 deletions itest/rust/src/object_tests/class_rename_test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
use crate::framework::itest;
use godot::prelude::*;

pub mod dont_rename {
use super::*;

#[derive(GodotClass)]
#[class(init, base = Node)]
pub struct RepeatMe {}
}

pub mod rename {
use super::*;

#[derive(GodotClass)]
#[class(init, base = Node, rename = NoRepeat)]
pub struct RepeatMe {}
}

#[itest]
fn renaming_changes_the_name() {
// note: this test doesn't really fail; if it doesn't succeed, then
// the runner hangs. I don't know how to solve that.
assert!(dont_rename::RepeatMe::class_name() != rename::RepeatMe::class_name());
assert_eq!(dont_rename::RepeatMe::class_name().as_str(), "RepeatMe");
assert_eq!(rename::RepeatMe::class_name().as_str(), "NoRepeat");
}
1 change: 1 addition & 0 deletions itest/rust/src/object_tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
*/

mod base_test;
mod class_rename_test;
mod object_test;
mod property_test;
mod singleton_test;
Expand Down

0 comments on commit 2ab8845

Please sign in to comment.