use std::fmt;
use godot_ffi as sys;
use sys::{ExtVariantType, GodotFfi, ffi_methods};
use crate::builtin::{Encoding, GString, NodePath, Variant, inner};
use crate::meta::AsArg;
use crate::meta::error::StringError;
use crate::{impl_shared_string_api, meta};
#[repr(transparent)]
pub struct StringName {
opaque: sys::types::OpaqueStringName,
}
impl StringName {
fn from_opaque(opaque: sys::types::OpaqueStringName) -> Self {
Self { opaque }
}
pub fn try_from_bytes(bytes: &[u8], encoding: Encoding) -> Result<Self, StringError> {
Self::try_from_bytes_with_nul_check(bytes, encoding, true)
}
pub fn try_from_cstr(cstr: &std::ffi::CStr, encoding: Encoding) -> Result<Self, StringError> {
if encoding == Encoding::Latin1 {
let is_static = sys::conv::SYS_FALSE;
let s = unsafe {
Self::new_with_string_uninit(|string_ptr| {
let ctor = sys::interface_fn!(string_name_new_with_latin1_chars);
ctor(
string_ptr,
cstr.as_ptr() as *const std::ffi::c_char,
is_static,
);
})
};
return Ok(s);
}
Self::try_from_bytes_with_nul_check(cstr.to_bytes(), encoding, false)
}
fn try_from_bytes_with_nul_check(
bytes: &[u8],
encoding: Encoding,
check_nul: bool,
) -> Result<Self, StringError> {
match encoding {
Encoding::Ascii => {
if !bytes.is_ascii() {
Err(StringError::new("invalid ASCII"))
} else if check_nul && bytes.contains(&0) {
Err(StringError::new("intermediate NUL byte in ASCII string"))
} else {
let ascii = unsafe { std::str::from_utf8_unchecked(bytes) };
Ok(Self::from(ascii))
}
}
Encoding::Latin1 => {
GString::try_from_bytes_with_nul_check(bytes, Encoding::Latin1, check_nul)
.map(|s| Self::from(&s))
}
Encoding::Utf8 => {
let utf8 = std::str::from_utf8(bytes);
utf8.map(StringName::from)
.map_err(|e| StringError::with_source("invalid UTF-8", e))
}
}
}
#[doc(alias = "length")]
pub fn len(&self) -> usize {
self.as_inner().length() as usize
}
crate::declare_hash_u32_method! {
}
pub fn transient_ord(&self) -> TransientStringNameOrd<'_> {
TransientStringNameOrd(self)
}
#[cfg(since_api = "4.5")] #[cfg_attr(published_docs, doc(cfg(since_api = "4.5")))]
pub fn chars(&self) -> &[char] {
let gstring = GString::from(self);
let (ptr, len) = gstring.raw_slice();
if ptr.is_null() {
return &[];
}
unsafe { std::slice::from_raw_parts(ptr, len) }
}
ffi_methods! {
type sys::GDExtensionStringNamePtr = *mut Opaque;
fn new_from_string_sys = new_from_sys;
fn new_with_string_uninit = new_with_uninit;
fn string_sys = sys;
fn string_sys_mut = sys_mut;
}
pub(crate) fn into_owned_string_sys(self) -> sys::GDExtensionStringNamePtr {
sys::static_assert_eq_size_align!(StringName, sys::types::OpaqueStringName);
let leaked = Box::into_raw(Box::new(self));
leaked.cast()
}
pub(crate) unsafe fn from_owned_string_sys(ptr: sys::GDExtensionStringNamePtr) -> Self {
sys::static_assert_eq_size_align!(StringName, sys::types::OpaqueStringName);
let ptr = ptr.cast::<Self>();
let boxed = unsafe { Box::from_raw(ptr) };
*boxed
}
pub(crate) unsafe fn borrow_string_sys<'a>(
ptr: sys::GDExtensionConstStringNamePtr,
) -> &'a StringName {
unsafe {
sys::static_assert_eq_size_align!(StringName, sys::types::OpaqueStringName);
&*(ptr.cast::<StringName>())
}
}
pub(crate) unsafe fn borrow_string_sys_mut<'a>(
ptr: sys::GDExtensionStringNamePtr,
) -> &'a mut StringName {
unsafe {
sys::static_assert_eq_size_align!(StringName, sys::types::OpaqueStringName);
&mut *(ptr.cast::<StringName>())
}
}
#[doc(hidden)]
pub fn as_inner(&self) -> inner::InnerStringName<'_> {
inner::InnerStringName::from_outer(self)
}
#[doc(hidden)] pub fn __cstr(c_str: &'static std::ffi::CStr) -> Self {
let is_static = false;
Self::__cstr_with_static(c_str, is_static)
}
#[doc(hidden)] pub fn __cstr_with_static(c_str: &'static std::ffi::CStr, is_static: bool) -> Self {
unsafe {
Self::new_with_string_uninit(|ptr| {
sys::interface_fn!(string_name_new_with_latin1_chars)(
ptr,
c_str.as_ptr(),
sys::conv::bool_to_sys(is_static),
)
})
}
}
}
unsafe impl GodotFfi for StringName {
const VARIANT_TYPE: ExtVariantType = ExtVariantType::Concrete(sys::VariantType::STRING_NAME);
ffi_methods! { type sys::GDExtensionTypePtr = *mut Opaque; .. }
}
meta::impl_godot_as_self!(StringName: ByRef);
impl_builtin_traits! {
for StringName {
Default => string_name_construct_default;
Clone => string_name_construct_copy;
Drop => string_name_destroy;
Eq => string_name_operator_equal;
Hash;
}
}
impl_shared_string_api! {
builtin: StringName,
builtin_mod: string_name,
}
impl PartialEq<&str> for StringName {
#[cfg(since_api = "4.5")] #[cfg_attr(published_docs, doc(cfg(since_api = "4.5")))]
fn eq(&self, other: &&str) -> bool {
self.chars().iter().copied().eq(other.chars())
}
#[cfg(before_api = "4.5")] #[cfg_attr(published_docs, doc(cfg(before_api = "4.5")))]
fn eq(&self, other: &&str) -> bool {
GString::from(self) == *other
}
}
impl fmt::Display for StringName {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = GString::from(self);
<GString as fmt::Display>::fmt(&s, f)
}
}
impl fmt::Debug for StringName {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let string = GString::from(self);
write!(f, "&\"{string}\"")
}
}
unsafe impl Sync for StringName {}
unsafe impl Send for StringName {}
impl_rust_string_conv!(StringName);
impl From<&str> for StringName {
fn from(string: &str) -> Self {
let utf8 = string.as_bytes();
unsafe {
Self::new_with_string_uninit(|ptr| {
sys::interface_fn!(string_name_new_with_utf8_chars_and_len)(
ptr,
utf8.as_ptr() as *const std::ffi::c_char,
utf8.len() as i64,
);
})
}
}
}
impl From<&String> for StringName {
fn from(value: &String) -> Self {
value.as_str().into()
}
}
impl From<&GString> for StringName {
fn from(string: &GString) -> Self {
unsafe {
Self::new_with_uninit(|self_ptr| {
let ctor = sys::builtin_fn!(string_name_from_string);
let args = [string.sys()];
ctor(self_ptr, args.as_ptr());
})
}
}
}
impl From<&NodePath> for StringName {
fn from(path: &NodePath) -> Self {
Self::from(&GString::from(path))
}
}
pub struct TransientStringNameOrd<'a>(&'a StringName);
impl PartialEq for TransientStringNameOrd<'_> {
fn eq(&self, other: &Self) -> bool {
self.0 == other.0
}
}
impl Eq for TransientStringNameOrd<'_> {}
impl PartialOrd for TransientStringNameOrd<'_> {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for TransientStringNameOrd<'_> {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
let op_less = |lhs, rhs| unsafe {
let mut result = false;
sys::builtin_call! {
string_name_operator_less(lhs, rhs, result.sys_mut())
}
result
};
let self_ptr = self.0.sys();
let other_ptr = other.0.sys();
if op_less(self_ptr, other_ptr) {
std::cmp::Ordering::Less
} else if op_less(other_ptr, self_ptr) {
std::cmp::Ordering::Greater
} else if self.eq(other) {
std::cmp::Ordering::Equal
} else {
panic!(
"Godot provides inconsistent StringName ordering for \"{}\" and \"{}\"",
self.0, other.0
);
}
}
}
#[cfg(feature = "serde")] #[cfg_attr(published_docs, doc(cfg(feature = "serde")))]
mod serialize {
use std::fmt::Formatter;
use serde::de::{Error, Visitor};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use super::*;
#[cfg_attr(published_docs, doc(cfg(feature = "serde")))]
impl Serialize for StringName {
#[inline]
fn serialize<S>(
&self,
serializer: S,
) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error>
where
S: Serializer,
{
serializer.serialize_str(&self.to_string())
}
}
#[cfg_attr(published_docs, doc(cfg(feature = "serde")))]
impl<'de> Deserialize<'de> for StringName {
#[inline]
fn deserialize<D>(deserializer: D) -> Result<Self, <D as Deserializer<'de>>::Error>
where
D: Deserializer<'de>,
{
struct StringNameVisitor;
impl Visitor<'_> for StringNameVisitor {
type Value = StringName;
fn expecting(&self, formatter: &mut Formatter) -> fmt::Result {
formatter.write_str("a StringName")
}
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
where
E: Error,
{
Ok(StringName::from(s))
}
}
deserializer.deserialize_str(StringNameVisitor)
}
}
}
#[macro_export]
macro_rules! static_sname {
($str:literal) => {{
use std::sync::OnceLock;
let c_str: &'static std::ffi::CStr = $str;
static SNAME: OnceLock<StringName> = OnceLock::new();
SNAME.get_or_init(|| StringName::__cstr_with_static(c_str, true))
}};
}