Skip to content
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

Allow different class names in rust and Godot #419

Merged
merged 1 commit into from
Oct 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions godot-core/src/registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,12 @@ 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!(
"Godot class `{}` is defined multiple times in Rust; you can rename them with #[class(rename=NewName)]",
c.class_name,
)
);
c.godot_params.free_instance_func = Some(free_fn);
}
Expand All @@ -245,7 +251,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 @@ -260,12 +268,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
9 changes: 8 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,10 @@ 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: String = struct_cfg
.rename
.map_or_else(|| class.name.clone(), |rename| rename)
.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 @@ -108,6 +111,7 @@ fn parse_struct_attributes(class: &Struct) -> ParseResult<ClassAttributes> {
let mut has_generated_init = false;
let mut is_tool = false;
let mut is_editor_plugin = false;
let mut rename: Option<Ident> = None;

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

parser.finish()?;
}
Expand All @@ -136,6 +141,7 @@ fn parse_struct_attributes(class: &Struct) -> ParseResult<ClassAttributes> {
has_generated_init,
is_tool,
is_editor_plugin,
rename,
})
}

Expand Down Expand Up @@ -216,6 +222,7 @@ struct ClassAttributes {
has_generated_init: bool,
is_tool: bool,
is_editor_plugin: bool,
rename: Option<Ident>,
}

fn make_godot_init_impl(class_name: &Ident, fields: Fields) -> TokenStream {
Expand Down
24 changes: 24 additions & 0 deletions godot-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,30 @@ use crate::util::ident;
///
/// This should usually be combined with `#[class(tool)]` so that the code you write will actually run in the
/// editor.
///
/// # Class Renaming
///
/// 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 disambiguation. 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
33 changes: 33 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,33 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/

use crate::framework::itest;
use godot::prelude::*;

pub mod dont_rename {
use super::*;

#[derive(GodotClass)]
pub struct RepeatMe {}
}

pub mod rename {
use super::*;

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

#[itest]
fn renaming_changes_the_name() {
assert_ne!(
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