Skip to content

Commit 6e07b1d

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 ce3a2e1 commit 6e07b1d

File tree

5 files changed

+78
-4
lines changed

5 files changed

+78
-4
lines changed

godot-core/src/registry.rs

+12-3
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,12 @@ fn fill_class_info(component: PluginComponent, c: &mut ClassRegistrationInfo) {
227227
fill_into(
228228
&mut c.godot_params.create_instance_func,
229229
generated_create_fn,
230+
)
231+
.unwrap_or_else(|_|
232+
panic!(
233+
"Godot class `{}` is defined multiple times in Rust; you can rename them with #[class(rename=NewName)]",
234+
c.class_name,
235+
)
230236
);
231237
c.godot_params.free_instance_func = Some(free_fn);
232238
}
@@ -245,7 +251,9 @@ fn fill_class_info(component: PluginComponent, c: &mut ClassRegistrationInfo) {
245251
get_virtual_fn,
246252
} => {
247253
c.user_register_fn = user_register_fn;
248-
fill_into(&mut c.godot_params.create_instance_func, user_create_fn);
254+
// this shouldn't panic since rustc will error if there's
255+
// multiple `impl {Class}Virtual for Thing` definitions
256+
fill_into(&mut c.godot_params.create_instance_func, user_create_fn).unwrap();
249257
c.godot_params.to_string_func = user_to_string_fn;
250258
c.godot_params.notification_func = user_on_notification_fn;
251259
c.godot_params.get_virtual_func = Some(get_virtual_fn);
@@ -260,12 +268,13 @@ fn fill_class_info(component: PluginComponent, c: &mut ClassRegistrationInfo) {
260268
}
261269

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

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

godot-macros/src/class/derive_godot_class.rs

+8-1
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

@@ -108,6 +111,7 @@ fn parse_struct_attributes(class: &Struct) -> ParseResult<ClassAttributes> {
108111
let mut has_generated_init = false;
109112
let mut is_tool = false;
110113
let mut is_editor_plugin = false;
114+
let mut rename: Option<Ident> = None;
111115

112116
// #[class] attribute on struct
113117
if let Some(mut parser) = KvParser::parse(&class.attributes, "class")? {
@@ -127,6 +131,7 @@ fn parse_struct_attributes(class: &Struct) -> ParseResult<ClassAttributes> {
127131
if parser.handle_alone_ident("editor_plugin")?.is_some() {
128132
is_editor_plugin = true;
129133
}
134+
rename = parser.handle_ident("rename")?;
130135

131136
parser.finish()?;
132137
}
@@ -136,6 +141,7 @@ fn parse_struct_attributes(class: &Struct) -> ParseResult<ClassAttributes> {
136141
has_generated_init,
137142
is_tool,
138143
is_editor_plugin,
144+
rename,
139145
})
140146
}
141147

@@ -216,6 +222,7 @@ struct ClassAttributes {
216222
has_generated_init: bool,
217223
is_tool: bool,
218224
is_editor_plugin: bool,
225+
rename: Option<Ident>,
219226
}
220227

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

godot-macros/src/lib.rs

+24
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,30 @@ use crate::util::ident;
332332
///
333333
/// This should usually be combined with `#[class(tool)]` so that the code you write will actually run in the
334334
/// editor.
335+
///
336+
/// # Class Renaming
337+
///
338+
/// You may want to have structs with the same name. With Rust, this is allowed using `mod`. However in GDScript,
339+
/// there are no modules, namespaces, or any such disambiguation. Therefore, you need to change the names before they
340+
/// can get to Godot. You can use the `rename` key while defining your `GodotClass` for this.
341+
///
342+
/// ```
343+
/// mod animal {
344+
/// # use godot::prelude::*;
345+
/// #[derive(GodotClass)]
346+
/// #[class(init, rename=AnimalToad)]
347+
/// pub struct Toad {}
348+
/// }
349+
///
350+
/// mod npc {
351+
/// # use godot::prelude::*;
352+
/// #[derive(GodotClass)]
353+
/// #[class(init, rename=NpcToad)]
354+
/// pub struct Toad {}
355+
/// }
356+
/// ```
357+
///
358+
/// These classes will appear in the Godot editor and GDScript as "AnimalToad" or "NpcToad".
335359
#[proc_macro_derive(GodotClass, attributes(class, base, var, export, init, signal))]
336360
pub fn derive_godot_class(input: TokenStream) -> TokenStream {
337361
translate(input, class::derive_godot_class)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
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+
assert_ne!(
28+
dont_rename::RepeatMe::class_name(),
29+
rename::RepeatMe::class_name()
30+
);
31+
assert_eq!(dont_rename::RepeatMe::class_name().as_str(), "RepeatMe");
32+
assert_eq!(rename::RepeatMe::class_name().as_str(), "NoRepeat");
33+
}

itest/rust/src/object_tests/mod.rs

+1
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)