Skip to content

Commit d403c4c

Browse files
committed
Allow different class names in rust and Godot
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 there are aliased classes. Future work may seek to catch this issue at compile time rather than at runtime.
1 parent 0d4670f commit d403c4c

File tree

5 files changed

+82
-4
lines changed

5 files changed

+82
-4
lines changed

godot-core/src/registry.rs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,12 @@ fn fill_class_info(component: PluginComponent, c: &mut ClassRegistrationInfo) {
204204
fill_into(
205205
&mut c.godot_params.create_instance_func,
206206
generated_create_fn,
207+
)
208+
.unwrap_or_else(|_|
209+
panic!(
210+
"Godot class `{}` is defined multiple times in Rust; you can rename them with #[class(rename=NewName)]",
211+
c.class_name,
212+
)
207213
);
208214
c.godot_params.free_instance_func = Some(free_fn);
209215
}
@@ -222,7 +228,9 @@ fn fill_class_info(component: PluginComponent, c: &mut ClassRegistrationInfo) {
222228
get_virtual_fn,
223229
} => {
224230
c.user_register_fn = user_register_fn;
225-
fill_into(&mut c.godot_params.create_instance_func, user_create_fn);
231+
// this shouldn't panic since rustc will error if there's
232+
// multiple `impl {Class}Virtual for Thing` definitions
233+
fill_into(&mut c.godot_params.create_instance_func, user_create_fn).unwrap();
226234
c.godot_params.to_string_func = user_to_string_fn;
227235
c.godot_params.notification_func = user_on_notification_fn;
228236
c.godot_params.get_virtual_func = Some(get_virtual_fn);
@@ -233,12 +241,13 @@ fn fill_class_info(component: PluginComponent, c: &mut ClassRegistrationInfo) {
233241
}
234242

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

244253
/// Registers a class with given the dynamic type information `info`.

godot-macros/src/class/derive_godot_class.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,10 @@ pub fn derive_godot_class(decl: Declaration) -> ParseResult<TokenStream> {
2121
let fields = parse_fields(class)?;
2222

2323
let class_name = &class.name;
24-
let class_name_str = class.name.to_string();
24+
let class_name_str: String = struct_cfg
25+
.rename
26+
.map_or_else(|| class.name.clone(), |rename| rename)
27+
.to_string();
2528
let class_name_cstr = util::cstr_u8_slice(&class_name_str);
2629
let class_name_obj = util::class_name_obj(class_name);
2730

@@ -89,6 +92,7 @@ fn parse_struct_attributes(class: &Struct) -> ParseResult<ClassAttributes> {
8992
let mut base_ty = ident("RefCounted");
9093
let mut has_generated_init = false;
9194
let mut is_tool = false;
95+
let mut rename: Option<Ident> = None;
9296

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

111+
rename = parser.handle_ident("rename")?;
112+
107113
parser.finish()?;
108114
}
109115

110116
Ok(ClassAttributes {
111117
base_ty,
112118
has_generated_init,
113119
is_tool,
120+
rename,
114121
})
115122
}
116123

@@ -190,6 +197,7 @@ struct ClassAttributes {
190197
base_ty: Ident,
191198
has_generated_init: bool,
192199
is_tool: bool,
200+
rename: Option<Ident>,
193201
}
194202

195203
fn make_godot_init_impl(class_name: &Ident, fields: Fields) -> TokenStream {

godot-macros/src/lib.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,31 @@ use crate::util::ident;
318318
/// for more information and further customization.
319319
///
320320
/// This is very similar to [GDScript's `@tool` feature](https://docs.godotengine.org/en/stable/tutorials/plugins/running_code_in_the_editor.html).
321+
///
322+
///
323+
/// # Class Renaming
324+
///
325+
/// You may want to have structs with the same name. With Rust, this is allowed using `mod`. However in GDScript,
326+
/// there are no modules, namespaces, or any such disambiguation. Therefore, you need to change the names before they
327+
/// can get to Godot. You can use the `rename` key while defining your `GodotClass` for this.
328+
///
329+
/// ```
330+
/// mod animal {
331+
/// # use godot::prelude::*;
332+
/// #[derive(GodotClass)]
333+
/// #[class(init, rename=AnimalToad)]
334+
/// pub struct Toad {}
335+
/// }
336+
///
337+
/// mod npc {
338+
/// # use godot::prelude::*;
339+
/// #[derive(GodotClass)]
340+
/// #[class(init, rename=NpcToad)]
341+
/// pub struct Toad {}
342+
/// }
343+
/// ```
344+
///
345+
/// These classes will appear in the Godot editor and GDScript as "AnimalToad" or "NpcToad".
321346
#[proc_macro_derive(GodotClass, attributes(class, base, var, export, init, signal))]
322347
pub fn derive_godot_class(input: TokenStream) -> TokenStream {
323348
translate(input, class::derive_godot_class)
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* This Source Code Form is subject to the terms of the Mozilla Public
3+
* License, v. 2.0. If a copy of the MPL was not distributed with this
4+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
5+
*/
6+
7+
use crate::framework::itest;
8+
use godot::prelude::*;
9+
10+
pub mod dont_rename {
11+
use super::*;
12+
13+
#[derive(GodotClass)]
14+
pub struct RepeatMe {}
15+
}
16+
17+
pub mod rename {
18+
use super::*;
19+
20+
#[derive(GodotClass)]
21+
#[class(rename = NoRepeat)]
22+
pub struct RepeatMe {}
23+
}
24+
25+
#[itest]
26+
fn renaming_changes_the_name() {
27+
// note: this test doesn't really fail; if it doesn't succeed, then
28+
// the runner hangs. I don't know how to solve that.
29+
assert_ne!(
30+
dont_rename::RepeatMe::class_name(),
31+
rename::RepeatMe::class_name()
32+
);
33+
assert_eq!(dont_rename::RepeatMe::class_name().as_str(), "RepeatMe");
34+
assert_eq!(rename::RepeatMe::class_name().as_str(), "NoRepeat");
35+
}

itest/rust/src/object_tests/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
*/
66

77
mod base_test;
8+
mod class_rename_test;
89
mod object_test;
910
mod property_test;
1011
mod singleton_test;

0 commit comments

Comments
 (0)