use std::borrow::Cow;
use std::fmt::Display;
use godot_ffi as sys;
use crate::builtin::{CowStr, GString, StringName, VariantType};
use crate::global::godot_str;
use crate::meta::{ClassId, GodotType};
use crate::obj::EngineEnum as _;
use crate::registry::info::{
ParamMetadata, PropertyHint, PropertyHintInfo, PropertyInfo, PropertyUsageFlags,
};
#[non_exhaustive]
#[derive(Clone, Debug)]
pub enum GodotShape {
Variant,
Builtin {
variant_type: VariantType,
metadata: ParamMetadata,
},
Class {
class_id: ClassId,
heritage: ClassHeritage,
is_nullable: bool,
},
TypedArray {
element: GodotElementShape,
},
TypedDictionary {
key: GodotElementShape,
value: GodotElementShape,
},
Enum {
variant_type: VariantType,
enumerators: Cow<'static, [EnumeratorShape]>,
godot_name: Option<CowStr>,
is_bitfield: bool,
},
Custom {
variant_type: VariantType,
var_hint: PropertyHintInfo,
export_hint: PropertyHintInfo,
class_name: Option<CowStr>,
usage_flags: PropertyUsageFlags,
metadata: ParamMetadata,
},
}
impl GodotShape {
pub fn of_builtin<T: GodotType>() -> Self {
match crate::meta::ffi_variant_type::<T>() {
sys::ExtVariantType::Variant => Self::Variant,
ext => Self::Builtin {
variant_type: ext.variant_as_nil(),
metadata: T::default_metadata(),
},
}
}
pub fn param_metadata(&self) -> ParamMetadata {
match self {
Self::Builtin { metadata, .. } | Self::Custom { metadata, .. } => *metadata,
Self::Class {
is_nullable: false, ..
} => ParamMetadata::OBJECT_IS_REQUIRED,
_ => ParamMetadata::NONE,
}
}
pub fn variant_type(&self) -> VariantType {
match self {
Self::Variant => VariantType::NIL,
Self::Builtin { variant_type, .. } => *variant_type,
Self::Class { .. } => VariantType::OBJECT,
Self::Enum { variant_type, .. } => *variant_type,
Self::Custom { variant_type, .. } => *variant_type,
Self::TypedArray { .. } => VariantType::ARRAY,
Self::TypedDictionary { .. } => VariantType::DICTIONARY,
}
}
pub fn var_hint(&self) -> PropertyHintInfo {
match self {
Self::Variant | Self::Builtin { .. } | Self::Class { .. } => PropertyHintInfo::none(),
Self::Enum {
godot_name,
enumerators,
is_bitfield,
..
} => enum_hint_info(enumerators, *is_bitfield, godot_name.is_some()),
Self::Custom { var_hint, .. } => var_hint.clone(),
Self::TypedArray {
element: element_shape,
} => PropertyHintInfo {
hint: PropertyHint::ARRAY_TYPE,
hint_string: GString::from(&element_shape.element_godot_type_name()),
},
Self::TypedDictionary {
key: key_shape,
value: value_shape,
} => {
if let Some(hint) = PropertyHint::try_from_ord(38) {
PropertyHintInfo {
hint,
hint_string: godot_str!(
"{};{}",
key_shape.element_godot_type_name(),
value_shape.element_godot_type_name()
),
}
} else {
let _unused = (key_shape, value_shape);
PropertyHintInfo::none()
}
}
}
}
pub fn export_hint(&self) -> PropertyHintInfo {
match self {
Self::Variant => PropertyHintInfo::none(),
Self::Builtin { variant_type, .. } => {
if sys::GdextBuild::since_api("4.3") {
if let Some(elem_vtype) = packed_element_variant_type(*variant_type) {
return PropertyHintInfo {
hint: PropertyHint::TYPE_STRING,
hint_string: GString::from(&format_elements_untyped(elem_vtype)),
};
}
PropertyHintInfo::none()
} else {
PropertyHintInfo {
hint: PropertyHint::NONE,
hint_string: GString::from(variant_type.godot_type_name()),
}
}
}
Self::Class {
class_id, heritage, ..
} => match heritage {
ClassHeritage::Node => PropertyHintInfo {
hint: PropertyHint::NODE_TYPE,
hint_string: class_id.to_gstring(),
},
ClassHeritage::Resource => PropertyHintInfo {
hint: PropertyHint::RESOURCE_TYPE,
hint_string: class_id.to_gstring(),
},
ClassHeritage::DynResource { implementors } => PropertyHintInfo {
hint: PropertyHint::RESOURCE_TYPE,
hint_string: GString::from(&join_class_ids(implementors)),
},
ClassHeritage::Other => PropertyHintInfo::none(),
},
Self::TypedArray {
element: element_shape,
} => PropertyHintInfo {
hint: PropertyHint::TYPE_STRING,
hint_string: GString::from(&element_shape.element_type_string()),
},
Self::TypedDictionary {
key: key_shape,
value: value_shape,
} => {
if sys::GdextBuild::since_api("4.4") {
PropertyHintInfo {
hint: PropertyHint::TYPE_STRING,
hint_string: godot_str!(
"{};{}",
key_shape.element_type_string(),
value_shape.element_type_string()
),
}
} else {
PropertyHintInfo::none()
}
}
Self::Enum {
enumerators,
is_bitfield,
..
} => enum_hint_info(enumerators, *is_bitfield, false),
Self::Custom { export_hint, .. } => export_hint.clone(),
}
}
pub(crate) fn class_name_or_none(&self) -> StringName {
match self {
Self::Variant
| Self::Builtin { .. }
| Self::TypedArray { .. }
| Self::TypedDictionary { .. } => StringName::default(),
Self::Class { class_id, .. } => class_id.to_string_name(),
Self::Enum { godot_name, .. } => match godot_name {
Some(name) => StringName::from(name.as_ref()),
None => StringName::default(),
},
Self::Custom { class_name, .. } => match class_name {
Some(name) => StringName::from(name.as_ref()),
None => StringName::default(),
},
}
}
pub fn usage_flags(&self) -> PropertyUsageFlags {
match self {
Self::Variant => PropertyUsageFlags::NIL_IS_VARIANT,
Self::Builtin { .. }
| Self::Class { .. }
| Self::TypedArray { .. }
| Self::TypedDictionary { .. } => PropertyUsageFlags::NONE,
Self::Enum {
godot_name,
is_bitfield,
..
} => match (godot_name, *is_bitfield) {
(Some(_), true) => PropertyUsageFlags::CLASS_IS_BITFIELD,
(Some(_), false) => PropertyUsageFlags::CLASS_IS_ENUM,
(None, _) => PropertyUsageFlags::NONE, },
Self::Custom { usage_flags, .. } => *usage_flags,
}
}
pub fn to_method_signature_property(&self, property_name: &str) -> PropertyInfo {
let hint_info = self.var_hint();
self.to_property(property_name, hint_info, PropertyUsageFlags::DEFAULT)
}
pub fn to_var_property(&self, property_name: &str) -> PropertyInfo {
let hint_info = self.var_hint();
self.to_property(property_name, hint_info, PropertyUsageFlags::NONE)
}
pub fn to_export_property(&self, property_name: &str) -> PropertyInfo {
let hint_info = self.export_hint();
self.to_property(property_name, hint_info, PropertyUsageFlags::DEFAULT)
}
fn to_property(
&self,
property_name: &str,
hint_info: PropertyHintInfo,
base_usage: PropertyUsageFlags,
) -> PropertyInfo {
use crate::builtin::StringName;
let class_name = self.class_name_or_none();
let variant_type = self.variant_type();
let usage = base_usage | self.usage_flags();
PropertyInfo {
variant_type,
class_name,
property_name: StringName::from(property_name),
hint_info,
usage,
}
}
}
#[derive(Clone, Debug)]
pub enum ClassHeritage {
Node,
Resource,
DynResource {
implementors: Vec<ClassId>,
},
Other,
}
impl ClassHeritage {
pub fn export_property_hint(&self) -> PropertyHint {
match self {
Self::Resource | Self::DynResource { .. } => PropertyHint::RESOURCE_TYPE,
Self::Node => PropertyHint::NODE_TYPE,
Self::Other => PropertyHint::NONE,
}
}
}
#[non_exhaustive]
#[derive(Clone, Debug)]
pub enum GodotElementShape {
Variant,
Builtin {
variant_type: VariantType,
},
Class {
class_id: ClassId,
heritage: ClassHeritage,
},
Enum {
variant_type: VariantType,
enumerators: Cow<'static, [EnumeratorShape]>,
godot_name: Option<CowStr>,
is_bitfield: bool,
},
Custom {
variant_type: VariantType,
var_hint: PropertyHintInfo,
export_hint: PropertyHintInfo,
class_name: Option<CowStr>,
usage_flags: PropertyUsageFlags,
},
}
impl GodotElementShape {
#[rustfmt::skip]
pub(crate) fn new(outer: GodotShape) -> Self {
type GShape = GodotShape;
match outer {
GShape::Variant
=> Self::Variant,
GShape::Builtin { variant_type, .. }
=> Self::Builtin { variant_type },
GShape::Class { class_id, heritage, .. }
=> Self::Class { class_id, heritage },
GShape::Enum { variant_type, enumerators, godot_name, is_bitfield }
=> Self::Enum { variant_type, enumerators, godot_name, is_bitfield },
GShape::Custom { variant_type, var_hint, export_hint, class_name, usage_flags, .. }
=> Self::Custom { variant_type, var_hint, export_hint, class_name, usage_flags },
GShape::TypedArray { .. } |
GShape::TypedDictionary { .. } => panic!("nested shapes cannot be typed arrays/dictionaries")
}
}
#[rustfmt::skip]
pub fn into_outer(self) -> GodotShape {
type G = GodotShape;
match self {
Self::Variant
=> G::Variant,
Self::Builtin { variant_type }
=> G::Builtin { variant_type, metadata: ParamMetadata::NONE },
Self::Class { class_id, heritage }
=> G::Class { class_id, heritage, is_nullable: true },
Self::Enum { variant_type, enumerators, godot_name, is_bitfield }
=> G::Enum { variant_type, enumerators, godot_name, is_bitfield },
Self::Custom { variant_type, var_hint, export_hint, class_name, usage_flags}
=> G::Custom { variant_type, var_hint, export_hint, class_name, usage_flags, metadata: ParamMetadata::NONE },
}
}
pub(crate) fn element_godot_type_name(&self) -> String {
match self {
Self::Variant => VariantType::NIL.godot_type_name().to_string(),
Self::Builtin { variant_type } => variant_type.godot_type_name().to_string(),
Self::Class { class_id, .. } => class_id.to_cow_str().to_string(),
Self::Enum {
godot_name,
variant_type,
..
} => match godot_name {
Some(name) => name.to_string(),
None => variant_type.godot_type_name().to_string(),
},
Self::Custom { variant_type, .. } => variant_type.godot_type_name().to_string(),
}
}
pub(crate) fn element_type_string(&self) -> String {
match self {
Self::Variant
| Self::Builtin {
variant_type: VariantType::NIL,
} => {
format_elements_untyped(VariantType::NIL)
}
Self::Builtin { variant_type } => {
if sys::GdextBuild::since_api("4.3") {
format_elements_untyped(*variant_type)
} else {
format!("{}:{}", variant_type.ord(), variant_type.godot_type_name())
}
}
Self::Class { class_id, heritage } => {
let export_hint = heritage.export_property_hint();
sys::strict_assert_ne!(
export_hint,
PropertyHint::NONE,
"element_type_string() should only be called for exportable object classes (Resource or Node), \
but got ClassAncestor::Other for class `{}`",
class_id.to_cow_str()
);
let hint_string = match heritage {
ClassHeritage::DynResource { implementors } => join_class_ids(implementors),
_ => class_id.to_cow_str().to_string(),
};
format_elements_typed(VariantType::OBJECT, export_hint, &hint_string)
}
Self::Enum { .. } => {
let outer = self.clone().into_outer(); let variant_type = outer.variant_type();
let info = outer.export_hint();
format_elements_typed(variant_type, info.hint, &info.hint_string)
}
Self::Custom {
variant_type,
var_hint,
..
} => format_elements_typed(*variant_type, var_hint.hint, &var_hint.hint_string),
}
}
}
#[derive(Clone, Debug)]
pub struct EnumeratorShape {
pub name: CowStr,
pub value: Option<i64>,
}
impl EnumeratorShape {
pub const fn new_int(name: &'static str, value: i64) -> Self {
Self {
name: Cow::Borrowed(name),
value: Some(value),
}
}
pub const fn new_string(name: &'static str) -> Self {
Self {
name: Cow::Borrowed(name),
value: None,
}
}
}
pub(crate) fn format_elements_typed(
variant_type: VariantType,
hint: PropertyHint,
hint_string: impl std::fmt::Display,
) -> String {
if hint == PropertyHint::NONE {
format!("{}:", variant_type.ord())
} else {
format!("{}/{}:{}", variant_type.ord(), hint.ord(), hint_string)
}
}
fn format_elements_untyped(variant_type: VariantType) -> String {
format!("{}:", variant_type.ord())
}
fn enum_hint_info(
enumerators: &[EnumeratorShape],
is_bitfield: bool,
is_engine_enum: bool,
) -> PropertyHintInfo {
if is_engine_enum && sys::GdextBuild::before_api("4.7") {
return PropertyHintInfo::none();
}
let hint = if is_bitfield {
PropertyHint::FLAGS
} else {
PropertyHint::ENUM
};
PropertyHintInfo {
hint,
hint_string: GString::from(&format_hint_string(enumerators)),
}
}
fn format_hint_string(enumerators: &[EnumeratorShape]) -> String {
enumerators
.iter()
.map(|e| format_hint_entry(&e.name, e.value))
.collect::<Vec<_>>()
.join(",")
}
pub(crate) fn format_hint_entry(name: &str, value: Option<impl Display>) -> String {
match value {
Some(v) => format!("{name}:{v}"),
None => name.to_string(),
}
}
#[rustfmt::skip]
pub(crate) fn packed_element_variant_type(packed_vtype: VariantType) -> Option<VariantType> {
match packed_vtype {
| VariantType::PACKED_BYTE_ARRAY
| VariantType::PACKED_INT32_ARRAY
| VariantType::PACKED_INT64_ARRAY => Some(VariantType::INT),
| VariantType::PACKED_FLOAT32_ARRAY
| VariantType::PACKED_FLOAT64_ARRAY => Some(VariantType::FLOAT),
| VariantType::PACKED_STRING_ARRAY => Some(VariantType::STRING),
| VariantType::PACKED_VECTOR2_ARRAY => Some(VariantType::VECTOR2),
| VariantType::PACKED_VECTOR3_ARRAY => Some(VariantType::VECTOR3),
#[cfg(since_api = "4.3")]
| VariantType::PACKED_VECTOR4_ARRAY => Some(VariantType::VECTOR4),
| VariantType::PACKED_COLOR_ARRAY => Some(VariantType::COLOR),
_ => None,
}
}
fn join_class_ids(class_ids: &[ClassId]) -> String {
class_ids
.iter()
.map(|id| id.to_cow_str().to_string())
.collect::<Vec<_>>()
.join(",")
}