Skip to content

Commit 9d040aa

Browse files
authored
Merge pull request #1307 from beicause/string-name-cstr-non-static
Remove static lifetime in `StringName::from(&CStr)`
2 parents e6d317e + 1ae588a commit 9d040aa

File tree

4 files changed

+62
-22
lines changed

4 files changed

+62
-22
lines changed

godot-core/src/builtin/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ pub mod __prelude_reexport {
4141
pub use rect2i::*;
4242
pub use rid::*;
4343
pub use signal::*;
44-
pub use string::{Encoding, GString, NodePath, StringName};
44+
pub use string::{static_name, Encoding, GString, NodePath, StringName};
4545
pub use transform2d::*;
4646
pub use transform3d::*;
4747
pub use variant::*;

godot-core/src/builtin/string/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ pub use string_name::*;
2222
use crate::meta;
2323
use crate::meta::error::ConvertError;
2424
use crate::meta::{FromGodot, GodotConvert, ToGodot};
25+
pub use crate::static_name;
2526

2627
impl GodotConvert for &str {
2728
type Via = GString;

godot-core/src/builtin/string/string_name.rs

Lines changed: 35 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,8 @@ use crate::{impl_shared_string_api, meta};
3535
///
3636
/// # Performance
3737
///
38-
/// The fastest way to create string names is by using null-terminated C-string literals such as `c"MyClass"`. These have `'static` lifetime and
39-
/// can be used directly by Godot, without allocation or conversion. The encoding is limited to Latin-1, however. See the corresponding
40-
/// [`From<&'static CStr>` impl](#impl-From<%26CStr>-for-StringName).
38+
/// The fastest way to create string names is using [`static_name!`][crate::builtin::static_name], which creates a static cached `StringName` from null-terminated C-string literals such as `c"MyClass"`. These can be used directly by Godot without conversion. The encoding is limited to Latin-1, however. See the corresponding
39+
/// [`From<&CStr>` impl](#impl-From<%26CStr>-for-StringName).
4140
///
4241
/// # All string types
4342
///
@@ -249,11 +248,6 @@ impl StringName {
249248
pub fn as_inner(&self) -> inner::InnerStringName<'_> {
250249
inner::InnerStringName::from_outer(self)
251250
}
252-
253-
/// Increment ref-count. This may leak memory if used wrongly.
254-
fn inc_ref(&self) {
255-
std::mem::forget(self.clone());
256-
}
257251
}
258252

259253
// SAFETY:
@@ -360,8 +354,8 @@ impl From<&NodePath> for StringName {
360354
}
361355
}
362356

363-
impl From<&'static std::ffi::CStr> for StringName {
364-
/// Creates a `StringName` from a static ASCII/Latin-1 `c"string"`.
357+
impl From<&std::ffi::CStr> for StringName {
358+
/// Creates a `StringName` from a ASCII/Latin-1 `c"string"`.
365359
///
366360
/// This avoids unnecessary copies and allocations and directly uses the backing buffer. Useful for literals.
367361
///
@@ -375,23 +369,17 @@ impl From<&'static std::ffi::CStr> for StringName {
375369
/// // '±' is a Latin-1 character with codepoint 0xB1. Note that this is not UTF-8, where it would need two bytes.
376370
/// let sname = StringName::from(c"\xb1 Latin-1 string");
377371
/// ```
378-
fn from(c_str: &'static std::ffi::CStr) -> Self {
372+
fn from(c_str: &std::ffi::CStr) -> Self {
379373
// SAFETY: c_str is nul-terminated and remains valid for entire program duration.
380-
let result = unsafe {
374+
unsafe {
381375
Self::new_with_string_uninit(|ptr| {
382376
sys::interface_fn!(string_name_new_with_latin1_chars)(
383377
ptr,
384378
c_str.as_ptr(),
385-
sys::conv::SYS_TRUE, // p_is_static
379+
sys::conv::SYS_FALSE, // p_is_static
386380
)
387381
})
388-
};
389-
390-
// StringName expects that the destructor is not invoked on static instances (or only at global exit; see SNAME(..) macro in Godot).
391-
// According to testing with godot4 --verbose, there is no mention of "Orphan StringName" at shutdown when incrementing the ref-count,
392-
// so this should not leak memory.
393-
result.inc_ref();
394-
result
382+
}
395383
}
396384
}
397385

@@ -500,3 +488,30 @@ mod serialize {
500488
}
501489
}
502490
}
491+
492+
/// Creates and gets a reference to a static `StringName` from a ASCII/Latin-1 `c"string"`.
493+
///
494+
/// This is the fastest way to create a StringName repeatedly, with the result being cached and never released, like `SNAME` in Godot source code. Suitable for scenarios where high performance is required.
495+
#[macro_export]
496+
macro_rules! static_name {
497+
($str:literal) => {{
498+
use std::sync::OnceLock;
499+
500+
use godot::sys;
501+
502+
let c_str: &'static std::ffi::CStr = $str;
503+
static SNAME: OnceLock<StringName> = OnceLock::new();
504+
SNAME.get_or_init(|| {
505+
// SAFETY: c_str is nul-terminated and remains valid for entire program duration.
506+
unsafe {
507+
StringName::new_with_string_uninit(|ptr| {
508+
sys::interface_fn!(string_name_new_with_latin1_chars)(
509+
ptr,
510+
c_str.as_ptr(),
511+
sys::conv::SYS_TRUE, // p_is_static
512+
)
513+
})
514+
}
515+
})
516+
}};
517+
}

itest/rust/src/builtin_tests/string/string_name_test.rs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
use std::collections::HashSet;
99

10-
use godot::builtin::{Encoding, GString, NodePath, StringName};
10+
use godot::builtin::{static_name, Encoding, GString, NodePath, StringName};
1111

1212
use crate::framework::{assert_eq_self, itest};
1313

@@ -134,6 +134,30 @@ fn string_name_from_cstr() {
134134
}
135135
}
136136

137+
#[itest]
138+
fn string_name_static_name() {
139+
let a = static_name!(c"pure ASCII\t[~]").clone();
140+
let b = StringName::from("pure ASCII\t[~]");
141+
142+
assert_eq!(a, b);
143+
144+
let a1 = a.clone();
145+
let a2 = static_name!(c"pure ASCII\t[~]").clone();
146+
147+
assert_eq!(a, a1);
148+
assert_eq!(a1, a2);
149+
150+
let a = static_name!(c"\xB1").clone();
151+
let b = StringName::from("±");
152+
153+
assert_eq!(a, b);
154+
155+
let a = static_name!(c"Latin-1 \xA3 \xB1 text \xBE").clone();
156+
let b = StringName::from("Latin-1 £ ± text ¾");
157+
158+
assert_eq!(a, b);
159+
}
160+
137161
#[itest]
138162
fn string_name_with_null() {
139163
// Godot always ignores bytes after a null byte.

0 commit comments

Comments
 (0)