Skip to content

Commit 3d053d7

Browse files
authored
Merge pull request #891 from godot-rust/feature/unicode-class-names
Support Unicode class names (Godot 4.4+)
2 parents f9e7391 + 2f8e74c commit 3d053d7

File tree

6 files changed

+47
-6
lines changed

6 files changed

+47
-6
lines changed

godot-codegen/src/generator/classes.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ fn make_class(class: &Class, ctx: &mut Context, view: &ApiView) -> GeneratedClas
218218
// Optimization note: instead of lazy init, could use separate static which is manually initialized during registration.
219219
static CLASS_NAME: std::sync::OnceLock<ClassName> = std::sync::OnceLock::new();
220220

221-
let name: &'static ClassName = CLASS_NAME.get_or_init(|| ClassName::alloc_next(#class_name_cstr));
221+
let name: &'static ClassName = CLASS_NAME.get_or_init(|| ClassName::alloc_next_ascii(#class_name_cstr));
222222
*name
223223
}
224224

godot-core/src/meta/class_name.rs

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,12 +134,41 @@ impl ClassName {
134134
}
135135

136136
#[doc(hidden)]
137-
pub fn alloc_next(class_name_cstr: &'static CStr) -> Self {
137+
pub fn alloc_next_ascii(class_name_cstr: &'static CStr) -> Self {
138+
let utf8 = class_name_cstr
139+
.to_str()
140+
.expect("class name is invalid UTF-8");
141+
142+
assert!(
143+
utf8.is_ascii(),
144+
"ClassName::alloc_next_ascii() with non-ASCII Unicode string '{}'",
145+
utf8
146+
);
147+
138148
let global_index = insert_class(ClassNameSource::Borrowed(class_name_cstr));
139149

140150
Self { global_index }
141151
}
142152

153+
#[doc(hidden)]
154+
pub fn alloc_next_unicode(class_name_str: &'static str) -> Self {
155+
assert!(
156+
cfg!(since_api = "4.4"),
157+
"Before Godot 4.4, class names must be ASCII, but '{class_name_str}' is not.\nSee https://github.com/godotengine/godot/pull/96501."
158+
);
159+
160+
assert!(
161+
!class_name_str.is_ascii(),
162+
"ClassName::alloc_next_unicode() with ASCII string '{}'",
163+
class_name_str
164+
);
165+
166+
// StringNames use optimized 1-byte-per-char layout for Latin-1/ASCII, so Unicode can as well use the regular constructor.
167+
let global_index = insert_class(ClassNameSource::Owned(class_name_str.to_owned()));
168+
169+
Self { global_index }
170+
}
171+
143172
#[doc(hidden)]
144173
pub fn is_none(&self) -> bool {
145174
self.global_index == 0

godot-core/src/registry/class.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -399,7 +399,7 @@ fn register_class_raw(mut info: ClassRegistrationInfo) {
399399
// This can happen during hot reload if a class changes base type in an incompatible way (e.g. RefCounted -> Node).
400400
if registration_failed {
401401
godot_error!(
402-
"Failed to register class `{class_name}`; check preceding Godot stderr messages"
402+
"Failed to register class `{class_name}`; check preceding Godot stderr messages."
403403
);
404404
}
405405

godot-macros/src/class/derive_godot_class.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,14 @@ pub fn derive_godot_class(item: venial::Item) -> ParseResult<TokenStream> {
3434
.map_or_else(|| class.name.clone(), |rename| rename)
3535
.to_string();
3636

37-
let class_name_cstr = util::c_str(&class_name_str);
37+
// Determine if we can use ASCII for the class name (in most cases).
38+
let class_name_allocation = if class_name_str.is_ascii() {
39+
let c_str = util::c_str(&class_name_str);
40+
quote! { ClassName::alloc_next_ascii(#c_str) }
41+
} else {
42+
quote! { ClassName::alloc_next_unicode(#class_name_str) }
43+
};
44+
3845
let class_name_obj = util::class_name_obj(class_name);
3946

4047
let is_internal = struct_cfg.is_internal;
@@ -123,7 +130,7 @@ pub fn derive_godot_class(item: venial::Item) -> ParseResult<TokenStream> {
123130
// Optimization note: instead of lazy init, could use separate static which is manually initialized during registration.
124131
static CLASS_NAME: std::sync::OnceLock<ClassName> = std::sync::OnceLock::new();
125132

126-
let name: &'static ClassName = CLASS_NAME.get_or_init(|| ClassName::alloc_next(#class_name_cstr));
133+
let name: &'static ClassName = CLASS_NAME.get_or_init(|| #class_name_allocation);
127134
*name
128135
}
129136
}

itest/rust/src/register_tests/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ mod conversion_test;
1010
mod derive_godotconvert_test;
1111
mod func_test;
1212
mod gdscript_ffi_test;
13-
mod keyword_parameters_test;
13+
mod naming_tests;
1414
mod option_ffi_test;
1515
mod var_test;
1616

itest/rust/src/register_tests/keyword_parameters_test.rs renamed to itest/rust/src/register_tests/naming_tests.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,8 @@ impl IEditorExportPlugin for KeywordParameterEditorExportPlugin {
2626
fn get_customization_configuration_hash(&self) -> u64 { unreachable!() }
2727
fn get_name(&self) -> GString { unreachable!() }
2828
}
29+
30+
#[cfg(since_api = "4.4")]
31+
#[derive(GodotClass)]
32+
#[class(no_init)]
33+
struct 统一码 {}

0 commit comments

Comments
 (0)