diff --git a/godot-core/src/builtin/macros.rs b/godot-core/src/builtin/macros.rs index 5454835e0..4faa2e99e 100644 --- a/godot-core/src/builtin/macros.rs +++ b/godot-core/src/builtin/macros.rs @@ -107,17 +107,27 @@ macro_rules! impl_builtin_traits_inner { } } }; + + + // Requires a `hash` function. + ( Hash for $Type:ty ) => { + impl std::hash::Hash for $Type { + fn hash(&self, state: &mut H) { + self.hash().hash(state) + } + } + }; } macro_rules! impl_builtin_traits { ( for $Type:ty { - $( $Trait:ident => $gd_method:ident; )* + $( $Trait:ident $(=> $gd_method:ident)?; )* } ) => ( $( impl_builtin_traits_inner! { - $Trait for $Type => $gd_method + $Trait for $Type $(=> $gd_method)? } )* ) diff --git a/godot-core/src/builtin/mod.rs b/godot-core/src/builtin/mod.rs index 83548635c..b80e40b9e 100644 --- a/godot-core/src/builtin/mod.rs +++ b/godot-core/src/builtin/mod.rs @@ -42,7 +42,6 @@ pub use callable::*; pub use color::*; pub use dictionary_inner::Dictionary; pub use math::*; -pub use node_path::*; pub use others::*; pub use packed_array::*; pub use plane::*; @@ -52,7 +51,6 @@ pub use rect2::*; pub use rect2i::*; pub use rid::*; pub use string::*; -pub use string_name::*; pub use transform2d::*; pub use transform3d::*; pub use variant::*; @@ -95,7 +93,6 @@ mod callable; mod color; mod glam_helpers; mod math; -mod node_path; mod others; mod packed_array; mod plane; @@ -105,8 +102,6 @@ mod rect2; mod rect2i; mod rid; mod string; -mod string_chars; -mod string_name; mod transform2d; mod transform3d; mod variant; diff --git a/godot-core/src/builtin/string.rs b/godot-core/src/builtin/string/godot_string.rs similarity index 68% rename from godot-core/src/builtin/string.rs rename to godot-core/src/builtin/string/godot_string.rs index e0747abc7..edb9b2673 100644 --- a/godot-core/src/builtin/string.rs +++ b/godot-core/src/builtin/string/godot_string.rs @@ -10,17 +10,19 @@ use godot_ffi as sys; use sys::types::OpaqueString; use sys::{ffi_methods, interface_fn, GodotFfi}; -use super::{ - string_chars::validate_unicode_scalar_sequence, FromVariant, ToVariant, Variant, - VariantConversionError, -}; +use crate::builtin::inner; +use super::string_chars::validate_unicode_scalar_sequence; +use super::{NodePath, StringName}; + +/// Godot's reference counted string type. #[repr(C, align(8))] pub struct GodotString { opaque: OpaqueString, } impl GodotString { + /// Construct a new empty GodotString. pub fn new() -> Self { Self::default() } @@ -29,12 +31,12 @@ impl GodotString { Self { opaque } } - ffi_methods! { - type sys::GDExtensionStringPtr = *mut Opaque; - - fn from_string_sys = from_sys; - fn from_string_sys_init = from_sys_init; - fn string_sys = sys; + /// Returns a 32-bit integer hash value representing the string. + pub fn hash(&self) -> u32 { + self.as_inner() + .hash() + .try_into() + .expect("Godot hashes are uint32_t") } /// Move `self` into a system pointer. This transfers ownership and thus does not call the destructor. @@ -83,6 +85,19 @@ impl GodotString { } std::slice::from_raw_parts(ptr as *const char, len as usize) } + + ffi_methods! { + type sys::GDExtensionStringPtr = *mut Opaque; + + fn from_string_sys = from_sys; + fn from_string_sys_init = from_sys_init; + fn string_sys = sys; + } + + #[doc(hidden)] + pub fn as_inner(&self) -> inner::InnerString { + inner::InnerString::from_outer(self) + } } // SAFETY: @@ -122,9 +137,28 @@ impl_builtin_traits! { Drop => string_destroy; Eq => string_operator_equal; Ord => string_operator_less; + Hash; + } +} + +impl fmt::Display for GodotString { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let s: String = self.chars_checked().iter().collect(); + f.write_str(s.as_str()) + } +} + +/// Uses literal syntax from GDScript: `"string"` +impl fmt::Debug for GodotString { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let s = String::from(self); + write!(f, "\"{s}\"") } } +// ---------------------------------------------------------------------------------------------------------------------------------------------- +// Conversion from/into rust string-types + impl From for GodotString where S: AsRef, @@ -162,7 +196,14 @@ impl From<&GodotString> for String { } } -// TODO From<&NodePath> + test +impl From for String { + /// Converts this `GodotString` to a `String`. + /// + /// This is identical to `String::from(&string)`, and as such there is no performance benefit. + fn from(string: GodotString) -> Self { + Self::from(&string) + } +} impl FromStr for GodotString { type Err = Infallible; @@ -172,49 +213,47 @@ impl FromStr for GodotString { } } -impl fmt::Display for GodotString { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let s = String::from(self); - f.write_str(s.as_str()) - } -} - -/// Uses literal syntax from GDScript: `"string"` -impl fmt::Debug for GodotString { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let s = String::from(self); - write!(f, "\"{s}\"") - } -} +// ---------------------------------------------------------------------------------------------------------------------------------------------- +// Conversion from other Godot string-types -impl ToVariant for &str { - fn to_variant(&self) -> Variant { - GodotString::from(*self).to_variant() +impl From<&StringName> for GodotString { + fn from(string: &StringName) -> Self { + unsafe { + Self::from_sys_init_default(|self_ptr| { + let ctor = sys::builtin_fn!(string_from_string_name); + let args = [string.sys_const()]; + ctor(self_ptr, args.as_ptr()); + }) + } } } -impl ToVariant for String { - fn to_variant(&self) -> Variant { - GodotString::from(self).to_variant() +impl From for GodotString { + /// Converts this `StringName` to a `GodotString`. + /// + /// This is identical to `GodotString::from(&string_name)`, and as such there is no performance benefit. + fn from(string_name: StringName) -> Self { + Self::from(&string_name) } } -impl FromVariant for String { - fn try_from_variant(variant: &Variant) -> Result { - Ok(GodotString::try_from_variant(variant)?.to_string()) +impl From<&NodePath> for GodotString { + fn from(path: &NodePath) -> Self { + unsafe { + Self::from_sys_init_default(|self_ptr| { + let ctor = sys::builtin_fn!(string_from_node_path); + let args = [path.sys_const()]; + ctor(self_ptr, args.as_ptr()); + }) + } } } -// While this is a nice optimisation for ptrcalls, it's not easily possible -// to pass in &GodotString when doing varcalls. -/* -impl PtrCall for &GodotString { - unsafe fn from_ptr_call_arg(arg: *const godot_ffi::GDExtensionTypePtr) -> Self { - &*(*arg as *const GodotString) - } - - unsafe fn to_ptr_call_arg(self, arg: godot_ffi::GDExtensionTypePtr) { - std::ptr::write(arg as *mut GodotString, self.clone()); +impl From for GodotString { + /// Converts this `NodePath` to a `GodotString`. + /// + /// This is identical to `GodotString::from(&path)`, and as such there is no performance benefit. + fn from(path: NodePath) -> Self { + Self::from(&path) } } -*/ diff --git a/godot-core/src/builtin/string/macros.rs b/godot-core/src/builtin/string/macros.rs new file mode 100644 index 000000000..4da7a76b1 --- /dev/null +++ b/godot-core/src/builtin/string/macros.rs @@ -0,0 +1,42 @@ +/* + * 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/. + */ + +#![macro_use] + +macro_rules! impl_rust_string_conv { + ($Ty:ty) => { + impl From for $Ty + where + S: AsRef, + { + fn from(string: S) -> Self { + let intermediate = GodotString::from(string.as_ref()); + Self::from(&intermediate) + } + } + + impl From<&$Ty> for String { + fn from(string: &$Ty) -> Self { + let intermediate = GodotString::from(string); + Self::from(&intermediate) + } + } + + impl From<$Ty> for String { + fn from(string: $Ty) -> Self { + Self::from(&string) + } + } + + impl std::str::FromStr for $Ty { + type Err = std::convert::Infallible; + + fn from_str(string: &str) -> Result { + Ok(Self::from(string)) + } + } + }; +} diff --git a/godot-core/src/builtin/string/mod.rs b/godot-core/src/builtin/string/mod.rs new file mode 100644 index 000000000..190be424c --- /dev/null +++ b/godot-core/src/builtin/string/mod.rs @@ -0,0 +1,44 @@ +/* + * 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/. + */ + +//! Godot-types that are Strings. + +mod godot_string; +mod macros; +mod node_path; +mod string_chars; +mod string_name; + +use godot_ffi::VariantType; +pub use godot_string::*; +pub use node_path::*; +pub use string_name::*; + +use super::{meta::VariantMetadata, FromVariant, ToVariant, Variant, VariantConversionError}; + +impl ToVariant for &str { + fn to_variant(&self) -> Variant { + GodotString::from(*self).to_variant() + } +} + +impl ToVariant for String { + fn to_variant(&self) -> Variant { + GodotString::from(self).to_variant() + } +} + +impl FromVariant for String { + fn try_from_variant(variant: &Variant) -> Result { + Ok(GodotString::try_from_variant(variant)?.to_string()) + } +} + +impl VariantMetadata for String { + fn variant_type() -> VariantType { + VariantType::String + } +} diff --git a/godot-core/src/builtin/node_path.rs b/godot-core/src/builtin/string/node_path.rs similarity index 61% rename from godot-core/src/builtin/node_path.rs rename to godot-core/src/builtin/string/node_path.rs index fb0b7a759..c80691490 100644 --- a/godot-core/src/builtin/node_path.rs +++ b/godot-core/src/builtin/string/node_path.rs @@ -4,14 +4,17 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use std::convert::Infallible; use std::fmt; -use std::str::FromStr; -use crate::builtin::GodotString; use godot_ffi as sys; use godot_ffi::{ffi_methods, GDExtensionTypePtr, GodotFfi}; +use crate::builtin::inner; + +use super::{GodotString, StringName}; + +/// A pre-parsed scene tree path. +#[repr(C)] pub struct NodePath { opaque: sys::types::OpaqueNodePath, } @@ -20,6 +23,19 @@ impl NodePath { fn from_opaque(opaque: sys::types::OpaqueNodePath) -> Self { Self { opaque } } + + /// Returns a 32-bit integer hash value representing the string. + pub fn hash(&self) -> u32 { + self.as_inner() + .hash() + .try_into() + .expect("Godot hashes are uint32_t") + } + + #[doc(hidden)] + pub fn as_inner(&self) -> inner::InnerNodePath { + inner::InnerNodePath::from_outer(self) + } } // SAFETY: @@ -52,65 +68,68 @@ unsafe impl GodotFfi for NodePath { } } -impl From<&GodotString> for NodePath { - fn from(path: &GodotString) -> Self { - unsafe { - Self::from_sys_init_default(|self_ptr| { - let ctor = sys::builtin_fn!(node_path_from_string); - let args = [path.sys_const()]; - ctor(self_ptr, args.as_ptr()); - }) - } +impl_builtin_traits! { + for NodePath { + Default => node_path_construct_default; + Clone => node_path_construct_copy; + Drop => node_path_destroy; + Eq => node_path_operator_equal; + Hash; } } -impl From<&NodePath> for GodotString { - fn from(path: &NodePath) -> Self { - unsafe { - Self::from_sys_init_default(|self_ptr| { - let ctor = sys::builtin_fn!(string_from_node_path); - let args = [path.sys_const()]; - ctor(self_ptr, args.as_ptr()); - }) - } +impl fmt::Display for NodePath { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let string = GodotString::from(self); + ::fmt(&string, f) } } -impl From<&str> for NodePath { - fn from(path: &str) -> Self { - Self::from(&GodotString::from(path)) +/// Uses literal syntax from GDScript: `^"node_path"` +impl fmt::Debug for NodePath { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let string = GodotString::from(self); + write!(f, "^\"{string}\"") } } -impl FromStr for NodePath { - type Err = Infallible; +// ---------------------------------------------------------------------------------------------------------------------------------------------- +// Conversion from/into other string-types + +impl_rust_string_conv!(NodePath); - fn from_str(path: &str) -> Result { - Ok(Self::from(path)) +impl From<&GodotString> for NodePath { + fn from(string: &GodotString) -> Self { + unsafe { + Self::from_sys_init_default(|self_ptr| { + let ctor = sys::builtin_fn!(node_path_from_string); + let args = [string.sys_const()]; + ctor(self_ptr, args.as_ptr()); + }) + } } } -impl fmt::Display for NodePath { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let string = GodotString::from(self); - ::fmt(&string, f) +impl From for NodePath { + /// Converts this `GodotString` to a `NodePath`. + /// + /// This is identical to `NodePath::from(&string)`, and as such there is no performance benefit. + fn from(string: GodotString) -> Self { + Self::from(&string) } } -/// Uses literal syntax from GDScript: `^"node_path"` -impl fmt::Debug for NodePath { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let string = GodotString::from(self); - write!(f, "^\"{string}\"") +impl From<&StringName> for NodePath { + fn from(string_name: &StringName) -> Self { + Self::from(GodotString::from(string_name)) } } -impl_builtin_traits! { - for NodePath { - Default => node_path_construct_default; - Clone => node_path_construct_copy; - Drop => node_path_destroy; - Eq => node_path_operator_equal; - // Ord => node_path_operator_less; +impl From for NodePath { + /// Converts this `StringName` to a `NodePath`. + /// + /// This is identical to `NodePath::from(&string_name)`, and as such there is no performance benefit. + fn from(string_name: StringName) -> Self { + Self::from(GodotString::from(string_name)) } } diff --git a/godot-core/src/builtin/string_chars.rs b/godot-core/src/builtin/string/string_chars.rs similarity index 100% rename from godot-core/src/builtin/string_chars.rs rename to godot-core/src/builtin/string/string_chars.rs diff --git a/godot-core/src/builtin/string_name.rs b/godot-core/src/builtin/string/string_name.rs similarity index 65% rename from godot-core/src/builtin/string_name.rs rename to godot-core/src/builtin/string/string_name.rs index 8a1190e20..4a4998d32 100644 --- a/godot-core/src/builtin/string_name.rs +++ b/godot-core/src/builtin/string/string_name.rs @@ -4,15 +4,19 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use crate::builtin::GodotString; use godot_ffi as sys; use sys::{ffi_methods, GodotFfi}; use std::fmt; -use std::hash::{Hash, Hasher}; -use super::inner; +use crate::builtin::inner; +use super::{GodotString, NodePath}; + +/// A string optimized for unique names. +/// +/// StringNames are immutable strings designed for representing unique names. StringName ensures that only +/// one instance of a given name exists. #[repr(C)] pub struct StringName { opaque: sys::types::OpaqueStringName, @@ -26,6 +30,7 @@ impl StringName { /// Returns the number of characters in the string. /// /// _Godot equivalent: `length`_ + #[doc(alias = "length")] pub fn len(&self) -> usize { self.as_inner().length() as usize } @@ -37,9 +42,12 @@ impl StringName { self.as_inner().is_empty() } - #[doc(hidden)] - pub fn as_inner(&self) -> inner::InnerStringName { - inner::InnerStringName::from_outer(self) + /// Returns a 32-bit integer hash value representing the string. + pub fn hash(&self) -> u32 { + self.as_inner() + .hash() + .try_into() + .expect("Godot hashes are uint32_t") } ffi_methods! { @@ -50,6 +58,11 @@ impl StringName { fn from_string_sys_init = from_sys_init; fn string_sys = sys; } + + #[doc(hidden)] + pub fn as_inner(&self) -> inner::InnerStringName { + inner::InnerStringName::from_outer(self) + } } // SAFETY: @@ -84,27 +97,13 @@ unsafe impl GodotFfi for StringName { impl_builtin_traits! { for StringName { + Default => string_name_construct_default; Clone => string_name_construct_copy; Drop => string_name_destroy; Eq => string_name_operator_equal; - Ord => string_name_operator_less; - } -} - -impl Default for StringName { - fn default() -> Self { - // Note: can't use from_sys_init(), as that calls the default constructor - - let mut uninit = std::mem::MaybeUninit::::uninit(); - - unsafe { - let self_ptr = (*uninit.as_mut_ptr()).sys_mut(); - sys::builtin_call! { - string_name_construct_default(self_ptr, std::ptr::null_mut()) - } - - uninit.assume_init() - } + // currently broken: https://github.com/godotengine/godot/issues/76218 + // Ord => string_name_operator_less; + Hash; } } @@ -123,52 +122,43 @@ impl fmt::Debug for StringName { } } -impl Hash for StringName { - fn hash(&self, state: &mut H) { - // TODO use Godot hash via codegen - // C++: internal::gdn_interface->variant_get_ptr_builtin_method(GDEXTENSION_VARIANT_TYPE_STRING_NAME, "hash", 171192809); +// ---------------------------------------------------------------------------------------------------------------------------------------------- +// Conversion from/into other string-types - self.to_string().hash(state) - } -} +impl_rust_string_conv!(StringName); impl From<&GodotString> for StringName { - fn from(s: &GodotString) -> Self { + fn from(string: &GodotString) -> Self { unsafe { Self::from_sys_init_default(|self_ptr| { let ctor = sys::builtin_fn!(string_name_from_string); - let args = [s.sys_const()]; + let args = [string.sys_const()]; ctor(self_ptr, args.as_ptr()); }) } } } -impl From for StringName -where - S: AsRef, -{ - fn from(s: S) -> Self { - let intermediate = GodotString::from(s.as_ref()); - Self::from(&intermediate) +impl From for StringName { + /// Converts this `GodotString` to a `StringName`. + /// + /// This is identical to `StringName::from(&string)`, and as such there is no performance benefit. + fn from(string: GodotString) -> Self { + Self::from(&string) } } -impl From<&StringName> for GodotString { - fn from(s: &StringName) -> Self { - unsafe { - Self::from_sys_init_default(|self_ptr| { - let ctor = sys::builtin_fn!(string_from_string_name); - let args = [s.sys_const()]; - ctor(self_ptr, args.as_ptr()); - }) - } +impl From<&NodePath> for StringName { + fn from(path: &NodePath) -> Self { + Self::from(GodotString::from(path)) } } -impl From<&StringName> for String { - fn from(s: &StringName) -> Self { - let intermediate = GodotString::from(s); - Self::from(&intermediate) +impl From for StringName { + /// Converts this `NodePath` to a `StringName`. + /// + /// This is identical to `StringName::from(&path)`, and as such there is no performance benefit. + fn from(path: NodePath) -> Self { + Self::from(GodotString::from(path)) } } diff --git a/itest/rust/src/lib.rs b/itest/rust/src/lib.rs index 145c62cf5..652528406 100644 --- a/itest/rust/src/lib.rs +++ b/itest/rust/src/lib.rs @@ -29,7 +29,7 @@ mod quaternion_test; mod rect2i_test; mod rid_test; mod singleton_test; -mod string_test; +mod string; mod transform2d_test; mod transform3d_test; mod utilities_test; diff --git a/itest/rust/src/string_test.rs b/itest/rust/src/string/godot_string_test.rs similarity index 65% rename from itest/rust/src/string_test.rs rename to itest/rust/src/string/godot_string_test.rs index ad1ab52e9..15d07cfe6 100644 --- a/itest/rust/src/string_test.rs +++ b/itest/rust/src/string/godot_string_test.rs @@ -4,8 +4,10 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +use std::collections::HashSet; + use crate::itest; -use godot::builtin::{GodotString, StringName}; +use godot::builtin::GodotString; // TODO use tests from godot-rust/gdnative @@ -24,6 +26,11 @@ fn string_conversion() { let back = String::from(&second); assert_eq!(string, back); + + let second = GodotString::from(string.clone()); + let back = String::from(second); + + assert_eq!(string, back); } #[itest] @@ -64,36 +71,27 @@ fn empty_string_chars() { assert_eq!(unsafe { s.chars_unchecked() }, &[]); } -// ---------------------------------------------------------------------------------------------------------------------------------------------- - #[itest] -fn string_name_conversion() { - let string = GodotString::from("some string"); - let name = StringName::from(&string); - let back = GodotString::from(&name); +fn string_chars() { + let string = String::from("some_string"); + let string_chars: Vec = string.chars().collect(); + let godot_string = GodotString::from(string); + let godot_string_chars: Vec = godot_string.chars_checked().to_vec(); - assert_eq!(string, back); + assert_eq!(godot_string_chars, string_chars); } #[itest] -fn string_name_default_construct() { - let name = StringName::default(); - let back = GodotString::from(&name); - - assert_eq!(back, GodotString::new()); -} - -#[itest(skip)] -fn string_name_eq_hash() { - // TODO -} - -#[itest(skip)] -fn string_name_ord() { - // TODO -} - -#[itest(skip)] -fn string_name_clone() { - // TODO +fn string_hash() { + let set: HashSet = [ + "string_1", + "SECOND string! :D", + "emoji time: 😎", + r#"got/!()%)=!"/]}¡[$½{¥¡}@£symbol characters"#, + "some garbageTƉ馧쟻�韂󥢛ꮛ૎ཾ̶D@/8ݚ򹾴-䌗򤷨񄣷8", + ] + .into_iter() + .map(GodotString::from) + .collect(); + assert_eq!(set.len(), 5); } diff --git a/itest/rust/src/string/mod.rs b/itest/rust/src/string/mod.rs new file mode 100644 index 000000000..e5a006e21 --- /dev/null +++ b/itest/rust/src/string/mod.rs @@ -0,0 +1,9 @@ +/* + * 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/. + */ + +mod godot_string_test; +mod node_path_test; +mod string_name_test; diff --git a/itest/rust/src/string/node_path_test.rs b/itest/rust/src/string/node_path_test.rs new file mode 100644 index 000000000..285eee0cb --- /dev/null +++ b/itest/rust/src/string/node_path_test.rs @@ -0,0 +1,65 @@ +/* + * 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 std::collections::HashSet; + +use crate::itest; +use godot::builtin::{GodotString, NodePath}; + +#[itest] +fn node_path_default() { + let name = NodePath::default(); + let back = GodotString::from(&name); + + assert_eq!(back, GodotString::new()); +} + +#[itest] +fn node_path_conversion() { + let string = GodotString::from("some string"); + let name = NodePath::from(&string); + let back = GodotString::from(&name); + + assert_eq!(string, back); + + let second = NodePath::from(string.clone()); + let back = GodotString::from(second); + + assert_eq!(string, back); +} +#[itest] +fn node_path_equality() { + let string = NodePath::from("some string"); + let second = NodePath::from("some string"); + let different = NodePath::from("some"); + + assert_eq!(string, second); + assert_ne!(string, different); +} + +#[itest] +fn node_path_clone() { + let first = NodePath::from("some string"); + #[allow(clippy::redundant_clone)] + let cloned = first.clone(); + + assert_eq!(first, cloned); +} + +#[itest] +fn node_path_hash() { + let set: HashSet = [ + "string_1", + "SECOND string! :D", + "emoji time: 😎", + r#"got/!()%)=!"/]}¡[$½{¥¡}@£symbol characters"#, + "some garbageTƉ馧쟻�韂󥢛ꮛ૎ཾ̶D@/8ݚ򹾴-䌗򤷨񄣷8", + ] + .into_iter() + .map(NodePath::from) + .collect(); + assert_eq!(set.len(), 5); +} diff --git a/itest/rust/src/string/string_name_test.rs b/itest/rust/src/string/string_name_test.rs new file mode 100644 index 000000000..84d6fbd29 --- /dev/null +++ b/itest/rust/src/string/string_name_test.rs @@ -0,0 +1,111 @@ +/* + * 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 std::collections::HashSet; + +use crate::itest; +use godot::builtin::{GodotString, NodePath, StringName}; + +#[itest] +fn string_name_default() { + let name = StringName::default(); + let back = GodotString::from(&name); + + assert_eq!(back, GodotString::new()); +} + +#[itest] +fn string_name_conversion() { + let string = GodotString::from("some string"); + let name = StringName::from(&string); + let back = GodotString::from(&name); + + assert_eq!(string, back); + + let second = StringName::from(string.clone()); + let back = GodotString::from(second); + + assert_eq!(string, back); +} + +#[itest] +fn string_name_node_path_conversion() { + let string = StringName::from("some string"); + let name = NodePath::from(&string); + let back = StringName::from(&name); + + assert_eq!(string, back); + + let second = NodePath::from(string.clone()); + let back = StringName::from(second); + + assert_eq!(string, back); +} + +#[itest] +fn string_name_equality() { + let string = StringName::from("some string"); + let second = StringName::from("some string"); + let different = StringName::from("some"); + + assert_eq!(string, second); + assert_ne!(string, different); +} + +// TODO: add back in when ordering StringNames is fixed +#[itest(skip)] +fn string_ordering() { + let _low = StringName::from("Alpha"); + let _high = StringName::from("Beta"); + /* + assert!(low < high); + assert!(low <= high); + assert!(high > low); + assert!(high >= low); + */ +} + +#[itest] +fn string_name_clone() { + let first = StringName::from("some string"); + #[allow(clippy::redundant_clone)] + let cloned = first.clone(); + + assert_eq!(first, cloned); +} + +#[itest] +fn string_name_hash() { + let set: HashSet = [ + "string_1", + "SECOND string! :D", + "emoji time: 😎", + r#"got/!()%)=!"/]}¡[$½{¥¡}@£symbol characters"#, + "some garbageTƉ馧쟻�韂󥢛ꮛ૎ཾ̶D@/8ݚ򹾴-䌗򤷨񄣷8", + ] + .into_iter() + .map(StringName::from) + .collect(); + assert_eq!(set.len(), 5); +} + +#[itest] +fn string_name_length() { + let string = "hello!"; + let name = StringName::from(string); + assert_eq!(name.len(), string.len()); + + let empty = StringName::default(); + assert_eq!(empty.len(), 0); +} + +#[itest] +fn string_name_is_empty() { + let name = StringName::from("hello!"); + assert!(!name.is_empty()); + let empty = StringName::default(); + assert!(empty.is_empty()); +}